From 8632c77d68e4129effef60b1ad8d839995f9134b Mon Sep 17 00:00:00 2001 From: quantum-x Date: Thu, 30 Jun 2022 16:47:08 +0200 Subject: [PATCH 01/26] 1342 add mifare infineon (#1346) * Adding MIFARE 1K Infineon Compatibility As per Issue #1342, MIFARE Classic 1K Cards from NXP have the SAK value of 0x08. MIFARE Classic 1K Cards from Infineon have an SAK value of 0x88. Adding the SAK values accordingly so that Infineon tags are properly handled. --- lib/nfc_protocols/mifare_classic.c | 4 ++-- lib/nfc_protocols/mifare_common.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc_protocols/mifare_classic.c index b44f77cb..9dbfd9d0 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc_protocols/mifare_classic.c @@ -198,7 +198,7 @@ static bool mf_classic_is_allowed_access( bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { UNUSED(ATQA1); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88)) { return true; } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { return true; @@ -219,7 +219,7 @@ bool mf_classic_get_type( furi_assert(reader); memset(reader, 0, sizeof(MfClassicReader)); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88)) { reader->type = MfClassicType1k; } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { reader->type = MfClassicType4k; diff --git a/lib/nfc_protocols/mifare_common.c b/lib/nfc_protocols/mifare_common.c index 0be24b51..78094cdd 100644 --- a/lib/nfc_protocols/mifare_common.c +++ b/lib/nfc_protocols/mifare_common.c @@ -6,7 +6,7 @@ MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { type = MifareTypeUltralight; } else if( - ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) || + ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88)) || ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18))) { type = MifareTypeClassic; } else if(ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20) { From b3767d143f47cfbb28194545ba13458c2680e301 Mon Sep 17 00:00:00 2001 From: hedger Date: Thu, 30 Jun 2022 19:06:12 +0300 Subject: [PATCH 02/26] fbt: fixes (#1352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: added --git-tasks; fixed typos * fbt: fixed --extra-int-apps handling; scripts: moved storage.py & selfupdate.py to App() framework * fbt: changed pseudo-builders to PhonyTargets with commands; added link to latest build dir as build/latest * fbt: Restored old ep git handling * fbt: dropped git tasks & dirlink.py * fbt: removed extra quoting in fbt.cmd * docs: added flash_usb to ReadMe.md Co-authored-by: あく --- ReadMe.md | 8 +- SConstruct | 88 +++++++------------- assets/SConscript | 48 +++++------ documentation/fbt.md | 11 +-- fbt | 14 +++- fbt.cmd | 9 +- fbt_options.py | 8 +- firmware.scons | 62 +++++++++++--- scripts/flipper/app.py | 1 - scripts/flipper/utils/cdc.py | 17 ++++ scripts/selfupdate.py | 114 +++++++++----------------- scripts/storage.py | 89 ++++++++++---------- site_scons/fbt/util.py | 27 +++++- site_scons/site_tools/fbt_apps.py | 5 +- site_scons/site_tools/fbt_dist.py | 37 ++++++--- site_scons/site_tools/gdb.py | 16 ---- site_scons/site_tools/openocd.py | 10 +-- site_scons/site_tools/sconsmodular.py | 11 +++ 18 files changed, 312 insertions(+), 263 deletions(-) create mode 100644 scripts/flipper/utils/cdc.py diff --git a/ReadMe.md b/ReadMe.md index 6a3eed37..e8b4daab 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -27,9 +27,15 @@ They both must be flashed in order described. ## With offline update package +With Flipper attached over USB: + +`./fbt --with-updater flash_usb` + +Just building the package: + `./fbt --with-updater updater_package` -Copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app. +To update, copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app. ## With STLink diff --git a/SConstruct b/SConstruct index 312b23a6..7e8fc896 100644 --- a/SConstruct +++ b/SConstruct @@ -17,7 +17,6 @@ fbt_variables = SConscript("site_scons/commandline.scons") cmd_environment = Environment(tools=[], variables=fbt_variables) Help(fbt_variables.GenerateHelpText(cmd_environment)) - # Building basic environment - tools, utility methods, cross-compilation # settings, gcc flags for Cortex-M4, basic builders and more coreenv = SConscript( @@ -43,7 +42,6 @@ firmware_out = distenv.AddFwProject( fw_env_key="FW_ENV", ) - # If enabled, initialize updater-related targets if GetOption("fullenv"): updater_out = distenv.AddFwProject( @@ -71,91 +69,67 @@ if GetOption("fullenv"): "--splash", distenv.subst("assets/slideshow/$UPDATE_SPLASH"), ] - selfupdate_dist = distenv.DistBuilder( - "selfupdate.pseudo", + + selfupdate_dist = distenv.DistCommand( + "updater_package", (distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"]), DIST_EXTRA=dist_arguments, ) - distenv.Pseudo("selfupdate.pseudo") - AlwaysBuild(selfupdate_dist) - Alias("updater_package", selfupdate_dist) # Updater debug - debug_updater_elf = distenv.AddDebugTarget(updater_out, False) - Alias("updater_debug", debug_updater_elf) + distenv.AddDebugTarget("updater_debug", updater_out, False) # Installation over USB & CLI usb_update_package = distenv.UsbInstall( - "usbinstall.flag", - (distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"], selfupdate_dist), + "#build/usbinstall.flag", + ( + distenv["DIST_DEPENDS"], + firmware_out["FW_RESOURCES"], + selfupdate_dist, + ), ) if distenv["FORCE"]: - AlwaysBuild(usb_update_package) - Depends(usb_update_package, selfupdate_dist) - Alias("flash_usb", usb_update_package) - + distenv.AlwaysBuild(usb_update_package) + distenv.Depends(usb_update_package, selfupdate_dist) + distenv.Alias("flash_usb", usb_update_package) # Target for copying & renaming binaries to dist folder -basic_dist = distenv.DistBuilder("dist.pseudo", distenv["DIST_DEPENDS"]) -distenv.Pseudo("dist.pseudo") -AlwaysBuild(basic_dist) -Alias("fw_dist", basic_dist) -Default(basic_dist) - +basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"]) +distenv.Default(basic_dist) # Target for bundling core2 package for qFlipper copro_dist = distenv.CoproBuilder( - Dir("assets/core2_firmware"), + distenv.Dir("assets/core2_firmware"), [], ) -AlwaysBuild(copro_dist) -Alias("copro_dist", copro_dist) - +distenv.Alias("copro_dist", copro_dist) # Debugging firmware - -debug_fw_elf = distenv.AddDebugTarget(firmware_out) -Alias("debug", debug_fw_elf) - - +distenv.AddDebugTarget("debug", firmware_out) # Debug alien elf -debug_other = distenv.GDBPy( - "debugother.pseudo", - None, +distenv.PhonyTarget( + "debug_other", + "$GDBPYCOM", GDBPYOPTS= # '-ex "source ${ROOT_DIR.abspath}/debug/FreeRTOS/FreeRTOS.py" ' '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ', ) -distenv.Pseudo("debugother.pseudo") -AlwaysBuild(debug_other) -Alias("debug_other", debug_other) - # Just start OpenOCD -openocd = distenv.OOCDCommand("openocd.pseudo", []) -distenv.Pseudo("openocd.pseudo") -AlwaysBuild(openocd) -Alias("openocd", openocd) - +distenv.PhonyTarget( + "openocd", + "${OPENOCDCOM}", +) # Linter -lint_check = distenv.Command( - "lint.check.pseudo", - [], - "${PYTHON3} scripts/lint.py check $LINT_SOURCES", +distenv.PhonyTarget( + "lint", + "${PYTHON3} scripts/lint.py check ${LINT_SOURCES}", LINT_SOURCES=firmware_out["LINT_SOURCES"], ) -distenv.Pseudo("lint.check.pseudo") -AlwaysBuild(lint_check) -Alias("lint", lint_check) - -lint_format = distenv.Command( - "lint.format.pseudo", - [], - "${PYTHON3} scripts/lint.py format $LINT_SOURCES", +distenv.PhonyTarget( + "format", + "${PYTHON3} scripts/lint.py format ${LINT_SOURCES}", LINT_SOURCES=firmware_out["LINT_SOURCES"], ) -distenv.Pseudo("lint.format.pseudo") -AlwaysBuild(lint_format) -Alias("format", lint_format) diff --git a/assets/SConscript b/assets/SConscript index e70208ab..d5465534 100644 --- a/assets/SConscript +++ b/assets/SConscript @@ -28,38 +28,38 @@ icons_src = assetsenv.GlobRecursive("*.png", "icons") icons_src += assetsenv.GlobRecursive("frame_rate", "icons") icons = assetsenv.IconBuilder(Dir("compiled"), Dir("#/assets/icons")) -Depends(icons, icons_src) -Alias("icons", icons) +assetsenv.Depends(icons, icons_src) +assetsenv.Alias("icons", icons) # Protobuf .proto -> .c + .h -proto_src = Glob("protobuf/*.proto", source=True) -proto_options = Glob("protobuf/*.options", source=True) -proto = assetsenv.ProtoBuilder(Dir("compiled"), proto_src) -Depends(proto, proto_options) +proto_src = assetsenv.Glob("protobuf/*.proto", source=True) +proto_options = assetsenv.Glob("protobuf/*.options", source=True) +proto = assetsenv.ProtoBuilder(assetsenv.Dir("compiled"), proto_src) +assetsenv.Depends(proto, proto_options) # Precious(proto) -Alias("proto", proto) +assetsenv.Alias("proto", proto) # Internal animations dolphin_internal = assetsenv.DolphinSymBuilder( - Dir("compiled"), - Dir("#/assets/dolphin"), + assetsenv.Dir("compiled"), + assetsenv.Dir("#/assets/dolphin"), DOLPHIN_RES_TYPE="internal", ) -Alias("dolphin_internal", dolphin_internal) +assetsenv.Alias("dolphin_internal", dolphin_internal) # Blocking animations dolphin_blocking = assetsenv.DolphinSymBuilder( - Dir("compiled"), - Dir("#/assets/dolphin"), + assetsenv.Dir("compiled"), + assetsenv.Dir("#/assets/dolphin"), DOLPHIN_RES_TYPE="blocking", ) -Alias("dolphin_blocking", dolphin_blocking) +assetsenv.Alias("dolphin_blocking", dolphin_blocking) # Protobuf version meta @@ -67,8 +67,8 @@ proto_ver = assetsenv.ProtoVerBuilder( "compiled/protobuf_version.h", "#/assets/protobuf/Changelog", ) -Depends(proto_ver, proto) -Alias("proto_ver", proto_ver) +assetsenv.Depends(proto_ver, proto) +assetsenv.Alias("proto_ver", proto_ver) # Gather everything into a static lib assets_parts = (icons, proto, dolphin_blocking, dolphin_internal, proto_ver) @@ -82,14 +82,14 @@ assetsenv.Install("${LIB_DIST_DIR}", assetslib) if assetsenv["IS_BASE_FIRMWARE"]: # External dolphin animations dolphin_external = assetsenv.DolphinExtBuilder( - Dir("#/assets/resources/dolphin"), - Dir("#/assets/dolphin"), + assetsenv.Dir("#/assets/resources/dolphin"), + assetsenv.Dir("#/assets/dolphin"), DOLPHIN_RES_TYPE="external", ) - NoClean(dolphin_external) + assetsenv.NoClean(dolphin_external) if assetsenv["FORCE"]: - AlwaysBuild(dolphin_external) - Alias("dolphin_ext", dolphin_external) + assetsenv.AlwaysBuild(dolphin_external) + assetsenv.Alias("dolphin_ext", dolphin_external) # Resources manifest @@ -101,13 +101,13 @@ if assetsenv["IS_BASE_FIRMWARE"]: "${RESMANIFESTCOMSTR}", ), ) - Precious(resources) - NoClean(resources) + assetsenv.Precious(resources) + assetsenv.NoClean(resources) if assetsenv["FORCE"]: - AlwaysBuild(resources) + assetsenv.AlwaysBuild(resources) # Exporting resources node to external environment env["FW_RESOURCES"] = resources - Alias("resources", resources) + assetsenv.Alias("resources", resources) Return("assetslib") diff --git a/documentation/fbt.md b/documentation/fbt.md index 061339da..9658ff6a 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -1,11 +1,12 @@ # Flipper Build Tool -FBT is the entry point for most firmware-related commands and utilities. +FBT is the entry point for firmware-related commands and utilities. It is invoked by `./fbt` in firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. ## Requirements Please install Python packages required by assets build scripts: `pip3 install -r scripts/requirements.txt` +Make sure that `gcc-arm-none-eabi` toolchain & OpenOCD executables are in system's PATH. ## NB @@ -17,22 +18,22 @@ To build with FBT, call it specifying configuration options & targets to build. `./fbt --with-updater COMPACT=1 DEBUG=0 VERBOSE=1 updater_package copro_dist` -To run cleanup (think of `make clean`) for specified targets, all `-c` option. +To run cleanup (think of `make clean`) for specified targets, add `-c` option. ## FBT targets -FBT keeps track of internal dependencies, so you only need to build the highest-level target you need, and FBT will make sure everything it needs is up-to-date. +FBT keeps track of internal dependencies, so you only need to build the highest-level target you need, and FBT will make sure everything they depend on is up-to-date. ### High-level (what you most likely need) -- `fw_dist` - build & publish firmware to `dist` folder +- `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified - `updater_package` - build self-update package. _Requires `--with-updater` option_ - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper - `flash` - flash attached device with OpenOCD over ST-Link - `flash_usb` - build, upload and install update package to device over USB. _Requires `--with-updater` option_ - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded - `debug_updater` - attach gdb with updater's .elf loaded. _Requires `--with-updater` option_ -- `debug_other` - attach gdb without loading built elf. Allows to manually add external elf files with `add-symbol-file` in gdb. +- `debug_other` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb. - `openocd` - just start OpenOCD ### Firmware targets diff --git a/fbt b/fbt index 81193a46..c95b1371 100755 --- a/fbt +++ b/fbt @@ -2,11 +2,17 @@ set -e +SCRIPTDIR="$( dirname -- "$0"; )"; +SCONS_EP=${SCRIPTDIR}/lib/scons/scripts/scons.py + if [[ -d .git ]]; then - echo "Updating git submodules" - git submodule update --init + echo Updating git submodules + git submodule update --init +else # Not in a git repo + echo Not in a git repo, please clone with git clone --recursive + # Return error code 1 to indicate failure + exit 1 fi -SCRIPTDIR="$( dirname -- "$0"; )"; SCONS_DEFAULT_FLAGS="-Q --warn=target-not-built" -python3 ${SCRIPTDIR}/lib/scons/scripts/scons.py ${SCONS_DEFAULT_FLAGS} "$@" +python3 ${SCONS_EP} ${SCONS_DEFAULT_FLAGS} "$@" diff --git a/fbt.cmd b/fbt.cmd index 7711e44b..67d42132 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -1,8 +1,11 @@ @echo off + +set SCONS_EP=%~dp0\lib\scons\scripts\scons.py + if exist ".git" ( - echo Prepairing git submodules - git submodule update --init + echo Updating git submodules + git submodule update --init ) set "SCONS_DEFAULT_FLAGS=-Q --warn=target-not-built" -python lib/scons/scripts/scons.py %SCONS_DEFAULT_FLAGS% %* +python %SCONS_EP% %SCONS_DEFAULT_FLAGS% %* diff --git a/fbt_options.py b/fbt_options.py index ddeff048..3cad7e58 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -46,7 +46,7 @@ OPENOCD_OPTS = '-f interface/stlink.cfg -c "transport select hla_swd" -f debug/s SVD_FILE = "debug/STM32WB55_CM4.svd" FIRMWARE_APPS = { - "default": ( + "default": [ "crypto_start", # Svc "basic_services", @@ -62,11 +62,11 @@ FIRMWARE_APPS = { "basic_plugins", # Debug "debug_apps", - ), - "unit_tests": ( + ], + "unit_tests": [ "basic_services", "unit_tests", - ), + ], } FIRMWARE_APP_SET = "default" diff --git a/firmware.scons b/firmware.scons index 890be5f8..4c9988ba 100644 --- a/firmware.scons +++ b/firmware.scons @@ -2,6 +2,7 @@ Import("ENV", "fw_build_meta") import os +from fbt.util import link_dir # Building initial C environment for libs env = ENV.Clone( @@ -72,7 +73,7 @@ if not env["VERBOSE"]: HEXCOMSTR="\tHEX\t${TARGET}", BINCOMSTR="\tBIN\t${TARGET}", DFUCOMSTR="\tDFU\t${TARGET}", - OOCDCOMSTR="\tFLASH\t${SOURCE}", + OPENOCDCOMSTR="\tFLASH\t${SOURCE}", ) @@ -116,8 +117,7 @@ else: fwenv.Append(APPS=["updater"]) if extra_int_apps := GetOption("extra_int_apps"): - for extra_int_app in extra_int_apps.split(","): - fwenv.Append(APPS=[extra_int_app]) + fwenv.Append(APPS=extra_int_apps.split(",")) fwenv.LoadApplicationManifests() fwenv.PrepareApplicationsBuild() @@ -198,41 +198,83 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( ], ) + +def link_elf_dir_as_latest(env, elf_target): + # Ugly way to check if updater-related targets were requested + elf_dir = elf_target.Dir(".") + explicitly_building_updater = False + # print("BUILD_TARGETS:", ','.join(BUILD_TARGETS)) + for build_target in BUILD_TARGETS: + # print(">>> ", str(build_target)) + if "updater" in str(build_target): + explicitly_building_updater = True + + latest_dir = env.Dir("#build/latest") + + link_this_dir = True + if explicitly_building_updater: + # If updater is explicitly requested, link to the latest updater + # Otherwise, link to the latest firmware + link_this_dir = not env["IS_BASE_FIRMWARE"] + + if link_this_dir: + print(f"Setting {elf_dir} as latest built dir") + return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") + + +def link_latest_dir(env, target, source): + return link_elf_dir_as_latest(env, target[0]) + + # Make it depend on everything child builders returned Depends(fwelf, lib_targets) AddPostAction(fwelf, fwenv["APPBUILD_DUMP"]) AddPostAction(fwelf, Action("@$SIZECOM")) +AddPostAction(fwelf, Action(link_latest_dir, None)) + +link_dir_command = fwenv["LINK_DIR_CMD"] = fwenv.PhonyTarget( + "${FIRMWARE_BUILD_CFG}" + "_latest", + Action(lambda target, source, env: link_elf_dir_as_latest(env, source[0]), None), + source=fwelf, +) fwhex = fwenv["FW_HEX"] = fwenv.HEXBuilder("${FIRMWARE_BUILD_CFG}") fwbin = fwenv["FW_BIN"] = fwenv.BINBuilder("${FIRMWARE_BUILD_CFG}") fwdfu = fwenv["FW_DFU"] = fwenv.DFUBuilder("${FIRMWARE_BUILD_CFG}") -# Default(dfu) Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_dfu", fwdfu) fwdump = fwenv.ObjDump("${FIRMWARE_BUILD_CFG}") Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_list", fwdump) # Additional FW-related pseudotargets -flash = fwenv["FW_FLASH"] = fwenv.OOCDFlashCommand( +flash = fwenv["FW_FLASH"] = fwenv.OpenOCDFlash( + "#build/oocd-${FIRMWARE_BUILD_CFG}-flash.flag", "${FIRMWARE_BUILD_CFG}", OPENOCD_COMMAND='-c "program ${SOURCE.posix} reset exit ${IMAGE_BASE_ADDRESS}"', ) if fwenv["FORCE"]: - AlwaysBuild(flash) -Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_flash", flash) + fwenv.AlwaysBuild(flash) +fwenv.Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_flash", flash) if fwenv["IS_BASE_FIRMWARE"]: - Alias("flash", flash) + fwenv.Alias("flash", flash) # Compile DB generation fwcdb = fwenv["FW_CDB"] = fwenv.CompilationDatabase("compile_commands.json") -Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) +fwenv.Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) -artifacts = [fwhex, fwbin, fwdfu, env["FW_VERSION_JSON"]] +artifacts = [ + fwhex, + fwbin, + fwdfu, + env["FW_VERSION_JSON"], + fwcdb, +] fwenv["FW_ARTIFACTS"] = artifacts Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", artifacts) + Return("fwenv") diff --git a/scripts/flipper/app.py b/scripts/flipper/app.py index a75a8a9e..95835602 100644 --- a/scripts/flipper/app.py +++ b/scripts/flipper/app.py @@ -1,7 +1,6 @@ import logging import argparse import sys -import os class App: diff --git a/scripts/flipper/utils/cdc.py b/scripts/flipper/utils/cdc.py new file mode 100644 index 00000000..081705cc --- /dev/null +++ b/scripts/flipper/utils/cdc.py @@ -0,0 +1,17 @@ +import serial.tools.list_ports as list_ports + +# Returns a valid port or None, if it cannot be found +def resolve_port(logger, portname: str = "auto"): + if portname != "auto": + return portname + # Try guessing + flippers = list(list_ports.grep("flip")) + if len(flippers) == 1: + flipper = flippers[0] + logger.info(f"Using {flipper.serial_number} on {flipper.device}") + return flipper.device + elif len(flippers) == 0: + logger.error("Failed to find connected Flipper") + elif len(flippers) > 1: + logger.error("More than one Flipper is attached") + logger.error("Failed to guess which port to use. Specify --port") diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py index 538ecdb9..a22ca1f5 100644 --- a/scripts/selfupdate.py +++ b/scripts/selfupdate.py @@ -1,20 +1,18 @@ #!/usr/bin/env python3 +from typing import final +from flipper.app import App from flipper.storage import FlipperStorage +from flipper.utils.cdc import resolve_port import logging -import argparse import os -import sys import pathlib import serial.tools.list_ports as list_ports -class Main: - def __init__(self): - # command args - self.parser = argparse.ArgumentParser() - self.parser.add_argument("-d", "--debug", action="store_true", help="Debug") +class Main(App): + def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( "-b", @@ -25,35 +23,15 @@ class Main: type=int, ) - self.subparsers = self.parser.add_subparsers(help="sub-command help") - - self.parser_install = self.subparsers.add_parser( - "install", help="Install OTA package" - ) - self.parser_install.add_argument("manifest_path", help="Manifest path") - self.parser_install.add_argument( + self.parser.add_argument("manifest_path", help="Manifest path") + self.parser.add_argument( "--pkg_dir_name", help="Update dir name", default="pcbundle", required=False ) - self.parser_install.set_defaults(func=self.install) + self.parser.set_defaults(func=self.install) # logging self.logger = logging.getLogger() - def __call__(self): - self.args = self.parser.parse_args() - if "func" not in self.args: - self.parser.error("Choose something to do") - # configure log output - self.log_level = logging.DEBUG if self.args.debug else logging.INFO - self.logger.setLevel(self.log_level) - self.handler = logging.StreamHandler(sys.stdout) - self.handler.setLevel(self.log_level) - self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") - self.handler.setFormatter(self.formatter) - self.logger.addHandler(self.handler) - # execute requested function - self.args.func() - # make directory with exist check def mkdir_on_storage(self, storage, flipper_dir_path): if not storage.exist_dir(flipper_dir_path): @@ -82,61 +60,49 @@ class Main: return False return True - def _get_port(self): - if self.args.port != "auto": - return self.args.port - # Try guessing - flippers = list(list_ports.grep("flip")) - if len(flippers) == 1: - flipper = flippers[0] - self.logger.info(f"Using {flipper.serial_number} on {flipper.device}") - return flipper.device - elif len(flippers) == 0: - self.logger.error("Failed to find connected Flipper") - elif len(flippers) > 1: - self.logger.error("More than one Flipper is attached") - self.logger.error("Failed to guess which port to use. Specify --port") - def install(self): - if not (port := self._get_port()): + if not (port := resolve_port(self.logger, self.args.port)): return 1 storage = FlipperStorage(port, self.args.baud) storage.start() - if not os.path.isfile(self.args.manifest_path): - self.logger.error("Error: manifest not found") - return 2 + try: + if not os.path.isfile(self.args.manifest_path): + self.logger.error("Error: manifest not found") + return 2 - manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) - manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] + manifest_path = pathlib.Path(os.path.abspath(self.args.manifest_path)) + manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] - pkg_dir_name = self.args.pkg_dir_name or pkg_name - flipper_update_path = f"/ext/update/{pkg_dir_name}" + pkg_dir_name = self.args.pkg_dir_name or pkg_name + flipper_update_path = f"/ext/update/{pkg_dir_name}" - self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') - # if not os.path.exists(self.args.manifest_path): - # self.logger.error("Error: package not found") - if not self.mkdir_on_storage(storage, flipper_update_path): - self.logger.error(f"Error: cannot create {storage.last_error}") - return -2 + self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') + # if not os.path.exists(self.args.manifest_path): + # self.logger.error("Error: package not found") + if not self.mkdir_on_storage(storage, flipper_update_path): + self.logger.error(f"Error: cannot create {storage.last_error}") + return -2 - for dirpath, dirnames, filenames in os.walk(manifest_path.parents[0]): - for fname in filenames: - self.logger.debug(f"Uploading {fname}") - local_file_path = os.path.join(dirpath, fname) - flipper_file_path = f"{flipper_update_path}/{fname}" - if not self.send_file_to_storage( - storage, flipper_file_path, local_file_path, False - ): - self.logger.error(f"Error: {storage.last_error}") - return -3 + for dirpath, dirnames, filenames in os.walk(manifest_path.parents[0]): + for fname in filenames: + self.logger.debug(f"Uploading {fname}") + local_file_path = os.path.join(dirpath, fname) + flipper_file_path = f"{flipper_update_path}/{fname}" + if not self.send_file_to_storage( + storage, flipper_file_path, local_file_path, False + ): + self.logger.error(f"Error: {storage.last_error}") + return -3 - storage.send_and_wait_eol( - f"update install {flipper_update_path}/{manifest_name}\r" - ) - break - storage.stop() + storage.send_and_wait_eol( + f"update install {flipper_update_path}/{manifest_name}\r" + ) + break + return 0 + finally: + storage.stop() if __name__ == "__main__": diff --git a/scripts/storage.py b/scripts/storage.py index 1281253b..0ddc2fc0 100755 --- a/scripts/storage.py +++ b/scripts/storage.py @@ -1,23 +1,19 @@ #!/usr/bin/env python3 +from flipper.app import App from flipper.storage import FlipperStorage +from flipper.utils.cdc import resolve_port import logging -import argparse import os -import sys import binascii -import posixpath import filecmp import tempfile -class Main: - def __init__(self): - # command args - self.parser = argparse.ArgumentParser() - self.parser.add_argument("-d", "--debug", action="store_true", help="Debug") - self.parser.add_argument("-p", "--port", help="CDC Port", required=True) +class Main(App): + def init(self): + self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( "-b", "--baud", @@ -77,43 +73,37 @@ class Main: ) self.parser_stress.set_defaults(func=self.stress) - # logging - self.logger = logging.getLogger() + def _get_storage(self): + if not (port := resolve_port(self.logger, self.args.port)): + return None - def __call__(self): - self.args = self.parser.parse_args() - if "func" not in self.args: - self.parser.error("Choose something to do") - # configure log output - self.log_level = logging.DEBUG if self.args.debug else logging.INFO - self.logger.setLevel(self.log_level) - self.handler = logging.StreamHandler(sys.stdout) - self.handler.setLevel(self.log_level) - self.formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") - self.handler.setFormatter(self.formatter) - self.logger.addHandler(self.handler) - # execute requested function - self.args.func() + storage = FlipperStorage(port, self.args.baud) + storage.start() + return storage def mkdir(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Creating "{self.args.flipper_path}"') if not storage.mkdir(self.args.flipper_path): self.logger.error(f"Error: {storage.last_error}") storage.stop() + return 0 def remove(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Removing "{self.args.flipper_path}"') if not storage.remove(self.args.flipper_path): self.logger.error(f"Error: {storage.last_error}") storage.stop() + return 0 def receive(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 if storage.exist_dir(self.args.flipper_path): for dirpath, dirnames, filenames in storage.walk(self.args.flipper_path): @@ -155,14 +145,17 @@ class Main: if not storage.receive_file(self.args.flipper_path, self.args.local_path): self.logger.error(f"Error: {storage.last_error}") storage.stop() + return 0 def send(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.send_to_storage( storage, self.args.flipper_path, self.args.local_path, self.args.force ) storage.stop() + return 0 # send file or folder recursively def send_to_storage(self, storage, flipper_path, local_path, force): @@ -250,8 +243,9 @@ class Main: self.logger.error(f"Error: {storage.last_error}") def read(self): - storage = FlipperStorage(self.args.port, self.args.baud) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Reading "{self.args.flipper_path}"') data = storage.read_file(self.args.flipper_path) if not data: @@ -264,10 +258,12 @@ class Main: print("Binary hexadecimal data:") print(binascii.hexlify(data).decode()) storage.stop() + return 0 def size(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Getting size of "{self.args.flipper_path}"') size = storage.size(self.args.flipper_path) if size < 0: @@ -275,13 +271,16 @@ class Main: else: print(size) storage.stop() + return 0 def list(self): - storage = FlipperStorage(self.args.port) - storage.start() + if not (storage := self._get_storage()): + return 1 + self.logger.debug(f'Listing "{self.args.flipper_path}"') storage.list_tree(self.args.flipper_path) storage.stop() + return 0 def stress(self): self.logger.error("This test is wearing out flash memory.") @@ -293,18 +292,21 @@ class Main: self.logger.error("Stop at this point or device warranty will be void") say = input("Anything to say? ").strip().lower() if say != "void": - return + return 2 say = input("Why, Mr. Anderson? ").strip().lower() if say != "because": - return + return 3 with tempfile.TemporaryDirectory() as tmpdirname: send_file_name = os.path.join(tmpdirname, "send") receive_file_name = os.path.join(tmpdirname, "receive") with open(send_file_name, "w") as fout: fout.write("A" * self.args.file_size) - storage = FlipperStorage(self.args.port) - storage.start() + + storage = self._get_storage() + if not storage: + return 1 + if storage.exist_file(self.args.flipper_path): self.logger.error("File exists, remove it first") return @@ -318,6 +320,7 @@ class Main: os.unlink(receive_file_name) self.args.count -= 1 storage.stop() + return 0 if __name__ == "__main__": diff --git a/site_scons/fbt/util.py b/site_scons/fbt/util.py index 9de24a9a..11509b2d 100644 --- a/site_scons/fbt/util.py +++ b/site_scons/fbt/util.py @@ -2,7 +2,9 @@ import SCons from SCons.Subst import quote_spaces import re - +import os +import random +import string WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)") @@ -17,3 +19,26 @@ def tempfile_arg_esc_func(arg): def wrap_tempfile(env, command): env[command] = '${TEMPFILE("' + env[command] + '","$' + command + 'STR")}' + + +def link_dir(target_path, source_path, is_windows): + # print(f"link_dir: {target_path} -> {source_path}") + if os.path.lexists(target_path) or os.path.exists(target_path): + os.unlink(target_path) + if is_windows: + # Crete junction + import _winapi + + if not os.path.isdir(source_path): + raise Exception(f"Source directory {source_path} is not a directory") + + if not os.path.exists(target_path): + _winapi.CreateJunction(source_path, target_path) + else: + os.symlink(source_path, target_path) + + +def random_alnum(length): + return "".join( + random.choice(string.ascii_letters + string.digits) for _ in range(length) + ) diff --git a/site_scons/site_tools/fbt_apps.py b/site_scons/site_tools/fbt_apps.py index cbeae2d0..bdccccf7 100644 --- a/site_scons/site_tools/fbt_apps.py +++ b/site_scons/site_tools/fbt_apps.py @@ -51,7 +51,10 @@ def generate(env): env.Append( BUILDERS={ "ApplicationsC": Builder( - action=Action(build_apps_c, "${APPSCOMSTR}"), + action=Action( + build_apps_c, + "${APPSCOMSTR}", + ), ), } ) diff --git a/site_scons/site_tools/fbt_dist.py b/site_scons/site_tools/fbt_dist.py index 8bfb4068..fcfecbcb 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/site_scons/site_tools/fbt_dist.py @@ -46,17 +46,19 @@ def AddFwProject(env, base_env, fw_type, fw_env_key): ], DIST_DEPENDS=[ project_env["FW_ARTIFACTS"], + project_env["LINK_DIR_CMD"], ], ) + env.Replace(DIST_DIR=get_variant_dirname(env)) return project_env -def AddDebugTarget(env, targetenv, force_flash=True): - pseudo_name = f"debug.{targetenv.subst('$FIRMWARE_BUILD_CFG')}.pseudo" - debug_target = env.GDBPy( - pseudo_name, - targetenv["FW_ELF"], +def AddDebugTarget(env, alias, targetenv, force_flash=True): + debug_target = env.PhonyTarget( + alias, + "$GDBPYCOM", + source=targetenv["FW_ELF"], GDBPYOPTS='-ex "source debug/FreeRTOS/FreeRTOS.py" ' '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ' '-ex "svd_load ${SVD_FILE}" ' @@ -64,28 +66,37 @@ def AddDebugTarget(env, targetenv, force_flash=True): ) if force_flash: env.Depends(debug_target, targetenv["FW_FLASH"]) - env.Pseudo(pseudo_name) - env.AlwaysBuild(debug_target) + return debug_target +def DistCommand(env, name, source, **kw): + target = f"dist_{name}" + command = env.Command( + target, + source, + '@${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', + **kw, + ) + env.Pseudo(target) + env.Alias(name, command) + return command + + def generate(env): env.AddMethod(AddFwProject) env.AddMethod(AddDebugTarget) + env.AddMethod(DistCommand) env.SetDefault( COPRO_MCU_FAMILY="STM32WB5x", ) + env.Append( BUILDERS={ - "DistBuilder": Builder( - action=Action( - '@${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}', - ), - ), "UsbInstall": Builder( action=[ Action( - "${PYTHON3} ${ROOT_DIR.abspath}/scripts/selfupdate.py install dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf" + "${PYTHON3} ${ROOT_DIR.abspath}/scripts/selfupdate.py dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}/update.fuf" ), Touch("${TARGET}"), ] diff --git a/site_scons/site_tools/gdb.py b/site_scons/site_tools/gdb.py index e7b6bdd6..94ea9fbe 100644 --- a/site_scons/site_tools/gdb.py +++ b/site_scons/site_tools/gdb.py @@ -11,22 +11,6 @@ def generate(env): GDBCOM="$GDB $GDBOPTS $SOURCES", # no $TARGET GDBPYCOM="$GDBPY $GDBOPTS $GDBPYOPTS $SOURCES", # no $TARGET ) - env.Append( - BUILDERS={ - "GDB": Builder( - action=Action( - "${GDBCOM}", - "${GDBCOMSTR}", - ), - ), - "GDBPy": Builder( - action=Action( - "${GDBPYCOM}", - "${GDBPYCOMSTR}", - ), - ), - } - ) def exists(env): diff --git a/site_scons/site_tools/openocd.py b/site_scons/site_tools/openocd.py index 135e1100..dcf0bf92 100644 --- a/site_scons/site_tools/openocd.py +++ b/site_scons/site_tools/openocd.py @@ -7,7 +7,7 @@ __OPENOCD_BIN = "openocd" _oocd_action = Action( "${OPENOCD} ${OPENOCD_OPTS} ${OPENOCD_COMMAND}", - "${OOCDCOMSTR}", + "${OPENOCDCOMSTR}", ) @@ -16,12 +16,13 @@ def generate(env): OPENOCD=__OPENOCD_BIN, OPENOCD_OPTS="", OPENOCD_COMMAND="", - OOCDCOMSTR="", + OPENOCDCOM="${OPENOCD} ${OPENOCD_OPTS} ${OPENOCD_COMMAND}", + OPENOCDCOMSTR="", ) env.Append( BUILDERS={ - "OOCDFlashCommand": Builder( + "OpenOCDFlash": Builder( action=[ _oocd_action, Touch("${TARGET}"), @@ -29,9 +30,6 @@ def generate(env): suffix=".flash", src_suffix=".bin", ), - "OOCDCommand": Builder( - action=_oocd_action, - ), } ) diff --git a/site_scons/site_tools/sconsmodular.py b/site_scons/site_tools/sconsmodular.py index eeb90022..a4bb9f65 100644 --- a/site_scons/site_tools/sconsmodular.py +++ b/site_scons/site_tools/sconsmodular.py @@ -29,9 +29,20 @@ def BuildModules(env, modules): return result +def PhonyTarget(env, name, action, source=None, **kw): + if not source: + source = [] + phony_name = "phony_" + name + env.Pseudo(phony_name) + return env.AlwaysBuild( + env.Alias(name, env.Command(phony_name, source, action, **kw)) + ) + + def generate(env): env.AddMethod(BuildModule) env.AddMethod(BuildModules) + env.AddMethod(PhonyTarget) def exists(env): From 1975868ed8abb5da83df9ec06a806c1e3e1008df Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Sun, 3 Jul 2022 01:44:38 -0700 Subject: [PATCH 03/26] PicoPass / iClass (#1298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add mdedtls for des3 implementation * add localss from RfidResearchGroup/proxmark3 * picopass reader app and rfal for communicating with picopass cards * always turn off field * close storage when keys are not found * Add mbedtls as submodule * add mbedtl_config * Switched to only including specific mbedtls files I need. Thank you @kevinwallace * cherry-pick kevinwallace sconsify * scons for mbedtls/loclass * Reset to ready state on error * unsigned FC/CN * clean FC/CN if not decoded Co-authored-by: hedger Co-authored-by: あく Co-authored-by: Kevin Wallace --- .gitmodules | 3 + applications/picopass/application.fam | 11 + applications/picopass/picopass.c | 436 ++++++++++++++++++++++++ applications/picopass/picopass.h | 9 + firmware.scons | 2 + lib/SConscript | 2 + lib/ST25RFAL002/include/rfal_picopass.h | 65 ++++ lib/ST25RFAL002/source/rfal_picopass.c | 172 ++++++++++ lib/loclass.scons | 19 ++ lib/loclass/optimized_cipher.c | 338 ++++++++++++++++++ lib/loclass/optimized_cipher.h | 90 +++++ lib/loclass/optimized_cipherutils.c | 137 ++++++++ lib/loclass/optimized_cipherutils.h | 64 ++++ lib/loclass/optimized_elite.c | 234 +++++++++++++ lib/loclass/optimized_elite.h | 58 ++++ lib/loclass/optimized_ikeys.c | 321 +++++++++++++++++ lib/loclass/optimized_ikeys.h | 66 ++++ lib/mbedtls | 1 + lib/mbedtls.scons | 20 ++ 19 files changed, 2048 insertions(+) create mode 100644 applications/picopass/application.fam create mode 100644 applications/picopass/picopass.c create mode 100644 applications/picopass/picopass.h create mode 100644 lib/ST25RFAL002/include/rfal_picopass.h create mode 100644 lib/ST25RFAL002/source/rfal_picopass.c create mode 100644 lib/loclass.scons create mode 100644 lib/loclass/optimized_cipher.c create mode 100644 lib/loclass/optimized_cipher.h create mode 100644 lib/loclass/optimized_cipherutils.c create mode 100644 lib/loclass/optimized_cipherutils.h create mode 100644 lib/loclass/optimized_elite.c create mode 100644 lib/loclass/optimized_elite.h create mode 100644 lib/loclass/optimized_ikeys.c create mode 100644 lib/loclass/optimized_ikeys.h create mode 160000 lib/mbedtls create mode 100644 lib/mbedtls.scons diff --git a/.gitmodules b/.gitmodules index 5846b705..b580a8c7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "lib/scons"] path = lib/scons url = https://github.com/SCons/scons.git +[submodule "lib/mbedtls"] + path = lib/mbedtls + url = https://github.com/Mbed-TLS/mbedtls.git diff --git a/applications/picopass/application.fam b/applications/picopass/application.fam new file mode 100644 index 00000000..3ad72d27 --- /dev/null +++ b/applications/picopass/application.fam @@ -0,0 +1,11 @@ +App( + appid="picopass", + name="PicoPass Reader", + apptype=FlipperAppType.PLUGIN, + entry_point="picopass_app", + cdefines=["APP_PICOPASS"], + requires=["gui"], + stack_size=1 * 1024, + icon="A_Plugins_14", + order=30, +) diff --git a/applications/picopass/picopass.c b/applications/picopass/picopass.c new file mode 100644 index 00000000..477b7b86 --- /dev/null +++ b/applications/picopass/picopass.c @@ -0,0 +1,436 @@ +#include "picopass.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TAG "PicoPass" + +#define PICOPASS_APP_ICLASS_KEY_PATH "/any/picopass/iclass_key.bin" +#define PICOPASS_APP_ICLASS_DECRYPT_KEY_PATH "/any/picopass/iclass_decryptionkey.bin" + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef struct { + bool valid; + uint8_t bitLength; + uint8_t FacilityCode; + uint16_t CardNumber; +} WiegandRecord; + +typedef struct { + bool biometrics; + uint8_t encryption; + uint8_t credential[8]; + uint8_t pin0[8]; + uint8_t pin1[8]; + WiegandRecord record; +} PACS; + +enum State { INIT, KEYS_MISSING, READY, RESULT }; +typedef struct { + enum State state; + PACS pacs; +} PluginState; + +uint8_t iclass_key[8] = {0}; // NB: not the permuted version +uint8_t iclass_decryptionkey[16] = {0}; +ApplicationArea AA1; + +static bool picopass_load_keys() { + Storage* storage = furi_record_open("storage"); + File* file = storage_file_alloc(storage); + + if(!storage_file_open(file, PICOPASS_APP_ICLASS_KEY_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open iClass key"); + storage_file_free(file); + furi_record_close("storage"); + return false; + }; + storage_file_read(file, iclass_key, sizeof(iclass_key)); + storage_file_close(file); + FURI_LOG_D(TAG, "iClass key loaded"); + + if(!storage_file_open( + file, PICOPASS_APP_ICLASS_DECRYPT_KEY_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open iClass decryption key"); + storage_file_free(file); + furi_record_close("storage"); + return false; + }; + storage_file_read(file, iclass_decryptionkey, sizeof(iclass_decryptionkey)); + storage_file_close(file); + FURI_LOG_D(TAG, "iClass decryption key loaded"); + + storage_file_free(file); + furi_record_close("storage"); + return true; +} + +static void render_callback(Canvas* const canvas, void* ctx) { + const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state == NULL) { + return; + } + // border around the edge of the screen + canvas_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontPrimary); + + if(plugin_state->state == INIT) { + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Loading..."); + } else if(plugin_state->state == KEYS_MISSING) { + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Keys missing"); + } else if(plugin_state->state == READY) { + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Push center to scan"); + } else if(plugin_state->state == RESULT) { + char raw_credential[25] = {0}; + sprintf( + raw_credential, + "%02x %02x %02x %02x %02x %02x %02x %02x", + plugin_state->pacs.credential[0], + plugin_state->pacs.credential[1], + plugin_state->pacs.credential[2], + plugin_state->pacs.credential[3], + plugin_state->pacs.credential[4], + plugin_state->pacs.credential[5], + plugin_state->pacs.credential[6], + plugin_state->pacs.credential[7]); + canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignTop, raw_credential); + + if(plugin_state->pacs.record.valid) { + char parsed[20] = {0}; + sprintf( + parsed, + "FC: %03u CN: %05u", + plugin_state->pacs.record.FacilityCode, + plugin_state->pacs.record.CardNumber); + canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignBottom, parsed); + } + } + + release_mutex((ValueMutex*)ctx, plugin_state); +} + +static void input_callback(InputEvent* input_event, osMessageQueueId_t event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + osMessageQueuePut(event_queue, &event, 0, osWaitForever); +} + +static void picopass_state_init(PluginState* const plugin_state) { + plugin_state->state = INIT; + if(picopass_load_keys()) { + plugin_state->state = READY; + } else { + plugin_state->state = KEYS_MISSING; + } +} + +ReturnCode decrypt(uint8_t* enc_data, uint8_t* dec_data) { + uint8_t key[32] = {0}; + memcpy(key, iclass_decryptionkey, sizeof(iclass_decryptionkey)); + mbedtls_des3_context ctx; + mbedtls_des3_init(&ctx); + mbedtls_des3_set2key_dec(&ctx, key); + mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); + mbedtls_des3_free(&ctx); + return ERR_NONE; +} + +ReturnCode parseWiegand(uint8_t* data, WiegandRecord* record) { + uint32_t* halves = (uint32_t*)data; + if(halves[0] == 0) { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); + record->bitLength = 31 - leading0s; + } else { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); + record->bitLength = 63 - leading0s; + } + FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); + + if(record->bitLength == 26) { + uint8_t* v4 = data + 4; + v4[0] = 0; + + uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); + + record->CardNumber = (bot >> 1) & 0xFFFF; + record->FacilityCode = (bot >> 17) & 0xFF; + record->valid = true; + } else { + record->CardNumber = 0; + record->FacilityCode = 0; + record->valid = false; + } + return ERR_NONE; +} + +ReturnCode disable_field(ReturnCode rc) { + st25r3916TxRxOff(); + rfalLowPowerModeStart(); + return rc; +} + +ReturnCode picopass_read_card(ApplicationArea* AA1) { + rfalPicoPassIdentifyRes idRes; + rfalPicoPassSelectRes selRes; + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; + + ReturnCode err; + + uint8_t div_key[8] = {0}; + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; + + st25r3916TxRxOn(); + rfalLowPowerModeStop(); + rfalWorker(); + err = rfalPicoPassPollerInitialize(); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d\n", err); + return disable_field(err); + } + + err = rfalFieldOnAndStartGT(); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d\n", err); + return disable_field(err); + } + + err = rfalPicoPassPollerCheckPresence(); + if(err != ERR_RF_COLLISION) { + FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d\n", err); + return disable_field(err); + } + + err = rfalPicoPassPollerIdentify(&idRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d\n", err); + return disable_field(err); + } + + err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d\n", err); + return disable_field(err); + } + + err = rfalPicoPassPollerReadCheck(&rcRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); + return disable_field(err); + } + memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 + + diversifyKey(selRes.CSN, iclass_key, div_key); + opt_doReaderMAC(ccnr, div_key, mac); + + err = rfalPicoPassPollerCheck(mac, &chkRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); + return disable_field(err); + } + + for(size_t i = 0; i < 4; i++) { + FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i + 6); + err = rfalPicoPassPollerReadBlock(i + 6, &(AA1->block[i])); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err); + return disable_field(err); + } + } + return disable_field(ERR_NONE); +} + +int32_t picopass_app(void* p) { + UNUSED(p); + osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(PluginEvent), NULL); + + PluginState* plugin_state = malloc(sizeof(PluginState)); + picopass_state_init(plugin_state); + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E("Hello_world", "cannot create mutex\r\n"); + free(plugin_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + ReturnCode err; + for(bool processing = true; processing;) { + osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100); + PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); + + if(event_status == osOK) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + FURI_LOG_D(TAG, "Input Up"); + break; + case InputKeyDown: + FURI_LOG_D(TAG, "Input Down"); + break; + case InputKeyRight: + FURI_LOG_D(TAG, "Input Right"); + break; + case InputKeyLeft: + FURI_LOG_D(TAG, "Input Left"); + break; + case InputKeyOk: + FURI_LOG_D(TAG, "Input OK"); + err = picopass_read_card(&AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_read_card error %d", err); + plugin_state->state = READY; + break; + } + FURI_LOG_D(TAG, "read OK"); + + plugin_state->pacs.biometrics = AA1.block[0].data[4]; + plugin_state->pacs.encryption = AA1.block[0].data[7]; + if(plugin_state->pacs.encryption == 0x17) { + FURI_LOG_D(TAG, "3DES Encrypted"); + err = decrypt(AA1.block[1].data, plugin_state->pacs.credential); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + break; + } + FURI_LOG_D(TAG, "Decrypted 7"); + + err = decrypt(AA1.block[2].data, plugin_state->pacs.pin0); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + break; + } + FURI_LOG_D(TAG, "Decrypted 8"); + + err = decrypt(AA1.block[3].data, plugin_state->pacs.pin1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + break; + } + FURI_LOG_D(TAG, "Decrypted 9"); + } else if(plugin_state->pacs.encryption == 0x14) { + FURI_LOG_D(TAG, "No Encryption"); + memcpy( + plugin_state->pacs.credential, + AA1.block[1].data, + RFAL_PICOPASS_MAX_BLOCK_LEN); + memcpy( + plugin_state->pacs.pin0, + AA1.block[2].data, + RFAL_PICOPASS_MAX_BLOCK_LEN); + memcpy( + plugin_state->pacs.pin1, + AA1.block[3].data, + RFAL_PICOPASS_MAX_BLOCK_LEN); + } else if(plugin_state->pacs.encryption == 0x15) { + FURI_LOG_D(TAG, "DES Encrypted"); + } else { + FURI_LOG_D(TAG, "Unknown encryption"); + break; + } + + FURI_LOG_D( + TAG, + "credential %02x%02x%02x%02x%02x%02x%02x%02x", + plugin_state->pacs.credential[0], + plugin_state->pacs.credential[1], + plugin_state->pacs.credential[2], + plugin_state->pacs.credential[3], + plugin_state->pacs.credential[4], + plugin_state->pacs.credential[5], + plugin_state->pacs.credential[6], + plugin_state->pacs.credential[7]); + FURI_LOG_D( + TAG, + "pin0 %02x%02x%02x%02x%02x%02x%02x%02x", + plugin_state->pacs.pin0[0], + plugin_state->pacs.pin0[1], + plugin_state->pacs.pin0[2], + plugin_state->pacs.pin0[3], + plugin_state->pacs.pin0[4], + plugin_state->pacs.pin0[5], + plugin_state->pacs.pin0[6], + plugin_state->pacs.pin0[7]); + FURI_LOG_D( + TAG, + "pin1 %02x%02x%02x%02x%02x%02x%02x%02x", + plugin_state->pacs.pin1[0], + plugin_state->pacs.pin1[1], + plugin_state->pacs.pin1[2], + plugin_state->pacs.pin1[3], + plugin_state->pacs.pin1[4], + plugin_state->pacs.pin1[5], + plugin_state->pacs.pin1[6], + plugin_state->pacs.pin1[7]); + + err = parseWiegand( + plugin_state->pacs.credential, &plugin_state->pacs.record); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "parse error %d", err); + break; + } + if(plugin_state->pacs.record.valid) { + FURI_LOG_D( + TAG, + "FC: %03d CN: %05d", + plugin_state->pacs.record.FacilityCode, + plugin_state->pacs.record.CardNumber); + } + plugin_state->state = RESULT; + + break; + case InputKeyBack: + FURI_LOG_D(TAG, "Input Back"); + processing = false; + break; + } + } + } + } else { + // FURI_LOG_D(TAG, "osMessageQueue: event timeout"); + // event timeout + } + + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + osMessageQueueDelete(event_queue); + + return 0; +} diff --git a/applications/picopass/picopass.h b/applications/picopass/picopass.h new file mode 100644 index 00000000..e24d97d7 --- /dev/null +++ b/applications/picopass/picopass.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include +#include + +#define PP_MAX_DUMP_SIZE 1024 +#define FURI_HAL_PICOPASS_UID_MAX_LEN 10 diff --git a/firmware.scons b/firmware.scons index 4c9988ba..4bed960f 100644 --- a/firmware.scons +++ b/firmware.scons @@ -195,6 +195,8 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( # 2nd round "flipperformat", "toolbox", + "mbedtls", + "loclass", ], ) diff --git a/lib/SConscript b/lib/SConscript index ee8b8356..a3617c5d 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -72,6 +72,8 @@ libs = env.BuildModules( "subghz", "appframe", "misc", + "mbedtls", + "loclass", ], ) diff --git a/lib/ST25RFAL002/include/rfal_picopass.h b/lib/ST25RFAL002/include/rfal_picopass.h new file mode 100644 index 00000000..c1b07981 --- /dev/null +++ b/lib/ST25RFAL002/include/rfal_picopass.h @@ -0,0 +1,65 @@ + +#ifndef RFAL_PICOPASS_H +#define RFAL_PICOPASS_H + +/* + ****************************************************************************** + * INCLUDES + ****************************************************************************** + */ +#include "platform.h" +#include "rfal_rf.h" +#include "st_errno.h" + +#define RFAL_PICOPASS_UID_LEN 8 +#define RFAL_PICOPASS_MAX_BLOCK_LEN 8 + +#define RFAL_PICOPASS_TXRX_FLAGS \ + (RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_AGC_ON | RFAL_TXRX_FLAGS_PAR_RX_REMV | \ + RFAL_TXRX_FLAGS_CRC_RX_KEEP) + +enum { + RFAL_PICOPASS_CMD_ACTALL = 0x0A, + RFAL_PICOPASS_CMD_IDENTIFY = 0x0C, + RFAL_PICOPASS_CMD_SELECT = 0x81, + RFAL_PICOPASS_CMD_READCHECK = 0x88, + RFAL_PICOPASS_CMD_CHECK = 0x05, + RFAL_PICOPASS_CMD_READ = 0x0C, +}; + +typedef struct { + uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Anti-collision CSN + uint8_t crc[2]; +} rfalPicoPassIdentifyRes; + +typedef struct { + uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Real CSN + uint8_t crc[2]; +} rfalPicoPassSelectRes; + +typedef struct { + uint8_t CCNR[8]; +} rfalPicoPassReadCheckRes; + +typedef struct { + uint8_t mac[4]; +} rfalPicoPassCheckRes; + +typedef struct { + uint8_t data[RFAL_PICOPASS_MAX_BLOCK_LEN]; + uint8_t crc[2]; +} rfalPicoPassReadBlockRes; + +typedef struct { + rfalPicoPassReadBlockRes block[4]; +} ApplicationArea; + +ReturnCode rfalPicoPassPollerInitialize(void); +ReturnCode rfalPicoPassPollerCheckPresence(void); +ReturnCode rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes); +ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes); +ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); +ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); +ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); + +#endif /* RFAL_PICOPASS_H */ diff --git a/lib/ST25RFAL002/source/rfal_picopass.c b/lib/ST25RFAL002/source/rfal_picopass.c new file mode 100644 index 00000000..c99fa224 --- /dev/null +++ b/lib/ST25RFAL002/source/rfal_picopass.c @@ -0,0 +1,172 @@ + +#include "rfal_picopass.h" +#include "utils.h" + +typedef struct { + uint8_t CMD; + uint8_t CSN[RFAL_PICOPASS_UID_LEN]; +} rfalPicoPassSelectReq; + +typedef struct { + uint8_t CMD; + uint8_t null[4]; + uint8_t mac[4]; +} rfalPicoPassCheckReq; + +ReturnCode rfalPicoPassPollerInitialize(void) { + ReturnCode ret; + + EXIT_ON_ERR(ret, rfalSetMode(RFAL_MODE_POLL_PICOPASS, RFAL_BR_26p48, RFAL_BR_26p48)); + rfalSetErrorHandling(RFAL_ERRORHANDLING_NFC); + + rfalSetGT(RFAL_GT_PICOPASS); + rfalSetFDTListen(RFAL_FDT_LISTEN_PICOPASS_POLLER); + rfalSetFDTPoll(RFAL_FDT_POLL_PICOPASS_POLLER); + + return ERR_NONE; +} + +ReturnCode rfalPicoPassPollerCheckPresence(void) { + ReturnCode ret; + uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_ACTALL}; + uint8_t rxBuf[32] = {0}; + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt); + return ret; +} + +ReturnCode rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) { + ReturnCode ret; + + uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_IDENTIFY}; + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx( + txBuf, + sizeof(txBuf), + (uint8_t*)idRes, + sizeof(rfalPicoPassIdentifyRes), + &recvLen, + flags, + fwt); + // printf("identify rx: %d %s\n", recvLen, hex2Str(idRes->CSN, RFAL_PICOPASS_UID_LEN)); + + return ret; +} + +ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) { + ReturnCode ret; + + rfalPicoPassSelectReq selReq; + selReq.CMD = RFAL_PICOPASS_CMD_SELECT; + ST_MEMCPY(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN); + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx( + (uint8_t*)&selReq, + sizeof(rfalPicoPassSelectReq), + (uint8_t*)selRes, + sizeof(rfalPicoPassSelectRes), + &recvLen, + flags, + fwt); + // printf("select rx: %d %s\n", recvLen, hex2Str(selRes->CSN, RFAL_PICOPASS_UID_LEN)); + if(ret == ERR_TIMEOUT) { + return ERR_NONE; + } + + return ret; +} + +ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) { + ReturnCode ret; + uint8_t txBuf[2] = {RFAL_PICOPASS_CMD_READCHECK, 0x02}; + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx( + txBuf, + sizeof(txBuf), + (uint8_t*)rcRes, + sizeof(rfalPicoPassReadCheckRes), + &recvLen, + flags, + fwt); + // printf("readcheck rx: %d %s\n", recvLen, hex2Str(rcRes->CCNR, 8)); + + if(ret == ERR_CRC) { + return ERR_NONE; + } + + return ret; +} + +ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) { + ReturnCode ret; + rfalPicoPassCheckReq chkReq; + chkReq.CMD = RFAL_PICOPASS_CMD_CHECK; + ST_MEMCPY(chkReq.mac, mac, 4); + ST_MEMSET(chkReq.null, 0, 4); + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + // printf("check tx: %s\n", hex2Str((uint8_t *)&chkReq, sizeof(rfalPicoPassCheckReq))); + ret = rfalTransceiveBlockingTxRx( + (uint8_t*)&chkReq, + sizeof(rfalPicoPassCheckReq), + (uint8_t*)chkRes, + sizeof(rfalPicoPassCheckRes), + &recvLen, + flags, + fwt); + // printf("check rx: %d %s\n", recvLen, hex2Str(chkRes->mac, 4)); + if(ret == ERR_CRC) { + return ERR_NONE; + } + + return ret; +} + +ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) { + ReturnCode ret; + /* + * ./reveng -w 16 -s 0c07cc47 0c064556 0c083bbf 0c09b2ae + width=16 poly=0x1021 init=0xd924 refin=true refout=true xorout=0x0000 check=0x1329 residue=0x0000 name=(none) +0c 06 45 56 +0c 07 cc 47 +0c 08 3b bf +0c 09 b2 ae + */ + + uint8_t readCmds[4][4] = { + {RFAL_PICOPASS_CMD_READ, 6, 0x45, 0x56}, + {RFAL_PICOPASS_CMD_READ, 7, 0xcc, 0x47}, + {RFAL_PICOPASS_CMD_READ, 8, 0x3b, 0xbf}, + {RFAL_PICOPASS_CMD_READ, 9, 0xb2, 0xae}}; + + uint8_t* txBuf = readCmds[blockNum - 6]; + uint16_t recvLen = 0; + uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; + uint32_t fwt = rfalConvMsTo1fc(20); + + ret = rfalTransceiveBlockingTxRx( + txBuf, + sizeof(txBuf), + (uint8_t*)readRes, + sizeof(rfalPicoPassReadBlockRes), + &recvLen, + flags, + fwt); + // printf("check rx: %d %s\n", recvLen, hex2Str(readRes->data, RFAL_PICOPASS_MAX_BLOCK_LEN)); + + return ret; +} diff --git a/lib/loclass.scons b/lib/loclass.scons new file mode 100644 index 00000000..ba43dd6e --- /dev/null +++ b/lib/loclass.scons @@ -0,0 +1,19 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/loclass", + ], + CPPDEFINES=[ + ], +) + + +libenv = env.Clone(FW_LIB_NAME="loclass") +libenv.ApplyLibFlags() + +sources = Glob("loclass/*.c", source=True) + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/loclass/optimized_cipher.c b/lib/loclass/optimized_cipher.c new file mode 100644 index 00000000..68b36af8 --- /dev/null +++ b/lib/loclass/optimized_cipher.c @@ -0,0 +1,338 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// 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. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +/* + This file contains an optimized version of the MAC-calculation algorithm. Some measurements on + a std laptop showed it runs in about 1/3 of the time: + + Std: 0.428962 + Opt: 0.151609 + + Additionally, it is self-reliant, not requiring e.g. bitstreams from the cipherutils, thus can + be easily dropped into a code base. + + The optimizations have been performed in the following steps: + * Parameters passed by reference instead of by value. + * Iteration instead of recursion, un-nesting recursive loops into for-loops. + * Handling of bytes instead of individual bits, for less shuffling and masking + * Less creation of "objects", structs, and instead reuse of alloc:ed memory + * Inlining some functions via #define:s + + As a consequence, this implementation is less generic. Also, I haven't bothered documenting this. + For a thorough documentation, check out the MAC-calculation within cipher.c instead. + + -- MHS 2015 +**/ + +/** + + The runtime of opt_doTagMAC_2() with the MHS optimized version was 403 microseconds on Proxmark3. + This was still to slow for some newer readers which didn't want to wait that long. + + Further optimizations to speedup the MAC calculations: + * Optimized opt_Tt logic + * Look up table for opt_select + * Removing many unnecessary bit maskings (& 0x1) + * updating state in place instead of alternating use of a second state structure + * remove the necessity to reverse bits of input and output bytes + + opt_doTagMAC_2() now completes in 270 microseconds. + + -- piwi 2019 +**/ + +/** + add the possibility to do iCLASS on device only + -- iceman 2020 +**/ + +#include "optimized_cipher.h" +#include "optimized_elite.h" +#include "optimized_ikeys.h" +#include "optimized_cipherutils.h" + +static const uint8_t opt_select_LUT[256] = { + 00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, + 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06, + 07, 04, 05, 06, 04, 05, 06, 07, 07, 04, 04, 07, 04, 05, 07, 06, + 06, 05, 04, 07, 04, 05, 06, 07, 02, 01, 01, 02, 00, 01, 03, 02, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 00, 03, 02, 01, 02, 03, 00, 01, 00, 03, 03, 00, 02, 03, 01, 00, + 05, 06, 07, 04, 06, 07, 04, 05, 05, 06, 06, 05, 06, 07, 05, 04, + 02, 01, 00, 03, 00, 01, 02, 03, 06, 05, 05, 06, 04, 05, 07, 06, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 02, 01, 00, 03, 00, 01, 02, 03, 02, 01, 01, 02, 00, 01, 03, 02, + 03, 00, 01, 02, 00, 01, 02, 03, 03, 00, 00, 03, 00, 01, 03, 02, + 04, 07, 06, 05, 06, 07, 04, 05, 00, 03, 03, 00, 02, 03, 01, 00, + 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, + 04, 07, 06, 05, 06, 07, 04, 05, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 01, 02, 02, 01, 02, 03, 01, 00 +}; + +/********************** the table above has been generated with this code: ******** +#include "util.h" +static void init_opt_select_LUT(void) { + for (int r = 0; r < 256; r++) { + uint8_t r_ls2 = r << 2; + uint8_t r_and_ls2 = r & r_ls2; + uint8_t r_or_ls2 = r | r_ls2; + uint8_t z0 = (r_and_ls2 >> 5) ^ ((r & ~r_ls2) >> 4) ^ ( r_or_ls2 >> 3); + uint8_t z1 = (r_or_ls2 >> 6) ^ ( r_or_ls2 >> 1) ^ (r >> 5) ^ r; + uint8_t z2 = ((r & ~r_ls2) >> 4) ^ (r_and_ls2 >> 3) ^ r; + opt_select_LUT[r] = (z0 & 4) | (z1 & 2) | (z2 & 1); + } + print_result("", opt_select_LUT, 256); +} +***********************************************************************************/ + +#define opt__select(x,y,r) (4 & (((r & (r << 2)) >> 5) ^ ((r & ~(r << 2)) >> 4) ^ ( (r | r << 2) >> 3)))\ + |(2 & (((r | r << 2) >> 6) ^ ( (r | r << 2) >> 1) ^ (r >> 5) ^ r ^ ((x^y) << 1)))\ + |(1 & (((r & ~(r << 2)) >> 4) ^ ((r & (r << 2)) >> 3) ^ r ^ x)) + +/* + * Some background on the expression above can be found here... +uint8_t xopt__select(bool x, bool y, uint8_t r) +{ + + //r: r0 r1 r2 r3 r4 r5 r6 r7 + //r_ls2: r2 r3 r4 r5 r6 r7 0 0 + // z0 + // z1 + +// uint8_t z0 = (r0 & r2) ^ (r1 & ~r3) ^ (r2 | r4); // <-- original + uint8_t z0 = (r_and_ls2 >> 5) ^ ((r & ~r_ls2) >> 4) ^ ( r_or_ls2 >> 3); + +// uint8_t z1 = (r0 | r2) ^ ( r5 | r7) ^ r1 ^ r6 ^ x ^ y; // <-- original + uint8_t z1 = (r_or_ls2 >> 6) ^ ( r_or_ls2 >> 1) ^ (r >> 5) ^ r ^ ((x^y) << 1); + +// uint8_t z2 = (r3 & ~r5) ^ (r4 & r6 ) ^ r7 ^ x; // <-- original + uint8_t z2 = ((r & ~r_ls2) >> 4) ^ (r_and_ls2 >> 3) ^ r ^ x; + + return (z0 & 4) | (z1 & 2) | (z2 & 1); +} +*/ + +static void opt_successor(const uint8_t *k, State_t *s, uint8_t y) { +// #define opt_T(s) (0x1 & ((s->t >> 15) ^ (s->t >> 14) ^ (s->t >> 10) ^ (s->t >> 8) ^ (s->t >> 5) ^ (s->t >> 4)^ (s->t >> 1) ^ s->t)) + // uint8_t Tt = opt_T(s); + uint16_t Tt = s->t & 0xc533; + Tt = Tt ^ (Tt >> 1); + Tt = Tt ^ (Tt >> 4); + Tt = Tt ^ (Tt >> 10); + Tt = Tt ^ (Tt >> 8); + + s->t = (s->t >> 1); + s->t |= (Tt ^ (s->r >> 7) ^ (s->r >> 3)) << 15; + + uint8_t opt_B = s->b; + opt_B ^= s->b >> 6; + opt_B ^= s->b >> 5; + opt_B ^= s->b >> 4; + + s->b = s->b >> 1; + s->b |= (opt_B ^ s->r) << 7; + + uint8_t opt_select = opt_select_LUT[s->r] & 0x04; + opt_select |= (opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02; + opt_select |= (opt_select_LUT[s->r] ^ Tt) & 0x01; + + uint8_t r = s->r; + s->r = (k[opt_select] ^ s->b) + s->l ; + s->l = s->r + r; +} + +static void opt_suc(const uint8_t *k, State_t *s, const uint8_t *in, uint8_t length, bool add32Zeroes) { + for (int i = 0; i < length; i++) { + uint8_t head; + head = in[i]; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + + head >>= 1; + opt_successor(k, s, head); + } + //For tag MAC, an additional 32 zeroes + if (add32Zeroes) { + for (int i = 0; i < 16; i++) { + opt_successor(k, s, 0); + opt_successor(k, s, 0); + } + } +} + +static void opt_output(const uint8_t *k, State_t *s, uint8_t *buffer) { + for (uint8_t times = 0; times < 4; times++) { + uint8_t bout = 0; + bout |= (s->r & 0x4) >> 2; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) >> 1; + opt_successor(k, s, 0); + bout |= (s->r & 0x4); + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 1; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 2; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 3; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 4; + opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 5; + opt_successor(k, s, 0); + buffer[times] = bout; + } +} + +static void opt_MAC(uint8_t *k, uint8_t *input, uint8_t *out) { + State_t _init = { + ((k[0] ^ 0x4c) + 0xEC) & 0xFF,// l + ((k[0] ^ 0x4c) + 0x21) & 0xFF,// r + 0x4c, // b + 0xE012 // t + }; + + opt_suc(k, &_init, input, 12, false); + opt_output(k, &_init, out); +} + +static void opt_MAC_N(uint8_t *k, uint8_t *input, uint8_t in_size, uint8_t *out) { + State_t _init = { + ((k[0] ^ 0x4c) + 0xEC) & 0xFF,// l + ((k[0] ^ 0x4c) + 0x21) & 0xFF,// r + 0x4c, // b + 0xE012 // t + }; + + opt_suc(k, &_init, input, in_size, false); + opt_output(k, &_init, out); +} + +void opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]) { + uint8_t dest [] = {0, 0, 0, 0, 0, 0, 0, 0}; + opt_MAC(div_key_p, cc_nr_p, dest); + memcpy(mac, dest, 4); +} + +void opt_doReaderMAC_2(State_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { + opt_suc(div_key_p, &_init, nr, 4, false); + opt_output(div_key_p, &_init, mac); +} + + +void doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]) { + uint8_t dest [] = {0, 0, 0, 0, 0, 0, 0, 0}; + opt_MAC_N(div_key_p, in_p, in_size, dest); + memcpy(mac, dest, 4); +} + +void opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]) { + State_t _init = { + ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF,// l + ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF,// r + 0x4c, // b + 0xE012 // t + }; + opt_suc(div_key_p, &_init, cc_p, 12, true); + opt_output(div_key_p, &_init, mac); +} + +/** + * The tag MAC can be divided (both can, but no point in dividing the reader mac) into + * two functions, since the first 8 bytes are known, we can pre-calculate the state + * reached after feeding CC to the cipher. + * @param cc_p + * @param div_key_p + * @return the cipher state + */ +State_t opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p) { + State_t _init = { + ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF,// l + ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF,// r + 0x4c, // b + 0xE012 // t + }; + opt_suc(div_key_p, &_init, cc_p, 8, false); + return _init; +} + +/** + * The second part of the tag MAC calculation, since the CC is already calculated into the state, + * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag + * MAC response. + * @param _init - precalculated cipher state + * @param nr - the reader challenge + * @param mac - where to store the MAC + * @param div_key_p - the key to use + */ +void opt_doTagMAC_2(State_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { + opt_suc(div_key_p, &_init, nr, 4, true); + opt_output(div_key_p, &_init, mac); +} + + +void iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite) { + if (elite) { + uint8_t keytable[128] = {0}; + uint8_t key_index[8] = {0}; + uint8_t key_sel[8] = { 0 }; + uint8_t key_sel_p[8] = { 0 }; + hash2(key, keytable); + hash1(csn, key_index); + for (uint8_t i = 0; i < 8 ; i++) + key_sel[i] = keytable[key_index[i]]; + + //Permute from iclass format to standard format + permutekey_rev(key_sel, key_sel_p); + diversifyKey(csn, key_sel_p, div_key); + } else { + diversifyKey(csn, key, div_key); + } +} diff --git a/lib/loclass/optimized_cipher.h b/lib/loclass/optimized_cipher.h new file mode 100644 index 00000000..06f85b08 --- /dev/null +++ b/lib/loclass/optimized_cipher.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// 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. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef OPTIMIZED_CIPHER_H +#define OPTIMIZED_CIPHER_H +#include +#include +#include +#include + +/** +* Definition 1 (Cipher state). A cipher state of iClass s is an element of F 40/2 +* consisting of the following four components: +* 1. the left register l = (l 0 . . . l 7 ) ∈ F 8/2 ; +* 2. the right register r = (r 0 . . . r 7 ) ∈ F 8/2 ; +* 3. the top register t = (t 0 . . . t 15 ) ∈ F 16/2 . +* 4. the bottom register b = (b 0 . . . b 7 ) ∈ F 8/2 . +**/ +typedef struct { + uint8_t l; + uint8_t r; + uint8_t b; + uint16_t t; +} State_t; + +/** The reader MAC is MAC(key, CC * NR ) + **/ +void opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]); + +void opt_doReaderMAC_2(State_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); + +/** + * The tag MAC is MAC(key, CC * NR * 32x0)) + */ +void opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]); + +/** + * The tag MAC can be divided (both can, but no point in dividing the reader mac) into + * two functions, since the first 8 bytes are known, we can pre-calculate the state + * reached after feeding CC to the cipher. + * @param cc_p + * @param div_key_p + * @return the cipher state + */ +State_t opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p); +/** + * The second part of the tag MAC calculation, since the CC is already calculated into the state, + * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag + * MAC response. + * @param _init - precalculated cipher state + * @param nr - the reader challenge + * @param mac - where to store the MAC + * @param div_key_p - the key to use + */ +void opt_doTagMAC_2(State_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); + +void doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]); +void iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite); +#endif // OPTIMIZED_CIPHER_H diff --git a/lib/loclass/optimized_cipherutils.c b/lib/loclass/optimized_cipherutils.c new file mode 100644 index 00000000..299d1480 --- /dev/null +++ b/lib/loclass/optimized_cipherutils.c @@ -0,0 +1,137 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// 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. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#include "optimized_cipherutils.h" +#include + +/** + * + * @brief Return and remove the first bit (x0) in the stream : + * @param stream + * @return + */ +bool headBit(BitstreamIn_t *stream) { + int bytepos = stream->position >> 3; // divide by 8 + int bitpos = (stream->position++) & 7; // mask out 00000111 + return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; +} +/** + * @brief Return and remove the last bit (xn) in the stream: + * @param stream + * @return + */ +bool tailBit(BitstreamIn_t *stream) { + int bitpos = stream->numbits - 1 - (stream->position++); + + int bytepos = bitpos >> 3; + bitpos &= 7; + return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; +} +/** + * @brief Pushes bit onto the stream + * @param stream + * @param bit + */ +void pushBit(BitstreamOut_t *stream, bool bit) { + int bytepos = stream->position >> 3; // divide by 8 + int bitpos = stream->position & 7; + *(stream->buffer + bytepos) |= (bit) << (7 - bitpos); + stream->position++; + stream->numbits++; +} + +/** + * @brief Pushes the lower six bits onto the stream + * as b0 b1 b2 b3 b4 b5 b6 + * @param stream + * @param bits + */ +void push6bits(BitstreamOut_t *stream, uint8_t bits) { + pushBit(stream, bits & 0x20); + pushBit(stream, bits & 0x10); + pushBit(stream, bits & 0x08); + pushBit(stream, bits & 0x04); + pushBit(stream, bits & 0x02); + pushBit(stream, bits & 0x01); +} + +/** + * @brief bitsLeft + * @param stream + * @return number of bits left in stream + */ +int bitsLeft(BitstreamIn_t *stream) { + return stream->numbits - stream->position; +} +/** + * @brief numBits + * @param stream + * @return Number of bits stored in stream + */ +void x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest) { + while (len--) { + dest[len] = (uint8_t) n; + n >>= 8; + } +} + +uint64_t x_bytes_to_num(uint8_t *src, size_t len) { + uint64_t num = 0; + while (len--) { + num = (num << 8) | (*src); + src++; + } + return num; +} + +uint8_t reversebytes(uint8_t b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} + +void reverse_arraybytes(uint8_t *arr, size_t len) { + uint8_t i; + for (i = 0; i < len ; i++) { + arr[i] = reversebytes(arr[i]); + } +} + +void reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len) { + uint8_t i; + for (i = 0; i < len ; i++) { + dest[i] = reversebytes(arr[i]); + } +} + diff --git a/lib/loclass/optimized_cipherutils.h b/lib/loclass/optimized_cipherutils.h new file mode 100644 index 00000000..07bb012f --- /dev/null +++ b/lib/loclass/optimized_cipherutils.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// 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. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef CIPHERUTILS_H +#define CIPHERUTILS_H +#include +#include +#include + +typedef struct { + uint8_t *buffer; + uint8_t numbits; + uint8_t position; +} BitstreamIn_t; + +typedef struct { + uint8_t *buffer; + uint8_t numbits; + uint8_t position; +} BitstreamOut_t; + +bool headBit(BitstreamIn_t *stream); +bool tailBit(BitstreamIn_t *stream); +void pushBit(BitstreamOut_t *stream, bool bit); +int bitsLeft(BitstreamIn_t *stream); + +void push6bits(BitstreamOut_t *stream, uint8_t bits); +void x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest); +uint64_t x_bytes_to_num(uint8_t *src, size_t len); +uint8_t reversebytes(uint8_t b); +void reverse_arraybytes(uint8_t *arr, size_t len); +void reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len); +#endif // CIPHERUTILS_H diff --git a/lib/loclass/optimized_elite.c b/lib/loclass/optimized_elite.c new file mode 100644 index 00000000..8940a51e --- /dev/null +++ b/lib/loclass/optimized_elite.c @@ -0,0 +1,234 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// 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. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#include "optimized_elite.h" + +#include +#include +#include +#include +#include "optimized_ikeys.h" + +/** + * @brief Permutes a key from standard NIST format to Iclass specific format + * from http://www.proxmark.org/forum/viewtopic.php?pid=11220#p11220 + * + * If you permute [6c 8d 44 f9 2a 2d 01 bf] you get [8a 0d b9 88 bb a7 90 ea] as shown below. + * + * 1 0 1 1 1 1 1 1 bf + * 0 0 0 0 0 0 0 1 01 + * 0 0 1 0 1 1 0 1 2d + * 0 0 1 0 1 0 1 0 2a + * 1 1 1 1 1 0 0 1 f9 + * 0 1 0 0 0 1 0 0 44 + * 1 0 0 0 1 1 0 1 8d + * 0 1 1 0 1 1 0 0 6c + * + * 8 0 b 8 b a 9 e + * a d 9 8 b 7 0 a + * + * @param key + * @param dest + */ +void permutekey(const uint8_t key[8], uint8_t dest[8]) { + int i; + for (i = 0 ; i < 8 ; i++) { + dest[i] = (((key[7] & (0x80 >> i)) >> (7 - i)) << 7) | + (((key[6] & (0x80 >> i)) >> (7 - i)) << 6) | + (((key[5] & (0x80 >> i)) >> (7 - i)) << 5) | + (((key[4] & (0x80 >> i)) >> (7 - i)) << 4) | + (((key[3] & (0x80 >> i)) >> (7 - i)) << 3) | + (((key[2] & (0x80 >> i)) >> (7 - i)) << 2) | + (((key[1] & (0x80 >> i)) >> (7 - i)) << 1) | + (((key[0] & (0x80 >> i)) >> (7 - i)) << 0); + } +} +/** + * Permutes a key from iclass specific format to NIST format + * @brief permutekey_rev + * @param key + * @param dest + */ +void permutekey_rev(const uint8_t key[8], uint8_t dest[8]) { + int i; + for (i = 0 ; i < 8 ; i++) { + dest[7 - i] = (((key[0] & (0x80 >> i)) >> (7 - i)) << 7) | + (((key[1] & (0x80 >> i)) >> (7 - i)) << 6) | + (((key[2] & (0x80 >> i)) >> (7 - i)) << 5) | + (((key[3] & (0x80 >> i)) >> (7 - i)) << 4) | + (((key[4] & (0x80 >> i)) >> (7 - i)) << 3) | + (((key[5] & (0x80 >> i)) >> (7 - i)) << 2) | + (((key[6] & (0x80 >> i)) >> (7 - i)) << 1) | + (((key[7] & (0x80 >> i)) >> (7 - i)) << 0); + } +} + +/** + * Helper function for hash1 + * @brief rr + * @param val + * @return + */ +static uint8_t rr(uint8_t val) { + return val >> 1 | ((val & 1) << 7); +} + +/** + * Helper function for hash1 + * @brief rl + * @param val + * @return + */ +static uint8_t rl(uint8_t val) { + return val << 1 | ((val & 0x80) >> 7); +} + +/** + * Helper function for hash1 + * @brief swap + * @param val + * @return + */ +static uint8_t swap(uint8_t val) { + return ((val >> 4) & 0xFF) | ((val & 0xFF) << 4); +} + +/** + * Hash1 takes CSN as input, and determines what bytes in the keytable will be used + * when constructing the K_sel. + * @param csn the CSN used + * @param k output + */ +void hash1(const uint8_t csn[], uint8_t k[]) { + k[0] = csn[0] ^ csn[1] ^ csn[2] ^ csn[3] ^ csn[4] ^ csn[5] ^ csn[6] ^ csn[7]; + k[1] = csn[0] + csn[1] + csn[2] + csn[3] + csn[4] + csn[5] + csn[6] + csn[7]; + k[2] = rr(swap(csn[2] + k[1])); + k[3] = rl(swap(csn[3] + k[0])); + k[4] = ~rr(csn[4] + k[2]) + 1; + k[5] = ~rl(csn[5] + k[3]) + 1; + k[6] = rr(csn[6] + (k[4] ^ 0x3c)); + k[7] = rl(csn[7] + (k[5] ^ 0xc3)); + + k[7] &= 0x7F; + k[6] &= 0x7F; + k[5] &= 0x7F; + k[4] &= 0x7F; + k[3] &= 0x7F; + k[2] &= 0x7F; + k[1] &= 0x7F; + k[0] &= 0x7F; +} +/** +Definition 14. Define the rotate key function rk : (F 82 ) 8 × N → (F 82 ) 8 as +rk(x [0] . . . x [7] , 0) = x [0] . . . x [7] +rk(x [0] . . . x [7] , n + 1) = rk(rl(x [0] ) . . . rl(x [7] ), n) +**/ +static void rk(uint8_t *key, uint8_t n, uint8_t *outp_key) { + memcpy(outp_key, key, 8); + uint8_t j; + while (n-- > 0) { + for (j = 0; j < 8 ; j++) + outp_key[j] = rl(outp_key[j]); + } + return; +} + +static mbedtls_des_context ctx_enc; +static mbedtls_des_context ctx_dec; + +static void desdecrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { + uint8_t key_std_format[8] = {0}; + permutekey_rev(iclass_key, key_std_format); + mbedtls_des_setkey_dec(&ctx_dec, key_std_format); + mbedtls_des_crypt_ecb(&ctx_dec, input, output); +} + +static void desencrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { + uint8_t key_std_format[8] = {0}; + permutekey_rev(iclass_key, key_std_format); + mbedtls_des_setkey_enc(&ctx_enc, key_std_format); + mbedtls_des_crypt_ecb(&ctx_enc, input, output); +} + +/** + * @brief Insert uint8_t[8] custom master key to calculate hash2 and return key_select. + * @param key unpermuted custom key + * @param hash1 hash1 + * @param key_sel output key_sel=h[hash1[i]] + */ +void hash2(uint8_t *key64, uint8_t *outp_keytable) { + /** + *Expected: + * High Security Key Table + + 00 F1 35 59 A1 0D 5A 26 7F 18 60 0B 96 8A C0 25 C1 + 10 BF A1 3B B0 FF 85 28 75 F2 1F C6 8F 0E 74 8F 21 + 20 14 7A 55 16 C8 A9 7D B3 13 0C 5D C9 31 8D A9 B2 + 30 A3 56 83 0F 55 7E DE 45 71 21 D2 6D C1 57 1C 9C + 40 78 2F 64 51 42 7B 64 30 FA 26 51 76 D3 E0 FB B6 + 50 31 9F BF 2F 7E 4F 94 B4 BD 4F 75 91 E3 1B EB 42 + 60 3F 88 6F B8 6C 2C 93 0D 69 2C D5 20 3C C1 61 95 + 70 43 08 A0 2F FE B3 26 D7 98 0B 34 7B 47 70 A0 AB + + **** The 64-bit HS Custom Key Value = 5B7C62C491C11B39 ******/ + uint8_t key64_negated[8] = {0}; + uint8_t z[8][8] = {{0}, {0}}; + uint8_t temp_output[8] = {0}; + + //calculate complement of key + int i; + for (i = 0; i < 8; i++) + key64_negated[i] = ~key64[i]; + + // Once again, key is on iclass-format + desencrypt_iclass(key64, key64_negated, z[0]); + + uint8_t y[8][8] = {{0}, {0}}; + + // y[0]=DES_dec(z[0],~key) + // Once again, key is on iclass-format + desdecrypt_iclass(z[0], key64_negated, y[0]); + + for (i = 1; i < 8; i++) { + rk(key64, i, temp_output); + desdecrypt_iclass(temp_output, z[i - 1], z[i]); + desencrypt_iclass(temp_output, y[i - 1], y[i]); + } + + if (outp_keytable != NULL) { + for (i = 0 ; i < 8 ; i++) { + memcpy(outp_keytable + i * 16, y[i], 8); + memcpy(outp_keytable + 8 + i * 16, z[i], 8); + } + } +} diff --git a/lib/loclass/optimized_elite.h b/lib/loclass/optimized_elite.h new file mode 100644 index 00000000..f1b41d54 --- /dev/null +++ b/lib/loclass/optimized_elite.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// 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. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef ELITE_CRACK_H +#define ELITE_CRACK_H + +#include +#include + +void permutekey(const uint8_t key[8], uint8_t dest[8]); +/** + * Permutes a key from iclass specific format to NIST format + * @brief permutekey_rev + * @param key + * @param dest + */ +void permutekey_rev(const uint8_t key[8], uint8_t dest[8]); +/** + * Hash1 takes CSN as input, and determines what bytes in the keytable will be used + * when constructing the K_sel. + * @param csn the CSN used + * @param k output + */ +void hash1(const uint8_t *csn, uint8_t *k); +void hash2(uint8_t *key64, uint8_t *outp_keytable); + +#endif diff --git a/lib/loclass/optimized_ikeys.c b/lib/loclass/optimized_ikeys.c new file mode 100644 index 00000000..ec414f1a --- /dev/null +++ b/lib/loclass/optimized_ikeys.c @@ -0,0 +1,321 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// 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. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- + +/** +From "Dismantling iclass": + This section describes in detail the built-in key diversification algorithm of iClass. + Besides the obvious purpose of deriving a card key from a master key, this + algorithm intends to circumvent weaknesses in the cipher by preventing the + usage of certain ‘weak’ keys. In order to compute a diversified key, the iClass + reader first encrypts the card identity id with the master key K, using single + DES. The resulting ciphertext is then input to a function called hash0 which + outputs the diversified key k. + + k = hash0(DES enc (id, K)) + + Here the DES encryption of id with master key K outputs a cryptogram c + of 64 bits. These 64 bits are divided as c = x, y, z [0] , . . . , z [7] ∈ F 82 × F 82 × (F 62 ) 8 + which is used as input to the hash0 function. This function introduces some + obfuscation by performing a number of permutations, complement and modulo + operations, see Figure 2.5. Besides that, it checks for and removes patterns like + similar key bytes, which could produce a strong bias in the cipher. Finally, the + output of hash0 is the diversified card key k = k [0] , . . . , k [7] ∈ (F 82 ) 8 . + +**/ +#include "optimized_ikeys.h" + +#include +#include +#include +#include +#include "optimized_cipherutils.h" + +static uint8_t pi[35] = { + 0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D, + 0x2E, 0x33, 0x35, 0x39, 0x36, 0x3A, 0x3C, 0x47, + 0x4B, 0x4D, 0x4E, 0x53, 0x55, 0x56, 0x59, 0x5A, + 0x5C, 0x63, 0x65, 0x66, 0x69, 0x6A, 0x6C, 0x71, + 0x72, 0x74, 0x78 +}; + +static mbedtls_des_context ctx_enc; + +/** + * @brief The key diversification algorithm uses 6-bit bytes. + * This implementation uses 64 bit uint to pack seven of them into one + * variable. When they are there, they are placed as follows: + * XXXX XXXX N0 .... N7, occupying the last 48 bits. + * + * This function picks out one from such a collection + * @param all + * @param n bitnumber + * @return + */ +static uint8_t getSixBitByte(uint64_t c, int n) { + return (c >> (42 - 6 * n)) & 0x3F; +} + +/** + * @brief Puts back a six-bit 'byte' into a uint64_t. + * @param c buffer + * @param z the value to place there + * @param n bitnumber. + */ +static void pushbackSixBitByte(uint64_t *c, uint8_t z, int n) { + //0x XXXX YYYY ZZZZ ZZZZ ZZZZ + // ^z0 ^z7 + //z0: 1111 1100 0000 0000 + + uint64_t masked = z & 0x3F; + uint64_t eraser = 0x3F; + masked <<= 42 - 6 * n; + eraser <<= 42 - 6 * n; + + //masked <<= 6*n; + //eraser <<= 6*n; + + eraser = ~eraser; + (*c) &= eraser; + (*c) |= masked; + +} +/** + * @brief Swaps the z-values. + * If the input value has format XYZ0Z1...Z7, the output will have the format + * XYZ7Z6...Z0 instead + * @param c + * @return + */ +static uint64_t swapZvalues(uint64_t c) { + uint64_t newz = 0; + pushbackSixBitByte(&newz, getSixBitByte(c, 0), 7); + pushbackSixBitByte(&newz, getSixBitByte(c, 1), 6); + pushbackSixBitByte(&newz, getSixBitByte(c, 2), 5); + pushbackSixBitByte(&newz, getSixBitByte(c, 3), 4); + pushbackSixBitByte(&newz, getSixBitByte(c, 4), 3); + pushbackSixBitByte(&newz, getSixBitByte(c, 5), 2); + pushbackSixBitByte(&newz, getSixBitByte(c, 6), 1); + pushbackSixBitByte(&newz, getSixBitByte(c, 7), 0); + newz |= (c & 0xFFFF000000000000); + return newz; +} + +/** +* @return 4 six-bit bytes chunked into a uint64_t,as 00..00a0a1a2a3 +*/ +static uint64_t ck(int i, int j, uint64_t z) { + if (i == 1 && j == -1) { + // ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] + return z; + } else if (j == -1) { + // ck(i, −1, z [0] . . . z [3] ) = ck(i − 1, i − 2, z [0] . . . z [3] ) + return ck(i - 1, i - 2, z); + } + + if (getSixBitByte(z, i) == getSixBitByte(z, j)) { + //ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ) + uint64_t newz = 0; + int c; + for (c = 0; c < 4; c++) { + uint8_t val = getSixBitByte(z, c); + if (c == i) + pushbackSixBitByte(&newz, j, c); + else + pushbackSixBitByte(&newz, val, c); + } + return ck(i, j - 1, newz); + } else { + return ck(i, j - 1, z); + } +} +/** + + Definition 8. + Let the function check : (F 62 ) 8 → (F 62 ) 8 be defined as + check(z [0] . . . z [7] ) = ck(3, 2, z [0] . . . z [3] ) · ck(3, 2, z [4] . . . z [7] ) + + where ck : N × N × (F 62 ) 4 → (F 62 ) 4 is defined as + + ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] + ck(i, −1, z [0] . . . z [3] ) = ck(i − 1, i − 2, z [0] . . . z [3] ) + ck(i, j, z [0] . . . z [3] ) = + ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ), if z [i] = z [j] ; + ck(i, j − 1, z [0] . . . z [3] ), otherwise + + otherwise. +**/ + +static uint64_t check(uint64_t z) { + //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] + + // ck(3, 2, z [0] . . . z [3] ) + uint64_t ck1 = ck(3, 2, z); + + // ck(3, 2, z [4] . . . z [7] ) + uint64_t ck2 = ck(3, 2, z << 24); + + //The ck function will place the values + // in the middle of z. + ck1 &= 0x00000000FFFFFF000000; + ck2 &= 0x00000000FFFFFF000000; + + return ck1 | ck2 >> 24; +} + +static void permute(BitstreamIn_t *p_in, uint64_t z, int l, int r, BitstreamOut_t *out) { + if (bitsLeft(p_in) == 0) + return; + + bool pn = tailBit(p_in); + if (pn) { // pn = 1 + uint8_t zl = getSixBitByte(z, l); + + push6bits(out, zl + 1); + permute(p_in, z, l + 1, r, out); + } else { // otherwise + uint8_t zr = getSixBitByte(z, r); + + push6bits(out, zr); + permute(p_in, z, l, r + 1, out); + } +} + +/** + * @brief + *Definition 11. Let the function hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as + * hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where + * z'[i] = (z[i] mod (63-i)) + i i = 0...3 + * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 + * ẑ = check(z'); + * @param c + * @param k this is where the diversified key is put (should be 8 bytes) + * @return + */ +void hash0(uint64_t c, uint8_t k[8]) { + c = swapZvalues(c); + + //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] + // x = 8 bits + // y = 8 bits + // z0-z7 6 bits each : 48 bits + uint8_t x = (c & 0xFF00000000000000) >> 56; + uint8_t y = (c & 0x00FF000000000000) >> 48; + uint64_t zP = 0; + + for (int n = 0; n < 4 ; n++) { + uint8_t zn = getSixBitByte(c, n); + uint8_t zn4 = getSixBitByte(c, n + 4); + uint8_t _zn = (zn % (63 - n)) + n; + uint8_t _zn4 = (zn4 % (64 - n)) + n; + pushbackSixBitByte(&zP, _zn, n); + pushbackSixBitByte(&zP, _zn4, n + 4); + } + + uint64_t zCaret = check(zP); + uint8_t p = pi[x % 35]; + + if (x & 1) //Check if x7 is 1 + p = ~p; + + BitstreamIn_t p_in = { &p, 8, 0 }; + uint8_t outbuffer[] = {0, 0, 0, 0, 0, 0, 0, 0}; + BitstreamOut_t out = {outbuffer, 0, 0}; + permute(&p_in, zCaret, 0, 4, &out); //returns 48 bits? or 6 8-bytes + + //Out is now a buffer containing six-bit bytes, should be 48 bits + // if all went well + //Shift z-values down onto the lower segment + + uint64_t zTilde = x_bytes_to_num(outbuffer, sizeof(outbuffer)); + + zTilde >>= 16; + + for (int i = 0; i < 8; i++) { + // the key on index i is first a bit from y + // then six bits from z, + // then a bit from p + + // Init with zeroes + k[i] = 0; + // First, place yi leftmost in k + //k[i] |= (y << i) & 0x80 ; + + // First, place y(7-i) leftmost in k + k[i] |= (y << (7 - i)) & 0x80 ; + + uint8_t zTilde_i = getSixBitByte(zTilde, i); + // zTildeI is now on the form 00XXXXXX + // with one leftshift, it'll be + // 0XXXXXX0 + // So after leftshift, we can OR it into k + // However, when doing complement, we need to + // again MASK 0XXXXXX0 (0x7E) + zTilde_i <<= 1; + + //Finally, add bit from p or p-mod + //Shift bit i into rightmost location (mask only after complement) + uint8_t p_i = p >> i & 0x1; + + if (k[i]) { // yi = 1 + k[i] |= ~zTilde_i & 0x7E; + k[i] |= p_i & 1; + k[i] += 1; + + } else { // otherwise + k[i] |= zTilde_i & 0x7E; + k[i] |= (~p_i) & 1; + } + } +} +/** + * @brief Performs Elite-class key diversification + * @param csn + * @param key + * @param div_key + */ +void diversifyKey(uint8_t *csn, uint8_t *key, uint8_t *div_key) { + // Prepare the DES key + mbedtls_des_setkey_enc(&ctx_enc, key); + + uint8_t crypted_csn[8] = {0}; + + // Calculate DES(CSN, KEY) + mbedtls_des_crypt_ecb(&ctx_enc, csn, crypted_csn); + + //Calculate HASH0(DES)) + uint64_t c_csn = x_bytes_to_num(crypted_csn, sizeof(crypted_csn)); + + hash0(c_csn, div_key); +} + diff --git a/lib/loclass/optimized_ikeys.h b/lib/loclass/optimized_ikeys.h new file mode 100644 index 00000000..fd990cac --- /dev/null +++ b/lib/loclass/optimized_ikeys.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// 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. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef IKEYS_H +#define IKEYS_H + +#include + +/** + * @brief + *Definition 11. Let the function hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as + * hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where + * z'[i] = (z[i] mod (63-i)) + i i = 0...3 + * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 + * ẑ = check(z'); + * @param c + * @param k this is where the diversified key is put (should be 8 bytes) + * @return + */ +void hash0(uint64_t c, uint8_t k[8]); +/** + * @brief Performs Elite-class key diversification + * @param csn + * @param key + * @param div_key + */ + +void diversifyKey(uint8_t *csn, uint8_t *key, uint8_t *div_key); +/** + * @brief Permutes a key from standard NIST format to Iclass specific format + * @param key + * @param dest + */ + +#endif // IKEYS_H diff --git a/lib/mbedtls b/lib/mbedtls new file mode 160000 index 00000000..d65aeb37 --- /dev/null +++ b/lib/mbedtls @@ -0,0 +1 @@ +Subproject commit d65aeb37349ad1a50e0f6c9b694d4b5290d60e49 diff --git a/lib/mbedtls.scons b/lib/mbedtls.scons new file mode 100644 index 00000000..35de7e6f --- /dev/null +++ b/lib/mbedtls.scons @@ -0,0 +1,20 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/mbedtls", + "#/lib/mbedtls/include", + ], + CPPDEFINES=[ + ], +) + + +libenv = env.Clone(FW_LIB_NAME="mbedtls") +libenv.ApplyLibFlags() + +sources = ["mbedtls/library/des.c", "mbedtls/library/platform_util.c"] + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") From 5769595e67e0a0b90253559461bce669b2ec5a90 Mon Sep 17 00:00:00 2001 From: gornekich Date: Sun, 3 Jul 2022 17:51:50 +0300 Subject: [PATCH 04/26] NFC emulation software tunning (#1341) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * digital_signal: optimize calculationxx * firmware: add listen start and listen rx * digital signal: rework with fixed point calculation * nfc: tune timings * nfc: fix array overflow * mifare classic: fix key access * nfc: rework spi bus access * nfc: rework listen mode with st25r3916 calls * digital signal: speed up digital_signal_append() * digital signal: remove unused profiling * nfc: clean up code * nfc: correct sleep state * nfc: add unit tests * nfc: fix memory leak in unit test * digital_signal: remove unused code * nfc: fix incorrect sak load in pt memory Co-authored-by: あく --- applications/nfc/nfc_worker.c | 10 +- applications/unit_tests/nfc/nfc_test.c | 184 ++++++++++++++++++ applications/unit_tests/test_index.c | 2 + .../unit_tests/nfc/nfc_nfca_signal_long.nfc | 6 + .../unit_tests/nfc/nfc_nfca_signal_short.nfc | 6 + firmware/targets/f7/furi_hal/furi_hal_nfc.c | 119 ++++++++++- .../targets/f7/furi_hal/furi_hal_spi_config.c | 68 ++++++- .../targets/furi_hal_include/furi_hal_nfc.h | 33 ++++ lib/ST25RFAL002/platform.c | 26 ++- lib/ST25RFAL002/platform.h | 2 + lib/digital_signal/digital_signal.c | 46 ++--- lib/digital_signal/digital_signal.h | 8 +- lib/nfc_protocols/mifare_classic.c | 34 ++-- lib/nfc_protocols/nfca.c | 13 +- 14 files changed, 496 insertions(+), 61 deletions(-) create mode 100644 applications/unit_tests/nfc/nfc_test.c create mode 100644 assets/unit_tests/nfc/nfc_nfca_signal_long.nfc create mode 100644 assets/unit_tests/nfc/nfc_nfca_signal_short.nfc diff --git a/applications/nfc/nfc_worker.c b/applications/nfc/nfc_worker.c index 176b15c6..7d78fa74 100644 --- a/applications/nfc/nfc_worker.c +++ b/applications/nfc/nfc_worker.c @@ -1,6 +1,8 @@ #include "nfc_worker_i.h" #include +#include + #define TAG "NfcWorker" /***************************** NFC Worker API *******************************/ @@ -495,9 +497,11 @@ void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) { 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 == NfcWorkerStateEmulateMifareClassic) { - if(furi_hal_nfc_listen( - nfc_data->uid, nfc_data->uid_len, nfc_data->atqa, nfc_data->sak, true, 300)) { + if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { mf_classic_emulator(&emulator, &tx_rx); } } @@ -510,6 +514,8 @@ void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) { } nfca_signal_free(nfca_signal); + + rfal_platform_spi_release(); } void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) { diff --git a/applications/unit_tests/nfc/nfc_test.c b/applications/unit_tests/nfc/nfc_test.c new file mode 100644 index 00000000..a2262735 --- /dev/null +++ b/applications/unit_tests/nfc/nfc_test.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../minunit.h" + +#define TAG "NfcTest" + +#define NFC_TEST_RESOURCES_DIR "/ext/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" + +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 + +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("storage"); +} + +static void nfc_test_free() { + furi_assert(nfc_test); + + furi_record_close("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); + string_t file_type; + string_init(file_type); + 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(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); + + string_clear(file_type); + flipper_format_free(file); + + return success; +} + +static bool nfc_test_digital_signal_test_encode( + const char* file_name, + uint32_t encode_max_time, + uint32_t timing_tolerance, + uint32_t timings_sum_tolerance) { + furi_assert(nfc_test); + + bool success = false; + uint32_t time = 0; + 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)) break; + + // Encode signal + FURI_CRITICAL_ENTER(); + time = DWT->CYCCNT; + nfca_signal_encode( + nfc_test->signal, nfc_test->test_data, nfc_test->test_data_len * 8, parity); + digital_signal_prepare_arr(nfc_test->signal->tx_signal); + time = (DWT->CYCCNT - time) / furi_hal_delay_instructions_per_microsecond(); + FURI_CRITICAL_EXIT(); + + // Check timings + if(time > encode_max_time) { + FURI_LOG_E( + TAG, "Encoding time: %d us while accepted value: %d us", time, encode_max_time); + 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 differece in %d timings. Ref: %d, DUT: %d", 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: %d, DUT: %d", + ref_timings_sum, + dut_timings_sum); + break; + } + + FURI_LOG_I(TAG, "Encoding time: %d us. Acceptable time: %d us", time, encode_max_time); + FURI_LOG_I( + TAG, + "Timings sum difference: %d [1/64MHZ]. Acceptable difference: %d [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, 500, 1, 37), + "NFC short digital signal test failed\r\n"); + mu_assert( + nfc_test_digital_signal_test_encode( + NFC_TEST_RESOURCES_DIR NFC_TEST_SIGNAL_LONG_FILE, 2000, 1, 37), + "NFC long digital signal test failed\r\n"); +} + +MU_TEST_SUITE(nfc) { + nfc_test_alloc(); + + MU_RUN_TEST(nfc_digital_signal_test); + + nfc_test_free(); +} + +int run_minunit_test_nfc() { + MU_RUN_SUITE(nfc); + return MU_EXIT_CODE; +} diff --git a/applications/unit_tests/test_index.c b/applications/unit_tests/test_index.c index d8a8174c..8ff27b4d 100644 --- a/applications/unit_tests/test_index.c +++ b/applications/unit_tests/test_index.c @@ -19,6 +19,7 @@ int run_minunit_test_stream(); int run_minunit_test_storage(); int run_minunit_test_subghz(); int run_minunit_test_dirwalk(); +int run_minunit_test_nfc(); void minunit_print_progress(void) { static char progress[] = {'\\', '|', '/', '-'}; @@ -67,6 +68,7 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { test_result |= run_minunit_test_infrared_decoder_encoder(); test_result |= run_minunit_test_rpc(); test_result |= run_minunit_test_subghz(); + test_result |= run_minunit_test_nfc(); cycle_counter = (furi_hal_get_tick() - cycle_counter); diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc b/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc new file mode 100644 index 00000000..fae69cb5 --- /dev/null +++ b/assets/unit_tests/nfc/nfc_nfca_signal_long.nfc @@ -0,0 +1,6 @@ +Filetype: Flipper NFC test +Version: 1 +Data length: 18 +Plain data: f1 99 41 43 a1 2f 23 01 de f3 c5 8d 91 4b 1e 50 4a c9 +Timings length: 1304 +Timings: 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 640 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 641 37 36 37 37 37 36 37 37 37 36 37 37 37 37 36 641 37 37 36 37 37 37 36 37 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 0 diff --git a/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc b/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc new file mode 100644 index 00000000..3b7e2d9e --- /dev/null +++ b/assets/unit_tests/nfc/nfc_nfca_signal_short.nfc @@ -0,0 +1,6 @@ +Filetype: Flipper NFC test +Version: 1 +Data length: 4 +Plain data: 14 d8 a0 c9 +Timings length: 296 +Timings: 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 339 37 37 36 37 37 37 36 339 37 37 37 36 37 37 37 338 37 37 37 36 37 37 37 338 37 37 37 37 36 37 37 339 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 641 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 339 37 37 36 37 37 37 37 640 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 339 36 37 37 37 36 37 37 339 37 36 37 37 37 36 37 339 37 36 37 37 37 36 37 339 37 37 36 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 641 36 37 37 37 36 37 37 37 36 37 37 37 36 37 37 641 37 36 37 37 37 36 37 339 37 36 37 37 37 37 36 37 37 37 36 37 37 37 36 641 37 37 36 37 37 37 37 338 37 37 37 36 37 37 37 36 37 37 37 36 37 37 37 339 36 37 37 37 36 37 37 641 36 37 37 37 37 36 37 0 diff --git a/firmware/targets/f7/furi_hal/furi_hal_nfc.c b/firmware/targets/f7/furi_hal/furi_hal_nfc.c index cba2a703..9723dea1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_nfc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_nfc.c @@ -8,6 +8,9 @@ #include #include +#include +#include +#include #define TAG "FuriHalNfc" @@ -19,6 +22,8 @@ osEventFlagsId_t event = NULL; #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() { ReturnCode ret = rfalNfcInitialize(); if(ret == ERR_NONE) { @@ -243,6 +248,108 @@ bool furi_hal_nfc_listen( return true; } +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; +} + +void furi_hal_nfc_listen_sleep() { + st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SLEEP); +} + +bool furi_hal_nfc_listen_rx(FuriHalNfcTxRxContext* tx_rx, uint32_t timeout_ms) { + furi_assert(tx_rx); + + // Wait for interrupts + uint32_t start = osKernelGetTickCount(); + 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; + break; + } + continue; + } + if(osKernelGetTickCount() - start > timeout_ms) { + FURI_LOG_D(TAG, "Interrupt waiting timeout"); + break; + } + } + + return data_received; +} + +void furi_hal_nfc_listen_start(FuriHalNfcDevData* nfc_data) { + furi_assert(nfc_data); + + 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( + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + 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] = nfc_data->sak | 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 sence + st25r3916ExecuteCommand(ST25R3916_CMD_GOTO_SENSE); +} + void rfal_interrupt_callback_handler() { osEventFlagsSet(event, EVENT_FLAG_INTERRUPT); } @@ -369,23 +476,18 @@ bool furi_hal_nfc_emulate_nfca( static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { furi_assert(tx_rx->nfca_signal); - platformDisableIrqCallback(); - bool ret = false; // Start transparent mode st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); - // Reconfigure gpio + // Reconfigure gpio for Transparent mode furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); - furi_hal_gpio_init(&gpio_spi_r_sck, GpioModeInput, GpioPullUp, GpioSpeedLow); - furi_hal_gpio_init(&gpio_spi_r_miso, GpioModeInput, GpioPullUp, GpioSpeedLow); - furi_hal_gpio_init(&gpio_nfc_cs, GpioModeInput, GpioPullUp, GpioSpeedLow); - furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(&gpio_spi_r_mosi, false); // 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); // Configure gpio back to SPI and exit transparent @@ -443,7 +545,6 @@ static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_ } st25r3916ClearInterrupts(); - platformEnableIrqCallback(); return ret; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c index 0bdb2508..e01f132e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c @@ -188,6 +188,72 @@ inline static void furi_hal_spi_bus_r_handle_event_callback( } } +inline static void furi_hal_spi_bus_nfc_handle_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event, + const LL_SPI_InitTypeDef* preset) { + if(event == FuriHalSpiBusHandleEventInit) { + // Configure GPIOs in normal SPI mode + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventDeinit) { + // Configure GPIOs for st25r3916 Transparent mode + furi_hal_gpio_init(handle->sck, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(handle->miso, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_init(handle->cs, GpioModeInput, GpioPullUp, GpioSpeedLow); + furi_hal_gpio_write(handle->mosi, false); + furi_hal_gpio_init(handle->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventActivate) { + LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); + LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); + LL_SPI_Enable(handle->bus->spi); + + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullNo, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + + } else if(event == FuriHalSpiBusHandleEventDeactivate) { + furi_hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + LL_SPI_Disable(handle->bus->spi); + } +} + static void furi_hal_spi_bus_handle_subghz_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { @@ -206,7 +272,7 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { static void furi_hal_spi_bus_handle_nfc_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { - furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_2edge_low_8m); + furi_hal_spi_bus_nfc_handle_event_callback(handle, event, &furi_hal_spi_preset_2edge_low_8m); } FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { diff --git a/firmware/targets/furi_hal_include/furi_hal_nfc.h b/firmware/targets/furi_hal_include/furi_hal_nfc.h index 6e438367..a25e7c6d 100644 --- a/firmware/targets/furi_hal_include/furi_hal_nfc.h +++ b/firmware/targets/furi_hal_include/furi_hal_nfc.h @@ -163,6 +163,39 @@ bool furi_hal_nfc_listen( 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, diff --git a/lib/ST25RFAL002/platform.c b/lib/ST25RFAL002/platform.c index c688bd59..1facfa2a 100644 --- a/lib/ST25RFAL002/platform.c +++ b/lib/ST25RFAL002/platform.c @@ -6,11 +6,13 @@ 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) { @@ -71,10 +73,30 @@ bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len) { return ret; } -void platformProtectST25RComm() { +// 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 platformUnprotectST25RComm() { +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 index 99f97ace..832e034e 100644 --- a/lib/ST25RFAL002/platform.h +++ b/lib/ST25RFAL002/platform.h @@ -19,6 +19,8 @@ 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 diff --git a/lib/digital_signal/digital_signal.c b/lib/digital_signal/digital_signal.c index 23ddaf90..46ca307a 100644 --- a/lib/digital_signal/digital_signal.c +++ b/lib/digital_signal/digital_signal.c @@ -5,14 +5,17 @@ #include #include +#pragma GCC optimize("O3,unroll-loops,Ofast") + #define F_TIM (64000000.0) -#define T_TIM (1.0 / F_TIM) +#define T_TIM 1562 //15.625 ns *100 +#define T_TIM_DIV2 781 //15.625 ns / 2 *100 DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) { DigitalSignal* signal = malloc(sizeof(DigitalSignal)); signal->start_level = true; signal->edges_max_cnt = max_edges_cnt; - signal->edge_timings = malloc(max_edges_cnt * sizeof(float)); + signal->edge_timings = malloc(max_edges_cnt * sizeof(uint32_t)); signal->reload_reg_buff = malloc(max_edges_cnt * sizeof(uint32_t)); signal->edge_cnt = 0; @@ -48,10 +51,10 @@ bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b) { signal_a->edge_timings[signal_a->edge_cnt] += signal_b->edge_timings[0]; } } - memcpy( - &signal_a->edge_timings[signal_a->edge_cnt], - &signal_b->edge_timings[start_copy], - (signal_b->edge_cnt - start_copy) * sizeof(float)); + + 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; @@ -69,34 +72,33 @@ uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal) { return signal->edge_cnt; } -float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) { +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]; } -static void digital_signal_prepare_arr(DigitalSignal* signal) { - float t_signal = 0; - float t_current = 0; - float r = 0; - float r_int = 0; - float r_dec = 0; +void digital_signal_prepare_arr(DigitalSignal* signal) { + uint32_t t_signal_rest = signal->edge_timings[0]; + uint32_t r_count_tick_arr = 0; + uint32_t r_rest_div = 0; for(size_t i = 0; i < signal->edge_cnt - 1; i++) { - t_signal += signal->edge_timings[i]; - r = (t_signal - t_current) / T_TIM; - r_dec = modff(r, &r_int); - if(r_dec < 0.5f) { - signal->reload_reg_buff[i] = (uint32_t)r_int - 1; + r_count_tick_arr = t_signal_rest / T_TIM; + r_rest_div = t_signal_rest % T_TIM; + t_signal_rest = signal->edge_timings[i + 1] + r_rest_div; + + if(r_rest_div < T_TIM_DIV2) { + signal->reload_reg_buff[i] = r_count_tick_arr - 1; } else { - signal->reload_reg_buff[i] = (uint32_t)r_int; + signal->reload_reg_buff[i] = r_count_tick_arr; + t_signal_rest -= T_TIM; } - t_current += (signal->reload_reg_buff[i] + 1) * T_TIM; } } -bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { +void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { furi_assert(signal); furi_assert(gpio); @@ -168,6 +170,4 @@ bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) { LL_TIM_SetCounter(TIM2, 0); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1); LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); - - return true; } diff --git a/lib/digital_signal/digital_signal.h b/lib/digital_signal/digital_signal.h index 5e20e733..d828444c 100644 --- a/lib/digital_signal/digital_signal.h +++ b/lib/digital_signal/digital_signal.h @@ -10,7 +10,7 @@ typedef struct { bool start_level; uint32_t edge_cnt; uint32_t edges_max_cnt; - float* edge_timings; + uint32_t* edge_timings; uint32_t* reload_reg_buff; } DigitalSignal; @@ -20,10 +20,12 @@ void digital_signal_free(DigitalSignal* signal); 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); -float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); +uint32_t digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num); -bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); +void digital_signal_send(DigitalSignal* signal, const GpioPin* gpio); diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc_protocols/mifare_classic.c index 9dbfd9d0..b6a1c23a 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc_protocols/mifare_classic.c @@ -537,25 +537,23 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ // Read command while(!command_processed) { - if(!is_encrypted) { - // Read first frame - tx_rx->tx_bits = 0; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; - } - 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); - break; - } if(!is_encrypted) { 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); + break; + } mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); } - // TODO Check crc - if(plain_data[0] == 0x50 && plain_data[1] == 00) { + if(plain_data[0] == 0x50 && plain_data[1] == 0x00) { FURI_LOG_T(TAG, "Halt received"); + furi_hal_nfc_listen_sleep(); command_processed = true; break; } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) { @@ -564,11 +562,11 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ uint8_t sector_trailer_block = mf_classic_get_sector_trailer(block); MfClassicSectorTrailer* sector_trailer = (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; - if(plain_data[0] == 0x61) { - key = nfc_util_bytes2num(sector_trailer->key_b, 6); + if(plain_data[0] == 0x60) { + key = nfc_util_bytes2num(sector_trailer->key_a, 6); access_key = MfClassicKeyA; } else { - key = nfc_util_bytes2num(sector_trailer->key_a, 6); + key = nfc_util_bytes2num(sector_trailer->key_b, 6); access_key = MfClassicKeyB; } @@ -581,8 +579,12 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ 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; + for(size_t i = 0; i < sizeof(nt); i++) { + tx_rx->tx_parity[0] |= nfc_util_odd_parity8(nt[i]) << (7 - i); + } tx_rx->tx_bits = sizeof(nt) * 8; - tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxRaw; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; } else { mf_crypto1_encrypt( &emulator->crypto, diff --git a/lib/nfc_protocols/nfca.c b/lib/nfc_protocols/nfca.c index e1cbdafe..c401f8cc 100755 --- a/lib/nfc_protocols/nfca.c +++ b/lib/nfc_protocols/nfca.c @@ -8,7 +8,10 @@ #define NFCA_CRC_INIT (0x6363) #define NFCA_F_SIG (13560000.0) -#define NFCA_T_SIG (1.0 / NFCA_F_SIG) +#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) @@ -64,15 +67,15 @@ 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] = 8 * NFCA_T_SIG; + signal->edge_timings[i] = T_SIG_x8; } - signal->edge_timings[7] = 9 * 8 * NFCA_T_SIG; + signal->edge_timings[7] = T_SIG_x8_x9; signal->edge_cnt = 8; } else { signal->start_level = false; - signal->edge_timings[0] = 8 * 8 * NFCA_T_SIG; + signal->edge_timings[0] = T_SIG_x8_x8; for(size_t i = 1; i < 9; i++) { - signal->edge_timings[i] = 8 * NFCA_T_SIG; + signal->edge_timings[i] = T_SIG_x8; } signal->edge_cnt = 9; } From 09ec3832c8a9ff67f620536ffe2905592438c195 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 3 Jul 2022 10:01:24 -0500 Subject: [PATCH 05/26] Improved PR build comment (#1348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improved PR build comment * Update build.yml Co-authored-by: あく --- .github/workflows/build.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15b7f786..d3ebb9ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -144,7 +144,7 @@ jobs: with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' - body-includes: 'Install with web updater' + body-includes: 'Compiled firmware for commit' - name: 'Create or update comment' if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}} @@ -153,7 +153,10 @@ jobs: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | - [Install with web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}). + **Compiled firmware for commit `${{steps.names.outputs.short-hash}}`:** + - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz) + - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu) + - [☁️ Web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}) edit-mode: replace compact: From fea2bfa82215a0b71464177aec5dc21be39f0ee9 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Sun, 3 Jul 2022 20:03:36 +0400 Subject: [PATCH 06/26] [FL-2610] SubGhz: add keypad lock SubGhz -> Read (#1343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FL-2610] SubGhz: add keypad lock SubGhz -> Read * SubGhz: fix multiple clicks on the back button * SubGhz: turn on the backlight when receiving with the keypad locked. key processing delay when exiting Locked mode * SubGhz: chanage lock variable and enums names * SubGhz: replace direct return with consumed Co-authored-by: あく --- .../subghz/helpers/subghz_custom_event.h | 3 + applications/subghz/helpers/subghz_types.h | 69 +++++++++ .../subghz/scenes/subghz_scene_read_raw.c | 23 +-- .../subghz/scenes/subghz_scene_receiver.c | 45 ++++-- .../scenes/subghz_scene_receiver_config.c | 38 ++++- applications/subghz/subghz.c | 3 +- applications/subghz/subghz_i.h | 64 +-------- applications/subghz/views/receiver.c | 136 ++++++++++++++++-- applications/subghz/views/receiver.h | 3 + 9 files changed, 287 insertions(+), 97 deletions(-) create mode 100644 applications/subghz/helpers/subghz_types.h diff --git a/applications/subghz/helpers/subghz_custom_event.h b/applications/subghz/helpers/subghz_custom_event.h index a2e5ee3e..05f0f492 100644 --- a/applications/subghz/helpers/subghz_custom_event.h +++ b/applications/subghz/helpers/subghz_custom_event.h @@ -41,6 +41,7 @@ typedef enum { SubGhzCustomEventSceneShowOnlyRX, SubGhzCustomEventSceneAnalyzerLock, SubGhzCustomEventSceneAnalyzerUnlock, + SubGhzCustomEventSceneSettingLock, SubGhzCustomEventSceneExit, SubGhzCustomEventSceneStay, @@ -48,6 +49,8 @@ typedef enum { SubGhzCustomEventViewReceiverOK, SubGhzCustomEventViewReceiverConfig, SubGhzCustomEventViewReceiverBack, + SubGhzCustomEventViewReceiverOffDisplay, + SubGhzCustomEventViewReceiverUnlock, SubGhzCustomEventViewReadRAWBack, SubGhzCustomEventViewReadRAWIDLE, diff --git a/applications/subghz/helpers/subghz_types.h b/applications/subghz/helpers/subghz_types.h new file mode 100644 index 00000000..8d2dcf17 --- /dev/null +++ b/applications/subghz/helpers/subghz_types.h @@ -0,0 +1,69 @@ +#pragma once + +/** SubGhzNotification state */ +typedef enum { + SubGhzNotificationStateStarting, + SubGhzNotificationStateIDLE, + SubGhzNotificationStateTx, + SubGhzNotificationStateRx, + SubGhzNotificationStateRxDone, +} SubGhzNotificationState; + +/** SubGhzTxRx state */ +typedef enum { + SubGhzTxRxStateIDLE, + SubGhzTxRxStateRx, + SubGhzTxRxStateTx, + SubGhzTxRxStateSleep, +} SubGhzTxRxState; + +/** SubGhzHopperState state */ +typedef enum { + SubGhzHopperStateOFF, + SubGhzHopperStateRunnig, + SubGhzHopperStatePause, + SubGhzHopperStateRSSITimeOut, +} SubGhzHopperState; + +/** SubGhzRxKeyState state */ +typedef enum { + SubGhzRxKeyStateIDLE, + SubGhzRxKeyStateNoSave, + SubGhzRxKeyStateNeedSave, + SubGhzRxKeyStateBack, + SubGhzRxKeyStateStart, + SubGhzRxKeyStateAddKey, + SubGhzRxKeyStateExit, + SubGhzRxKeyStateRAWLoad, + SubGhzRxKeyStateRAWSave, +} SubGhzRxKeyState; + +/** SubGhzLoadKeyState state */ +typedef enum { + SubGhzLoadKeyStateUnknown, + SubGhzLoadKeyStateOK, + SubGhzLoadKeyStateParseErr, + SubGhzLoadKeyStateOnlyRx, +} SubGhzLoadKeyState; + +/** SubGhzLock */ +typedef enum { + SubGhzLockOff, + SubGhzLockOn, +} SubGhzLock; + +typedef enum { + SubGhzViewIdMenu, + SubGhzViewIdReceiver, + SubGhzViewIdPopup, + SubGhzViewIdTextInput, + SubGhzViewIdWidget, + SubGhzViewIdTransmitter, + SubGhzViewIdVariableItemList, + SubGhzViewIdFrequencyAnalyzer, + SubGhzViewIdReadRAW, + + SubGhzViewIdStatic, + SubGhzViewIdTestCarrier, + SubGhzViewIdTestPacket, +} SubGhzViewId; diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index 45bb8a50..598f235e 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -106,6 +106,7 @@ void subghz_scene_read_raw_on_enter(void* context) { bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; + bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case SubGhzCustomEventViewReadRAWBack: @@ -141,7 +142,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { } } } - return true; + consumed = true; break; case SubGhzCustomEventViewReadRAWTXRXStop: @@ -156,14 +157,14 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_sleep(subghz); }; subghz->state_notifications = SubGhzNotificationStateIDLE; - return true; + consumed = true; break; case SubGhzCustomEventViewReadRAWConfig: scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig); - return true; + consumed = true; break; case SubGhzCustomEventViewReadRAWErase: @@ -175,7 +176,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { } subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; notification_message(subghz->notifications, &sequence_reset_rgb); - return true; + consumed = true; break; case SubGhzCustomEventViewReadRAWMore: @@ -184,7 +185,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); - return true; + consumed = true; } else { furi_crash("SubGhz: RAW file name update error."); } @@ -214,7 +215,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { } } } - return true; + consumed = true; break; case SubGhzCustomEventViewReadRAWSendStop: @@ -224,7 +225,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_sleep(subghz); } subghz_read_raw_stop_send(subghz->subghz_read_raw); - return true; + consumed = true; break; case SubGhzCustomEventViewReadRAWIDLE: @@ -255,7 +256,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz->state_notifications = SubGhzNotificationStateIDLE; subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; - return true; + consumed = true; break; case SubGhzCustomEventViewReadRAWREC: @@ -281,7 +282,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } } - return true; + consumed = true; break; case SubGhzCustomEventViewReadRAWSave: @@ -291,7 +292,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } - return true; + consumed = true; break; default: @@ -315,7 +316,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { break; } } - return false; + return consumed; } void subghz_scene_read_raw_on_exit(void* context) { diff --git a/applications/subghz/scenes/subghz_scene_receiver.c b/applications/subghz/scenes/subghz_scene_receiver.c index 01005148..55302b01 100644 --- a/applications/subghz/scenes/subghz_scene_receiver.c +++ b/applications/subghz/scenes/subghz_scene_receiver.c @@ -14,6 +14,23 @@ static const NotificationSequence subghs_sequence_rx = { NULL, }; +static const NotificationSequence subghs_sequence_rx_locked = { + &message_green_255, + + &message_display_backlight_on, + + &message_vibro_on, + &message_note_c6, + &message_delay_50, + &message_sound_off, + &message_vibro_off, + + &message_delay_500, + + &message_display_backlight_off, + NULL, +}; + static void subghz_scene_receiver_update_statusbar(void* context) { SubGhz* subghz = context; string_t history_stat_str; @@ -92,6 +109,8 @@ void subghz_scene_receiver_on_enter(void* context) { subghz->txrx->rx_key_state = SubGhzRxKeyStateStart; } + subghz_view_receiver_set_lock(subghz->subghz_receiver, subghz->lock); + //Load history to receiver subghz_view_receiver_exit(subghz->subghz_receiver); for(uint8_t i = 0; i < subghz_history_get_item(subghz->txrx->history); i++) { @@ -126,11 +145,10 @@ void subghz_scene_receiver_on_enter(void* context) { bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; - + bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { case SubGhzCustomEventViewReceiverBack: - // Stop CC1101 Rx subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { @@ -151,20 +169,28 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); } - return true; + consumed = true; break; case SubGhzCustomEventViewReceiverOK: subghz->txrx->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); - return true; + consumed = true; break; case SubGhzCustomEventViewReceiverConfig: subghz->state_notifications = SubGhzNotificationStateIDLE; subghz->txrx->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverConfig); - return true; + consumed = true; + break; + case SubGhzCustomEventViewReceiverOffDisplay: + notification_message(subghz->notifications, &sequence_display_backlight_off); + consumed = true; + break; + case SubGhzCustomEventViewReceiverUnlock: + subghz->lock = SubGhzLockOff; + consumed = true; break; default: break; @@ -174,20 +200,23 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz_hopper_update(subghz); subghz_scene_receiver_update_statusbar(subghz); } - switch(subghz->state_notifications) { case SubGhzNotificationStateRx: notification_message(subghz->notifications, &sequence_blink_cyan_10); break; case SubGhzNotificationStateRxDone: - notification_message(subghz->notifications, &subghs_sequence_rx); + if(subghz->lock != SubGhzLockOn) { + notification_message(subghz->notifications, &subghs_sequence_rx); + } else { + notification_message(subghz->notifications, &subghs_sequence_rx_locked); + } subghz->state_notifications = SubGhzNotificationStateRx; break; default: break; } } - return false; + return consumed; } void subghz_scene_receiver_on_exit(void* context) { diff --git a/applications/subghz/scenes/subghz_scene_receiver_config.c b/applications/subghz/scenes/subghz_scene_receiver_config.c index 8789a243..cf31c1e9 100644 --- a/applications/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/subghz/scenes/subghz_scene_receiver_config.c @@ -1,5 +1,12 @@ #include "../subghz_i.h" +enum SubGhzSettingIndex { + SubGhzSettingIndexFrequency, + SubGhzSettingIndexHopping, + SubGhzSettingIndexModulation, + SubGhzSettingIndexLock, +}; + #define PRESET_COUNT 4 const char* const preset_text[PRESET_COUNT] = { "AM270", @@ -137,6 +144,15 @@ static void subghz_scene_receiver_config_set_hopping_runing(VariableItem* item) subghz->txrx->hopper_state = hopping_value[index]; } +static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { + furi_assert(context); + SubGhz* subghz = context; + if(index == SubGhzSettingIndexLock) { + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock); + } +} + void subghz_scene_receiver_config_on_enter(void* context) { SubGhz* subghz = context; VariableItem* item; @@ -185,13 +201,29 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, preset_text[value_index]); + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != + SubGhzCustomEventManagerSet) { + variable_item_list_add(subghz->variable_item_list, "Lock Keyboard", 1, NULL, NULL); + variable_item_list_set_enter_callback( + subghz->variable_item_list, + subghz_scene_receiver_config_var_list_enter_callback, + subghz); + } view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); } bool subghz_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - return false; + SubGhz* subghz = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubGhzCustomEventSceneSettingLock) { + subghz->lock = SubGhzLockOn; + scene_manager_previous_scene(subghz->scene_manager); + consumed = true; + } + } + return consumed; } void subghz_scene_receiver_config_on_exit(void* context) { diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index d9dc19f4..ade7a844 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -133,7 +133,8 @@ SubGhz* subghz_alloc() { subghz->setting = subghz_setting_alloc(); subghz_setting_load(subghz->setting, "/ext/subghz/assets/setting_user"); - //init Worker & Protocol & History + //init Worker & Protocol & History & KeyBoard + subghz->lock = SubGhzLockOff; subghz->txrx = malloc(sizeof(SubGhzTxRx)); subghz->txrx->frequency = subghz_setting_get_default_frequency(subghz->setting); subghz->txrx->preset = FuriHalSubGhzPresetOok650Async; diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index 5d1b0699..d75e92a7 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -1,5 +1,6 @@ #pragma once +#include "helpers/subghz_types.h" #include "subghz.h" #include "views/receiver.h" #include "views/transmitter.h" @@ -37,52 +38,6 @@ #define SUBGHZ_MAX_LEN_NAME 64 -/** SubGhzNotification state */ -typedef enum { - SubGhzNotificationStateStarting, - SubGhzNotificationStateIDLE, - SubGhzNotificationStateTx, - SubGhzNotificationStateRx, - SubGhzNotificationStateRxDone, -} SubGhzNotificationState; - -/** SubGhzTxRx state */ -typedef enum { - SubGhzTxRxStateIDLE, - SubGhzTxRxStateRx, - SubGhzTxRxStateTx, - SubGhzTxRxStateSleep, -} SubGhzTxRxState; - -/** SubGhzHopperState state */ -typedef enum { - SubGhzHopperStateOFF, - SubGhzHopperStateRunnig, - SubGhzHopperStatePause, - SubGhzHopperStateRSSITimeOut, -} SubGhzHopperState; - -/** SubGhzRxKeyState state */ -typedef enum { - SubGhzRxKeyStateIDLE, - SubGhzRxKeyStateNoSave, - SubGhzRxKeyStateNeedSave, - SubGhzRxKeyStateBack, - SubGhzRxKeyStateStart, - SubGhzRxKeyStateAddKey, - SubGhzRxKeyStateExit, - SubGhzRxKeyStateRAWLoad, - SubGhzRxKeyStateRAWSave, -} SubGhzRxKeyState; - -/** SubGhzLoadKeyState state */ -typedef enum { - SubGhzLoadKeyStateUnknown, - SubGhzLoadKeyStateOK, - SubGhzLoadKeyStateParseErr, - SubGhzLoadKeyStateOnlyRx, -} SubGhzLoadKeyState; - struct SubGhzTxRx { SubGhzWorker* worker; @@ -135,24 +90,9 @@ struct SubGhz { SubGhzTestPacket* subghz_test_packet; string_t error_str; SubGhzSetting* setting; + SubGhzLock lock; }; -typedef enum { - SubGhzViewIdMenu, - SubGhzViewIdReceiver, - SubGhzViewIdPopup, - SubGhzViewIdTextInput, - SubGhzViewIdWidget, - SubGhzViewIdTransmitter, - SubGhzViewIdVariableItemList, - SubGhzViewIdFrequencyAnalyzer, - SubGhzViewIdReadRAW, - - SubGhzViewIdStatic, - SubGhzViewIdTestCarrier, - SubGhzViewIdTestPacket, -} SubGhzViewId; - bool subghz_set_preset(SubGhz* subghz, const char* preset); void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_t modulation); void subghz_begin(SubGhz* subghz, FuriHalSubGhzPreset preset); diff --git a/applications/subghz/views/receiver.c b/applications/subghz/views/receiver.c index 866d8272..bb4a8f16 100644 --- a/applications/subghz/views/receiver.c +++ b/applications/subghz/views/receiver.c @@ -11,6 +11,7 @@ #define FRAME_HEIGHT 12 #define MAX_LEN_PX 100 #define MENU_ITEMS 4u +#define UNLOCK_CNT 3 typedef struct { string_t item_str; @@ -34,7 +35,17 @@ static const Icon* ReceiverItemIcons[] = { [SubGhzProtocolTypeDynamic] = &I_Lock_7x8, }; +typedef enum { + SubGhzViewReceiverBarShowDefault, + SubGhzViewReceiverBarShowLock, + SubGhzViewReceiverBarShowToUnlockPress, + SubGhzViewReceiverBarShowUnlock, +} SubGhzViewReceiverBarShow; + struct SubGhzViewReceiver { + SubGhzLock lock; + uint8_t lock_count; + osTimerId_t timer; View* view; SubGhzViewReceiverCallback callback; void* context; @@ -48,8 +59,29 @@ typedef struct { uint16_t idx; uint16_t list_offset; uint16_t history_item; + SubGhzViewReceiverBarShow bar_show; } SubGhzViewReceiverModel; +void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock lock) { + furi_assert(subghz_receiver); + subghz_receiver->lock_count = 0; + if(lock == SubGhzLockOn) { + subghz_receiver->lock = lock; + with_view_model( + subghz_receiver->view, (SubGhzViewReceiverModel * model) { + model->bar_show = SubGhzViewReceiverBarShowLock; + return true; + }); + osTimerStart(subghz_receiver->timer, pdMS_TO_TICKS(1000)); + } else { + with_view_model( + subghz_receiver->view, (SubGhzViewReceiverModel * model) { + model->bar_show = SubGhzViewReceiverBarShowDefault; + return true; + }); + } +} + void subghz_view_receiver_set_callback( SubGhzViewReceiver* subghz_receiver, SubGhzViewReceiverCallback callback, @@ -138,17 +170,6 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { canvas_set_font(canvas, FontSecondary); elements_button_left(canvas, "Config"); - - canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str)); - canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str)); - if(model->history_item == 0) { - canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 63, 46, "Scanning..."); - canvas_draw_line(canvas, 46, 51, 125, 51); - return; - } canvas_draw_line(canvas, 46, 51, 125, 51); bool scrollbar = model->history_item > 4; @@ -175,12 +196,96 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); } string_clear(str_buff); + + canvas_set_color(canvas, ColorBlack); + + if(model->history_item == 0) { + canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 63, 46, "Scanning..."); + canvas_draw_line(canvas, 46, 51, 125, 51); + canvas_set_font(canvas, FontSecondary); + } + + switch(model->bar_show) { + case SubGhzViewReceiverBarShowLock: + canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); + canvas_draw_str(canvas, 74, 62, "Locked"); + break; + case SubGhzViewReceiverBarShowToUnlockPress: + canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str)); + canvas_set_font(canvas, FontSecondary); + elements_bold_rounded_frame(canvas, 14, 8, 99, 48); + elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); + canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42); + canvas_draw_dot(canvas, 17, 61); + break; + case SubGhzViewReceiverBarShowUnlock: + canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8); + canvas_draw_str(canvas, 74, 62, "Unlocked"); + break; + default: + canvas_draw_str(canvas, 44, 62, string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, string_get_cstr(model->history_stat_str)); + break; + } +} + +static void subghz_view_receiver_timer_callback(void* context) { + furi_assert(context); + SubGhzViewReceiver* subghz_receiver = context; + with_view_model( + subghz_receiver->view, (SubGhzViewReceiverModel * model) { + model->bar_show = SubGhzViewReceiverBarShowDefault; + return true; + }); + if(subghz_receiver->lock_count < UNLOCK_CNT) { + subghz_receiver->callback( + SubGhzCustomEventViewReceiverOffDisplay, subghz_receiver->context); + } else { + subghz_receiver->lock = SubGhzLockOff; + subghz_receiver->callback(SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context); + } + subghz_receiver->lock_count = 0; } bool subghz_view_receiver_input(InputEvent* event, void* context) { furi_assert(context); SubGhzViewReceiver* subghz_receiver = context; + if(subghz_receiver->lock == SubGhzLockOn) { + with_view_model( + subghz_receiver->view, (SubGhzViewReceiverModel * model) { + model->bar_show = SubGhzViewReceiverBarShowToUnlockPress; + return true; + }); + if(subghz_receiver->lock_count == 0) { + osTimerStart(subghz_receiver->timer, pdMS_TO_TICKS(1000)); + } + if(event->key == InputKeyBack && event->type == InputTypeShort) { + subghz_receiver->lock_count++; + } + if(subghz_receiver->lock_count >= UNLOCK_CNT) { + // subghz_receiver->callback( + // SubGhzCustomEventViewReceiverUnlock, subghz_receiver->context); + with_view_model( + subghz_receiver->view, (SubGhzViewReceiverModel * model) { + model->bar_show = SubGhzViewReceiverBarShowUnlock; + return true; + }); + //subghz_receiver->lock = SubGhzLockOff; + osTimerStart(subghz_receiver->timer, pdMS_TO_TICKS(650)); + } + + return true; + } + if(event->key == InputKeyBack && event->type == InputTypeShort) { subghz_receiver->callback(SubGhzCustomEventViewReceiverBack, subghz_receiver->context); } else if( @@ -240,6 +345,7 @@ void subghz_view_receiver_exit(void* context) { model->history_item = 0; return false; }); + osTimerStop(subghz_receiver->timer); } SubGhzViewReceiver* subghz_view_receiver_alloc() { @@ -247,6 +353,9 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() { // View allocation and configuration subghz_receiver->view = view_alloc(); + + subghz_receiver->lock = SubGhzLockOff; + subghz_receiver->lock_count = 0; view_allocate_model( subghz_receiver->view, ViewModelTypeLocking, sizeof(SubGhzViewReceiverModel)); view_set_context(subghz_receiver->view, subghz_receiver); @@ -260,11 +369,13 @@ SubGhzViewReceiver* subghz_view_receiver_alloc() { string_init(model->frequency_str); string_init(model->preset_str); string_init(model->history_stat_str); + model->bar_show = SubGhzViewReceiverBarShowDefault; model->history = malloc(sizeof(SubGhzReceiverHistory)); SubGhzReceiverMenuItemArray_init(model->history->data); return true; }); - + subghz_receiver->timer = + osTimerNew(subghz_view_receiver_timer_callback, osTimerOnce, subghz_receiver, NULL); return subghz_receiver; } @@ -285,6 +396,7 @@ void subghz_view_receiver_free(SubGhzViewReceiver* subghz_receiver) { free(model->history); return false; }); + osTimerDelete(subghz_receiver->timer); view_free(subghz_receiver->view); free(subghz_receiver); } diff --git a/applications/subghz/views/receiver.h b/applications/subghz/views/receiver.h index 87065ef0..aab7a76c 100644 --- a/applications/subghz/views/receiver.h +++ b/applications/subghz/views/receiver.h @@ -1,12 +1,15 @@ #pragma once #include +#include "../helpers/subghz_types.h" #include "../helpers/subghz_custom_event.h" typedef struct SubGhzViewReceiver SubGhzViewReceiver; typedef void (*SubGhzViewReceiverCallback)(SubGhzCustomEvent event, void* context); +void subghz_view_receiver_set_lock(SubGhzViewReceiver* subghz_receiver, SubGhzLock keyboard); + void subghz_view_receiver_set_callback( SubGhzViewReceiver* subghz_receiver, SubGhzViewReceiverCallback callback, From ffd6eda3618e94ade83f2944e2e6ccbf658177bf Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Mon, 4 Jul 2022 03:54:36 -0700 Subject: [PATCH 07/26] Add iClass keys to source (#1360) --- applications/picopass/picopass.c | 49 ++++---------------------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/applications/picopass/picopass.c b/applications/picopass/picopass.c index 477b7b86..2bf0d6f0 100644 --- a/applications/picopass/picopass.c +++ b/applications/picopass/picopass.c @@ -13,9 +13,6 @@ #define TAG "PicoPass" -#define PICOPASS_APP_ICLASS_KEY_PATH "/any/picopass/iclass_key.bin" -#define PICOPASS_APP_ICLASS_DECRYPT_KEY_PATH "/any/picopass/iclass_decryptionkey.bin" - typedef enum { EventTypeTick, EventTypeKey, @@ -42,46 +39,17 @@ typedef struct { WiegandRecord record; } PACS; -enum State { INIT, KEYS_MISSING, READY, RESULT }; +enum State { INIT, READY, RESULT }; typedef struct { enum State state; PACS pacs; } PluginState; -uint8_t iclass_key[8] = {0}; // NB: not the permuted version -uint8_t iclass_decryptionkey[16] = {0}; +uint8_t iclass_key[8] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; +uint8_t iclass_decryptionkey[16] = + {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; ApplicationArea AA1; -static bool picopass_load_keys() { - Storage* storage = furi_record_open("storage"); - File* file = storage_file_alloc(storage); - - if(!storage_file_open(file, PICOPASS_APP_ICLASS_KEY_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { - FURI_LOG_E(TAG, "Unable to open iClass key"); - storage_file_free(file); - furi_record_close("storage"); - return false; - }; - storage_file_read(file, iclass_key, sizeof(iclass_key)); - storage_file_close(file); - FURI_LOG_D(TAG, "iClass key loaded"); - - if(!storage_file_open( - file, PICOPASS_APP_ICLASS_DECRYPT_KEY_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { - FURI_LOG_E(TAG, "Unable to open iClass decryption key"); - storage_file_free(file); - furi_record_close("storage"); - return false; - }; - storage_file_read(file, iclass_decryptionkey, sizeof(iclass_decryptionkey)); - storage_file_close(file); - FURI_LOG_D(TAG, "iClass decryption key loaded"); - - storage_file_free(file); - furi_record_close("storage"); - return true; -} - static void render_callback(Canvas* const canvas, void* ctx) { const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); if(plugin_state == NULL) { @@ -94,8 +62,6 @@ static void render_callback(Canvas* const canvas, void* ctx) { if(plugin_state->state == INIT) { canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Loading..."); - } else if(plugin_state->state == KEYS_MISSING) { - canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Keys missing"); } else if(plugin_state->state == READY) { canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Push center to scan"); } else if(plugin_state->state == RESULT) { @@ -135,12 +101,7 @@ static void input_callback(InputEvent* input_event, osMessageQueueId_t event_que } static void picopass_state_init(PluginState* const plugin_state) { - plugin_state->state = INIT; - if(picopass_load_keys()) { - plugin_state->state = READY; - } else { - plugin_state->state = KEYS_MISSING; - } + plugin_state->state = READY; } ReturnCode decrypt(uint8_t* enc_data, uint8_t* dec_data) { From 0e78f384044806b98361b8060f1666a87ecfea7f Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 4 Jul 2022 05:16:59 -0600 Subject: [PATCH 08/26] nfc: On-device tag generator (#1319) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc: Add tag generator base * nfc: Fix BCC generation * nfc: Add MFUL EV1 generators * nfc: Fix typos in generator * nfc: Add NTAG21x generators * nfc: More const * nfc: Add NTAG I2C generators * nfc: Add field names to generator initializers * nfc: Move generators to add manually scene * nfc: Revise tag generator UX * nfc: Revert add manually menu item name * nfc: Remove unused scene start submenu index Co-authored-by: あく Co-authored-by: gornekich --- applications/nfc/helpers/nfc_generators.c | 327 ++++++++++++++++++ applications/nfc/helpers/nfc_generators.h | 13 + applications/nfc/nfc.c | 3 + applications/nfc/nfc_i.h | 5 + applications/nfc/scenes/nfc_scene_config.h | 1 + .../nfc/scenes/nfc_scene_generate_info.c | 55 +++ applications/nfc/scenes/nfc_scene_set_type.c | 17 + 7 files changed, 421 insertions(+) create mode 100644 applications/nfc/helpers/nfc_generators.c create mode 100644 applications/nfc/helpers/nfc_generators.h create mode 100644 applications/nfc/scenes/nfc_scene_generate_info.c mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_set_type.c diff --git a/applications/nfc/helpers/nfc_generators.c b/applications/nfc/helpers/nfc_generators.c new file mode 100644 index 00000000..67d9be7a --- /dev/null +++ b/applications/nfc/helpers/nfc_generators.c @@ -0,0 +1,327 @@ +#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_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_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_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; + 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_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; + 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; + 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_assert(false); + 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; +} + +static const NfcGenerator mf_ul_generator = { + .name = "Mifare Ultralight", + .generator_func = nfc_generate_mf_ul_orig, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator mf_ul_11_generator = { + .name = "Mifare Ultralight EV1 11", + .generator_func = nfc_generate_mf_ul_11, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator mf_ul_h11_generator = { + .name = "Mifare Ultralight EV1 H11", + .generator_func = nfc_generate_mf_ul_h11, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator mf_ul_21_generator = { + .name = "Mifare Ultralight EV1 21", + .generator_func = nfc_generate_mf_ul_21, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator mf_ul_h21_generator = { + .name = "Mifare Ultralight EV1 H21", + .generator_func = nfc_generate_mf_ul_h21, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator ntag213_generator = { + .name = "NTAG213", + .generator_func = nfc_generate_ntag213, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator ntag215_generator = { + .name = "NTAG215", + .generator_func = nfc_generate_ntag215, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator ntag216_generator = { + .name = "NTAG216", + .generator_func = nfc_generate_ntag216, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator ntag_i2c_1k_generator = { + .name = "NTAG I2C 1k", + .generator_func = nfc_generate_ntag_i2c_1k, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator ntag_i2c_2k_generator = { + .name = "NTAG I2C 2k", + .generator_func = nfc_generate_ntag_i2c_2k, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator ntag_i2c_plus_1k_generator = { + .name = "NTAG I2C Plus 1k", + .generator_func = nfc_generate_ntag_i2c_plus_1k, + .next_scene = NfcSceneMifareUlMenu}; + +static const NfcGenerator ntag_i2c_plus_2k_generator = { + .name = "NTAG I2C Plus 2k", + .generator_func = nfc_generate_ntag_i2c_plus_2k, + .next_scene = NfcSceneMifareUlMenu}; + +const NfcGenerator* const nfc_generators[] = { + &mf_ul_generator, + &mf_ul_11_generator, + &mf_ul_h11_generator, + &mf_ul_21_generator, + &mf_ul_h21_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, + NULL, +}; diff --git a/applications/nfc/helpers/nfc_generators.h b/applications/nfc/helpers/nfc_generators.h new file mode 100644 index 00000000..10a05591 --- /dev/null +++ b/applications/nfc/helpers/nfc_generators.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../nfc_i.h" + +typedef void (*NfcGeneratorFunc)(NfcDeviceData* data); + +struct NfcGenerator { + const char* name; + NfcGeneratorFunc generator_func; + NfcScene next_scene; +}; + +extern const NfcGenerator* const nfc_generators[]; diff --git a/applications/nfc/nfc.c b/applications/nfc/nfc.c index adc3b149..1999ba6a 100755 --- a/applications/nfc/nfc.c +++ b/applications/nfc/nfc.c @@ -84,6 +84,9 @@ Nfc* nfc_alloc() { view_dispatcher_add_view( nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack)); + // Generator + nfc->generator = NULL; + return nfc; } diff --git a/applications/nfc/nfc_i.h b/applications/nfc/nfc_i.h index a640d957..cdbe3bea 100755 --- a/applications/nfc/nfc_i.h +++ b/applications/nfc/nfc_i.h @@ -33,6 +33,9 @@ #define NFC_SEND_NOTIFICATION_TRUE (1UL) #define NFC_TEXT_STORE_SIZE 128 +// Forward declaration due to circular dependency +typedef struct NfcGenerator NfcGenerator; + struct Nfc { NfcWorker* worker; ViewDispatcher* view_dispatcher; @@ -55,6 +58,8 @@ struct Nfc { Widget* widget; BankCard* bank_card; DictAttack* dict_attack; + + const NfcGenerator* generator; }; typedef enum { diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/nfc/scenes/nfc_scene_config.h index e6351d42..5cf2c86f 100755 --- a/applications/nfc/scenes/nfc_scene_config.h +++ b/applications/nfc/scenes/nfc_scene_config.h @@ -37,3 +37,4 @@ ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic) ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic) ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu) ADD_SCENE(nfc, dict_not_found, DictNotFound) +ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/nfc/scenes/nfc_scene_generate_info.c b/applications/nfc/scenes/nfc_scene_generate_info.c new file mode 100644 index 00000000..7fb7eb94 --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_generate_info.c @@ -0,0 +1,55 @@ +#include "../nfc_i.h" +#include "../helpers/nfc_generators.h" + +void nfc_scene_generate_info_dialog_callback(DialogExResult result, void* context) { + Nfc* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); +} + +void nfc_scene_generate_info_on_enter(void* context) { + Nfc* nfc = context; + + // 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"); + + // Create info text + string_t info_str; + string_init_printf( + info_str, "%s\n%s\nUID:", nfc->generator->name, nfc_get_dev_type(data->type)); + // Append UID + for(int i = 0; i < data->uid_len; ++i) { + string_cat_printf(info_str, " %02X", data->uid[i]); + } + nfc_text_store_set(nfc, string_get_cstr(info_str)); + string_clear(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); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); +} + +bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultRight) { + scene_manager_next_scene(nfc->scene_manager, nfc->generator->next_scene); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_generate_info_on_exit(void* context) { + Nfc* nfc = context; + + // Clean views + dialog_ex_reset(nfc->dialog_ex); +} diff --git a/applications/nfc/scenes/nfc_scene_set_type.c b/applications/nfc/scenes/nfc_scene_set_type.c old mode 100755 new mode 100644 index 0fe63424..ec6d1144 --- a/applications/nfc/scenes/nfc_scene_set_type.c +++ b/applications/nfc/scenes/nfc_scene_set_type.c @@ -1,9 +1,11 @@ #include "../nfc_i.h" #include "m-string.h" +#include "../helpers/nfc_generators.h" enum SubmenuIndex { SubmenuIndexNFCA4, SubmenuIndexNFCA7, + SubmenuIndexGeneratorsStart, }; void nfc_scene_set_type_submenu_callback(void* context, uint32_t index) { @@ -22,6 +24,14 @@ void nfc_scene_set_type_on_enter(void* context) { submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc); submenu_add_item( submenu, "NFC-A 4-bytes UID", SubmenuIndexNFCA4, nfc_scene_set_type_submenu_callback, nfc); + + // 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); + } + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); } @@ -40,6 +50,13 @@ bool nfc_scene_set_type_on_event(void* context, SceneManagerEvent event) { nfc->dev->format = NfcDeviceSaveFormatUid; scene_manager_next_scene(nfc->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); + consumed = true; } } return consumed; From 4a1695ba1cc787c73a6f135c8a352fe300cdea72 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 4 Jul 2022 16:09:46 +0300 Subject: [PATCH 09/26] [FL-2589] RPC App control commands (#1350) * RPC App control commands * Button release timeout * SubGhz tx fix Co-authored-by: Aleksandr Kutuzov --- applications/gpio/usb_uart_bridge.c | 2 - applications/ibutton/ibutton.c | 60 +++++- applications/ibutton/ibutton_custom_event.h | 2 + applications/ibutton/ibutton_i.h | 2 + .../ibutton/scenes/ibutton_scene_config.h | 1 + .../ibutton/scenes/ibutton_scene_rpc.c | 36 ++++ applications/infrared/infrared.c | 76 +++++++- applications/infrared/infrared_i.h | 4 + applications/infrared/infrared_remote.c | 13 ++ applications/infrared/infrared_remote.h | 1 + .../infrared/scenes/infrared_scene_config.h | 1 + .../infrared/scenes/infrared_scene_rpc.c | 37 ++++ applications/lfrfid/lfrfid_app.cpp | 63 ++++++- applications/lfrfid/lfrfid_app.h | 9 +- .../lfrfid/scene/lfrfid_app_scene_rpc.cpp | 37 ++++ .../lfrfid/scene/lfrfid_app_scene_rpc.h | 12 ++ applications/nfc/nfc.c | 78 +++++++- applications/nfc/nfc_device.c | 12 +- applications/nfc/nfc_device.h | 2 +- applications/nfc/nfc_i.h | 13 ++ applications/nfc/scenes/nfc_scene_config.h | 1 + applications/nfc/scenes/nfc_scene_rpc.c | 34 ++++ applications/rpc/rpc.c | 2 +- applications/rpc/rpc_app.c | 175 +++++++++++++++++- applications/rpc/rpc_app.h | 24 +++ applications/rpc/rpc_i.h | 1 + .../subghz/scenes/subghz_scene_config.h | 1 + applications/subghz/scenes/subghz_scene_rpc.c | 34 ++++ applications/subghz/subghz.c | 60 +++++- applications/subghz/subghz_i.c | 12 +- applications/subghz/subghz_i.h | 6 +- assets/protobuf | 2 +- 32 files changed, 768 insertions(+), 45 deletions(-) create mode 100644 applications/ibutton/scenes/ibutton_scene_rpc.c create mode 100644 applications/infrared/scenes/infrared_scene_rpc.c create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp create mode 100644 applications/lfrfid/scene/lfrfid_app_scene_rpc.h mode change 100755 => 100644 applications/nfc/nfc.c create mode 100644 applications/nfc/scenes/nfc_scene_rpc.c create mode 100644 applications/rpc/rpc_app.h create mode 100644 applications/subghz/scenes/subghz_scene_rpc.c diff --git a/applications/gpio/usb_uart_bridge.c b/applications/gpio/usb_uart_bridge.c index cf7e7687..9c6ba0a6 100644 --- a/applications/gpio/usb_uart_bridge.c +++ b/applications/gpio/usb_uart_bridge.c @@ -85,7 +85,6 @@ static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) { furi_hal_usb_unlock(); - FURI_LOG_I("", "Init %d", vcp_ch); if(vcp_ch == 0) { Cli* cli = furi_record_open("cli"); cli_session_close(cli); @@ -103,7 +102,6 @@ static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) { static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) { UNUSED(usb_uart); furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL); - FURI_LOG_I("", "Deinit %d", vcp_ch); if(vcp_ch != 0) { Cli* cli = furi_record_open("cli"); cli_session_close(cli); diff --git a/applications/ibutton/ibutton.c b/applications/ibutton/ibutton.c index ae93f972..0f54dc3e 100644 --- a/applications/ibutton/ibutton.c +++ b/applications/ibutton/ibutton.c @@ -5,6 +5,9 @@ #include "m-string.h" #include #include +#include "rpc/rpc_app.h" + +#define TAG "iButtonApp" static const NotificationSequence sequence_blink_start_cyan = { &message_blink_start_10, @@ -55,7 +58,7 @@ static void ibutton_make_app_folder(iButton* ibutton) { } } -static bool ibutton_load_key_data(iButton* ibutton, string_t key_path) { +static bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show_dialog) { FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); bool result = false; string_t data; @@ -89,13 +92,40 @@ static bool ibutton_load_key_data(iButton* ibutton, string_t key_path) { flipper_format_free(file); string_clear(data); - if(!result) { + if((!result) && (show_dialog)) { dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file"); } return result; } +static bool ibutton_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { + furi_assert(context); + iButton* ibutton = context; + + bool result = false; + + if(event == RpcAppEventSessionClose) { + rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL); + ibutton->rpc_ctx = NULL; + view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit); + result = true; + } else if(event == RpcAppEventAppExit) { + view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit); + result = true; + } else if(event == RpcAppEventLoadFile) { + if(arg) { + string_set_str(ibutton->file_path, arg); + if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) { + ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key); + result = true; + } + } + } + + return result; +} + bool ibutton_custom_event_callback(void* context, uint32_t event) { furi_assert(context); iButton* ibutton = context; @@ -226,7 +256,7 @@ bool ibutton_file_select(iButton* ibutton) { true); if(success) { - success = ibutton_load_key_data(ibutton, ibutton->file_path); + success = ibutton_load_key_data(ibutton, ibutton->file_path, true); } return success; @@ -334,16 +364,27 @@ int32_t ibutton_app(void* p) { ibutton_make_app_folder(ibutton); bool key_loaded = false; + bool rpc_mode = false; if(p) { - string_set_str(ibutton->file_path, (const char*)p); - if(ibutton_load_key_data(ibutton, ibutton->file_path)) { - key_loaded = true; - // TODO: Display an error if the key from p could not be loaded + uint32_t rpc_ctx = 0; + if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) { + FURI_LOG_D(TAG, "Running in RPC mode"); + ibutton->rpc_ctx = (void*)rpc_ctx; + rpc_mode = true; + rpc_system_app_set_callback(ibutton->rpc_ctx, ibutton_rpc_command_callback, ibutton); + } else { + string_set_str(ibutton->file_path, (const char*)p); + if(ibutton_load_key_data(ibutton, ibutton->file_path, true)) { + key_loaded = true; + // TODO: Display an error if the key from p could not be loaded + } } } - if(key_loaded) { + if(rpc_mode) { + scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc); + } else if(key_loaded) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); } else { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); @@ -351,6 +392,9 @@ int32_t ibutton_app(void* p) { view_dispatcher_run(ibutton->view_dispatcher); + if(ibutton->rpc_ctx) { + rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL); + } ibutton_free(ibutton); return 0; } diff --git a/applications/ibutton/ibutton_custom_event.h b/applications/ibutton/ibutton_custom_event.h index 2be42d66..1706e00f 100644 --- a/applications/ibutton/ibutton_custom_event.h +++ b/applications/ibutton/ibutton_custom_event.h @@ -9,4 +9,6 @@ enum iButtonCustomEvent { iButtonCustomEventByteEditResult, iButtonCustomEventWorkerEmulated, iButtonCustomEventWorkerRead, + + iButtonCustomEventRpcExit, }; diff --git a/applications/ibutton/ibutton_i.h b/applications/ibutton/ibutton_i.h index a85dd5f6..889d5a67 100644 --- a/applications/ibutton/ibutton_i.h +++ b/applications/ibutton/ibutton_i.h @@ -50,6 +50,8 @@ struct iButton { Popup* popup; Widget* widget; DialogEx* dialog_ex; + + void* rpc_ctx; }; typedef enum { diff --git a/applications/ibutton/scenes/ibutton_scene_config.h b/applications/ibutton/scenes/ibutton_scene_config.h index d30b43be..87fa1a03 100644 --- a/applications/ibutton/scenes/ibutton_scene_config.h +++ b/applications/ibutton/scenes/ibutton_scene_config.h @@ -18,3 +18,4 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm) ADD_SCENE(ibutton, delete_success, DeleteSuccess) ADD_SCENE(ibutton, retry_confirm, RetryConfirm) ADD_SCENE(ibutton, exit_confirm, ExitConfirm) +ADD_SCENE(ibutton, rpc, Rpc) diff --git a/applications/ibutton/scenes/ibutton_scene_rpc.c b/applications/ibutton/scenes/ibutton_scene_rpc.c new file mode 100644 index 00000000..ceeca017 --- /dev/null +++ b/applications/ibutton/scenes/ibutton_scene_rpc.c @@ -0,0 +1,36 @@ +#include "../ibutton_i.h" +#include + +void ibutton_scene_rpc_on_enter(void* context) { + iButton* ibutton = context; + Widget* widget = ibutton->widget; + + widget_add_text_box_element( + widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false); + + view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + + notification_message(ibutton->notifications, &sequence_display_backlight_on); +} + +bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + iButton* ibutton = context; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == iButtonCustomEventRpcExit) { + view_dispatcher_stop(ibutton->view_dispatcher); + } + } + + return consumed; +} + +void ibutton_scene_rpc_on_exit(void* context) { + iButton* ibutton = context; + widget_reset(ibutton->widget); +} diff --git a/applications/infrared/infrared.c b/applications/infrared/infrared.c index e641302c..62206116 100644 --- a/applications/infrared/infrared.c +++ b/applications/infrared/infrared.c @@ -36,6 +36,52 @@ static void infrared_tick_event_callback(void* context) { scene_manager_handle_tick_event(infrared->scene_manager); } +static bool + infrared_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { + furi_assert(context); + Infrared* infrared = context; + + if(!infrared->rpc_ctx) { + return false; + } + + bool result = false; + + if(event == RpcAppEventSessionClose) { + rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL); + infrared->rpc_ctx = NULL; + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeBackPressed); + result = true; + } else if(event == RpcAppEventAppExit) { + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeBackPressed); + result = true; + } else if(event == RpcAppEventLoadFile) { + if(arg) { + string_set_str(infrared->file_path, arg); + result = infrared_remote_load(infrared->remote, infrared->file_path); + infrared_worker_tx_set_get_signal_callback( + infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared); + infrared_worker_tx_set_signal_sent_callback( + infrared->worker, infrared_signal_sent_callback, infrared); + } + } else if(event == RpcAppEventButtonPress) { + if(arg) { + size_t button_index = 0; + if(infrared_remote_find_button_by_name(infrared->remote, arg, &button_index)) { + infrared_tx_start_button_index(infrared, button_index); + result = true; + } + } + } else if(event == RpcAppEventButtonRelease) { + infrared_tx_stop(infrared); + result = true; + } + + return result; +} + static void infrared_find_vacant_remote_name(string_t name, const char* path) { Storage* storage = furi_record_open("storage"); @@ -154,6 +200,11 @@ static void infrared_free(Infrared* infrared) { ViewDispatcher* view_dispatcher = infrared->view_dispatcher; InfraredAppState* app_state = &infrared->app_state; + if(infrared->rpc_ctx) { + rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL); + infrared->rpc_ctx = NULL; + } + view_dispatcher_remove_view(view_dispatcher, InfraredViewSubmenu); submenu_free(infrared->submenu); @@ -375,18 +426,29 @@ int32_t infrared_app(void* p) { infrared_make_app_folder(infrared); bool is_remote_loaded = false; + bool is_rpc_mode = false; if(p) { - string_set_str(infrared->file_path, (const char*)p); - is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path); - if(!is_remote_loaded) { - dialog_message_show_storage_error( - infrared->dialogs, "Failed to load\nselected remote"); - return -1; + uint32_t rpc_ctx = 0; + if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) { + infrared->rpc_ctx = (void*)rpc_ctx; + rpc_system_app_set_callback( + infrared->rpc_ctx, infrared_rpc_command_callback, infrared); + is_rpc_mode = true; + } else { + string_set_str(infrared->file_path, (const char*)p); + is_remote_loaded = infrared_remote_load(infrared->remote, infrared->file_path); + if(!is_remote_loaded) { + dialog_message_show_storage_error( + infrared->dialogs, "Failed to load\nselected remote"); + return -1; + } } } - if(is_remote_loaded) { + if(is_rpc_mode) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneRpc); + } else if(is_remote_loaded) { scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote); } else { scene_manager_next_scene(infrared->scene_manager, InfraredSceneStart); diff --git a/applications/infrared/infrared_i.h b/applications/infrared/infrared_i.h index 5c447adc..c47753f8 100644 --- a/applications/infrared/infrared_i.h +++ b/applications/infrared/infrared_i.h @@ -30,6 +30,8 @@ #include "views/infrared_progress_view.h" #include "views/infrared_debug_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 @@ -95,6 +97,8 @@ struct Infrared { string_t file_path; char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; InfraredAppState app_state; + + void* rpc_ctx; }; typedef enum { diff --git a/applications/infrared/infrared_remote.c b/applications/infrared/infrared_remote.c index ad75efe7..94658035 100644 --- a/applications/infrared/infrared_remote.c +++ b/applications/infrared/infrared_remote.c @@ -1,5 +1,7 @@ #include "infrared_remote.h" +#include +#include #include #include #include @@ -73,6 +75,17 @@ InfraredRemoteButton* infrared_remote_get_button(InfraredRemote* remote, size_t return *InfraredButtonArray_get(remote->buttons, index); } +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)) { + *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); diff --git a/applications/infrared/infrared_remote.h b/applications/infrared/infrared_remote.h index 1336383f..b6f63a19 100644 --- a/applications/infrared/infrared_remote.h +++ b/applications/infrared/infrared_remote.h @@ -18,6 +18,7 @@ const char* infrared_remote_get_path(InfraredRemote* remote); 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); 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); diff --git a/applications/infrared/scenes/infrared_scene_config.h b/applications/infrared/scenes/infrared_scene_config.h index c4e083c1..8ff3b167 100644 --- a/applications/infrared/scenes/infrared_scene_config.h +++ b/applications/infrared/scenes/infrared_scene_config.h @@ -16,3 +16,4 @@ ADD_SCENE(infrared, universal, Universal) ADD_SCENE(infrared, universal_tv, UniversalTV) ADD_SCENE(infrared, debug, Debug) ADD_SCENE(infrared, error_databases, ErrorDatabases) +ADD_SCENE(infrared, rpc, Rpc) diff --git a/applications/infrared/scenes/infrared_scene_rpc.c b/applications/infrared/scenes/infrared_scene_rpc.c new file mode 100644 index 00000000..3cab9f80 --- /dev/null +++ b/applications/infrared/scenes/infrared_scene_rpc.c @@ -0,0 +1,37 @@ +#include "../infrared_i.h" +#include "gui/canvas.h" + +void infrared_scene_rpc_on_enter(void* context) { + Infrared* infrared = context; + Popup* popup = infrared->popup; + + popup_set_text(popup, "Rpc mode", 64, 28, AlignCenter, AlignCenter); + + popup_set_context(popup, context); + popup_set_callback(popup, infrared_popup_closed_callback); + + infrared_play_notification_message(infrared, InfraredNotificationMessageYellowOn); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); + + notification_message(infrared->notifications, &sequence_display_backlight_on); +} + +bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { + Infrared* infrared = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == InfraredCustomEventTypeBackPressed) { + view_dispatcher_stop(infrared->view_dispatcher); + } else if(event.event == InfraredCustomEventTypePopupClosed) { + view_dispatcher_stop(infrared->view_dispatcher); + } + } + return consumed; +} + +void infrared_scene_rpc_on_exit(void* context) { + Infrared* infrared = context; + popup_reset(infrared->popup); +} diff --git a/applications/lfrfid/lfrfid_app.cpp b/applications/lfrfid/lfrfid_app.cpp index 4027a07e..d2c92d24 100644 --- a/applications/lfrfid/lfrfid_app.cpp +++ b/applications/lfrfid/lfrfid_app.cpp @@ -20,10 +20,13 @@ #include "scene/lfrfid_app_scene_saved_info.h" #include "scene/lfrfid_app_scene_delete_confirm.h" #include "scene/lfrfid_app_scene_delete_success.h" +#include "scene/lfrfid_app_scene_rpc.h" #include #include +#include "rpc/rpc_app.h" + const char* LfRfidApp::app_folder = "/any/lfrfid"; const char* LfRfidApp::app_extension = ".rfid"; const char* LfRfidApp::app_filetype = "Flipper RFID key"; @@ -39,6 +42,43 @@ LfRfidApp::LfRfidApp() LfRfidApp::~LfRfidApp() { string_clear(file_path); + if(rpc_ctx) { + rpc_system_app_set_callback(rpc_ctx, NULL, NULL); + } +} + +static bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { + furi_assert(context); + LfRfidApp* app = static_cast(context); + + bool result = false; + + if(event == RpcAppEventSessionClose) { + rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL); + app->rpc_ctx = NULL; + LfRfidApp::Event event; + event.type = LfRfidApp::EventType::Exit; + app->view_controller.send_event(&event); + result = true; + } else if(event == RpcAppEventAppExit) { + LfRfidApp::Event event; + event.type = LfRfidApp::EventType::Exit; + app->view_controller.send_event(&event); + result = true; + } else if(event == RpcAppEventLoadFile) { + if(arg) { + string_set_str(app->file_path, arg); + if(app->load_key_data(app->file_path, &(app->worker.key), false)) { + LfRfidApp::Event event; + event.type = LfRfidApp::EventType::EmulateStart; + app->view_controller.send_event(&event); + app->worker.start_emulate(); + result = true; + } + } + } + + return result; } void LfRfidApp::run(void* _args) { @@ -47,10 +87,19 @@ void LfRfidApp::run(void* _args) { make_app_folder(); if(strlen(args)) { - string_set_str(file_path, args); - load_key_data(file_path, &worker.key); - scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); - scene_controller.process(100, SceneType::Emulate); + uint32_t rpc_ctx_ptr = 0; + if(sscanf(args, "RPC %lX", &rpc_ctx_ptr) == 1) { + rpc_ctx = (RpcAppSystem*)rpc_ctx_ptr; + rpc_system_app_set_callback(rpc_ctx, rpc_command_callback, this); + scene_controller.add_scene(SceneType::Rpc, new LfRfidAppSceneRpc()); + scene_controller.process(100, SceneType::Rpc); + } else { + string_set_str(file_path, args); + load_key_data(file_path, &worker.key, true); + scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); + scene_controller.process(100, SceneType::Emulate); + } + } else { scene_controller.add_scene(SceneType::Start, new LfRfidAppSceneStart()); scene_controller.add_scene(SceneType::Read, new LfRfidAppSceneRead()); @@ -99,7 +148,7 @@ bool LfRfidApp::load_key_from_file_select(bool need_restore) { dialogs, file_path, file_path, app_extension, true, &I_125_10px, true); if(result) { - result = load_key_data(file_path, &worker.key); + result = load_key_data(file_path, &worker.key, true); } return result; @@ -110,7 +159,7 @@ bool LfRfidApp::delete_key(RfidKey* key) { return storage_simply_remove(storage, string_get_cstr(file_path)); } -bool LfRfidApp::load_key_data(string_t path, RfidKey* key) { +bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) { FlipperFormat* file = flipper_format_file_alloc(storage); bool result = false; string_t str_result; @@ -149,7 +198,7 @@ bool LfRfidApp::load_key_data(string_t path, RfidKey* key) { flipper_format_free(file); string_clear(str_result); - if(!result) { + if((!result) && (show_dialog)) { dialog_message_show_storage_error(dialogs, "Cannot load\nkey file"); } diff --git a/applications/lfrfid/lfrfid_app.h b/applications/lfrfid/lfrfid_app.h index 3f8209a1..3372552f 100644 --- a/applications/lfrfid/lfrfid_app.h +++ b/applications/lfrfid/lfrfid_app.h @@ -21,6 +21,7 @@ #include #include "helpers/rfid_worker.h" +#include "rpc/rpc_app.h" class LfRfidApp { public: @@ -30,6 +31,8 @@ public: MenuSelected, Stay, Retry, + Exit, + EmulateStart, }; enum class SceneType : uint8_t { @@ -51,6 +54,7 @@ public: SavedInfo, DeleteConfirm, DeleteSuccess, + Rpc, }; class Event { @@ -79,6 +83,8 @@ public: string_t file_path; + RpcAppSystem* rpc_ctx; + void run(void* args); static const char* app_folder; @@ -89,8 +95,9 @@ public: bool load_key_from_file_select(bool need_restore); bool delete_key(RfidKey* key); - bool load_key_data(string_t path, RfidKey* key); + bool load_key_data(string_t path, RfidKey* key, bool show_dialog); bool save_key_data(string_t path, RfidKey* key); void make_app_folder(); + //bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context); }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp b/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp new file mode 100644 index 00000000..012c96ac --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_rpc.cpp @@ -0,0 +1,37 @@ +#include "lfrfid_app_scene_rpc.h" +#include "furi/common_defines.h" +#include + +void LfRfidAppSceneRpc::on_enter(LfRfidApp* app, bool /* need_restore */) { + auto popup = app->view_controller.get(); + + popup->set_header("RPC Mode", 64, 30, AlignCenter, AlignTop); + + app->view_controller.switch_to(); + + notification_message(app->notification, &sequence_display_backlight_on); +} + +bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) { + UNUSED(app); + UNUSED(event); + bool consumed = false; + + if(event->type == LfRfidApp::EventType::Exit) { + consumed = true; + LfRfidApp::Event view_event; + view_event.type = LfRfidApp::EventType::Back; + app->view_controller.send_event(&view_event); + } else if(event->type == LfRfidApp::EventType::EmulateStart) { + consumed = true; + emulating = true; + } + return consumed; +} + +void LfRfidAppSceneRpc::on_exit(LfRfidApp* app) { + if(emulating) { + app->worker.stop_emulate(); + } + app->view_controller.get()->clean(); +} diff --git a/applications/lfrfid/scene/lfrfid_app_scene_rpc.h b/applications/lfrfid/scene/lfrfid_app_scene_rpc.h new file mode 100644 index 00000000..f630dfd3 --- /dev/null +++ b/applications/lfrfid/scene/lfrfid_app_scene_rpc.h @@ -0,0 +1,12 @@ +#pragma once +#include "../lfrfid_app.h" + +class LfRfidAppSceneRpc : public GenericScene { +public: + void on_enter(LfRfidApp* app, bool need_restore) final; + bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; + void on_exit(LfRfidApp* app) final; + +private: + bool emulating = false; +}; diff --git a/applications/nfc/nfc.c b/applications/nfc/nfc.c old mode 100755 new mode 100644 index 1999ba6a..e98b5d9a --- a/applications/nfc/nfc.c +++ b/applications/nfc/nfc.c @@ -19,6 +19,77 @@ void nfc_tick_event_callback(void* context) { scene_manager_handle_tick_event(nfc->scene_manager); } +void nfc_rpc_exit_callback(Nfc* 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 + nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); + } + if(nfc->rpc_ctx) { + rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); + nfc->rpc_ctx = NULL; + } +} + +static void nfc_rpc_emulate_callback(NfcWorkerEvent event, void* context) { + UNUSED(event); + Nfc* nfc = context; + + nfc->rpc_state = NfcRpcStateEmulated; +} + +static bool nfc_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { + furi_assert(context); + Nfc* nfc = context; + + if(!nfc->rpc_ctx) { + return false; + } + + bool result = false; + + if(event == RpcAppEventSessionClose) { + rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); + nfc->rpc_ctx = NULL; + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); + result = true; + } else if(event == RpcAppEventAppExit) { + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); + result = true; + } else if(event == RpcAppEventLoadFile) { + if((arg) && (nfc->rpc_state == NfcRpcStateIdle)) { + if(nfc_device_load(nfc->dev, arg, false)) { + if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { + nfc_worker_start( + nfc->worker, + NfcWorkerStateEmulateMifareUltralight, + &nfc->dev->dev_data, + nfc_rpc_emulate_callback, + nfc); + } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { + nfc_worker_start( + nfc->worker, + NfcWorkerStateEmulateMifareClassic, + &nfc->dev->dev_data, + nfc_rpc_emulate_callback, + nfc); + } else { + nfc_worker_start( + nfc->worker, NfcWorkerStateEmulate, &nfc->dev->dev_data, NULL, nfc); + } + nfc->rpc_state = NfcRpcStateEmulating; + result = true; + } + } + } + + return result; +} + Nfc* nfc_alloc() { Nfc* nfc = malloc(sizeof(Nfc)); @@ -193,7 +264,12 @@ int32_t nfc_app(void* p) { // Check argument and run corresponding scene if((*args != '\0')) { - if(nfc_device_load(nfc->dev, p)) { + 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); + scene_manager_next_scene(nfc->scene_manager, NfcSceneRpc); + } else if(nfc_device_load(nfc->dev, p, true)) { if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl); } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c index 092d0089..155cc3f8 100644 --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -837,7 +837,7 @@ bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true); } -static bool nfc_device_load_data(NfcDevice* dev, string_t path) { +static bool nfc_device_load_data(NfcDevice* dev, string_t path, bool show_dialog) { bool parsed = false; FlipperFormat* file = flipper_format_file_alloc(dev->storage); FuriHalNfcDevData* data = &dev->dev_data.nfc_data; @@ -887,7 +887,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) { parsed = true; } while(false); - if(!parsed) { + if((!parsed) && (show_dialog)) { if(deprecated_version) { dialog_message_show_storage_error(dev->dialogs, "File format deprecated"); } else { @@ -900,13 +900,13 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) { return parsed; } -bool nfc_device_load(NfcDevice* dev, const char* file_path) { +bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) { furi_assert(dev); furi_assert(file_path); // Load device data string_set_str(dev->load_path, file_path); - bool dev_load = nfc_device_load_data(dev, dev->load_path); + bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog); if(dev_load) { // Set device name string_t filename; @@ -933,7 +933,7 @@ bool nfc_file_select(NfcDevice* dev) { string_init(filename); path_extract_filename(dev->load_path, filename, true); strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); - res = nfc_device_load_data(dev, dev->load_path); + res = nfc_device_load_data(dev, dev->load_path, true); if(res) { nfc_device_set_name(dev, dev->dev_name); } @@ -1017,7 +1017,7 @@ bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { } else { string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); } - if(!nfc_device_load_data(dev, path)) break; + if(!nfc_device_load_data(dev, path, true)) break; restored = true; } while(0); diff --git a/applications/nfc/nfc_device.h b/applications/nfc/nfc_device.h index 400f6c36..3b2875c0 100644 --- a/applications/nfc/nfc_device.h +++ b/applications/nfc/nfc_device.h @@ -71,7 +71,7 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name); bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name); -bool nfc_device_load(NfcDevice* dev, const char* file_path); +bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog); bool nfc_file_select(NfcDevice* dev); diff --git a/applications/nfc/nfc_i.h b/applications/nfc/nfc_i.h index cdbe3bea..c42ddced 100755 --- a/applications/nfc/nfc_i.h +++ b/applications/nfc/nfc_i.h @@ -29,10 +29,18 @@ #include #include +#include "rpc/rpc_app.h" + #define NFC_SEND_NOTIFICATION_FALSE (0UL) #define NFC_SEND_NOTIFICATION_TRUE (1UL) #define NFC_TEXT_STORE_SIZE 128 +typedef enum { + NfcRpcStateIdle, + NfcRpcStateEmulating, + NfcRpcStateEmulated, +} NfcRpcState; + // Forward declaration due to circular dependency typedef struct NfcGenerator NfcGenerator; @@ -48,6 +56,9 @@ struct Nfc { char text_store[NFC_TEXT_STORE_SIZE + 1]; string_t text_box_store; + void* rpc_ctx; + NfcRpcState rpc_state; + // Common Views Submenu* submenu; DialogEx* dialog_ex; @@ -85,3 +96,5 @@ void nfc_text_store_clear(Nfc* nfc); void nfc_blink_start(Nfc* nfc); void nfc_blink_stop(Nfc* nfc); + +void nfc_rpc_exit_callback(Nfc* nfc); diff --git a/applications/nfc/scenes/nfc_scene_config.h b/applications/nfc/scenes/nfc_scene_config.h index 5cf2c86f..ffd757de 100755 --- a/applications/nfc/scenes/nfc_scene_config.h +++ b/applications/nfc/scenes/nfc_scene_config.h @@ -37,4 +37,5 @@ ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic) ADD_SCENE(nfc, emulate_mifare_classic, EmulateMifareClassic) ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu) ADD_SCENE(nfc, dict_not_found, DictNotFound) +ADD_SCENE(nfc, rpc, Rpc) ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/nfc/scenes/nfc_scene_rpc.c b/applications/nfc/scenes/nfc_scene_rpc.c new file mode 100644 index 00000000..b94bf424 --- /dev/null +++ b/applications/nfc/scenes/nfc_scene_rpc.c @@ -0,0 +1,34 @@ +#include "../nfc_i.h" + +void nfc_scene_rpc_on_enter(void* context) { + Nfc* nfc = context; + Widget* widget = nfc->widget; + + widget_add_text_box_element( + widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false); + + notification_message(nfc->notifications, &sequence_display_backlight_on); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) { + Nfc* nfc = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == NfcCustomEventViewExit) { + view_dispatcher_stop(nfc->view_dispatcher); + } + } + return consumed; +} + +void nfc_scene_rpc_on_exit(void* context) { + Nfc* nfc = context; + + nfc_rpc_exit_callback(nfc); + + widget_reset(nfc->widget); +} diff --git a/applications/rpc/rpc.c b/applications/rpc/rpc.c index c974a0e5..4832afe3 100644 --- a/applications/rpc/rpc.c +++ b/applications/rpc/rpc.c @@ -45,7 +45,7 @@ static RpcSystemCallbacks rpc_systems[] = { }, { .alloc = rpc_system_app_alloc, - .free = NULL, + .free = rpc_system_app_free, }, { .alloc = rpc_system_gui_alloc, diff --git a/applications/rpc/rpc_app.c b/applications/rpc/rpc_app.c index 728c2052..f6678c3b 100644 --- a/applications/rpc/rpc_app.c +++ b/applications/rpc/rpc_app.c @@ -1,16 +1,39 @@ +#include "cmsis_os2.h" #include "flipper.pb.h" #include "furi/record.h" #include "rpc_i.h" #include #include +#include "rpc_app.h" #define TAG "RpcSystemApp" +#define APP_BUTTON_TIMEOUT 1000 + +struct RpcAppSystem { + RpcSession* session; + RpcAppSystemCallback app_callback; + void* app_context; + osTimerId_t timer; +}; + +static void rpc_system_app_timer_callback(void* context) { + furi_assert(context); + RpcAppSystem* rpc_app = context; + + if(rpc_app->app_callback) { + rpc_app->app_callback(RpcAppEventButtonRelease, NULL, rpc_app->app_context); + } +} 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); - RpcSession* session = (RpcSession*)context; + RpcAppSystem* rpc_app = context; + RpcSession* session = rpc_app->session; furi_assert(session); + char args_temp[16]; FURI_LOG_D(TAG, "Start"); @@ -20,6 +43,11 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) const char* app_name = request->content.app_start_request.name; if(app_name) { const char* app_args = request->content.app_start_request.args; + if(strcmp(app_args, "RPC") == 0) { + // If app is being started in RPC mode - pass RPC context via args string + snprintf(args_temp, 16, "RPC %08lX", (uint32_t)rpc_app); + app_args = args_temp; + } LoaderStatus status = loader_start(loader, app_name, app_args); if(status == LoaderStatusErrorAppStarted) { result = PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED; @@ -43,8 +71,11 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) 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); - RpcSession* session = (RpcSession*)context; + RpcAppSystem* rpc_app = context; + RpcSession* session = rpc_app->session; furi_assert(session); FURI_LOG_D(TAG, "LockStatus"); @@ -66,13 +97,123 @@ static void rpc_system_app_lock_status_process(const PB_Main* request, void* con pb_release(&PB_Main_msg, &response); } +static void rpc_system_app_exit(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; + RpcSession* session = rpc_app->session; + furi_assert(session); + + PB_CommandStatus status; + + if(rpc_app->app_callback) { + if(rpc_app->app_callback(RpcAppEventAppExit, NULL, rpc_app->app_context)) { + status = PB_CommandStatus_OK; + osTimerStop(rpc_app->timer); + } else { + status = PB_CommandStatus_ERROR_APP_CMD_ERROR; + } + } else { + status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; + } + + rpc_send_and_release_empty(session, request->command_id, status); +} + +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; + RpcSession* session = rpc_app->session; + furi_assert(session); + + PB_CommandStatus status; + if(rpc_app->app_callback) { + const char* file_path = request->content.app_load_file_request.path; + if(rpc_app->app_callback(RpcAppEventLoadFile, file_path, rpc_app->app_context)) { + status = PB_CommandStatus_OK; + } else { + status = PB_CommandStatus_ERROR_APP_CMD_ERROR; + } + } else { + status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; + } + + rpc_send_and_release_empty(session, request->command_id, status); +} + +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; + RpcSession* session = rpc_app->session; + furi_assert(session); + + PB_CommandStatus status; + if(rpc_app->app_callback) { + const char* args = request->content.app_button_press_request.args; + if(rpc_app->app_callback(RpcAppEventButtonPress, args, rpc_app->app_context)) { + status = PB_CommandStatus_OK; + osTimerStart(rpc_app->timer, APP_BUTTON_TIMEOUT); + } else { + status = PB_CommandStatus_ERROR_APP_CMD_ERROR; + } + } else { + status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; + } + + rpc_send_and_release_empty(session, request->command_id, status); +} + +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; + RpcSession* session = rpc_app->session; + furi_assert(session); + + PB_CommandStatus status; + if(rpc_app->app_callback) { + if(rpc_app->app_callback(RpcAppEventButtonRelease, NULL, rpc_app->app_context)) { + status = PB_CommandStatus_OK; + osTimerStop(rpc_app->timer); + } else { + status = PB_CommandStatus_ERROR_APP_CMD_ERROR; + } + } else { + status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; + } + + rpc_send_and_release_empty(session, request->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; +} + void* rpc_system_app_alloc(RpcSession* session) { furi_assert(session); + RpcAppSystem* rpc_app = malloc(sizeof(RpcAppSystem)); + rpc_app->session = session; + + rpc_app->timer = osTimerNew(rpc_system_app_timer_callback, osTimerOnce, rpc_app, NULL); + RpcHandler rpc_handler = { .message_handler = NULL, .decode_submessage = NULL, - .context = session, + .context = rpc_app, }; rpc_handler.message_handler = rpc_system_app_start_process; @@ -81,5 +222,31 @@ void* rpc_system_app_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_app_lock_status_process; rpc_add_handler(session, PB_Main_app_lock_status_request_tag, &rpc_handler); - return NULL; + rpc_handler.message_handler = rpc_system_app_exit; + rpc_add_handler(session, PB_Main_app_exit_request_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_app_load_file; + rpc_add_handler(session, PB_Main_app_load_file_request_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_app_button_press; + rpc_add_handler(session, PB_Main_app_button_press_request_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_app_button_release; + rpc_add_handler(session, PB_Main_app_button_release_request_tag, &rpc_handler); + + return rpc_app; +} + +void rpc_system_app_free(void* context) { + RpcAppSystem* rpc_app = context; + RpcSession* session = rpc_app->session; + furi_assert(session); + + osTimerDelete(rpc_app->timer); + + if(rpc_app->app_callback) { + rpc_app->app_callback(RpcAppEventSessionClose, NULL, rpc_app->app_context); + } + + free(rpc_app); } diff --git a/applications/rpc/rpc_app.h b/applications/rpc/rpc_app.h new file mode 100644 index 00000000..396eef1a --- /dev/null +++ b/applications/rpc/rpc_app.h @@ -0,0 +1,24 @@ +#pragma once +#include "rpc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RpcAppEventSessionClose, + RpcAppEventAppExit, + RpcAppEventLoadFile, + RpcAppEventButtonPress, + RpcAppEventButtonRelease, +} RpcAppSystemEvent; + +typedef bool (*RpcAppSystemCallback)(RpcAppSystemEvent event, const char* arg, void* context); + +typedef struct RpcAppSystem RpcAppSystem; + +void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx); + +#ifdef __cplusplus +} +#endif diff --git a/applications/rpc/rpc_i.h b/applications/rpc/rpc_i.h index f84cc991..d0c6cf76 100644 --- a/applications/rpc/rpc_i.h +++ b/applications/rpc/rpc_i.h @@ -29,6 +29,7 @@ void* rpc_system_system_alloc(RpcSession* session); void* rpc_system_storage_alloc(RpcSession* session); void rpc_system_storage_free(void* ctx); void* rpc_system_app_alloc(RpcSession* session); +void rpc_system_app_free(void* ctx); void* rpc_system_gui_alloc(RpcSession* session); void rpc_system_gui_free(void* ctx); diff --git a/applications/subghz/scenes/subghz_scene_config.h b/applications/subghz/scenes/subghz_scene_config.h index 1cb217ad..fd1271c8 100644 --- a/applications/subghz/scenes/subghz_scene_config.h +++ b/applications/subghz/scenes/subghz_scene_config.h @@ -22,3 +22,4 @@ ADD_SCENE(subghz, read_raw, ReadRAW) ADD_SCENE(subghz, more_raw, MoreRAW) ADD_SCENE(subghz, delete_raw, DeleteRAW) ADD_SCENE(subghz, need_saving, NeedSaving) +ADD_SCENE(subghz, rpc, Rpc) diff --git a/applications/subghz/scenes/subghz_scene_rpc.c b/applications/subghz/scenes/subghz_scene_rpc.c new file mode 100644 index 00000000..c7573fda --- /dev/null +++ b/applications/subghz/scenes/subghz_scene_rpc.c @@ -0,0 +1,34 @@ +#include "../subghz_i.h" + +void subghz_scene_rpc_on_enter(void* context) { + SubGhz* subghz = context; + Widget* widget = subghz->widget; + + widget_add_text_box_element( + widget, 0, 0, 128, 28, AlignCenter, AlignCenter, "RPC mode", false); + + notification_message(subghz->notifications, &sequence_display_backlight_on); + + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); +} + +bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { + SubGhz* subghz = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == SubGhzCustomEventSceneExit) { + view_dispatcher_stop(subghz->view_dispatcher); + } + } + return consumed; +} + +void subghz_scene_rpc_on_exit(void* context) { + SubGhz* subghz = context; + + //subghz_rpc_exit_callback(subghz); + + widget_reset(subghz->widget); +} diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index ade7a844..984ce472 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -23,6 +23,54 @@ void subghz_tick_event_callback(void* context) { scene_manager_handle_tick_event(subghz->scene_manager); } +static bool subghz_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { + furi_assert(context); + SubGhz* subghz = context; + + if(!subghz->rpc_ctx) { + return false; + } + + bool result = false; + + if(event == RpcAppEventSessionClose) { + rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL); + subghz->rpc_ctx = NULL; + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit); + if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { + subghz_tx_stop(subghz); + subghz_sleep(subghz); + } + result = true; + } else if(event == RpcAppEventAppExit) { + view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit); + if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { + subghz_tx_stop(subghz); + subghz_sleep(subghz); + } + result = true; + } else if(event == RpcAppEventLoadFile) { + if(arg) { + if(subghz_key_load(subghz, arg, false)) { + string_set_str(subghz->file_path, arg); + result = true; + } + } + } else if(event == RpcAppEventButtonPress) { + if(subghz->txrx->txrx_state == SubGhzTxRxStateSleep) { + result = subghz_tx_start(subghz, subghz->txrx->fff_data); + } + } else if(event == RpcAppEventButtonRelease) { + if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { + subghz_tx_stop(subghz); + subghz_sleep(subghz); + result = true; + } + } + + return result; +} + SubGhz* subghz_alloc() { SubGhz* subghz = malloc(sizeof(SubGhz)); @@ -168,6 +216,11 @@ SubGhz* subghz_alloc() { void subghz_free(SubGhz* subghz) { furi_assert(subghz); + if(subghz->rpc_ctx) { + rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL); + subghz->rpc_ctx = NULL; + } + // Packet Test view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket); subghz_test_packet_free(subghz->subghz_test_packet); @@ -265,7 +318,12 @@ int32_t subghz_app(void* p) { subghz->txrx->environment, "/ext/subghz/assets/keeloq_mfcodes_user"); // Check argument and run corresponding scene if(p) { - if(subghz_key_load(subghz, p)) { + uint32_t rpc_ctx = 0; + if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) { + subghz->rpc_ctx = (void*)rpc_ctx; + rpc_system_app_set_callback(subghz->rpc_ctx, subghz_rpc_command_callback, subghz); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneRpc); + } else if(subghz_key_load(subghz, p, true)) { string_set_str(subghz->file_path, p); if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c index 33ceb9bb..0593cc6f 100644 --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -218,7 +218,7 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) { dialog_message_free(message); } -bool subghz_key_load(SubGhz* subghz, const char* file_path) { +bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { furi_assert(subghz); furi_assert(file_path); @@ -308,11 +308,15 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { switch(load_key_state) { case SubGhzLoadKeyStateParseErr: - dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile"); + if(show_dialog) { + dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile"); + } return false; case SubGhzLoadKeyStateOnlyRx: - subghz_dialog_message_show_only_rx(subghz); + if(show_dialog) { + subghz_dialog_message_show_only_rx(subghz); + } return false; case SubGhzLoadKeyStateOK: @@ -427,7 +431,7 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { true); if(res) { - res = subghz_key_load(subghz, string_get_cstr(subghz->file_path)); + res = subghz_key_load(subghz, string_get_cstr(subghz->file_path), true); } string_clear(file_path); diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index d75e92a7..0a4bcf0b 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -36,6 +36,8 @@ #include #include +#include "rpc/rpc_app.h" + #define SUBGHZ_MAX_LEN_NAME 64 struct SubGhzTxRx { @@ -91,6 +93,8 @@ struct SubGhz { string_t error_str; SubGhzSetting* setting; SubGhzLock lock; + + void* rpc_ctx; }; bool subghz_set_preset(SubGhz* subghz, const char* preset); @@ -102,7 +106,7 @@ void subghz_sleep(SubGhz* subghz); bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format); void subghz_tx_stop(SubGhz* subghz); void subghz_dialog_message_show_only_rx(SubGhz* subghz); -bool subghz_key_load(SubGhz* subghz, const char* file_path); +bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog); bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len); bool subghz_save_protocol_to_file( SubGhz* subghz, diff --git a/assets/protobuf b/assets/protobuf index ffa62429..e3d9cdb6 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit ffa62429f3c678537e0e883a3a8c3ae5f1398ed4 +Subproject commit e3d9cdb66ce789f84f6f8e0bdd6d022187964425 From b95cd2df143d20a5dc5a236e83b455a4c7d2d9b1 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 4 Jul 2022 16:36:12 +0300 Subject: [PATCH 10/26] [FL-2578] Updater fixes related to /int handling (#1359) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updater fixes related to /int handling updater: performing factory reset on update, checking for LFS free space before updating, fixed improper error handling on backup/restore operations, rebalanced update stage weights for better progress visuals scripts: added CLI output validation for selfupdate.py storage: added pointer validation in storage_int_common_fs_info desktop: fixed crash on rendering invalid slideshows * Typo fix * rpc: Updated protobuf to 0.9 * rpc: removed updater status conversion Co-authored-by: あく --- applications/desktop/helpers/slideshow.c | 12 +++++-- applications/desktop/helpers/slideshow.h | 1 + .../desktop/views/desktop_view_slideshow.c | 4 ++- applications/rpc/rpc_system.c | 4 --- applications/storage/storages/storage_int.c | 8 +++-- applications/updater/util/update_task.c | 15 ++++---- applications/updater/util/update_task_i.h | 2 ++ .../updater/util/update_task_worker_backup.c | 35 ++++++++++--------- .../updater/util/update_task_worker_flasher.c | 2 ++ assets/protobuf | 2 +- lib/update_util/update_operation.c | 12 ++++++- lib/update_util/update_operation.h | 2 ++ scripts/selfupdate.py | 15 ++++++-- 13 files changed, 76 insertions(+), 38 deletions(-) diff --git a/applications/desktop/helpers/slideshow.c b/applications/desktop/helpers/slideshow.c index 37f66cf3..593b2854 100644 --- a/applications/desktop/helpers/slideshow.c +++ b/applications/desktop/helpers/slideshow.c @@ -12,6 +12,7 @@ struct Slideshow { Icon icon; uint32_t current_frame; + bool loaded; }; #pragma pack(push, 1) @@ -34,6 +35,7 @@ _Static_assert(sizeof(SlideshowFrameHeader) == 2, "Incorrect SlideshowFrameHeade Slideshow* slideshow_alloc() { Slideshow* ret = malloc(sizeof(Slideshow)); + ret->loaded = false; return ret; } @@ -52,7 +54,7 @@ void slideshow_free(Slideshow* slideshow) { bool slideshow_load(Slideshow* slideshow, const char* fspath) { Storage* storage = furi_record_open("storage"); File* slideshow_file = storage_file_alloc(storage); - bool load_success = false; + slideshow->loaded = false; do { if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) { break; @@ -80,12 +82,16 @@ bool slideshow_load(Slideshow* slideshow, const char* fspath) { frame_header.size) { break; } - load_success = (frame_idx + 1) == header.frame_count; + slideshow->loaded = (frame_idx + 1) == header.frame_count; } } while(false); storage_file_free(slideshow_file); furi_record_close("storage"); - return load_success; + return slideshow->loaded; +} + +bool slideshow_is_loaded(Slideshow* slideshow) { + return slideshow->loaded; } bool slideshow_advance(Slideshow* slideshow) { diff --git a/applications/desktop/helpers/slideshow.h b/applications/desktop/helpers/slideshow.h index db19779a..eeaac0e8 100644 --- a/applications/desktop/helpers/slideshow.h +++ b/applications/desktop/helpers/slideshow.h @@ -8,6 +8,7 @@ Slideshow* slideshow_alloc(); void slideshow_free(Slideshow* slideshow); bool slideshow_load(Slideshow* slideshow, const char* fspath); +bool slideshow_is_loaded(Slideshow* slideshow); void slideshow_goback(Slideshow* slideshow); bool slideshow_advance(Slideshow* slideshow); void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y); diff --git a/applications/desktop/views/desktop_view_slideshow.c b/applications/desktop/views/desktop_view_slideshow.c index 97f779b5..cd22b39d 100644 --- a/applications/desktop/views/desktop_view_slideshow.c +++ b/applications/desktop/views/desktop_view_slideshow.c @@ -21,7 +21,9 @@ static void desktop_view_slideshow_draw(Canvas* canvas, void* model) { DesktopSlideshowViewModel* m = model; canvas_clear(canvas); - slideshow_draw(m->slideshow, canvas, 0, 0); + if(slideshow_is_loaded(m->slideshow)) { + slideshow_draw(m->slideshow, canvas, 0, 0); + } } static bool desktop_view_slideshow_input(InputEvent* event, void* context) { diff --git a/applications/rpc/rpc_system.c b/applications/rpc/rpc_system.c index 45ae2e5a..350602fd 100644 --- a/applications/rpc/rpc_system.c +++ b/applications/rpc/rpc_system.c @@ -277,10 +277,6 @@ static void rpc_system_system_update_request_process(const PB_Main* request, voi UpdatePrepareResult update_prepare_result = update_operation_prepare(request->content.system_update_request.update_manifest); - /* RPC enum does not have such entry; setting to closest one */ - if(update_prepare_result == UpdatePrepareResultOutdatedManifestVersion) { - update_prepare_result = UpdatePrepareResultManifestInvalid; - } PB_Main* response = malloc(sizeof(PB_Main)); response->command_id = request->command_id; diff --git a/applications/storage/storages/storage_int.c b/applications/storage/storages/storage_int.c index 291fb426..9b1ec700 100644 --- a/applications/storage/storages/storage_int.c +++ b/applications/storage/storages/storage_int.c @@ -655,11 +655,13 @@ static FS_Error storage_int_common_fs_info( lfs_t* lfs = lfs_get_from_storage(storage); LFSData* lfs_data = lfs_data_get_from_storage(storage); - *total_space = lfs_data->config.block_size * lfs_data->config.block_count; + if(total_space) { + *total_space = lfs_data->config.block_size * lfs_data->config.block_count; + } lfs_ssize_t result = lfs_fs_size(lfs); - if(result >= 0) { - *free_space = *total_space - (result * lfs_data->config.block_size); + if(free_space && (result >= 0)) { + *free_space = (lfs_data->config.block_count - result) * lfs_data->config.block_size; } return storage_int_parse_error(result); diff --git a/applications/updater/util/update_task.c b/applications/updater/util/update_task.c index 316fe680..47bb7ae5 100644 --- a/applications/updater/util/update_task.c +++ b/applications/updater/util/update_task.c @@ -44,17 +44,17 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15), - [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 10), - [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 50), - [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 90), - [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), - [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), + [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), + [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), + [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 80), + [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), + [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 80), [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10), - [UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 100), + [UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50), [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200), - [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50), + [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30), [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30), @@ -214,6 +214,7 @@ UpdateTask* update_task_alloc() { update_task->storage = furi_record_open("storage"); update_task->file = storage_file_alloc(update_task->storage); update_task->status_change_cb = NULL; + update_task->boot_mode = furi_hal_rtc_get_boot_mode(); string_init(update_task->update_path); FuriThread* thread = update_task->thread = furi_thread_alloc(); diff --git a/applications/updater/util/update_task_i.h b/applications/updater/util/update_task_i.h index fb6b4ac1..95c63f64 100644 --- a/applications/updater/util/update_task_i.h +++ b/applications/updater/util/update_task_i.h @@ -1,6 +1,7 @@ #pragma once #include +#include #define UPDATE_TASK_NOERR 0 #define UPDATE_TASK_FAILED -1 @@ -14,6 +15,7 @@ typedef struct UpdateTask { File* file; updateProgressCb status_change_cb; void* status_change_cb_state; + FuriHalRtcBootMode boot_mode; } UpdateTask; void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress); diff --git a/applications/updater/util/update_task_worker_backup.c b/applications/updater/util/update_task_worker_backup.c index f1ce5281..1e8c2f09 100644 --- a/applications/updater/util/update_task_worker_backup.c +++ b/applications/updater/util/update_task_worker_backup.c @@ -67,7 +67,6 @@ static bool update_task_post_update(UpdateTask* update_task) { string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path); update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0); - update_operation_disarm(); CHECK_RESULT(lfs_backup_unpack(update_task->storage, string_get_cstr(file_path))); @@ -117,28 +116,32 @@ static bool update_task_post_update(UpdateTask* update_task) { int32_t update_task_worker_backup_restore(void* context) { furi_assert(context); UpdateTask* update_task = context; - bool success = false; - FuriHalRtcBootMode boot_mode = furi_hal_rtc_get_boot_mode(); + FuriHalRtcBootMode boot_mode = update_task->boot_mode; if((boot_mode != FuriHalRtcBootModePreUpdate) && (boot_mode != FuriHalRtcBootModePostUpdate)) { - /* no idea how we got here. Clear to normal boot */ - update_operation_disarm(); + /* no idea how we got here. Do nothing */ return UPDATE_TASK_NOERR; } - if(!update_task_parse_manifest(update_task)) { - return UPDATE_TASK_FAILED; - } + bool success = false; + do { + if(!update_task_parse_manifest(update_task)) { + break; + } - /* Waiting for BT service to 'start', so we don't race for boot mode flag */ - furi_record_open("bt"); - furi_record_close("bt"); + /* Waiting for BT service to 'start', so we don't race for boot mode flag */ + furi_record_open("bt"); + furi_record_close("bt"); - if(boot_mode == FuriHalRtcBootModePreUpdate) { - success = update_task_pre_update(update_task); - } else if(boot_mode == FuriHalRtcBootModePostUpdate) { - success = update_task_post_update(update_task); - } + if(boot_mode == FuriHalRtcBootModePreUpdate) { + success = update_task_pre_update(update_task); + } else if(boot_mode == FuriHalRtcBootModePostUpdate) { + success = update_task_post_update(update_task); + if(success) { + update_operation_disarm(); + } + } + } while(false); if(!success) { update_task_set_progress(update_task, UpdateTaskStageError, 0); diff --git a/applications/updater/util/update_task_worker_flasher.c b/applications/updater/util/update_task_worker_flasher.c index 1f22e2f7..d02858bc 100644 --- a/applications/updater/util/update_task_worker_flasher.c +++ b/applications/updater/util/update_task_worker_flasher.c @@ -340,6 +340,8 @@ int32_t update_task_worker_flash_writer(void* context) { } furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); + // Format LFS before restoring backup on next boot + furi_hal_rtc_set_flag(FuriHalRtcFlagFactoryReset); update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); success = true; diff --git a/assets/protobuf b/assets/protobuf index e3d9cdb6..6c1b8ae6 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit e3d9cdb66ce789f84f6f8e0bdd6d022187964425 +Subproject commit 6c1b8ae66a85bcd7e79e993a0b5573c38c302db5 diff --git a/lib/update_util/update_operation.c b/lib/update_util/update_operation.c index 142dcaaa..9082d262 100644 --- a/lib/update_util/update_operation.c +++ b/lib/update_util/update_operation.c @@ -12,6 +12,8 @@ #define UPDATE_ROOT_DIR "/ext" UPDATE_DIR_DEFAULT_REL_PATH #define UPDATE_PREFIX "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/" #define UPDATE_SUFFIX "/" UPDATE_MANIFEST_DEFAULT_NAME +/* Need at least 4 free LFS pages before update */ +#define UPDATE_MIN_INT_FREE_SPACE 4 * 4 * 1024 static const char* update_prepare_result_descr[] = { [UpdatePrepareResultOK] = "OK", @@ -22,6 +24,7 @@ static const char* update_prepare_result_descr[] = { [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader", [UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file", [UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old", + [UpdatePrepareResultIntFull] = "Need more free space in internal storage", }; const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) { @@ -133,15 +136,22 @@ static bool update_operation_persist_manifest_path(Storage* storage, const char* } UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { - UpdatePrepareResult result = UpdatePrepareResultManifestFolderNotFound; + UpdatePrepareResult result = UpdatePrepareResultIntFull; Storage* storage = furi_record_open("storage"); UpdateManifest* manifest = update_manifest_alloc(); File* file = storage_file_alloc(storage); + uint64_t free_int_space; string_t stage_path; string_init(stage_path); do { + if((storage_common_fs_info(storage, "/int", NULL, &free_int_space) != FSE_OK) || + (free_int_space < UPDATE_MIN_INT_FREE_SPACE)) { + break; + } + if(storage_common_stat(storage, manifest_file_path, NULL) != FSE_OK) { + result = UpdatePrepareResultManifestFolderNotFound; break; } diff --git a/lib/update_util/update_operation.h b/lib/update_util/update_operation.h index 39d58028..056520e1 100644 --- a/lib/update_util/update_operation.h +++ b/lib/update_util/update_operation.h @@ -32,6 +32,8 @@ typedef enum { UpdatePrepareResultManifestPointerError, UpdatePrepareResultTargetMismatch, UpdatePrepareResultOutdatedManifestVersion, + UpdatePrepareResultIntFull, + UpdatePrepareResultUnspecifiedError, } UpdatePrepareResult; const char* update_operation_describe_preparation_result(const UpdatePrepareResult value); diff --git a/scripts/selfupdate.py b/scripts/selfupdate.py index a22ca1f5..6d705747 100644 --- a/scripts/selfupdate.py +++ b/scripts/selfupdate.py @@ -76,12 +76,15 @@ class Main(App): manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] pkg_dir_name = self.args.pkg_dir_name or pkg_name - flipper_update_path = f"/ext/update/{pkg_dir_name}" + update_root = "/ext/update" + flipper_update_path = f"{update_root}/{pkg_dir_name}" self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') # if not os.path.exists(self.args.manifest_path): # self.logger.error("Error: package not found") - if not self.mkdir_on_storage(storage, flipper_update_path): + if not self.mkdir_on_storage( + storage, update_root + ) or not self.mkdir_on_storage(storage, flipper_update_path): self.logger.error(f"Error: cannot create {storage.last_error}") return -2 @@ -99,6 +102,14 @@ class Main(App): storage.send_and_wait_eol( f"update install {flipper_update_path}/{manifest_name}\r" ) + result = storage.read.until(storage.CLI_EOL) + if not b"Verifying" in result: + self.logger.error(f"Unexpected response: {result.decode('ascii')}") + return -4 + result = storage.read.until(storage.CLI_EOL) + if not result.startswith(b"OK"): + self.logger.error(result.decode("ascii")) + return -5 break return 0 finally: From c495677eb5c202280c7a904594479e4dc8879ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 4 Jul 2022 17:26:02 +0300 Subject: [PATCH 11/26] FuriHal: RTC recovery routine and ext3v3 enabled on start (#1357) * FuriHal: leave ext3v3 enabled on start * FuriHal: RTC recovery routine, cleanup resources Co-authored-by: SG --- firmware/targets/f7/furi_hal/furi_hal_power.c | 9 +- .../targets/f7/furi_hal/furi_hal_resources.c | 8 +- .../targets/f7/furi_hal/furi_hal_resources.h | 3 - firmware/targets/f7/furi_hal/furi_hal_rtc.c | 108 ++++++++++++------ 4 files changed, 88 insertions(+), 40 deletions(-) diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index 52bb7bcf..28e6cb05 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -299,6 +300,10 @@ void furi_hal_power_shutdown() { } void furi_hal_power_off() { + // Crutch: shutting down with ext 3V3 off is causing LSE to stop + furi_hal_power_enable_external_3_3v(); + furi_hal_delay_us(1000); + // Send poweroff to charger furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); bq25896_poweroff(&furi_hal_i2c_handle_power); furi_hal_i2c_release(&furi_hal_i2c_handle_power); @@ -482,11 +487,11 @@ void furi_hal_power_dump_state() { } void furi_hal_power_enable_external_3_3v() { - LL_GPIO_SetOutputPin(PERIPH_POWER_GPIO_Port, PERIPH_POWER_Pin); + furi_hal_gpio_write(&periph_power, 1); } void furi_hal_power_disable_external_3_3v() { - LL_GPIO_ResetOutputPin(PERIPH_POWER_GPIO_Port, PERIPH_POWER_Pin); + furi_hal_gpio_write(&periph_power, 0); } void furi_hal_power_suppress_charge_enter() { diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index 21fac834..c1238212 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -58,7 +58,7 @@ const GpioPin gpio_i2c_power_scl = {.port = GPIOA, .pin = LL_GPIO_PIN_9}; const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8}; -const GpioPin periph_power = {.port = PERIPH_POWER_GPIO_Port, .pin = PERIPH_POWER_Pin}; +const GpioPin periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3}; const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11}; const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; @@ -77,6 +77,10 @@ const size_t input_pins_count = sizeof(input_pins) / sizeof(InputPin); void furi_hal_resources_init_early() { furi_hal_gpio_init(&gpio_button_left, GpioModeInput, GpioPullUp, GpioSpeedLow); + // SD Card stepdown control + furi_hal_gpio_write(&periph_power, 1); + furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + // Display pins furi_hal_gpio_write(&gpio_display_rst_n, 1); furi_hal_gpio_init_simple(&gpio_display_rst_n, GpioModeOutputPushPull); @@ -142,8 +146,6 @@ void furi_hal_resources_init() { furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); - NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0)); NVIC_EnableIRQ(EXTI0_IRQn); diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h index 820760a1..d16c567e 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.h +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h @@ -147,9 +147,6 @@ extern const GpioPin gpio_usb_dp; #define PC3_GPIO_Port GPIOC #define PC3_Pin LL_GPIO_PIN_3 -#define PERIPH_POWER_GPIO_Port GPIOA -#define PERIPH_POWER_Pin LL_GPIO_PIN_3 - #define QUARTZ_32MHZ_IN_GPIO_Port GPIOC #define QUARTZ_32MHZ_IN_Pin LL_GPIO_PIN_14 #define QUARTZ_32MHZ_OUT_GPIO_Port GPIOC diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index df410a9f..24dad38f 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -12,7 +12,9 @@ #define TAG "FuriHalRtc" -#define RTC_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady()) +#define FURI_HAL_RTC_LSE_STARTUP_TIME 300 + +#define FURI_HAL_RTC_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady()) #define FURI_HAL_RTC_HEADER_MAGIC 0x10F1 #define FURI_HAL_RTC_HEADER_VERSION 0 @@ -47,33 +49,83 @@ static const uint8_t furi_hal_rtc_days_per_month[][FURI_HAL_RTC_MONTHS_COUNT] = static const uint16_t furi_hal_rtc_days_per_year[] = {365, 366}; -void furi_hal_rtc_init_early() { - // LSE and RTC +static void furi_hal_rtc_reset() { + LL_RCC_ForceBackupDomainReset(); + LL_RCC_ReleaseBackupDomainReset(); +} + +static bool furi_hal_rtc_start_clock_and_switch() { + // Clock operation require access to Backup Domain LL_PWR_EnableBkUpAccess(); - if(!RTC_CLOCK_IS_READY()) { - LL_RCC_LSI1_Enable(); - // Try to start LSE normal way - LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_HIGH); - LL_RCC_LSE_Enable(); - uint32_t c = 0; - while(!RTC_CLOCK_IS_READY() && c < 200) { - LL_mDelay(10); - c++; - } - // Plan B: reset backup domain - if(!RTC_CLOCK_IS_READY()) { - furi_hal_light_sequence("rgb R.r.R.r.R"); - LL_RCC_ForceBackupDomainReset(); - LL_RCC_ReleaseBackupDomainReset(); - NVIC_SystemReset(); - } - // Set RTC domain clock to LSE - LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); + + // Enable LSI and LSE + LL_RCC_LSI1_Enable(); + LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_HIGH); + LL_RCC_LSE_Enable(); + + // Wait for LSI and LSE startup + uint32_t c = 0; + while(!FURI_HAL_RTC_CLOCK_IS_READY() && c < FURI_HAL_RTC_LSE_STARTUP_TIME) { + LL_mDelay(1); + c++; } - // Enable clocking - LL_RCC_EnableRTC(); + + if(FURI_HAL_RTC_CLOCK_IS_READY()) { + LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); + LL_RCC_EnableRTC(); + return LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSE; + } else { + return false; + } +} + +static void furi_hal_rtc_recover() { + FuriHalRtcDateTime datetime = {0}; + + // Handle fixable LSE failure + if(LL_RCC_LSE_IsCSSDetected()) { + furi_hal_light_sequence("rgb B"); + // Shutdown LSE and LSECSS + LL_RCC_LSE_DisableCSS(); + LL_RCC_LSE_Disable(); + } else { + furi_hal_light_sequence("rgb R"); + } + + // Temporary switch to LSI + LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSI); + if(LL_RCC_GetRTCClockSource() == LL_RCC_RTC_CLKSOURCE_LSI) { + // Get datetime before RTC Domain reset + furi_hal_rtc_get_datetime(&datetime); + } + + // Reset RTC Domain + furi_hal_rtc_reset(); + + // Start Clock + if(!furi_hal_rtc_start_clock_and_switch()) { + // Plan C: reset RTC and restart + furi_hal_light_sequence("rgb R.r.R.r.R.r"); + furi_hal_rtc_reset(); + NVIC_SystemReset(); + } + + // Set date if it valid + if(datetime.year != 0) { + furi_hal_rtc_set_datetime(&datetime); + } +} + +void furi_hal_rtc_init_early() { + // Enable RTCAPB clock LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_RTCAPB); + // Prepare clock + if(!furi_hal_rtc_start_clock_and_switch()) { + // Plan B: try to recover + furi_hal_rtc_recover(); + } + // Verify header register uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterHeader); FuriHalRtcHeader* data = (FuriHalRtcHeader*)&data_reg; @@ -98,14 +150,6 @@ void furi_hal_rtc_deinit_early() { } void furi_hal_rtc_init() { - if(LL_RCC_GetRTCClockSource() != LL_RCC_RTC_CLKSOURCE_LSE) { - LL_RCC_ForceBackupDomainReset(); - LL_RCC_ReleaseBackupDomainReset(); - LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSE); - } - - LL_RCC_EnableRTC(); - LL_RTC_InitTypeDef RTC_InitStruct = {0}; RTC_InitStruct.HourFormat = LL_RTC_HOURFORMAT_24HOUR; RTC_InitStruct.AsynchPrescaler = 127; From 793501d62d6ac3ce4d5bca80b3988f06ce9fd6d6 Mon Sep 17 00:00:00 2001 From: Samuel Yvon Date: Mon, 4 Jul 2022 11:32:06 -0400 Subject: [PATCH 12/26] Add GPIO control through RPC (#1282) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add GPIO control through RPC * Assets: sync protobuf to 0.10 * Assets: update protobuf to fixed v10 Co-authored-by: あく --- applications/rpc/rpc.c | 5 +- applications/rpc/rpc_gpio.c | 221 ++++++++++++++++++++++++++++++++++++ applications/rpc/rpc_i.h | 2 + assets/compiled/gpio.pb.c | 33 ++++++ assets/compiled/gpio.pb.h | 183 +++++++++++++++++++++++++++++ assets/protobuf | 2 +- 6 files changed, 444 insertions(+), 2 deletions(-) create mode 100644 applications/rpc/rpc_gpio.c create mode 100644 assets/compiled/gpio.pb.c create mode 100644 assets/compiled/gpio.pb.h diff --git a/applications/rpc/rpc.c b/applications/rpc/rpc.c index 4832afe3..ce063b8e 100644 --- a/applications/rpc/rpc.c +++ b/applications/rpc/rpc.c @@ -51,7 +51,10 @@ static RpcSystemCallbacks rpc_systems[] = { .alloc = rpc_system_gui_alloc, .free = rpc_system_gui_free, }, -}; + { + .alloc = rpc_system_gpio_alloc, + .free = NULL, + }}; struct RpcSession { Rpc* rpc; diff --git a/applications/rpc/rpc_gpio.c b/applications/rpc/rpc_gpio.c new file mode 100644 index 00000000..614e775a --- /dev/null +++ b/applications/rpc/rpc_gpio.c @@ -0,0 +1,221 @@ +#include "flipper.pb.h" +#include "rpc_i.h" +#include "gpio.pb.h" +#include +#include + +static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) { + switch(rpc_pin) { + case PB_Gpio_GpioPin_PC0: + return &gpio_ext_pc0; + case PB_Gpio_GpioPin_PC1: + return &gpio_ext_pc1; + case PB_Gpio_GpioPin_PC3: + return &gpio_ext_pc3; + case PB_Gpio_GpioPin_PB2: + return &gpio_ext_pb2; + case PB_Gpio_GpioPin_PB3: + return &gpio_ext_pb3; + case PB_Gpio_GpioPin_PA4: + return &gpio_ext_pa4; + case PB_Gpio_GpioPin_PA6: + return &gpio_ext_pa6; + case PB_Gpio_GpioPin_PA7: + return &gpio_ext_pa7; + } + + __builtin_unreachable(); +} + +static GpioMode rpc_mode_to_hal_mode(PB_Gpio_GpioPinMode rpc_mode) { + switch(rpc_mode) { + case PB_Gpio_GpioPinMode_OUTPUT: + return GpioModeOutputPushPull; + case PB_Gpio_GpioPinMode_INPUT: + return GpioModeInput; + } + + __builtin_unreachable(); +} + +static GpioPull rpc_pull_mode_to_hall_pull_mode(PB_Gpio_GpioInputPull pull_mode) { + switch(pull_mode) { + case PB_Gpio_GpioInputPull_UP: + return GpioPullUp; + case PB_Gpio_GpioInputPull_DOWN: + return GpioPullDown; + case PB_Gpio_GpioInputPull_NO: + return GpioPullNo; + } + + __builtin_unreachable(); +} + +static void rpc_system_gpio_set_pin_mode(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_set_pin_mode_tag); + + RpcSession* session = context; + furi_assert(session); + + PB_Gpio_SetPinMode cmd = request->content.gpio_set_pin_mode; + const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); + GpioMode mode = rpc_mode_to_hal_mode(cmd.mode); + + furi_hal_gpio_init_simple(pin, mode); + if(mode == GpioModeOutputPushPull) { + furi_hal_gpio_write(pin, false); + } + + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); +} + +static void rpc_system_gpio_write_pin(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_write_pin_tag); + + RpcSession* session = context; + furi_assert(session); + + PB_Gpio_WritePin cmd = request->content.gpio_write_pin; + const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); + uint8_t value = !!(cmd.value); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + response->has_next = false; + + if(LL_GPIO_MODE_OUTPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) { + response->command_status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT; + } else { + response->command_status = PB_CommandStatus_OK; + furi_hal_gpio_write(pin, value); + } + + rpc_send_and_release(session, response); + + free(response); +} + +static void rpc_system_gpio_read_pin(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_read_pin_tag); + + RpcSession* session = context; + furi_assert(session); + + PB_Gpio_ReadPin cmd = request->content.gpio_read_pin; + const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + response->has_next = false; + + if(LL_GPIO_MODE_INPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) { + response->command_status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT; + } else { + response->command_status = PB_CommandStatus_OK; + response->which_content = PB_Main_gpio_read_pin_response_tag; + response->content.gpio_read_pin_response.value = !!furi_hal_gpio_read(pin); + } + + rpc_send_and_release(session, response); + + free(response); +} + +void rpc_system_gpio_get_pin_mode(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_get_pin_mode_tag); + + RpcSession* session = context; + furi_assert(session); + + PB_Gpio_GetPinMode cmd = request->content.gpio_get_pin_mode; + const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + response->has_next = false; + + uint32_t raw_pin_mode = LL_GPIO_GetPinMode(pin->port, pin->pin); + + PB_Gpio_GpioPinMode pin_mode; + if(LL_GPIO_MODE_INPUT == raw_pin_mode) { + pin_mode = PB_Gpio_GpioPinMode_INPUT; + response->command_status = PB_CommandStatus_OK; + } else if(LL_GPIO_MODE_OUTPUT == raw_pin_mode) { + pin_mode = PB_Gpio_GpioPinMode_OUTPUT; + response->command_status = PB_CommandStatus_OK; + } else { + pin_mode = PB_Gpio_GpioPinMode_INPUT; + response->command_status = PB_CommandStatus_ERROR_GPIO_UNKNOWN_PIN_MODE; + } + + response->which_content = PB_Main_gpio_get_pin_mode_response_tag; + response->content.gpio_get_pin_mode_response.mode = pin_mode; + + rpc_send_and_release(session, response); + + free(response); +} + +void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_set_input_pull_tag); + + RpcSession* session = context; + furi_assert(session); + + PB_Gpio_SetInputPull cmd = request->content.gpio_set_input_pull; + const GpioPin* pin = rpc_pin_to_hal_pin(cmd.pin); + const GpioPull pull_mode = rpc_pull_mode_to_hall_pull_mode(cmd.pull_mode); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + response->has_next = false; + + PB_CommandStatus status; + if(LL_GPIO_MODE_INPUT != LL_GPIO_GetPinMode(pin->port, pin->pin)) { + status = PB_CommandStatus_ERROR_GPIO_MODE_INCORRECT; + } else { + status = PB_CommandStatus_OK; + furi_hal_gpio_init(pin, GpioModeInput, pull_mode, GpioSpeedLow); + } + + rpc_send_and_release_empty(session, request->command_id, status); + + free(response); +} + +void* rpc_system_gpio_alloc(RpcSession* session) { + furi_assert(session); + + RpcHandler rpc_handler = { + .message_handler = NULL, + .decode_submessage = NULL, + .context = session, + }; + + rpc_handler.message_handler = rpc_system_gpio_set_pin_mode; + rpc_add_handler(session, PB_Main_gpio_set_pin_mode_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_gpio_write_pin; + rpc_add_handler(session, PB_Main_gpio_write_pin_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_gpio_read_pin; + rpc_add_handler(session, PB_Main_gpio_read_pin_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_gpio_get_pin_mode; + rpc_add_handler(session, PB_Main_gpio_get_pin_mode_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_gpio_set_input_pull; + rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler); + + return NULL; +} diff --git a/applications/rpc/rpc_i.h b/applications/rpc/rpc_i.h index d0c6cf76..e512ad39 100644 --- a/applications/rpc/rpc_i.h +++ b/applications/rpc/rpc_i.h @@ -32,6 +32,8 @@ void* rpc_system_app_alloc(RpcSession* session); void rpc_system_app_free(void* ctx); void* rpc_system_gui_alloc(RpcSession* session); void rpc_system_gui_free(void* ctx); +void* rpc_system_gpio_alloc(RpcSession* session); +void rpc_system_gpio_free(void* ctx); void rpc_print_message(const PB_Main* message); void rpc_cli_command_start_session(Cli* cli, string_t args, void* context); diff --git a/assets/compiled/gpio.pb.c b/assets/compiled/gpio.pb.c new file mode 100644 index 00000000..a47c928d --- /dev/null +++ b/assets/compiled/gpio.pb.c @@ -0,0 +1,33 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.5 */ + +#include "gpio.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(PB_Gpio_SetPinMode, PB_Gpio_SetPinMode, AUTO) + + +PB_BIND(PB_Gpio_SetInputPull, PB_Gpio_SetInputPull, AUTO) + + +PB_BIND(PB_Gpio_GetPinMode, PB_Gpio_GetPinMode, AUTO) + + +PB_BIND(PB_Gpio_GetPinModeResponse, PB_Gpio_GetPinModeResponse, AUTO) + + +PB_BIND(PB_Gpio_ReadPin, PB_Gpio_ReadPin, AUTO) + + +PB_BIND(PB_Gpio_ReadPinResponse, PB_Gpio_ReadPinResponse, AUTO) + + +PB_BIND(PB_Gpio_WritePin, PB_Gpio_WritePin, AUTO) + + + + + + diff --git a/assets/compiled/gpio.pb.h b/assets/compiled/gpio.pb.h new file mode 100644 index 00000000..a8a08df7 --- /dev/null +++ b/assets/compiled/gpio.pb.h @@ -0,0 +1,183 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.5 */ + +#ifndef PB_PB_GPIO_GPIO_PB_H_INCLUDED +#define PB_PB_GPIO_GPIO_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +typedef enum _PB_Gpio_GpioPin { + PB_Gpio_GpioPin_PC0 = 0, + PB_Gpio_GpioPin_PC1 = 1, + PB_Gpio_GpioPin_PC3 = 2, + PB_Gpio_GpioPin_PB2 = 3, + PB_Gpio_GpioPin_PB3 = 4, + PB_Gpio_GpioPin_PA4 = 5, + PB_Gpio_GpioPin_PA6 = 6, + PB_Gpio_GpioPin_PA7 = 7 +} PB_Gpio_GpioPin; + +typedef enum _PB_Gpio_GpioPinMode { + PB_Gpio_GpioPinMode_OUTPUT = 0, + PB_Gpio_GpioPinMode_INPUT = 1 +} PB_Gpio_GpioPinMode; + +typedef enum _PB_Gpio_GpioInputPull { + PB_Gpio_GpioInputPull_NO = 0, + PB_Gpio_GpioInputPull_UP = 1, + PB_Gpio_GpioInputPull_DOWN = 2 +} PB_Gpio_GpioInputPull; + +/* Struct definitions */ +typedef struct _PB_Gpio_GetPinMode { + PB_Gpio_GpioPin pin; +} PB_Gpio_GetPinMode; + +typedef struct _PB_Gpio_GetPinModeResponse { + PB_Gpio_GpioPinMode mode; +} PB_Gpio_GetPinModeResponse; + +typedef struct _PB_Gpio_ReadPin { + PB_Gpio_GpioPin pin; +} PB_Gpio_ReadPin; + +typedef struct _PB_Gpio_ReadPinResponse { + uint32_t value; +} PB_Gpio_ReadPinResponse; + +typedef struct _PB_Gpio_SetInputPull { + PB_Gpio_GpioPin pin; + PB_Gpio_GpioInputPull pull_mode; +} PB_Gpio_SetInputPull; + +typedef struct _PB_Gpio_SetPinMode { + PB_Gpio_GpioPin pin; + PB_Gpio_GpioPinMode mode; +} PB_Gpio_SetPinMode; + +typedef struct _PB_Gpio_WritePin { + PB_Gpio_GpioPin pin; + uint32_t value; +} PB_Gpio_WritePin; + + +/* Helper constants for enums */ +#define _PB_Gpio_GpioPin_MIN PB_Gpio_GpioPin_PC0 +#define _PB_Gpio_GpioPin_MAX PB_Gpio_GpioPin_PA7 +#define _PB_Gpio_GpioPin_ARRAYSIZE ((PB_Gpio_GpioPin)(PB_Gpio_GpioPin_PA7+1)) + +#define _PB_Gpio_GpioPinMode_MIN PB_Gpio_GpioPinMode_OUTPUT +#define _PB_Gpio_GpioPinMode_MAX PB_Gpio_GpioPinMode_INPUT +#define _PB_Gpio_GpioPinMode_ARRAYSIZE ((PB_Gpio_GpioPinMode)(PB_Gpio_GpioPinMode_INPUT+1)) + +#define _PB_Gpio_GpioInputPull_MIN PB_Gpio_GpioInputPull_NO +#define _PB_Gpio_GpioInputPull_MAX PB_Gpio_GpioInputPull_DOWN +#define _PB_Gpio_GpioInputPull_ARRAYSIZE ((PB_Gpio_GpioInputPull)(PB_Gpio_GpioInputPull_DOWN+1)) + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define PB_Gpio_SetPinMode_init_default {_PB_Gpio_GpioPin_MIN, _PB_Gpio_GpioPinMode_MIN} +#define PB_Gpio_SetInputPull_init_default {_PB_Gpio_GpioPin_MIN, _PB_Gpio_GpioInputPull_MIN} +#define PB_Gpio_GetPinMode_init_default {_PB_Gpio_GpioPin_MIN} +#define PB_Gpio_GetPinModeResponse_init_default {_PB_Gpio_GpioPinMode_MIN} +#define PB_Gpio_ReadPin_init_default {_PB_Gpio_GpioPin_MIN} +#define PB_Gpio_ReadPinResponse_init_default {0} +#define PB_Gpio_WritePin_init_default {_PB_Gpio_GpioPin_MIN, 0} +#define PB_Gpio_SetPinMode_init_zero {_PB_Gpio_GpioPin_MIN, _PB_Gpio_GpioPinMode_MIN} +#define PB_Gpio_SetInputPull_init_zero {_PB_Gpio_GpioPin_MIN, _PB_Gpio_GpioInputPull_MIN} +#define PB_Gpio_GetPinMode_init_zero {_PB_Gpio_GpioPin_MIN} +#define PB_Gpio_GetPinModeResponse_init_zero {_PB_Gpio_GpioPinMode_MIN} +#define PB_Gpio_ReadPin_init_zero {_PB_Gpio_GpioPin_MIN} +#define PB_Gpio_ReadPinResponse_init_zero {0} +#define PB_Gpio_WritePin_init_zero {_PB_Gpio_GpioPin_MIN, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define PB_Gpio_GetPinMode_pin_tag 1 +#define PB_Gpio_GetPinModeResponse_mode_tag 1 +#define PB_Gpio_ReadPin_pin_tag 1 +#define PB_Gpio_ReadPinResponse_value_tag 2 +#define PB_Gpio_SetInputPull_pin_tag 1 +#define PB_Gpio_SetInputPull_pull_mode_tag 2 +#define PB_Gpio_SetPinMode_pin_tag 1 +#define PB_Gpio_SetPinMode_mode_tag 2 +#define PB_Gpio_WritePin_pin_tag 1 +#define PB_Gpio_WritePin_value_tag 2 + +/* Struct field encoding specification for nanopb */ +#define PB_Gpio_SetPinMode_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, pin, 1) \ +X(a, STATIC, SINGULAR, UENUM, mode, 2) +#define PB_Gpio_SetPinMode_CALLBACK NULL +#define PB_Gpio_SetPinMode_DEFAULT NULL + +#define PB_Gpio_SetInputPull_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, pin, 1) \ +X(a, STATIC, SINGULAR, UENUM, pull_mode, 2) +#define PB_Gpio_SetInputPull_CALLBACK NULL +#define PB_Gpio_SetInputPull_DEFAULT NULL + +#define PB_Gpio_GetPinMode_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, pin, 1) +#define PB_Gpio_GetPinMode_CALLBACK NULL +#define PB_Gpio_GetPinMode_DEFAULT NULL + +#define PB_Gpio_GetPinModeResponse_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, mode, 1) +#define PB_Gpio_GetPinModeResponse_CALLBACK NULL +#define PB_Gpio_GetPinModeResponse_DEFAULT NULL + +#define PB_Gpio_ReadPin_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, pin, 1) +#define PB_Gpio_ReadPin_CALLBACK NULL +#define PB_Gpio_ReadPin_DEFAULT NULL + +#define PB_Gpio_ReadPinResponse_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, value, 2) +#define PB_Gpio_ReadPinResponse_CALLBACK NULL +#define PB_Gpio_ReadPinResponse_DEFAULT NULL + +#define PB_Gpio_WritePin_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, pin, 1) \ +X(a, STATIC, SINGULAR, UINT32, value, 2) +#define PB_Gpio_WritePin_CALLBACK NULL +#define PB_Gpio_WritePin_DEFAULT NULL + +extern const pb_msgdesc_t PB_Gpio_SetPinMode_msg; +extern const pb_msgdesc_t PB_Gpio_SetInputPull_msg; +extern const pb_msgdesc_t PB_Gpio_GetPinMode_msg; +extern const pb_msgdesc_t PB_Gpio_GetPinModeResponse_msg; +extern const pb_msgdesc_t PB_Gpio_ReadPin_msg; +extern const pb_msgdesc_t PB_Gpio_ReadPinResponse_msg; +extern const pb_msgdesc_t PB_Gpio_WritePin_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define PB_Gpio_SetPinMode_fields &PB_Gpio_SetPinMode_msg +#define PB_Gpio_SetInputPull_fields &PB_Gpio_SetInputPull_msg +#define PB_Gpio_GetPinMode_fields &PB_Gpio_GetPinMode_msg +#define PB_Gpio_GetPinModeResponse_fields &PB_Gpio_GetPinModeResponse_msg +#define PB_Gpio_ReadPin_fields &PB_Gpio_ReadPin_msg +#define PB_Gpio_ReadPinResponse_fields &PB_Gpio_ReadPinResponse_msg +#define PB_Gpio_WritePin_fields &PB_Gpio_WritePin_msg + +/* Maximum encoded size of messages (where known) */ +#define PB_Gpio_GetPinModeResponse_size 2 +#define PB_Gpio_GetPinMode_size 2 +#define PB_Gpio_ReadPinResponse_size 6 +#define PB_Gpio_ReadPin_size 2 +#define PB_Gpio_SetInputPull_size 4 +#define PB_Gpio_SetPinMode_size 4 +#define PB_Gpio_WritePin_size 8 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/assets/protobuf b/assets/protobuf index 6c1b8ae6..d9e34366 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 6c1b8ae66a85bcd7e79e993a0b5573c38c302db5 +Subproject commit d9e343661dd36cfab792b78be1dea4e5950cb4dd From 6b6ea44802d2e12bff37f4c371a9f4e32537d4e5 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 4 Jul 2022 19:53:04 +0300 Subject: [PATCH 13/26] fbt: initial blackmagic support (#1362) * fbt: added separate script for Windows env setup; moved flash targets from firmware.scons to SConstruct; added Blackmagic support with automatic probe port resolution; added apps.c rebuild on any manifest.fam changes; fixed simultaneous flash & debug ops * fbt: added networked BlackmagicResolver mode; added `get_blackmagic` target for IDE integration * fbt: cleanup * fbt: docs update; fixed blackmagic lookup on certain usb hubs * fbt: removed explicit python serial port import * fbt: cleanup * fbt: raising exception on multiple serial blackmagic probes --- SConstruct | 109 ++++++++++++++++++++++---- documentation/fbt.md | 3 + fbt | 1 - fbt.cmd | 3 +- fbt_options.py | 16 +++- firmware.scons | 18 +---- site_scons/commandline.scons | 6 ++ site_scons/environ.scons | 1 + site_scons/fbt/util.py | 4 + site_scons/site_tools/blackmagic.py | 74 +++++++++++++++++ site_scons/site_tools/fbt_dist.py | 29 +++---- site_scons/site_tools/sconsmodular.py | 6 +- 12 files changed, 220 insertions(+), 50 deletions(-) create mode 100644 site_scons/site_tools/blackmagic.py diff --git a/SConstruct b/SConstruct index 7e8fc896..33463cdc 100644 --- a/SConstruct +++ b/SConstruct @@ -28,15 +28,41 @@ SConscript("site_scons/cc.scons", exports={"ENV": coreenv}) # Store root dir in environment for certain tools coreenv["ROOT_DIR"] = Dir(".") + # Create a separate "dist" environment and add construction envs to it distenv = coreenv.Clone( - tools=["fbt_dist", "openocd"], - GDBOPTS="-ex 'target extended-remote | ${OPENOCD} -c \"gdb_port pipe\" ${OPENOCD_OPTS}' " - '-ex "set confirm off" ', + tools=["fbt_dist", "openocd", "blackmagic"], + OPENOCD_GDB_PIPE=["|openocd -c 'gdb_port pipe' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}"], + GDBOPTS_BASE=[ + "-ex", + "target extended-remote ${GDBREMOTE}", + "-ex", + "set confirm off", + ], + GDBOPTS_BLACKMAGIC=[ + "-ex", + "monitor swdp_scan", + "-ex", + "monitor debug_bmp enable", + "-ex", + "attach 1", + "-ex", + "set mem inaccessible-by-default off", + ], + GDBPYOPTS=[ + "-ex", + "source debug/FreeRTOS/FreeRTOS.py", + "-ex", + "source debug/PyCortexMDebug/PyCortexMDebug.py", + "-ex", + "svd_load ${SVD_FILE}", + "-ex", + "compare-sections", + ], ENV=os.environ, ) -firmware_out = distenv.AddFwProject( +firmware_env = distenv.AddFwProject( base_env=coreenv, fw_type="firmware", fw_env_key="FW_ENV", @@ -44,7 +70,7 @@ firmware_out = distenv.AddFwProject( # If enabled, initialize updater-related targets if GetOption("fullenv"): - updater_out = distenv.AddFwProject( + updater_env = distenv.AddFwProject( base_env=coreenv, fw_type="updater", fw_env_key="UPD_ENV", @@ -72,19 +98,32 @@ if GetOption("fullenv"): selfupdate_dist = distenv.DistCommand( "updater_package", - (distenv["DIST_DEPENDS"], firmware_out["FW_RESOURCES"]), + (distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES"]), DIST_EXTRA=dist_arguments, ) # Updater debug - distenv.AddDebugTarget("updater_debug", updater_out, False) + distenv.PhonyTarget( + "updater_debug", + "${GDBPYCOM}", + source=updater_env["FW_ELF"], + GDBREMOTE="${OPENOCD_GDB_PIPE}", + ) + + distenv.PhonyTarget( + "updater_blackmagic", + "${GDBPYCOM}", + source=updater_env["FW_ELF"], + GDBOPTS=distenv.subst("$GDBOPTS_BLACKMAGIC"), + GDBREMOTE="${BLACKMAGIC_ADDR}", + ) # Installation over USB & CLI usb_update_package = distenv.UsbInstall( "#build/usbinstall.flag", ( distenv["DIST_DEPENDS"], - firmware_out["FW_RESOURCES"], + firmware_env["FW_RESOURCES"], selfupdate_dist, ), ) @@ -104,15 +143,47 @@ copro_dist = distenv.CoproBuilder( ) distenv.Alias("copro_dist", copro_dist) +firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env) +distenv.Alias("flash", firmware_flash) + +firmware_bm_flash = distenv.PhonyTarget( + "flash_blackmagic", + "$GDB $GDBOPTS $SOURCES $GDBFLASH", + source=firmware_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="${BLACKMAGIC_ADDR}", + GDBFLASH=[ + "-ex", + "load", + "-ex", + "quit", + ], +) + # Debugging firmware -distenv.AddDebugTarget("debug", firmware_out) +firmware_debug = distenv.PhonyTarget( + "debug", + "${GDBPYCOM}", + source=firmware_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE}", + GDBREMOTE="${OPENOCD_GDB_PIPE}", +) +distenv.Depends(firmware_debug, firmware_flash) + +distenv.PhonyTarget( + "blackmagic", + "${GDBPYCOM}", + source=firmware_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="${BLACKMAGIC_ADDR}", +) + # Debug alien elf distenv.PhonyTarget( "debug_other", - "$GDBPYCOM", - GDBPYOPTS= - # '-ex "source ${ROOT_DIR.abspath}/debug/FreeRTOS/FreeRTOS.py" ' - '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ', + "${GDBPYCOM}", + GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ', + GDBREMOTE="${OPENOCD_GDB_PIPE}", ) # Just start OpenOCD @@ -125,11 +196,19 @@ distenv.PhonyTarget( distenv.PhonyTarget( "lint", "${PYTHON3} scripts/lint.py check ${LINT_SOURCES}", - LINT_SOURCES=firmware_out["LINT_SOURCES"], + LINT_SOURCES=firmware_env["LINT_SOURCES"], ) distenv.PhonyTarget( "format", "${PYTHON3} scripts/lint.py format ${LINT_SOURCES}", - LINT_SOURCES=firmware_out["LINT_SOURCES"], + LINT_SOURCES=firmware_env["LINT_SOURCES"], +) + + +# Find blackmagic probe + +distenv.PhonyTarget( + "get_blackmagic", + "@echo $( ${BLACKMAGIC_ADDR} $)", ) diff --git a/documentation/fbt.md b/documentation/fbt.md index 9658ff6a..48d761f5 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -34,7 +34,9 @@ FBT keeps track of internal dependencies, so you only need to build the highest- - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded - `debug_updater` - attach gdb with updater's .elf loaded. _Requires `--with-updater` option_ - `debug_other` - attach gdb without loading any .elf. Allows to manually add external elf files with `add-symbol-file` in gdb. +- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board) - `openocd` - just start OpenOCD +- `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration ### Firmware targets @@ -43,6 +45,7 @@ FBT keeps track of internal dependencies, so you only need to build the highest- - Check out `--extra-ext-apps` for force adding extra apps to external build - `firmware_snake_game_list`, etc - generate source + assembler listing for app's .elf - `flash`, `firmware_flash` - flash current version to attached device with OpenOCD over ST-Link +- `flash_blackmagic` - flash current version to attached device with Blackmagic probe - `firmware_cdb` - generate compilation database - `firmware_all`, `updater_all` - build basic set of binaries - `firmware_list`, `updater_list` - generate source + assembler listing diff --git a/fbt b/fbt index c95b1371..47444aa7 100755 --- a/fbt +++ b/fbt @@ -6,7 +6,6 @@ SCRIPTDIR="$( dirname -- "$0"; )"; SCONS_EP=${SCRIPTDIR}/lib/scons/scripts/scons.py if [[ -d .git ]]; then - echo Updating git submodules git submodule update --init else # Not in a git repo echo Not in a git repo, please clone with git clone --recursive diff --git a/fbt.cmd b/fbt.cmd index 67d42132..cecab465 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -3,8 +3,7 @@ set SCONS_EP=%~dp0\lib\scons\scripts\scons.py if exist ".git" ( - echo Updating git submodules - git submodule update --init + git submodule update --init ) set "SCONS_DEFAULT_FLAGS=-Q --warn=target-not-built" diff --git a/fbt_options.py b/fbt_options.py index 3cad7e58..1c9f9c82 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -41,10 +41,24 @@ COPRO_STACK_BIN_DIR = posixpath.join( # Supported toolchain versions FBT_TOOLCHAIN_VERSIONS = (" 10.3.",) -OPENOCD_OPTS = '-f interface/stlink.cfg -c "transport select hla_swd" -f debug/stm32wbx.cfg -c "stm32wbx.cpu configure -rtos auto" -c "init"' +OPENOCD_OPTS = [ + "-f", + "interface/stlink.cfg", + "-c", + "transport select hla_swd", + "-f", + "debug/stm32wbx.cfg", + "-c", + "stm32wbx.cpu configure -rtos auto", + "-c", + "init", +] SVD_FILE = "debug/STM32WB55_CM4.svd" +# Look for blackmagic probe on serial ports +BLACKMAGIC = "auto" + FIRMWARE_APPS = { "default": [ "crypto_start", diff --git a/firmware.scons b/firmware.scons index 4bed960f..95deea0a 100644 --- a/firmware.scons +++ b/firmware.scons @@ -6,7 +6,7 @@ from fbt.util import link_dir # Building initial C environment for libs env = ENV.Clone( - tools=["compilation_db", "fwbin", "openocd", "fbt_apps"], + tools=["compilation_db", "fwbin", "fbt_apps"], COMPILATIONDB_USE_ABSPATH=True, BUILD_DIR=fw_build_meta["build_dir"], IS_BASE_FIRMWARE=fw_build_meta["type"] == "firmware", @@ -139,6 +139,8 @@ apps_c = fwenv.ApplicationsC( "applications/applications.c", Value(fwenv["APPS"]), ) +# Adding dependency on manifest files so apps.c is rebuilt when any manifest is changed +fwenv.Depends(apps_c, fwenv.GlobRecursive("*.fam", "applications")) sources = [apps_c] # Gather sources only from app folders from current configuration @@ -235,7 +237,7 @@ AddPostAction(fwelf, Action("@$SIZECOM")) AddPostAction(fwelf, Action(link_latest_dir, None)) link_dir_command = fwenv["LINK_DIR_CMD"] = fwenv.PhonyTarget( - "${FIRMWARE_BUILD_CFG}" + "_latest", + fwenv.subst("${FIRMWARE_BUILD_CFG}_latest"), Action(lambda target, source, env: link_elf_dir_as_latest(env, source[0]), None), source=fwelf, ) @@ -249,18 +251,6 @@ Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_dfu", fwdfu) fwdump = fwenv.ObjDump("${FIRMWARE_BUILD_CFG}") Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_list", fwdump) -# Additional FW-related pseudotargets -flash = fwenv["FW_FLASH"] = fwenv.OpenOCDFlash( - "#build/oocd-${FIRMWARE_BUILD_CFG}-flash.flag", - "${FIRMWARE_BUILD_CFG}", - OPENOCD_COMMAND='-c "program ${SOURCE.posix} reset exit ${IMAGE_BASE_ADDRESS}"', -) -if fwenv["FORCE"]: - fwenv.AlwaysBuild(flash) -fwenv.Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_flash", flash) -if fwenv["IS_BASE_FIRMWARE"]: - fwenv.Alias("flash", flash) - # Compile DB generation fwcdb = fwenv["FW_CDB"] = fwenv.CompilationDatabase("compile_commands.json") diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 06e83657..5a7a0dd2 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -162,6 +162,12 @@ vars.Add( default="", ) +vars.Add( + "BLACKMAGIC", + help="Blackmagic probe location", + default="auto", +) + vars.Add( "UPDATE_SPLASH", help="Directory name with slideshow frames to render after installing update package", diff --git a/site_scons/environ.scons b/site_scons/environ.scons index 9e49e8b6..99d4cc0b 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -78,5 +78,6 @@ coreenv["TEMPFILEARGESCFUNC"] = util.tempfile_arg_esc_func util.wrap_tempfile(coreenv, "LINKCOM") util.wrap_tempfile(coreenv, "ARCOM") +coreenv["SINGLEQUOTEFUNC"] = util.single_quote Return("coreenv") diff --git a/site_scons/fbt/util.py b/site_scons/fbt/util.py index 11509b2d..e77c9e58 100644 --- a/site_scons/fbt/util.py +++ b/site_scons/fbt/util.py @@ -42,3 +42,7 @@ def random_alnum(length): return "".join( random.choice(string.ascii_letters + string.digits) for _ in range(length) ) + + +def single_quote(arg_list): + return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list) diff --git a/site_scons/site_tools/blackmagic.py b/site_scons/site_tools/blackmagic.py new file mode 100644 index 00000000..ec48c15f --- /dev/null +++ b/site_scons/site_tools/blackmagic.py @@ -0,0 +1,74 @@ +from SCons.Errors import StopError + + +class BlackmagicResolver: + BLACKMAGIC_HOSTNAME = "blackmagic.local" + + def __init__(self, env): + self.env = env + + # On Win: + # 'location': '1-5:x.0', 'name': 'COM4', + # 'location': '1-5:x.2', 'name': 'COM13', + # On Linux: + # 'location': '1-1.2:1.0', 'name': 'ttyACM0', + # 'location': '1-1.2:1.2', 'name': 'ttyACM1', + # On MacOS: + # 'location': '0-1.3', 'name': 'cu.usbmodemblackmagic1', + # 'location': '0-1.3', 'name': 'cu.usbmodemblackmagic3', + def _find_probe(self): + import serial.tools.list_ports as list_ports + + ports = list(list_ports.grep("blackmagic")) + if len(ports) == 0: + # Blackmagic probe serial port not found, will be handled later + pass + elif len(ports) > 2: + raise StopError("More than one Blackmagic probe found") + else: + # If you're getting any issues with auto lookup, uncomment this + # print("\n".join([f"{p.device} {vars(p)}" for p in ports])) + return sorted(ports, key=lambda p: f"{p.location}_{p.name}")[0] + + # Look up blackmagic probe hostname with dns + def _resolve_hostname(self): + import socket + + try: + return socket.gethostbyname(self.BLACKMAGIC_HOSTNAME) + except socket.gaierror: + print("Failed to resolve Blackmagic hostname") + return None + + def get_serial(self): + if not (probe := self._find_probe()): + return None + # print(f"Found Blackmagic probe on {probe.device}") + if self.env.subst("$PLATFORM") == "win32": + return f"\\\\.\\{probe.device}" + return probe.device + + def get_networked(self): + if not (probe := self._resolve_hostname()): + return None + + return f"tcp:{probe}:2345" + + def __str__(self): + # print("distenv blackmagic", self.env.subst("$BLACKMAGIC")) + if (blackmagic := self.env.subst("$BLACKMAGIC")) != "auto": + return blackmagic + + # print("Looking for Blackmagic...") + if probe := self.get_serial() or self.get_networked(): + return probe + + raise Exception("Please specify BLACKMAGIC=...") + + +def generate(env): + env.SetDefault(BLACKMAGIC_ADDR=BlackmagicResolver(env)) + + +def exists(env): + return True diff --git a/site_scons/site_tools/fbt_dist.py b/site_scons/site_tools/fbt_dist.py index fcfecbcb..37fbf74b 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/site_scons/site_tools/fbt_dist.py @@ -54,20 +54,20 @@ def AddFwProject(env, base_env, fw_type, fw_env_key): return project_env -def AddDebugTarget(env, alias, targetenv, force_flash=True): - debug_target = env.PhonyTarget( - alias, - "$GDBPYCOM", - source=targetenv["FW_ELF"], - GDBPYOPTS='-ex "source debug/FreeRTOS/FreeRTOS.py" ' - '-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ' - '-ex "svd_load ${SVD_FILE}" ' - '-ex "compare-sections"', +def AddOpenOCDFlashTarget(env, targetenv, **kw): + openocd_target = env.OpenOCDFlash( + "#build/oocd-${BUILD_CFG}-flash.flag", + targetenv["FW_BIN"], + OPENOCD_COMMAND=[ + "-c", + "program ${SOURCE.posix} reset exit ${BASE_ADDRESS}", + ], + BUILD_CFG=targetenv.subst("$FIRMWARE_BUILD_CFG"), + BASE_ADDRESS=targetenv.subst("$IMAGE_BASE_ADDRESS"), + **kw, ) - if force_flash: - env.Depends(debug_target, targetenv["FW_FLASH"]) - - return debug_target + env.Alias(targetenv.subst("${FIRMWARE_BUILD_CFG}_flash"), openocd_target) + return openocd_target def DistCommand(env, name, source, **kw): @@ -85,8 +85,9 @@ def DistCommand(env, name, source, **kw): def generate(env): env.AddMethod(AddFwProject) - env.AddMethod(AddDebugTarget) env.AddMethod(DistCommand) + env.AddMethod(AddOpenOCDFlashTarget) + env.SetDefault( COPRO_MCU_FAMILY="STM32WB5x", ) diff --git a/site_scons/site_tools/sconsmodular.py b/site_scons/site_tools/sconsmodular.py index a4bb9f65..db0cb8f3 100644 --- a/site_scons/site_tools/sconsmodular.py +++ b/site_scons/site_tools/sconsmodular.py @@ -34,9 +34,9 @@ def PhonyTarget(env, name, action, source=None, **kw): source = [] phony_name = "phony_" + name env.Pseudo(phony_name) - return env.AlwaysBuild( - env.Alias(name, env.Command(phony_name, source, action, **kw)) - ) + command = env.Command(phony_name, source, action, **kw) + env.AlwaysBuild(env.Alias(name, command)) + return command def generate(env): From 43fd2e44339f3c392bdfbf7f57739b51d5aa9157 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 4 Jul 2022 20:38:18 +0300 Subject: [PATCH 14/26] fbt: added support for FBT_NO_SYNC environment variable to skip submodule update at start (#1363) --- assets/compiled/gpio.pb.c | 33 ------- assets/compiled/gpio.pb.h | 183 -------------------------------------- documentation/fbt.md | 5 +- fbt | 13 +-- fbt.cmd | 9 +- 5 files changed, 18 insertions(+), 225 deletions(-) delete mode 100644 assets/compiled/gpio.pb.c delete mode 100644 assets/compiled/gpio.pb.h diff --git a/assets/compiled/gpio.pb.c b/assets/compiled/gpio.pb.c deleted file mode 100644 index a47c928d..00000000 --- a/assets/compiled/gpio.pb.c +++ /dev/null @@ -1,33 +0,0 @@ -/* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.5 */ - -#include "gpio.pb.h" -#if PB_PROTO_HEADER_VERSION != 40 -#error Regenerate this file with the current version of nanopb generator. -#endif - -PB_BIND(PB_Gpio_SetPinMode, PB_Gpio_SetPinMode, AUTO) - - -PB_BIND(PB_Gpio_SetInputPull, PB_Gpio_SetInputPull, AUTO) - - -PB_BIND(PB_Gpio_GetPinMode, PB_Gpio_GetPinMode, AUTO) - - -PB_BIND(PB_Gpio_GetPinModeResponse, PB_Gpio_GetPinModeResponse, AUTO) - - -PB_BIND(PB_Gpio_ReadPin, PB_Gpio_ReadPin, AUTO) - - -PB_BIND(PB_Gpio_ReadPinResponse, PB_Gpio_ReadPinResponse, AUTO) - - -PB_BIND(PB_Gpio_WritePin, PB_Gpio_WritePin, AUTO) - - - - - - diff --git a/assets/compiled/gpio.pb.h b/assets/compiled/gpio.pb.h deleted file mode 100644 index a8a08df7..00000000 --- a/assets/compiled/gpio.pb.h +++ /dev/null @@ -1,183 +0,0 @@ -/* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.5 */ - -#ifndef PB_PB_GPIO_GPIO_PB_H_INCLUDED -#define PB_PB_GPIO_GPIO_PB_H_INCLUDED -#include - -#if PB_PROTO_HEADER_VERSION != 40 -#error Regenerate this file with the current version of nanopb generator. -#endif - -/* Enum definitions */ -typedef enum _PB_Gpio_GpioPin { - PB_Gpio_GpioPin_PC0 = 0, - PB_Gpio_GpioPin_PC1 = 1, - PB_Gpio_GpioPin_PC3 = 2, - PB_Gpio_GpioPin_PB2 = 3, - PB_Gpio_GpioPin_PB3 = 4, - PB_Gpio_GpioPin_PA4 = 5, - PB_Gpio_GpioPin_PA6 = 6, - PB_Gpio_GpioPin_PA7 = 7 -} PB_Gpio_GpioPin; - -typedef enum _PB_Gpio_GpioPinMode { - PB_Gpio_GpioPinMode_OUTPUT = 0, - PB_Gpio_GpioPinMode_INPUT = 1 -} PB_Gpio_GpioPinMode; - -typedef enum _PB_Gpio_GpioInputPull { - PB_Gpio_GpioInputPull_NO = 0, - PB_Gpio_GpioInputPull_UP = 1, - PB_Gpio_GpioInputPull_DOWN = 2 -} PB_Gpio_GpioInputPull; - -/* Struct definitions */ -typedef struct _PB_Gpio_GetPinMode { - PB_Gpio_GpioPin pin; -} PB_Gpio_GetPinMode; - -typedef struct _PB_Gpio_GetPinModeResponse { - PB_Gpio_GpioPinMode mode; -} PB_Gpio_GetPinModeResponse; - -typedef struct _PB_Gpio_ReadPin { - PB_Gpio_GpioPin pin; -} PB_Gpio_ReadPin; - -typedef struct _PB_Gpio_ReadPinResponse { - uint32_t value; -} PB_Gpio_ReadPinResponse; - -typedef struct _PB_Gpio_SetInputPull { - PB_Gpio_GpioPin pin; - PB_Gpio_GpioInputPull pull_mode; -} PB_Gpio_SetInputPull; - -typedef struct _PB_Gpio_SetPinMode { - PB_Gpio_GpioPin pin; - PB_Gpio_GpioPinMode mode; -} PB_Gpio_SetPinMode; - -typedef struct _PB_Gpio_WritePin { - PB_Gpio_GpioPin pin; - uint32_t value; -} PB_Gpio_WritePin; - - -/* Helper constants for enums */ -#define _PB_Gpio_GpioPin_MIN PB_Gpio_GpioPin_PC0 -#define _PB_Gpio_GpioPin_MAX PB_Gpio_GpioPin_PA7 -#define _PB_Gpio_GpioPin_ARRAYSIZE ((PB_Gpio_GpioPin)(PB_Gpio_GpioPin_PA7+1)) - -#define _PB_Gpio_GpioPinMode_MIN PB_Gpio_GpioPinMode_OUTPUT -#define _PB_Gpio_GpioPinMode_MAX PB_Gpio_GpioPinMode_INPUT -#define _PB_Gpio_GpioPinMode_ARRAYSIZE ((PB_Gpio_GpioPinMode)(PB_Gpio_GpioPinMode_INPUT+1)) - -#define _PB_Gpio_GpioInputPull_MIN PB_Gpio_GpioInputPull_NO -#define _PB_Gpio_GpioInputPull_MAX PB_Gpio_GpioInputPull_DOWN -#define _PB_Gpio_GpioInputPull_ARRAYSIZE ((PB_Gpio_GpioInputPull)(PB_Gpio_GpioInputPull_DOWN+1)) - - -#ifdef __cplusplus -extern "C" { -#endif - -/* Initializer values for message structs */ -#define PB_Gpio_SetPinMode_init_default {_PB_Gpio_GpioPin_MIN, _PB_Gpio_GpioPinMode_MIN} -#define PB_Gpio_SetInputPull_init_default {_PB_Gpio_GpioPin_MIN, _PB_Gpio_GpioInputPull_MIN} -#define PB_Gpio_GetPinMode_init_default {_PB_Gpio_GpioPin_MIN} -#define PB_Gpio_GetPinModeResponse_init_default {_PB_Gpio_GpioPinMode_MIN} -#define PB_Gpio_ReadPin_init_default {_PB_Gpio_GpioPin_MIN} -#define PB_Gpio_ReadPinResponse_init_default {0} -#define PB_Gpio_WritePin_init_default {_PB_Gpio_GpioPin_MIN, 0} -#define PB_Gpio_SetPinMode_init_zero {_PB_Gpio_GpioPin_MIN, _PB_Gpio_GpioPinMode_MIN} -#define PB_Gpio_SetInputPull_init_zero {_PB_Gpio_GpioPin_MIN, _PB_Gpio_GpioInputPull_MIN} -#define PB_Gpio_GetPinMode_init_zero {_PB_Gpio_GpioPin_MIN} -#define PB_Gpio_GetPinModeResponse_init_zero {_PB_Gpio_GpioPinMode_MIN} -#define PB_Gpio_ReadPin_init_zero {_PB_Gpio_GpioPin_MIN} -#define PB_Gpio_ReadPinResponse_init_zero {0} -#define PB_Gpio_WritePin_init_zero {_PB_Gpio_GpioPin_MIN, 0} - -/* Field tags (for use in manual encoding/decoding) */ -#define PB_Gpio_GetPinMode_pin_tag 1 -#define PB_Gpio_GetPinModeResponse_mode_tag 1 -#define PB_Gpio_ReadPin_pin_tag 1 -#define PB_Gpio_ReadPinResponse_value_tag 2 -#define PB_Gpio_SetInputPull_pin_tag 1 -#define PB_Gpio_SetInputPull_pull_mode_tag 2 -#define PB_Gpio_SetPinMode_pin_tag 1 -#define PB_Gpio_SetPinMode_mode_tag 2 -#define PB_Gpio_WritePin_pin_tag 1 -#define PB_Gpio_WritePin_value_tag 2 - -/* Struct field encoding specification for nanopb */ -#define PB_Gpio_SetPinMode_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, pin, 1) \ -X(a, STATIC, SINGULAR, UENUM, mode, 2) -#define PB_Gpio_SetPinMode_CALLBACK NULL -#define PB_Gpio_SetPinMode_DEFAULT NULL - -#define PB_Gpio_SetInputPull_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, pin, 1) \ -X(a, STATIC, SINGULAR, UENUM, pull_mode, 2) -#define PB_Gpio_SetInputPull_CALLBACK NULL -#define PB_Gpio_SetInputPull_DEFAULT NULL - -#define PB_Gpio_GetPinMode_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, pin, 1) -#define PB_Gpio_GetPinMode_CALLBACK NULL -#define PB_Gpio_GetPinMode_DEFAULT NULL - -#define PB_Gpio_GetPinModeResponse_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, mode, 1) -#define PB_Gpio_GetPinModeResponse_CALLBACK NULL -#define PB_Gpio_GetPinModeResponse_DEFAULT NULL - -#define PB_Gpio_ReadPin_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, pin, 1) -#define PB_Gpio_ReadPin_CALLBACK NULL -#define PB_Gpio_ReadPin_DEFAULT NULL - -#define PB_Gpio_ReadPinResponse_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UINT32, value, 2) -#define PB_Gpio_ReadPinResponse_CALLBACK NULL -#define PB_Gpio_ReadPinResponse_DEFAULT NULL - -#define PB_Gpio_WritePin_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, UENUM, pin, 1) \ -X(a, STATIC, SINGULAR, UINT32, value, 2) -#define PB_Gpio_WritePin_CALLBACK NULL -#define PB_Gpio_WritePin_DEFAULT NULL - -extern const pb_msgdesc_t PB_Gpio_SetPinMode_msg; -extern const pb_msgdesc_t PB_Gpio_SetInputPull_msg; -extern const pb_msgdesc_t PB_Gpio_GetPinMode_msg; -extern const pb_msgdesc_t PB_Gpio_GetPinModeResponse_msg; -extern const pb_msgdesc_t PB_Gpio_ReadPin_msg; -extern const pb_msgdesc_t PB_Gpio_ReadPinResponse_msg; -extern const pb_msgdesc_t PB_Gpio_WritePin_msg; - -/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ -#define PB_Gpio_SetPinMode_fields &PB_Gpio_SetPinMode_msg -#define PB_Gpio_SetInputPull_fields &PB_Gpio_SetInputPull_msg -#define PB_Gpio_GetPinMode_fields &PB_Gpio_GetPinMode_msg -#define PB_Gpio_GetPinModeResponse_fields &PB_Gpio_GetPinModeResponse_msg -#define PB_Gpio_ReadPin_fields &PB_Gpio_ReadPin_msg -#define PB_Gpio_ReadPinResponse_fields &PB_Gpio_ReadPinResponse_msg -#define PB_Gpio_WritePin_fields &PB_Gpio_WritePin_msg - -/* Maximum encoded size of messages (where known) */ -#define PB_Gpio_GetPinModeResponse_size 2 -#define PB_Gpio_GetPinMode_size 2 -#define PB_Gpio_ReadPinResponse_size 6 -#define PB_Gpio_ReadPin_size 2 -#define PB_Gpio_SetInputPull_size 4 -#define PB_Gpio_SetPinMode_size 4 -#define PB_Gpio_WritePin_size 8 - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/documentation/fbt.md b/documentation/fbt.md index 48d761f5..a1e92849 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -10,7 +10,10 @@ Make sure that `gcc-arm-none-eabi` toolchain & OpenOCD executables are in system ## NB -FBT constructs all referenced environments & their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding construction of certain targets behind command-line options. +* `fbt` constructs all referenced environments & their targets' dependency trees on startup. So, to keep startup time as low as possible, we're hiding construction of certain targets behind command-line options. +* `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in environment: + * On Windows, that's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from + * On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` ## Invoking FBT diff --git a/fbt b/fbt index 47444aa7..b3d2ca35 100755 --- a/fbt +++ b/fbt @@ -5,12 +5,13 @@ set -e SCRIPTDIR="$( dirname -- "$0"; )"; SCONS_EP=${SCRIPTDIR}/lib/scons/scripts/scons.py -if [[ -d .git ]]; then - git submodule update --init -else # Not in a git repo - echo Not in a git repo, please clone with git clone --recursive - # Return error code 1 to indicate failure - exit 1 +if [[ -z "${FBT_NO_SYNC:-}" ]] ; then + if [[ -d .git ]]; then + git submodule update --init + else + echo Not in a git repo, please clone with git clone --recursive + exit 1 + fi fi SCONS_DEFAULT_FLAGS="-Q --warn=target-not-built" diff --git a/fbt.cmd b/fbt.cmd index cecab465..5edecd21 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -2,8 +2,13 @@ set SCONS_EP=%~dp0\lib\scons\scripts\scons.py -if exist ".git" ( - git submodule update --init +if [%FBT_NO_SYNC%] == [] ( + if exist ".git" ( + git submodule update --init + ) else ( + echo Not in a git repo, please clone with git clone --recursive + exit /b 1 + ) ) set "SCONS_DEFAULT_FLAGS=-Q --warn=target-not-built" From 05b7b7f2db4f811c5a30915286ceed2db904d004 Mon Sep 17 00:00:00 2001 From: Roman Shchekin Date: Mon, 4 Jul 2022 20:49:23 +0300 Subject: [PATCH 15/26] plugins: snake: simplification in direction calculation (#1361) Co-authored-by: hedger --- applications/snake_game/snake_game.c | 43 ++++------------------------ 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/applications/snake_game/snake_game.c b/applications/snake_game/snake_game.c index bb9e207d..1a4bf812 100644 --- a/applications/snake_game/snake_game.c +++ b/applications/snake_game/snake_game.c @@ -29,6 +29,8 @@ typedef enum { GameStateGameOver, } GameState; +// Note: do not change without purpose. Current values are used in smart +// orthogonality calculation in `snake_game_get_turn_snake`. typedef enum { DirectionUp, DirectionRight, @@ -195,44 +197,9 @@ static bool } static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) { - switch(snake_state->currentMovement) { - case DirectionUp: - switch(snake_state->nextMovement) { - case DirectionRight: - return DirectionRight; - case DirectionLeft: - return DirectionLeft; - default: - return snake_state->currentMovement; - } - case DirectionRight: - switch(snake_state->nextMovement) { - case DirectionUp: - return DirectionUp; - case DirectionDown: - return DirectionDown; - default: - return snake_state->currentMovement; - } - case DirectionDown: - switch(snake_state->nextMovement) { - case DirectionRight: - return DirectionRight; - case DirectionLeft: - return DirectionLeft; - default: - return snake_state->currentMovement; - } - default: // case DirectionLeft: - switch(snake_state->nextMovement) { - case DirectionUp: - return DirectionUp; - case DirectionDown: - return DirectionDown; - default: - return snake_state->currentMovement; - } - } + // Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality. + bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1; + return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement; } static Point snake_game_get_next_step(SnakeState const* const snake_state) { From c49db35ee0fa58909e5f37876a935c7baa9337bd Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 5 Jul 2022 15:17:26 +0300 Subject: [PATCH 16/26] storage: lfs fingerprint wasn't updated when both geometry changed & factory reset was requested, resulting in second re-format on next boot (#1372) --- applications/storage/storages/storage_int.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/applications/storage/storages/storage_int.c b/applications/storage/storages/storage_int.c index 9b1ec700..bdd78172 100644 --- a/applications/storage/storages/storage_int.c +++ b/applications/storage/storages/storage_int.c @@ -4,6 +4,7 @@ #define TAG "StorageInt" #define STORAGE_PATH "/int" +#define LFS_CLEAN_FINGERPRINT 0 typedef struct { const size_t start_address; @@ -162,8 +163,9 @@ static LFSData* storage_int_lfs_data_alloc() { return lfs_data; }; -static bool storage_int_is_fingerprint_valid(LFSData* lfs_data) { - bool value = true; +// Returns true if fingerprint was invalid and LFS reformatting is needed +static bool storage_int_check_and_set_fingerprint(LFSData* lfs_data) { + bool value = false; uint32_t os_fingerprint = 0; os_fingerprint |= ((lfs_data->start_page & 0xFF) << 0); @@ -171,13 +173,13 @@ static bool storage_int_is_fingerprint_valid(LFSData* lfs_data) { os_fingerprint |= ((LFS_DISK_VERSION_MAJOR & 0xFFFF) << 16); uint32_t rtc_fingerprint = furi_hal_rtc_get_register(FuriHalRtcRegisterLfsFingerprint); - if(rtc_fingerprint == 0) { + if(rtc_fingerprint == LFS_CLEAN_FINGERPRINT) { FURI_LOG_I(TAG, "Storing LFS fingerprint in RTC"); furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint); } else if(rtc_fingerprint != os_fingerprint) { FURI_LOG_E(TAG, "LFS fingerprint mismatch"); furi_hal_rtc_set_register(FuriHalRtcRegisterLfsFingerprint, os_fingerprint); - value = false; + value = true; } return value; @@ -187,8 +189,9 @@ static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) { int err; lfs_t* lfs = &lfs_data->lfs; + bool was_fingerprint_outdated = storage_int_check_and_set_fingerprint(lfs_data); bool need_format = furi_hal_rtc_is_flag_set(FuriHalRtcFlagFactoryReset) || - !storage_int_is_fingerprint_valid(lfs_data); + was_fingerprint_outdated; if(need_format) { // Format storage From 34d97ebb4a32a54270cdf968edf2313b14377de1 Mon Sep 17 00:00:00 2001 From: hedger Date: Tue, 5 Jul 2022 15:24:59 +0300 Subject: [PATCH 17/26] fbt: compile_commands fixes & better `latest` directory handling (#1368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: fixed linking updater as latest build dir for "flash_usb" * fbt: fixed cdb regeneration logic; refactored build/latest linking logic * fbt: docs update Co-authored-by: あく --- documentation/fbt.md | 2 +- firmware.scons | 101 ++++++++++++------------------ site_scons/fbt/util.py | 28 +++++++-- site_scons/site_tools/fbt_dist.py | 1 - 4 files changed, 63 insertions(+), 69 deletions(-) diff --git a/documentation/fbt.md b/documentation/fbt.md index a1e92849..575b0265 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -14,6 +14,7 @@ Make sure that `gcc-arm-none-eabi` toolchain & OpenOCD executables are in system * `fbt` always performs `git submodule update --init` on start, unless you set `FBT_NO_SYNC=1` in environment: * On Windows, that's `set "FBT_NO_SYNC=1"` in the shell you're running `fbt` from * On \*nix, it's `$ FBT_NO_SYNC=1 ./fbt ...` +* `fbt` builds updater & firmware in separate subdirectories in `build`, with their names depending on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder, which is used for code completion support in IDE. ## Invoking FBT @@ -49,7 +50,6 @@ FBT keeps track of internal dependencies, so you only need to build the highest- - `firmware_snake_game_list`, etc - generate source + assembler listing for app's .elf - `flash`, `firmware_flash` - flash current version to attached device with OpenOCD over ST-Link - `flash_blackmagic` - flash current version to attached device with Blackmagic probe -- `firmware_cdb` - generate compilation database - `firmware_all`, `updater_all` - build basic set of binaries - `firmware_list`, `updater_list` - generate source + assembler listing diff --git a/firmware.scons b/firmware.scons index 95deea0a..7cc67f43 100644 --- a/firmware.scons +++ b/firmware.scons @@ -2,12 +2,15 @@ Import("ENV", "fw_build_meta") import os -from fbt.util import link_dir +from fbt.util import ( + should_gen_cdb_and_link_dir, + link_elf_dir_as_latest, +) # Building initial C environment for libs env = ENV.Clone( tools=["compilation_db", "fwbin", "fbt_apps"], - COMPILATIONDB_USE_ABSPATH=True, + 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"], @@ -77,7 +80,12 @@ if not env["VERBOSE"]: ) -if fw_build_meta["type"] == "updater": +if env["IS_BASE_FIRMWARE"]: + env.Append( + FIRMWARE_BUILD_CFG="firmware", + RAM_EXEC=False, + ) +else: env.Append( FIRMWARE_BUILD_CFG="updater", RAM_EXEC=True, @@ -85,13 +93,6 @@ if fw_build_meta["type"] == "updater": "FURI_RAM_EXEC", ], ) -else: - env.Append( - FIRMWARE_BUILD_CFG="firmware", - RAM_EXEC=False, - ) -# print(env.Dump()) - # Invoke child SCopscripts to populate global `env` + build their own part of the code lib_targets = env.BuildModules( @@ -131,9 +132,7 @@ fwenv.AppendUnique( CPPDEFINES=fwenv["APPBUILD"].get_apps_cdefs(), ) - # Build applications.c for selected services & apps - # Depends on virtual value-only node, so it only gets rebuilt when set of apps changes apps_c = fwenv.ApplicationsC( "applications/applications.c", @@ -143,7 +142,7 @@ apps_c = fwenv.ApplicationsC( fwenv.Depends(apps_c, fwenv.GlobRecursive("*.fam", "applications")) sources = [apps_c] -# Gather sources only from app folders from current configuration +# Gather sources only from app folders in current configuration for app_folder in fwenv["APPBUILD"].get_builtin_app_folders(): sources += fwenv.GlobRecursive("*.c*", os.path.join("applications", app_folder)) @@ -194,55 +193,22 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( "appframe", "assets", "misc", + "mbedtls", + "loclass", # 2nd round "flipperformat", "toolbox", - "mbedtls", - "loclass", ], ) -def link_elf_dir_as_latest(env, elf_target): - # Ugly way to check if updater-related targets were requested - elf_dir = elf_target.Dir(".") - explicitly_building_updater = False - # print("BUILD_TARGETS:", ','.join(BUILD_TARGETS)) - for build_target in BUILD_TARGETS: - # print(">>> ", str(build_target)) - if "updater" in str(build_target): - explicitly_building_updater = True - - latest_dir = env.Dir("#build/latest") - - link_this_dir = True - if explicitly_building_updater: - # If updater is explicitly requested, link to the latest updater - # Otherwise, link to the latest firmware - link_this_dir = not env["IS_BASE_FIRMWARE"] - - if link_this_dir: - print(f"Setting {elf_dir} as latest built dir") - return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") - - -def link_latest_dir(env, target, source): - return link_elf_dir_as_latest(env, target[0]) - - -# Make it depend on everything child builders returned +# Firmware depends on everything child builders returned Depends(fwelf, lib_targets) +# Output extra details after building firmware AddPostAction(fwelf, fwenv["APPBUILD_DUMP"]) AddPostAction(fwelf, Action("@$SIZECOM")) -AddPostAction(fwelf, Action(link_latest_dir, None)) - -link_dir_command = fwenv["LINK_DIR_CMD"] = fwenv.PhonyTarget( - fwenv.subst("${FIRMWARE_BUILD_CFG}_latest"), - Action(lambda target, source, env: link_elf_dir_as_latest(env, source[0]), None), - source=fwelf, -) - +# Produce extra firmware files fwhex = fwenv["FW_HEX"] = fwenv.HEXBuilder("${FIRMWARE_BUILD_CFG}") fwbin = fwenv["FW_BIN"] = fwenv.BINBuilder("${FIRMWARE_BUILD_CFG}") fwdfu = fwenv["FW_DFU"] = fwenv.DFUBuilder("${FIRMWARE_BUILD_CFG}") @@ -252,21 +218,34 @@ fwdump = fwenv.ObjDump("${FIRMWARE_BUILD_CFG}") Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_list", fwdump) -# Compile DB generation -fwcdb = fwenv["FW_CDB"] = fwenv.CompilationDatabase("compile_commands.json") -fwenv.Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb) - - -artifacts = [ +fw_artifacts = fwenv["FW_ARTIFACTS"] = [ fwhex, fwbin, fwdfu, - env["FW_VERSION_JSON"], - fwcdb, + fwenv["FW_VERSION_JSON"], ] -fwenv["FW_ARTIFACTS"] = artifacts -Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", artifacts) +# If current configuration was explicitly requested, generate compilation database +# and link its directory as build/latest +if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS): + fwcdb = fwenv.CompilationDatabase() + # without filtering, both updater & firmware commands would be generated + fwenv.Replace(COMPILATIONDB_PATH_FILTER=fwenv.subst("*${FW_FLAVOR}*")) + Depends(fwcdb, fwelf) + fw_artifacts.append(fwcdb) + + # Adding as a phony target, so folder link is updated even if elf didn't change + link_dir_command = fwenv.PhonyTarget( + fwenv.subst("${FIRMWARE_BUILD_CFG}_latest"), + Action( + lambda source, target, env: link_elf_dir_as_latest(env, source[0]), + None, + ), + source=fwelf, + ) + fw_artifacts.append(link_dir_command) + +Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_all", fw_artifacts) Return("fwenv") diff --git a/site_scons/fbt/util.py b/site_scons/fbt/util.py index e77c9e58..a6bc4c06 100644 --- a/site_scons/fbt/util.py +++ b/site_scons/fbt/util.py @@ -38,11 +38,27 @@ def link_dir(target_path, source_path, is_windows): os.symlink(source_path, target_path) -def random_alnum(length): - return "".join( - random.choice(string.ascii_letters + string.digits) for _ in range(length) - ) - - def single_quote(arg_list): return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list) + + +def link_elf_dir_as_latest(env, elf_node): + elf_dir = elf_node.Dir(".") + latest_dir = env.Dir("#build/latest") + print(f"Setting {elf_dir} as latest built dir (./build/latest/)") + return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32") + + +def should_gen_cdb_and_link_dir(env, requested_targets): + explicitly_building_updater = False + # Hacky way to check if updater-related targets were requested + for build_target in requested_targets: + if "updater" in str(build_target): + explicitly_building_updater = True + + is_updater = not env["IS_BASE_FIRMWARE"] + # If updater is explicitly requested, link to the latest updater + # Otherwise, link to firmware + return (is_updater and explicitly_building_updater) or ( + not is_updater and not explicitly_building_updater + ) diff --git a/site_scons/site_tools/fbt_dist.py b/site_scons/site_tools/fbt_dist.py index 37fbf74b..15a653a6 100644 --- a/site_scons/site_tools/fbt_dist.py +++ b/site_scons/site_tools/fbt_dist.py @@ -46,7 +46,6 @@ def AddFwProject(env, base_env, fw_type, fw_env_key): ], DIST_DEPENDS=[ project_env["FW_ARTIFACTS"], - project_env["LINK_DIR_CMD"], ], ) From 8af2198684e92343fdd9bb6c8de2f95efc9d2d05 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Tue, 5 Jul 2022 08:28:27 -0700 Subject: [PATCH 18/26] Iclass UI (#1366) * Move structs to header * roll mbedtls into loclass * Picopass with scene for reading card * Picopass: fix memory leak * Lib: return mbedtls back * Picopass: rename symbols to match naming guide Co-authored-by: Aleksandr Kutuzov --- applications/picopass/application.fam | 2 +- applications/picopass/picopass.c | 478 ++++-------------- applications/picopass/picopass.h | 8 +- applications/picopass/picopass_device.c | 33 ++ applications/picopass/picopass_device.h | 43 ++ applications/picopass/picopass_i.h | 66 +++ applications/picopass/picopass_worker.c | 317 ++++++++++++ applications/picopass/picopass_worker.h | 45 ++ applications/picopass/picopass_worker_i.h | 24 + applications/picopass/scenes/picopass_scene.c | 30 ++ applications/picopass/scenes/picopass_scene.h | 29 ++ .../picopass/scenes/picopass_scene_config.h | 3 + .../scenes/picopass_scene_read_card.c | 55 ++ .../scenes/picopass_scene_read_card_success.c | 78 +++ .../picopass/scenes/picopass_scene_start.c | 45 ++ lib/loclass/optimized_ikeys.c | 2 +- lib/loclass/optimized_ikeys.h | 2 +- 17 files changed, 881 insertions(+), 379 deletions(-) create mode 100644 applications/picopass/picopass_device.c create mode 100644 applications/picopass/picopass_device.h create mode 100644 applications/picopass/picopass_i.h create mode 100644 applications/picopass/picopass_worker.c create mode 100755 applications/picopass/picopass_worker.h create mode 100644 applications/picopass/picopass_worker_i.h create mode 100755 applications/picopass/scenes/picopass_scene.c create mode 100644 applications/picopass/scenes/picopass_scene.h create mode 100755 applications/picopass/scenes/picopass_scene_config.h create mode 100644 applications/picopass/scenes/picopass_scene_read_card.c create mode 100644 applications/picopass/scenes/picopass_scene_read_card_success.c create mode 100644 applications/picopass/scenes/picopass_scene_start.c diff --git a/applications/picopass/application.fam b/applications/picopass/application.fam index 3ad72d27..22309425 100644 --- a/applications/picopass/application.fam +++ b/applications/picopass/application.fam @@ -4,7 +4,7 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="picopass_app", cdefines=["APP_PICOPASS"], - requires=["gui"], + requires=["storage", "gui"], stack_size=1 * 1024, icon="A_Plugins_14", order=30, diff --git a/applications/picopass/picopass.c b/applications/picopass/picopass.c index 2bf0d6f0..fb9e6b0d 100644 --- a/applications/picopass/picopass.c +++ b/applications/picopass/picopass.c @@ -1,397 +1,137 @@ -#include "picopass.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include +#include "picopass_i.h" #define TAG "PicoPass" -typedef enum { - EventTypeTick, - EventTypeKey, -} EventType; - -typedef struct { - EventType type; - InputEvent input; -} PluginEvent; - -typedef struct { - bool valid; - uint8_t bitLength; - uint8_t FacilityCode; - uint16_t CardNumber; -} WiegandRecord; - -typedef struct { - bool biometrics; - uint8_t encryption; - uint8_t credential[8]; - uint8_t pin0[8]; - uint8_t pin1[8]; - WiegandRecord record; -} PACS; - -enum State { INIT, READY, RESULT }; -typedef struct { - enum State state; - PACS pacs; -} PluginState; - -uint8_t iclass_key[8] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; -uint8_t iclass_decryptionkey[16] = - {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; -ApplicationArea AA1; - -static void render_callback(Canvas* const canvas, void* ctx) { - const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); - if(plugin_state == NULL) { - return; - } - // border around the edge of the screen - canvas_draw_frame(canvas, 0, 0, 128, 64); - - canvas_set_font(canvas, FontPrimary); - - if(plugin_state->state == INIT) { - canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Loading..."); - } else if(plugin_state->state == READY) { - canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Push center to scan"); - } else if(plugin_state->state == RESULT) { - char raw_credential[25] = {0}; - sprintf( - raw_credential, - "%02x %02x %02x %02x %02x %02x %02x %02x", - plugin_state->pacs.credential[0], - plugin_state->pacs.credential[1], - plugin_state->pacs.credential[2], - plugin_state->pacs.credential[3], - plugin_state->pacs.credential[4], - plugin_state->pacs.credential[5], - plugin_state->pacs.credential[6], - plugin_state->pacs.credential[7]); - canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignTop, raw_credential); - - if(plugin_state->pacs.record.valid) { - char parsed[20] = {0}; - sprintf( - parsed, - "FC: %03u CN: %05u", - plugin_state->pacs.record.FacilityCode, - plugin_state->pacs.record.CardNumber); - canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignBottom, parsed); - } - } - - release_mutex((ValueMutex*)ctx, plugin_state); +bool picopass_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + Picopass* picopass = context; + return scene_manager_handle_custom_event(picopass->scene_manager, event); } -static void input_callback(InputEvent* input_event, osMessageQueueId_t event_queue) { - furi_assert(event_queue); - - PluginEvent event = {.type = EventTypeKey, .input = *input_event}; - osMessageQueuePut(event_queue, &event, 0, osWaitForever); +bool picopass_back_event_callback(void* context) { + furi_assert(context); + Picopass* picopass = context; + return scene_manager_handle_back_event(picopass->scene_manager); } -static void picopass_state_init(PluginState* const plugin_state) { - plugin_state->state = READY; +void picopass_tick_event_callback(void* context) { + furi_assert(context); + Picopass* picopass = context; + scene_manager_handle_tick_event(picopass->scene_manager); } -ReturnCode decrypt(uint8_t* enc_data, uint8_t* dec_data) { - uint8_t key[32] = {0}; - memcpy(key, iclass_decryptionkey, sizeof(iclass_decryptionkey)); - mbedtls_des3_context ctx; - mbedtls_des3_init(&ctx); - mbedtls_des3_set2key_dec(&ctx, key); - mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); - mbedtls_des3_free(&ctx); - return ERR_NONE; +Picopass* picopass_alloc() { + Picopass* picopass = malloc(sizeof(Picopass)); + + picopass->worker = picopass_worker_alloc(); + picopass->view_dispatcher = view_dispatcher_alloc(); + picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass); + view_dispatcher_enable_queue(picopass->view_dispatcher); + view_dispatcher_set_event_callback_context(picopass->view_dispatcher, picopass); + view_dispatcher_set_custom_event_callback( + picopass->view_dispatcher, picopass_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + picopass->view_dispatcher, picopass_back_event_callback); + view_dispatcher_set_tick_event_callback( + picopass->view_dispatcher, picopass_tick_event_callback, 100); + + // Picopass device + picopass->dev = picopass_device_alloc(); + + // Open GUI record + picopass->gui = furi_record_open("gui"); + view_dispatcher_attach_to_gui( + picopass->view_dispatcher, picopass->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + picopass->notifications = furi_record_open("notification"); + + // Submenu + picopass->submenu = submenu_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, PicopassViewMenu, submenu_get_view(picopass->submenu)); + + // Popup + picopass->popup = popup_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup)); + + // Custom Widget + picopass->widget = widget_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, PicopassViewWidget, widget_get_view(picopass->widget)); + + return picopass; } -ReturnCode parseWiegand(uint8_t* data, WiegandRecord* record) { - uint32_t* halves = (uint32_t*)data; - if(halves[0] == 0) { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); - record->bitLength = 31 - leading0s; - } else { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); - record->bitLength = 63 - leading0s; - } - FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); +void picopass_free(Picopass* picopass) { + furi_assert(picopass); - if(record->bitLength == 26) { - uint8_t* v4 = data + 4; - v4[0] = 0; + // Submenu + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu); + submenu_free(picopass->submenu); - uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); + // Popup + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); + popup_free(picopass->popup); - record->CardNumber = (bot >> 1) & 0xFFFF; - record->FacilityCode = (bot >> 17) & 0xFF; - record->valid = true; - } else { - record->CardNumber = 0; - record->FacilityCode = 0; - record->valid = false; - } - return ERR_NONE; + // Custom Widget + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget); + widget_free(picopass->widget); + + // Worker + picopass_worker_stop(picopass->worker); + picopass_worker_free(picopass->worker); + + // View Dispatcher + view_dispatcher_free(picopass->view_dispatcher); + + // Scene Manager + scene_manager_free(picopass->scene_manager); + + // GUI + furi_record_close("gui"); + picopass->gui = NULL; + + // Notifications + furi_record_close("notification"); + picopass->notifications = NULL; + + picopass_device_free(picopass->dev); + picopass->dev = NULL; + + free(picopass); } -ReturnCode disable_field(ReturnCode rc) { - st25r3916TxRxOff(); - rfalLowPowerModeStart(); - return rc; +static const NotificationSequence picopass_sequence_blink_start_blue = { + &message_blink_start_10, + &message_blink_set_color_blue, + &message_do_not_reset, + NULL, +}; + +static const NotificationSequence picopass_sequence_blink_stop = { + &message_blink_stop, + NULL, +}; + +void picopass_blink_start(Picopass* picopass) { + notification_message(picopass->notifications, &picopass_sequence_blink_start_blue); } -ReturnCode picopass_read_card(ApplicationArea* AA1) { - rfalPicoPassIdentifyRes idRes; - rfalPicoPassSelectRes selRes; - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - - ReturnCode err; - - uint8_t div_key[8] = {0}; - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - st25r3916TxRxOn(); - rfalLowPowerModeStop(); - rfalWorker(); - err = rfalPicoPassPollerInitialize(); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d\n", err); - return disable_field(err); - } - - err = rfalFieldOnAndStartGT(); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d\n", err); - return disable_field(err); - } - - err = rfalPicoPassPollerCheckPresence(); - if(err != ERR_RF_COLLISION) { - FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d\n", err); - return disable_field(err); - } - - err = rfalPicoPassPollerIdentify(&idRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d\n", err); - return disable_field(err); - } - - err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d\n", err); - return disable_field(err); - } - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - return disable_field(err); - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - diversifyKey(selRes.CSN, iclass_key, div_key); - opt_doReaderMAC(ccnr, div_key, mac); - - err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); - return disable_field(err); - } - - for(size_t i = 0; i < 4; i++) { - FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i + 6); - err = rfalPicoPassPollerReadBlock(i + 6, &(AA1->block[i])); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err); - return disable_field(err); - } - } - return disable_field(ERR_NONE); +void picopass_blink_stop(Picopass* picopass) { + notification_message(picopass->notifications, &picopass_sequence_blink_stop); } int32_t picopass_app(void* p) { UNUSED(p); - osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(PluginEvent), NULL); + Picopass* picopass = picopass_alloc(); - PluginState* plugin_state = malloc(sizeof(PluginState)); - picopass_state_init(plugin_state); - ValueMutex state_mutex; - if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { - FURI_LOG_E("Hello_world", "cannot create mutex\r\n"); - free(plugin_state); - return 255; - } + scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart); - // Set system callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, &state_mutex); - view_port_input_callback_set(view_port, input_callback, event_queue); + view_dispatcher_run(picopass->view_dispatcher); - // Open GUI and register view_port - Gui* gui = furi_record_open("gui"); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); - - PluginEvent event; - ReturnCode err; - for(bool processing = true; processing;) { - osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100); - PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); - - if(event_status == osOK) { - // press events - if(event.type == EventTypeKey) { - if(event.input.type == InputTypePress) { - switch(event.input.key) { - case InputKeyUp: - FURI_LOG_D(TAG, "Input Up"); - break; - case InputKeyDown: - FURI_LOG_D(TAG, "Input Down"); - break; - case InputKeyRight: - FURI_LOG_D(TAG, "Input Right"); - break; - case InputKeyLeft: - FURI_LOG_D(TAG, "Input Left"); - break; - case InputKeyOk: - FURI_LOG_D(TAG, "Input OK"); - err = picopass_read_card(&AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_card error %d", err); - plugin_state->state = READY; - break; - } - FURI_LOG_D(TAG, "read OK"); - - plugin_state->pacs.biometrics = AA1.block[0].data[4]; - plugin_state->pacs.encryption = AA1.block[0].data[7]; - if(plugin_state->pacs.encryption == 0x17) { - FURI_LOG_D(TAG, "3DES Encrypted"); - err = decrypt(AA1.block[1].data, plugin_state->pacs.credential); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - FURI_LOG_D(TAG, "Decrypted 7"); - - err = decrypt(AA1.block[2].data, plugin_state->pacs.pin0); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - FURI_LOG_D(TAG, "Decrypted 8"); - - err = decrypt(AA1.block[3].data, plugin_state->pacs.pin1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - FURI_LOG_D(TAG, "Decrypted 9"); - } else if(plugin_state->pacs.encryption == 0x14) { - FURI_LOG_D(TAG, "No Encryption"); - memcpy( - plugin_state->pacs.credential, - AA1.block[1].data, - RFAL_PICOPASS_MAX_BLOCK_LEN); - memcpy( - plugin_state->pacs.pin0, - AA1.block[2].data, - RFAL_PICOPASS_MAX_BLOCK_LEN); - memcpy( - plugin_state->pacs.pin1, - AA1.block[3].data, - RFAL_PICOPASS_MAX_BLOCK_LEN); - } else if(plugin_state->pacs.encryption == 0x15) { - FURI_LOG_D(TAG, "DES Encrypted"); - } else { - FURI_LOG_D(TAG, "Unknown encryption"); - break; - } - - FURI_LOG_D( - TAG, - "credential %02x%02x%02x%02x%02x%02x%02x%02x", - plugin_state->pacs.credential[0], - plugin_state->pacs.credential[1], - plugin_state->pacs.credential[2], - plugin_state->pacs.credential[3], - plugin_state->pacs.credential[4], - plugin_state->pacs.credential[5], - plugin_state->pacs.credential[6], - plugin_state->pacs.credential[7]); - FURI_LOG_D( - TAG, - "pin0 %02x%02x%02x%02x%02x%02x%02x%02x", - plugin_state->pacs.pin0[0], - plugin_state->pacs.pin0[1], - plugin_state->pacs.pin0[2], - plugin_state->pacs.pin0[3], - plugin_state->pacs.pin0[4], - plugin_state->pacs.pin0[5], - plugin_state->pacs.pin0[6], - plugin_state->pacs.pin0[7]); - FURI_LOG_D( - TAG, - "pin1 %02x%02x%02x%02x%02x%02x%02x%02x", - plugin_state->pacs.pin1[0], - plugin_state->pacs.pin1[1], - plugin_state->pacs.pin1[2], - plugin_state->pacs.pin1[3], - plugin_state->pacs.pin1[4], - plugin_state->pacs.pin1[5], - plugin_state->pacs.pin1[6], - plugin_state->pacs.pin1[7]); - - err = parseWiegand( - plugin_state->pacs.credential, &plugin_state->pacs.record); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "parse error %d", err); - break; - } - if(plugin_state->pacs.record.valid) { - FURI_LOG_D( - TAG, - "FC: %03d CN: %05d", - plugin_state->pacs.record.FacilityCode, - plugin_state->pacs.record.CardNumber); - } - plugin_state->state = RESULT; - - break; - case InputKeyBack: - FURI_LOG_D(TAG, "Input Back"); - processing = false; - break; - } - } - } - } else { - // FURI_LOG_D(TAG, "osMessageQueue: event timeout"); - // event timeout - } - - view_port_update(view_port); - release_mutex(&state_mutex, plugin_state); - } - - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close("gui"); - view_port_free(view_port); - osMessageQueueDelete(event_queue); + picopass_free(picopass); return 0; } diff --git a/applications/picopass/picopass.h b/applications/picopass/picopass.h index e24d97d7..a1a87d7f 100644 --- a/applications/picopass/picopass.h +++ b/applications/picopass/picopass.h @@ -1,9 +1,3 @@ #pragma once -#include -#include -#include -#include - -#define PP_MAX_DUMP_SIZE 1024 -#define FURI_HAL_PICOPASS_UID_MAX_LEN 10 +typedef struct Picopass Picopass; diff --git a/applications/picopass/picopass_device.c b/applications/picopass/picopass_device.c new file mode 100644 index 00000000..802c24e4 --- /dev/null +++ b/applications/picopass/picopass_device.c @@ -0,0 +1,33 @@ +#include "picopass_device.h" + +#include +#include + +#define TAG "PicopassDevice" + +PicopassDevice* picopass_device_alloc() { + PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); + picopass_dev->storage = furi_record_open("storage"); + picopass_dev->dialogs = furi_record_open("dialogs"); + return picopass_dev; +} + +void picopass_device_clear(PicopassDevice* dev) { + furi_assert(dev); + + picopass_device_data_clear(&dev->dev_data); + memset(&dev->dev_data, 0, sizeof(dev->dev_data)); +} + +void picopass_device_free(PicopassDevice* picopass_dev) { + furi_assert(picopass_dev); + picopass_device_clear(picopass_dev); + furi_record_close("storage"); + furi_record_close("dialogs"); + free(picopass_dev); +} + +void picopass_device_data_clear(PicopassDeviceData* dev_data) { + FURI_LOG_D(TAG, "picopass_device_data_clear"); + memset(&dev_data->AA1, 0, sizeof(ApplicationArea)); +} diff --git a/applications/picopass/picopass_device.h b/applications/picopass/picopass_device.h new file mode 100644 index 00000000..af4c07b9 --- /dev/null +++ b/applications/picopass/picopass_device.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include + +#include + +typedef struct { + bool valid; + uint8_t bitLength; + uint8_t FacilityCode; + uint16_t CardNumber; +} PicopassWiegandRecord; + +typedef struct { + bool biometrics; + uint8_t encryption; + uint8_t credential[8]; + uint8_t pin0[8]; + uint8_t pin1[8]; + PicopassWiegandRecord record; +} PicopassPacs; + +typedef struct { + ApplicationArea AA1; + PicopassPacs pacs; +} PicopassDeviceData; + +typedef struct { + Storage* storage; + DialogsApp* dialogs; + PicopassDeviceData dev_data; +} PicopassDevice; + +PicopassDevice* picopass_device_alloc(); + +void picopass_device_free(PicopassDevice* picopass_dev); + +void picopass_device_data_clear(PicopassDeviceData* dev_data); + +void picopass_device_clear(PicopassDevice* dev); diff --git a/applications/picopass/picopass_i.h b/applications/picopass/picopass_i.h new file mode 100644 index 00000000..04fd6876 --- /dev/null +++ b/applications/picopass/picopass_i.h @@ -0,0 +1,66 @@ +#pragma once + +#include "picopass.h" +#include "picopass_worker.h" +#include "picopass_device.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include + +enum PicopassCustomEvent { + // Reserve first 100 events for button types and indexes, starting from 0 + PicopassCustomEventReserved = 100, + + PicopassCustomEventViewExit, + PicopassCustomEventWorkerExit, + PicopassCustomEventByteInputDone, + PicopassCustomEventTextInputDone, + PicopassCustomEventDictAttackDone, +}; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +struct Picopass { + PicopassWorker* worker; + ViewDispatcher* view_dispatcher; + Gui* gui; + NotificationApp* notifications; + SceneManager* scene_manager; + PicopassDevice* dev; + + // Common Views + Submenu* submenu; + Popup* popup; + Widget* widget; +}; + +typedef enum { + PicopassViewMenu, + PicopassViewPopup, + PicopassViewWidget, +} PicopassView; + +Picopass* picopass_alloc(); + +void picopass_blink_start(Picopass* picopass); + +void picopass_blink_stop(Picopass* picopass); diff --git a/applications/picopass/picopass_worker.c b/applications/picopass/picopass_worker.c new file mode 100644 index 00000000..abefcb71 --- /dev/null +++ b/applications/picopass/picopass_worker.c @@ -0,0 +1,317 @@ +#include "picopass_worker_i.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define TAG "PicopassWorker" + +const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; +const uint8_t picopass_iclass_decryptionkey[] = + {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; + +static void picopass_worker_enable_field() { + st25r3916TxRxOn(); + rfalLowPowerModeStop(); + rfalWorker(); +} + +static ReturnCode picopass_worker_disable_field(ReturnCode rc) { + st25r3916TxRxOff(); + rfalLowPowerModeStart(); + return rc; +} + +static ReturnCode picopass_worker_decrypt(uint8_t* enc_data, uint8_t* dec_data) { + uint8_t key[32] = {0}; + memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey)); + mbedtls_des3_context ctx; + mbedtls_des3_init(&ctx); + mbedtls_des3_set2key_dec(&ctx, key); + mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); + mbedtls_des3_free(&ctx); + return ERR_NONE; +} + +static ReturnCode picopass_worker_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) { + uint32_t* halves = (uint32_t*)data; + if(halves[0] == 0) { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); + record->bitLength = 31 - leading0s; + } else { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); + record->bitLength = 63 - leading0s; + } + FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); + + if(record->bitLength == 26) { + uint8_t* v4 = data + 4; + v4[0] = 0; + + uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); + + record->CardNumber = (bot >> 1) & 0xFFFF; + record->FacilityCode = (bot >> 17) & 0xFF; + record->valid = true; + } else { + record->CardNumber = 0; + record->FacilityCode = 0; + record->valid = false; + } + return ERR_NONE; +} + +/***************************** Picopass Worker API *******************************/ + +PicopassWorker* picopass_worker_alloc() { + PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker)); + + // Worker thread attributes + picopass_worker->thread = furi_thread_alloc(); + furi_thread_set_name(picopass_worker->thread, "PicopassWorker"); + furi_thread_set_stack_size(picopass_worker->thread, 8192); + furi_thread_set_callback(picopass_worker->thread, picopass_worker_task); + furi_thread_set_context(picopass_worker->thread, picopass_worker); + + picopass_worker->callback = NULL; + picopass_worker->context = NULL; + picopass_worker->storage = furi_record_open("storage"); + + picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); + + return picopass_worker; +} + +void picopass_worker_free(PicopassWorker* picopass_worker) { + furi_assert(picopass_worker); + + furi_thread_free(picopass_worker->thread); + + furi_record_close("storage"); + + free(picopass_worker); +} + +PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker) { + return picopass_worker->state; +} + +void picopass_worker_start( + PicopassWorker* picopass_worker, + PicopassWorkerState state, + PicopassDeviceData* dev_data, + PicopassWorkerCallback callback, + void* context) { + furi_assert(picopass_worker); + furi_assert(dev_data); + + FURI_LOG_D(TAG, "picopass_worker_start"); + + picopass_worker->callback = callback; + picopass_worker->context = context; + picopass_worker->dev_data = dev_data; + picopass_worker_change_state(picopass_worker, state); + furi_thread_start(picopass_worker->thread); +} + +void picopass_worker_stop(PicopassWorker* picopass_worker) { + furi_assert(picopass_worker); + if(picopass_worker->state == PicopassWorkerStateBroken || + picopass_worker->state == PicopassWorkerStateReady) { + return; + } + picopass_worker_disable_field(ERR_NONE); + + picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop); + furi_thread_join(picopass_worker->thread); +} + +void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) { + picopass_worker->state = state; +} + +/***************************** Picopass Worker Thread *******************************/ + +ReturnCode picopass_detect_card(int timeout) { + UNUSED(timeout); + + ReturnCode err; + + err = rfalPicoPassPollerInitialize(); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d", err); + return err; + } + + err = rfalFieldOnAndStartGT(); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d", err); + return err; + } + + err = rfalPicoPassPollerCheckPresence(); + if(err != ERR_RF_COLLISION) { + FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d", err); + return err; + } + + return ERR_NONE; +} + +ReturnCode picopass_read_card(ApplicationArea* AA1) { + rfalPicoPassIdentifyRes idRes; + rfalPicoPassSelectRes selRes; + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; + + ReturnCode err; + + uint8_t div_key[8] = {0}; + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; + + err = rfalPicoPassPollerIdentify(&idRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); + return err; + } + + err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); + return err; + } + + err = rfalPicoPassPollerReadCheck(&rcRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); + return err; + } + memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 + + diversifyKey(selRes.CSN, picopass_iclass_key, div_key); + opt_doReaderMAC(ccnr, div_key, mac); + + err = rfalPicoPassPollerCheck(mac, &chkRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); + return err; + } + + for(size_t i = 0; i < 4; i++) { + FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i + 6); + rfalPicoPassReadBlockRes block; + err = rfalPicoPassPollerReadBlock(i + 6, &block); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err); + return err; + } + + FURI_LOG_D( + TAG, + "rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x", + i + 6, + block.data[0], + block.data[1], + block.data[2], + block.data[3], + block.data[4], + block.data[5], + block.data[6], + block.data[7]); + + memcpy(&(AA1->block[i]), &block, sizeof(block)); + } + + return ERR_NONE; +} + +int32_t picopass_worker_task(void* context) { + PicopassWorker* picopass_worker = context; + + picopass_worker_enable_field(); + if(picopass_worker->state == PicopassWorkerStateDetect) { + picopass_worker_detect(picopass_worker); + } + picopass_worker_disable_field(ERR_NONE); + + picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); + + return 0; +} + +void picopass_worker_detect(PicopassWorker* picopass_worker) { + picopass_device_data_clear(picopass_worker->dev_data); + PicopassDeviceData* dev_data = picopass_worker->dev_data; + + ApplicationArea* AA1 = &dev_data->AA1; + PicopassPacs* pacs = &dev_data->pacs; + ReturnCode err; + + while(picopass_worker->state == PicopassWorkerStateDetect) { + FURI_LOG_D(TAG, "PicopassWorkerStateDetect"); + if(picopass_detect_card(1000) == ERR_NONE) { + // Process first found device + FURI_LOG_D(TAG, "picopass_read_card"); + err = picopass_read_card(AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_read_card error %d", err); + } + + pacs->biometrics = AA1->block[0].data[4]; + pacs->encryption = AA1->block[0].data[7]; + + if(pacs->encryption == 0x17) { + FURI_LOG_D(TAG, "3DES Encrypted"); + err = picopass_worker_decrypt(AA1->block[1].data, pacs->credential); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + break; + } + FURI_LOG_D(TAG, "Decrypted 7"); + + err = picopass_worker_decrypt(AA1->block[2].data, pacs->pin0); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + break; + } + FURI_LOG_D(TAG, "Decrypted 8"); + + err = picopass_worker_decrypt(AA1->block[3].data, pacs->pin1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + break; + } + FURI_LOG_D(TAG, "Decrypted 9"); + } else if(pacs->encryption == 0x14) { + FURI_LOG_D(TAG, "No Encryption"); + memcpy(pacs->credential, AA1->block[1].data, RFAL_PICOPASS_MAX_BLOCK_LEN); + memcpy(pacs->pin0, AA1->block[2].data, RFAL_PICOPASS_MAX_BLOCK_LEN); + memcpy(pacs->pin1, AA1->block[3].data, RFAL_PICOPASS_MAX_BLOCK_LEN); + } else if(pacs->encryption == 0x15) { + FURI_LOG_D(TAG, "DES Encrypted"); + } else { + FURI_LOG_D(TAG, "Unknown encryption"); + break; + } + + picopass_worker_parse_wiegand(pacs->credential, &pacs->record); + + // Notify caller and exit + if(picopass_worker->callback) { + picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); + } + break; + } + osDelay(100); + } +} diff --git a/applications/picopass/picopass_worker.h b/applications/picopass/picopass_worker.h new file mode 100755 index 00000000..9035f1c8 --- /dev/null +++ b/applications/picopass/picopass_worker.h @@ -0,0 +1,45 @@ +#pragma once + +#include "picopass_device.h" + +typedef struct PicopassWorker PicopassWorker; + +typedef enum { + // Init states + PicopassWorkerStateNone, + PicopassWorkerStateBroken, + PicopassWorkerStateReady, + // Main worker states + PicopassWorkerStateDetect, + // Transition + PicopassWorkerStateStop, +} PicopassWorkerState; + +typedef enum { + // Reserve first 50 events for application events + PicopassWorkerEventReserved = 50, + + // Picopass worker common events + PicopassWorkerEventSuccess, + PicopassWorkerEventFail, + PicopassWorkerEventNoCardDetected, + + PicopassWorkerEventStartReading, +} PicopassWorkerEvent; + +typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context); + +PicopassWorker* picopass_worker_alloc(); + +PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker); + +void picopass_worker_free(PicopassWorker* picopass_worker); + +void picopass_worker_start( + PicopassWorker* picopass_worker, + PicopassWorkerState state, + PicopassDeviceData* dev_data, + PicopassWorkerCallback callback, + void* context); + +void picopass_worker_stop(PicopassWorker* picopass_worker); diff --git a/applications/picopass/picopass_worker_i.h b/applications/picopass/picopass_worker_i.h new file mode 100644 index 00000000..2610d5e7 --- /dev/null +++ b/applications/picopass/picopass_worker_i.h @@ -0,0 +1,24 @@ +#pragma once + +#include "picopass_worker.h" +#include "picopass_i.h" + +#include +#include + +struct PicopassWorker { + FuriThread* thread; + Storage* storage; + + PicopassDeviceData* dev_data; + PicopassWorkerCallback callback; + void* context; + + PicopassWorkerState state; +}; + +void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state); + +int32_t picopass_worker_task(void* context); + +void picopass_worker_detect(PicopassWorker* picopass_worker); diff --git a/applications/picopass/scenes/picopass_scene.c b/applications/picopass/scenes/picopass_scene.c new file mode 100755 index 00000000..61bd5e8f --- /dev/null +++ b/applications/picopass/scenes/picopass_scene.c @@ -0,0 +1,30 @@ +#include "picopass_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const picopass_on_enter_handlers[])(void*) = { +#include "picopass_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const picopass_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "picopass_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const picopass_on_exit_handlers[])(void* context) = { +#include "picopass_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers picopass_scene_handlers = { + .on_enter_handlers = picopass_on_enter_handlers, + .on_event_handlers = picopass_on_event_handlers, + .on_exit_handlers = picopass_on_exit_handlers, + .scene_num = PicopassSceneNum, +}; diff --git a/applications/picopass/scenes/picopass_scene.h b/applications/picopass/scenes/picopass_scene.h new file mode 100644 index 00000000..2faa80b1 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) PicopassScene##id, +typedef enum { +#include "picopass_scene_config.h" + PicopassSceneNum, +} PicopassScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers picopass_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "picopass_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "picopass_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "picopass_scene_config.h" +#undef ADD_SCENE diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/picopass/scenes/picopass_scene_config.h new file mode 100755 index 00000000..7a87737e --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(picopass, start, Start) +ADD_SCENE(picopass, read_card, ReadCard) +ADD_SCENE(picopass, read_card_success, ReadCardSuccess) diff --git a/applications/picopass/scenes/picopass_scene_read_card.c b/applications/picopass/scenes/picopass_scene_read_card.c new file mode 100644 index 00000000..add05e47 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_read_card.c @@ -0,0 +1,55 @@ +#include "../picopass_i.h" +#include + +void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { + UNUSED(event); + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); +} + +void picopass_scene_read_card_on_enter(void* context) { + Picopass* picopass = context; + DOLPHIN_DEED(DolphinDeedNfcRead); + + // Setup view + Popup* popup = picopass->popup; + popup_set_header(popup, "Detecting\npicopass card", 70, 34, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61); + + // Start worker + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); + picopass_worker_start( + picopass->worker, + PicopassWorkerStateDetect, + &picopass->dev->dev_data, + picopass_read_card_worker_callback, + picopass); + + picopass_blink_start(picopass); +} + +bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventWorkerExit) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + consumed = true; + } + return consumed; +} + +void picopass_scene_read_card_on_exit(void* context) { + Picopass* picopass = context; + + // Stop worker + picopass_worker_stop(picopass->worker); + // Clear view + popup_reset(picopass->popup); + + picopass_blink_stop(picopass); +} diff --git a/applications/picopass/scenes/picopass_scene_read_card_success.c b/applications/picopass/scenes/picopass_scene_read_card_success.c new file mode 100644 index 00000000..8e65ce00 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_read_card_success.c @@ -0,0 +1,78 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_read_card_success_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + furi_assert(context); + Picopass* picopass = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(picopass->view_dispatcher, result); + } +} + +void picopass_scene_read_card_success_on_enter(void* context) { + Picopass* picopass = context; + string_t credential_str; + string_t wiegand_str; + string_init(credential_str); + string_init(wiegand_str); + + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + + // Send notification + notification_message(picopass->notifications, &sequence_success); + + // Setup view + PicopassPacs* pacs = &picopass->dev->dev_data.pacs; + Widget* widget = picopass->widget; + + string_set_str(credential_str, ""); + for(uint8_t i = 0; i < RFAL_PICOPASS_MAX_BLOCK_LEN; i++) { + string_cat_printf(credential_str, " %02X", pacs->credential[i]); + } + + if(pacs->record.valid) { + string_cat_printf( + wiegand_str, "FC: %03u CN: %05u", pacs->record.FacilityCode, pacs->record.CardNumber); + } + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + picopass_scene_read_card_success_widget_callback, + picopass); + if(pacs->record.valid) { + widget_add_string_element( + widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); + } + widget_add_string_element( + widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str)); + + string_clear(credential_str); + string_clear(wiegand_str); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); +} + +bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + } + return consumed; +} + +void picopass_scene_read_card_success_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + widget_reset(picopass->widget); +} diff --git a/applications/picopass/scenes/picopass_scene_start.c b/applications/picopass/scenes/picopass_scene_start.c new file mode 100644 index 00000000..7f42fb13 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_start.c @@ -0,0 +1,45 @@ +#include "../picopass_i.h" +enum SubmenuIndex { + SubmenuIndexRead, + SubmenuIndexRunScript, + SubmenuIndexSaved, + SubmenuIndexAddManualy, + SubmenuIndexDebug, +}; + +void picopass_scene_start_submenu_callback(void* context, uint32_t index) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, index); +} +void picopass_scene_start_on_enter(void* context) { + Picopass* picopass = context; + + Submenu* submenu = picopass->submenu; + submenu_add_item( + submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart)); + picopass_device_clear(picopass->dev); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); +} + +bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexRead) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard); + consumed = true; + } + scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event); + } + + return consumed; +} + +void picopass_scene_start_on_exit(void* context) { + Picopass* picopass = context; + submenu_reset(picopass->submenu); +} diff --git a/lib/loclass/optimized_ikeys.c b/lib/loclass/optimized_ikeys.c index ec414f1a..8e093feb 100644 --- a/lib/loclass/optimized_ikeys.c +++ b/lib/loclass/optimized_ikeys.c @@ -304,7 +304,7 @@ void hash0(uint64_t c, uint8_t k[8]) { * @param key * @param div_key */ -void diversifyKey(uint8_t *csn, uint8_t *key, uint8_t *div_key) { +void diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key) { // Prepare the DES key mbedtls_des_setkey_enc(&ctx_enc, key); diff --git a/lib/loclass/optimized_ikeys.h b/lib/loclass/optimized_ikeys.h index fd990cac..e366bb6e 100644 --- a/lib/loclass/optimized_ikeys.h +++ b/lib/loclass/optimized_ikeys.h @@ -56,7 +56,7 @@ void hash0(uint64_t c, uint8_t k[8]); * @param div_key */ -void diversifyKey(uint8_t *csn, uint8_t *key, uint8_t *div_key); +void diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key); /** * @brief Permutes a key from standard NIST format to Iclass specific format * @param key From e147b2ceea7e69d637b45c26533662337289a0d1 Mon Sep 17 00:00:00 2001 From: Equip <72751518+equipter@users.noreply.github.com> Date: Tue, 5 Jul 2022 16:34:21 +0100 Subject: [PATCH 19/26] Added Javacard Emulated mifare classic 1K compatibility (#1369) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Mifare classic 1k JC handling Add mifare classic Javacard emulation handling * Adding MIFARE 1K Javacard Emulation Compatibility MIFARE Classic 1K Cards from NXP have the SAK value of 0x08. MIFARE Classic 1K Cards that are emulated via javacard applet have an SAK value of 0x09. Adding the SAK values accordingly so that Javacard emulated mifare classic tags are properly handled. * update mifare_common.c added javacard emulation handling for mifare classic 1k Co-authored-by: gornekich Co-authored-by: あく --- lib/nfc_protocols/mifare_classic.c | 4 ++-- lib/nfc_protocols/mifare_common.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc_protocols/mifare_classic.c index b6a1c23a..28334119 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc_protocols/mifare_classic.c @@ -198,7 +198,7 @@ static bool mf_classic_is_allowed_access( 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)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { return true; } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { return true; @@ -219,7 +219,7 @@ bool mf_classic_get_type( furi_assert(reader); memset(reader, 0, sizeof(MfClassicReader)); - if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88)) { + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { reader->type = MfClassicType1k; } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { reader->type = MfClassicType4k; diff --git a/lib/nfc_protocols/mifare_common.c b/lib/nfc_protocols/mifare_common.c index 78094cdd..fd622765 100644 --- a/lib/nfc_protocols/mifare_common.c +++ b/lib/nfc_protocols/mifare_common.c @@ -6,7 +6,7 @@ MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { type = MifareTypeUltralight; } else if( - ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88)) || + ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) || ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18))) { type = MifareTypeClassic; } else if(ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20) { From ece142a6677550c9fbc805a72b4f09b052251d45 Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 5 Jul 2022 18:41:19 +0300 Subject: [PATCH 20/26] Fix buffer overflow in mifare classic lib #1374 --- lib/nfc_protocols/mifare_classic.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc_protocols/mifare_classic.c index 28334119..21d470bc 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc_protocols/mifare_classic.c @@ -386,11 +386,25 @@ bool mf_classic_read_block( tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; if(furi_hal_nfc_tx_rx(tx_rx, 50)) { - if(tx_rx->rx_bits == 8 * 18) { - for(uint8_t i = 0; i < 18; i++) { - block->value[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i]; + if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) { + uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2]; + for(uint8_t i = 0; i < MF_CLASSIC_BLOCK_SIZE + 2; i++) { + block_received[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i]; + } + 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; } - read_block_success = true; } } return read_block_success; From e17dae2d001216e472fb1885805e534aa7f81303 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 6 Jul 2022 16:54:08 +0300 Subject: [PATCH 21/26] [FL-2612, FL-2618, FL-2619, FL-2622] CLI, threads, notifications, archive fixes (#1354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CLI, notifications, archive fixes * Led blink fix * Fix thread flags notification index * Archive: fix infinite tab switch on empty SD card Co-authored-by: あく --- .../archive/helpers/archive_browser.c | 33 +++++++++++++++---- applications/cli/cli.c | 12 ++++--- applications/cli/cli_vcp.c | 1 + .../desktop/views/desktop_view_locked.c | 12 ++++--- .../ibutton/scenes/ibutton_scene_emulate.c | 16 ++++++++- applications/notification/notification_app.c | 14 +++++--- core/furi/thread.c | 22 ++++++++----- 7 files changed, 82 insertions(+), 28 deletions(-) diff --git a/applications/archive/helpers/archive_browser.c b/applications/archive/helpers/archive_browser.c index b2f39c07..3e4d6731 100644 --- a/applications/archive/helpers/archive_browser.c +++ b/applications/archive/helpers/archive_browser.c @@ -15,8 +15,9 @@ static void int32_t load_offset = 0; browser->is_root = is_root; + ArchiveTabEnum tab = archive_get_tab(browser); - if((item_cnt == 0) && (archive_is_home(browser))) { + if((item_cnt == 0) && (archive_is_home(browser)) && (tab != ArchiveTabBrowser)) { archive_switch_tab(browser, browser->last_tab_switch_dir); } else if(!string_start_with_str_p(browser->path, "/app:")) { with_view_model( @@ -389,6 +390,22 @@ void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) { }); } +static bool archive_is_dir_exists(string_t path) { + if(string_equal_str_p(path, "/any")) { + return true; + } + bool state = false; + FileInfo file_info; + Storage* storage = furi_record_open("storage"); + if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { + if(file_info.flags & FSF_DIRECTORY) { + state = true; + } + } + furi_record_close("storage"); + return state; +} + void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { furi_assert(browser); ArchiveTabEnum tab = archive_get_tab(browser); @@ -418,11 +435,15 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { } } } else { - ArchiveTabEnum tab = archive_get_tab(browser); - bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; - file_browser_worker_set_config( - browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets); - tab_empty = false; // Empty check will be performed later + tab = archive_get_tab(browser); + if(archive_is_dir_exists(browser->path)) { + bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; + file_browser_worker_set_config( + browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets); + tab_empty = false; // Empty check will be performed later + } else { + tab_empty = true; + } } if((tab_empty) && (tab != ArchiveTabBrowser)) { diff --git a/applications/cli/cli.c b/applications/cli/cli.c index aa48e93b..35baad7e 100644 --- a/applications/cli/cli.c +++ b/applications/cli/cli.c @@ -229,18 +229,22 @@ static void cli_handle_enter(Cli* cli) { // Search for command furi_check(osMutexAcquire(cli->mutex, osWaitForever) == osOK); - CliCommand* cli_command = CliCommandTree_get(cli->commands, command); - if(cli_command) { + CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command); + + if(cli_command_ptr) { + CliCommand cli_command; + memcpy(&cli_command, cli_command_ptr, sizeof(CliCommand)); + furi_check(osMutexRelease(cli->mutex) == osOK); cli_nl(cli); - cli_execute_command(cli, cli_command, args); + cli_execute_command(cli, &cli_command, args); } else { + furi_check(osMutexRelease(cli->mutex) == osOK); cli_nl(cli); printf( "`%s` command not found, use `help` or `?` to list all available commands", string_get_cstr(command)); cli_putc(cli, CliSymbolAsciiBell); } - furi_check(osMutexRelease(cli->mutex) == osOK); cli_reset(cli); cli_prompt(cli); diff --git a/applications/cli/cli_vcp.c b/applications/cli/cli_vcp.c index 7e23e4b1..332dd7cb 100644 --- a/applications/cli/cli_vcp.c +++ b/applications/cli/cli_vcp.c @@ -199,6 +199,7 @@ static int32_t vcp_worker(void* context) { furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); // Restore previous USB mode (if it was set during init) if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { + furi_hal_usb_unlock(); furi_hal_usb_set_config(vcp->usb_if_prev, NULL); } xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); diff --git a/applications/desktop/views/desktop_view_locked.c b/applications/desktop/views/desktop_view_locked.c index 4b544988..1245c152 100644 --- a/applications/desktop/views/desktop_view_locked.c +++ b/applications/desktop/views/desktop_view_locked.c @@ -158,7 +158,7 @@ static bool desktop_view_locked_input(InputEvent* event, void* context) { const bool pin_locked = model->pin_locked; view_commit_model(locked_view->view, is_changed); - if(view_state == DesktopViewLockedStateUnlocked || event->type != InputTypeShort) { + if(view_state == DesktopViewLockedStateUnlocked) { return view_state != DesktopViewLockedStateUnlocked; } else if(view_state == DesktopViewLockedStateLocked && pin_locked) { locked_view->callback(DesktopLockedEventShowPinInput, locked_view->context); @@ -173,10 +173,12 @@ static bool desktop_view_locked_input(InputEvent* event, void* context) { desktop_view_locked_update_hint_icon_timeout(locked_view); if(event->key == InputKeyBack) { - locked_view->lock_lastpress = press_time; - locked_view->lock_count++; - if(locked_view->lock_count == UNLOCK_CNT) { - locked_view->callback(DesktopLockedEventUnlocked, locked_view->context); + if(event->type == InputTypeShort) { + locked_view->lock_lastpress = press_time; + locked_view->lock_count++; + if(locked_view->lock_count == UNLOCK_CNT) { + locked_view->callback(DesktopLockedEventUnlocked, locked_view->context); + } } } else { locked_view->lock_count = 0; diff --git a/applications/ibutton/scenes/ibutton_scene_emulate.c b/applications/ibutton/scenes/ibutton_scene_emulate.c index 9551ea90..0483b77a 100644 --- a/applications/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/ibutton/scenes/ibutton_scene_emulate.c @@ -3,6 +3,8 @@ #include #include +#define EMULATE_TIMEOUT_TICKS 10 + static void ibutton_scene_emulate_callback(void* context, bool emulated) { iButton* ibutton = context; if(emulated) { @@ -95,11 +97,23 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeTick) { + uint32_t cnt = scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneEmulate); + if(cnt > 0) { + cnt--; + if(cnt == 0) { + ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink); + } + scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneEmulate, cnt); + } consumed = true; } else if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == iButtonCustomEventWorkerEmulated) { - ibutton_notification_message(ibutton, iButtonNotificationMessageYellowBlink); + if(scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneEmulate) == 0) { + ibutton_notification_message(ibutton, iButtonNotificationMessageYellowBlink); + } + scene_manager_set_scene_state( + ibutton->scene_manager, iButtonSceneEmulate, EMULATE_TIMEOUT_TICKS); } } diff --git a/applications/notification/notification_app.c b/applications/notification/notification_app.c index 687672c9..9f362358 100644 --- a/applications/notification/notification_app.c +++ b/applications/notification/notification_app.c @@ -93,6 +93,9 @@ void notification_reset_notification_led_layer(NotificationLedLayer* layer) { } void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_mask) { + if(reset_mask & reset_blink_mask) { + furi_hal_light_blink_stop(); + } if(reset_mask & reset_red_mask) { notification_reset_notification_led_layer(&app->led[0]); } @@ -102,9 +105,6 @@ void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_m if(reset_mask & reset_blue_mask) { notification_reset_notification_led_layer(&app->led[2]); } - if(reset_mask & reset_blink_mask) { - furi_hal_light_blink_stop(); - } if(reset_mask & reset_vibro_mask) { notification_vibro_off(); } @@ -243,6 +243,9 @@ void notification_process_notification_message( notification_message->data.led_blink.on_time, notification_message->data.led_blink.period); reset_mask |= reset_blink_mask; + reset_mask |= reset_red_mask; + reset_mask |= reset_green_mask; + reset_mask |= reset_blue_mask; break; case NotificationMessageTypeLedBlinkColor: led_active = true; @@ -251,6 +254,9 @@ void notification_process_notification_message( case NotificationMessageTypeLedBlinkStop: furi_hal_light_blink_stop(); reset_mask &= ~reset_blink_mask; + reset_mask |= reset_red_mask; + reset_mask |= reset_green_mask; + reset_mask |= reset_blue_mask; break; case NotificationMessageTypeVibro: if(notification_message->data.vibro.on) { @@ -326,7 +332,7 @@ void notification_process_notification_message( reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; - if(need_minimal_delay) { + if((need_minimal_delay) && (reset_notifications)) { notification_apply_notification_leds(app, led_off_values); furi_hal_delay_ms(minimal_delay); } diff --git a/core/furi/thread.c b/core/furi/thread.c index 692e5e05..266a3855 100644 --- a/core/furi/thread.c +++ b/core/furi/thread.c @@ -7,6 +7,8 @@ #include #include +#define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers + struct FuriThread { FuriThreadState state; int32_t ret; @@ -221,13 +223,14 @@ uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags) { if(FURI_IS_IRQ_MODE()) { yield = pdFALSE; - (void)xTaskNotifyFromISR(hTask, flags, eSetBits, &yield); - (void)xTaskNotifyAndQueryFromISR(hTask, 0, eNoAction, &rflags, NULL); + (void)xTaskNotifyIndexedFromISR(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits, &yield); + (void)xTaskNotifyAndQueryIndexedFromISR( + hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags, NULL); portYIELD_FROM_ISR(yield); } else { - (void)xTaskNotify(hTask, flags, eSetBits); - (void)xTaskNotifyAndQuery(hTask, 0, eNoAction, &rflags); + (void)xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits); + (void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags); } } /* Return flags after setting */ @@ -245,11 +248,13 @@ uint32_t furi_thread_flags_clear(uint32_t flags) { } else { hTask = xTaskGetCurrentTaskHandle(); - if(xTaskNotifyAndQuery(hTask, 0, eNoAction, &cflags) == pdPASS) { + if(xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &cflags) == + pdPASS) { rflags = cflags; cflags &= ~flags; - if(xTaskNotify(hTask, cflags, eSetValueWithOverwrite) != pdPASS) { + if(xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, cflags, eSetValueWithOverwrite) != + pdPASS) { rflags = (uint32_t)osError; } } else { @@ -270,7 +275,8 @@ uint32_t furi_thread_flags_get(void) { } else { hTask = xTaskGetCurrentTaskHandle(); - if(xTaskNotifyAndQuery(hTask, 0, eNoAction, &rflags) != pdPASS) { + if(xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags) != + pdPASS) { rflags = (uint32_t)osError; } } @@ -300,7 +306,7 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo t0 = xTaskGetTickCount(); do { - rval = xTaskNotifyWait(0, clear, &nval, tout); + rval = xTaskNotifyWaitIndexed(THREAD_NOTIFY_INDEX, 0, clear, &nval, tout); if(rval == pdPASS) { rflags &= flags; From c72b678510facf34ad1fd48293709c1fa8bda1dd Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:28:31 +0300 Subject: [PATCH 22/26] SubGh: fix a race condition (#1376) --- lib/subghz/subghz_tx_rx_worker.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/subghz/subghz_tx_rx_worker.c b/lib/subghz/subghz_tx_rx_worker.c index 863aaa31..0f687f15 100644 --- a/lib/subghz/subghz_tx_rx_worker.c +++ b/lib/subghz/subghz_tx_rx_worker.c @@ -237,13 +237,13 @@ bool subghz_tx_rx_worker_start(SubGhzTxRxWorker* instance, uint32_t frequency) { instance->worker_running = true; - furi_thread_start(instance->thread); - if(furi_hal_subghz_is_tx_allowed(frequency)) { instance->frequency = frequency; res = true; } + furi_thread_start(instance->thread); + return res; } From 6b3625f46b7ac8dff1ea410df25567ca8356b883 Mon Sep 17 00:00:00 2001 From: Michael Marcucci Date: Fri, 8 Jul 2022 08:36:34 -0400 Subject: [PATCH 23/26] Bluetooth Remote Additions (#1330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update the HID Keycodes to pull from the library * Composite BLE Report Map, add consumer & mouse HID * Add Mouse & keyboard bt remote, fixed media remote * BT Keyboard remove long press shift * Fix usb hid modifier keys * Fixed misaligned bad usb keys * Fix keyboard app keys * Partial fix for bt app and linux * Update to work across platforms * Fix for report ids * BtHidApp: move variable from bss to model, cleanup naming. * FuriHal: add const to immutable data declaration Co-authored-by: あく --- applications/bad_usb/bad_usb_script.c | 112 ++--- applications/bt/bt_hid_app/bt_hid.c | 36 +- applications/bt/bt_hid_app/bt_hid.h | 6 + .../bt/bt_hid_app/views/bt_hid_keyboard.c | 384 ++++++++++++++++++ .../bt/bt_hid_app/views/bt_hid_keyboard.h | 13 + .../bt/bt_hid_app/views/bt_hid_keynote.c | 155 +++---- .../bt/bt_hid_app/views/bt_hid_media.c | 36 +- .../bt/bt_hid_app/views/bt_hid_mouse.c | 207 ++++++++++ .../bt/bt_hid_app/views/bt_hid_mouse.h | 13 + .../icons/BLE/BLE_HID/Ble_connected_15x15.png | Bin 0 -> 177 bytes .../BLE/BLE_HID/Ble_disconnected_15x15.png | Bin 0 -> 178 bytes firmware/targets/f7/ble_glue/hid_service.c | 214 ++++++---- firmware/targets/f7/ble_glue/hid_service.h | 15 +- firmware/targets/f7/furi_hal/furi_hal_bt.c | 4 +- .../targets/f7/furi_hal/furi_hal_bt_hid.c | 343 +++++++++------- .../targets/f7/furi_hal/furi_hal_usb_hid.c | 5 - .../furi_hal_include/furi_hal_bt_hid.h | 60 ++- .../furi_hal_include/furi_hal_usb_hid.h | 371 +++++++---------- 18 files changed, 1341 insertions(+), 633 deletions(-) create mode 100644 applications/bt/bt_hid_app/views/bt_hid_keyboard.c create mode 100644 applications/bt/bt_hid_app/views/bt_hid_keyboard.h create mode 100644 applications/bt/bt_hid_app/views/bt_hid_mouse.c create mode 100644 applications/bt/bt_hid_app/views/bt_hid_mouse.h create mode 100644 assets/icons/BLE/BLE_HID/Ble_connected_15x15.png create mode 100644 assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png diff --git a/applications/bad_usb/bad_usb_script.c b/applications/bad_usb/bad_usb_script.c index 0266b51f..e6c0f893 100644 --- a/applications/bad_usb/bad_usb_script.c +++ b/applications/bad_usb/bad_usb_script.c @@ -57,48 +57,48 @@ static const DuckyKey ducky_keys[] = { {"GUI", KEY_MOD_LEFT_GUI}, {"WINDOWS", KEY_MOD_LEFT_GUI}, - {"DOWNARROW", KEY_DOWN_ARROW}, - {"DOWN", KEY_DOWN_ARROW}, - {"LEFTARROW", KEY_LEFT_ARROW}, - {"LEFT", KEY_LEFT_ARROW}, - {"RIGHTARROW", KEY_RIGHT_ARROW}, - {"RIGHT", KEY_RIGHT_ARROW}, - {"UPARROW", KEY_UP_ARROW}, - {"UP", KEY_UP_ARROW}, + {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UPARROW", HID_KEYBOARD_UP_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, - {"ENTER", KEY_ENTER}, - {"BREAK", KEY_PAUSE}, - {"PAUSE", KEY_PAUSE}, - {"CAPSLOCK", KEY_CAPS_LOCK}, - {"DELETE", KEY_DELETE}, - {"BACKSPACE", KEY_BACKSPACE}, - {"END", KEY_END}, - {"ESC", KEY_ESC}, - {"ESCAPE", KEY_ESC}, - {"HOME", KEY_HOME}, - {"INSERT", KEY_INSERT}, - {"NUMLOCK", KEY_NUM_LOCK}, - {"PAGEUP", KEY_PAGE_UP}, - {"PAGEDOWN", KEY_PAGE_DOWN}, - {"PRINTSCREEN", KEY_PRINT}, - {"SCROLLOCK", KEY_SCROLL_LOCK}, - {"SPACE", KEY_SPACE}, - {"TAB", KEY_TAB}, - {"MENU", KEY_APPLICATION}, - {"APP", KEY_APPLICATION}, + {"ENTER", HID_KEYBOARD_RETURN}, + {"BREAK", HID_KEYBOARD_PAUSE}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE}, + {"BACKSPACE", HID_KEYPAD_BACKSPACE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"ESCAPE", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + {"APP", HID_KEYBOARD_APPLICATION}, - {"F1", KEY_F1}, - {"F2", KEY_F2}, - {"F3", KEY_F3}, - {"F4", KEY_F4}, - {"F5", KEY_F5}, - {"F6", KEY_F6}, - {"F7", KEY_F7}, - {"F8", KEY_F8}, - {"F9", KEY_F9}, - {"F10", KEY_F10}, - {"F11", KEY_F11}, - {"F12", KEY_F12}, + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, }; static const char ducky_cmd_comment[] = {"REM"}; @@ -114,16 +114,16 @@ static const char ducky_cmd_altstr_1[] = {"ALTSTRING "}; static const char ducky_cmd_altstr_2[] = {"ALTCODE "}; static const uint8_t numpad_keys[10] = { - KEYPAD_0, - KEYPAD_1, - KEYPAD_2, - KEYPAD_3, - KEYPAD_4, - KEYPAD_5, - KEYPAD_6, - KEYPAD_7, - KEYPAD_8, - KEYPAD_9, + HID_KEYPAD_0, + HID_KEYPAD_1, + HID_KEYPAD_2, + HID_KEYPAD_3, + HID_KEYPAD_4, + HID_KEYPAD_5, + HID_KEYPAD_6, + HID_KEYPAD_7, + HID_KEYPAD_8, + HID_KEYPAD_9, }; static bool ducky_get_number(const char* param, uint32_t* val) { @@ -149,8 +149,8 @@ static bool ducky_is_line_end(const char chr) { static void ducky_numlock_on() { if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) { - furi_hal_hid_kb_press(KEY_NUM_LOCK); - furi_hal_hid_kb_release(KEY_NUM_LOCK); + furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK); + furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK); } } @@ -170,7 +170,7 @@ static bool ducky_altchar(const char* charcode) { FURI_LOG_I(WORKER_TAG, "char %s", charcode); - furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT); + furi_hal_hid_kb_press(HID_KEYBOARD_L_ALT); while(!ducky_is_line_end(charcode[i])) { state = ducky_numpad_press(charcode[i]); @@ -178,7 +178,7 @@ static bool ducky_altchar(const char* charcode) { i++; } - furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT); + furi_hal_hid_kb_release(HID_KEYBOARD_L_ALT); return state; } @@ -206,7 +206,7 @@ static bool ducky_string(const char* param) { uint32_t i = 0; while(param[i] != '\0') { uint16_t keycode = HID_ASCII_TO_KEY(param[i]); - if(keycode != KEY_NONE) { + if(keycode != HID_KEYBOARD_NONE) { furi_hal_hid_kb_press(keycode); furi_hal_hid_kb_release(keycode); } @@ -294,7 +294,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) { } else { // Special keys + modifiers uint16_t key = ducky_get_keycode(line_tmp, false); - if(key == KEY_NONE) return SCRIPT_STATE_ERROR; + if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR; if((key & 0xFF00) != 0) { // It's a modifier key line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; diff --git a/applications/bt/bt_hid_app/bt_hid.c b/applications/bt/bt_hid_app/bt_hid.c index 4b1037cd..47ee2268 100755 --- a/applications/bt/bt_hid_app/bt_hid.c +++ b/applications/bt/bt_hid_app/bt_hid.c @@ -6,7 +6,9 @@ enum BtDebugSubmenuIndex { BtHidSubmenuIndexKeynote, + BtHidSubmenuIndexKeyboard, BtHidSubmenuIndexMedia, + BtHidSubmenuIndexMouse, }; void bt_hid_submenu_callback(void* context, uint32_t index) { @@ -15,9 +17,15 @@ void bt_hid_submenu_callback(void* context, uint32_t index) { if(index == BtHidSubmenuIndexKeynote) { app->view_id = BtHidViewKeynote; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote); + } else if(index == BtHidSubmenuIndexKeyboard) { + app->view_id = BtHidViewKeyboard; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeyboard); } else if(index == BtHidSubmenuIndexMedia) { app->view_id = BtHidViewMedia; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMedia); + } else if(index == BtHidSubmenuIndexMouse) { + app->view_id = BtHidViewMouse; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse); } } @@ -25,10 +33,11 @@ void bt_hid_dialog_callback(DialogExResult result, void* context) { furi_assert(context); BtHid* app = context; if(result == DialogExResultLeft) { - // TODO switch to Submenu after Media is done view_dispatcher_stop(app->view_dispatcher); } else if(result == DialogExResultRight) { - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote); + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view + } else if(result == DialogExResultCenter) { + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewSubmenu); } } @@ -52,7 +61,9 @@ void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { notification_internal_message(bt_hid->notifications, &sequence_reset_blue); } bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected); + bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected); bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected); + bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected); } BtHid* bt_hid_app_alloc() { @@ -76,8 +87,11 @@ BtHid* bt_hid_app_alloc() { app->submenu = submenu_alloc(); submenu_add_item( app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app); + submenu_add_item( + app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); submenu_add_item( app->submenu, "Media player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); + submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app); view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit); view_dispatcher_add_view( app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu)); @@ -88,6 +102,7 @@ BtHid* bt_hid_app_alloc() { dialog_ex_set_context(app->dialog, app); dialog_ex_set_left_button_text(app->dialog, "Exit"); dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); dialog_ex_set_header(app->dialog, "Close current app?", 16, 12, AlignLeft, AlignTop); view_dispatcher_add_view( app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog)); @@ -99,12 +114,25 @@ BtHid* bt_hid_app_alloc() { view_dispatcher_add_view( app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote)); + // Keyboard view + app->bt_hid_keyboard = bt_hid_keyboard_alloc(); + view_set_previous_callback( + bt_hid_keyboard_get_view(app->bt_hid_keyboard), bt_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, BtHidViewKeyboard, bt_hid_keyboard_get_view(app->bt_hid_keyboard)); + // Media view app->bt_hid_media = bt_hid_media_alloc(); view_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media)); + // Mouse view + app->bt_hid_mouse = bt_hid_mouse_alloc(); + view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse)); + // TODO switch to menu after Media is done view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote); @@ -124,8 +152,12 @@ void bt_hid_app_free(BtHid* app) { dialog_ex_free(app->dialog); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote); bt_hid_keynote_free(app->bt_hid_keynote); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeyboard); + bt_hid_keyboard_free(app->bt_hid_keyboard); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia); bt_hid_media_free(app->bt_hid_media); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse); + bt_hid_mouse_free(app->bt_hid_mouse); view_dispatcher_free(app->view_dispatcher); // Close records diff --git a/applications/bt/bt_hid_app/bt_hid.h b/applications/bt/bt_hid_app/bt_hid.h index 875cac58..81d092db 100644 --- a/applications/bt/bt_hid_app/bt_hid.h +++ b/applications/bt/bt_hid_app/bt_hid.h @@ -10,7 +10,9 @@ #include #include #include "views/bt_hid_keynote.h" +#include "views/bt_hid_keyboard.h" #include "views/bt_hid_media.h" +#include "views/bt_hid_mouse.h" typedef struct { Bt* bt; @@ -20,13 +22,17 @@ typedef struct { Submenu* submenu; DialogEx* dialog; BtHidKeynote* bt_hid_keynote; + BtHidKeyboard* bt_hid_keyboard; BtHidMedia* bt_hid_media; + BtHidMouse* bt_hid_mouse; uint32_t view_id; } BtHid; typedef enum { BtHidViewSubmenu, BtHidViewKeynote, + BtHidViewKeyboard, BtHidViewMedia, + BtHidViewMouse, BtHidViewExitConfirm, } BtHidView; diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.c b/applications/bt/bt_hid_app/views/bt_hid_keyboard.c new file mode 100644 index 00000000..1088e295 --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_keyboard.c @@ -0,0 +1,384 @@ +#include "bt_hid_keyboard.h" +#include +#include +#include +#include +#include + +struct BtHidKeyboard { + View* view; +}; + +typedef struct { + bool shift; + bool alt; + bool ctrl; + bool gui; + uint8_t x; + uint8_t y; + uint8_t last_key_code; + uint16_t modifier_code; + bool ok_pressed; + bool back_pressed; + bool connected; + char key_string[5]; +} BtHidKeyboardModel; + +typedef struct { + uint8_t width; + char* key; + const Icon* icon; + char* shift_key; + uint8_t value; +} BtHidKeyboardKey; + +typedef struct { + int8_t x; + int8_t y; +} BtHidKeyboardPoint; + +// 4 BY 12 +#define MARGIN_TOP 0 +#define MARGIN_LEFT 4 +#define KEY_WIDTH 9 +#define KEY_HEIGHT 12 +#define KEY_PADDING 1 +#define ROW_COUNT 6 +#define COLUMN_COUNT 12 + +// 0 width items are not drawn, but there value is used +const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { + { + {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, + {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, + {.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3}, + {.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4}, + {.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5}, + {.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6}, + {.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7}, + {.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8}, + {.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9}, + {.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0}, + {.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE}, + {.width = 0, .value = HID_KEYBOARD_DELETE}, + }, + { + {.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q}, + {.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W}, + {.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E}, + {.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R}, + {.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T}, + {.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y}, + {.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U}, + {.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I}, + {.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O}, + {.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P}, + {.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET}, + {.width = 1, + .icon = NULL, + .key = "]", + .shift_key = "}", + .value = HID_KEYBOARD_CLOSE_BRACKET}, + }, + { + {.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A}, + {.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S}, + {.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D}, + {.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F}, + {.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G}, + {.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H}, + {.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J}, + {.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K}, + {.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L}, + {.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON}, + {.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN}, + {.width = 0, .value = HID_KEYBOARD_RETURN}, + }, + { + {.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z}, + {.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X}, + {.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C}, + {.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V}, + {.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B}, + {.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N}, + {.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M}, + {.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH}, + {.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH}, + {.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT}, + {.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW}, + {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS}, + }, + { + {.width = 1, .icon = &I_Pin_arrow_up7x9, .value = HID_KEYBOARD_L_SHIFT}, + {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA}, + {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, + {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, + {.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE}, + {.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN}, + {.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW}, + {.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW}, + {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, + }, + { + {.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL}, + {.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT}, + {.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI}, + {.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB}, + {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB}, + }, +}; + +static void bt_hid_keyboard_to_upper(char* str) { + while(*str) { + *str = toupper((unsigned char)*str); + str++; + } +} + +static void bt_hid_keyboard_draw_key( + Canvas* canvas, + BtHidKeyboardModel* model, + uint8_t x, + uint8_t y, + BtHidKeyboardKey key, + bool selected) { + if(!key.width) return; + + canvas_set_color(canvas, ColorBlack); + uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1); + if(selected) { + // Draw a filled box + elements_slightly_rounded_box( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), + keyWidth, + KEY_HEIGHT); + canvas_set_color(canvas, ColorWhite); + } else { + // Draw a framed box + elements_slightly_rounded_frame( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING), + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING), + keyWidth, + KEY_HEIGHT); + } + if(key.icon != NULL) { + // Draw the icon centered on the button + canvas_draw_icon( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2, + key.icon); + } else { + // If shift is toggled use the shift key when available + strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key); + // Upper case if ctrl or alt was toggled true + if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || + (model->alt && key.value == HID_KEYBOARD_L_ALT) || + (model->gui && key.value == HID_KEYBOARD_L_GUI)) { + bt_hid_keyboard_to_upper(model->key_string); + } + canvas_draw_str_aligned( + canvas, + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2, + AlignCenter, + AlignCenter, + model->key_string); + } +} + +static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + BtHidKeyboardModel* model = context; + + // Header + if(!model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard"); + elements_multiline_text_aligned( + canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection..."); + return; // Dont render the keyboard if we are not yet connected + } + + canvas_set_font(canvas, FontKeyboard); + // Start shifting the all keys up if on the next row (Scrolling) + uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0; + for(uint8_t y = initY; y < ROW_COUNT; y++) { + const BtHidKeyboardKey* keyboardKeyRow = bt_hid_keyboard_keyset[y]; + uint8_t x = 0; + for(uint8_t i = 0; i < COLUMN_COUNT; i++) { + BtHidKeyboardKey key = keyboardKeyRow[i]; + // Select when the button is hovered + // Select if the button is hovered within its width + // Select if back is clicked and its the backspace key + // Deselect when the button clicked or not hovered + bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y; + bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE; + bt_hid_keyboard_draw_key( + canvas, + model, + x, + y - initY, + key, + (!model->ok_pressed && keySelected) || backSelected); + x += key.width; + } + } +} + +static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) { + BtHidKeyboardKey key = bt_hid_keyboard_keyset[model->y][model->x]; + // Use upper case if shift is toggled + bool useUppercase = model->shift; + // Check if the key has an upper case version + bool hasUppercase = key.shift_key != 0; + if(useUppercase && hasUppercase) + return key.value; + else + return key.value; +} + +static void bt_hid_keyboard_get_select_key(BtHidKeyboardModel* model, BtHidKeyboardPoint delta) { + // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map + do { + if(((int8_t)model->y) + delta.y < 0) + model->y = ROW_COUNT - 1; + else + model->y = (model->y + delta.y) % ROW_COUNT; + } while(delta.y != 0 && bt_hid_keyboard_keyset[model->y][model->x].value == 0); + + do { + if(((int8_t)model->x) + delta.x < 0) + model->x = COLUMN_COUNT - 1; + else + model->x = (model->x + delta.x) % COLUMN_COUNT; + } while(delta.x != 0 && bt_hid_keyboard_keyset[model->y][model->x].width == + 0); // Skip zero width keys, pretend they are one key +} + +static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* event) { + with_view_model( + bt_hid_keyboard->view, (BtHidKeyboardModel * model) { + if(event->key == InputKeyOk) { + if(event->type == InputTypePress) { + model->ok_pressed = true; + } else if(event->type == InputTypeLong || event->type == InputTypeShort) { + model->last_key_code = bt_hid_keyboard_get_selected_key(model); + + // Toggle the modifier key when clicked, and click the key + if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { + model->shift = !model->shift; + if(model->shift) + model->modifier_code |= KEY_MOD_LEFT_SHIFT; + else + model->modifier_code &= ~KEY_MOD_LEFT_SHIFT; + } else if(model->last_key_code == HID_KEYBOARD_L_ALT) { + model->alt = !model->alt; + if(model->alt) + model->modifier_code |= KEY_MOD_LEFT_ALT; + else + model->modifier_code &= ~KEY_MOD_LEFT_ALT; + } else if(model->last_key_code == HID_KEYBOARD_L_CTRL) { + model->ctrl = !model->ctrl; + if(model->ctrl) + model->modifier_code |= KEY_MOD_LEFT_CTRL; + else + model->modifier_code &= ~KEY_MOD_LEFT_CTRL; + } else if(model->last_key_code == HID_KEYBOARD_L_GUI) { + model->gui = !model->gui; + if(model->gui) + model->modifier_code |= KEY_MOD_LEFT_GUI; + else + model->modifier_code &= ~KEY_MOD_LEFT_GUI; + } + furi_hal_bt_hid_kb_press(model->modifier_code | model->last_key_code); + } else if(event->type == InputTypeRelease) { + // Release happens after short and long presses + furi_hal_bt_hid_kb_release(model->modifier_code | model->last_key_code); + model->ok_pressed = false; + } + } else if(event->key == InputKeyBack) { + // If back is pressed for a short time, backspace + if(event->type == InputTypePress) { + model->back_pressed = true; + } else if(event->type == InputTypeShort) { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); + } else if(event->type == InputTypeRelease) { + model->back_pressed = false; + } + } else if(event->type == InputTypePress || event->type == InputTypeRepeat) { + // Cycle the selected keys + if(event->key == InputKeyUp) { + bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = -1}); + } else if(event->key == InputKeyDown) { + bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = 1}); + } else if(event->key == InputKeyLeft) { + bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = -1, .y = 0}); + } else if(event->key == InputKeyRight) { + bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0}); + } + } + return true; + }); +} + +static bool bt_hid_keyboard_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BtHidKeyboard* bt_hid_keyboard = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_bt_hid_kb_release_all(); + } else { + bt_hid_keyboard_process(bt_hid_keyboard, event); + consumed = true; + } + + return consumed; +} + +BtHidKeyboard* bt_hid_keyboard_alloc() { + BtHidKeyboard* bt_hid_keyboard = malloc(sizeof(BtHidKeyboard)); + bt_hid_keyboard->view = view_alloc(); + view_set_context(bt_hid_keyboard->view, bt_hid_keyboard); + view_allocate_model(bt_hid_keyboard->view, ViewModelTypeLocking, sizeof(BtHidKeyboardModel)); + view_set_draw_callback(bt_hid_keyboard->view, bt_hid_keyboard_draw_callback); + view_set_input_callback(bt_hid_keyboard->view, bt_hid_keyboard_input_callback); + + return bt_hid_keyboard; +} + +void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard) { + furi_assert(bt_hid_keyboard); + view_free(bt_hid_keyboard->view); + free(bt_hid_keyboard); +} + +View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) { + furi_assert(bt_hid_keyboard); + return bt_hid_keyboard->view; +} + +void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) { + furi_assert(bt_hid_keyboard); + with_view_model( + bt_hid_keyboard->view, (BtHidKeyboardModel * model) { + model->connected = connected; + return true; + }); +} diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.h b/applications/bt/bt_hid_app/views/bt_hid_keyboard.h new file mode 100644 index 00000000..b2cc928e --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_keyboard.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct BtHidKeyboard BtHidKeyboard; + +BtHidKeyboard* bt_hid_keyboard_alloc(); + +void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard); + +View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard); + +void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected); diff --git a/applications/bt/bt_hid_app/views/bt_hid_keynote.c b/applications/bt/bt_hid_app/views/bt_hid_keynote.c index c4225227..60a1ebc0 100755 --- a/applications/bt/bt_hid_app/views/bt_hid_keynote.c +++ b/applications/bt/bt_hid_app/views/bt_hid_keynote.c @@ -14,6 +14,7 @@ typedef struct { bool right_pressed; bool down_pressed; bool ok_pressed; + bool back_pressed; bool connected; } BtHidKeynoteModel; @@ -35,106 +36,119 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { BtHidKeynoteModel* model = context; // Header + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Keynote"); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); canvas_set_font(canvas, FontSecondary); - // Connected status - if(model->connected) { - canvas_draw_icon(canvas, 18, 18, &I_Ble_connected_38x34); - elements_multiline_text_aligned(canvas, 9, 60, AlignLeft, AlignBottom, "Connected"); - } else { - canvas_draw_icon(canvas, 18, 18, &I_Ble_disconnected_24x34); - elements_multiline_text_aligned(canvas, 3, 60, AlignLeft, AlignBottom, "Disconnected"); - } - // Up - canvas_draw_icon(canvas, 86, 4, &I_Button_18x18); + canvas_draw_icon(canvas, 21, 24, &I_Button_18x18); if(model->up_pressed) { - elements_slightly_rounded_box(canvas, 89, 6, 13, 13); + elements_slightly_rounded_box(canvas, 24, 26, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 95, 10, CanvasDirectionBottomToTop); + bt_hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop); canvas_set_color(canvas, ColorBlack); // Down - canvas_draw_icon(canvas, 86, 25, &I_Button_18x18); + canvas_draw_icon(canvas, 21, 45, &I_Button_18x18); if(model->down_pressed) { - elements_slightly_rounded_box(canvas, 89, 27, 13, 13); + elements_slightly_rounded_box(canvas, 24, 47, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 95, 35, CanvasDirectionTopToBottom); + bt_hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom); canvas_set_color(canvas, ColorBlack); // Left - canvas_draw_icon(canvas, 65, 25, &I_Button_18x18); + canvas_draw_icon(canvas, 0, 45, &I_Button_18x18); if(model->left_pressed) { - elements_slightly_rounded_box(canvas, 68, 27, 13, 13); + elements_slightly_rounded_box(canvas, 3, 47, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 72, 33, CanvasDirectionRightToLeft); + bt_hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft); canvas_set_color(canvas, ColorBlack); // Right - canvas_draw_icon(canvas, 107, 25, &I_Button_18x18); + canvas_draw_icon(canvas, 42, 45, &I_Button_18x18); if(model->right_pressed) { - elements_slightly_rounded_box(canvas, 110, 27, 13, 13); + elements_slightly_rounded_box(canvas, 45, 47, 13, 13); canvas_set_color(canvas, ColorWhite); } - bt_hid_keynote_draw_arrow(canvas, 118, 33, CanvasDirectionLeftToRight); + bt_hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight); canvas_set_color(canvas, ColorBlack); // Ok - canvas_draw_icon(canvas, 63, 45, &I_Space_65x18); + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); if(model->ok_pressed) { + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space"); + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 63, 45, &I_Space_65x18); + if(model->back_pressed) { elements_slightly_rounded_box(canvas, 66, 47, 60, 13); canvas_set_color(canvas, ColorWhite); } - canvas_draw_icon(canvas, 74, 49, &I_Ok_btn_9x9); - elements_multiline_text_aligned(canvas, 91, 56, AlignLeft, AlignBottom, "Space"); + canvas_draw_icon(canvas, 110, 49, &I_Ok_btn_9x9); + elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back"); } -static void bt_hid_keynote_process_press(BtHidKeynote* bt_hid_keynote, InputEvent* event) { +static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) { with_view_model( bt_hid_keynote->view, (BtHidKeynoteModel * model) { - if(event->key == InputKeyUp) { - model->up_pressed = true; - furi_hal_bt_hid_kb_press(KEY_UP_ARROW); - } else if(event->key == InputKeyDown) { - model->down_pressed = true; - furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW); - } else if(event->key == InputKeyLeft) { - model->left_pressed = true; - furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW); - } else if(event->key == InputKeyRight) { - model->right_pressed = true; - furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW); - } else if(event->key == InputKeyOk) { - model->ok_pressed = true; - furi_hal_bt_hid_kb_press(KEY_SPACE); - } - return true; - }); -} - -static void bt_hid_keynote_process_release(BtHidKeynote* bt_hid_keynote, InputEvent* event) { - with_view_model( - bt_hid_keynote->view, (BtHidKeynoteModel * model) { - if(event->key == InputKeyUp) { - model->up_pressed = false; - furi_hal_bt_hid_kb_release(KEY_UP_ARROW); - } else if(event->key == InputKeyDown) { - model->down_pressed = false; - furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW); - } else if(event->key == InputKeyLeft) { - model->left_pressed = false; - furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW); - } else if(event->key == InputKeyRight) { - model->right_pressed = false; - furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW); - } else if(event->key == InputKeyOk) { - model->ok_pressed = false; - furi_hal_bt_hid_kb_release(KEY_SPACE); + if(event->type == InputTypePress) { + if(event->key == InputKeyUp) { + model->up_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_UP_ARROW); + } else if(event->key == InputKeyDown) { + model->down_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = true; + furi_hal_bt_hid_kb_press(HID_KEYBOARD_SPACEBAR); + } else if(event->key == InputKeyBack) { + model->back_pressed = true; + } + } else if(event->type == InputTypeRelease) { + if(event->key == InputKeyUp) { + model->up_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_UP_ARROW); + } else if(event->key == InputKeyDown) { + model->down_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_DOWN_ARROW); + } else if(event->key == InputKeyLeft) { + model->left_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_LEFT_ARROW); + } else if(event->key == InputKeyRight) { + model->right_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_RIGHT_ARROW); + } else if(event->key == InputKeyOk) { + model->ok_pressed = false; + furi_hal_bt_hid_kb_release(HID_KEYBOARD_SPACEBAR); + } else if(event->key == InputKeyBack) { + model->back_pressed = false; + } + } else if(event->type == InputTypeShort) { + if(event->key == InputKeyBack) { + furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE); + furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_AC_BACK); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_AC_BACK); + } } return true; }); @@ -145,16 +159,11 @@ static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) { BtHidKeynote* bt_hid_keynote = context; bool consumed = false; - if(event->type == InputTypePress) { - bt_hid_keynote_process_press(bt_hid_keynote, event); + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_bt_hid_kb_release_all(); + } else { + bt_hid_keynote_process(bt_hid_keynote, event); consumed = true; - } else if(event->type == InputTypeRelease) { - bt_hid_keynote_process_release(bt_hid_keynote, event); - consumed = true; - } else if(event->type == InputTypeShort) { - if(event->key == InputKeyBack) { - furi_hal_hid_kb_release_all(); - } } return consumed; diff --git a/applications/bt/bt_hid_app/views/bt_hid_media.c b/applications/bt/bt_hid_app/views/bt_hid_media.c index 695ce3c3..b384f47c 100755 --- a/applications/bt/bt_hid_app/views/bt_hid_media.c +++ b/applications/bt/bt_hid_app/views/bt_hid_media.c @@ -35,18 +35,14 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { BtHidMediaModel* model = context; // Header - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 9, 3, AlignLeft, AlignTop, "Media player"); - canvas_set_font(canvas, FontSecondary); - - // Connected status if(model->connected) { - canvas_draw_icon(canvas, 23, 17, &I_Ble_connected_38x34); - elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Connected"); + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); } else { - canvas_draw_icon(canvas, 23, 17, &I_Ble_disconnected_24x34); - elements_multiline_text_aligned(canvas, 35, 61, AlignCenter, AlignBottom, "Disconnected"); + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media"); + canvas_set_font(canvas, FontSecondary); // Keypad circles canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47); @@ -100,19 +96,19 @@ static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* eve bt_hid_media->view, (BtHidMediaModel * model) { if(event->key == InputKeyUp) { model->up_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeUp); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyDown) { model->down_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaVolumeDown); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyLeft) { model->left_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanPrevious); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_PREVIOUS_TRACK); } else if(event->key == InputKeyRight) { model->right_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaScanNext); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_NEXT_TRACK); } else if(event->key == InputKeyOk) { model->ok_pressed = true; - furi_hal_bt_hid_media_press(FuriHalBtHidMediaPlayPause); + furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE); } return true; }); @@ -123,19 +119,19 @@ static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* e bt_hid_media->view, (BtHidMediaModel * model) { if(event->key == InputKeyUp) { model->up_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeUp); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyDown) { model->down_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaVolumeDown); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyLeft) { model->left_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanPrevious); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_PREVIOUS_TRACK); } else if(event->key == InputKeyRight) { model->right_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaScanNext); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_NEXT_TRACK); } else if(event->key == InputKeyOk) { model->ok_pressed = false; - furi_hal_bt_hid_media_release(FuriHalBtHidMediaPlayPause); + furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE); } return true; }); @@ -154,7 +150,7 @@ static bool bt_hid_media_input_callback(InputEvent* event, void* context) { consumed = true; } else if(event->type == InputTypeShort) { if(event->key == InputKeyBack) { - furi_hal_bt_hid_media_release_all(); + furi_hal_bt_hid_consumer_key_release_all(); } } diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.c b/applications/bt/bt_hid_app/views/bt_hid_mouse.c new file mode 100644 index 00000000..fb1537a2 --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_mouse.c @@ -0,0 +1,207 @@ +#include "bt_hid_mouse.h" +#include +#include +#include +#include + +struct BtHidMouse { + View* view; +}; +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef struct { + bool left_pressed; + bool up_pressed; + bool right_pressed; + bool down_pressed; + bool left_mouse_pressed; + bool left_mouse_held; + bool right_mouse_pressed; + bool connected; +} BtHidMouseModel; + +static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + BtHidMouseModel* model = context; + + // Header + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse"); + canvas_set_font(canvas, FontSecondary); + + if(model->left_mouse_held == true) { + elements_multiline_text_aligned(canvas, 0, 60, AlignLeft, AlignBottom, "Selecting..."); + } + + // Keypad circles + canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47); + + // Up + if(model->up_pressed) { + canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9); + canvas_set_color(canvas, ColorBlack); + + // Down + if(model->down_pressed) { + canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9); + canvas_set_color(canvas, ColorBlack); + + // Left + if(model->left_pressed) { + canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7); + canvas_set_color(canvas, ColorBlack); + + // Right + if(model->right_pressed) { + canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7); + canvas_set_color(canvas, ColorBlack); + + // Ok + if(model->left_mouse_pressed) { + canvas_draw_icon(canvas, 81, 25, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 83, 27, &I_Ok_btn_9x9); + canvas_set_color(canvas, ColorBlack); + + // Back + if(model->right_mouse_pressed) { + canvas_draw_icon(canvas, 108, 48, &I_Pressed_Button_13x13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 110, 50, &I_Ok_btn_9x9); +} + +static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { + with_view_model( + bt_hid_mouse->view, (BtHidMouseModel * model) { + if(event->key == InputKeyBack) { + if(event->type == InputTypeShort) { + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_RIGHT); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_RIGHT); + } else if(event->type == InputTypePress) { + model->right_mouse_pressed = true; + } else if(event->type == InputTypeRelease) { + model->right_mouse_pressed = false; + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypeShort) { + // Just release if it was being held before + if(!model->left_mouse_held) furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT); + model->left_mouse_held = false; + } else if(event->type == InputTypeLong) { + furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT); + model->left_mouse_held = true; + model->left_mouse_pressed = true; + } else if(event->type == InputTypePress) { + model->left_mouse_pressed = true; + } else if(event->type == InputTypeRelease) { + // Only release if it wasn't a long press + if(!model->left_mouse_held) model->left_mouse_pressed = false; + } + + } else if(event->key == InputKeyRight) { + if(event->type == InputTypePress) { + model->right_pressed = true; + furi_hal_bt_hid_mouse_move(MOUSE_MOVE_SHORT, 0); + } else if(event->type == InputTypeRepeat) { + furi_hal_bt_hid_mouse_move(MOUSE_MOVE_LONG, 0); + } else if(event->type == InputTypeRelease) { + model->right_pressed = false; + } + } else if(event->key == InputKeyLeft) { + if(event->type == InputTypePress) { + model->left_pressed = true; + furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_SHORT, 0); + } else if(event->type == InputTypeRepeat) { + furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_LONG, 0); + } else if(event->type == InputTypeRelease) { + model->left_pressed = false; + } + } else if(event->key == InputKeyDown) { + if(event->type == InputTypePress) { + model->down_pressed = true; + furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_SHORT); + } else if(event->type == InputTypeRepeat) { + furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_LONG); + } else if(event->type == InputTypeRelease) { + model->down_pressed = false; + } + } else if(event->key == InputKeyUp) { + if(event->type == InputTypePress) { + model->up_pressed = true; + furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_SHORT); + } else if(event->type == InputTypeRepeat) { + furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_LONG); + } else if(event->type == InputTypeRelease) { + model->up_pressed = false; + } + } + return true; + }); +} + +static bool bt_hid_mouse_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BtHidMouse* bt_hid_mouse = context; + bool consumed = false; + + if(event->type == InputTypeLong && event->key == InputKeyBack) { + furi_hal_bt_hid_mouse_release_all(); + } else { + bt_hid_mouse_process(bt_hid_mouse, event); + consumed = true; + } + + return consumed; +} + +BtHidMouse* bt_hid_mouse_alloc() { + BtHidMouse* bt_hid_mouse = malloc(sizeof(BtHidMouse)); + bt_hid_mouse->view = view_alloc(); + view_set_context(bt_hid_mouse->view, bt_hid_mouse); + view_allocate_model(bt_hid_mouse->view, ViewModelTypeLocking, sizeof(BtHidMouseModel)); + view_set_draw_callback(bt_hid_mouse->view, bt_hid_mouse_draw_callback); + view_set_input_callback(bt_hid_mouse->view, bt_hid_mouse_input_callback); + + return bt_hid_mouse; +} + +void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse) { + furi_assert(bt_hid_mouse); + view_free(bt_hid_mouse->view); + free(bt_hid_mouse); +} + +View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse) { + furi_assert(bt_hid_mouse); + return bt_hid_mouse->view; +} + +void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected) { + furi_assert(bt_hid_mouse); + with_view_model( + bt_hid_mouse->view, (BtHidMouseModel * model) { + model->connected = connected; + return true; + }); +} diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.h b/applications/bt/bt_hid_app/views/bt_hid_mouse.h new file mode 100644 index 00000000..a82971d7 --- /dev/null +++ b/applications/bt/bt_hid_app/views/bt_hid_mouse.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct BtHidMouse BtHidMouse; + +BtHidMouse* bt_hid_mouse_alloc(); + +void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse); + +View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse); + +void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected); diff --git a/assets/icons/BLE/BLE_HID/Ble_connected_15x15.png b/assets/icons/BLE/BLE_HID/Ble_connected_15x15.png new file mode 100644 index 0000000000000000000000000000000000000000..58776828ed48a1c3a497755800660b79ea3b8137 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^{2({Zh|P-#^I!>>dQMXri7U8|T@ zv7L;LDP~#8^={?X^Shd#k?^xXW6s* bch2Oog!)SEiB$UnbOVE@tDnm{r-UW|4(3Jx literal 0 HcmV?d00001 diff --git a/assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png b/assets/icons/BLE/BLE_HID/Ble_disconnected_15x15.png new file mode 100644 index 0000000000000000000000000000000000000000..bfc1e7d7f187d88125e1e4b4bb2367d5261359b4 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^{220QW{FyL_7{J;KY zD!cHpBWd?U7^82mSk>g>lELXLt>-t(#MCds=m!JOCzjqq-I*4xa*eE?TmsDd?!?S> zc)obEnRW9-o?TaSjl7>-ye`fAx` literal 0 HcmV?d00001 diff --git a/firmware/targets/f7/ble_glue/hid_service.c b/firmware/targets/f7/ble_glue/hid_service.c index e30cdcb0..0efe1747 100644 --- a/firmware/targets/f7/ble_glue/hid_service.c +++ b/firmware/targets/f7/ble_glue/hid_service.c @@ -9,10 +9,9 @@ typedef struct { uint16_t svc_handle; uint16_t protocol_mode_char_handle; - uint16_t report_char_handle; - uint16_t report_ref_desc_handle; + uint16_t report_char_handle[HID_SVC_REPORT_COUNT]; + uint16_t report_ref_desc_handle[HID_SVC_REPORT_COUNT]; uint16_t report_map_char_handle; - uint16_t keyboard_boot_char_handle; uint16_t info_char_handle; uint16_t ctrl_point_char_handle; } HIDSvc; @@ -47,12 +46,22 @@ void hid_svc_start() { SVCCTL_RegisterSvcHandler(hid_svc_event_handler); // Add service svc_uuid.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID; - status = - aci_gatt_add_service(UUID_TYPE_16, &svc_uuid, PRIMARY_SERVICE, 30, &hid_svc->svc_handle); + /** + * Add Human Interface Device Service + */ + status = aci_gatt_add_service( + UUID_TYPE_16, + &svc_uuid, + PRIMARY_SERVICE, + 2 + /* protocol mode */ + (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + + (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + + 2, /* Service + Report Map + HID Information + HID Control Point */ + &hid_svc->svc_handle); if(status) { FURI_LOG_E(TAG, "Failed to add HID service: %d", status); } - // Add Protocol mode characterstics + // Add Protocol mode characteristics char_uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID; status = aci_gatt_add_char( hid_svc->svc_handle, @@ -75,42 +84,120 @@ void hid_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to update protocol mode characteristic: %d", status); } - // Add Report characterstics - char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_REPORT_MAX_LEN, - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_NONE, - GATT_DONT_NOTIFY_EVENTS, - 10, - CHAR_VALUE_LEN_VARIABLE, - &hid_svc->report_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); - } - // Add Report descriptor - uint8_t desc_val[] = {0x00, 0x01}; - desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; - status = aci_gatt_add_char_desc( - hid_svc->svc_handle, - hid_svc->report_char_handle, - UUID_TYPE_16, - &desc_uuid, - HID_SVC_REPORT_REF_LEN, - HID_SVC_REPORT_REF_LEN, - desc_val, - ATTR_PERMISSION_NONE, - ATTR_ACCESS_READ_ONLY, - GATT_DONT_NOTIFY_EVENTS, - MIN_ENCRY_KEY_SIZE, - CHAR_VALUE_LEN_CONSTANT, - &hid_svc->report_ref_desc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + +#if(HID_SVC_REPORT_COUNT != 0) + for(uint8_t i = 0; i < HID_SVC_REPORT_COUNT; i++) { + if(i < HID_SVC_INPUT_REPORT_COUNT) { + uint8_t buf[2] = {i + 1, 1}; // 1 input + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &(hid_svc->report_char_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->report_char_handle[i], + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->report_ref_desc_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + } else if((i - HID_SVC_INPUT_REPORT_COUNT) < HID_SVC_OUTPUT_REPORT_COUNT) { + uint8_t buf[2] = {i + 1, 2}; // 2 output + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &(hid_svc->report_char_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->report_char_handle[i], + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->report_ref_desc_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + } else { + uint8_t buf[2] = {i + 1, 3}; // 3 feature + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char( + hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &(hid_svc->report_char_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc( + hid_svc->svc_handle, + hid_svc->report_char_handle[i], + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + buf, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_WRITE, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &(hid_svc->report_ref_desc_handle[i])); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + } } +#endif // Add Report Map characteristic char_uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID; status = aci_gatt_add_char( @@ -127,22 +214,7 @@ void hid_svc_start() { if(status) { FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); } - // Add Boot Keyboard characteristic - char_uuid.Char_UUID_16 = BOOT_KEYBOARD_INPUT_REPORT_CHAR_UUID; - status = aci_gatt_add_char( - hid_svc->svc_handle, - UUID_TYPE_16, - &char_uuid, - HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN, - CHAR_PROP_READ | CHAR_PROP_NOTIFY, - ATTR_PERMISSION_NONE, - GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP, - 10, - CHAR_VALUE_LEN_VARIABLE, - &hid_svc->keyboard_boot_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); - } + // Add Information characteristic char_uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID; status = aci_gatt_add_char( @@ -177,27 +249,27 @@ void hid_svc_start() { } } -bool hid_svc_update_report_map(uint8_t* data, uint16_t len) { +bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); tBleStatus status = aci_gatt_update_char_value( hid_svc->svc_handle, hid_svc->report_map_char_handle, 0, len, data); if(status) { - FURI_LOG_E(TAG, "Failed updating report map characteristic"); + FURI_LOG_E(TAG, "Failed updating report map characteristic: %d", status); return false; } return true; } -bool hid_svc_update_input_report(uint8_t* data, uint16_t len) { +bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); - tBleStatus status = - aci_gatt_update_char_value(hid_svc->svc_handle, hid_svc->report_char_handle, 0, len, data); + tBleStatus status = aci_gatt_update_char_value( + hid_svc->svc_handle, hid_svc->report_char_handle[input_report_num], 0, len, data); if(status) { - FURI_LOG_E(TAG, "Failed updating report characteristic"); + FURI_LOG_E(TAG, "Failed updating report characteristic: %d", status); return false; } return true; @@ -210,7 +282,7 @@ bool hid_svc_update_info(uint8_t* data, uint16_t len) { tBleStatus status = aci_gatt_update_char_value(hid_svc->svc_handle, hid_svc->info_char_handle, 0, len, data); if(status) { - FURI_LOG_E(TAG, "Failed updating info characteristic"); + FURI_LOG_E(TAG, "Failed updating info characteristic: %d", status); return false; } return true; @@ -228,18 +300,18 @@ void hid_svc_stop() { if(status) { FURI_LOG_E(TAG, "Failed to delete Report Map characteristic: %d", status); } - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Report characteristic: %d", status); +#if(HID_SVC_INPUT_REPORT_COUNT != 0) + for(uint8_t i = 0; i < HID_SVC_REPORT_COUNT; i++) { + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_char_handle[i]); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Report characteristic: %d", status); + } } +#endif status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->protocol_mode_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to delete Protocol Mode characteristic: %d", status); } - status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->keyboard_boot_char_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Keyboard Boot characteristic: %d", status); - } status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->info_char_handle); if(status) { FURI_LOG_E(TAG, "Failed to delete Information characteristic: %d", status); diff --git a/firmware/targets/f7/ble_glue/hid_service.h b/firmware/targets/f7/ble_glue/hid_service.h index ed1394be..723460d4 100644 --- a/firmware/targets/f7/ble_glue/hid_service.h +++ b/firmware/targets/f7/ble_glue/hid_service.h @@ -3,21 +3,26 @@ #include #include -#define HID_SVC_REPORT_MAP_MAX_LEN (120) -#define HID_SVC_REPORT_MAX_LEN (9) -#define HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN (8) +#define HID_SVC_REPORT_MAP_MAX_LEN (255) +#define HID_SVC_REPORT_MAX_LEN (255) #define HID_SVC_REPORT_REF_LEN (2) #define HID_SVC_INFO_LEN (4) #define HID_SVC_CONTROL_POINT_LEN (1) +#define HID_SVC_INPUT_REPORT_COUNT (3) +#define HID_SVC_OUTPUT_REPORT_COUNT (0) +#define HID_SVC_FEATURE_REPORT_COUNT (0) +#define HID_SVC_REPORT_COUNT \ + (HID_SVC_INPUT_REPORT_COUNT + HID_SVC_OUTPUT_REPORT_COUNT + HID_SVC_FEATURE_REPORT_COUNT) + void hid_svc_start(); void hid_svc_stop(); bool hid_svc_is_started(); -bool hid_svc_update_report_map(uint8_t* data, uint16_t len); +bool hid_svc_update_report_map(const uint8_t* data, uint16_t len); -bool hid_svc_update_input_report(uint8_t* data, uint16_t len); +bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len); bool hid_svc_update_info(uint8_t* data, uint16_t len); diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c index ad12d773..dde3842d 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c @@ -218,8 +218,8 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, } else if(profile == FuriHalBtProfileHidKeyboard) { // Change MAC address for HID profile config->mac_address[2]++; - // Change name Flipper -> Keynote - const char* clicker_str = "Keynote"; + // Change name Flipper -> Control + const char* clicker_str = "Control"; memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); } if(!gap_init(config, event_cb, context)) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c index 507dedfb..22415199 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_bt_hid.c @@ -1,4 +1,6 @@ #include "furi_hal_bt_hid.h" +#include "furi_hal_usb_hid.h" +#include "usb_hid.h" #include "dev_info_service.h" #include "battery_service.h" #include "hid_service.h" @@ -10,128 +12,118 @@ #define FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) #define FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) -#define FURI_HAL_BT_HID_KB_KEYS_MAX (6) +#define FURI_HAL_BT_HID_KB_MAX_KEYS 6 +#define FURI_HAL_BT_HID_CONSUMER_MAX_KEYS 1 -typedef struct { - // uint8_t report_id; - uint8_t mods; - uint8_t reserved; - uint8_t key[FURI_HAL_BT_HID_KB_KEYS_MAX]; -} FuriHalBtHidKbReport; - -typedef struct { - uint8_t report_id; - uint8_t key; -} FuriHalBtHidMediaReport; - -// TODO make composite HID device -static uint8_t furi_hal_bt_hid_report_map_data[] = { - 0x05, - 0x01, // Usage Page (Generic Desktop) - 0x09, - 0x06, // Usage (Keyboard) - 0xA1, - 0x01, // Collection (Application) - // 0x85, 0x01, // Report ID (1) - 0x05, - 0x07, // Usage Page (Key Codes) - 0x19, - 0xe0, // Usage Minimum (224) - 0x29, - 0xe7, // Usage Maximum (231) - 0x15, - 0x00, // Logical Minimum (0) - 0x25, - 0x01, // Logical Maximum (1) - 0x75, - 0x01, // Report Size (1) - 0x95, - 0x08, // Report Count (8) - 0x81, - 0x02, // Input (Data, Variable, Absolute) - - 0x95, - 0x01, // Report Count (1) - 0x75, - 0x08, // Report Size (8) - 0x81, - 0x01, // Input (Constant) reserved byte(1) - - 0x95, - 0x05, // Report Count (5) - 0x75, - 0x01, // Report Size (1) - 0x05, - 0x08, // Usage Page (Page# for LEDs) - 0x19, - 0x01, // Usage Minimum (1) - 0x29, - 0x05, // Usage Maximum (5) - 0x91, - 0x02, // Output (Data, Variable, Absolute), Led report - 0x95, - 0x01, // Report Count (1) - 0x75, - 0x03, // Report Size (3) - 0x91, - 0x01, // Output (Data, Variable, Absolute), Led report padding - - 0x95, - 0x06, // Report Count (6) - 0x75, - 0x08, // Report Size (8) - 0x15, - 0x00, // Logical Minimum (0) - 0x25, - 0x65, // Logical Maximum (101) - 0x05, - 0x07, // Usage Page (Key codes) - 0x19, - 0x00, // Usage Minimum (0) - 0x29, - 0x65, // Usage Maximum (101) - 0x81, - 0x00, // Input (Data, Array) Key array(6 bytes) - - 0x09, - 0x05, // Usage (Vendor Defined) - 0x15, - 0x00, // Logical Minimum (0) - 0x26, - 0xFF, - 0x00, // Logical Maximum (255) - 0x75, - 0x08, // Report Size (8 bit) - 0x95, - 0x02, // Report Count (2) - 0xB1, - 0x02, // Feature (Data, Variable, Absolute) - - 0xC0, // End Collection (Application) - - // 0x05, 0x0C, // Usage Page (Consumer) - // 0x09, 0x01, // Usage (Consumer Control) - // 0xA1, 0x01, // Collection (Application) - // 0x85, 0x02, // Report ID (2) - // 0x05, 0x0C, // Usage Page (Consumer) - // 0x15, 0x00, // Logical Minimum (0) - // 0x25, 0x01, // Logical Maximum (1) - // 0x75, 0x01, // Report Size (1) - // 0x95, 0x07, // Report Count (7) - // 0x09, 0xB5, // Usage (Scan Next Track) - // 0x09, 0xB6, // Usage (Scan Previous Track) - // 0x09, 0xB7, // Usage (Stop) - // 0x09, 0xB8, // Usage (Eject) - // 0x09, 0xCD, // Usage (Play/Pause) - // 0x09, 0xE2, // Usage (Mute) - // 0x09, 0xE9, // Usage (Volume Increment) - // 0x09, 0xEA, // Usage (Volume Decrement) - // 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) - // 0xC0, // End Collection +// Report ids cant be 0 +enum HidReportId { + ReportIdKeyboard = 1, + ReportIdMouse = 2, + ReportIdConsumer = 3, +}; +// Report numbers corresponded to the report id with an offset of 1 +enum HidInputNumber { + ReportNumberKeyboard = 0, + ReportNumberMouse = 1, + ReportNumberConsumer = 2, }; +typedef struct { + uint8_t mods; + uint8_t reserved; + uint8_t key[FURI_HAL_BT_HID_KB_MAX_KEYS]; +} __attribute__((__packed__)) FuriHalBtHidKbReport; + +typedef struct { + uint8_t btn; + int8_t x; + int8_t y; + int8_t wheel; +} __attribute__((__packed__)) FuriHalBtHidMouseReport; + +typedef struct { + uint16_t key[FURI_HAL_BT_HID_CONSUMER_MAX_KEYS]; +} __attribute__((__packed__)) FuriHalBtHidConsumerReport; + +// keyboard+mouse+consumer hid report +static const uint8_t furi_hal_bt_hid_report_map_data[] = { + // Keyboard Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_KEYBOARD), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdKeyboard), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), + HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(8), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(8), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(FURI_HAL_BT_HID_KB_MAX_KEYS), + HID_REPORT_SIZE(8), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(101), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(0), + HID_USAGE_MAXIMUM(101), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, + // Mouse Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_MOUSE), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_USAGE(HID_DESKTOP_POINTER), + HID_COLLECTION(HID_PHYSICAL_COLLECTION), + HID_REPORT_ID(ReportIdMouse), + HID_USAGE_PAGE(HID_PAGE_BUTTON), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(3), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_COUNT(3), + HID_REPORT_SIZE(1), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(5), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_X), + HID_USAGE(HID_DESKTOP_Y), + HID_USAGE(HID_DESKTOP_WHEEL), + HID_LOGICAL_MINIMUM(-127), + HID_LOGICAL_MAXIMUM(127), + HID_REPORT_SIZE(8), + HID_REPORT_COUNT(3), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + HID_END_COLLECTION, + HID_END_COLLECTION, + // Consumer Report + HID_USAGE_PAGE(HID_PAGE_CONSUMER), + HID_USAGE(HID_CONSUMER_CONTROL), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdConsumer), + HID_LOGICAL_MINIMUM(0), + HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), + HID_USAGE_MINIMUM(0), + HID_RI_USAGE_MAXIMUM(16, 0x3FF), + HID_REPORT_COUNT(FURI_HAL_BT_HID_CONSUMER_MAX_KEYS), + HID_REPORT_SIZE(16), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, +}; FuriHalBtHidKbReport* kb_report = NULL; -FuriHalBtHidMediaReport* media_report = NULL; +FuriHalBtHidMouseReport* mouse_report = NULL; +FuriHalBtHidConsumerReport* consumer_report = NULL; void furi_hal_bt_hid_start() { // Start device info @@ -148,7 +140,8 @@ void furi_hal_bt_hid_start() { } // Configure HID Keyboard kb_report = malloc(sizeof(FuriHalBtHidKbReport)); - media_report = malloc(sizeof(FuriHalBtHidMediaReport)); + mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); + consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); // Configure Report Map characteristic hid_svc_update_report_map( furi_hal_bt_hid_report_map_data, sizeof(furi_hal_bt_hid_report_map_data)); @@ -165,6 +158,8 @@ void furi_hal_bt_hid_start() { void furi_hal_bt_hid_stop() { furi_assert(kb_report); + furi_assert(mouse_report); + furi_assert(consumer_report); // Stop all services if(dev_info_svc_is_started()) { dev_info_svc_stop(); @@ -176,61 +171,119 @@ void furi_hal_bt_hid_stop() { hid_svc_stop(); } free(kb_report); - free(media_report); - media_report = NULL; + free(mouse_report); + free(consumer_report); kb_report = NULL; + mouse_report = NULL; + consumer_report = NULL; } bool furi_hal_bt_hid_kb_press(uint16_t button) { furi_assert(kb_report); - // kb_report->report_id = 0x01; - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { if(kb_report->key[i] == 0) { kb_report->key[i] = button & 0xFF; break; } } kb_report->mods |= (button >> 8); - return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); + return hid_svc_update_input_report( + ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); } bool furi_hal_bt_hid_kb_release(uint16_t button) { furi_assert(kb_report); - // kb_report->report_id = 0x01; - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { if(kb_report->key[i] == (button & 0xFF)) { kb_report->key[i] = 0; break; } } kb_report->mods &= ~(button >> 8); - return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); + return hid_svc_update_input_report( + ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); } bool furi_hal_bt_hid_kb_release_all() { furi_assert(kb_report); - // kb_report->report_id = 0x01; - memset(kb_report, 0, sizeof(FuriHalBtHidKbReport)); - return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { + kb_report->key[i] = 0; + } + kb_report->mods = 0; + return hid_svc_update_input_report( + ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); } -bool furi_hal_bt_hid_media_press(uint8_t button) { - furi_assert(media_report); - media_report->report_id = 0x02; - media_report->key |= (0x01 << button); - return hid_svc_update_input_report((uint8_t*)media_report, sizeof(FuriHalBtHidMediaReport)); +bool furi_hal_bt_hid_consumer_key_press(uint16_t button) { + furi_assert(consumer_report); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + if(consumer_report->key[i] == 0) { + consumer_report->key[i] = button; + break; + } + } + return hid_svc_update_input_report( + ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); } -bool furi_hal_bt_hid_media_release(uint8_t button) { - furi_assert(media_report); - media_report->report_id = 0x02; - media_report->key &= ~(0x01 << button); - return hid_svc_update_input_report((uint8_t*)media_report, sizeof(FuriHalBtHidMediaReport)); +bool furi_hal_bt_hid_consumer_key_release(uint16_t button) { + furi_assert(consumer_report); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + if(consumer_report->key[i] == button) { + consumer_report->key[i] = 0; + break; + } + } + return hid_svc_update_input_report( + ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); } -bool furi_hal_bt_hid_media_release_all() { - furi_assert(media_report); - media_report->report_id = 0x02; - media_report->key = 0x00; - return hid_svc_update_input_report((uint8_t*)media_report, sizeof(FuriHalBtHidMediaReport)); +bool furi_hal_bt_hid_consumer_key_release_all() { + furi_assert(consumer_report); + for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { + consumer_report->key[i] = 0; + } + return hid_svc_update_input_report( + ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); +} + +bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy) { + furi_assert(mouse_report); + mouse_report->x = dx; + mouse_report->y = dy; + bool state = hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); + mouse_report->x = 0; + mouse_report->y = 0; + return state; +} + +bool furi_hal_bt_hid_mouse_press(uint8_t button) { + furi_assert(mouse_report); + mouse_report->btn |= button; + return hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); +} + +bool furi_hal_bt_hid_mouse_release(uint8_t button) { + furi_assert(mouse_report); + mouse_report->btn &= ~button; + return hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); +} + +bool furi_hal_bt_hid_mouse_release_all() { + furi_assert(mouse_report); + mouse_report->btn = 0; + return hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); +} + +bool furi_hal_bt_hid_mouse_scroll(int8_t delta) { + furi_assert(mouse_report); + mouse_report->wheel = delta; + bool state = hid_svc_update_input_report( + ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); + mouse_report->wheel = 0; + return state; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c index aae4dd4f..06f3f231 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/firmware/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -6,11 +6,6 @@ #include "usb.h" #include "usb_hid.h" -#include "hid_usage_desktop.h" -#include "hid_usage_button.h" -#include "hid_usage_keyboard.h" -#include "hid_usage_consumer.h" -#include "hid_usage_led.h" #define HID_EP_IN 0x81 #define HID_EP_OUT 0x01 diff --git a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h index 4faeebae..e35edd01 100644 --- a/firmware/targets/furi_hal_include/furi_hal_bt_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_bt_hid.h @@ -3,17 +3,6 @@ #include #include -enum FuriHalBtHidMediKeys { - FuriHalBtHidMediaScanNext, - FuriHalBtHidMediaScanPrevious, - FuriHalBtHidMediaStop, - FuriHalBtHidMediaEject, - FuriHalBtHidMediaPlayPause, - FuriHalBtHidMediaMute, - FuriHalBtHidMediaVolumeUp, - FuriHalBtHidMediaVolumeDown, -}; - /** Start Hid Keyboard Profile */ void furi_hal_bt_hid_start(); @@ -44,20 +33,51 @@ bool furi_hal_bt_hid_kb_release(uint16_t button); */ bool furi_hal_bt_hid_kb_release_all(); -/** Release all media buttons +/** Set mouse movement and send HID report * - * @return true on success + * @param dx x coordinate delta + * @param dy y coordinate delta */ -bool furi_hal_bt_hid_media_press(uint8_t button); +bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy); -/** Release all media buttons +/** Set mouse button to pressed state and send HID report * - * @return true on success + * @param button key code */ -bool furi_hal_bt_hid_media_release(uint8_t button); +bool furi_hal_bt_hid_mouse_press(uint8_t button); -/** Release all media buttons +/** Set mouse button to released state and send HID report * - * @return true on success + * @param button key code */ -bool furi_hal_bt_hid_media_release_all(); +bool furi_hal_bt_hid_mouse_release(uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_mouse_release_all(); + +/** Set mouse wheel position and send HID report + * + * @param delta number of scroll steps + */ +bool furi_hal_bt_hid_mouse_scroll(int8_t delta); + +/** Set the following consumer key to pressed state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_consumer_key_press(uint16_t button); + +/** Set the following consumer key to released state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_consumer_key_release(uint16_t button); + +/** Set consumer key to released state and send HID report + * + * @param button key code + */ +bool furi_hal_bt_hid_consumer_key_release_all(); diff --git a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h index 20c76223..97bac7f0 100644 --- a/firmware/targets/furi_hal_include/furi_hal_usb_hid.h +++ b/firmware/targets/furi_hal_include/furi_hal_usb_hid.h @@ -1,110 +1,13 @@ #pragma once +#include "hid_usage_desktop.h" +#include "hid_usage_button.h" +#include "hid_usage_keyboard.h" +#include "hid_usage_consumer.h" +#include "hid_usage_led.h" -/** HID keyboard key codes */ -enum HidKeyboardKeys { - KEY_NONE = 0x00, - KEY_ERROR_ROLLOVER = 0x01, - KEY_POST_FAIL = 0x02, - KEY_ERROR_UNDEFINED = 0x03, - KEY_A = 0x04, - KEY_B = 0x05, - KEY_C = 0x06, - KEY_D = 0x07, - KEY_E = 0x08, - KEY_F = 0x09, - KEY_G = 0x0A, - KEY_H = 0x0B, - KEY_I = 0x0C, - KEY_J = 0x0D, - KEY_K = 0x0E, - KEY_L = 0x0F, - KEY_M = 0x10, - KEY_N = 0x11, - KEY_O = 0x12, - KEY_P = 0x13, - KEY_Q = 0x14, - KEY_R = 0x15, - KEY_S = 0x16, - KEY_T = 0x17, - KEY_U = 0x18, - KEY_V = 0x19, - KEY_W = 0x1A, - KEY_X = 0x1B, - KEY_Y = 0x1C, - KEY_Z = 0x1D, - KEY_1 = 0x1E, - KEY_2 = 0x1F, - KEY_3 = 0x20, - KEY_4 = 0x21, - KEY_5 = 0x22, - KEY_6 = 0x23, - KEY_7 = 0x24, - KEY_8 = 0x25, - KEY_9 = 0x26, - KEY_0 = 0x27, - KEY_ENTER = 0x28, - KEY_ESC = 0x29, - KEY_BACKSPACE = 0x2A, - KEY_TAB = 0x2B, - KEY_SPACE = 0x2C, - KEY_MINUS = 0x2D, - KEY_EQUAL = 0x2E, - KEY_LEFT_BRACE = 0x2F, - KEY_RIGHT_BRACE = 0x30, - KEY_BACKSLASH = 0x31, - KEY_NON_US_NUM = 0x32, - KEY_SEMICOLON = 0x33, - KEY_QUOTE = 0x34, - KEY_TILDE = 0x35, - KEY_COMMA = 0x36, - KEY_PERIOD = 0x37, - KEY_SLASH = 0x38, - KEY_CAPS_LOCK = 0x39, - KEY_F1 = 0x3A, - KEY_F2 = 0x3B, - KEY_F3 = 0x3C, - KEY_F4 = 0x3D, - KEY_F5 = 0x3E, - KEY_F6 = 0x3F, - KEY_F7 = 0x40, - KEY_F8 = 0x41, - KEY_F9 = 0x42, - KEY_F10 = 0x43, - KEY_F11 = 0x44, - KEY_F12 = 0x45, - KEY_PRINT = 0x46, - KEY_SCROLL_LOCK = 0x47, - KEY_PAUSE = 0x48, - KEY_INSERT = 0x49, - KEY_HOME = 0x4A, - KEY_PAGE_UP = 0x4B, - KEY_DELETE = 0x4C, - KEY_END = 0x4D, - KEY_PAGE_DOWN = 0x4E, - KEY_RIGHT_ARROW = 0x4F, - KEY_LEFT_ARROW = 0x50, - KEY_DOWN_ARROW = 0x51, - KEY_UP_ARROW = 0x52, - KEY_NUM_LOCK = 0x53, - KEYPAD_DIVIDE = 0x54, - KEYPAD_MULTIPLY = 0x55, - KEYPAD_SUBTRACT = 0x56, - KEYPAD_ADD = 0x57, - KEYPAD_ENTER = 0x58, - KEYPAD_1 = 0x59, - KEYPAD_2 = 0x5A, - KEYPAD_3 = 0x5B, - KEYPAD_4 = 0x5C, - KEYPAD_5 = 0x5D, - KEYPAD_6 = 0x5E, - KEYPAD_7 = 0x5F, - KEYPAD_8 = 0x60, - KEYPAD_9 = 0x61, - KEYPAD_0 = 0x62, - KEYPAD_DOT = 0x63, - KEY_NON_US = 0x64, - KEY_APPLICATION = 0x65, -}; +#define HID_KEYBOARD_NONE 0x00 +// Remapping the colon key which is shift + ; to comma +#define HID_KEYBOARD_COMMA HID_KEYBOARD_COLON /** HID keyboard modifier keys */ enum HidKeyboardMods { @@ -120,134 +23,134 @@ enum HidKeyboardMods { /** ASCII to keycode conversion table */ static const uint16_t hid_asciimap[] = { - KEY_NONE, // NUL - KEY_NONE, // SOH - KEY_NONE, // STX - KEY_NONE, // ETX - KEY_NONE, // EOT - KEY_NONE, // ENQ - KEY_NONE, // ACK - KEY_NONE, // BEL - KEY_BACKSPACE, // BS Backspace - KEY_TAB, // TAB Tab - KEY_ENTER, // LF Enter - KEY_NONE, // VT - KEY_NONE, // FF - KEY_NONE, // CR - KEY_NONE, // SO - KEY_NONE, // SI - KEY_NONE, // DEL - KEY_NONE, // DC1 - KEY_NONE, // DC2 - KEY_NONE, // DC3 - KEY_NONE, // DC4 - KEY_NONE, // NAK - KEY_NONE, // SYN - KEY_NONE, // ETB - KEY_NONE, // CAN - KEY_NONE, // EM - KEY_NONE, // SUB - KEY_NONE, // ESC - KEY_NONE, // FS - KEY_NONE, // GS - KEY_NONE, // RS - KEY_NONE, // US - KEY_SPACE, // ' ' Space - KEY_1 | KEY_MOD_LEFT_SHIFT, // ! - KEY_QUOTE | KEY_MOD_LEFT_SHIFT, // " - KEY_3 | KEY_MOD_LEFT_SHIFT, // # - KEY_4 | KEY_MOD_LEFT_SHIFT, // $ - KEY_5 | KEY_MOD_LEFT_SHIFT, // % - KEY_7 | KEY_MOD_LEFT_SHIFT, // & - KEY_QUOTE, // ' - KEY_9 | KEY_MOD_LEFT_SHIFT, // ( - KEY_0 | KEY_MOD_LEFT_SHIFT, // ) - KEY_8 | KEY_MOD_LEFT_SHIFT, // * - KEY_EQUAL | KEY_MOD_LEFT_SHIFT, // + - KEY_COMMA, // , - KEY_MINUS, // - - KEY_PERIOD, // . - KEY_SLASH, // / - KEY_0, // 0 - KEY_1, // 1 - KEY_2, // 2 - KEY_3, // 3 - KEY_4, // 4 - KEY_5, // 5 - KEY_6, // 6 - KEY_7, // 7 - KEY_8, // 8 - KEY_9, // 9 - KEY_SEMICOLON | KEY_MOD_LEFT_SHIFT, // : - KEY_SEMICOLON, // ; - KEY_COMMA | KEY_MOD_LEFT_SHIFT, // < - KEY_EQUAL, // = - KEY_PERIOD | KEY_MOD_LEFT_SHIFT, // > - KEY_SLASH | KEY_MOD_LEFT_SHIFT, // ? - KEY_2 | KEY_MOD_LEFT_SHIFT, // @ - KEY_A | KEY_MOD_LEFT_SHIFT, // A - KEY_B | KEY_MOD_LEFT_SHIFT, // B - KEY_C | KEY_MOD_LEFT_SHIFT, // C - KEY_D | KEY_MOD_LEFT_SHIFT, // D - KEY_E | KEY_MOD_LEFT_SHIFT, // E - KEY_F | KEY_MOD_LEFT_SHIFT, // F - KEY_G | KEY_MOD_LEFT_SHIFT, // G - KEY_H | KEY_MOD_LEFT_SHIFT, // H - KEY_I | KEY_MOD_LEFT_SHIFT, // I - KEY_J | KEY_MOD_LEFT_SHIFT, // J - KEY_K | KEY_MOD_LEFT_SHIFT, // K - KEY_L | KEY_MOD_LEFT_SHIFT, // L - KEY_M | KEY_MOD_LEFT_SHIFT, // M - KEY_N | KEY_MOD_LEFT_SHIFT, // N - KEY_O | KEY_MOD_LEFT_SHIFT, // O - KEY_P | KEY_MOD_LEFT_SHIFT, // P - KEY_Q | KEY_MOD_LEFT_SHIFT, // Q - KEY_R | KEY_MOD_LEFT_SHIFT, // R - KEY_S | KEY_MOD_LEFT_SHIFT, // S - KEY_T | KEY_MOD_LEFT_SHIFT, // T - KEY_U | KEY_MOD_LEFT_SHIFT, // U - KEY_V | KEY_MOD_LEFT_SHIFT, // V - KEY_W | KEY_MOD_LEFT_SHIFT, // W - KEY_X | KEY_MOD_LEFT_SHIFT, // X - KEY_Y | KEY_MOD_LEFT_SHIFT, // Y - KEY_Z | KEY_MOD_LEFT_SHIFT, // Z - KEY_LEFT_BRACE, // [ - KEY_BACKSLASH, // bslash - KEY_RIGHT_BRACE, // ] - KEY_6 | KEY_MOD_LEFT_SHIFT, // ^ - KEY_MINUS | KEY_MOD_LEFT_SHIFT, // _ - KEY_TILDE, // ` - KEY_A, // a - KEY_B, // b - KEY_C, // c - KEY_D, // d - KEY_E, // e - KEY_F, // f - KEY_G, // g - KEY_H, // h - KEY_I, // i - KEY_J, // j - KEY_K, // k - KEY_L, // l - KEY_M, // m - KEY_N, // n - KEY_O, // o - KEY_P, // p - KEY_Q, // q - KEY_R, // r - KEY_S, // s - KEY_T, // t - KEY_U, // u - KEY_V, // v - KEY_W, // w - KEY_X, // x - KEY_Y, // y - KEY_Z, // z - KEY_LEFT_BRACE | KEY_MOD_LEFT_SHIFT, // { - KEY_BACKSLASH | KEY_MOD_LEFT_SHIFT, // | - KEY_RIGHT_BRACE | KEY_MOD_LEFT_SHIFT, // } - KEY_TILDE | KEY_MOD_LEFT_SHIFT, // ~ - KEY_NONE, // DEL + HID_KEYBOARD_NONE, // NUL + HID_KEYBOARD_NONE, // SOH + HID_KEYBOARD_NONE, // STX + HID_KEYBOARD_NONE, // ETX + HID_KEYBOARD_NONE, // EOT + HID_KEYBOARD_NONE, // ENQ + HID_KEYBOARD_NONE, // ACK + HID_KEYBOARD_NONE, // BEL + HID_KEYBOARD_DELETE, // BS Backspace + HID_KEYBOARD_TAB, // TAB Tab + HID_KEYBOARD_RETURN, // LF Enter + HID_KEYBOARD_NONE, // VT + HID_KEYBOARD_NONE, // FF + HID_KEYBOARD_NONE, // CR + HID_KEYBOARD_NONE, // SO + HID_KEYBOARD_NONE, // SI + HID_KEYBOARD_NONE, // DEL + HID_KEYBOARD_NONE, // DC1 + HID_KEYBOARD_NONE, // DC2 + HID_KEYBOARD_NONE, // DC3 + HID_KEYBOARD_NONE, // DC4 + HID_KEYBOARD_NONE, // NAK + HID_KEYBOARD_NONE, // SYN + HID_KEYBOARD_NONE, // ETB + HID_KEYBOARD_NONE, // CAN + HID_KEYBOARD_NONE, // EM + HID_KEYBOARD_NONE, // SUB + HID_KEYBOARD_NONE, // ESC + HID_KEYBOARD_NONE, // FS + HID_KEYBOARD_NONE, // GS + HID_KEYBOARD_NONE, // RS + HID_KEYBOARD_NONE, // US + HID_KEYBOARD_SPACEBAR, // ' ' Space + HID_KEYBOARD_1 | KEY_MOD_LEFT_SHIFT, // ! + HID_KEYBOARD_APOSTROPHE | KEY_MOD_LEFT_SHIFT, // " + HID_KEYBOARD_3 | KEY_MOD_LEFT_SHIFT, // # + HID_KEYBOARD_4 | KEY_MOD_LEFT_SHIFT, // $ + HID_KEYBOARD_5 | KEY_MOD_LEFT_SHIFT, // % + HID_KEYBOARD_7 | KEY_MOD_LEFT_SHIFT, // & + HID_KEYBOARD_APOSTROPHE, // ' + HID_KEYBOARD_9 | KEY_MOD_LEFT_SHIFT, // ( + HID_KEYBOARD_0 | KEY_MOD_LEFT_SHIFT, // ) + HID_KEYBOARD_8 | KEY_MOD_LEFT_SHIFT, // * + HID_KEYBOARD_EQUAL_SIGN | KEY_MOD_LEFT_SHIFT, // + + HID_KEYBOARD_COMMA, // , + HID_KEYBOARD_MINUS, // - + HID_KEYBOARD_DOT, // . + HID_KEYBOARD_SLASH, // / + HID_KEYBOARD_0, // 0 + HID_KEYBOARD_1, // 1 + HID_KEYBOARD_2, // 2 + HID_KEYBOARD_3, // 3 + HID_KEYBOARD_4, // 4 + HID_KEYBOARD_5, // 5 + HID_KEYBOARD_6, // 6 + HID_KEYBOARD_7, // 7 + HID_KEYBOARD_8, // 8 + HID_KEYBOARD_9, // 9 + HID_KEYBOARD_SEMICOLON | KEY_MOD_LEFT_SHIFT, // : + HID_KEYBOARD_SEMICOLON, // ; + HID_KEYBOARD_COMMA | KEY_MOD_LEFT_SHIFT, // < + HID_KEYBOARD_EQUAL_SIGN, // = + HID_KEYBOARD_DOT | KEY_MOD_LEFT_SHIFT, // > + HID_KEYBOARD_SLASH | KEY_MOD_LEFT_SHIFT, // ? + HID_KEYBOARD_2 | KEY_MOD_LEFT_SHIFT, // @ + HID_KEYBOARD_A | KEY_MOD_LEFT_SHIFT, // A + HID_KEYBOARD_B | KEY_MOD_LEFT_SHIFT, // B + HID_KEYBOARD_C | KEY_MOD_LEFT_SHIFT, // C + HID_KEYBOARD_D | KEY_MOD_LEFT_SHIFT, // D + HID_KEYBOARD_E | KEY_MOD_LEFT_SHIFT, // E + HID_KEYBOARD_F | KEY_MOD_LEFT_SHIFT, // F + HID_KEYBOARD_G | KEY_MOD_LEFT_SHIFT, // G + HID_KEYBOARD_H | KEY_MOD_LEFT_SHIFT, // H + HID_KEYBOARD_I | KEY_MOD_LEFT_SHIFT, // I + HID_KEYBOARD_J | KEY_MOD_LEFT_SHIFT, // J + HID_KEYBOARD_K | KEY_MOD_LEFT_SHIFT, // K + HID_KEYBOARD_L | KEY_MOD_LEFT_SHIFT, // L + HID_KEYBOARD_M | KEY_MOD_LEFT_SHIFT, // M + HID_KEYBOARD_N | KEY_MOD_LEFT_SHIFT, // N + HID_KEYBOARD_O | KEY_MOD_LEFT_SHIFT, // O + HID_KEYBOARD_P | KEY_MOD_LEFT_SHIFT, // P + HID_KEYBOARD_Q | KEY_MOD_LEFT_SHIFT, // Q + HID_KEYBOARD_R | KEY_MOD_LEFT_SHIFT, // R + HID_KEYBOARD_S | KEY_MOD_LEFT_SHIFT, // S + HID_KEYBOARD_T | KEY_MOD_LEFT_SHIFT, // T + HID_KEYBOARD_U | KEY_MOD_LEFT_SHIFT, // U + HID_KEYBOARD_V | KEY_MOD_LEFT_SHIFT, // V + HID_KEYBOARD_W | KEY_MOD_LEFT_SHIFT, // W + HID_KEYBOARD_X | KEY_MOD_LEFT_SHIFT, // X + HID_KEYBOARD_Y | KEY_MOD_LEFT_SHIFT, // Y + HID_KEYBOARD_Z | KEY_MOD_LEFT_SHIFT, // Z + HID_KEYBOARD_OPEN_BRACKET, // [ + HID_KEYBOARD_BACKSLASH, // bslash + HID_KEYBOARD_CLOSE_BRACKET, // ] + HID_KEYBOARD_6 | KEY_MOD_LEFT_SHIFT, // ^ + HID_KEYBOARD_MINUS | KEY_MOD_LEFT_SHIFT, // _ + HID_KEYBOARD_GRAVE_ACCENT, // ` + HID_KEYBOARD_A, // a + HID_KEYBOARD_B, // b + HID_KEYBOARD_C, // c + HID_KEYBOARD_D, // d + HID_KEYBOARD_E, // e + HID_KEYBOARD_F, // f + HID_KEYBOARD_G, // g + HID_KEYBOARD_H, // h + HID_KEYBOARD_I, // i + HID_KEYBOARD_J, // j + HID_KEYBOARD_K, // k + HID_KEYBOARD_L, // l + HID_KEYBOARD_M, // m + HID_KEYBOARD_N, // n + HID_KEYBOARD_O, // o + HID_KEYBOARD_P, // p + HID_KEYBOARD_Q, // q + HID_KEYBOARD_R, // r + HID_KEYBOARD_S, // s + HID_KEYBOARD_T, // t + HID_KEYBOARD_U, // u + HID_KEYBOARD_V, // v + HID_KEYBOARD_W, // w + HID_KEYBOARD_X, // x + HID_KEYBOARD_Y, // y + HID_KEYBOARD_Z, // z + HID_KEYBOARD_OPEN_BRACKET | KEY_MOD_LEFT_SHIFT, // { + HID_KEYBOARD_BACKSLASH | KEY_MOD_LEFT_SHIFT, // | + HID_KEYBOARD_CLOSE_BRACKET | KEY_MOD_LEFT_SHIFT, // } + HID_KEYBOARD_GRAVE_ACCENT | KEY_MOD_LEFT_SHIFT, // ~ + HID_KEYBOARD_NONE, // DEL }; typedef struct { @@ -260,7 +163,7 @@ typedef struct { typedef void (*HidStateCallback)(bool state, void* context); /** ASCII to keycode conversion macro */ -#define HID_ASCII_TO_KEY(x) (((uint8_t)x < 128) ? (hid_asciimap[(uint8_t)x]) : KEY_NONE) +#define HID_ASCII_TO_KEY(x) (((uint8_t)x < 128) ? (hid_asciimap[(uint8_t)x]) : HID_KEYBOARD_NONE) /** HID keyboard leds */ enum HidKeyboardLeds { From f46ae5a7042ee51379c9dd1a4847c07a8fc7f96f Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Fri, 8 Jul 2022 05:56:23 -0700 Subject: [PATCH 24/26] Namespace loclass library (#1379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Namespace loclass library * Lib: const for immutable variables and bss cleanup Co-authored-by: あく --- applications/picopass/picopass_worker.c | 4 +- lib/loclass/optimized_cipher.c | 143 ++++++++++-------------- lib/loclass/optimized_cipher.h | 16 +-- lib/loclass/optimized_cipherutils.c | 38 +++---- lib/loclass/optimized_cipherutils.h | 24 ++-- lib/loclass/optimized_elite.c | 82 +++++++------- lib/loclass/optimized_elite.h | 10 +- lib/loclass/optimized_ikeys.c | 142 +++++++++++------------ lib/loclass/optimized_ikeys.h | 8 +- 9 files changed, 221 insertions(+), 246 deletions(-) diff --git a/applications/picopass/picopass_worker.c b/applications/picopass/picopass_worker.c index abefcb71..8e19b3c4 100644 --- a/applications/picopass/picopass_worker.c +++ b/applications/picopass/picopass_worker.c @@ -198,8 +198,8 @@ ReturnCode picopass_read_card(ApplicationArea* AA1) { } memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - diversifyKey(selRes.CSN, picopass_iclass_key, div_key); - opt_doReaderMAC(ccnr, div_key, mac); + loclass_diversifyKey(selRes.CSN, picopass_iclass_key, div_key); + loclass_opt_doReaderMAC(ccnr, div_key, mac); err = rfalPicoPassPollerCheck(mac, &chkRes); if(err != ERR_NONE) { diff --git a/lib/loclass/optimized_cipher.c b/lib/loclass/optimized_cipher.c index 68b36af8..e4f6a58c 100644 --- a/lib/loclass/optimized_cipher.c +++ b/lib/loclass/optimized_cipher.c @@ -81,7 +81,7 @@ #include "optimized_ikeys.h" #include "optimized_cipherutils.h" -static const uint8_t opt_select_LUT[256] = { +static const uint8_t loclass_opt_select_LUT[256] = { 00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, 01, 02, 03, 00, 02, 03, 00, 01, 05, 06, 06, 05, 06, 07, 05, 04, 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06, @@ -110,42 +110,18 @@ static void init_opt_select_LUT(void) { uint8_t z0 = (r_and_ls2 >> 5) ^ ((r & ~r_ls2) >> 4) ^ ( r_or_ls2 >> 3); uint8_t z1 = (r_or_ls2 >> 6) ^ ( r_or_ls2 >> 1) ^ (r >> 5) ^ r; uint8_t z2 = ((r & ~r_ls2) >> 4) ^ (r_and_ls2 >> 3) ^ r; - opt_select_LUT[r] = (z0 & 4) | (z1 & 2) | (z2 & 1); + loclass_opt_select_LUT[r] = (z0 & 4) | (z1 & 2) | (z2 & 1); } - print_result("", opt_select_LUT, 256); + print_result("", loclass_opt_select_LUT, 256); } ***********************************************************************************/ -#define opt__select(x,y,r) (4 & (((r & (r << 2)) >> 5) ^ ((r & ~(r << 2)) >> 4) ^ ( (r | r << 2) >> 3)))\ +#define loclass_opt__select(x,y,r) (4 & (((r & (r << 2)) >> 5) ^ ((r & ~(r << 2)) >> 4) ^ ( (r | r << 2) >> 3)))\ |(2 & (((r | r << 2) >> 6) ^ ( (r | r << 2) >> 1) ^ (r >> 5) ^ r ^ ((x^y) << 1)))\ |(1 & (((r & ~(r << 2)) >> 4) ^ ((r & (r << 2)) >> 3) ^ r ^ x)) -/* - * Some background on the expression above can be found here... -uint8_t xopt__select(bool x, bool y, uint8_t r) -{ - //r: r0 r1 r2 r3 r4 r5 r6 r7 - //r_ls2: r2 r3 r4 r5 r6 r7 0 0 - // z0 - // z1 - -// uint8_t z0 = (r0 & r2) ^ (r1 & ~r3) ^ (r2 | r4); // <-- original - uint8_t z0 = (r_and_ls2 >> 5) ^ ((r & ~r_ls2) >> 4) ^ ( r_or_ls2 >> 3); - -// uint8_t z1 = (r0 | r2) ^ ( r5 | r7) ^ r1 ^ r6 ^ x ^ y; // <-- original - uint8_t z1 = (r_or_ls2 >> 6) ^ ( r_or_ls2 >> 1) ^ (r >> 5) ^ r ^ ((x^y) << 1); - -// uint8_t z2 = (r3 & ~r5) ^ (r4 & r6 ) ^ r7 ^ x; // <-- original - uint8_t z2 = ((r & ~r_ls2) >> 4) ^ (r_and_ls2 >> 3) ^ r ^ x; - - return (z0 & 4) | (z1 & 2) | (z2 & 1); -} -*/ - -static void opt_successor(const uint8_t *k, State_t *s, uint8_t y) { -// #define opt_T(s) (0x1 & ((s->t >> 15) ^ (s->t >> 14) ^ (s->t >> 10) ^ (s->t >> 8) ^ (s->t >> 5) ^ (s->t >> 4)^ (s->t >> 1) ^ s->t)) - // uint8_t Tt = opt_T(s); +static void loclass_opt_successor(const uint8_t *k, LoclassState_t *s, uint8_t y) { uint16_t Tt = s->t & 0xc533; Tt = Tt ^ (Tt >> 1); Tt = Tt ^ (Tt >> 4); @@ -163,125 +139,125 @@ static void opt_successor(const uint8_t *k, State_t *s, uint8_t y) { s->b = s->b >> 1; s->b |= (opt_B ^ s->r) << 7; - uint8_t opt_select = opt_select_LUT[s->r] & 0x04; - opt_select |= (opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02; - opt_select |= (opt_select_LUT[s->r] ^ Tt) & 0x01; + uint8_t opt_select = loclass_opt_select_LUT[s->r] & 0x04; + opt_select |= (loclass_opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02; + opt_select |= (loclass_opt_select_LUT[s->r] ^ Tt) & 0x01; uint8_t r = s->r; s->r = (k[opt_select] ^ s->b) + s->l ; s->l = s->r + r; } -static void opt_suc(const uint8_t *k, State_t *s, const uint8_t *in, uint8_t length, bool add32Zeroes) { +static void loclass_opt_suc(const uint8_t *k, LoclassState_t *s, const uint8_t *in, uint8_t length, bool add32Zeroes) { for (int i = 0; i < length; i++) { uint8_t head; head = in[i]; - opt_successor(k, s, head); + loclass_opt_successor(k, s, head); head >>= 1; - opt_successor(k, s, head); + loclass_opt_successor(k, s, head); head >>= 1; - opt_successor(k, s, head); + loclass_opt_successor(k, s, head); head >>= 1; - opt_successor(k, s, head); + loclass_opt_successor(k, s, head); head >>= 1; - opt_successor(k, s, head); + loclass_opt_successor(k, s, head); head >>= 1; - opt_successor(k, s, head); + loclass_opt_successor(k, s, head); head >>= 1; - opt_successor(k, s, head); + loclass_opt_successor(k, s, head); head >>= 1; - opt_successor(k, s, head); + loclass_opt_successor(k, s, head); } //For tag MAC, an additional 32 zeroes if (add32Zeroes) { for (int i = 0; i < 16; i++) { - opt_successor(k, s, 0); - opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); } } } -static void opt_output(const uint8_t *k, State_t *s, uint8_t *buffer) { +static void loclass_opt_output(const uint8_t *k, LoclassState_t *s, uint8_t *buffer) { for (uint8_t times = 0; times < 4; times++) { uint8_t bout = 0; bout |= (s->r & 0x4) >> 2; - opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); bout |= (s->r & 0x4) >> 1; - opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); bout |= (s->r & 0x4); - opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); bout |= (s->r & 0x4) << 1; - opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); bout |= (s->r & 0x4) << 2; - opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); bout |= (s->r & 0x4) << 3; - opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); bout |= (s->r & 0x4) << 4; - opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); bout |= (s->r & 0x4) << 5; - opt_successor(k, s, 0); + loclass_opt_successor(k, s, 0); buffer[times] = bout; } } -static void opt_MAC(uint8_t *k, uint8_t *input, uint8_t *out) { - State_t _init = { +static void loclass_opt_MAC(uint8_t *k, uint8_t *input, uint8_t *out) { + LoclassState_t _init = { ((k[0] ^ 0x4c) + 0xEC) & 0xFF,// l ((k[0] ^ 0x4c) + 0x21) & 0xFF,// r 0x4c, // b 0xE012 // t }; - opt_suc(k, &_init, input, 12, false); - opt_output(k, &_init, out); + loclass_opt_suc(k, &_init, input, 12, false); + loclass_opt_output(k, &_init, out); } -static void opt_MAC_N(uint8_t *k, uint8_t *input, uint8_t in_size, uint8_t *out) { - State_t _init = { +static void loclass_opt_MAC_N(uint8_t *k, uint8_t *input, uint8_t in_size, uint8_t *out) { + LoclassState_t _init = { ((k[0] ^ 0x4c) + 0xEC) & 0xFF,// l ((k[0] ^ 0x4c) + 0x21) & 0xFF,// r 0x4c, // b 0xE012 // t }; - opt_suc(k, &_init, input, in_size, false); - opt_output(k, &_init, out); + loclass_opt_suc(k, &_init, input, in_size, false); + loclass_opt_output(k, &_init, out); } -void opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]) { +void loclass_opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]) { uint8_t dest [] = {0, 0, 0, 0, 0, 0, 0, 0}; - opt_MAC(div_key_p, cc_nr_p, dest); + loclass_opt_MAC(div_key_p, cc_nr_p, dest); memcpy(mac, dest, 4); } -void opt_doReaderMAC_2(State_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { - opt_suc(div_key_p, &_init, nr, 4, false); - opt_output(div_key_p, &_init, mac); +void loclass_opt_doReaderMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { + loclass_opt_suc(div_key_p, &_init, nr, 4, false); + loclass_opt_output(div_key_p, &_init, mac); } -void doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]) { +void loclass_doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]) { uint8_t dest [] = {0, 0, 0, 0, 0, 0, 0, 0}; - opt_MAC_N(div_key_p, in_p, in_size, dest); + loclass_opt_MAC_N(div_key_p, in_p, in_size, dest); memcpy(mac, dest, 4); } -void opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]) { - State_t _init = { +void loclass_opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]) { + LoclassState_t _init = { ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF,// l ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF,// r 0x4c, // b 0xE012 // t }; - opt_suc(div_key_p, &_init, cc_p, 12, true); - opt_output(div_key_p, &_init, mac); + loclass_opt_suc(div_key_p, &_init, cc_p, 12, true); + loclass_opt_output(div_key_p, &_init, mac); } /** @@ -292,14 +268,14 @@ void opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]) { * @param div_key_p * @return the cipher state */ -State_t opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p) { - State_t _init = { +LoclassState_t loclass_opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p) { + LoclassState_t _init = { ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF,// l ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF,// r 0x4c, // b 0xE012 // t }; - opt_suc(div_key_p, &_init, cc_p, 8, false); + loclass_opt_suc(div_key_p, &_init, cc_p, 8, false); return _init; } @@ -312,27 +288,26 @@ State_t opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p) { * @param mac - where to store the MAC * @param div_key_p - the key to use */ -void opt_doTagMAC_2(State_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { - opt_suc(div_key_p, &_init, nr, 4, true); - opt_output(div_key_p, &_init, mac); +void loclass_opt_doTagMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p) { + loclass_opt_suc(div_key_p, &_init, nr, 4, true); + loclass_opt_output(div_key_p, &_init, mac); } - -void iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite) { +void loclass_iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite) { if (elite) { uint8_t keytable[128] = {0}; uint8_t key_index[8] = {0}; uint8_t key_sel[8] = { 0 }; uint8_t key_sel_p[8] = { 0 }; - hash2(key, keytable); - hash1(csn, key_index); + loclass_hash2(key, keytable); + loclass_hash1(csn, key_index); for (uint8_t i = 0; i < 8 ; i++) key_sel[i] = keytable[key_index[i]]; //Permute from iclass format to standard format - permutekey_rev(key_sel, key_sel_p); - diversifyKey(csn, key_sel_p, div_key); + loclass_permutekey_rev(key_sel, key_sel_p); + loclass_diversifyKey(csn, key_sel_p, div_key); } else { - diversifyKey(csn, key, div_key); + loclass_diversifyKey(csn, key, div_key); } } diff --git a/lib/loclass/optimized_cipher.h b/lib/loclass/optimized_cipher.h index 06f85b08..e7b8cbd6 100644 --- a/lib/loclass/optimized_cipher.h +++ b/lib/loclass/optimized_cipher.h @@ -52,18 +52,18 @@ typedef struct { uint8_t r; uint8_t b; uint16_t t; -} State_t; +} LoclassState_t; /** The reader MAC is MAC(key, CC * NR ) **/ -void opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]); +void loclass_opt_doReaderMAC(uint8_t *cc_nr_p, uint8_t *div_key_p, uint8_t mac[4]); -void opt_doReaderMAC_2(State_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); +void loclass_opt_doReaderMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); /** * The tag MAC is MAC(key, CC * NR * 32x0)) */ -void opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]); +void loclass_opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]); /** * The tag MAC can be divided (both can, but no point in dividing the reader mac) into @@ -73,7 +73,7 @@ void opt_doTagMAC(uint8_t *cc_p, const uint8_t *div_key_p, uint8_t mac[4]); * @param div_key_p * @return the cipher state */ -State_t opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p); +LoclassState_t loclass_opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p); /** * The second part of the tag MAC calculation, since the CC is already calculated into the state, * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag @@ -83,8 +83,8 @@ State_t opt_doTagMAC_1(uint8_t *cc_p, const uint8_t *div_key_p); * @param mac - where to store the MAC * @param div_key_p - the key to use */ -void opt_doTagMAC_2(State_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); +void loclass_opt_doTagMAC_2(LoclassState_t _init, uint8_t *nr, uint8_t mac[4], const uint8_t *div_key_p); -void doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]); -void iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite); +void loclass_doMAC_N(uint8_t *in_p, uint8_t in_size, uint8_t *div_key_p, uint8_t mac[4]); +void loclass_iclass_calc_div_key(uint8_t *csn, uint8_t *key, uint8_t *div_key, bool elite); #endif // OPTIMIZED_CIPHER_H diff --git a/lib/loclass/optimized_cipherutils.c b/lib/loclass/optimized_cipherutils.c index 299d1480..c5bcbacc 100644 --- a/lib/loclass/optimized_cipherutils.c +++ b/lib/loclass/optimized_cipherutils.c @@ -40,7 +40,7 @@ * @param stream * @return */ -bool headBit(BitstreamIn_t *stream) { +bool loclass_headBit(LoclassBitstreamIn_t *stream) { int bytepos = stream->position >> 3; // divide by 8 int bitpos = (stream->position++) & 7; // mask out 00000111 return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; @@ -50,7 +50,7 @@ bool headBit(BitstreamIn_t *stream) { * @param stream * @return */ -bool tailBit(BitstreamIn_t *stream) { +bool loclass_tailBit(LoclassBitstreamIn_t *stream) { int bitpos = stream->numbits - 1 - (stream->position++); int bytepos = bitpos >> 3; @@ -62,7 +62,7 @@ bool tailBit(BitstreamIn_t *stream) { * @param stream * @param bit */ -void pushBit(BitstreamOut_t *stream, bool bit) { +void loclass_pushBit(LoclassBitstreamOut_t *stream, bool bit) { int bytepos = stream->position >> 3; // divide by 8 int bitpos = stream->position & 7; *(stream->buffer + bytepos) |= (bit) << (7 - bitpos); @@ -76,21 +76,21 @@ void pushBit(BitstreamOut_t *stream, bool bit) { * @param stream * @param bits */ -void push6bits(BitstreamOut_t *stream, uint8_t bits) { - pushBit(stream, bits & 0x20); - pushBit(stream, bits & 0x10); - pushBit(stream, bits & 0x08); - pushBit(stream, bits & 0x04); - pushBit(stream, bits & 0x02); - pushBit(stream, bits & 0x01); +void loclass_push6bits(LoclassBitstreamOut_t *stream, uint8_t bits) { + loclass_pushBit(stream, bits & 0x20); + loclass_pushBit(stream, bits & 0x10); + loclass_pushBit(stream, bits & 0x08); + loclass_pushBit(stream, bits & 0x04); + loclass_pushBit(stream, bits & 0x02); + loclass_pushBit(stream, bits & 0x01); } /** - * @brief bitsLeft + * @brief loclass_bitsLeft * @param stream * @return number of bits left in stream */ -int bitsLeft(BitstreamIn_t *stream) { +int loclass_bitsLeft(LoclassBitstreamIn_t *stream) { return stream->numbits - stream->position; } /** @@ -98,14 +98,14 @@ int bitsLeft(BitstreamIn_t *stream) { * @param stream * @return Number of bits stored in stream */ -void x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest) { +void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest) { while (len--) { dest[len] = (uint8_t) n; n >>= 8; } } -uint64_t x_bytes_to_num(uint8_t *src, size_t len) { +uint64_t loclass_x_bytes_to_num(uint8_t *src, size_t len) { uint64_t num = 0; while (len--) { num = (num << 8) | (*src); @@ -114,24 +114,24 @@ uint64_t x_bytes_to_num(uint8_t *src, size_t len) { return num; } -uint8_t reversebytes(uint8_t b) { +uint8_t loclass_reversebytes(uint8_t b) { b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; b = (b & 0xCC) >> 2 | (b & 0x33) << 2; b = (b & 0xAA) >> 1 | (b & 0x55) << 1; return b; } -void reverse_arraybytes(uint8_t *arr, size_t len) { +void loclass_reverse_arraybytes(uint8_t *arr, size_t len) { uint8_t i; for (i = 0; i < len ; i++) { - arr[i] = reversebytes(arr[i]); + arr[i] = loclass_reversebytes(arr[i]); } } -void reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len) { +void loclass_reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len) { uint8_t i; for (i = 0; i < len ; i++) { - dest[i] = reversebytes(arr[i]); + dest[i] = loclass_reversebytes(arr[i]); } } diff --git a/lib/loclass/optimized_cipherutils.h b/lib/loclass/optimized_cipherutils.h index 07bb012f..cb9d2724 100644 --- a/lib/loclass/optimized_cipherutils.h +++ b/lib/loclass/optimized_cipherutils.h @@ -42,23 +42,23 @@ typedef struct { uint8_t *buffer; uint8_t numbits; uint8_t position; -} BitstreamIn_t; +} LoclassBitstreamIn_t; typedef struct { uint8_t *buffer; uint8_t numbits; uint8_t position; -} BitstreamOut_t; +} LoclassBitstreamOut_t; -bool headBit(BitstreamIn_t *stream); -bool tailBit(BitstreamIn_t *stream); -void pushBit(BitstreamOut_t *stream, bool bit); -int bitsLeft(BitstreamIn_t *stream); +bool loclass_headBit(LoclassBitstreamIn_t *stream); +bool loclass_tailBit(LoclassBitstreamIn_t *stream); +void loclass_pushBit(LoclassBitstreamOut_t *stream, bool bit); +int loclass_bitsLeft(LoclassBitstreamIn_t *stream); -void push6bits(BitstreamOut_t *stream, uint8_t bits); -void x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest); -uint64_t x_bytes_to_num(uint8_t *src, size_t len); -uint8_t reversebytes(uint8_t b); -void reverse_arraybytes(uint8_t *arr, size_t len); -void reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len); +void loclass_push6bits(LoclassBitstreamOut_t *stream, uint8_t bits); +void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t *dest); +uint64_t loclass_x_bytes_to_num(uint8_t *src, size_t len); +uint8_t loclass_reversebytes(uint8_t b); +void loclass_reverse_arraybytes(uint8_t *arr, size_t len); +void loclass_reverse_arraycopy(uint8_t *arr, uint8_t *dest, size_t len); #endif // CIPHERUTILS_H diff --git a/lib/loclass/optimized_elite.c b/lib/loclass/optimized_elite.c index 8940a51e..fc1e5d74 100644 --- a/lib/loclass/optimized_elite.c +++ b/lib/loclass/optimized_elite.c @@ -43,7 +43,7 @@ * @brief Permutes a key from standard NIST format to Iclass specific format * from http://www.proxmark.org/forum/viewtopic.php?pid=11220#p11220 * - * If you permute [6c 8d 44 f9 2a 2d 01 bf] you get [8a 0d b9 88 bb a7 90 ea] as shown below. + * If you loclass_permute [6c 8d 44 f9 2a 2d 01 bf] you get [8a 0d b9 88 bb a7 90 ea] as shown below. * * 1 0 1 1 1 1 1 1 bf * 0 0 0 0 0 0 0 1 01 @@ -60,7 +60,7 @@ * @param key * @param dest */ -void permutekey(const uint8_t key[8], uint8_t dest[8]) { +void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]) { int i; for (i = 0 ; i < 8 ; i++) { dest[i] = (((key[7] & (0x80 >> i)) >> (7 - i)) << 7) | @@ -75,11 +75,11 @@ void permutekey(const uint8_t key[8], uint8_t dest[8]) { } /** * Permutes a key from iclass specific format to NIST format - * @brief permutekey_rev + * @brief loclass_permutekey_rev * @param key * @param dest */ -void permutekey_rev(const uint8_t key[8], uint8_t dest[8]) { +void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]) { int i; for (i = 0 ; i < 8 ; i++) { dest[7 - i] = (((key[0] & (0x80 >> i)) >> (7 - i)) << 7) | @@ -94,32 +94,32 @@ void permutekey_rev(const uint8_t key[8], uint8_t dest[8]) { } /** - * Helper function for hash1 - * @brief rr + * Helper function for loclass_hash1 + * @brief loclass_rr * @param val * @return */ -static uint8_t rr(uint8_t val) { +static uint8_t loclass_rr(uint8_t val) { return val >> 1 | ((val & 1) << 7); } /** - * Helper function for hash1 + * Helper function for loclass_hash1 * @brief rl * @param val * @return */ -static uint8_t rl(uint8_t val) { +static uint8_t loclass_rl(uint8_t val) { return val << 1 | ((val & 0x80) >> 7); } /** - * Helper function for hash1 - * @brief swap + * Helper function for loclass_hash1 + * @brief loclass_swap * @param val * @return */ -static uint8_t swap(uint8_t val) { +static uint8_t loclass_swap(uint8_t val) { return ((val >> 4) & 0xFF) | ((val & 0xFF) << 4); } @@ -129,15 +129,15 @@ static uint8_t swap(uint8_t val) { * @param csn the CSN used * @param k output */ -void hash1(const uint8_t csn[], uint8_t k[]) { +void loclass_hash1(const uint8_t csn[], uint8_t k[]) { k[0] = csn[0] ^ csn[1] ^ csn[2] ^ csn[3] ^ csn[4] ^ csn[5] ^ csn[6] ^ csn[7]; k[1] = csn[0] + csn[1] + csn[2] + csn[3] + csn[4] + csn[5] + csn[6] + csn[7]; - k[2] = rr(swap(csn[2] + k[1])); - k[3] = rl(swap(csn[3] + k[0])); - k[4] = ~rr(csn[4] + k[2]) + 1; - k[5] = ~rl(csn[5] + k[3]) + 1; - k[6] = rr(csn[6] + (k[4] ^ 0x3c)); - k[7] = rl(csn[7] + (k[5] ^ 0xc3)); + k[2] = loclass_rr(loclass_swap(csn[2] + k[1])); + k[3] = loclass_rl(loclass_swap(csn[3] + k[0])); + k[4] = ~loclass_rr(csn[4] + k[2]) + 1; + k[5] = ~loclass_rl(csn[5] + k[3]) + 1; + k[6] = loclass_rr(csn[6] + (k[4] ^ 0x3c)); + k[7] = loclass_rl(csn[7] + (k[5] ^ 0xc3)); k[7] &= 0x7F; k[6] &= 0x7F; @@ -149,42 +149,42 @@ void hash1(const uint8_t csn[], uint8_t k[]) { k[0] &= 0x7F; } /** -Definition 14. Define the rotate key function rk : (F 82 ) 8 × N → (F 82 ) 8 as -rk(x [0] . . . x [7] , 0) = x [0] . . . x [7] -rk(x [0] . . . x [7] , n + 1) = rk(rl(x [0] ) . . . rl(x [7] ), n) +Definition 14. Define the rotate key function loclass_rk : (F 82 ) 8 × N → (F 82 ) 8 as +loclass_rk(x [0] . . . x [7] , 0) = x [0] . . . x [7] +loclass_rk(x [0] . . . x [7] , n + 1) = loclass_rk(loclass_rl(x [0] ) . . . loclass_rl(x [7] ), n) **/ -static void rk(uint8_t *key, uint8_t n, uint8_t *outp_key) { +static void loclass_rk(uint8_t *key, uint8_t n, uint8_t *outp_key) { memcpy(outp_key, key, 8); uint8_t j; while (n-- > 0) { for (j = 0; j < 8 ; j++) - outp_key[j] = rl(outp_key[j]); + outp_key[j] = loclass_rl(outp_key[j]); } return; } -static mbedtls_des_context ctx_enc; -static mbedtls_des_context ctx_dec; +static mbedtls_des_context loclass_ctx_enc; +static mbedtls_des_context loclass_ctx_dec; -static void desdecrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { +static void loclass_desdecrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { uint8_t key_std_format[8] = {0}; - permutekey_rev(iclass_key, key_std_format); - mbedtls_des_setkey_dec(&ctx_dec, key_std_format); - mbedtls_des_crypt_ecb(&ctx_dec, input, output); + loclass_permutekey_rev(iclass_key, key_std_format); + mbedtls_des_setkey_dec(&loclass_ctx_dec, key_std_format); + mbedtls_des_crypt_ecb(&loclass_ctx_dec, input, output); } -static void desencrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { +static void loclass_desencrypt_iclass(uint8_t *iclass_key, uint8_t *input, uint8_t *output) { uint8_t key_std_format[8] = {0}; - permutekey_rev(iclass_key, key_std_format); - mbedtls_des_setkey_enc(&ctx_enc, key_std_format); - mbedtls_des_crypt_ecb(&ctx_enc, input, output); + loclass_permutekey_rev(iclass_key, key_std_format); + mbedtls_des_setkey_enc(&loclass_ctx_enc, key_std_format); + mbedtls_des_crypt_ecb(&loclass_ctx_enc, input, output); } /** * @brief Insert uint8_t[8] custom master key to calculate hash2 and return key_select. * @param key unpermuted custom key - * @param hash1 hash1 - * @param key_sel output key_sel=h[hash1[i]] + * @param loclass_hash1 loclass_hash1 + * @param key_sel output key_sel=h[loclass_hash1[i]] */ void hash2(uint8_t *key64, uint8_t *outp_keytable) { /** @@ -211,18 +211,18 @@ void hash2(uint8_t *key64, uint8_t *outp_keytable) { key64_negated[i] = ~key64[i]; // Once again, key is on iclass-format - desencrypt_iclass(key64, key64_negated, z[0]); + loclass_desencrypt_iclass(key64, key64_negated, z[0]); uint8_t y[8][8] = {{0}, {0}}; // y[0]=DES_dec(z[0],~key) // Once again, key is on iclass-format - desdecrypt_iclass(z[0], key64_negated, y[0]); + loclass_desdecrypt_iclass(z[0], key64_negated, y[0]); for (i = 1; i < 8; i++) { - rk(key64, i, temp_output); - desdecrypt_iclass(temp_output, z[i - 1], z[i]); - desencrypt_iclass(temp_output, y[i - 1], y[i]); + loclass_rk(key64, i, temp_output); + loclass_desdecrypt_iclass(temp_output, z[i - 1], z[i]); + loclass_desencrypt_iclass(temp_output, y[i - 1], y[i]); } if (outp_keytable != NULL) { diff --git a/lib/loclass/optimized_elite.h b/lib/loclass/optimized_elite.h index f1b41d54..9bc30e57 100644 --- a/lib/loclass/optimized_elite.h +++ b/lib/loclass/optimized_elite.h @@ -38,21 +38,21 @@ #include #include -void permutekey(const uint8_t key[8], uint8_t dest[8]); +void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]); /** * Permutes a key from iclass specific format to NIST format - * @brief permutekey_rev + * @brief loclass_permutekey_rev * @param key * @param dest */ -void permutekey_rev(const uint8_t key[8], uint8_t dest[8]); +void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]); /** * Hash1 takes CSN as input, and determines what bytes in the keytable will be used * when constructing the K_sel. * @param csn the CSN used * @param k output */ -void hash1(const uint8_t *csn, uint8_t *k); -void hash2(uint8_t *key64, uint8_t *outp_keytable); +void loclass_hash1(const uint8_t *csn, uint8_t *k); +void loclass_hash2(uint8_t *key64, uint8_t *outp_keytable); #endif diff --git a/lib/loclass/optimized_ikeys.c b/lib/loclass/optimized_ikeys.c index 8e093feb..9531c16c 100644 --- a/lib/loclass/optimized_ikeys.c +++ b/lib/loclass/optimized_ikeys.c @@ -39,18 +39,18 @@ From "Dismantling iclass": algorithm intends to circumvent weaknesses in the cipher by preventing the usage of certain ‘weak’ keys. In order to compute a diversified key, the iClass reader first encrypts the card identity id with the master key K, using single - DES. The resulting ciphertext is then input to a function called hash0 which + DES. The resulting ciphertext is then input to a function called loclass_hash0 which outputs the diversified key k. - k = hash0(DES enc (id, K)) + k = loclass_hash0(DES enc (id, K)) Here the DES encryption of id with master key K outputs a cryptogram c of 64 bits. These 64 bits are divided as c = x, y, z [0] , . . . , z [7] ∈ F 82 × F 82 × (F 62 ) 8 - which is used as input to the hash0 function. This function introduces some + which is used as input to the loclass_hash0 function. This function introduces some obfuscation by performing a number of permutations, complement and modulo operations, see Figure 2.5. Besides that, it checks for and removes patterns like similar key bytes, which could produce a strong bias in the cipher. Finally, the - output of hash0 is the diversified card key k = k [0] , . . . , k [7] ∈ (F 82 ) 8 . + output of loclass_hash0 is the diversified card key k = k [0] , . . . , k [7] ∈ (F 82 ) 8 . **/ #include "optimized_ikeys.h" @@ -61,7 +61,7 @@ From "Dismantling iclass": #include #include "optimized_cipherutils.h" -static uint8_t pi[35] = { +static const uint8_t loclass_pi[35] = { 0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D, 0x2E, 0x33, 0x35, 0x39, 0x36, 0x3A, 0x3C, 0x47, 0x4B, 0x4D, 0x4E, 0x53, 0x55, 0x56, 0x59, 0x5A, @@ -69,8 +69,6 @@ static uint8_t pi[35] = { 0x72, 0x74, 0x78 }; -static mbedtls_des_context ctx_enc; - /** * @brief The key diversification algorithm uses 6-bit bytes. * This implementation uses 64 bit uint to pack seven of them into one @@ -82,7 +80,7 @@ static mbedtls_des_context ctx_enc; * @param n bitnumber * @return */ -static uint8_t getSixBitByte(uint64_t c, int n) { +static uint8_t loclass_getSixBitByte(uint64_t c, int n) { return (c >> (42 - 6 * n)) & 0x3F; } @@ -92,7 +90,7 @@ static uint8_t getSixBitByte(uint64_t c, int n) { * @param z the value to place there * @param n bitnumber. */ -static void pushbackSixBitByte(uint64_t *c, uint8_t z, int n) { +static void loclass_pushbackSixBitByte(uint64_t *c, uint8_t z, int n) { //0x XXXX YYYY ZZZZ ZZZZ ZZZZ // ^z0 ^z7 //z0: 1111 1100 0000 0000 @@ -117,16 +115,16 @@ static void pushbackSixBitByte(uint64_t *c, uint8_t z, int n) { * @param c * @return */ -static uint64_t swapZvalues(uint64_t c) { +static uint64_t loclass_swapZvalues(uint64_t c) { uint64_t newz = 0; - pushbackSixBitByte(&newz, getSixBitByte(c, 0), 7); - pushbackSixBitByte(&newz, getSixBitByte(c, 1), 6); - pushbackSixBitByte(&newz, getSixBitByte(c, 2), 5); - pushbackSixBitByte(&newz, getSixBitByte(c, 3), 4); - pushbackSixBitByte(&newz, getSixBitByte(c, 4), 3); - pushbackSixBitByte(&newz, getSixBitByte(c, 5), 2); - pushbackSixBitByte(&newz, getSixBitByte(c, 6), 1); - pushbackSixBitByte(&newz, getSixBitByte(c, 7), 0); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 0), 7); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 1), 6); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 2), 5); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 3), 4); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 4), 3); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 5), 2); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 6), 1); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 7), 0); newz |= (c & 0xFFFF000000000000); return newz; } @@ -134,58 +132,58 @@ static uint64_t swapZvalues(uint64_t c) { /** * @return 4 six-bit bytes chunked into a uint64_t,as 00..00a0a1a2a3 */ -static uint64_t ck(int i, int j, uint64_t z) { +static uint64_t loclass_ck(int i, int j, uint64_t z) { if (i == 1 && j == -1) { - // ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] + // loclass_ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] return z; } else if (j == -1) { - // ck(i, −1, z [0] . . . z [3] ) = ck(i − 1, i − 2, z [0] . . . z [3] ) - return ck(i - 1, i - 2, z); + // loclass_ck(i, −1, z [0] . . . z [3] ) = loclass_ck(i − 1, i − 2, z [0] . . . z [3] ) + return loclass_ck(i - 1, i - 2, z); } - if (getSixBitByte(z, i) == getSixBitByte(z, j)) { - //ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ) + if (loclass_getSixBitByte(z, i) == loclass_getSixBitByte(z, j)) { + //loclass_ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ) uint64_t newz = 0; int c; for (c = 0; c < 4; c++) { - uint8_t val = getSixBitByte(z, c); + uint8_t val = loclass_getSixBitByte(z, c); if (c == i) - pushbackSixBitByte(&newz, j, c); + loclass_pushbackSixBitByte(&newz, j, c); else - pushbackSixBitByte(&newz, val, c); + loclass_pushbackSixBitByte(&newz, val, c); } - return ck(i, j - 1, newz); + return loclass_ck(i, j - 1, newz); } else { - return ck(i, j - 1, z); + return loclass_ck(i, j - 1, z); } } /** Definition 8. Let the function check : (F 62 ) 8 → (F 62 ) 8 be defined as - check(z [0] . . . z [7] ) = ck(3, 2, z [0] . . . z [3] ) · ck(3, 2, z [4] . . . z [7] ) + check(z [0] . . . z [7] ) = loclass_ck(3, 2, z [0] . . . z [3] ) · loclass_ck(3, 2, z [4] . . . z [7] ) - where ck : N × N × (F 62 ) 4 → (F 62 ) 4 is defined as + where loclass_ck : N × N × (F 62 ) 4 → (F 62 ) 4 is defined as - ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] - ck(i, −1, z [0] . . . z [3] ) = ck(i − 1, i − 2, z [0] . . . z [3] ) - ck(i, j, z [0] . . . z [3] ) = - ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ), if z [i] = z [j] ; - ck(i, j − 1, z [0] . . . z [3] ), otherwise + loclass_ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] + loclass_ck(i, −1, z [0] . . . z [3] ) = loclass_ck(i − 1, i − 2, z [0] . . . z [3] ) + loclass_ck(i, j, z [0] . . . z [3] ) = + loclass_ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ), if z [i] = z [j] ; + loclass_ck(i, j − 1, z [0] . . . z [3] ), otherwise otherwise. **/ -static uint64_t check(uint64_t z) { +static uint64_t loclass_check(uint64_t z) { //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] - // ck(3, 2, z [0] . . . z [3] ) - uint64_t ck1 = ck(3, 2, z); + // loclass_ck(3, 2, z [0] . . . z [3] ) + uint64_t ck1 = loclass_ck(3, 2, z); - // ck(3, 2, z [4] . . . z [7] ) - uint64_t ck2 = ck(3, 2, z << 24); + // loclass_ck(3, 2, z [4] . . . z [7] ) + uint64_t ck2 = loclass_ck(3, 2, z << 24); - //The ck function will place the values + //The loclass_ck function will place the values // in the middle of z. ck1 &= 0x00000000FFFFFF000000; ck2 &= 0x00000000FFFFFF000000; @@ -193,28 +191,28 @@ static uint64_t check(uint64_t z) { return ck1 | ck2 >> 24; } -static void permute(BitstreamIn_t *p_in, uint64_t z, int l, int r, BitstreamOut_t *out) { - if (bitsLeft(p_in) == 0) +static void loclass_permute(LoclassBitstreamIn_t *p_in, uint64_t z, int l, int r, LoclassBitstreamOut_t *out) { + if (loclass_bitsLeft(p_in) == 0) return; - bool pn = tailBit(p_in); + bool pn = loclass_tailBit(p_in); if (pn) { // pn = 1 - uint8_t zl = getSixBitByte(z, l); + uint8_t zl = loclass_getSixBitByte(z, l); - push6bits(out, zl + 1); - permute(p_in, z, l + 1, r, out); + loclass_push6bits(out, zl + 1); + loclass_permute(p_in, z, l + 1, r, out); } else { // otherwise - uint8_t zr = getSixBitByte(z, r); + uint8_t zr = loclass_getSixBitByte(z, r); - push6bits(out, zr); - permute(p_in, z, l, r + 1, out); + loclass_push6bits(out, zr); + loclass_permute(p_in, z, l, r + 1, out); } } /** * @brief - *Definition 11. Let the function hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as - * hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where + *Definition 11. Let the function loclass_hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as + * loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where * z'[i] = (z[i] mod (63-i)) + i i = 0...3 * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 * ẑ = check(z'); @@ -222,8 +220,8 @@ static void permute(BitstreamIn_t *p_in, uint64_t z, int l, int r, BitstreamOut_ * @param k this is where the diversified key is put (should be 8 bytes) * @return */ -void hash0(uint64_t c, uint8_t k[8]) { - c = swapZvalues(c); +void loclass_hash0(uint64_t c, uint8_t k[8]) { + c = loclass_swapZvalues(c); //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] // x = 8 bits @@ -234,30 +232,30 @@ void hash0(uint64_t c, uint8_t k[8]) { uint64_t zP = 0; for (int n = 0; n < 4 ; n++) { - uint8_t zn = getSixBitByte(c, n); - uint8_t zn4 = getSixBitByte(c, n + 4); + uint8_t zn = loclass_getSixBitByte(c, n); + uint8_t zn4 = loclass_getSixBitByte(c, n + 4); uint8_t _zn = (zn % (63 - n)) + n; uint8_t _zn4 = (zn4 % (64 - n)) + n; - pushbackSixBitByte(&zP, _zn, n); - pushbackSixBitByte(&zP, _zn4, n + 4); + loclass_pushbackSixBitByte(&zP, _zn, n); + loclass_pushbackSixBitByte(&zP, _zn4, n + 4); } - uint64_t zCaret = check(zP); - uint8_t p = pi[x % 35]; + uint64_t zCaret = loclass_check(zP); + uint8_t p = loclass_pi[x % 35]; if (x & 1) //Check if x7 is 1 p = ~p; - BitstreamIn_t p_in = { &p, 8, 0 }; + LoclassBitstreamIn_t p_in = { &p, 8, 0 }; uint8_t outbuffer[] = {0, 0, 0, 0, 0, 0, 0, 0}; - BitstreamOut_t out = {outbuffer, 0, 0}; - permute(&p_in, zCaret, 0, 4, &out); //returns 48 bits? or 6 8-bytes + LoclassBitstreamOut_t out = {outbuffer, 0, 0}; + loclass_permute(&p_in, zCaret, 0, 4, &out); //returns 48 bits? or 6 8-bytes //Out is now a buffer containing six-bit bytes, should be 48 bits // if all went well //Shift z-values down onto the lower segment - uint64_t zTilde = x_bytes_to_num(outbuffer, sizeof(outbuffer)); + uint64_t zTilde = loclass_x_bytes_to_num(outbuffer, sizeof(outbuffer)); zTilde >>= 16; @@ -274,7 +272,7 @@ void hash0(uint64_t c, uint8_t k[8]) { // First, place y(7-i) leftmost in k k[i] |= (y << (7 - i)) & 0x80 ; - uint8_t zTilde_i = getSixBitByte(zTilde, i); + uint8_t zTilde_i = loclass_getSixBitByte(zTilde, i); // zTildeI is now on the form 00XXXXXX // with one leftshift, it'll be // 0XXXXXX0 @@ -304,18 +302,20 @@ void hash0(uint64_t c, uint8_t k[8]) { * @param key * @param div_key */ -void diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key) { +void loclass_diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key) { + mbedtls_des_context loclass_ctx_enc; + // Prepare the DES key - mbedtls_des_setkey_enc(&ctx_enc, key); + mbedtls_des_setkey_enc(&loclass_ctx_enc, key); uint8_t crypted_csn[8] = {0}; // Calculate DES(CSN, KEY) - mbedtls_des_crypt_ecb(&ctx_enc, csn, crypted_csn); + mbedtls_des_crypt_ecb(&loclass_ctx_enc, csn, crypted_csn); //Calculate HASH0(DES)) - uint64_t c_csn = x_bytes_to_num(crypted_csn, sizeof(crypted_csn)); + uint64_t c_csn = loclass_x_bytes_to_num(crypted_csn, sizeof(crypted_csn)); - hash0(c_csn, div_key); + loclass_hash0(c_csn, div_key); } diff --git a/lib/loclass/optimized_ikeys.h b/lib/loclass/optimized_ikeys.h index e366bb6e..e960b8be 100644 --- a/lib/loclass/optimized_ikeys.h +++ b/lib/loclass/optimized_ikeys.h @@ -39,8 +39,8 @@ /** * @brief - *Definition 11. Let the function hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as - * hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where + *Definition 11. Let the function loclass_hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as + * loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where * z'[i] = (z[i] mod (63-i)) + i i = 0...3 * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 * ẑ = check(z'); @@ -48,7 +48,7 @@ * @param k this is where the diversified key is put (should be 8 bytes) * @return */ -void hash0(uint64_t c, uint8_t k[8]); +void loclass_hash0(uint64_t c, uint8_t k[8]); /** * @brief Performs Elite-class key diversification * @param csn @@ -56,7 +56,7 @@ void hash0(uint64_t c, uint8_t k[8]); * @param div_key */ -void diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key); +void loclass_diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key); /** * @brief Permutes a key from standard NIST format to Iclass specific format * @param key From 41bd1fb9a54acafc13d7c2121bdf4e1c2fe39280 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Sun, 10 Jul 2022 05:46:46 -0700 Subject: [PATCH 25/26] Save picopass as picopass or, for 26bit, as lfrfid (#1380) --- applications/picopass/picopass.c | 31 ++++- applications/picopass/picopass_device.c | 115 +++++++++++++++++- applications/picopass/picopass_device.h | 29 ++++- applications/picopass/picopass_i.h | 13 +- applications/picopass/picopass_worker.c | 7 -- .../scenes/picopass_scene_card_menu.c | 65 ++++++++++ .../picopass/scenes/picopass_scene_config.h | 4 + .../scenes/picopass_scene_read_card_success.c | 13 ++ .../scenes/picopass_scene_save_name.c | 84 +++++++++++++ .../scenes/picopass_scene_save_success.c | 47 +++++++ .../scenes/picopass_scene_saved_menu.c | 35 ++++++ 11 files changed, 430 insertions(+), 13 deletions(-) create mode 100644 applications/picopass/scenes/picopass_scene_card_menu.c create mode 100644 applications/picopass/scenes/picopass_scene_save_name.c create mode 100644 applications/picopass/scenes/picopass_scene_save_success.c create mode 100644 applications/picopass/scenes/picopass_scene_saved_menu.c diff --git a/applications/picopass/picopass.c b/applications/picopass/picopass.c index fb9e6b0d..19189548 100644 --- a/applications/picopass/picopass.c +++ b/applications/picopass/picopass.c @@ -56,6 +56,13 @@ Picopass* picopass_alloc() { view_dispatcher_add_view( picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup)); + // Text Input + picopass->text_input = text_input_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, + PicopassViewTextInput, + text_input_get_view(picopass->text_input)); + // Custom Widget picopass->widget = widget_alloc(); view_dispatcher_add_view( @@ -67,6 +74,10 @@ Picopass* picopass_alloc() { void picopass_free(Picopass* picopass) { furi_assert(picopass); + // Picopass device + picopass_device_free(picopass->dev); + picopass->dev = NULL; + // Submenu view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu); submenu_free(picopass->submenu); @@ -75,6 +86,10 @@ void picopass_free(Picopass* picopass) { view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); popup_free(picopass->popup); + // TextInput + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput); + text_input_free(picopass->text_input); + // Custom Widget view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget); widget_free(picopass->widget); @@ -97,12 +112,22 @@ void picopass_free(Picopass* picopass) { furi_record_close("notification"); picopass->notifications = NULL; - picopass_device_free(picopass->dev); - picopass->dev = NULL; - free(picopass); } +void picopass_text_store_set(Picopass* picopass, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(picopass->text_store, sizeof(picopass->text_store), text, args); + + va_end(args); +} + +void picopass_text_store_clear(Picopass* picopass) { + memset(picopass->text_store, 0, sizeof(picopass->text_store)); +} + static const NotificationSequence picopass_sequence_blink_start_blue = { &message_blink_start_10, &message_blink_set_color_blue, diff --git a/applications/picopass/picopass_device.c b/applications/picopass/picopass_device.c index 802c24e4..8cce5288 100644 --- a/applications/picopass/picopass_device.c +++ b/applications/picopass/picopass_device.c @@ -5,6 +5,9 @@ #define TAG "PicopassDevice" +static const char* picopass_file_header = "Flipper Picopass device"; +static const uint32_t picopass_file_version = 1; + PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); picopass_dev->storage = furi_record_open("storage"); @@ -12,6 +15,116 @@ PicopassDevice* picopass_device_alloc() { return picopass_dev; } +void picopass_device_set_name(PicopassDevice* dev, const char* name) { + furi_assert(dev); + + strlcpy(dev->dev_name, name, PICOPASS_DEV_NAME_MAX_LEN); +} + +static bool picopass_device_save_file( + PicopassDevice* dev, + const char* dev_name, + const char* folder, + const char* extension, + bool use_load_path) { + furi_assert(dev); + + bool saved = false; + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + PicopassPacs* pacs = &dev->dev_data.pacs; + ApplicationArea* AA1 = &dev->dev_data.AA1; + string_t temp_str; + string_init(temp_str); + + do { + if(use_load_path && !string_empty_p(dev->load_path)) { + // Get directory name + path_extract_dirname(string_get_cstr(dev->load_path), temp_str); + // Create picopass directory if necessary + if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break; + // Make path to file to save + string_cat_printf(temp_str, "/%s%s", dev_name, extension); + } else { + // Create picopass directory if necessary + if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break; + // First remove picopass device file if it was saved + string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + } + // Open file + if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; + + if(dev->format == PicopassDeviceSaveFormatHF) { + // Write header + if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version)) + break; + if(pacs->record.valid) { + if(!flipper_format_write_uint32( + file, "Facility Code", (uint32_t*)&pacs->record.FacilityCode, 1)) + break; + if(!flipper_format_write_uint32( + file, "Card Number", (uint32_t*)&pacs->record.CardNumber, 1)) + break; + if(!flipper_format_write_hex( + file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN)) + break; + if(!flipper_format_write_hex(file, "PIN", pacs->pin0, PICOPASS_BLOCK_LEN)) break; + if(!flipper_format_write_hex(file, "PIN(cont.)", pacs->pin1, PICOPASS_BLOCK_LEN)) + break; + + if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break; + // TODO: Save CSN, CFG, AA1, etc + bool block_saved = true; + for(size_t i = 0; i < 4; i++) { + string_printf(temp_str, "Block %d", i + 6); + if(!flipper_format_write_hex( + file, + string_get_cstr(temp_str), + AA1->block[i].data, + PICOPASS_BLOCK_LEN)) { + block_saved = false; + break; + } + } + if(!block_saved) break; + if(!flipper_format_write_comment_cstr(file, "This is currently incomplete")) break; + } + } else if(dev->format == PicopassDeviceSaveFormatLF) { + const char* lf_header = "Flipper RFID key"; + // Write header + if(!flipper_format_write_header_cstr(file, lf_header, 1)) break; + if(!flipper_format_write_comment_cstr( + file, + "This was generated from the Picopass plugin and may not match current lfrfid")) + break; + // When lfrfid supports more formats, update this + if(!flipper_format_write_string_cstr(file, "Key type", "H10301")) break; + uint8_t H10301[3] = {0}; + H10301[0] = pacs->record.FacilityCode; + H10301[1] = pacs->record.CardNumber >> 8; + H10301[2] = pacs->record.CardNumber & 0x00FF; + if(!flipper_format_write_hex(file, "Data", H10301, 3)) break; + } + saved = true; + } while(0); + + if(!saved) { + dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); + } + string_clear(temp_str); + flipper_format_free(file); + return saved; +} + +bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { + if(dev->format == PicopassDeviceSaveFormatHF) { + return picopass_device_save_file( + dev, dev_name, PICOPASS_APP_FOLDER, PICOPASS_APP_EXTENSION, true); + } else if(dev->format == PicopassDeviceSaveFormatLF) { + return picopass_device_save_file(dev, dev_name, "/any/lfrfid", ".rfid", true); + } + return false; +} + void picopass_device_clear(PicopassDevice* dev) { furi_assert(dev); @@ -24,10 +137,10 @@ void picopass_device_free(PicopassDevice* picopass_dev) { picopass_device_clear(picopass_dev); furi_record_close("storage"); furi_record_close("dialogs"); + string_clear(picopass_dev->load_path); free(picopass_dev); } void picopass_device_data_clear(PicopassDeviceData* dev_data) { - FURI_LOG_D(TAG, "picopass_device_data_clear"); memset(&dev_data->AA1, 0, sizeof(ApplicationArea)); } diff --git a/applications/picopass/picopass_device.h b/applications/picopass/picopass_device.h index af4c07b9..a0f7a667 100644 --- a/applications/picopass/picopass_device.h +++ b/applications/picopass/picopass_device.h @@ -7,6 +7,26 @@ #include +#define PICOPASS_DEV_NAME_MAX_LEN 22 +#define PICOPASS_READER_DATA_MAX_SIZE 64 +#define PICOPASS_BLOCK_LEN 8 + +#define PICOPASS_APP_FOLDER "/any/picopass" +#define PICOPASS_APP_EXTENSION ".picopass" +#define PICOPASS_APP_SHADOW_EXTENSION ".pas" + +typedef enum { + PicopassDeviceEncryptionUnknown = 0, + PicopassDeviceEncryptionNone = 0x14, + PicopassDeviceEncryptionDES = 0x15, + PicopassDeviceEncryption3DES = 0x17, +} PicopassEncryption; + +typedef enum { + PicopassDeviceSaveFormatHF, + PicopassDeviceSaveFormatLF, +} PicopassDeviceSaveFormat; + typedef struct { bool valid; uint8_t bitLength; @@ -16,7 +36,7 @@ typedef struct { typedef struct { bool biometrics; - uint8_t encryption; + PicopassEncryption encryption; uint8_t credential[8]; uint8_t pin0[8]; uint8_t pin1[8]; @@ -32,12 +52,19 @@ typedef struct { Storage* storage; DialogsApp* dialogs; PicopassDeviceData dev_data; + char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1]; + string_t load_path; + PicopassDeviceSaveFormat format; } PicopassDevice; PicopassDevice* picopass_device_alloc(); void picopass_device_free(PicopassDevice* picopass_dev); +void picopass_device_set_name(PicopassDevice* dev, const char* name); + +bool picopass_device_save(PicopassDevice* dev, const char* dev_name); + void picopass_device_data_clear(PicopassDeviceData* dev_data); void picopass_device_clear(PicopassDevice* dev); diff --git a/applications/picopass/picopass_i.h b/applications/picopass/picopass_i.h index 04fd6876..dbf4f8be 100644 --- a/applications/picopass/picopass_i.h +++ b/applications/picopass/picopass_i.h @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -23,6 +24,8 @@ #include #include +#define PICOPASS_TEXT_STORE_SIZE 128 + enum PicopassCustomEvent { // Reserve first 100 events for button types and indexes, starting from 0 PicopassCustomEventReserved = 100, @@ -31,7 +34,6 @@ enum PicopassCustomEvent { PicopassCustomEventWorkerExit, PicopassCustomEventByteInputDone, PicopassCustomEventTextInputDone, - PicopassCustomEventDictAttackDone, }; typedef enum { @@ -47,20 +49,29 @@ struct Picopass { SceneManager* scene_manager; PicopassDevice* dev; + char text_store[PICOPASS_TEXT_STORE_SIZE + 1]; + string_t text_box_store; + // Common Views Submenu* submenu; Popup* popup; + TextInput* text_input; Widget* widget; }; typedef enum { PicopassViewMenu, PicopassViewPopup, + PicopassViewTextInput, PicopassViewWidget, } PicopassView; Picopass* picopass_alloc(); +void picopass_text_store_set(Picopass* picopass, const char* text, ...); + +void picopass_text_store_clear(Picopass* picopass); + void picopass_blink_start(Picopass* picopass); void picopass_blink_stop(Picopass* picopass); diff --git a/applications/picopass/picopass_worker.c b/applications/picopass/picopass_worker.c index 8e19b3c4..40fe4448 100644 --- a/applications/picopass/picopass_worker.c +++ b/applications/picopass/picopass_worker.c @@ -114,8 +114,6 @@ void picopass_worker_start( furi_assert(picopass_worker); furi_assert(dev_data); - FURI_LOG_D(TAG, "picopass_worker_start"); - picopass_worker->callback = callback; picopass_worker->context = context; picopass_worker->dev_data = dev_data; @@ -258,10 +256,8 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { ReturnCode err; while(picopass_worker->state == PicopassWorkerStateDetect) { - FURI_LOG_D(TAG, "PicopassWorkerStateDetect"); if(picopass_detect_card(1000) == ERR_NONE) { // Process first found device - FURI_LOG_D(TAG, "picopass_read_card"); err = picopass_read_card(AA1); if(err != ERR_NONE) { FURI_LOG_E(TAG, "picopass_read_card error %d", err); @@ -277,21 +273,18 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { FURI_LOG_E(TAG, "decrypt error %d", err); break; } - FURI_LOG_D(TAG, "Decrypted 7"); err = picopass_worker_decrypt(AA1->block[2].data, pacs->pin0); if(err != ERR_NONE) { FURI_LOG_E(TAG, "decrypt error %d", err); break; } - FURI_LOG_D(TAG, "Decrypted 8"); err = picopass_worker_decrypt(AA1->block[3].data, pacs->pin1); if(err != ERR_NONE) { FURI_LOG_E(TAG, "decrypt error %d", err); break; } - FURI_LOG_D(TAG, "Decrypted 9"); } else if(pacs->encryption == 0x14) { FURI_LOG_D(TAG, "No Encryption"); memcpy(pacs->credential, AA1->block[1].data, RFAL_PICOPASS_MAX_BLOCK_LEN); diff --git a/applications/picopass/scenes/picopass_scene_card_menu.c b/applications/picopass/scenes/picopass_scene_card_menu.c new file mode 100644 index 00000000..a424b919 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_card_menu.c @@ -0,0 +1,65 @@ +#include "../picopass_i.h" + +enum SubmenuIndex { + SubmenuIndexSave, + SubmenuIndexSaveAsLF, +}; + +void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) { + Picopass* picopass = context; + + view_dispatcher_send_custom_event(picopass->view_dispatcher, index); +} + +void picopass_scene_card_menu_on_enter(void* context) { + Picopass* picopass = context; + Submenu* submenu = picopass->submenu; + + submenu_add_item( + submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass); + if(picopass->dev->dev_data.pacs.record.valid) { + submenu_add_item( + submenu, + "Save as LF", + SubmenuIndexSaveAsLF, + picopass_scene_card_menu_submenu_callback, + picopass); + } + submenu_set_selected_item( + picopass->submenu, + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneCardMenu)); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); +} + +bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSave) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSave); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); + picopass->dev->format = PicopassDeviceSaveFormatHF; + consumed = true; + } else if(event.event == SubmenuIndexSaveAsLF) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSaveAsLF); + picopass->dev->format = PicopassDeviceSaveFormatLF; + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + + return consumed; +} + +void picopass_scene_card_menu_on_exit(void* context) { + Picopass* picopass = context; + + submenu_reset(picopass->submenu); +} diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/picopass/scenes/picopass_scene_config.h index 7a87737e..0a3e73f2 100755 --- a/applications/picopass/scenes/picopass_scene_config.h +++ b/applications/picopass/scenes/picopass_scene_config.h @@ -1,3 +1,7 @@ ADD_SCENE(picopass, start, Start) ADD_SCENE(picopass, read_card, ReadCard) ADD_SCENE(picopass, read_card_success, ReadCardSuccess) +ADD_SCENE(picopass, card_menu, CardMenu) +ADD_SCENE(picopass, save_name, SaveName) +ADD_SCENE(picopass, save_success, SaveSuccess) +ADD_SCENE(picopass, saved_menu, SavedMenu) diff --git a/applications/picopass/scenes/picopass_scene_read_card_success.c b/applications/picopass/scenes/picopass_scene_read_card_success.c index 8e65ce00..96a08031 100644 --- a/applications/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/picopass/scenes/picopass_scene_read_card_success.c @@ -45,6 +45,14 @@ void picopass_scene_read_card_success_on_enter(void* context) { "Retry", picopass_scene_read_card_success_widget_callback, picopass); + + widget_add_button_element( + widget, + GuiButtonTypeRight, + "More", + picopass_scene_read_card_success_widget_callback, + picopass); + if(pacs->record.valid) { widget_add_string_element( widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); @@ -65,6 +73,11 @@ bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(picopass->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + // Clear device name + picopass_device_set_name(picopass->dev, ""); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu); + consumed = true; } } return consumed; diff --git a/applications/picopass/scenes/picopass_scene_save_name.c b/applications/picopass/scenes/picopass_scene_save_name.c new file mode 100644 index 00000000..c5fa7dd1 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_save_name.c @@ -0,0 +1,84 @@ +#include "../picopass_i.h" +#include "m-string.h" +#include +#include +#include + +void picopass_scene_save_name_text_input_callback(void* context) { + Picopass* picopass = context; + + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventTextInputDone); +} + +void picopass_scene_save_name_on_enter(void* context) { + Picopass* picopass = context; + + // Setup view + TextInput* text_input = picopass->text_input; + bool dev_name_empty = false; + if(!strcmp(picopass->dev->dev_name, "")) { + set_random_name(picopass->text_store, sizeof(picopass->text_store)); + dev_name_empty = true; + } else { + picopass_text_store_set(picopass, picopass->dev->dev_name); + } + text_input_set_header_text(text_input, "Name the card"); + text_input_set_result_callback( + text_input, + picopass_scene_save_name_text_input_callback, + picopass, + picopass->text_store, + PICOPASS_DEV_NAME_MAX_LEN, + dev_name_empty); + + string_t folder_path; + string_init(folder_path); + + if(string_end_with_str_p(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) { + path_extract_dirname(string_get_cstr(picopass->dev->load_path), folder_path); + } else { + string_set_str(folder_path, PICOPASS_APP_FOLDER); + } + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(folder_path), PICOPASS_APP_EXTENSION, picopass->dev->dev_name); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextInput); + + string_clear(folder_path); +} + +bool picopass_scene_save_name_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventTextInputDone) { + if(strcmp(picopass->dev->dev_name, "")) { + // picopass_device_delete(picopass->dev, true); + } + strlcpy( + picopass->dev->dev_name, picopass->text_store, strlen(picopass->text_store) + 1); + if(picopass_device_save(picopass->dev, picopass->text_store)) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveSuccess); + consumed = true; + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + } + } + return consumed; +} + +void picopass_scene_save_name_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + void* validator_context = text_input_get_validator_callback_context(picopass->text_input); + text_input_set_validator(picopass->text_input, NULL, NULL); + validator_is_file_free(validator_context); + + text_input_reset(picopass->text_input); +} diff --git a/applications/picopass/scenes/picopass_scene_save_success.c b/applications/picopass/scenes/picopass_scene_save_success.c new file mode 100644 index 00000000..e92d91fb --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_save_success.c @@ -0,0 +1,47 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_save_success_popup_callback(void* context) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit); +} + +void picopass_scene_save_success_on_enter(void* context) { + Picopass* picopass = context; + DOLPHIN_DEED(DolphinDeedNfcSave); + + // Setup view + Popup* popup = picopass->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, picopass); + popup_set_callback(popup, picopass_scene_save_success_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); +} + +bool picopass_scene_save_success_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventViewExit) { + if(scene_manager_has_previous_scene(picopass->scene_manager, PicopassSceneCardMenu)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneCardMenu); + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + } + } + return consumed; +} + +void picopass_scene_save_success_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + popup_reset(picopass->popup); +} diff --git a/applications/picopass/scenes/picopass_scene_saved_menu.c b/applications/picopass/scenes/picopass_scene_saved_menu.c new file mode 100644 index 00000000..232cf26a --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_saved_menu.c @@ -0,0 +1,35 @@ +#include "../picopass_i.h" + +void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { + Picopass* picopass = context; + + view_dispatcher_send_custom_event(picopass->view_dispatcher, index); +} + +void picopass_scene_saved_menu_on_enter(void* context) { + Picopass* picopass = context; + + submenu_set_selected_item( + picopass->submenu, + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneSavedMenu)); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); +} + +bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneSavedMenu, event.event); + } + + return consumed; +} + +void picopass_scene_saved_menu_on_exit(void* context) { + Picopass* picopass = context; + + submenu_reset(picopass->submenu); +} From 222394d3ea673d09c3c5d045cde26476deb4d50a Mon Sep 17 00:00:00 2001 From: perspecdev Date: Sun, 10 Jul 2022 07:51:35 -0500 Subject: [PATCH 26/26] added gui-shift command to ducky script (#1381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Isenhower Co-authored-by: あく --- applications/bad_usb/bad_usb_script.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/bad_usb/bad_usb_script.c b/applications/bad_usb/bad_usb_script.c index e6c0f893..38b45acd 100644 --- a/applications/bad_usb/bad_usb_script.c +++ b/applications/bad_usb/bad_usb_script.c @@ -49,6 +49,7 @@ static const DuckyKey ducky_keys[] = { {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, + {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, {"CTRL", KEY_MOD_LEFT_CTRL}, {"CONTROL", KEY_MOD_LEFT_CTRL},