Version in base suite: 2024.12.1+dfsg-3+deb13u2 Base version: swupdate_2024.12.1+dfsg-3+deb13u2 Target version: swupdate_2025.12+dfsg-10~bpo13+2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/s/swupdate/swupdate_2024.12.1+dfsg-3+deb13u2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/s/swupdate/swupdate_2025.12+dfsg-10~bpo13+2.dsc /srv/release.debian.org/tmp/48LOKlTuXA/swupdate-2025.12+dfsg/doc/source/images/crypto_architecture.png |binary swupdate-2025.12+dfsg/.github/workflows/ci_tests.yml | 27 swupdate-2025.12+dfsg/.github/workflows/contributing.yml | 1 swupdate-2025.12+dfsg/.github/workflows/reuse.yml | 19 swupdate-2025.12+dfsg/.gitignore | 4 swupdate-2025.12+dfsg/.reuse/dep5 | 88 swupdate-2025.12+dfsg/Kconfig | 138 swupdate-2025.12+dfsg/Makefile | 29 swupdate-2025.12+dfsg/Makefile.deps | 12 swupdate-2025.12+dfsg/Makefile.flags | 65 swupdate-2025.12+dfsg/Makefile.help | 17 swupdate-2025.12+dfsg/README.md | 2 swupdate-2025.12+dfsg/REUSE.toml | 133 swupdate-2025.12+dfsg/bindings/lua_swupdate.c | 1 swupdate-2025.12+dfsg/bindings/lua_swupdate.lua | 19 swupdate-2025.12+dfsg/ci/install-src-deps.sh | 33 swupdate-2025.12+dfsg/ci/setup.sh | 114 swupdate-2025.12+dfsg/ci/test-configs.sh | 10 swupdate-2025.12+dfsg/configs/all_handlers_defconfig | 1 swupdate-2025.12+dfsg/configs/cms_defconfig | 9 swupdate-2025.12+dfsg/configs/crypto_all_defconfig | 42 swupdate-2025.12+dfsg/configs/test_defconfig | 3 swupdate-2025.12+dfsg/configs/without_libconfig_defconfig | 15 swupdate-2025.12+dfsg/core/Makefile | 2 swupdate-2025.12+dfsg/core/artifacts_versions.c | 15 swupdate-2025.12+dfsg/core/cpio_utils.c | 128 swupdate-2025.12+dfsg/core/crypto.c | 257 swupdate-2025.12+dfsg/core/decrypt_keys.c | 194 swupdate-2025.12+dfsg/core/installer.c | 8 swupdate-2025.12+dfsg/core/network_thread.c | 15 swupdate-2025.12+dfsg/core/parser.c | 55 swupdate-2025.12+dfsg/core/parsing_library.c | 34 swupdate-2025.12+dfsg/core/progress_thread.c | 1 swupdate-2025.12+dfsg/core/stream_interface.c | 101 swupdate-2025.12+dfsg/core/swupdate.c | 310 swupdate-2025.12+dfsg/core/util.c | 152 swupdate-2025.12+dfsg/corelib/Makefile | 33 swupdate-2025.12+dfsg/corelib/channel_curl.c | 17 swupdate-2025.12+dfsg/corelib/downloader.c | 5 swupdate-2025.12+dfsg/corelib/lua_interface.c | 87 swupdate-2025.12+dfsg/corelib/mtd-interface.c | 1 swupdate-2025.12+dfsg/corelib/parsing_library_libconfig.c | 51 swupdate-2025.12+dfsg/corelib/parsing_library_libjson.c | 50 swupdate-2025.12+dfsg/corelib/server_utils.c | 6 swupdate-2025.12+dfsg/corelib/swupdate_cms_verify.c | 305 swupdate-2025.12+dfsg/corelib/swupdate_decrypt_mbedtls.c | 125 swupdate-2025.12+dfsg/corelib/swupdate_decrypt_openssl.c | 124 swupdate-2025.12+dfsg/corelib/swupdate_decrypt_pkcs11.c | 195 swupdate-2025.12+dfsg/corelib/swupdate_gpg_verify.c | 171 swupdate-2025.12+dfsg/corelib/swupdate_pkcs7_verify.c | 173 swupdate-2025.12+dfsg/corelib/swupdate_rsa_verify.c | 194 swupdate-2025.12+dfsg/corelib/swupdate_rsa_verify_mbedtls.c | 90 swupdate-2025.12+dfsg/corelib/swupdate_settings.c | 24 swupdate-2025.12+dfsg/corelib/swupdate_verify_private.h | 25 swupdate-2025.12+dfsg/corelib/verify_signature.c | 204 swupdate-2025.12+dfsg/corelib/verify_signature_mbedtls.c | 136 swupdate-2025.12+dfsg/crypto/Kconfig | 138 swupdate-2025.12+dfsg/crypto/Makefile | 30 swupdate-2025.12+dfsg/crypto/swupdate_HASH_mbedtls.c | 134 swupdate-2025.12+dfsg/crypto/swupdate_HASH_openssl.c | 124 swupdate-2025.12+dfsg/crypto/swupdate_HASH_wolfssl.c | 25 swupdate-2025.12+dfsg/crypto/swupdate_cms_verify_openssl.c | 418 swupdate-2025.12+dfsg/crypto/swupdate_decrypt_mbedtls.c | 161 swupdate-2025.12+dfsg/crypto/swupdate_decrypt_openssl.c | 148 swupdate-2025.12+dfsg/crypto/swupdate_decrypt_openssl_cms.c | 201 swupdate-2025.12+dfsg/crypto/swupdate_decrypt_wolfssl.c | 212 swupdate-2025.12+dfsg/crypto/swupdate_gpg.h | 17 swupdate-2025.12+dfsg/crypto/swupdate_gpg_verify.c | 216 swupdate-2025.12+dfsg/crypto/swupdate_mbedtls.h | 24 swupdate-2025.12+dfsg/crypto/swupdate_openssl.h | 55 swupdate-2025.12+dfsg/crypto/swupdate_pkcs7_verify_wolfssl.c | 238 swupdate-2025.12+dfsg/crypto/swupdate_rsa_verify_mbedtls.c | 129 swupdate-2025.12+dfsg/crypto/swupdate_rsa_verify_openssl.c | 266 swupdate-2025.12+dfsg/crypto/swupdate_wolfssl.h | 56 swupdate-2025.12+dfsg/crypto/verify_signature.c | 113 swupdate-2025.12+dfsg/debian/README.Debian | 17 swupdate-2025.12+dfsg/debian/changelog | 109 swupdate-2025.12+dfsg/debian/clean | 2 swupdate-2025.12+dfsg/debian/configs/defconfig | 9 swupdate-2025.12+dfsg/debian/control | 25 swupdate-2025.12+dfsg/debian/copyright | 169 swupdate-2025.12+dfsg/debian/libswupdate0.1.symbols | 1 swupdate-2025.12+dfsg/debian/patches/Declare-public-key-or-gpg-cfg-mandatory.diff | 54 swupdate-2025.12+dfsg/debian/patches/Remove-element-of-char-failing-the-doc-build.diff | 15 swupdate-2025.12+dfsg/debian/patches/Remove-tabularcolumns-that-crashes-latex.diff | 18 swupdate-2025.12+dfsg/debian/patches/Replace-Font-Awesome-5-with-Fork-Awesome.diff | 24 swupdate-2025.12+dfsg/debian/patches/series | 5 swupdate-2025.12+dfsg/debian/patches/suricatta-wfx-Fix-rebooting.diff | 126 swupdate-2025.12+dfsg/debian/rules | 36 swupdate-2025.12+dfsg/debian/swupdate-www.links | 1 swupdate-2025.12+dfsg/doc/source/asym_encrypted_images.rst | 165 swupdate-2025.12+dfsg/doc/source/bindings.rst | 16 swupdate-2025.12+dfsg/doc/source/building-with-yocto.rst | 63 swupdate-2025.12+dfsg/doc/source/conf.py | 8 swupdate-2025.12+dfsg/doc/source/delta-update.rst | 11 swupdate-2025.12+dfsg/doc/source/encrypted_images.rst | 2 swupdate-2025.12+dfsg/doc/source/handlers.rst | 89 swupdate-2025.12+dfsg/doc/source/improvement_proposals.rst | 169 swupdate-2025.12+dfsg/doc/source/index.rst | 1 swupdate-2025.12+dfsg/doc/source/licensing.rst | 19 swupdate-2025.12+dfsg/doc/source/previous-releases.rst | 2 swupdate-2025.12+dfsg/doc/source/signed_images.rst | 32 swupdate-2025.12+dfsg/doc/source/suricatta.rst | 22 swupdate-2025.12+dfsg/doc/source/sw-description.rst | 86 swupdate-2025.12+dfsg/doc/source/swupdate-client.rst | 4 swupdate-2025.12+dfsg/doc/source/swupdate.rst | 23 swupdate-2025.12+dfsg/examples/configuration/swupdate.cfg | 76 swupdate-2025.12+dfsg/handlers/archive_handler.c | 10 swupdate-2025.12+dfsg/handlers/copy_handler.c | 72 swupdate-2025.12+dfsg/handlers/delta_handler.c | 12 swupdate-2025.12+dfsg/handlers/diskpart_handler.c | 20 swupdate-2025.12+dfsg/handlers/emmc_csd_handler.c | 2 swupdate-2025.12+dfsg/handlers/flash_hamming1_handler.c | 2 swupdate-2025.12+dfsg/handlers/flash_handler.c | 618 swupdate-2025.12+dfsg/handlers/raw_handler.c | 14 swupdate-2025.12+dfsg/handlers/remote_handler.c | 1 swupdate-2025.12+dfsg/handlers/swupdate.lua | 27 swupdate-2025.12+dfsg/include/channel_curl.h | 4 swupdate-2025.12+dfsg/include/flash.h | 1 swupdate-2025.12+dfsg/include/globals.h | 12 swupdate-2025.12+dfsg/include/installer.h | 2 swupdate-2025.12+dfsg/include/network_ipc.h | 5 swupdate-2025.12+dfsg/include/parselib-private.h | 23 swupdate-2025.12+dfsg/include/parselib.h | 6 swupdate-2025.12+dfsg/include/progress_ipc.h | 22 swupdate-2025.12+dfsg/include/sslapi.h | 231 swupdate-2025.12+dfsg/include/swupdate.h | 56 swupdate-2025.12+dfsg/include/swupdate_aes.h | 55 swupdate-2025.12+dfsg/include/swupdate_crypto.h | 125 swupdate-2025.12+dfsg/include/swupdate_image.h | 3 swupdate-2025.12+dfsg/include/swupdate_settings.h | 44 swupdate-2025.12+dfsg/include/util.h | 52 swupdate-2025.12+dfsg/ipc/network_ipc-if.c | 20 swupdate-2025.12+dfsg/ipc/network_ipc.c | 2 swupdate-2025.12+dfsg/ipc/progress_ipc.c | 4 swupdate-2025.12+dfsg/mongoose/Kconfig | 1 swupdate-2025.12+dfsg/mongoose/Makefile | 8 swupdate-2025.12+dfsg/mongoose/mongoose.c |14575 ++++++++-- swupdate-2025.12+dfsg/mongoose/mongoose.h | 2045 + swupdate-2025.12+dfsg/mongoose/mongoose_interface.c | 68 swupdate-2025.12+dfsg/mongoose/mongoose_multipart.c | 9 swupdate-2025.12+dfsg/parser/Kconfig | 4 swupdate-2025.12+dfsg/parser/parse_external.c | 2 swupdate-2025.12+dfsg/parser/parser.c | 98 swupdate-2025.12+dfsg/scripts/Kbuild.src | 2 swupdate-2025.12+dfsg/scripts/Kconfiglib/README.md | 21 swupdate-2025.12+dfsg/scripts/Kconfiglib/alldefconfig.py | 27 swupdate-2025.12+dfsg/scripts/Kconfiglib/allnoconfig.py | 45 swupdate-2025.12+dfsg/scripts/Kconfiglib/allyesconfig.py | 56 swupdate-2025.12+dfsg/scripts/Kconfiglib/defconfig.py | 43 swupdate-2025.12+dfsg/scripts/Kconfiglib/genconfig.py | 154 swupdate-2025.12+dfsg/scripts/Kconfiglib/guiconfig.py | 2324 + swupdate-2025.12+dfsg/scripts/Kconfiglib/kconfiglib.py | 7165 ++++ swupdate-2025.12+dfsg/scripts/Kconfiglib/listnewconfig.py | 76 swupdate-2025.12+dfsg/scripts/Kconfiglib/menuconfig.py | 3282 ++ swupdate-2025.12+dfsg/scripts/Kconfiglib/oldconfig.py | 246 swupdate-2025.12+dfsg/scripts/Kconfiglib/olddefconfig.py | 28 swupdate-2025.12+dfsg/scripts/Kconfiglib/savedefconfig.py | 49 swupdate-2025.12+dfsg/scripts/Kconfiglib/setconfig.py | 92 swupdate-2025.12+dfsg/scripts/Makefile | 4 swupdate-2025.12+dfsg/scripts/acceptance-tests/CheckImage.mk | 4 swupdate-2025.12+dfsg/scripts/checkpatch.pl | 7671 ----- swupdate-2025.12+dfsg/scripts/gen_extraversion | 26 swupdate-2025.12+dfsg/scripts/kconfig/Makefile | 317 swupdate-2025.12+dfsg/scripts/kconfig/POTFILES.in | 12 swupdate-2025.12+dfsg/scripts/kconfig/check.sh | 13 swupdate-2025.12+dfsg/scripts/kconfig/conf.c | 723 swupdate-2025.12+dfsg/scripts/kconfig/confdata.c | 1254 swupdate-2025.12+dfsg/scripts/kconfig/expr.c | 1207 swupdate-2025.12+dfsg/scripts/kconfig/expr.h | 239 swupdate-2025.12+dfsg/scripts/kconfig/gconf.c | 1521 - swupdate-2025.12+dfsg/scripts/kconfig/gconf.glade | 661 swupdate-2025.12+dfsg/scripts/kconfig/images.c | 327 swupdate-2025.12+dfsg/scripts/kconfig/kxgettext.c | 235 swupdate-2025.12+dfsg/scripts/kconfig/list.h | 131 swupdate-2025.12+dfsg/scripts/kconfig/lkc.h | 187 swupdate-2025.12+dfsg/scripts/kconfig/lkc_defs.h | 53 swupdate-2025.12+dfsg/scripts/kconfig/lkc_proto.h | 52 swupdate-2025.12+dfsg/scripts/kconfig/lxdialog/BIG.FAT.WARNING | 4 swupdate-2025.12+dfsg/scripts/kconfig/lxdialog/check-lxdialog.sh | 91 swupdate-2025.12+dfsg/scripts/kconfig/lxdialog/checklist.c | 320 swupdate-2025.12+dfsg/scripts/kconfig/lxdialog/dialog.h | 245 swupdate-2025.12+dfsg/scripts/kconfig/lxdialog/inputbox.c | 289 swupdate-2025.12+dfsg/scripts/kconfig/lxdialog/menubox.c | 425 swupdate-2025.12+dfsg/scripts/kconfig/lxdialog/textbox.c | 397 swupdate-2025.12+dfsg/scripts/kconfig/lxdialog/util.c | 701 swupdate-2025.12+dfsg/scripts/kconfig/lxdialog/yesno.c | 102 swupdate-2025.12+dfsg/scripts/kconfig/mconf.c | 1048 swupdate-2025.12+dfsg/scripts/kconfig/menu.c | 698 swupdate-2025.12+dfsg/scripts/kconfig/merge_config.sh | 152 swupdate-2025.12+dfsg/scripts/kconfig/nconf.c | 1562 - swupdate-2025.12+dfsg/scripts/kconfig/nconf.gui.c | 657 swupdate-2025.12+dfsg/scripts/kconfig/nconf.h | 97 swupdate-2025.12+dfsg/scripts/kconfig/qconf.cc | 1799 - swupdate-2025.12+dfsg/scripts/kconfig/qconf.h | 339 swupdate-2025.12+dfsg/scripts/kconfig/streamline_config.pl | 648 swupdate-2025.12+dfsg/scripts/kconfig/symbol.c | 1382 swupdate-2025.12+dfsg/scripts/kconfig/util.c | 147 swupdate-2025.12+dfsg/scripts/kconfig/zconf.gperf | 48 swupdate-2025.12+dfsg/scripts/kconfig/zconf.hash.c_shipped | 288 swupdate-2025.12+dfsg/scripts/kconfig/zconf.l | 373 swupdate-2025.12+dfsg/scripts/kconfig/zconf.lex.c_shipped | 2476 - swupdate-2025.12+dfsg/scripts/kconfig/zconf.tab.c_shipped | 2579 - swupdate-2025.12+dfsg/scripts/kconfig/zconf.y | 743 swupdate-2025.12+dfsg/suricatta/server_general.c | 18 swupdate-2025.12+dfsg/suricatta/server_hawkbit.c | 14 swupdate-2025.12+dfsg/suricatta/server_lua.c | 21 swupdate-2025.12+dfsg/suricatta/server_wfx.lua | 173 swupdate-2025.12+dfsg/suricatta/suricatta.lua | 7 swupdate-2025.12+dfsg/test/Makefile | 18 swupdate-2025.12+dfsg/test/test_crypt.c | 13 swupdate-2025.12+dfsg/test/test_flash_handler.c | 1463 + swupdate-2025.12+dfsg/test/test_hash.c | 4 swupdate-2025.12+dfsg/test/test_verify.c | 2 swupdate-2025.12+dfsg/tools/python/swupdateclient/Pipfile | 13 swupdate-2025.12+dfsg/tools/python/swupdateclient/pyproject.toml | 30 swupdate-2025.12+dfsg/tools/python/swupdateclient/setup.py | 21 swupdate-2025.12+dfsg/tools/swupdate-client.c | 29 swupdate-2025.12+dfsg/tools/swupdate-ipc.c | 8 swupdate-2025.12+dfsg/tools/swupdate-progress.c | 9 220 files changed, 34821 insertions(+), 39308 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpjq_46bwa/swupdate_2024.12.1+dfsg-3+deb13u2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpjq_46bwa/swupdate_2025.12+dfsg-10~bpo13+2.dsc: no acceptable signature found diff -Nru swupdate-2024.12.1+dfsg/.github/workflows/ci_tests.yml swupdate-2025.12+dfsg/.github/workflows/ci_tests.yml --- swupdate-2024.12.1+dfsg/.github/workflows/ci_tests.yml 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/.github/workflows/ci_tests.yml 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2025 Daniel Braunwarth +# +# SPDX-License-Identifier: MIT +name: CI tests + +on: + push + +jobs: + test: + strategy: + fail-fast: false + matrix: + container: + - ubuntu:22.04 + - ubuntu:24.04 + - debian:bookworm-slim + - debian:testing-slim + name: ${{ matrix.container }} + runs-on: ubuntu-24.04 + container: ${{ matrix.container }} + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: ./ci/setup.sh + - name: Run tests + run: ./ci/test-configs.sh diff -Nru swupdate-2024.12.1+dfsg/.github/workflows/contributing.yml swupdate-2025.12+dfsg/.github/workflows/contributing.yml --- swupdate-2024.12.1+dfsg/.github/workflows/contributing.yml 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/.github/workflows/contributing.yml 2025-12-03 11:32:03.000000000 +0000 @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2021 James Hilliard # # SPDX-License-Identifier: MIT +name: PR contributing bot on: pull_request_target: diff -Nru swupdate-2024.12.1+dfsg/.github/workflows/reuse.yml swupdate-2025.12+dfsg/.github/workflows/reuse.yml --- swupdate-2024.12.1+dfsg/.github/workflows/reuse.yml 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/.github/workflows/reuse.yml 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2025 Daniel Braunwarth +# +# SPDX-License-Identifier: MIT +name: REUSE compliance + +on: + push + +jobs: + reuse: + name: REUSE compliance + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Install pipx + run: | + sudo apt-get -qq update && sudo apt-get install --yes --no-install-recommends pipx + - name: Check REUSE compliance + run: pipx run reuse lint diff -Nru swupdate-2024.12.1+dfsg/.gitignore swupdate-2025.12+dfsg/.gitignore --- swupdate-2024.12.1+dfsg/.gitignore 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/.gitignore 2025-12-03 11:32:03.000000000 +0000 @@ -52,6 +52,7 @@ test/test_crypt test/test_hash test/test_json +test/test_flash_handler test/test_server_hawkbit test/test_util test/test_verify @@ -133,3 +134,6 @@ tools/python/swupdateclient/Pipfile.lock tools/python/swupdateclient/build/ tools/python/swupdateclient/swupdateclient.egg-info/ + +# Python +__pycache__ diff -Nru swupdate-2024.12.1+dfsg/.reuse/dep5 swupdate-2025.12+dfsg/.reuse/dep5 --- swupdate-2024.12.1+dfsg/.reuse/dep5 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/.reuse/dep5 1970-01-01 00:00:00.000000000 +0000 @@ -1,88 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: SWUpdate -Upstream-Contact: Stefano Babic -Source: https://github.com/sbabic/swupdate - -Files: examples/www/v2/css/fontawesome.min.css -Copyright: 2019 Font Awesome -License: MIT - -Files: examples/www/v2/css/solid.min.css -Copyright: 2019 Font Awesome -License: MIT - -Files: examples/www/v2/webfonts/* -Copyright: 2016 Font Awesome -License: OFL-1.1 - -Files: examples/www/v2/images/favicon.png -Copyright: 2016 Andres Babic -License: CC-BY-SA-4.0 - -Files: web-app/images/favicon.png -Copyright: 2016 Andres Babic -License: CC-BY-SA-4.0 - -Files: examples/www/v2/images/logo.png -Copyright: 2016 Andres Babic -License: CC-BY-SA-4.0 - -Files: web-app/images/logo.png -Copyright: 2016 Andres Babic -License: CC-BY-SA-4.0 - -Files: SWUpdate.svg -Copyright: 2016 Andres Babic -License: CC-BY-SA-4.0 - -Files: examples/description/sw-description-json -Copyright: 2013-2021 Stefano Babic -License: CC0-1.0 - -Files: examples/www/v2/images/background.jpg -Copyright: 2016 Gerd Altmann -License: CC0-1.0 - -Files: web-app/images/background.jpg -Copyright: 2016 Gerd Altmann -License: CC0-1.0 - -Files: examples/www/v2/index.html -Copyright: 2018 Stefan Herbrechtsmeier -License: MIT - -Files: web-app/index.html -Copyright: 2018 Stefan Herbrechtsmeier -License: MIT - -Files: web-app/package.json -Copyright: 2018 Stefan Herbrechtsmeier -License: MIT - -Files: bindings/nodejs/package.json -Copyright: 2023 Stefano Babic -License: MIT - -Files: examples/www/v2/css/dropzone.min.css -Copyright: 2021 Matias Meno -License: MIT - -Files: examples/www/v2/js/dropzone.min.js -Copyright: 2021 Matias Meno -License: MIT - -Files: examples/www/v2/js/bootstrap.min.js -Copyright: 2011–2021 the Bootstrap Authors -License: MIT - -Files: examples/www/v2/js/jquery.min.js -Copyright: 2011–2021 OpenJS Foundation and other contributors -License: MIT - -Files: scripts/* -Copyright: Free Software Foundation, Inc. -License: GPL-2.0-only - -Files: doc/source/images/* -Copyright: 2013-2021 Stefano Babic -License: GPL-2.0-only diff -Nru swupdate-2024.12.1+dfsg/Kconfig swupdate-2025.12+dfsg/Kconfig --- swupdate-2024.12.1+dfsg/Kconfig 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/Kconfig 2025-12-03 11:32:03.000000000 +0000 @@ -21,10 +21,6 @@ bool option env="HAVE_LUA" -config HAVE_LIBCONFIG - bool - option env="HAVE_LIBCONFIG" - config HAVE_LIBARCHIVE bool option env="HAVE_LIBARCHIVE" @@ -81,6 +77,10 @@ bool option env="HAVE_ZLIB" +config HAVE_XZ + bool + option env="HAVE_XZ" + config HAVE_ZSTD bool option env="HAVE_ZSTD" @@ -105,6 +105,10 @@ bool option env="HAVE_MBEDTLS" +config HAVE_GPGME + bool + option env="HAVE_GPGME" + config HAVE_P11KIT bool option env="HAVE_P11KIT" @@ -369,7 +373,6 @@ default n depends on HAVE_LIBCURL select CHANNEL_CURL - select JSON help Enable update from image URL using libcurl. The stream is sent via IPC to the installer as it is done for other interfaces. @@ -396,132 +399,13 @@ comment "Security" -choice - prompt "SSL implementation to use" - default SSL_IMPL_OPENSSL - help - Select SSL implementation for hashing, verifying and decrypting images. - - config SSL_IMPL_NONE - bool "None" - - config SSL_IMPL_OPENSSL - bool "OpenSSL" - depends on HAVE_LIBSSL - - config SSL_IMPL_WOLFSSL - bool "wolfSSL (with OpenSSL compatibility layer)" - depends on HAVE_WOLFSSL - select CMS_IGNORE_CERTIFICATE_PURPOSE if SIGALG_CMS - select CMS_SKIP_UNKNOWN_SIGNERS if SIGALG_CMS - - config SSL_IMPL_MBEDTLS - bool "mbedTLS" - depends on HAVE_MBEDTLS -endchoice - +source "crypto/Kconfig" config CHANNEL_CURL_SSL bool depends on CHANNEL_CURL select CURL_SSL -config HASH_VERIFY - bool "Allow to add sha256 hash to each image" - depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS - help - Allow to add a sha256 hash to an artifact. - This is automatically set in case of Signed Image - -comment "Hash checking needs an SSL implementation" - depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS - -config SIGNED_IMAGES - bool "Enable verification of signed images" - depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS - select HASH_VERIFY -comment "Image signature verification needs an SSL implementation" - depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS - -choice - prompt "Signature verification algorithm" - depends on SIGNED_IMAGES - default SIGALG_RAWRSA - help - Select if the signature algorithm for signed images is a raw RSA signature - (following PKCS#1.5) or if it uses Cryptographic Message Syntax (CMS) with - OpenSSL/LibreSSL or PKCS#7 with wolfSSL. - wolfSSL's PKCS#7 implementation can only deal with one signature and cannot - deal with X509 purposes. - - config SIGALG_RAWRSA - bool "RSA PKCS#1.5" - - config SIGALG_RSAPSS - bool "RSA PSS" - - config SIGALG_CMS - bool "Cryptographic Message Syntax (CMS) / PKCS#7" - depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL - - config SIGALG_GPG - bool "GPG signing" -endchoice - -menu "CMS / PKCS#7 signature verification options" - depends on SIGALG_CMS - -config CMS_IGNORE_EXPIRED_CERTIFICATE - bool "Ignore expired certificates" - depends on SIGALG_CMS - -config CMS_IGNORE_CERTIFICATE_PURPOSE - bool "Ignore X.509 certificate purpose" - depends on SIGALG_CMS - -config CMS_SKIP_UNKNOWN_SIGNERS - bool "Ignore unverifiable signatures if known signer verifies" - depends on SIGALG_CMS -endmenu - - -config ENCRYPTED_IMAGES - bool "Images can be encrypted with a symmetric key" - depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS -comment "Image encryption needs an SSL implementation" - depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS - -config ENCRYPTED_SW_DESCRIPTION - bool "Even sw-description is encrypted" - depends on ENCRYPTED_IMAGES - help - sw-description is not encrypted as default, but it is encrypted - if this is set. It is a compile time option, and mix of plain and - encrypted sw-descriptions is not possible. - -config ENCRYPTED_IMAGES_HARDEN_LOGGING - bool "Harden logging for encrypted images" - default n - depends on ENCRYPTED_IMAGES - help - This option addresses a theoretical weakness of the AES-CBC encryption in - combination with streamed images. An adversary can target each 16-byte - block of encrypted data within an image and decrypt it, if they can apply a - huge amount of manipulated firmware updates and observe the logged - messages. On average, 2048 update attempts are needed for each block. - Select if this scenario poses a risk. If set, log messages related to a - hash mismatch and errors in the decryption finalization (padding) of a - streamed image are suppressed. - -config PKCS11 - bool "Enable PKCS#11 cryptographic operations" - default n - depends on HAVE_WOLFSSL && HAVE_P11KIT && ENCRYPTED_IMAGES - help - Enable using PKCS#11 for AES decryption instead of having the plain - key available in a file. This is implemented with wolfSSL independent - from the SSL implementation and replaces the plain key method. - comment "Compressors (zlib always on)" config GUNZIP @@ -529,6 +413,10 @@ depends on HAVE_ZLIB default y +config XZ + bool "XZ compression support" + depends on HAVE_XZ + config ZSTD bool "Zstd compression support" depends on HAVE_ZSTD diff -Nru swupdate-2024.12.1+dfsg/Makefile swupdate-2025.12+dfsg/Makefile --- swupdate-2024.12.1+dfsg/Makefile 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/Makefile 2025-12-03 11:32:03.000000000 +0000 @@ -2,11 +2,9 @@ # # SPDX-License-Identifier: GPL-2.0-only -VERSION = 2024 +VERSION = 2025 PATCHLEVEL = 12 -SUBLEVEL = 1 -EXTRAVERSION = -NAME = +SUBLEVEL = 0 IPCLIB_VERSION = 0.1 @@ -228,6 +226,9 @@ KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null) KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION) +# Kconfiglib +KCONFIGLIB = $(srctree)/scripts/Kconfiglib + export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC export CPP AR NM STRIP OBJCOPY OBJDUMP export MAKE AWK GENKSYMS INSTALLKERNEL PERL UTS_MACHINE @@ -269,7 +270,7 @@ # To make sure we do not include .config for any of the *config targets -# catch them early, and hand them over to scripts/kconfig/Makefile +# catch them early, and hand them over to scripts/Kconfiglib/. # It is allowed to specify more targets when calling make, including # mixing *config targets and build targets. # For example 'make oldconfig all'. @@ -309,8 +310,8 @@ else ifeq ($(config-targets),1) # =========================================================================== -# *config targets only - make sure prerequisites are updated, and descend -# in scripts/kconfig to make the *config target +# *config targets only - make sure prerequisites are updated, and call +# scripts/Kconfiglib to make the *config target # Read arch specific Makefile to set KBUILD_DEFCONFIG as needed. # KBUILD_DEFCONFIG may point out an alternative default configuration @@ -324,7 +325,11 @@ %config: scripts_basic outputmakefile FORCE $(Q)mkdir -p include/linux include/config - $(Q)$(MAKE) $(build)=scripts/kconfig $@ + $(Q)$(KCONFIGLIB)/$@.py + +%_defconfig: scripts_basic outputmakefile FORCE + $(Q)mkdir -p include/linux include/config + $(Q)$(KCONFIGLIB)/defconfig.py $(srctree)/configs/$@ else @@ -346,8 +351,10 @@ # if auto.conf.cmd is missing then we are probably in a cleaned tree so # we execute the config step to be sure to catch updated Kconfig files include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd - $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig - + $(Q)mkdir -p $(objtree)/include/config $(objtree)/include/generated + $(Q)$(KCONFIGLIB)/genconfig.py \ + --header-path $(objtree)/include/generated/autoconf.h \ + --sync-deps $(objtree)/include/config else # Dummy target needed, because used as prerequisite @@ -362,7 +369,7 @@ # This allow a user to issue only 'make' to build a kernel including modules # Defaults to vmlinux, but the arch makefile usually adds further targets -objs-y := core handlers bootloader suricatta +objs-y := core handlers crypto bootloader suricatta libs-y := corelib mongoose parser fs containers bindings-y := bindings tools-y := tools diff -Nru swupdate-2024.12.1+dfsg/Makefile.deps swupdate-2025.12+dfsg/Makefile.deps --- swupdate-2024.12.1+dfsg/Makefile.deps 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/Makefile.deps 2025-12-03 11:32:03.000000000 +0000 @@ -6,10 +6,6 @@ export HAVE_LUA = y endif -ifeq ($(HAVE_LIBCONFIG),) -export HAVE_LIBCONFIG = y -endif - ifeq ($(HAVE_LIBARCHIVE),) export HAVE_LIBARCHIVE = y endif @@ -62,6 +58,10 @@ export HAVE_ZLIB = y endif +ifeq ($(HAVE_XZ),) +export HAVE_XZ = y +endif + ifeq ($(HAVE_ZSTD),) export HAVE_ZSTD = y endif @@ -98,6 +98,10 @@ export HAVE_MBEDTLS = y endif +ifeq ($(HAVE_GPGME),) +export HAVE_GPGME = y +endif + ifeq ($(HAVE_P11KIT),) export HAVE_P11KIT = y endif diff -Nru swupdate-2024.12.1+dfsg/Makefile.flags swupdate-2025.12+dfsg/Makefile.flags --- swupdate-2024.12.1+dfsg/Makefile.flags 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/Makefile.flags 2025-12-03 11:32:03.000000000 +0000 @@ -5,9 +5,6 @@ # ========================================================================== # Build system # ========================================================================== -SWU_DIR = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -SWU_VER = $(shell git -C $(SWU_DIR) describe --tags --always --dirty) -export SWU_VER SKIP_STRIP ?= n ifneq ($(CC),clang) @@ -19,8 +16,13 @@ # -std=gnu99 needed for [U]LLONG_MAX on some systems KBUILD_CPPFLAGS += $(call cc-option,-std=gnu99,) -KBUILD_CPPFLAGS += -D_GNU_SOURCE -DNDEBUG \ - -D"SWU_VER=\"$(SWU_VER)\"" +KBUILD_CPPFLAGS += -D_GNU_SOURCE -DNDEBUG + +# Generate version +KBUILD_CPPFLAGS += -DVERSION=$(VERSION) +KBUILD_CPPFLAGS += -DVERSION_PATCHLEVEL=$(PATCHLEVEL) +KBUILD_CPPFLAGS += -DVERSION_SUBLEVEL=$(SUBLEVEL) +KBUILD_CPPFLAGS += -DVERSION_EXTRAVERSION=$(shell cd $(srctree) && ver=$$(./scripts/gen_extraversion); [ -n "$$ver" ] && echo -$$ver) KBUILD_CFLAGS += $(call cc-option,-Wall,) KBUILD_CFLAGS += $(call cc-option,-Wshadow,) @@ -106,7 +108,7 @@ export SYSROOT=$(CONFIG_SYSROOT) endif -# Links always pthread +# Always link pthread, libubootenv, and json-c LDLIBS += pthread ubootenv json-c # lua ifneq ($(CONFIG_LUA),) @@ -125,9 +127,18 @@ ifeq ($(CONFIG_LUA),y) ifeq ($(CONFIG_EMBEDDED_LUA_HANDLER),y) ifneq ($(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE),) +# Strip quotes from filename +CONFIG_EMBEDDED_LUA_HANDLER_SOURCE := $(patsubst "%",%,$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE)) +ifeq ($(findstring /,$(word 1,$(subst /, /,$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE)))),) +# It's not an absolute path, assume it's relative to $(srcdir) +ifeq ($(realpath $(srctree)/$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE)),) +$(error File specified in CONFIG_EMBEDDED_LUA_HANDLER_SOURCE=$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE) not found) +endif +CONFIG_EMBEDDED_LUA_HANDLER_SOURCE := $(realpath $(srctree)/$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE)) +endif LDFLAGS_swupdate += -Wl,--format=binary -Wl,$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE) -Wl,--format=default -KBUILD_CPPFLAGS += -DEMBEDDED_LUA_SRC_START="_binary_$(subst ",,$(subst .,_,$(subst /,_,$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE))))_start" -KBUILD_CPPFLAGS += -DEMBEDDED_LUA_SRC_END="_binary_$(subst ",,$(subst .,_,$(subst /,_,$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE))))_end" +KBUILD_CPPFLAGS += -DEMBEDDED_LUA_SRC_START="_binary_$(strip $(subst -,_,$(subst +,_,$(subst ",,$(subst .,_,$(subst /,_,$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE)))))))_start" +KBUILD_CPPFLAGS += -DEMBEDDED_LUA_SRC_END="_binary_$(strip $(subst -,_,$(subst +,_,$(subst ",,$(subst .,_,$(subst /,_,$(CONFIG_EMBEDDED_LUA_HANDLER_SOURCE)))))))_end" endif endif endif @@ -138,9 +149,7 @@ endif # libconfig is always compiled -ifeq ($(CONFIG_LIBCONFIG),y) LDLIBS += config -endif # libarchive ifeq ($(CONFIG_ARCHIVE),y) @@ -158,14 +167,21 @@ LDLIBS += wolfssl endif -ifeq ($(CONFIG_PKCS11),y) -LDLIBS += p11-kit -endif - ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y) LDLIBS += mbedcrypto mbedtls mbedx509 endif +ifeq ($(CONFIG_SSL_IMPL_GPGME),y) +LDLIBS += gpgme +endif + +ifeq ($(CONFIG_PKCS11),y) +$(eval $(call pkg_check_modules, P11BUILD, "p11-kit-1")) +KBUILD_CFLAGS += $(P11BUILD_CFLAGS) +KBUILD_LIBS += $(P11BUILD_LIBS) +LDLIBS += p11-kit +endif + # MTD ifeq ($(CONFIG_MTD),y) LDLIBS += ubi mtd @@ -175,6 +191,10 @@ LDLIBS += z endif +ifeq ($(CONFIG_XZ),y) +LDLIBS += lzma +endif + ifeq ($(CONFIG_ZSTD),y) LDLIBS += zstd endif @@ -257,9 +277,18 @@ ifneq ($(CONFIG_SURICATTA_LUA),) ifneq ($(CONFIG_EMBEDDED_SURICATTA_LUA),) ifneq ($(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE),) +# Strip quotes from filename +CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE := $(patsubst "%",%,$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE)) +ifeq ($(findstring /,$(word 1,$(subst /, /,$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE)))),) +# It's not an absolute path, assume it's relative to $(srcdir) +ifeq ($(realpath $(srctree)/$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE)),) +$(error File specified in CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE=$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE) not found) +endif +CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE := $(realpath $(srctree)/$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE)) +endif LDFLAGS_swupdate += -Wl,--format=binary -Wl,$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE) -Wl,--format=default -KBUILD_CPPFLAGS += -DEMBEDDED_SURICATTA_LUA_SOURCE_START="_binary_$(subst ",,$(subst .,_,$(subst /,_,$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE))))_start" -KBUILD_CPPFLAGS += -DEMBEDDED_SURICATTA_LUA_SOURCE_END="_binary_$(subst ",,$(subst .,_,$(subst /,_,$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE))))_end" +KBUILD_CPPFLAGS += -DEMBEDDED_SURICATTA_LUA_SOURCE_START="_binary_$(strip $(subst -,_,$(subst +,_,$(subst ",,$(subst .,_,$(subst /,_,$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE)))))))_start" +KBUILD_CPPFLAGS += -DEMBEDDED_SURICATTA_LUA_SOURCE_END="_binary_$(strip $(subst -,_,$(subst +,_,$(subst ",,$(subst .,_,$(subst /,_,$(CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE)))))))_end" endif endif endif @@ -300,7 +329,3 @@ # (we stole scripts/checkstack.pl from the kernel... thanks guys!) # Reduced from 20k to 16k in 1.9.0. FLTFLAGS += -s 16000 - -ifeq ($(CONFIG_SIGALG_GPG),y) -LDLIBS += gpgme -endif diff -Nru swupdate-2024.12.1+dfsg/Makefile.help swupdate-2025.12+dfsg/Makefile.help --- swupdate-2024.12.1+dfsg/Makefile.help 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/Makefile.help 2025-12-03 11:32:03.000000000 +0000 @@ -16,14 +16,19 @@ @echo ' swupdate - software updater executable' @echo @echo 'Configuration:' - @echo ' allnoconfig - disable all symbols in .config' - @echo ' allyesconfig - enable all symbols in .config' - @echo ' config - text based configurator (of last resort)' - @echo ' menuconfig - interactive curses-based configurator' - @echo ' oldconfig - resolve any unresolved symbols in .config' + @echo ' alldefconfig - New config with all symbols set to default' + @echo ' allnoconfig - New config where all options are answered with no' + @echo ' allyesconfig - New config where all options are answered with yes' + @echo ' defconfig - New config with default from supplied defconfig' + @echo ' guiconfig - Update current config utilizing a TK based front-end' + @echo ' listnewconfig - List new options' + @echo ' menuconfig - Update current config utilising a menu based program' + @echo ' oldconfig - Update current config utilising a provided .config as base' + @echo ' olddefconfig - Same as oldconfig but sets new symbols to their' + @echo ' default value without prompting' + @echo ' savedefconfig - Save current config as ./defconfig (minimal config)' @echo @echo 'Development:' - @echo ' randconfig - generate a random configuration' @echo ' tests - build and run tests for current configuration' @echo @echo 'Documentation:' diff -Nru swupdate-2024.12.1+dfsg/README.md swupdate-2025.12+dfsg/README.md --- swupdate-2024.12.1+dfsg/README.md 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/README.md 2025-12-03 11:32:03.000000000 +0000 @@ -9,7 +9,7 @@ SWUpdate - Software Update for Embedded Linux Devices ===================================================== -[![pipeline status](https://source.denx.de/swupdate/swupdate/badges/master/pipeline.svg?ignore_skipped=true)](https://source.denx.de/swupdate/swupdate/-/commits/master) +[![CI tests](https://github.com/sbabic/swupdate/actions/workflows/ci_tests.yml/badge.svg?branch=master)](https://github.com/sbabic/swupdate/actions/workflows/ci_tests.yml) [![Coverity Scan Build Status](https://scan.coverity.com/projects/20753/badge.svg)](https://scan.coverity.com/projects/20753) ![License](https://img.shields.io/github/license/sbabic/swupdate) [![REUSE status](https://api.reuse.software/badge/github.com/sbabic/swupdate)](https://api.reuse.software/info/github.com/sbabic/swupdate) diff -Nru swupdate-2024.12.1+dfsg/REUSE.toml swupdate-2025.12+dfsg/REUSE.toml --- swupdate-2024.12.1+dfsg/REUSE.toml 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/REUSE.toml 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,133 @@ +# SPDX-FileCopyrightText: 2025 Stefano Babic +# +# SPDX-License-Identifier: MIT +version = 1 +SPDX-PackageName = "SWUpdate" +SPDX-PackageSupplier = "Stefano Babic " +SPDX-PackageDownloadLocation = "https://github.com/sbabic/swupdate" + +[[annotations]] +path = "examples/www/v2/css/fontawesome.min.css" +precedence = "aggregate" +SPDX-FileCopyrightText = "2019 Font Awesome" +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "examples/www/v2/css/solid.min.css" +precedence = "aggregate" +SPDX-FileCopyrightText = "2019 Font Awesome" +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "examples/www/v2/webfonts/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "2016 Font Awesome" +SPDX-License-Identifier = "OFL-1.1" + +[[annotations]] +path = "examples/www/v2/images/favicon.png" +precedence = "aggregate" +SPDX-FileCopyrightText = "2016 Andres Babic " +SPDX-License-Identifier = "CC-BY-SA-4.0" + +[[annotations]] +path = "web-app/images/favicon.png" +precedence = "aggregate" +SPDX-FileCopyrightText = "2016 Andres Babic " +SPDX-License-Identifier = "CC-BY-SA-4.0" + +[[annotations]] +path = "examples/www/v2/images/logo.png" +precedence = "aggregate" +SPDX-FileCopyrightText = "2016 Andres Babic " +SPDX-License-Identifier = "CC-BY-SA-4.0" + +[[annotations]] +path = "web-app/images/logo.png" +precedence = "aggregate" +SPDX-FileCopyrightText = "2016 Andres Babic " +SPDX-License-Identifier = "CC-BY-SA-4.0" + +[[annotations]] +path = "SWUpdate.svg" +precedence = "aggregate" +SPDX-FileCopyrightText = "2016 Andres Babic " +SPDX-License-Identifier = "CC-BY-SA-4.0" + +[[annotations]] +path = "examples/description/sw-description-json" +precedence = "aggregate" +SPDX-FileCopyrightText = "2013-2021 Stefano Babic " +SPDX-License-Identifier = "CC0-1.0" + +[[annotations]] +path = "examples/www/v2/images/background.jpg" +precedence = "aggregate" +SPDX-FileCopyrightText = "2016 Gerd Altmann" +SPDX-License-Identifier = "CC0-1.0" + +[[annotations]] +path = "web-app/images/background.jpg" +precedence = "aggregate" +SPDX-FileCopyrightText = "2016 Gerd Altmann" +SPDX-License-Identifier = "CC0-1.0" + +[[annotations]] +path = "examples/www/v2/index.html" +precedence = "aggregate" +SPDX-FileCopyrightText = "2018 Stefan Herbrechtsmeier " +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "web-app/index.html" +precedence = "aggregate" +SPDX-FileCopyrightText = "2018 Stefan Herbrechtsmeier " +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "web-app/package.json" +precedence = "aggregate" +SPDX-FileCopyrightText = "2018 Stefan Herbrechtsmeier " +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "bindings/nodejs/package.json" +precedence = "aggregate" +SPDX-FileCopyrightText = "2023 Stefano Babic " +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "examples/www/v2/css/dropzone.min.css" +precedence = "aggregate" +SPDX-FileCopyrightText = "2021 Matias Meno " +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "examples/www/v2/js/dropzone.min.js" +precedence = "aggregate" +SPDX-FileCopyrightText = "2021 Matias Meno " +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "examples/www/v2/js/bootstrap.min.js" +precedence = "aggregate" +SPDX-FileCopyrightText = "2011-2021 the Bootstrap Authors" +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "examples/www/v2/js/jquery.min.js" +precedence = "aggregate" +SPDX-FileCopyrightText = "2011-2021 OpenJS Foundation and other contributors" +SPDX-License-Identifier = "MIT" + +[[annotations]] +path = "scripts/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "Free Software Foundation, Inc." +SPDX-License-Identifier = "GPL-2.0-only" + +[[annotations]] +path = "doc/source/images/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "2013-2021 Stefano Babic " +SPDX-License-Identifier = "GPL-2.0-only" diff -Nru swupdate-2024.12.1+dfsg/bindings/lua_swupdate.c swupdate-2025.12+dfsg/bindings/lua_swupdate.c --- swupdate-2024.12.1+dfsg/bindings/lua_swupdate.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/bindings/lua_swupdate.c 2025-12-03 11:32:03.000000000 +0000 @@ -321,6 +321,7 @@ }; lua_newtable(L); + LUA_PUSH_INT("apiversion", p->msg.apiversion); LUA_PUSH_INT("status", p->msg.status); LUA_PUSH_INT("download", p->msg.dwl_percent); LUA_PUSH_INT("source", p->msg.source); diff -Nru swupdate-2024.12.1+dfsg/bindings/lua_swupdate.lua swupdate-2025.12+dfsg/bindings/lua_swupdate.lua --- swupdate-2024.12.1+dfsg/bindings/lua_swupdate.lua 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/bindings/lua_swupdate.lua 2025-12-03 11:32:03.000000000 +0000 @@ -61,15 +61,16 @@ --- Lua equivalent of `struct progress_msg` as in `include/progress_ipc.h`. --- @class lua_swupdate.progress_msg ---- @field status lua_swupdate.RECOVERY_STATUS Update status, one of `lua_swupdate.RECOVERY_STATUS`'s values ---- @field download number Downloaded data percentage ---- @field source lua_swupdate.sourcetype Interface that triggered the update, one of `lua_swupdate.sourcetype`'s values ---- @field nsteps number Total number of steps ---- @field step number Current step ---- @field percent number Percentage done in current step ---- @field artifact string Name of image to be installed ---- @field handler string Name of running Handler ---- @field info string Additional information about installation +--- @field apiversion number API Version for compatibility check +--- @field status lua_swupdate.RECOVERY_STATUS Update status, one of `lua_swupdate.RECOVERY_STATUS`'s values +--- @field download number Downloaded data percentage +--- @field source lua_swupdate.sourcetype Interface that triggered the update, one of `lua_swupdate.sourcetype`'s values +--- @field nsteps number Total number of steps +--- @field step number Current step +--- @field percent number Percentage done in current step +--- @field artifact string Name of image to be installed +--- @field handler string Name of running Handler +--- @field info string Additional information about installation --- SWUpdate progress socket binding. diff -Nru swupdate-2024.12.1+dfsg/ci/install-src-deps.sh swupdate-2025.12+dfsg/ci/install-src-deps.sh --- swupdate-2024.12.1+dfsg/ci/install-src-deps.sh 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/ci/install-src-deps.sh 2025-12-03 11:32:03.000000000 +0000 @@ -19,9 +19,10 @@ $_SUDO mkdir -p /usr/local/lib $_SUDO mkdir -p /usr/local/include $_SUDO mkdir -p /usr/local/include/mtd - git clone https://github.com/sigma-star/mtd-utils - cd mtd-utils - git checkout -b tmp v2.0.0 + rm -rf /tmp/mtd-utils + git clone https://github.com/sigma-star/mtd-utils /tmp/mtd-utils + cd /tmp/mtd-utils + git checkout v2.0.0 ./autogen.sh ./configure make -j$(nproc) @@ -29,37 +30,35 @@ $_SUDO install -m 644 include/libmtd.h /usr/local/include $_SUDO install -m 644 include/mtd/ubi-media.h /usr/local/include/mtd $_SUDO install -m 644 *.a /usr/local/lib - cd .. } install_libubootenv() { - git clone https://github.com/sbabic/libubootenv.git - cd libubootenv + rm -rf /tmp/libubootenv + git clone https://github.com/sbabic/libubootenv.git /tmp/libubootenv + cd /tmp/libubootenv cmake . make -j$(nproc) $_SUDO make install - cd .. } install_efibootguard() { - git clone https://github.com/siemens/efibootguard.git - cd efibootguard + rm -rf /tmp/efibootguard + git clone https://github.com/siemens/efibootguard.git /tmp/efibootguard + cd /tmp/efibootguard git submodule update --init autoreconf -fi ./configure --disable-bootloader make -j$(nproc) $_SUDO make install - cd .. } install_zchunk() { - git clone https://github.com/zchunk/zchunk - cd zchunk - meson build - cd build - ninja - $_SUDO ninja install - cd .. + rm -rf /tmp/zchunk + git clone https://github.com/zchunk/zchunk /tmp/zchunk + cd /tmp/zchunk + meson setup --prefix /usr --libdir lib build + meson compile -C build + $_SUDO meson install -C build } install_mtd_utils diff -Nru swupdate-2024.12.1+dfsg/ci/setup.sh swupdate-2025.12+dfsg/ci/setup.sh --- swupdate-2024.12.1+dfsg/ci/setup.sh 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/ci/setup.sh 2025-12-03 11:32:03.000000000 +0000 @@ -21,62 +21,64 @@ export DEBIAN_FRONTEND=noninteractive export TZ=Europe/London -$_SUDO apt-get -qq update -$_SUDO apt-get install -y \ - build-essential \ - automake \ - cmake \ - curl \ - libzmq3-dev \ - liblua5.2-dev \ - libluajit-5.1-dev \ - libconfig-dev \ - libarchive-dev \ - libbtrfsutil-dev \ - libjson-c-dev \ - libyaml-dev \ - zlib1g-dev \ - git \ - uuid \ - uuid-dev \ - liblzo2-dev \ - libsystemd-dev \ - libsystemd0 \ - check \ - librsync2 \ - librsync-dev \ - libext2fs-dev \ - liburiparser-dev \ - doxygen \ - graphviz \ - autoconf-archive \ - linux-headers-generic \ - libmbedtls-dev \ - libcmocka-dev \ - libfdisk-dev \ - libwebsockets-dev \ - libgpiod-dev \ - libcurl4-openssl-dev \ - libpci-dev \ - gawk \ - cpio \ - meson \ - ninja-build \ - libzstd-dev \ - wget \ - python3 - -# packages are too old in Ubuntu Jammy -if ! grep -q UBUNTU_CODENAME=jammy /etc/os-release; then - apt-get install -y \ - libebgenv-dev \ - libmtd-dev \ - libubi-dev \ - libubootenv-dev \ - libzck-dev +$_SUDO apt-get -qq update && apt-get install --yes --no-install-recommends \ + cpio \ + curl \ + file \ + gawk \ + gcc \ + git \ + gulp \ + libarchive-dev \ + libblkid-dev \ + libbtrfsutil-dev \ + libcmocka-dev \ + libconfig-dev \ + libcurl4-openssl-dev \ + libczmq-dev \ + libext2fs-dev \ + libfdisk-dev \ + libgpiod-dev \ + libjson-c-dev \ + liblua5.2-dev \ + libluajit-5.1-dev \ + libmbedtls-dev \ + libp11-kit-dev \ + librsync-dev \ + libssl-dev \ + libsystemd-dev \ + libudev-dev \ + liburiparser-dev \ + libwebsockets-dev \ + liblzma-dev \ + libwolfssl-dev \ + libzstd-dev \ + make \ + npm \ + python3 \ + uuid-dev \ + zlib1g-dev \ + && rm -rf /var/lib/apt/lists/* +# packages are too old in Ubuntu Jammy and Debian Bookworm +if ! grep -qP "VERSION_CODENAME=(jammy|bookworm)" /etc/os-release; then + $_SUDO apt-get -qq update && apt-get install --yes --no-install-recommends \ + libebgenv-dev \ + libmtd-dev \ + libubi-dev \ + libubootenv-dev \ + libzck-dev \ + && rm -rf /var/lib/apt/lists/* else + $_SUDO apt-get -qq update && apt-get install --yes --no-install-recommends \ + autoconf \ + autoconf-archive \ + automake \ + check \ + cmake \ + liblzo2-dev \ + libtool \ + libyaml-dev \ + meson \ + && rm -rf /var/lib/apt/lists/* "$SCRIPT_DIR/install-src-deps.sh" fi - -PC_FILE_DIR=$(pkg-config --variable=pcfiledir lua5.2) -$_SUDO ln -sf "$PC_FILE_DIR/lua5.2.pc" "$PC_FILE_DIR/lua.pc" diff -Nru swupdate-2024.12.1+dfsg/ci/test-configs.sh swupdate-2025.12+dfsg/ci/test-configs.sh --- swupdate-2024.12.1+dfsg/ci/test-configs.sh 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/ci/test-configs.sh 2025-12-03 11:32:03.000000000 +0000 @@ -10,9 +10,13 @@ # SPDX-License-Identifier: GPL-2.0-only set -eu +BUILD_DIR=$(mktemp --directory) + find configs -type f | while read -r fname; do echo "*** Testing config: $fname" - make "$(basename "$fname")" - make "-j$(nproc)" - make tests + rm -rf "${BUILD_DIR}" + mkdir -p "${BUILD_DIR}" + make O="${BUILD_DIR}" "$(basename "$fname")" + make O="${BUILD_DIR}" "-j$(nproc)" + make O="${BUILD_DIR}" tests done diff -Nru swupdate-2024.12.1+dfsg/configs/all_handlers_defconfig swupdate-2025.12+dfsg/configs/all_handlers_defconfig --- swupdate-2024.12.1+dfsg/configs/all_handlers_defconfig 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/configs/all_handlers_defconfig 2025-12-03 11:32:03.000000000 +0000 @@ -10,6 +10,7 @@ CONFIG_HASH_VERIFY=y CONFIG_ENCRYPTED_IMAGES=y CONFIG_ZSTD=y +CONFIG_XZ=y CONFIG_LUAEXTERNAL=y CONFIG_ARCHIVE=y CONFIG_BOOTLOADERHANDLER=y diff -Nru swupdate-2024.12.1+dfsg/configs/cms_defconfig swupdate-2025.12+dfsg/configs/cms_defconfig --- swupdate-2024.12.1+dfsg/configs/cms_defconfig 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/configs/cms_defconfig 2025-12-03 11:32:03.000000000 +0000 @@ -4,14 +4,19 @@ # CONFIG_MTD is not set CONFIG_EXTRA_CFLAGS="-g" CONFIG_DOWNLOAD=y +CONFIG_SURICATTA=y +CONFIG_SURICATTA_SSL=y CONFIG_WEBSERVER=y CONFIG_MONGOOSESSL=y -CONFIG_SIGNED_IMAGES=y -CONFIG_SIGALG_CMS=y +CONFIG_HASH_VERIFY=y CONFIG_ENCRYPTED_IMAGES=y CONFIG_LUAEXTERNAL=y CONFIG_ARCHIVE=y +CONFIG_BOOTLOADERHANDLER=y CONFIG_LUASCRIPTHANDLER=y +CONFIG_EMMC_HANDLER=y CONFIG_RAW=y CONFIG_REMOTE_HANDLER=y CONFIG_SHELLSCRIPTHANDLER=y +CONFIG_SWUFORWARDER_HANDLER=y +CONFIG_UCFWHANDLER=y diff -Nru swupdate-2024.12.1+dfsg/configs/crypto_all_defconfig swupdate-2025.12+dfsg/configs/crypto_all_defconfig --- swupdate-2024.12.1+dfsg/configs/crypto_all_defconfig 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/configs/crypto_all_defconfig 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2021 Stefano Babic +# SPDX-License-Identifier: CC0-1.0 +CONFIG_HW_COMPATIBILITY=y +CONFIG_LUAPKG="lua5.2" +CONFIG_EXTRA_CFLAGS="-g" +CONFIG_DOWNLOAD=y +CONFIG_DOWNLOAD_SSL=y +CONFIG_SURICATTA=y +CONFIG_SURICATTA_SSL=y +CONFIG_WEBSERVER=y +CONFIG_MONGOOSESSL=y +CONFIG_SSL_IMPL_WOLFSSL=y +CONFIG_SSL_IMPL_MBEDTLS=y +CONFIG_SIGNED_IMAGES=y +CONFIG_SIGALG_RAWRSA=y +CONFIG_SIGALG_RSAPSS=y +CONFIG_SIGALG_CMS=y +CONFIG_ENCRYPTED_IMAGES=y +CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING=y +CONFIG_PKCS11=y +CONFIG_ZSTD=y +CONFIG_LUAEXTERNAL=y +CONFIG_ARCHIVE=y +CONFIG_BOOTLOADERHANDLER=y +CONFIG_CFI=y +CONFIG_DELTA=y +CONFIG_DISKPART=y +CONFIG_DISKPART_FORMAT=y +CONFIG_DISKFORMAT_HANDLER=y +CONFIG_FAT_FILESYSTEM=y +CONFIG_EXT_FILESYSTEM=y +CONFIG_LUASCRIPTHANDLER=y +CONFIG_RAW=y +CONFIG_RDIFFHANDLER=y +CONFIG_READBACKHANDLER=y +CONFIG_REMOTE_HANDLER=y +CONFIG_SHELLSCRIPTHANDLER=y +CONFIG_SWUFORWARDER_HANDLER=y +CONFIG_SSBLSWITCH=y +CONFIG_UBIVOL=y +CONFIG_UCFWHANDLER=y +CONFIG_UNIQUEUUID=y diff -Nru swupdate-2024.12.1+dfsg/configs/test_defconfig swupdate-2025.12+dfsg/configs/test_defconfig --- swupdate-2024.12.1+dfsg/configs/test_defconfig 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/configs/test_defconfig 2025-12-03 11:32:03.000000000 +0000 @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2021 Stefano Babic # SPDX-License-Identifier: CC0-1.0 CONFIG_HW_COMPATIBILITY=y -# CONFIG_MTD is not set CONFIG_EXTRA_CFLAGS="-g" CONFIG_DOWNLOAD=y CONFIG_WEBSERVER=y @@ -14,3 +13,5 @@ CONFIG_RAW=y CONFIG_REMOTE_HANDLER=y CONFIG_SHELLSCRIPTHANDLER=y +CONFIG_MTD=y +CONFIG_CFI=y diff -Nru swupdate-2024.12.1+dfsg/configs/without_libconfig_defconfig swupdate-2025.12+dfsg/configs/without_libconfig_defconfig --- swupdate-2024.12.1+dfsg/configs/without_libconfig_defconfig 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/configs/without_libconfig_defconfig 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -# SPDX-FileCopyrightText: 2021 Stefano Babic -# SPDX-License-Identifier: CC0-1.0 -CONFIG_HW_COMPATIBILITY=y -CONFIG_EXTRA_CFLAGS="-g" -CONFIG_DOWNLOAD=y -CONFIG_WEBSERVER=y -CONFIG_MONGOOSESSL=y -CONFIG_SIGNED_IMAGES=y -CONFIG_ENCRYPTED_IMAGES=y -# CONFIG_LIBCONFIG is not set -CONFIG_ARCHIVE=y -CONFIG_LUASCRIPTHANDLER=y -CONFIG_RAW=y -CONFIG_REMOTE_HANDLER=y -CONFIG_SHELLSCRIPTHANDLER=y diff -Nru swupdate-2024.12.1+dfsg/core/Makefile swupdate-2025.12+dfsg/core/Makefile --- swupdate-2024.12.1+dfsg/core/Makefile 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/Makefile 2025-12-03 11:32:03.000000000 +0000 @@ -10,6 +10,8 @@ obj-y += swupdate.o \ cpio_utils.o \ + crypto.o \ + decrypt_keys.o \ notifier.o \ handler.o \ bootloader.o \ diff -Nru swupdate-2024.12.1+dfsg/core/artifacts_versions.c swupdate-2025.12+dfsg/core/artifacts_versions.c --- swupdate-2024.12.1+dfsg/core/artifacts_versions.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/artifacts_versions.c 2025-12-03 11:32:03.000000000 +0000 @@ -93,7 +93,6 @@ return 0; } -#ifdef CONFIG_LIBCONFIG static int versions_settings(void *setting, void *data) { struct swupdate_cfg *sw = (struct swupdate_cfg *)data; @@ -136,14 +135,6 @@ /* If not found, fall back to a legacy file in the format " " */ read_sw_version_file(sw); } -#else - -void get_sw_versions(swupdate_cfg_handle __attribute__ ((__unused__))*handle, - struct swupdate_cfg *sw) -{ - read_sw_version_file(sw); -} -#endif /* * convert a version string into a number @@ -191,7 +182,7 @@ static const char ACCEPTED_CHARS[] = "0123456789."; -static bool is_oldstyle_version(const char *version_string, __u64 *version_number) +static bool is_numbered_version(const char *version_string, __u64 *version_number) { const char *ver = version_string; while (*ver) @@ -220,8 +211,8 @@ __u64 left_u64; __u64 right_u64; - if (is_oldstyle_version(left_version, &left_u64) - && is_oldstyle_version(right_version, &right_u64)) + if (is_numbered_version(left_version, &left_u64) + && is_numbered_version(right_version, &right_u64)) { DEBUG("Comparing old-style versions '%s' <-> '%s'", left_version, right_version); diff -Nru swupdate-2024.12.1+dfsg/core/cpio_utils.c swupdate-2025.12+dfsg/core/cpio_utils.c --- swupdate-2024.12.1+dfsg/core/cpio_utils.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/cpio_utils.c 2025-12-03 11:32:03.000000000 +0000 @@ -16,6 +16,9 @@ #ifdef CONFIG_GUNZIP #include #endif +#ifdef CONFIG_XZ +#include +#endif #ifdef CONFIG_ZSTD #include #endif @@ -24,7 +27,7 @@ #include "cpiohdr.h" #include "swupdate.h" #include "util.h" -#include "sslapi.h" +#include "swupdate_crypto.h" #include "progress.h" #define MODULE_NAME "cpio" @@ -69,6 +72,10 @@ while (nbytes > 0) { len = read(fd, buf, nbytes); if (len < 0) { + if (errno == EINTR) { + continue; + } + ERROR("Failure in stream %d: %s", fd, strerror(errno)); return -EFAULT; } @@ -107,7 +114,7 @@ { int padding; ssize_t len; - unsigned char buf[512]; + unsigned char buf[512]; int old_flags; struct pollfd pfd; int retval; @@ -142,6 +149,10 @@ padding -= len; } while (len > 0 && padding > 0); + if (poll(&pfd, 1, 1000) > 0 && read(fd, buf, 1) > 0) { + WARN("Excessive bytes after end-of-file in swu artifact."); + } + fcntl(fd, F_SETFL, old_flags); return; } @@ -301,14 +312,16 @@ ret = swupdate_DECRYPT_update(s->dcrypt, s->output, &s->outlen, s->input, inlen); } - if (inlen == 0 || s->outlen == 0) { + if (inlen == 0) { /* * Finalise the decryption. Further plaintext bytes may * be written at this stage. */ ret = swupdate_DECRYPT_final(s->dcrypt, s->output, &s->outlen); - s->eof = true; + if (ret == 0) { + s->eof = true; + } } if (ret < 0) { return ret; @@ -328,7 +341,7 @@ return 0; } -#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) +#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) || defined(CONFIG_XZ) typedef int (*DecompressStep)(void *state, void *buffer, size_t size); struct DecompressState { @@ -387,6 +400,53 @@ #endif +#ifdef CONFIG_XZ +struct XzState { + lzma_stream strm; + bool initialized; +}; +static int xz_step(void* state, void* buffer, size_t size) +{ + struct DecompressState *ds = (struct DecompressState *)state; + struct XzState *s = (struct XzState *)ds->impl_state; + lzma_ret ret; + int outlen = 0; + lzma_action action = LZMA_RUN; + + s->strm.next_out = buffer; + s->strm.avail_out = size; + + while (outlen == 0) { + if (s->strm.avail_in == 0) { + ret = ds->upstream_step(ds->upstream_state, ds->input, sizeof ds->input); + if (ret < 0) { + return ret; + } else if (ret == 0) { + ds->eof = true; + } + s->strm.avail_in = ret; + s->strm.next_in = ds->input; + } + if (ds->eof) { + break; + } + + ret = lzma_code(&s->strm, action); + outlen = size - s->strm.avail_out; + if (ret == LZMA_STREAM_END) { + ds->eof = true; + break; + } + if (ret != LZMA_OK && ret != LZMA_BUF_ERROR) { + ERROR("xz failed (returned %d)", ret); + return -1; + } + } + return outlen; +} + +#endif + #ifdef CONFIG_ZSTD struct ZstdState { @@ -434,7 +494,7 @@ #endif -static int hash_compare(struct swupdate_digest *dgst, unsigned char *hash) +static int hash_compare(void *dgst, unsigned char *hash) { /* * SHA256_HASH_LENGTH should be enough but openssl might write @@ -474,6 +534,8 @@ unsigned char *aes_key = NULL; unsigned char *ivt = NULL; unsigned char ivtbuf[AES_BLK_SIZE]; + unsigned char aesbuf[AES_256_KEY_LEN]; + char keylen = 0; struct InputState input_state = { .fdin = args->fdin, @@ -492,7 +554,7 @@ .outlen = 0, .eof = false }; -#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) +#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) || defined(CONFIG_XZ) struct DecompressState decompress_state = { .upstream_step = NULL, .upstream_state = NULL, .impl_state = NULL @@ -509,6 +571,12 @@ .initialized = false, }; #endif +#ifdef CONFIG_XZ + struct XzState xz_state = { + .strm = LZMA_STREAM_INIT, + .initialized = false, + }; +#endif #ifdef CONFIG_ZSTD struct ZstdState zstd_state = { .dctx = NULL, @@ -544,7 +612,6 @@ } if (args->encrypted) { - aes_key = get_aes_key(); if (args->imgivt && strlen(args->imgivt)) { if (!is_hex_str(args->imgivt) || ascii_to_bin(ivtbuf, sizeof(ivtbuf), args->imgivt)) { ERROR("Invalid image ivt"); @@ -553,7 +620,25 @@ ivt = ivtbuf; } else ivt = get_aes_ivt(); - decrypt_state.dcrypt = swupdate_DECRYPT_init(aes_key, get_aes_keylen(), ivt); + + if (args->imgaes && strlen(args->imgaes)) { + if (!is_hex_str(args->imgaes) || ascii_to_bin(aesbuf, sizeof(aesbuf), args->imgaes)) { + ERROR("Invalid image aes-key"); + return -EINVAL; + } + aes_key = aesbuf; + keylen = strlen(args->imgaes) / 2; + } else { + /* + * Only fall back to the default decryption key if the requested cipher matches. + */ + if (swupdate_get_decrypt_cipher() == args->cipher) { + aes_key = (unsigned char *)swupdate_get_decrypt_key(); + keylen = swupdate_get_decrypt_keylen(); + } + } + + decrypt_state.dcrypt = swupdate_DECRYPT_init(aes_key, keylen, ivt, args->cipher); if (!decrypt_state.dcrypt) { ERROR("decrypt initialization failure, aborting"); ret = -EFAULT; @@ -581,6 +666,19 @@ decompress_state.impl_state = &gunzip_state; } else #endif +#ifdef CONFIG_XZ + if (args->compressed == COMPRESSED_XZ) { + if (lzma_stream_decoder(&xz_state.strm, UINT32_MAX, + 0) != LZMA_OK) { + ERROR("(lzma_stream_decoder failed"); + ret = -EFAULT; + goto copyfile_exit; + } + xz_state.initialized = true; + decompress_step = &xz_step; + decompress_state.impl_state = &xz_state; + } else +#endif #ifdef CONFIG_ZSTD if (args->compressed == COMPRESSED_ZSTD) { if ((zstd_state.dctx = ZSTD_createDStream()) == NULL) { @@ -626,7 +724,7 @@ state = &decrypt_state; } -#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) +#if defined(CONFIG_GUNZIP) || defined(CONFIG_ZSTD) || defined(CONFIG_XZ) if (args->compressed) { decompress_state.upstream_step = step; decompress_state.upstream_state = state; @@ -637,6 +735,9 @@ for (;;) { ret = step(state, buffer, sizeof buffer); + if (ret == -EAGAIN) { + continue; + } if (ret < 0) { goto copyfile_exit; } @@ -695,6 +796,11 @@ inflateEnd(&gunzip_state.strm); } #endif +#ifdef CONFIG_XZ + if (xz_state.initialized) { + lzma_end(&xz_state.strm); + } +#endif #ifdef CONFIG_ZSTD if (zstd_state.dctx != NULL) { ZSTD_freeDStream(zstd_state.dctx); @@ -719,6 +825,8 @@ .hash = img->sha256, .encrypted = img->is_encrypted, .imgivt = img->ivt_ascii, + .imgaes = img->aes_ascii, + .cipher = img->cipher, }; return copyfile(©); } diff -Nru swupdate-2024.12.1+dfsg/core/crypto.c swupdate-2025.12+dfsg/core/crypto.c --- swupdate-2024.12.1+dfsg/core/crypto.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/core/crypto.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,257 @@ +/* + * (C) Copyright 2024 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include "swupdate_crypto.h" + +typedef enum { + DECRYPTLIB, + HASHLIB, + DGSTLIB +} crypto_lib_t; + +#define NUMLIBS (DGSTLIB + 1) + +const char *libdesc[] = { + "decrypt", + "hash", + "verify" +}; + +/* + * Reused from bootloader + */ +typedef struct { + const char *name; + void *lib; +} entry; + +static entry *current[NUMLIBS] = {NULL, NULL, NULL}; +static entry *available[NUMLIBS] = {NULL, NULL, NULL}; +static unsigned int num_available [] = {0 , 0, 0}; + +static int register_lib(const char *name, crypto_lib_t type, void *lib) +{ + int num = num_available[type]; + entry *avail = available[type]; + entry *tmp = realloc(avail, (num + 1) * sizeof(entry)); + if (!tmp) { + return -ENOMEM; + } + tmp[num].name = (char*)name; + tmp[num].lib = lib; + num_available[type]++; + available[type] = tmp; + current[type] = available[type]; + return 0; +} + +static int setlib(const char *name, crypto_lib_t type) +{ + int num = num_available[type]; + entry *elem; + + if (!name) { + return -ENOENT; + } + elem = available[type]; + for (unsigned int i = 0; i < num; i++) { + if (elem[i].lib && + (strcmp(elem[i].name, name) == 0)) { + current[type] = &elem[i]; + return 0; + } + } + return -ENOENT; +} + +static const char* getlib(crypto_lib_t type) +{ + return current[type] ? current[type]->name : NULL; +} + +int register_cryptolib(const char *name, swupdate_decrypt_lib *lib) +{ + return register_lib(name, DECRYPTLIB, lib); +} + +int register_hashlib(const char *name, swupdate_HASH_lib *lib) +{ + return register_lib(name, HASHLIB, lib); +} + +int register_dgstlib(const char *name, swupdate_dgst_lib *lib) +{ + return register_lib(name, DGSTLIB, lib); +} + +int set_cryptolib(const char *name) +{ + return setlib(name, DECRYPTLIB); +} + +int set_HASHlib(const char *name) +{ + return setlib(name, HASHLIB); +} + +int set_dgstlib(const char *name) +{ + return setlib(name, DGSTLIB); +} + +const char* get_cryptolib(void) +{ + return getlib(DECRYPTLIB); +} + +const char* get_HASHlib(void) +{ + return getlib(HASHLIB); +} + +const char* get_dgstlib(void) +{ + return getlib(DGSTLIB); +} + +void print_registered_cryptolib(void) +{ + INFO("Registered Crypto Providers:"); + + for (int type = 0; type < NUMLIBS; type++) { + int num = num_available[type]; + entry *elem = available[type]; + entry *cur = current[type]; + if (num > 0) { + INFO("\tProvider for %s", libdesc[type]); + } + for (unsigned int i = 0; i < num; i++) { + INFO("\t\t%s%s", elem[i].name, cur == &elem[i] ? "*" : ""); + } + } +} + +void *swupdate_DECRYPT_init(unsigned char *key, char keylen, unsigned char *iv, cipher_t cipher) +{ + swupdate_decrypt_lib *lib; + if (!get_cryptolib()) + return NULL; + + lib = (swupdate_decrypt_lib *)current[DECRYPTLIB]->lib; + return lib->DECRYPT_init(key, keylen, iv, cipher); +} + +int swupdate_DECRYPT_update(void *dgst, unsigned char *buf, + int *outlen, const unsigned char *cryptbuf, int inlen) +{ + swupdate_decrypt_lib *lib; + if (!get_cryptolib()) + return -EINVAL; + + lib = (swupdate_decrypt_lib *)current[DECRYPTLIB]->lib; + return lib->DECRYPT_update(dgst, buf, outlen, cryptbuf, inlen); +} + +int swupdate_DECRYPT_final(void *dgst, unsigned char *buf, int *outlen) +{ + swupdate_decrypt_lib *lib; + if (!get_cryptolib()) + return -EINVAL; + lib = (swupdate_decrypt_lib *)current[DECRYPTLIB]->lib; + return lib->DECRYPT_final(dgst, buf, outlen); +} + +void swupdate_DECRYPT_cleanup(void *dgst) +{ + swupdate_decrypt_lib *lib; + if (!get_cryptolib()) + return; + lib = (swupdate_decrypt_lib *)current[DECRYPTLIB]->lib; + return lib->DECRYPT_cleanup(dgst); +} + +void *swupdate_HASH_init(const char *SHAlength) +{ + swupdate_HASH_lib *lib; + + if (!get_HASHlib()) + return NULL; + lib = (swupdate_HASH_lib *)current[HASHLIB]->lib; + + return lib->HASH_init(SHAlength); +} + +int swupdate_HASH_update(void *dgst, const unsigned char *buf, size_t len) +{ + swupdate_HASH_lib *lib; + + if (!get_HASHlib()) + return -EFAULT; + lib = (swupdate_HASH_lib *)current[HASHLIB]->lib; + + return lib->HASH_update(dgst, buf, len); +} + +int swupdate_HASH_final(void *dgst, unsigned char *md_value, unsigned int *md_len) +{ + swupdate_HASH_lib *lib; + + if (!get_HASHlib()) + return -EFAULT; + lib = (swupdate_HASH_lib *)current[HASHLIB]->lib; + + return lib->HASH_final(dgst, md_value, md_len); +} + +int swupdate_HASH_compare(const unsigned char *hash1, const unsigned char *hash2) +{ + swupdate_HASH_lib *lib; + + if (!get_HASHlib()) + return -EFAULT; + lib = (swupdate_HASH_lib *)current[HASHLIB]->lib; + + return lib->HASH_compare(hash1, hash2); +} + +void swupdate_HASH_cleanup(void *dgst) +{ + swupdate_HASH_lib *lib; + + if (!get_HASHlib()) + return; + lib = (swupdate_HASH_lib *)current[HASHLIB]->lib; + + lib->HASH_cleanup(dgst); +} + +int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile) +{ + swupdate_dgst_lib *lib; + + if (!get_dgstlib()) + return -EFAULT; + lib = (swupdate_dgst_lib *)current[DGSTLIB]->lib; + + return lib->dgst_init(sw, keyfile); +} + +int swupdate_verify_file(void *dgst, const char *sigfile, + const char *file, const char *signer_name) +{ + swupdate_dgst_lib *lib; + + if (!get_dgstlib()) + return -EFAULT; + lib = (swupdate_dgst_lib *)current[DGSTLIB]->lib; + + return lib->verify_file(dgst, sigfile, file, signer_name); +} diff -Nru swupdate-2024.12.1+dfsg/core/decrypt_keys.c swupdate-2025.12+dfsg/core/decrypt_keys.c --- swupdate-2024.12.1+dfsg/core/decrypt_keys.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/core/decrypt_keys.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,194 @@ +/* + * (C) Copyright 2025 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "generated/autoconf.h" + +/* + * key is 256 bit for max aes_256 + * or is a pkcs#11 URL + * or a cms key file name + * keylen is the actual aes key length + * ivt is 128 bit + */ +struct decryption_key { + char *key; + char keylen; + unsigned char *ivt; + cipher_t cipher; +}; + +static struct decryption_key *decrypt_keys = NULL; + +int set_filename_as_key(const char *fname, cipher_t cipher) +{ + size_t len; + if (!decrypt_keys) { + decrypt_keys = (struct decryption_key *)calloc(1, sizeof(*decrypt_keys)); + if (!decrypt_keys) + return -ENOMEM; + } + len = strlen(fname); + + if (decrypt_keys->key) + free(decrypt_keys->key); + + decrypt_keys->key = calloc(1, len + 1); + if (!decrypt_keys->key) + return -ENOMEM; + + decrypt_keys->keylen = len; + strncpy(decrypt_keys->key, fname, len); + decrypt_keys->cipher = cipher; + return 0; +} + +int set_aes_key(const char *key, const char *ivt) +{ + int ret; + size_t keylen; + bool is_pkcs11 = false; + + /* + * Allocates the global structure just once + */ + if (!decrypt_keys) { + decrypt_keys = (struct decryption_key *)calloc(1, sizeof(*decrypt_keys)); + if (!decrypt_keys) + return -ENOMEM; + } + + if (strlen(ivt) != (AES_BLK_SIZE*2) || !is_hex_str(ivt)) { + ERROR("Invalid ivt"); + return -EINVAL; + } + + if (decrypt_keys->ivt) + free(decrypt_keys->ivt); + + decrypt_keys->ivt = calloc(1, AES_BLK_SIZE); + if (!decrypt_keys->ivt) + return -ENOMEM; + + ret = ascii_to_bin(decrypt_keys->ivt, AES_BLK_SIZE, ivt); + keylen = strlen(key); + + if (!strcmp("pkcs11", key)) { + is_pkcs11 = true; + decrypt_keys->keylen = keylen; + + } else { + switch (keylen) { + case AES_128_KEY_LEN * 2: + case AES_192_KEY_LEN * 2: + case AES_256_KEY_LEN * 2: + // valid hex string size for AES 128/192/256 + decrypt_keys->keylen = keylen / 2; + break; + default: + ERROR("Invalid decrypt_keys length"); + return -EINVAL; + } + } + + decrypt_keys->cipher = AES_CBC; + + if (decrypt_keys->key) + free(decrypt_keys->key); + + decrypt_keys->key = calloc(1, keylen + 1); + if (!decrypt_keys->key) + return -ENOMEM; + + if (is_pkcs11) { + strncpy(decrypt_keys->key, key, keylen); + } else { + ret |= !is_hex_str(key); + ret |= ascii_to_bin((unsigned char *)decrypt_keys->key, decrypt_keys->keylen, key); + } + + if (ret) { + ERROR("Invalid decrypt_keys"); + return -EINVAL; + } + + return 0; +} + +int load_decryption_key(char *fname) +{ + FILE *fp; + char *b1 = NULL, *b2 = NULL; + int ret; + +#ifdef CONFIG_ASYM_ENCRYPTED_SW_DESCRIPTION + return set_filename_as_key(fname, CMS); +#endif + + fp = fopen(fname, "r"); + if (!fp) + return -EBADF; + + ret = fscanf(fp, "%ms %ms", &b1, &b2); + switch (ret) { + case 2: + DEBUG("Read decryption key and initialization vector from file %s.", fname); + break; + default: + if (b1 != NULL) + free(b1); + fprintf(stderr, "File with decryption key is not in the format \n"); + fclose(fp); + return -EINVAL; + } + fclose(fp); + + ret = set_aes_key(b1, b2); + + if (b1 != NULL) + free(b1); + if (b2 != NULL) + free(b2); + + if (ret) { + fprintf(stderr, "Keys are invalid\n"); + return -EINVAL; + } + + return 0; +} + +char *swupdate_get_decrypt_key(void) { + if (!decrypt_keys) + return NULL; + return decrypt_keys->key; +} + +char swupdate_get_decrypt_keylen(void) { + if (!decrypt_keys) + return -1; + return decrypt_keys->keylen; +} + +unsigned char *get_aes_ivt(void) { + if (!decrypt_keys) + return NULL; + return decrypt_keys->ivt; +} + +cipher_t swupdate_get_decrypt_cipher(void) { + if (!decrypt_keys) + return AES_UNKNOWN; + return decrypt_keys->cipher; +} diff -Nru swupdate-2024.12.1+dfsg/core/installer.c swupdate-2025.12+dfsg/core/installer.c --- swupdate-2024.12.1+dfsg/core/installer.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/installer.c 2025-12-03 11:32:03.000000000 +0000 @@ -157,6 +157,8 @@ .hash = script->sha256, .encrypted = script->is_encrypted, .imgivt = script->ivt_ascii, + .imgaes = script->aes_ascii, + .cipher = script->cipher, }; ret = copyfile(©); close(fdin); @@ -308,7 +310,7 @@ return ret; } -static int update_installed_image_version(struct swver *sw_ver_list, +int update_installed_image_version(struct swver *sw_ver_list, struct img_type *img) { struct sw_version *swver; @@ -591,7 +593,7 @@ DEBUG("Dry run, skipping Pre-update command"); } else { DEBUG("Running Pre-update command"); - return run_system_cmd(swcfg->preupdatecmd); + return run_system_cmd(swcfg->update_type->preupdatecmd); } } @@ -607,7 +609,7 @@ DEBUG("Dry run, skipping Post-update command"); } else { DEBUG("Running Post-update command"); - return run_system_cmd(swcfg->postupdatecmd); + return run_system_cmd(swcfg->update_type->postupdatecmd); } } diff -Nru swupdate-2024.12.1+dfsg/core/network_thread.c swupdate-2025.12+dfsg/core/network_thread.c --- swupdate-2024.12.1+dfsg/core/network_thread.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/network_thread.c 2025-12-03 11:32:03.000000000 +0000 @@ -572,17 +572,20 @@ break; case SET_AES_KEY: -#ifndef CONFIG_PKCS11 - msg.type = ACK; - if (set_aes_key(msg.data.aeskeymsg.key_ascii, msg.data.aeskeymsg.ivt_ascii)) -#endif + if (IS_STR_EQUAL(msg.data.aeskeymsg.key_ascii, "pkcs11")) msg.type = NACK; + else { + msg.type = ACK; + if (set_aes_key(msg.data.aeskeymsg.key_ascii, msg.data.aeskeymsg.ivt_ascii)) + msg.type = NACK; + } break; case SET_VERSIONS_RANGE: msg.type = ACK; - set_version_range(msg.data.versions.minimum_version, + if (set_version_range(msg.data.versions.update_type, + msg.data.versions.minimum_version, msg.data.versions.maximum_version, - msg.data.versions.current_version); + msg.data.versions.current_version)) msg.type = NACK; break; case GET_HW_REVISION: cfg = get_swupdate_cfg(); diff -Nru swupdate-2024.12.1+dfsg/core/parser.c swupdate-2025.12+dfsg/core/parser.c --- swupdate-2024.12.1+dfsg/core/parser.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/parser.c 2025-12-03 11:32:03.000000000 +0000 @@ -13,10 +13,10 @@ #include #include "swupdate.h" #include "parsers.h" -#include "sslapi.h" #include "util.h" #include "progress.h" #include "handler.h" +#include "swupdate_crypto.h" static parser_fn parsers[] = { parse_cfg, @@ -130,6 +130,18 @@ return 0; } +struct swupdate_type_cfg *swupdate_find_update_type(struct swupdate_type_list *list, const char *name) +{ + struct swupdate_type_cfg *type_cfg = NULL; + + LIST_FOREACH(type_cfg, list, next) { + if (!strcmp(type_cfg->type_name, name)) { + return type_cfg; + } + } + return NULL; +} + int parse(struct swupdate_cfg *sw, const char *descfile) { int ret = -1; @@ -152,6 +164,7 @@ #endif char *errors[ARRAY_SIZE(parsers)] = {0}; + for (unsigned int i = 0; i < ARRAY_SIZE(parsers); i++) { current = parsers[i]; @@ -216,14 +229,36 @@ #endif /* + * Check if a an Update Type is set and + * load the configuration + */ + struct swupdate_type_cfg *update_type; + if (!strnlen(sw->update_type_name, sizeof(sw->update_type_name) - 1)) { + if (sw->update_type_required) { + ERROR("Update Type is mandatory but it was not set"); + return -EINVAL; + } else + strlcpy(sw->update_type_name, + "default", + sizeof(sw->update_type_name)); + } + update_type = swupdate_find_update_type(&sw->swupdate_types, sw->update_type_name); + if (!update_type) { + ERROR("Requested Update of Type %s but it is not configured", sw->update_type_name); + return -EINVAL; + } + + sw->update_type = update_type; + + /* * If downgrading is not allowed, convert * versions in numbers to be compared and check to get a * newer version */ - if (sw->no_downgrading) { - if (compare_versions(sw->version, sw->minimum_version) < 0) { + if (sw->update_type->no_downgrading) { + if (compare_versions(sw->version, sw->update_type->minimum_version) < 0) { ERROR("No downgrading allowed: new version %s < installed %s", - sw->version, sw->minimum_version); + sw->version, sw->update_type->minimum_version); return -EPERM; } } @@ -233,10 +268,10 @@ * versions in numbers to be compared and check to get a * newer version */ - if (sw->check_max_version) { - if (compare_versions(sw->version, sw->maximum_version) > 0) { + if (sw->update_type->check_max_version) { + if (compare_versions(sw->version, sw->update_type->maximum_version) > 0) { ERROR("Max version set: new version %s > max allowed %s", - sw->version, sw->maximum_version); + sw->version, sw->update_type->maximum_version); return -EPERM; } } @@ -245,11 +280,11 @@ * If reinstalling is not allowed, compare * version strings */ - if (sw->no_reinstalling) { + if (sw->update_type->no_reinstalling) { - if (strcmp(sw->version, sw->current_version) == 0) { + if (strcmp(sw->version, sw->update_type->current_version) == 0) { ERROR("No reinstalling allowed: new version %s == installed %s", - sw->version, sw->current_version); + sw->version, sw->update_type->current_version); return -EPERM; } } diff -Nru swupdate-2024.12.1+dfsg/core/parsing_library.c swupdate-2025.12+dfsg/core/parsing_library.c --- swupdate-2024.12.1+dfsg/core/parsing_library.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/parsing_library.c 2025-12-03 11:32:03.000000000 +0000 @@ -164,6 +164,36 @@ return false; } +bool is_field_bool(parsertype p, void *e, const char *path) +{ + switch (p) { + case LIBCFG_PARSER: + return is_field_bool_cfg((config_setting_t *)e, path); + case JSON_PARSER: + return is_field_bool_json((json_object *)e, path); + default: + (void)e; + (void)path; + } + return false; +} + +bool is_field_string(parsertype p, void *e, const char *path) +{ + switch (p) { + case LIBCFG_PARSER: + return is_field_string_cfg((config_setting_t *)e, path); + case JSON_PARSER: + return is_field_string_json((json_object *)e, path); + default: + (void)e; + (void)path; + } + return false; +} + + + void get_field(parsertype p, void *e, const char *path, void *dest, field_type_t type) { switch (p) { @@ -239,7 +269,7 @@ ascii_to_hash(hash, hash_ascii); } -bool set_find_path(const char **nodes, const char *newpath, char ***tmp) +bool set_find_path(const char **nodes, const char *newpath) { char **paths; unsigned int count; @@ -329,7 +359,7 @@ } free(ref); - *tmp = paths; + free(paths); return true; } diff -Nru swupdate-2024.12.1+dfsg/core/progress_thread.c swupdate-2025.12+dfsg/core/progress_thread.c --- swupdate-2024.12.1+dfsg/core/progress_thread.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/progress_thread.c 2025-12-03 11:32:03.000000000 +0000 @@ -333,6 +333,7 @@ conn = (struct progress_conn *)calloc(1, sizeof(*conn)); if (!conn) { ERROR("Out of memory, skipping..."); + close(connfd); continue; } conn->sockfd = connfd; diff -Nru swupdate-2024.12.1+dfsg/core/stream_interface.c swupdate-2025.12+dfsg/core/stream_interface.c --- swupdate-2024.12.1+dfsg/core/stream_interface.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/stream_interface.c 2025-12-03 11:32:03.000000000 +0000 @@ -45,6 +45,7 @@ #include "state.h" #include "bootloader.h" #include "hw-compatibility.h" +#include "swupdate_crypto.h" #define BUFF_SIZE 4096 #define PERCENT_LB_INDEX 4 @@ -58,6 +59,16 @@ static pthread_t network_thread_id; +static bool check_reboot_enabled(struct swupdate_cfg *sw) { + if (sw->reboot_enabled != REBOOT_UNSET) + return sw->reboot_enabled == REBOOT_ENABLED; + struct swupdate_type_cfg *typecfg = sw->update_type; + if (typecfg->reboot_enabled != REBOOT_UNSET) + return typecfg->reboot_enabled == REBOOT_ENABLED; + + return true; +} + /* * NOTE: these sync vars are _not_ static, they are _shared_ between the * installer, the display thread and the network thread @@ -78,28 +89,37 @@ { char output_file[MAX_IMAGE_FNAME]; struct filehdr fdh; - int fdout; + int fdout = -1; uint32_t checksum; const char* TMPDIR = get_tmpdir(); + cipher_t cipher = AES_CBC; + int ret = -1; + +#ifdef CONFIG_ASYM_ENCRYPTED_SW_DESCRIPTION + const char *cryptolib = get_cryptolib(); + cipher = CMS; + if (encrypted) + set_cryptolib("opensslCMS"); +#endif if (extract_cpio_header(fd, &fdh, poffs)) { - return -1; + goto err; } if (strcmp(fdh.filename, fname)) { TRACE("description file name not the first of the list: %s instead of %s", fdh.filename, fname); - return -1; + goto err; } if (snprintf(output_file, sizeof(output_file), "%s%s", TMPDIR, fdh.filename) >= (int)sizeof(output_file)) { ERROR("Path too long: %s%s", TMPDIR, fdh.filename); - return -1; + goto err; } if (max_size && fdh.size >= max_size) { ERROR("%s size (%ld) exceeds configured max of %d, aborting", fdh.filename, fdh.size, max_size); - return -1; + goto err; } TRACE("Found file"); TRACE("\tfilename %s", fdh.filename); @@ -107,7 +127,7 @@ fdout = openfileoutput(output_file); if (fdout < 0) - return -1; + goto err; struct swupdate_copy copy = { .fdin = fd, @@ -116,18 +136,23 @@ .offs = poffs, .checksum = &checksum, .encrypted = encrypted, + .cipher = cipher, }; if (copyfile(©) < 0) { - close(fdout); - return -1; + goto err; } if (!swupdate_verify_chksum(checksum, &fdh)) { - close(fdout); - return -1; + goto err; } - close(fdout); - - return 0; + ret = 0; +err: +#ifdef CONFIG_ASYM_ENCRYPTED_SW_DESCRIPTION + if (encrypted) + set_cryptolib(cryptolib); +#endif + if (fdout >= 0) + close(fdout); + return ret; } static bool update_transaction_state(struct swupdate_cfg *software, update_state_t newstate) @@ -319,6 +344,9 @@ ERROR("Error streaming %s", img->fname); return -1; } + + update_installed_image_version(&software->installed_sw_list, img); + TRACE("END INSTALLING STREAMING"); break; } @@ -562,6 +590,7 @@ struct swupdate_cfg *software = data; struct swupdate_request *req; struct swupdate_parms parms; + bool do_reboot = true; /* No installation in progress */ memset(&inst, 0, sizeof(inst)); @@ -711,10 +740,15 @@ * if this update is signalled to be without reboot (On the Fly), * send a notification via progress for processes responsible for booting */ - if (!software->reboot_required) { + do_reboot = check_reboot_enabled(software); + if (!do_reboot) { swupdate_progress_info(RUN, CAUSE_REBOOT_MODE , "{ \"reboot-mode\" : \"no-reboot\"}"); } + if (software->parms.dry_run) { + swupdate_progress_info(RUN, CAUSE_DRY_RUN_MODE , "{ \"dry-run\" : true }"); + } + ret = install_images(software); if (ret != 0) { update_transaction_state(software, STATE_FAILED); @@ -778,7 +812,7 @@ */ if (req->source == SOURCE_SURICATTA && /*simple check without JSON */ strstr(req->info, "hawkbit") && - !software->reboot_required) { + !do_reboot) { ipc_message msg; size_t size = sizeof(msg.data.procmsg.buf); char *buf = msg.data.procmsg.buf; @@ -845,27 +879,44 @@ return len; } +bool is_dryrun_install(void) +{ + return inst.software->parms.dry_run; +} + sourcetype get_install_source(void) { return inst.req.source; } -void set_version_range(const char *minversion, +int set_version_range(const char *update_type, const char *minversion, const char *maxversion, const char *current) { + struct swupdate_type_cfg *typecfg; + typecfg = swupdate_find_update_type(&inst.software->swupdate_types, + update_type && strnlen(update_type, SWUPDATE_GENERAL_STRING_SIZE) ? + update_type : "default"); + + if (!typecfg) { + ERROR("Configuration setup for Update Type not found"); + return -EINVAL; + } + if (minversion && strnlen(minversion, SWUPDATE_GENERAL_STRING_SIZE)) { - strlcpy(inst.software->minimum_version, minversion, - sizeof(inst.software->minimum_version)); - inst.software->no_downgrading = true; + strlcpy(typecfg->minimum_version, minversion, + sizeof(typecfg->minimum_version)); + typecfg->no_downgrading = true; } if (maxversion && strnlen(maxversion, SWUPDATE_GENERAL_STRING_SIZE)) { - strlcpy(inst.software->maximum_version, maxversion, - sizeof(inst.software->maximum_version)); - inst.software->check_max_version = true; + strlcpy(typecfg->maximum_version, maxversion, + sizeof(typecfg->maximum_version)); + typecfg->check_max_version = true; } if (current && strnlen(current, SWUPDATE_GENERAL_STRING_SIZE)) { - strlcpy(inst.software->current_version, current, - sizeof(inst.software->current_version)); - inst.software->no_reinstalling = true; + strlcpy(typecfg->current_version, current, + sizeof(typecfg->current_version)); + typecfg->no_reinstalling = true; } + + return 0; } diff -Nru swupdate-2024.12.1+dfsg/core/swupdate.c swupdate-2025.12+dfsg/core/swupdate.c --- swupdate-2024.12.1+dfsg/core/swupdate.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/swupdate.c 2025-12-03 11:32:03.000000000 +0000 @@ -40,7 +40,6 @@ #include "download_interface.h" #include "network_ipc.h" #include "network_utils.h" -#include "sslapi.h" #include "suricatta/suricatta.h" #include "delta_process.h" #include "progress.h" @@ -52,6 +51,7 @@ #include "versions.h" #include "hw-compatibility.h" #include "swupdate_vars.h" +#include "swupdate_crypto.h" #ifdef CONFIG_SYSTEMD #include @@ -101,9 +101,19 @@ #if defined(CONFIG_SIGALG_CMS) && !defined(CONFIG_SSL_IMPL_WOLFSSL) {"forced-signer-name", required_argument, NULL, '2'}, #endif +#ifdef CONFIG_SIGALG_GPG + {"gpg-home-dir", required_argument, NULL, '4'}, + {"gpg-protocol", required_argument, NULL, '5'}, +#endif + {"digest-provider", required_argument, NULL, '6'}, #endif #ifdef CONFIG_ENCRYPTED_IMAGES {"key-aes", required_argument, NULL, 'K'}, + {"decryption-key", required_argument, NULL, 'K'}, + {"decrypt-provider", required_argument, NULL, '8'}, +#endif +#ifdef CONFIG_HASH_VERIFY + {"hash-provider", required_argument, NULL, '7'}, #endif {"loglevel", required_argument, NULL, 'l'}, {"max-version", required_argument, NULL, '3'}, @@ -155,7 +165,7 @@ " -l, --loglevel : logging level\n" " -L, --syslog : enable syslog logger\n" #ifdef CONFIG_SIGNED_IMAGES -#ifndef CONFIG_SIGALG_GPG + " --digest-provider : the provider used to verify the update\n" " -k, --key : file with public key to verify images\n" " --cert-purpose : set expected certificate purpose\n" " [emailProtection|codeSigning] (default: emailProtection)\n" @@ -163,11 +173,19 @@ " --forced-signer-name : set expected common name of signer certificate\n" #endif " --ca-path : path to the Certificate Authority (PEM)\n" +#ifdef CONFIG_SIGALG_GPG + " For GnuPG only:\n" + " --gpg-home-dir : path where the GPG ring and keys are stored\n" + " --gpg-protocol : supported protocol, openpgp or cms\n" #endif #endif #ifdef CONFIG_ENCRYPTED_IMAGES - " -K, --key-aes : the file contains the symmetric key to be used\n" - " to decrypt images\n" + " -K, --key-aes : the file contains the decryption key to be used\n" + " --decryption-key : to decrypt images\n" + " --decrypt-provider : the provider used for decryption\n" +#endif +#ifdef CONFIG_HASH_VERIFY + " --hash-provider : the provider used to perform hashes\n" #endif " -n, --dry-run : run SWUpdate without installing the software\n" " -N, --no-downgrading : not install a release older as \n" @@ -265,6 +283,12 @@ static void swupdate_init(struct swupdate_cfg *sw) { + struct swupdate_type_cfg *update_type = calloc (1, sizeof(*update_type)); + if (!update_type) { + /* this should never happen */ + fprintf(stderr, "OOM at startup, exiting...\n"); + exit(EXIT_FAILURE); + } /* Initialize internal tree to store configuration */ memset(sw, 0, sizeof(*sw)); LIST_INIT(&sw->images); @@ -272,7 +296,12 @@ LIST_INIT(&sw->scripts); LIST_INIT(&sw->bootloader); LIST_INIT(&sw->extprocs); - sw->cert_purpose = SSL_PURPOSE_DEFAULT; + LIST_INIT(&sw->swupdate_types); + strlcpy(update_type->type_name, "default", sizeof(update_type->type_name)); + LIST_INSERT_HEAD(&sw->swupdate_types, update_type, next); + sw->update_type = update_type; + + sw->cert_purpose = CERT_PURPOSE_EMAIL_PROT; #ifdef CONFIG_MTD mtd_init(); @@ -286,15 +315,55 @@ static const char EMAIL_PROT[] = "emailProtection"; if (strncmp(CODE_SIGN, text, sizeof(CODE_SIGN)) == 0) - return SSL_PURPOSE_CODE_SIGN; + return CERT_PURPOSE_CODE_SIGN; if (strncmp(EMAIL_PROT, text, sizeof(EMAIL_PROT)) == 0) - return SSL_PURPOSE_EMAIL_PROT; + return CERT_PURPOSE_EMAIL_PROT; ERROR("unknown certificate purpose '%s'\n", text); exit(EXIT_FAILURE); } +static void read_updatetype_settings(void *elem, struct swupdate_type_cfg *typecfg) +{ + char tmp[SWUPDATE_GENERAL_STRING_SIZE] = ""; + + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "postupdatecmd", typecfg->postupdatecmd); + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "preupdatecmd", typecfg->preupdatecmd); + + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "no-downgrading", typecfg->minimum_version); + + if (strlen(typecfg->minimum_version)) + typecfg->no_downgrading = true; + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "max-version", typecfg->maximum_version); + if (strlen(typecfg->maximum_version)) + typecfg->check_max_version = true; + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "no-reinstalling", typecfg->current_version); + if (strlen(typecfg->current_version)) + typecfg->no_reinstalling = true; + + /* + * As default, reboot is initiated + */ + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "reboot", tmp); + typecfg->reboot_enabled = REBOOT_UNSET; + if (tmp[0] != '\0') { + if (!is_enabled_or_disabled(tmp)) { + WARN("Possible mismatch for reboot attribute, it is neither enabled nor disabled"); + } else if (is_enabled(tmp)) { + typecfg->reboot_enabled = REBOOT_ENABLED; + } else { + typecfg->reboot_enabled = REBOOT_DISABLED; + } + } +} + static int read_globals_settings(void *elem, void *data) { char tmp[SWUPDATE_GENERAL_STRING_SIZE] = ""; @@ -316,11 +385,9 @@ GET_FIELD_STRING(LIBCFG_PARSER, elem, "aes-key-file", sw->aeskeyfname); GET_FIELD_STRING(LIBCFG_PARSER, elem, - "mtd-blacklist", sw->mtdblacklist); - GET_FIELD_STRING(LIBCFG_PARSER, elem, - "postupdatecmd", sw->postupdatecmd); + "decryption-key-file", sw->aeskeyfname); GET_FIELD_STRING(LIBCFG_PARSER, elem, - "preupdatecmd", sw->preupdatecmd); + "mtd-blacklist", sw->mtdblacklist); GET_FIELD_STRING(LIBCFG_PARSER, elem, "namespace-vars", sw->namespace_for_vars); GET_FIELD_STRING(LIBCFG_PARSER, elem, @@ -333,8 +400,7 @@ GET_FIELD_BOOL(LIBCFG_PARSER, elem, "verbose", &sw->verbose); GET_FIELD_INT(LIBCFG_PARSER, elem, "loglevel", &sw->loglevel); GET_FIELD_BOOL(LIBCFG_PARSER, elem, "syslog", &sw->syslog_enabled); - GET_FIELD_STRING(LIBCFG_PARSER, elem, - "no-downgrading", sw->minimum_version); + GET_FIELD_BOOL(LIBCFG_PARSER, elem, "update-type-required", &sw->update_type_required); tmp[0] = '\0'; GET_FIELD_STRING(LIBCFG_PARSER, elem, "fwenv-config-location", tmp); @@ -342,16 +408,6 @@ set_fwenv_config(tmp); tmp[0] = '\0'; } - if (strlen(sw->minimum_version)) - sw->no_downgrading = true; - GET_FIELD_STRING(LIBCFG_PARSER, elem, - "max-version", sw->maximum_version); - if (strlen(sw->maximum_version)) - sw->check_max_version = true; - GET_FIELD_STRING(LIBCFG_PARSER, elem, - "no-reinstalling", sw->current_version); - if (strlen(sw->current_version)) - sw->no_reinstalling = true; GET_FIELD_STRING(LIBCFG_PARSER, elem, "cert-purpose", tmp); if (tmp[0] != '\0') @@ -361,6 +417,16 @@ char software_select[SWUPDATE_GENERAL_STRING_SIZE] = ""; GET_FIELD_STRING(LIBCFG_PARSER, elem, "select", software_select); + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "gpg-home-dir", sw->gpg_home_directory); + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "gpgme-protocol", sw->gpgme_protocol); + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "hash-provider", sw->hash_provider); + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "decrypt-provider", sw->decrypt_provider); + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "digest-provider", sw->digest_provider); if (software_select[0] != '\0') { /* by convention, errors in a configuration section are ignored */ (void)parse_image_selector(software_select, sw); @@ -372,6 +438,9 @@ GET_FIELD_INT(LIBCFG_PARSER, elem, "sw-description-max-size", &sw->swdesc_max_size); + + read_updatetype_settings(elem, sw->update_type); + return 0; } @@ -440,6 +509,61 @@ return 0; } +static int read_type_settings(void *settings, void *data) +{ + struct swupdate_cfg *sw = (struct swupdate_cfg *)data; + void *elem; + int count, i; + count = get_array_length(LIBCFG_PARSER, settings); + struct swupdate_type_cfg *current = LIST_FIRST(&sw->swupdate_types); + + for(i = 0; i < count; ++i) { + elem = get_elem_from_idx(LIBCFG_PARSER, settings, i); + + if (!elem) + continue; + + if(!(exist_field_string(LIBCFG_PARSER, elem, "name"))) + continue; + struct swupdate_type_cfg *update_type = calloc (1, sizeof(*update_type)); + if (!update_type) { + ERROR("OOM at startup"); + return -ENOMEM; + } + + GET_FIELD_STRING(LIBCFG_PARSER, elem, "name", update_type->type_name); + if (swupdate_find_update_type(&sw->swupdate_types, update_type->type_name)) { + WARN("Duplicated Update Type: %s, ignoring...", update_type->type_name); + free(update_type); + continue; + } + read_updatetype_settings(elem, update_type); + LIST_INSERT_AFTER(current, update_type, next); + current = update_type; + } + + return 0; +} + +static void print_registered_updatetypes(struct swupdate_cfg *sw) +{ + struct swupdate_type_cfg *update_type; + INFO("Registered : Update Types"); + LIST_FOREACH(update_type, &sw->swupdate_types, next) { + INFO("\tName:\t%s", update_type->type_name); + if (strlen(update_type->preupdatecmd)) + INFO("\t\tpreupdatecmd:\t%s", update_type->preupdatecmd); + if (strlen(update_type->postupdatecmd)) + INFO("\t\tpostupdatecmd:\t%s", update_type->postupdatecmd); + if (strlen(update_type->minimum_version)) + INFO("\t\tMin Version:\t%s", update_type->minimum_version); + if (strlen(update_type->maximum_version)) + INFO("\t\tMax Version:\t%s", update_type->maximum_version); + if (strlen(update_type->current_version)) + INFO("\t\tNo-Downgrading:\t%s", update_type->current_version); + } +} + static void sigterm_handler(int __attribute__ ((__unused__)) signum) { pthread_cancel(network_daemon); @@ -499,8 +623,8 @@ strcat(main_options, "H:"); #endif #ifdef CONFIG_SIGNED_IMAGES -#ifndef CONFIG_SIGALG_GPG strcat(main_options, "k:"); +#ifndef CONFIG_SIGALG_GPG public_key_mandatory = 1; #endif #endif @@ -531,7 +655,7 @@ char *root; int bootdev; case 'f': - cfgfname = strdup(optarg); + if (optarg) cfgfname = strdup(optarg); break; case 'g': root = get_root_device(); @@ -554,13 +678,13 @@ exit(EXIT_SUCCESS); break; case 'l': - loglevel = strtoul(optarg, NULL, 10); + if (optarg) loglevel = strtoul(optarg, NULL, 10); break; case 'v': loglevel = LASTLOGLEVEL; break; case '0': - printf("%s", BANNER); + printf("%s\n", BANNER); exit(EXIT_SUCCESS); } } @@ -599,6 +723,14 @@ exit(EXIT_FAILURE); } + /* + * Read configuration for different update types + */ + ret = read_module_settings(&handle, "update-types", read_type_settings, &swcfg); + if (ret && swcfg.update_type_required) { + WARN("Type is requested, but no type is configured, just default"); + } + loglevel = swcfg.verbose ? LASTLOGLEVEL : swcfg.loglevel; /* @@ -634,18 +766,20 @@ break; #ifdef CONFIG_UBIATTACH case 'b': - mtd_set_ubiblacklist(optarg); + if (optarg) mtd_set_ubiblacklist(optarg); break; #endif case 'i': - strlcpy(fname, optarg, sizeof(fname)); - opt_i = 1; + if (optarg) { + strlcpy(fname, optarg, sizeof(fname)); + opt_i = 1; + } break; case 'o': - strlcpy(swcfg.output, optarg, sizeof(swcfg.output)); + if (optarg) strlcpy(swcfg.output, optarg, sizeof(swcfg.output)); break; case 's': - strlcpy(swcfg.output_swversions, optarg, sizeof(swcfg.output_swversions)); + if (optarg) strlcpy(swcfg.output_swversions, optarg, sizeof(swcfg.output_swversions)); break; case 'B': if (set_bootloader(optarg) != 0) { @@ -655,7 +789,7 @@ } break; case 'l': - loglevel = strtoul(optarg, NULL, 10); + if (optarg) loglevel = strtoul(optarg, NULL, 10); break; case 'n': swcfg.parms.dry_run = true; @@ -664,38 +798,67 @@ swcfg.syslog_enabled = true; break; case 'k': - strlcpy(swcfg.publickeyfname, + if (optarg) strlcpy(swcfg.publickeyfname, optarg, sizeof(swcfg.publickeyfname)); break; case '1': - swcfg.cert_purpose = parse_cert_purpose(optarg); + if (optarg) swcfg.cert_purpose = parse_cert_purpose(optarg); break; case '2': - strlcpy(swcfg.forced_signer_name, optarg, + if (optarg) strlcpy(swcfg.forced_signer_name, optarg, sizeof(swcfg.forced_signer_name)); break; case '3': - swcfg.check_max_version = true; - strlcpy(swcfg.maximum_version, optarg, - sizeof(swcfg.maximum_version)); + if (optarg) { + swcfg.update_type->check_max_version = true; + strlcpy(swcfg.update_type->maximum_version, optarg, + sizeof(swcfg.update_type->maximum_version)); + } + break; + case '4': + strlcpy(swcfg.gpg_home_directory, + optarg, + sizeof(swcfg.gpg_home_directory)); + break; + case '5': + strlcpy(swcfg.gpgme_protocol, + optarg, + sizeof(swcfg.gpgme_protocol)); + break; + case '6': + strlcpy(swcfg.digest_provider, + optarg, + sizeof(swcfg.digest_provider)); + break; + case '7': + strlcpy(swcfg.hash_provider, + optarg, + sizeof(swcfg.hash_provider)); + break; + case '8': + strlcpy(swcfg.decrypt_provider, + optarg, + sizeof(swcfg.decrypt_provider)); break; #ifdef CONFIG_ENCRYPTED_IMAGES case 'K': - strlcpy(swcfg.aeskeyfname, - optarg, - sizeof(swcfg.aeskeyfname)); + if (optarg) strlcpy(swcfg.aeskeyfname, + optarg, + sizeof(swcfg.aeskeyfname)); break; #endif case 'N': - swcfg.no_downgrading = true; - strlcpy(swcfg.minimum_version, optarg, - sizeof(swcfg.minimum_version)); + swcfg.update_type->no_downgrading = true; + if (optarg) strlcpy(swcfg.update_type->minimum_version, optarg, + sizeof(swcfg.update_type->minimum_version)); break; case 'R': - swcfg.no_reinstalling = true; - strlcpy(swcfg.current_version, optarg, - sizeof(swcfg.current_version)); + if (optarg) { + swcfg.update_type->no_reinstalling = true; + strlcpy(swcfg.update_type->current_version, optarg, + sizeof(swcfg.update_type->current_version)); + } break; case 'M': swcfg.no_transaction_marker = true; @@ -706,8 +869,10 @@ TRACE("state_marker globally disabled"); break; case 'e': - software_select = optarg; - opt_e = 1; + if (optarg) { + software_select = optarg; + opt_e = 1; + } break; /* Configuration file already parsed, ignores it */ case 'f': @@ -733,7 +898,7 @@ exit(EXIT_FAILURE); break; case 'q': - dict_insert_value(&swcfg.accepted_set, "accepted", optarg); + if (optarg) dict_insert_value(&swcfg.accepted_set, "accepted", optarg); break; #ifdef CONFIG_SURICATTA case 'u': @@ -763,12 +928,12 @@ opt_c = true; break; case 'p': - strlcpy(swcfg.postupdatecmd, optarg, - sizeof(swcfg.postupdatecmd)); + if (optarg) strlcpy(swcfg.update_type->postupdatecmd, optarg, + sizeof(swcfg.update_type->postupdatecmd)); break; case 'P': - strlcpy(swcfg.preupdatecmd, optarg, - sizeof(swcfg.preupdatecmd)); + if (optarg )strlcpy(swcfg.update_type->preupdatecmd, optarg, + sizeof(swcfg.update_type->preupdatecmd)); break; default: fprintf(stdout, "Try %s -h for usage\n", argv[0]); @@ -828,15 +993,28 @@ swupdate_crypto_init(); - if (strlen(swcfg.publickeyfname) || strlen(swcfg.gpg_home_directory)) { - if (swupdate_dgst_init(&swcfg, swcfg.publickeyfname)) { - fprintf(stderr, - "Error: Crypto cannot be initialized.\n"); + if (strlen(swcfg.hash_provider)) { + if (set_HASHlib(swcfg.hash_provider)) { + ERROR("HASH provider %s cannot be set", swcfg.hash_provider); + exit(EXIT_FAILURE); + } + } + + if (strlen(swcfg.decrypt_provider)) { + if (set_cryptolib(swcfg.decrypt_provider)) { + ERROR("Decrypt provider %s cannot be set", swcfg.decrypt_provider); exit(EXIT_FAILURE); } } - printf("%s\n", BANNER); + if (strlen(swcfg.digest_provider)) { + if (set_dgstlib(swcfg.digest_provider)) { + ERROR("Verification provider %s cannot be set", swcfg.digest_provider); + exit(EXIT_FAILURE); + } + } + + printf("%s\n\n", BANNER); printf("Licensed under GPLv2. See source distribution for detailed " "copyright notices.\n\n"); @@ -854,6 +1032,18 @@ INFO("Using bootloader interface: %s", get_bootloader()); } + print_registered_updatetypes(&swcfg); + print_registered_cryptolib(); + +#ifdef CONFIG_SIGNED_IMAGES + if (strlen(swcfg.publickeyfname) || strlen(swcfg.gpg_home_directory)) { + if (swupdate_dgst_init(&swcfg, swcfg.publickeyfname)) { + ERROR("Error: Crypto cannot be initialized.\n"); + exit(EXIT_FAILURE); + } + } +#endif + /* * Install a child handler to check if a subprocess * dies diff -Nru swupdate-2024.12.1+dfsg/core/util.c swupdate-2025.12+dfsg/core/util.c --- swupdate-2024.12.1+dfsg/core/util.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/core/util.c 2025-12-03 11:32:03.000000000 +0000 @@ -38,23 +38,6 @@ #include "generated/autoconf.h" /* - * key is 256 bit for max aes_256 - * keylen is the actual aes key length - * ivt is 128 bit - */ -struct decryption_key { -#ifdef CONFIG_PKCS11 - char * key; -#else - unsigned char key[AES_256_KEY_LEN]; -#endif - char keylen; - unsigned char ivt[AES_BLK_SIZE]; -}; - -static struct decryption_key *aes_key = NULL; - -/* * Configuration file for fw_env.config */ @@ -509,63 +492,6 @@ return count; } -int load_decryption_key(char *fname) -{ - FILE *fp; - char *b1 = NULL, *b2 = NULL; - int ret; - - fp = fopen(fname, "r"); - if (!fp) - return -EBADF; - - ret = fscanf(fp, "%ms %ms", &b1, &b2); - switch (ret) { - case 2: - DEBUG("Read decryption key and initialization vector from file %s.", fname); - break; - default: - if (b1 != NULL) - free(b1); - fprintf(stderr, "File with decryption key is not in the format \n"); - fclose(fp); - return -EINVAL; - } - fclose(fp); - - ret = set_aes_key(b1, b2); - - if (b1 != NULL) - free(b1); - if (b2 != NULL) - free(b2); - - if (ret) { - fprintf(stderr, "Keys are invalid\n"); - return -EINVAL; - } - - return 0; -} - -unsigned char *get_aes_key(void) { - if (!aes_key) - return NULL; - return aes_key->key; -} - -char get_aes_keylen(void) { - if (!aes_key) - return -1; - return aes_key->keylen; -} - -unsigned char *get_aes_ivt(void) { - if (!aes_key) - return NULL; - return aes_key->ivt; -} - bool is_hex_str(const char *ascii) { unsigned int i, size; @@ -583,57 +509,6 @@ return true; } -int set_aes_key(const char *key, const char *ivt) -{ - int ret; - size_t keylen; - - /* - * Allocates the global structure just once - */ - if (!aes_key) { - aes_key = (struct decryption_key *)calloc(1, sizeof(*aes_key)); - if (!aes_key) - return -ENOMEM; - } - - if (strlen(ivt) != (AES_BLK_SIZE*2) || !is_hex_str(ivt)) { - ERROR("Invalid ivt"); - return -EINVAL; - } - - ret = ascii_to_bin(aes_key->ivt, sizeof(aes_key->ivt), ivt); -#ifdef CONFIG_PKCS11 - keylen = strlen(key) + 1; - aes_key->key = malloc(keylen); - if (!aes_key->key) - return -ENOMEM; - strncpy(aes_key->key, key, keylen); -#else - keylen = strlen(key); - switch (keylen) { - case AES_128_KEY_LEN * 2: - case AES_192_KEY_LEN * 2: - case AES_256_KEY_LEN * 2: - // valid hex string size for AES 128/192/256 - aes_key->keylen = keylen / 2; - break; - default: - ERROR("Invalid aes_key length"); - return -EINVAL; - } - ret |= !is_hex_str(key); - ret |= ascii_to_bin(aes_key->key, aes_key->keylen, key); -#endif - - if (ret) { - ERROR("Invalid aes_key"); - return -EINVAL; - } - - return 0; -} - const char *get_fwenv_config(void) { if (!fwenv_config) #if defined(CONFIG_UBOOT) @@ -723,8 +598,10 @@ char **iter; if (!nodes) return; - for (iter = nodes; *iter != NULL; iter++) + for (iter = nodes; *iter != NULL; iter++) { free(*iter); + *iter = NULL; + } free(nodes); } @@ -1162,11 +1039,10 @@ } #define MAX_CMDLINE_LENGTH 4096 -static char *get_root_from_cmdline(void) +char **parse_linux_cmdline(void) { char *buf; FILE *fp; - char *root = NULL; int ret; char **parms = NULL; @@ -1190,10 +1066,25 @@ */ buf[MAX_CMDLINE_LENGTH - 1] = '\0'; + fclose(fp); + if (ret > 0) { parms = string_split(buf, ' '); if (!parms) goto out; + } +out: + free(buf); + return parms; +} + +static char *get_root_from_cmdline(void) +{ + char **parms = NULL; + char *root = NULL; + + parms = parse_linux_cmdline(); + if (parms) { int nparms = count_string_array((const char **)parms); for (unsigned int index = 0; index < nparms; index++) { if (!strncmp(parms[index], "root=", strlen("root="))) { @@ -1205,11 +1096,8 @@ break; } } + free_string_array(parms); } -out: - fclose(fp); - free_string_array(parms); - free(buf); return root; } diff -Nru swupdate-2024.12.1+dfsg/corelib/Makefile swupdate-2025.12+dfsg/corelib/Makefile --- swupdate-2024.12.1+dfsg/corelib/Makefile 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/Makefile 2025-12-03 11:32:03.000000000 +0000 @@ -5,37 +5,10 @@ lib-y += emmc_utils.o \ multipart_parser.o \ parsing_library_libjson.o \ - server_utils.o + server_utils.o \ + swupdate_settings.o \ + parsing_library_libconfig.o lib-$(CONFIG_DOWNLOAD) += downloader.o lib-$(CONFIG_MTD) += mtd-interface.o lib-$(CONFIG_LUA) += lua_interface.o lua_compat.o -ifeq ($(CONFIG_SSL_IMPL_OPENSSL)$(CONFIG_SSL_IMPL_WOLFSSL),y) -lib-$(CONFIG_HASH_VERIFY) += verify_signature.o -ifeq ($(CONFIG_PKCS11),y) -lib-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_pkcs11.o -else -lib-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_openssl.o -endif -lib-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify.o -lib-$(CONFIG_SIGALG_RSAPSS) += swupdate_rsa_verify.o -endif -ifeq ($(CONFIG_SSL_IMPL_OPENSSL),y) -lib-$(CONFIG_SIGALG_CMS) += swupdate_cms_verify.o -endif -ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) -lib-$(CONFIG_SIGALG_CMS) += swupdate_pkcs7_verify.o -endif -ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y) -lib-$(CONFIG_HASH_VERIFY) += verify_signature_mbedtls.o -ifeq ($(CONFIG_PKCS11),y) -lib-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_pkcs11.o -else -lib-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_mbedtls.o -endif -lib-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify_mbedtls.o -lib-$(CONFIG_SIGALG_RSAPSS) += swupdate_rsa_verify_mbedtls.o -endif -lib-$(CONFIG_SIGALG_GPG) += swupdate_gpg_verify.o -lib-$(CONFIG_LIBCONFIG) += swupdate_settings.o \ - parsing_library_libconfig.o lib-$(CONFIG_CHANNEL_CURL) += channel_curl.o diff -Nru swupdate-2024.12.1+dfsg/corelib/channel_curl.c swupdate-2025.12+dfsg/corelib/channel_curl.c --- swupdate-2024.12.1+dfsg/corelib/channel_curl.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/channel_curl.c 2025-12-03 11:32:03.000000000 +0000 @@ -23,7 +23,7 @@ #include #include #include "channel_op_res.h" -#include "sslapi.h" +#include "swupdate_crypto.h" #include "channel.h" #include "channel_curl.h" #include "progress.h" @@ -693,6 +693,21 @@ goto cleanup; } + if ((channel_data->api_key_header) && (channel_data->api_key)) { + char *header; + if (ENOMEM_ASPRINTF == asprintf(&header, "%s: %s", channel_data->api_key_header, channel_data->api_key)) { + result = CHANNEL_EINIT; + goto cleanup; + } + if (((channel_curl->header = curl_slist_append( + channel_curl->header, header)) == NULL)) { + free(header); + result = CHANNEL_EINIT; + goto cleanup; + } + free(header); + } + if (channel_data->received_headers || channel_data->headers) { if ((curl_easy_setopt(channel_curl->handle, CURLOPT_HEADERFUNCTION, diff -Nru swupdate-2024.12.1+dfsg/corelib/downloader.c swupdate-2025.12+dfsg/corelib/downloader.c --- swupdate-2024.12.1+dfsg/corelib/downloader.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/downloader.c 2025-12-03 11:32:03.000000000 +0000 @@ -254,6 +254,11 @@ return -EINVAL; } } + if (optind < argc) { + ERROR("Non-option or unrecognized argument(s) given to downloader (-d), see --help."); + download_print_help(); + return -EINVAL; + } /* * if URL is passed, this is a one time step diff -Nru swupdate-2024.12.1+dfsg/corelib/lua_interface.c swupdate-2025.12+dfsg/corelib/lua_interface.c --- swupdate-2024.12.1+dfsg/corelib/lua_interface.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/lua_interface.c 2025-12-03 11:32:03.000000000 +0000 @@ -16,6 +16,7 @@ #include #endif #include +#include #include "lua.h" #include "lauxlib.h" @@ -282,6 +283,8 @@ if (!strcmp(key, "compressed")) { if (!strcmp(value, "zlib")) { img->compressed = COMPRESSED_ZLIB; + } else if (!strcmp(value, "xz")) { + img->compressed = COMPRESSED_XZ; } else if (!strcmp(value, "zstd")) { img->compressed = COMPRESSED_ZSTD; } else { @@ -327,7 +330,9 @@ if (!strcmp(key, "ivt")) strncpy(img->ivt_ascii, value, sizeof(img->ivt_ascii)); - + if (!strcmp(key, "aes-key")) + strncpy(img->aes_ascii, value, + sizeof(img->aes_ascii)); if (!strncmp(key, offset, sizeof(offset))) { strncpy(seek_str, value, sizeof(seek_str)); @@ -511,6 +516,7 @@ LUA_PUSH_IMG_STRING(img, "data", type_data); LUA_PUSH_IMG_STRING(img, "filesystem", filesystem); LUA_PUSH_IMG_STRING(img, "ivt", ivt_ascii); + LUA_PUSH_IMG_STRING(img, "aes-key", aes_ascii); LUA_PUSH_IMG_BOOL(img, "installed_directly", install_directly); LUA_PUSH_IMG_BOOL(img, "install_if_different", id.install_if_different); @@ -529,6 +535,9 @@ case COMPRESSED_ZLIB: LUA_PUSH_IMG_STRING_VALUE(img, "compressed", "zlib"); break; + case COMPRESSED_XZ: + LUA_PUSH_IMG_STRING_VALUE(img, "compressed", "xz"); + break; case COMPRESSED_ZSTD: LUA_PUSH_IMG_STRING_VALUE(img, "compressed", "zstd"); break; @@ -887,6 +896,41 @@ return 1; } +static int l_get_cmdline(lua_State *L) { + char **parms = NULL; + + parms = parse_linux_cmdline(); + if (!parms) { + lua_pushnil(L); + return 1; + } + lua_newtable (L); + + int nparms = count_string_array((const char **)parms); + TRACE("parms=%d", nparms); + for (unsigned int index = 0; index < nparms; index++) { + char *equal = strchr(parms[index], '='); + const char *val; + if (equal) { + val = equal + 1; + /* split into two strings */ + *equal = '\0'; + } else { + /* + * A table cannot have a nil value + * so just set an empty string + */ + val = ""; + } + lua_pushstring(L, parms[index]); + lua_pushstring(L, val); + lua_settable(L, -3); + } + + free_string_array(parms); + return 1; +} + static int l_get_bootenv(lua_State *L) { const char *name = luaL_checkstring(L, 1); char *value = NULL; @@ -929,6 +973,11 @@ return 2; } +static int l_is_dry_run(lua_State *L) { + bool dryrun = is_dryrun_install(); + lua_pushboolean(L, dryrun); + return 1; +} static int l_get_tmpdir(lua_State *L) { @@ -944,7 +993,8 @@ static int l_progress_update(lua_State *L) { - lua_Number percent = luaL_checknumber (L, 1); + lua_Number percent = luaL_checknumber(L, 1); + lua_pop(L, 1); swupdate_progress_update((unsigned int) percent); return 0; } @@ -1001,12 +1051,10 @@ int lua_get_swupdate_version(lua_State *L) { - unsigned int version = 0, patchlevel = 0; - /* Deliberately ignore sublevel and extraversion. */ - if (sscanf(SWU_VER, "%u.%u.%*s", &version, &patchlevel) != 2) { - version = 0; - patchlevel = 0; - } + const unsigned int version = VERSION; + const unsigned int patchlevel = VERSION_PATCHLEVEL; + const unsigned int sublevel = VERSION_SUBLEVEL; + lua_newtable (L); lua_pushnumber(L, 1); lua_pushnumber(L, version); @@ -1014,8 +1062,12 @@ lua_pushnumber(L, 2); lua_pushnumber(L, patchlevel); lua_settable(L, -3); + lua_pushnumber(L, 3); + lua_pushnumber(L, sublevel); + lua_settable(L, -3); lua_push_enum(L, "version", version); lua_push_enum(L, "patchlevel", patchlevel); + lua_push_enum(L, "sublevel", sublevel); return 1; } @@ -1183,17 +1235,18 @@ * @brief Dispatch a message to the progress interface. * * @param [Lua] Message to dispatch to progress interface. + * @param [Lua] progress_cause_t number (optional), default: CAUSE_NONE * @return [Lua] nil. */ int lua_notify_progress(lua_State *L) { - /* - * NOTE: level is INFOLEVEL for the sake of specifying a level. - * It is unused in core/notifier.c :: progress_notifier() as the - * progress emitter doesn't know about log levels. - */ - notify(PROGRESS, RECOVERY_NO_ERROR, INFOLEVEL, luaL_checkstring(L, -1)); - lua_pop(L, 1); - return 0; + lua_Number cause = CAUSE_NONE; + if (lua_isnumber(L, -1) == 1) { + cause = lua_tonumber(L, -1); + lua_pop(L, 1); + } + notify(PROGRESS, (progress_cause_t)cause, INFOLEVEL, luaL_checkstring(L, -1)); + lua_pop(L, 1); + return 0; } /** @@ -1214,6 +1267,7 @@ { "getversion", lua_get_swupdate_version }, { "progress", lua_notify_progress }, { "emmcbootpart", l_get_emmc_bootpart }, + { "is_dryrun", l_is_dry_run }, { NULL, NULL } }; @@ -1221,6 +1275,7 @@ { "get_bootenv", l_get_bootenv }, { "set_bootenv", l_set_bootenv }, { "get_selection", l_get_selection }, + { "get_cmdline", l_get_cmdline }, { NULL, NULL } }; diff -Nru swupdate-2024.12.1+dfsg/corelib/mtd-interface.c swupdate-2025.12+dfsg/corelib/mtd-interface.c --- swupdate-2024.12.1+dfsg/corelib/mtd-interface.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/mtd-interface.c 2025-12-03 11:32:03.000000000 +0000 @@ -289,6 +289,7 @@ err = ubi_get_vol_info1(libubi, info->dev_info.dev_num, i, &ubi_part->vol_info); if (err == -1) { + free(ubi_part); if (errno == ENOENT || errno == ENODEV) continue; diff -Nru swupdate-2024.12.1+dfsg/corelib/parsing_library_libconfig.c swupdate-2025.12+dfsg/corelib/parsing_library_libconfig.c --- swupdate-2024.12.1+dfsg/corelib/parsing_library_libconfig.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/parsing_library_libconfig.c 2025-12-03 11:32:03.000000000 +0000 @@ -37,6 +37,22 @@ } } +static bool get_field_type(config_setting_t *e, const char *path, int *type) +{ + config_setting_t *elem; + + if (path) + elem = config_setting_lookup(e, path); + else + elem = e; + + if (!elem) + return false; + + *type = config_setting_type(elem); + + return true; +} static void get_value_libconfig(const config_setting_t *e, const char *path, void *dest, field_type_t expected_type) { @@ -103,24 +119,37 @@ bool is_field_numeric_cfg(config_setting_t *e, const char *path) { - config_setting_t *elem; int type; - if (path) - elem = config_setting_lookup(e, path); - else - elem = e; - - if (!elem) + if (!get_field_type(e, path, &type)) return false; - type = config_setting_type(elem); - return type == CONFIG_TYPE_INT || type == CONFIG_TYPE_INT64 || type == CONFIG_TYPE_FLOAT; } +bool is_field_bool_cfg(config_setting_t *e, const char *path) +{ + int type; + + if (!get_field_type(e, path, &type)) + return false; + + return type == CONFIG_TYPE_BOOL; +} + +bool is_field_string_cfg(config_setting_t *e, const char *path) +{ + int type; + + if (!get_field_type(e, path, &type)) + return false; + + return type == CONFIG_TYPE_STRING; +} + + void get_field_cfg(config_setting_t *e, const char *path, void *dest, field_type_t type) { config_setting_t *elem; @@ -182,7 +211,6 @@ config_setting_t *elem; char *root; const char *ref; - char **tmp = NULL; /* * check for deadlock links, block recursion @@ -204,12 +232,11 @@ if (elem && config_setting_is_group(elem) == CONFIG_TRUE) { ref = get_field_string_libconfig(elem, "ref"); if (ref) { - if (!set_find_path(nodes, ref, &tmp)) { + if (!set_find_path(nodes, ref)) { free(root); return NULL; } elem = find_root_libconfig(cfg, nodes, depth); - free_string_array(tmp); } } diff -Nru swupdate-2024.12.1+dfsg/corelib/parsing_library_libjson.c swupdate-2025.12+dfsg/corelib/parsing_library_libjson.c --- swupdate-2024.12.1+dfsg/corelib/parsing_library_libjson.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/parsing_library_libjson.c 2025-12-03 11:32:03.000000000 +0000 @@ -38,6 +38,22 @@ } } +static bool get_field_type(json_object *e, const char *path, enum json_type *type) +{ + json_object *fld = NULL; + + if (path) { + if (!json_object_object_get_ex(e, path, &fld)) + return false; + } else { + fld = e; + } + + *type = json_object_get_type(fld); + + return true; +} + json_object *find_json_recursive_node(json_object *root, const char **names) { json_object *node = root; @@ -147,20 +163,34 @@ bool is_field_numeric_json(json_object *e, const char *path) { enum json_type type; - json_object *fld = NULL; - if (path) { - if (!json_object_object_get_ex(e, path, &fld)) - return false; - } else { - fld = e; - } + if (!get_field_type(e, path, &type)) + return false; - type = json_object_get_type(fld); return type == json_type_int || type == json_type_double; } +bool is_field_bool_json(json_object *e, const char *path) +{ + enum json_type type; + + if (!get_field_type(e, path, &type)) + return false; + + return type == json_type_boolean; +} + +bool is_field_string_json(json_object *e, const char *path) +{ + enum json_type type; + + if (!get_field_type(e, path, &type)) + return false; + + return type == json_type_string; +} + void get_field_json(json_object *e, const char *path, void *dest, field_type_t type) { json_object *fld = NULL; @@ -220,7 +250,6 @@ { json_object *node; enum json_type type; - char **tmp = NULL; const char *str; /* @@ -237,10 +266,9 @@ if (type == json_type_object || type == json_type_array) { str = get_field_string_json(node, "ref"); if (str) { - if (!set_find_path(nodes, str, &tmp)) + if (!set_find_path(nodes, str)) return NULL; node = find_root_json(root, nodes, depth); - free_string_array(tmp); } } } diff -Nru swupdate-2024.12.1+dfsg/corelib/server_utils.c swupdate-2025.12+dfsg/corelib/server_utils.c --- swupdate-2024.12.1+dfsg/corelib/server_utils.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/server_utils.c 2025-12-03 11:32:03.000000000 +0000 @@ -59,6 +59,12 @@ GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "interface", tmp); if (strlen(tmp)) SETSTRING(chan->iface, tmp); + GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "api_key_header", tmp); + if (strlen(tmp)) + SETSTRING(chan->api_key_header, tmp); + GET_FIELD_STRING_RESET(LIBCFG_PARSER, elem, "api_key", tmp); + if (strlen(tmp)) + SETSTRING(chan->api_key, tmp); return 0; } diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_cms_verify.c swupdate-2025.12+dfsg/corelib/swupdate_cms_verify.c --- swupdate-2024.12.1+dfsg/corelib/swupdate_cms_verify.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_cms_verify.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,305 +0,0 @@ -/* - * (C) Copyright 2019 - * Stefano Babic, stefano.babic@swupdate.org. - * - * SPDX-License-Identifier: GPL-2.0-only - * - * Code mostly taken from openssl examples - */ - -#include -#include -#include -#include -#include "swupdate.h" -#include "sslapi.h" -#include "util.h" -#include "swupdate_verify_private.h" - -#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS) -#define VERIFY_UNKNOWN_SIGNER_FLAGS (CMS_NO_SIGNER_CERT_VERIFY) -#else -#define VERIFY_UNKNOWN_SIGNER_FLAGS (0) -#endif - -#ifndef CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE -int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca) -{ - X509 *x = (X509 *)crt; - uint32_t ex_flags = SSL_X509_get_extension_flags(x); - uint32_t ex_xkusage = SSL_X509_get_extended_key_usage(x); - - (void)xp; - - if (ca) { - int idx; - const X509_PURPOSE *pt; - - if ((ex_flags & EXFLAG_XKUSAGE) && !(ex_xkusage & XKU_CODE_SIGN)) - return 0; - - idx = X509_PURPOSE_get_by_id(X509_PURPOSE_OCSP_HELPER); - if (idx == -1) - return 0; - - pt = X509_PURPOSE_get0(idx); - return pt->check_purpose(pt, x, ca); - } - - return (ex_flags & EXFLAG_XKUSAGE) && (ex_xkusage & XKU_CODE_SIGN); -} -#endif - -static int cms_verify_callback(int ok, X509_STORE_CTX *ctx) { - int cert_error = X509_STORE_CTX_get_error(ctx); - - if (!ok) { - switch (cert_error) { -#if defined(CONFIG_CMS_IGNORE_EXPIRED_CERTIFICATE) - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_CERT_NOT_YET_VALID: - ok = 1; - break; -#endif -#if defined(CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE) - case X509_V_ERR_INVALID_PURPOSE: - ok = 1; - break; -#endif - default: - break; - } - } - - return ok; -} - -X509_STORE *load_cert_chain(const char *file) -{ - X509_STORE *castore = X509_STORE_new(); - if (!castore) { - return NULL; - } - - /* - * Set error callback function for verification of CRTs and CRLs in order - * to ignore some errors depending on configuration - */ - X509_STORE_set_verify_cb(castore, cms_verify_callback); - - BIO *castore_bio = BIO_new_file(file, "r"); - if (!castore_bio) { - TRACE("failed: BIO_new_file(%s)", file); - return NULL; - } - - int crt_count = 0; - X509 *crt = NULL; - do { - crt = PEM_read_bio_X509(castore_bio, NULL, 0, NULL); - if (crt) { - crt_count++; - char *subj = X509_NAME_oneline(X509_get_subject_name(crt), NULL, 0); - char *issuer = X509_NAME_oneline(X509_get_issuer_name(crt), NULL, 0); - TRACE("Read PEM #%d: %s %s", crt_count, issuer, subj); - free(subj); - free(issuer); - if (X509_STORE_add_cert(castore, crt) == 0) { - TRACE("Adding certificate to X509_STORE failed"); - BIO_free(castore_bio); - X509_STORE_free(castore); - return NULL; - } - } - } while (crt); - BIO_free(castore_bio); - - if (crt_count == 0) { - X509_STORE_free(castore); - return NULL; - } - - return castore; -} - -static inline int next_common_name(X509_NAME *subject, int i) -{ - return X509_NAME_get_index_by_NID(subject, NID_commonName, i); -} - -static int check_common_name(X509_NAME *subject, const char *name) -{ - int i = -1, ret = 1; - - while ((i = next_common_name(subject, i)) > -1) { - X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, i); - ASN1_STRING *d = X509_NAME_ENTRY_get_data(e); - unsigned char *cn; - size_t len = strlen(name); - bool matches = (ASN1_STRING_to_UTF8(&cn, d) == (int)len) - && (strncmp(name, (const char *)cn, len) == 0); - - OPENSSL_free(cn); - if (!matches) { - char *subj = X509_NAME_oneline(subject, NULL, 0); - - ERROR("common name of '%s' does not match expected '%s'", - subj, name); - OPENSSL_free(subj); - return 2; - } else { - ret = 0; - } - } - - if (ret == 0) { - char *subj = X509_NAME_oneline(subject, NULL, 0); - - TRACE("verified signer cert: %s", subj); - OPENSSL_free(subj); - } - - return ret; -} - -static int check_signer_name(CMS_ContentInfo *cms, const char *name) -{ - STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms); - STACK_OF(X509) *crts; - int i, ret = 1; - - if ((name == NULL) || (name[0] == '\0')) - return 0; - - crts = CMS_get1_certs(cms); - for (i = 0; i < sk_CMS_SignerInfo_num(infos); ++i) { - CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i); - int j; - - for (j = 0; j < sk_X509_num(crts); ++j) { - X509 *crt = sk_X509_value(crts, j); - - if (CMS_SignerInfo_cert_cmp(si, crt) == 0) { - ret = check_common_name( - X509_get_subject_name(crt), name); - } - } - } - sk_X509_pop_free(crts, X509_free); - - return ret; -} - -#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS) -static int check_verified_signer(CMS_ContentInfo* cms, X509_STORE* store) -{ - int i, ret = 1; - - X509_STORE_CTX *ctx = X509_STORE_CTX_new(); - STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms); - STACK_OF(X509)* cms_certs = CMS_get1_certs(cms); - - if (!ctx) { - ERROR("Failed to allocate verification context"); - return ret; - } - - for (i = 0; i < sk_CMS_SignerInfo_num(infos) && ret != 0; ++i) { - CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i); - X509 *signer = NULL; - - CMS_SignerInfo_get0_algs(si, NULL, &signer, NULL, NULL); - if (!X509_STORE_CTX_init(ctx, store, signer, cms_certs)) { - ERROR("Failed to initialize signer verification operation"); - break; - } - - X509_STORE_CTX_set_default(ctx, "smime_sign"); - if (X509_verify_cert(ctx) > 0) { - TRACE("Verified signature %d in signer sequence", i); - ret = 0; - } else { - TRACE("Failed to verify certificate %d in signer sequence", i); - } - - X509_STORE_CTX_cleanup(ctx); - } - - X509_STORE_CTX_free(ctx); - - return ret; -} -#endif - -int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, - const char *file, const char *signer_name) -{ - int status = -EFAULT; - CMS_ContentInfo *cms = NULL; - BIO *content_bio = NULL; - - /* Open CMS blob that needs to be checked */ - BIO *sigfile_bio = BIO_new_file(sigfile, "rb"); - if (!sigfile_bio) { - ERROR("%s cannot be opened", sigfile); - status = -EBADF; - goto out; - } - - /* Parse the DER-encoded CMS message */ - cms = d2i_CMS_bio(sigfile_bio, NULL); - if (!cms) { - ERROR("%s cannot be parsed as DER-encoded CMS signature blob", sigfile); - status = -EFAULT; - goto out; - } - - if (check_signer_name(cms, signer_name)) { - ERROR("failed to verify signer name"); - status = -EFAULT; - goto out; - } - - /* Open the content file (data which was signed) */ - content_bio = BIO_new_file(file, "rb"); - if (!content_bio) { - ERROR("%s cannot be opened", file); - status = -EBADF; - goto out; - } - - /* Then try to verify signature */ - if (!CMS_verify(cms, NULL, dgst->certs, content_bio, - NULL, CMS_BINARY | VERIFY_UNKNOWN_SIGNER_FLAGS)) { - ERR_print_errors_fp(stderr); - ERROR("Signature verification failed"); - status = -EBADMSG; - goto out; - } - -#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS) - /* Verify at least one signer authenticates */ - if (check_verified_signer(cms, dgst->certs)) { - ERROR("Authentication of all signatures failed"); - status = -EBADMSG; - goto out; - } -#endif - - TRACE("Verified OK"); - - /* Signature is valid */ - status = 0; -out: - - if (cms) { - CMS_ContentInfo_free(cms); - } - if (content_bio) { - BIO_free(content_bio); - } - if (sigfile_bio) { - BIO_free(sigfile_bio); - } - return status; -} diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_decrypt_mbedtls.c swupdate-2025.12+dfsg/corelib/swupdate_decrypt_mbedtls.c --- swupdate-2024.12.1+dfsg/corelib/swupdate_decrypt_mbedtls.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_decrypt_mbedtls.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,125 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Laszlo Ashin -// -// SPDX-License-Identifier: GPL-2.0-only - -#include - -#include "sslapi.h" -#include "util.h" - -struct swupdate_digest *swupdate_DECRYPT_init(unsigned char *key, char keylen, unsigned char *iv) -{ - struct swupdate_digest *dgst; - mbedtls_cipher_type_t cipher_type; - const mbedtls_cipher_info_t *cipher_info; - int key_bitlen; - int error; - - if ((key == NULL) || (iv == NULL)) { - ERROR("no key provided for decryption!"); - return NULL; - } - - switch (keylen) { - case AES_128_KEY_LEN: - cipher_type = MBEDTLS_CIPHER_AES_128_CBC; - key_bitlen = 128; - break; - case AES_192_KEY_LEN: - cipher_type = MBEDTLS_CIPHER_AES_192_CBC; - key_bitlen = 192; - break; - case AES_256_KEY_LEN: - cipher_type = MBEDTLS_CIPHER_AES_256_CBC; - key_bitlen = 256; - break; - default: - return NULL; - } - - cipher_info = mbedtls_cipher_info_from_type(cipher_type); - if (!cipher_info) { - ERROR("mbedtls_cipher_info_from_type"); - return NULL; - } - - dgst = calloc(1, sizeof(*dgst)); - if (!dgst) { - return NULL; - } - - mbedtls_cipher_init(&dgst->mbedtls_cipher_context); - - error = mbedtls_cipher_setup(&dgst->mbedtls_cipher_context, cipher_info); - if (error) { - ERROR("mbedtls_cipher_setup: %d", error); - goto fail; - } - - error = mbedtls_cipher_setkey(&dgst->mbedtls_cipher_context, key, key_bitlen, MBEDTLS_DECRYPT); - if (error) { - ERROR("mbedtls_cipher_setkey: %d", error); - goto fail; - } - - error = mbedtls_cipher_set_iv(&dgst->mbedtls_cipher_context, iv, 16); - if (error) { - ERROR("mbedtls_cipher_set_iv: %d", error); - goto fail; - } - - return dgst; - -fail: - free(dgst); - return NULL; -} - -int swupdate_DECRYPT_update(struct swupdate_digest *dgst, unsigned char *buf, - int *outlen, const unsigned char *cryptbuf, int inlen) -{ - int error; - size_t olen = *outlen; - - error = mbedtls_cipher_update(&dgst->mbedtls_cipher_context, cryptbuf, inlen, buf, &olen); - if (error) { - ERROR("mbedtls_cipher_update: %d", error); - return -EFAULT; - } - *outlen = olen; - - return 0; -} - -int swupdate_DECRYPT_final(struct swupdate_digest *dgst, unsigned char *buf, - int *outlen) -{ - int error; - size_t olen = *outlen; - - if (!dgst) { - return -EINVAL; - } - - error = mbedtls_cipher_finish(&dgst->mbedtls_cipher_context, buf, &olen); - if (error) { -#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING - ERROR("mbedtls_cipher_finish: %d", error); -#endif - return -EFAULT; - } - *outlen = olen; - - return 0; - -} - -void swupdate_DECRYPT_cleanup(struct swupdate_digest *dgst) -{ - if (!dgst) { - return; - } - - mbedtls_cipher_free(&dgst->mbedtls_cipher_context); - free(dgst); -} diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_decrypt_openssl.c swupdate-2025.12+dfsg/corelib/swupdate_decrypt_openssl.c --- swupdate-2024.12.1+dfsg/corelib/swupdate_decrypt_openssl.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_decrypt_openssl.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -/* - * (C) Copyright 2016 - * Stefano Babic, stefano.babic@swupdate.org. - * - * SPDX-License-Identifier: GPL-2.0-only - * - * Code mostly taken from openssl examples - * - */ - -#include -#include -#include -#include -#include "swupdate.h" -#include "sslapi.h" -#include "util.h" - -struct swupdate_digest *swupdate_DECRYPT_init(unsigned char *key, char keylen, unsigned char *iv) -{ - struct swupdate_digest *dgst; - const EVP_CIPHER *cipher; - int ret; - - if ((key == NULL) || (iv == NULL)) { - ERROR("no key provided for decryption!"); - return NULL; - } - - switch (keylen) { - case AES_128_KEY_LEN: - cipher = EVP_aes_128_cbc(); - break; - case AES_192_KEY_LEN: - cipher = EVP_aes_192_cbc(); - break; - case AES_256_KEY_LEN: - cipher = EVP_aes_256_cbc(); - break; - default: - return NULL; - } - - dgst = calloc(1, sizeof(*dgst)); - if (!dgst) { - return NULL; - } - -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_CIPHER_CTX_init(&dgst->ctxdec); -#else - dgst->ctxdec = EVP_CIPHER_CTX_new(); - if (dgst->ctxdec == NULL) { - ERROR("Cannot initialize cipher context."); - free(dgst); - return NULL; - } - if (EVP_CIPHER_CTX_reset(dgst->ctxdec) != 1) { - ERROR("Cannot reset cipher context."); - EVP_CIPHER_CTX_free(dgst->ctxdec); - free(dgst); - return NULL; - } -#endif - - /* - * Check openSSL documentation for return errors - */ - ret = EVP_DecryptInit_ex(SSL_GET_CTXDEC(dgst), cipher, NULL, key, iv); - if (ret != 1) { - const char *reason = ERR_reason_error_string(ERR_peek_error()); - ERROR("Decrypt Engine not initialized, error 0x%lx, reason: %s", ERR_get_error(), - reason != NULL ? reason : "unknown"); - free(dgst); - return NULL; - } - - return dgst; -} - -int swupdate_DECRYPT_update(struct swupdate_digest *dgst, unsigned char *buf, - int *outlen, const unsigned char *cryptbuf, int inlen) -{ - if (EVP_DecryptUpdate(SSL_GET_CTXDEC(dgst), buf, outlen, cryptbuf, inlen) != 1) { - const char *reason = ERR_reason_error_string(ERR_peek_error()); - ERROR("Update: Decryption error 0x%lx, reason: %s", ERR_get_error(), - reason != NULL ? reason : "unknown"); - return -EFAULT; - } - - return 0; -} - -int swupdate_DECRYPT_final(struct swupdate_digest *dgst, unsigned char *buf, - int *outlen) -{ - if (!dgst) - return -EINVAL; - - if (EVP_DecryptFinal_ex(SSL_GET_CTXDEC(dgst), buf, outlen) != 1) { -#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING - const char *reason = ERR_reason_error_string(ERR_peek_error()); - ERROR("Final: Decryption error 0x%lx, reason: %s", ERR_get_error(), - reason != NULL ? reason : "unknown"); -#endif - return -EFAULT; - } - - return 0; - -} - -void swupdate_DECRYPT_cleanup(struct swupdate_digest *dgst) -{ - if (dgst) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_CIPHER_CTX_cleanup(SSL_GET_CTXDEC(dgst)); -#else - EVP_CIPHER_CTX_free(SSL_GET_CTXDEC(dgst)); -#endif - free(dgst); - dgst = NULL; - } -} diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_decrypt_pkcs11.c swupdate-2025.12+dfsg/corelib/swupdate_decrypt_pkcs11.c --- swupdate-2024.12.1+dfsg/corelib/swupdate_decrypt_pkcs11.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_decrypt_pkcs11.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,195 +0,0 @@ -/* - * (C) Copyright 2020, Linutronix GmbH - * Author: Bastian Germann - * - * SPDX-License-Identifier: GPL-2.0-only - */ - -#include -#include -#include -#include -#include "swupdate.h" -#include "sslapi.h" -#include "util.h" -#include -#include - -#ifdef DEBUG_WOLFSSL -static void wolfssl_debug(int __attribute__ ((__unused__)) level, const char *const msg) -{ - DEBUG("%s", msg); -} -#endif - -struct swupdate_digest *swupdate_DECRYPT_init(unsigned char *uri, - char __attribute__ ((__unused__)) keylen, unsigned char *iv) -{ - struct swupdate_digest *dgst; - const char *library; - const char *pin; - const char *msg; - CK_ATTRIBUTE_PTR key_id; - int slot_id; - int err = 0; - int dev_id = 1; - - if ((uri == NULL) || (iv == NULL)) { - ERROR("PKCS#11 URI or AES IV missing for decryption!"); - return NULL; - } - - dgst = calloc(1, sizeof(*dgst)); - if (!dgst) { - return NULL; - } - - dgst->p11uri = p11_kit_uri_new(); - err = p11_kit_uri_parse(uri, P11_KIT_URI_FOR_ANY, dgst->p11uri); - if (err) { - msg = p11_kit_uri_message(err); - ERROR("PKCS#11 URI: %s", msg); - free(dgst); - return NULL; - } - - slot_id = p11_kit_uri_get_slot_id(dgst->p11uri); - key_id = p11_kit_uri_get_attribute(dgst->p11uri, CKA_ID); - pin = p11_kit_uri_get_pin_value(dgst->p11uri); - library = p11_kit_uri_get_module_path(dgst->p11uri); - if (slot_id == -1 || key_id == NULL || pin == NULL || library == NULL) { - ERROR("PKCS#11 URI must contain slot-id, id, pin-value, and module-path."); - goto err_free; - } - - // Set up a valid PKCS#7 block plus one state octet - for (int i = 0; i <= AES_BLK_SIZE; i++) { - dgst->last_decr[i] = AES_BLK_SIZE; - } - -#ifdef DEBUG_WOLFSSL - wolfSSL_SetLoggingCb(wolfssl_debug); - wolfSSL_Debugging_ON(); -#endif - wolfCrypt_Init(); - err = wc_Pkcs11_Initialize(&dgst->pkdev, library, NULL); - if (err) - goto err_msg; - - err = wc_Pkcs11Token_Init(&dgst->pktoken, &dgst->pkdev, slot_id, - "unspecified", pin, strlen(pin)); - if (err) - goto err_msg; - - err = wc_Pkcs11Token_Open(&dgst->pktoken, 0); - if (err) - goto err_msg; - - err = wc_CryptoCb_RegisterDevice(dev_id, wc_Pkcs11_CryptoDevCb, &dgst->pktoken); - if (err) - goto err_msg; - - err = wc_AesInit_Id(&dgst->ctxdec, key_id->pValue, key_id->ulValueLen, NULL, dev_id); - if (err) - goto err_msg; - - err = wc_AesSetIV(&dgst->ctxdec, iv); - if (err) - goto err_msg; - - INFO("PKCS#11 key set up successfully."); - return dgst; - -err_msg: - msg = wc_GetErrorString(err); - ERROR("PKCS#11 initialization failed: %s", msg); - -err_free: - if (&dgst->pktoken) - wc_Pkcs11Token_Final(&dgst->pktoken); - if (&dgst->pkdev) - wc_Pkcs11_Finalize(&dgst->pkdev); - - p11_kit_uri_free(dgst->p11uri); - free(dgst); - - return NULL; -} - -int swupdate_DECRYPT_update(struct swupdate_digest *dgst, unsigned char *buf, - int *outlen, const unsigned char *cryptbuf, int inlen) -{ - // precondition: len(buf) >= inlen + AES_BLK_SIZE - unsigned char *pad_buf = &buf[AES_BLK_SIZE]; - const char *msg; - int err; - int one_off_sz = inlen - AES_BLK_SIZE; - - if (inlen < AES_BLK_SIZE) - return -EFAULT; - - if (dgst->last_decr[AES_BLK_SIZE]) { - // This is for the first decryption operation - pad_buf = buf; - dgst->last_decr[AES_BLK_SIZE] = 0; - *outlen = one_off_sz; - } else { - memcpy(buf, dgst->last_decr, AES_BLK_SIZE); - *outlen = inlen; - } - - err = wc_AesCbcDecrypt(&dgst->ctxdec, pad_buf, cryptbuf, inlen); - if (err) { - msg = wc_GetErrorString(err); - ERROR("PKCS#11 AES decryption failed: %s", msg); - return -EFAULT; - } - // Remember the last decrypted block which might contain padding - memcpy(dgst->last_decr, &pad_buf[one_off_sz], AES_BLK_SIZE); - - wc_AesSetIV(&dgst->ctxdec, &cryptbuf[one_off_sz]); - - return 0; -} - -// Gets rid of PKCS#7 padding -int swupdate_DECRYPT_final(struct swupdate_digest *dgst, unsigned char *buf, int *outlen) -{ - unsigned char last_oct = dgst->last_decr[AES_BLK_SIZE - 1]; - if (last_oct > AES_BLK_SIZE || last_oct == 0) { -#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING - ERROR("AES: Invalid PKCS#7 padding."); -#endif - return -EFAULT; - } - - for (int i = 2; i <= last_oct; i++) { - if (dgst->last_decr[AES_BLK_SIZE - i] != last_oct) { -#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING - ERROR("AES: Invalid PKCS#7 padding."); -#endif - return -EFAULT; - } - } - - *outlen = AES_BLK_SIZE - last_oct; - memcpy(buf, dgst->last_decr, *outlen); - - return 0; -} - -void swupdate_DECRYPT_cleanup(struct swupdate_digest *dgst) -{ - if (dgst) { - if (&dgst->pktoken) - wc_Pkcs11Token_Final(&dgst->pktoken); - if (&dgst->pkdev) - wc_Pkcs11_Finalize(&dgst->pkdev); - p11_kit_uri_free(dgst->p11uri); - - free(dgst); - dgst = NULL; - } - - wolfCrypt_Cleanup(); -} diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_gpg_verify.c swupdate-2025.12+dfsg/corelib/swupdate_gpg_verify.c --- swupdate-2024.12.1+dfsg/corelib/swupdate_gpg_verify.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_gpg_verify.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,171 +0,0 @@ -/* - * Author: Amy Fong - * Copyright (C) 2023, Siemens AG - * - * SPDX-License-Identifier: GPL-2.0-only - */ -#include -#include -#include -#include -#include "swupdate.h" -#include "sslapi.h" -#include "util.h" - -#include -#include -#include - -static gpg_error_t -status_cb(void *opaque, const char *keyword, const char *value) -{ - (void)opaque; - DEBUG("status_cb: %s %s", keyword, value); - return 0; -} - -#define MSGBUF_LEN 256 - -int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, - const char *file, const char *signer_name) -{ - gpgme_ctx_t ctx; - gpgme_error_t err; - gpgme_data_t image_sig, image; - FILE *fp_sig = NULL; - FILE *fp = NULL; - gpgme_signature_t sig; - int status = 0; - gpgme_protocol_t protocol; - gpgme_verify_result_t result; - char msg[MSGBUF_LEN]; - int r; - const char *tmp; - - tmp = gpgme_check_version(NULL); - if (tmp == NULL) { - ERROR("Failed to check gpgme library version"); - status = -EFAULT; - goto out; - } - - err = gpgme_new(&ctx); - if (err) { - ERROR("Failed to create new gpg context"); - r = gpgme_strerror_r(err, msg, MSGBUF_LEN); - if (r == 0) { - ERROR("Reason: %s", msg); - } - status = -EFAULT; - goto out; - } - - if (dgst->gpgme_protocol != NULL) { - DEBUG("gpg: Enabling protocol %s", dgst->gpgme_protocol); - if (!strcmp(dgst->gpgme_protocol, "openpgp")) { - TRACE("gpg: using protocol OpenPGP"); - protocol = GPGME_PROTOCOL_OpenPGP; - } else if (!strcmp(dgst->gpgme_protocol, "cms")) { - TRACE("gpg: using protocol cms"); - protocol = GPGME_PROTOCOL_CMS; - } else { - ERROR("gpg: unsupported protocol! %s", dgst->gpgme_protocol); - status = -EFAULT; - goto out; - } - } else { - ERROR("gpg protocol unspecified!"); - status = -EFAULT; - goto out; - } - - gpgme_set_protocol(ctx, protocol); - gpgme_set_status_cb(ctx, status_cb, NULL); - if (dgst->verbose) { - gpgme_set_ctx_flag(ctx, "full-status", "1"); - } - gpgme_set_locale(ctx, LC_ALL, setlocale(LC_ALL, "")); - - if (dgst->gpg_home_directory != NULL) { - err = gpgme_ctx_set_engine_info(ctx, protocol, NULL, dgst->gpg_home_directory); - if (err) { - ERROR("Something went wrong while setting the engine info"); - r = gpgme_strerror_r(err, msg, MSGBUF_LEN); - if (r == 0) { - ERROR("Reason: %s", msg); - } - status = -EFAULT; - goto out; - } - } - - fp_sig = fopen(sigfile, "rb"); - if (!fp_sig) { - ERROR("Failed to open %s", sigfile); - status = -EBADF; - goto out; - } - err = gpgme_data_new_from_stream(&image_sig, fp_sig); - if (err) { - ERROR("error allocating data object"); - r = gpgme_strerror_r(err, msg, MSGBUF_LEN); - if (r == 0) { - ERROR("Reason: %s", msg); - } - status = -ENOMEM; - goto out; - } - - fp = fopen(file, "rb"); - if (!fp) { - ERROR("Failed to open %s", file); - status = -EBADF; - goto out; - } - err = gpgme_data_new_from_stream(&image, fp); - if (err) { - ERROR("error allocating data object"); - r = gpgme_strerror_r(err, msg, MSGBUF_LEN); - if (r == 0) { - ERROR("Reason: %s", msg); - } - status = -ENOMEM; - goto out; - } - - err = gpgme_op_verify(ctx, image_sig, image, NULL); - result = gpgme_op_verify_result(ctx); - if (err) { - ERROR("verify failed"); - r = gpgme_strerror_r(err, msg, MSGBUF_LEN); - if (r == 0) { - ERROR("Reason: %s", msg); - } - status = -EBADMSG; - goto out; - } - - if (result) { - for (sig = result->signatures; sig; sig = sig->next) { - if (sig->status == GPG_ERR_NO_ERROR) { - TRACE("Verified OK"); - status = 0; - goto out; - } - } - } - TRACE("Verification failed"); - status = -EBADMSG; - - out: - gpgme_data_release(image); - gpgme_data_release(image_sig); - gpgme_release(ctx); - - if (fp) - fclose(fp); - if (fp_sig) - fclose(fp_sig); - - return status; -} diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_pkcs7_verify.c swupdate-2025.12+dfsg/corelib/swupdate_pkcs7_verify.c --- swupdate-2024.12.1+dfsg/corelib/swupdate_pkcs7_verify.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_pkcs7_verify.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,173 +0,0 @@ -/* - * (C) Copyright 2019 - * Stefano Babic, stefano.babic@swupdate.org. - * (C) Copyright 2023 - * Bastian Germann - * - * SPDX-License-Identifier: GPL-2.0-only - * - * Code mostly taken from openssl examples - */ - -#include -#include -#include -#include -#include "swupdate.h" -#include "sslapi.h" -#include "util.h" -#include "swupdate_verify_private.h" - -static int store_verify_callback(int ok, X509_STORE_CTX *ctx) { - int cert_error = X509_STORE_CTX_get_error(ctx); - - if (!ok) { - switch (cert_error) { -#if defined(CONFIG_CMS_IGNORE_EXPIRED_CERTIFICATE) - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_CERT_NOT_YET_VALID: - ok = 1; - break; -#endif -#if defined(CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE) - case X509_V_ERR_INVALID_PURPOSE: - ok = 1; - break; -#endif - default: - break; - } - } - - return ok; -} - -X509_STORE *load_cert_chain(const char *file) -{ - X509_STORE *castore = X509_STORE_new(); - if (!castore) { - return NULL; - } - - /* - * Set error callback function for verification of CRTs and CRLs in order - * to ignore some errors depending on configuration - */ - X509_STORE_set_verify_cb(castore, store_verify_callback); - - BIO *castore_bio = BIO_new_file(file, "r"); - if (!castore_bio) { - TRACE("failed: BIO_new_file(%s)", file); - return NULL; - } - - int crt_count = 0; - X509 *crt = NULL; - do { - crt = PEM_read_bio_X509(castore_bio, NULL, 0, NULL); - if (crt) { - crt_count++; - char *subj = X509_NAME_oneline(X509_get_subject_name(crt), NULL, 0); - char *issuer = X509_NAME_oneline(X509_get_issuer_name(crt), NULL, 0); - TRACE("Read PEM #%d: %s %s", crt_count, issuer, subj); - free(subj); - free(issuer); - if (X509_STORE_add_cert(castore, crt) == 0) { - TRACE("Adding certificate to X509_STORE failed"); - BIO_free(castore_bio); - X509_STORE_free(castore); - return NULL; - } - } - } while (crt); - BIO_free(castore_bio); - - if (crt_count == 0) { - X509_STORE_free(castore); - return NULL; - } - - return castore; -} - -static int check_signer_name(const char *name) -{ - // TODO work around wolfSSL's PKCS7_get0_signers always returning NULL - if (name) - WARN("The X.509 common name might not be equal to %s.", name); - return 0; -} - -int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, - const char *file, const char *signer_name) -{ - int status = -EFAULT; - WOLFSSL_PKCS7* pkcs7 = (WOLFSSL_PKCS7 *)PKCS7_new(); - BIO *bio_mem = NULL; - BIO *content_bio = NULL; - - /* Open detached signature that needs to be checked */ - BIO *sigfile_bio = BIO_new_file(sigfile, "rb"); - if (!sigfile_bio) { - ERROR("%s cannot be opened", sigfile); - status = -EBADF; - goto out; - } - - pkcs7->len = wolfSSL_BIO_get_len(sigfile_bio); - pkcs7->data = calloc(1, pkcs7->len); - BIO_read(sigfile_bio, pkcs7->data, pkcs7->len); - if (!pkcs7->data) { - ERROR("%s cannot be parsed as DER-encoded PKCS#7 signature blob", sigfile); - status = -EFAULT; - goto out; - } - - /* Open the content file (data which was signed) */ - content_bio = BIO_new_file(file, "rb"); - if (!content_bio) { - ERROR("%s cannot be opened", file); - status = -EBADF; - goto out; - } - long content_len = wolfSSL_BIO_get_len(content_bio); - char *content = calloc(1, content_len); - BIO_read(content_bio, content, content_len); - bio_mem = BIO_new_mem_buf(content, content_len); - - /* Then try to verify signature. The BIO* in parameter has to be a mem BIO. - See https://github.com/wolfSSL/wolfssl/issues/6174. */ - if (!PKCS7_verify((PKCS7 *)pkcs7, NULL, dgst->certs, bio_mem, - NULL, PKCS7_BINARY)) { - ERR_print_errors_fp(stderr); - ERROR("Signature verification failed"); - status = -EBADMSG; - goto out; - } - - if (check_signer_name(signer_name)) { - ERROR("failed to verify signer name"); - status = -EFAULT; - goto out; - } - - TRACE("Verified OK"); - - /* Signature is valid */ - status = 0; -out: - - if (pkcs7) { - PKCS7_free((PKCS7 *)pkcs7); - } - if (bio_mem) { - BIO_free(bio_mem); - } - if (content_bio) { - BIO_free(content_bio); - } - if (sigfile_bio) { - BIO_free(sigfile_bio); - } - return status; -} diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_rsa_verify.c swupdate-2025.12+dfsg/corelib/swupdate_rsa_verify.c --- swupdate-2024.12.1+dfsg/corelib/swupdate_rsa_verify.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_rsa_verify.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,194 +0,0 @@ -/* - * (C) Copyright 2019 - * Stefano Babic, stefano.babic@swupdate.org. - * - * SPDX-License-Identifier: GPL-2.0-only - * - * Code mostly taken from openssl examples - */ - -#include -#include -#include -#include -#include "swupdate.h" -#include "sslapi.h" -#include "util.h" -#include "swupdate_verify_private.h" - -#define BUFSIZE (1024 * 8) - -EVP_PKEY *load_pubkey(const char *file) -{ - BIO *key=NULL; - EVP_PKEY *pkey=NULL; - - if (file == NULL) - { - ERROR("no keyfile specified"); - goto end; - } - - key=BIO_new(BIO_s_file()); - if (key == NULL) - { - goto end; - } - - if (BIO_read_filename(key, file) <= 0) - { - printf("Error opening %s \n", file); - goto end; - } - - pkey=PEM_read_bio_PUBKEY(key, NULL, NULL, NULL); -end: - if (key != NULL) BIO_free(key); - if (pkey == NULL) - ERROR("unable to load key filename %s", file); - return(pkey); -} - -static int dgst_verify_init(struct swupdate_digest *dgst) -{ - int rc; - - rc = EVP_DigestVerifyInit(dgst->ctx, &dgst->ckey, EVP_sha256(), NULL, dgst->pkey); - if (rc != 1) { - ERROR("EVP_DigestVerifyInit failed, error 0x%lx", ERR_get_error()); - return -EFAULT; /* failed */ - } - -#if defined(CONFIG_SIGALG_RSAPSS) - rc = EVP_PKEY_CTX_set_rsa_padding(dgst->ckey, RSA_PKCS1_PSS_PADDING); - if (rc <= 0) { - ERROR("EVP_PKEY_CTX_set_rsa_padding failed, error 0x%lx", ERR_get_error()); - return -EFAULT; /* failed */ - } - rc = EVP_PKEY_CTX_set_rsa_pss_saltlen(dgst->ckey, -2); - if (rc <= 0) { - ERROR("EVP_PKEY_CTX_set_rsa_pss_saltlen failed, error 0x%lx", ERR_get_error()); - return -EFAULT; /* failed */ - } -#endif - - return 0; -} - -static int verify_update(struct swupdate_digest *dgst, char *msg, unsigned int mlen) -{ - int rc; - - rc = EVP_DigestVerifyUpdate(dgst->ctx, msg, mlen); - if(rc != 1) { - ERROR("EVP_DigestVerifyUpdate failed, error 0x%lx", ERR_get_error()); - return -EFAULT; - } - - return 0; -} - -static int verify_final(struct swupdate_digest *dgst, unsigned char *sig, unsigned int slen) -{ - unsigned int rc; - - /* Clear any errors for the call below */ - ERR_clear_error(); - rc = EVP_DigestVerifyFinal(dgst->ctx, sig, slen); - if(rc != 1) { - ERROR("EVP_DigestVerifyFinal failed, error 0x%lx %d", ERR_get_error(), rc); - return -1; - } - - return rc; -} - -int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, - const char *file, const char *signer_name) -{ - FILE *fp = NULL; - BIO *sigbio; - int siglen = 0; - int i; - unsigned char *sigbuf = NULL; - char *msg = NULL; - int size; - size_t rbytes; - int status = 0; - - (void)signer_name; - if (!dgst) { - ERROR("Wrong crypto initialization: did you pass the key ?"); - status = -ENOKEY; - goto out; - } - - msg = malloc(BUFSIZE); - if (!msg) { - status = -ENOMEM; - goto out; - } - - sigbio = BIO_new_file(sigfile, "rb"); - siglen = EVP_PKEY_size(dgst->pkey); - sigbuf = OPENSSL_malloc(siglen); - - siglen = BIO_read(sigbio, sigbuf, siglen); - BIO_free(sigbio); - - if(siglen <= 0) { - ERROR("Error reading signature file %s", sigfile); - status = -ENOKEY; - goto out; - } - - if ((dgst_init(dgst, EVP_sha256()) < 0) || (dgst_verify_init(dgst) < 0)) { - status = -ENOKEY; - goto out; - } - - fp = fopen(file, "r"); - if (!fp) { - ERROR("%s cannot be opened", file); - status = -EBADF; - goto out; - } - - size = 0; - for (;;) { - rbytes = fread(msg, 1, BUFSIZE, fp); - if (rbytes > 0) { - size += rbytes; - if (verify_update(dgst, msg, rbytes) < 0) - break; - } - if (feof(fp)) - break; - } - - TRACE("Verify signed image: Read %d bytes", size); - i = verify_final(dgst, sigbuf, (unsigned int)siglen); - if(i > 0) { - TRACE("Verified OK"); - status = 0; - } else if(i == 0) { - TRACE("Verification Failure"); - status = -EBADMSG; - } else { - TRACE("Error Verifying Data"); - status = -EFAULT; - } - -out: - if (fp) - fclose(fp); - if (msg) - free(msg); - if (sigbuf) - OPENSSL_free(sigbuf); - - return status; -} - - - diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_rsa_verify_mbedtls.c swupdate-2025.12+dfsg/corelib/swupdate_rsa_verify_mbedtls.c --- swupdate-2024.12.1+dfsg/corelib/swupdate_rsa_verify_mbedtls.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_rsa_verify_mbedtls.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,90 +0,0 @@ -/* - * (C) Copyright 2019 - * Stefano Babic, stefano.babic@swupdate.org. - * - * SPDX-License-Identifier: GPL-2.0-only - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "sslapi.h" -#include "util.h" - -static int read_file_into_buffer(uint8_t *buffer, int size, const char *filename) -{ - int fd; - ssize_t rd; - int result = -1; - - fd = open(filename, O_RDONLY); - if (fd == -1) { - ERROR("Failed to open file \"%s\"", filename); - return -errno; - } - - rd = read(fd, buffer, size); - if (rd != size) { - ERROR("Failed to read %d bytes from file \"%s\"", size, filename); - result = -EMSGSIZE; - goto exit; - } - - result = 0; - -exit: - close(fd); - return result; -} - -int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, - const char *file, const char *signer_name) -{ - int error; - uint8_t hash_computed[32]; - const mbedtls_md_info_t *md_info; - mbedtls_pk_type_t pk_type = MBEDTLS_PK_RSA; - uint8_t signature[256]; - void *pss_options = NULL; -#if defined(CONFIG_SIGALG_RSAPSS) - pk_type = MBEDTLS_PK_RSASSA_PSS; - mbedtls_pk_rsassa_pss_options options = { - .mgf1_hash_id = MBEDTLS_MD_SHA256, - .expected_salt_len = MBEDTLS_RSA_SALT_LEN_ANY - }; - pss_options = &options; -#endif - - (void)signer_name; - - md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - if (!md_info) { - ERROR("mbedtls_md_info_from_type"); - return -ENOENT; - } - - assert(mbedtls_md_get_size(md_info) == sizeof(hash_computed)); - - error = mbedtls_md_file(md_info, file, hash_computed); - if (error) { - ERROR("mbedtls_md_file: %d", error); - return error; - } - - error = read_file_into_buffer(signature, sizeof(signature), sigfile); - if (error) { - return error; - } - - return mbedtls_pk_verify_ext( - pk_type, pss_options, - &dgst->mbedtls_pk_context, mbedtls_md_get_type(md_info), - hash_computed, sizeof(hash_computed), - signature, sizeof(signature) - ); -} diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_settings.c swupdate-2025.12+dfsg/corelib/swupdate_settings.c --- swupdate-2024.12.1+dfsg/corelib/swupdate_settings.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_settings.c 2025-12-03 11:32:03.000000000 +0000 @@ -71,6 +71,20 @@ return ret; } +static int get_run_as(void *elem, void *data) +{ + struct run_as *pid = (struct run_as *)data; + + GET_FIELD_INT(LIBCFG_PARSER, elem, "userid", (int *)&pid->userid); + GET_FIELD_INT(LIBCFG_PARSER, elem, "groupid", (int *)&pid->groupid); + + return 0; +} + +/* + * Public interface for the Settings + * These are the functions exported to the other modules + */ int read_module_settings(swupdate_cfg_handle *handle, const char *module, settings_callback fcn, void *data) { config_setting_t *elem; @@ -90,16 +104,6 @@ return 0; } - -static int get_run_as(void *elem, void *data) -{ - struct run_as *pid = (struct run_as *)data; - - GET_FIELD_INT(LIBCFG_PARSER, elem, "userid", (int *)&pid->userid); - GET_FIELD_INT(LIBCFG_PARSER, elem, "groupid", (int *)&pid->groupid); - - return 0; -} int read_settings_user_id(swupdate_cfg_handle *handle, const char *module, uid_t *userid, gid_t *groupid) { diff -Nru swupdate-2024.12.1+dfsg/corelib/swupdate_verify_private.h swupdate-2025.12+dfsg/corelib/swupdate_verify_private.h --- swupdate-2024.12.1+dfsg/corelib/swupdate_verify_private.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/swupdate_verify_private.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -/* - * (C) Copyright 2019 - * Stefano Babic, stefano.babic@swupdate.org. - * - * SPDX-License-Identifier: GPL-2.0-only - */ - -#ifndef _SWUPDATE_VERIFY_H -#define _SWUPDATE_VERIFY_H - -struct swupdate_digest; -int dgst_init(struct swupdate_digest *dgst, const EVP_MD *md); - -#if defined(CONFIG_SIGALG_RAWRSA) || defined(CONFIG_SIGALG_RSAPSS) -EVP_PKEY *load_pubkey(const char *file); -#endif - -#ifdef CONFIG_SIGALG_CMS -#ifndef CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE -int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca); -#endif -X509_STORE *load_cert_chain(const char *file); -#endif - -#endif diff -Nru swupdate-2024.12.1+dfsg/corelib/verify_signature.c swupdate-2025.12+dfsg/corelib/verify_signature.c --- swupdate-2024.12.1+dfsg/corelib/verify_signature.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/verify_signature.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,204 +0,0 @@ -/* - * (C) Copyright 2016 - * Stefano Babic, stefano.babic@swupdate.org. - * - * SPDX-License-Identifier: GPL-2.0-only - * - * Code mostly taken from openssl examples - */ - -#include -#include -#include -#include -#include "swupdate.h" -#include "sslapi.h" -#include "util.h" -#include "compat.h" -#include "swupdate_verify_private.h" - -int dgst_init(struct swupdate_digest *dgst, const EVP_MD *md) -{ - int rc; - - ERR_clear_error(); - rc = EVP_DigestInit_ex(dgst->ctx, md, NULL); - if (rc != 1) { - ERROR("EVP_DigestInit_ex failed: %s", ERR_error_string(ERR_get_error(), NULL)); - return -EINVAL; /* failed */ - } - - return 0; -} - -struct swupdate_digest *swupdate_HASH_init(const char *SHAlength) -{ - struct swupdate_digest *dgst; - const EVP_MD *md; - int ret; - - dgst = calloc(1, sizeof(*dgst)); - if (!dgst) { - return NULL; - } - - if ((!SHAlength) || strcmp(SHAlength, "sha1")) - md = EVP_sha256(); - else - md = EVP_sha1(); - - dgst->ctx = EVP_MD_CTX_create(); - if(dgst->ctx == NULL) { - ERROR("EVP_MD_CTX_create failed, error 0x%lx", ERR_get_error()); - free(dgst); - return NULL; - } - - ret = dgst_init(dgst, md); - if (ret) { - free(dgst); - return NULL; - } - - return dgst; -} - -int swupdate_HASH_update(struct swupdate_digest *dgst, const unsigned char *buf, - size_t len) -{ - if (!dgst) - return -EFAULT; - - if (EVP_DigestUpdate (dgst->ctx, buf, len) != 1) - return -EIO; - - return 0; -} - -int swupdate_HASH_final(struct swupdate_digest *dgst, unsigned char *md_value, - unsigned int *md_len) -{ - if (!dgst) - return -EFAULT; - - return EVP_DigestFinal_ex (dgst->ctx, md_value, md_len); - -} - -void swupdate_HASH_cleanup(struct swupdate_digest *dgst) -{ - if (dgst) { - EVP_MD_CTX_destroy(dgst->ctx); - free(dgst); - dgst = NULL; - } -} - -/* - * Just a wrap function to memcmp - */ -int swupdate_HASH_compare(const unsigned char *hash1, const unsigned char *hash2) -{ - int i; - - for (i = 0; i < SHA256_HASH_LENGTH; i++) - if (hash1[i] != hash2[i]) - return -1; - - return 0; -} - -int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile) -{ - struct swupdate_digest *dgst; - int ret; - - /* - * Check that it was not called before - */ - if (sw->dgst) { - return -EBUSY; - } - - dgst = calloc(1, sizeof(*dgst)); - if (!dgst) { - ret = -ENOMEM; - goto dgst_init_error; - } - -#if defined(CONFIG_SIGALG_RAWRSA) || defined(CONFIG_SIGALG_RSAPSS) - /* - * Load public key - */ - dgst->pkey = load_pubkey(keyfile); - if (!dgst->pkey) { - ERROR("Error loading pub key from %s", keyfile); - ret = -EINVAL; - goto dgst_init_error; - } - dgst->ckey = EVP_PKEY_CTX_new(dgst->pkey, NULL); - if (!dgst->ckey) { - ERROR("Error creating context key for %s", keyfile); - ret = -EINVAL; - goto dgst_init_error; - } -#elif defined(CONFIG_SIGALG_CMS) - /* - * Load certificate chain - */ - dgst->certs = load_cert_chain(keyfile); - if (!dgst->certs) { - ERROR("Error loading certificate chain from %s", keyfile); - ret = -EINVAL; - goto dgst_init_error; - } - -#ifndef CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE - { - static char code_sign_name[] = "Code signing"; - static char code_sign_sname[] = "codesign"; - - if (!X509_PURPOSE_add(X509_PURPOSE_CODE_SIGN, X509_TRUST_EMAIL, - 0, check_code_sign, code_sign_name, - code_sign_sname, NULL)) { - ERROR("failed to add code sign purpose"); - ret = -EINVAL; - goto dgst_init_error; - } - } - - if (!X509_STORE_set_purpose(dgst->certs, sw->cert_purpose)) { - ERROR("failed to set purpose"); - ret = -EINVAL; - goto dgst_init_error; - } -#endif - -#elif defined(CONFIG_SIGALG_GPG) - dgst->gpg_home_directory = sw->gpg_home_directory; - dgst->gpgme_protocol = sw->gpgme_protocol; - dgst->verbose = sw->verbose; -#else - TRACE("public key / cert %s ignored, you need to set SIGALG", keyfile); -#endif - - /* - * Create context - */ - dgst->ctx = EVP_MD_CTX_create(); - if(dgst->ctx == NULL) { - ERROR("EVP_MD_CTX_create failed, error 0x%lx", ERR_get_error()); - ret = -ENOMEM; - goto dgst_init_error; - } - - sw->dgst = dgst; - - return 0; - -dgst_init_error: - if (dgst) - free(dgst); - - return ret; -} diff -Nru swupdate-2024.12.1+dfsg/corelib/verify_signature_mbedtls.c swupdate-2025.12+dfsg/corelib/verify_signature_mbedtls.c --- swupdate-2024.12.1+dfsg/corelib/verify_signature_mbedtls.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/corelib/verify_signature_mbedtls.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,136 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Laszlo Ashin -// -// SPDX-License-Identifier: GPL-2.0-only - -#include -#include -#include - -#include "sslapi.h" -#include "util.h" -#include "swupdate.h" - -static char *algo_upper(const char *algo) -{ - static char result[16]; - unsigned i; - - for (i = 0; algo[i] && (i < sizeof(result) - 1); ++i) { - result[i] = toupper(algo[i]); - } - result[i] = '\0'; - return result; -} - -struct swupdate_digest *swupdate_HASH_init(const char *algo) -{ - struct swupdate_digest *dgst; - int error; - - const mbedtls_md_info_t *info = mbedtls_md_info_from_string(algo_upper(algo)); - if (!info) { - ERROR("mbedtls_md_info_from_string(\"%s\")", algo); - return NULL; - } - - dgst = calloc(1, sizeof(*dgst)); - if (!dgst) { - return NULL; - } - - mbedtls_md_init(&dgst->mbedtls_md_context); - - error = mbedtls_md_setup(&dgst->mbedtls_md_context, info, 0); - if (error) { - ERROR("mbedtls_md_setup: %d", error); - goto fail; - } - - error = mbedtls_md_starts(&dgst->mbedtls_md_context); - if (error) { - ERROR("mbedtls_md_starts: %d", error); - goto fail; - } - - return dgst; - -fail: - free(dgst); - return 0; -} - -int swupdate_HASH_update(struct swupdate_digest *dgst, const unsigned char *buf, - size_t len) -{ - if (!dgst) { - return -EFAULT; - } - - const int error = mbedtls_md_update(&dgst->mbedtls_md_context, buf, len); - if (error) { - ERROR("mbedtls_md_update: %d", error); - return -EIO; - } - - return 0; -} - -int swupdate_HASH_final(struct swupdate_digest *dgst, unsigned char *md_value, - unsigned int *md_len) -{ - if (!dgst) { - return -EFAULT; - } - - int error = mbedtls_md_finish(&dgst->mbedtls_md_context, md_value); - if (error) { - return -EINVAL; - } - if (md_len) { - *md_len = mbedtls_md_get_size(dgst->mbedtls_md_context.md_info); - } - return 1; - -} - -void swupdate_HASH_cleanup(struct swupdate_digest *dgst) -{ - if (!dgst) { - return; - } - - mbedtls_md_free(&dgst->mbedtls_md_context); - free(dgst); -} - -/* - * Just a wrap function to memcmp - */ -int swupdate_HASH_compare(const unsigned char *hash1, const unsigned char *hash2) -{ - return memcmp(hash1, hash2, SHA256_HASH_LENGTH) ? -1 : 0; -} - -int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile) -{ - struct swupdate_digest *dgst; - - dgst = calloc(1, sizeof(*dgst)); - if (!dgst) { - return -ENOMEM; - } - -#ifdef CONFIG_SIGNED_IMAGES - mbedtls_pk_init(&dgst->mbedtls_pk_context); - - int error = mbedtls_pk_parse_public_keyfile(&dgst->mbedtls_pk_context, keyfile); - if (error) { - ERROR("mbedtls_pk_parse_public_keyfile: %d", error); - free(dgst); - return -EIO; - } -#endif - - sw->dgst = dgst; - return 0; -} diff -Nru swupdate-2024.12.1+dfsg/crypto/Kconfig swupdate-2025.12+dfsg/crypto/Kconfig --- swupdate-2024.12.1+dfsg/crypto/Kconfig 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/Kconfig 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,138 @@ +# SPDX-FileCopyrightText: 2024 Stefano Babic +# +# SPDX-License-Identifier: GPL-2.0-only + +menu "Crypto libraries" + config SSL_IMPL_OPENSSL + bool "OpenSSL" + default y + depends on HAVE_LIBSSL + + config SSL_IMPL_WOLFSSL + bool "wolfSSL (with OpenSSL compatibility layer)" + depends on HAVE_WOLFSSL + select CMS_IGNORE_CERTIFICATE_PURPOSE if SIGALG_CMS + select CMS_SKIP_UNKNOWN_SIGNERS if SIGALG_CMS + + config SSL_IMPL_MBEDTLS + bool "mbedTLS" + depends on HAVE_MBEDTLS + + config SSL_IMPL_GPGME + bool "gpgme" + depends on HAVE_GPGME +endmenu + +config HASH_VERIFY + bool "Allow to add sha256 hash to each image" + depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS + help + Allow to add a sha256 hash to an artifact. + This is automatically set in case of Signed Image + +comment "Hash checking needs an SSL implementation" + depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS + +config SIGNED_IMAGES + bool "Enable verification of signed images" + depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS + select HASH_VERIFY +comment "Image signature verification needs an SSL implementation" + depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS + +menu "Signature verification algorithm" + depends on SIGNED_IMAGES + + config SIGALG_RAWRSA + bool "RSA PKCS#1.5" + default n + depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS + + config SIGALG_RSAPSS + bool "RSA PSS" + default n + depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS + + config SIGALG_CMS + bool "Cryptographic Message Syntax (CMS) / PKCS#7" + depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL + + config SIGALG_GPG + bool "GPG signing" + depends on SSL_IMPL_GPGME +endmenu + +menu "CMS / PKCS#7 signature verification options" + depends on SIGALG_CMS + +config CMS_IGNORE_EXPIRED_CERTIFICATE + bool "Ignore expired certificates" + depends on SIGALG_CMS + +config CMS_IGNORE_CERTIFICATE_PURPOSE + bool "Ignore X.509 certificate purpose" + depends on SIGALG_CMS + +config CMS_SKIP_UNKNOWN_SIGNERS + bool "Ignore unverifiable signatures if known signer verifies" + depends on SIGALG_CMS +endmenu + +menu "Encryption" + +config ENCRYPTED_IMAGES + bool "Images can be encrypted with a symmetric key" + depends on SSL_IMPL_OPENSSL || SSL_IMPL_WOLFSSL || SSL_IMPL_MBEDTLS +comment "Image encryption needs an SSL implementation" + depends on !SSL_IMPL_OPENSSL && !SSL_IMPL_WOLFSSL && !SSL_IMPL_MBEDTLS + +config ENCRYPTED_SW_DESCRIPTION + bool "Even sw-description is encrypted" + depends on ENCRYPTED_IMAGES + help + sw-description is not encrypted as default, but it is encrypted + if this is set. It is a compile time option, and mix of plain and + encrypted sw-descriptions is not possible. + +config ASYM_ENCRYPTED_SW_DESCRIPTION + bool "Asymmetrical encrypted sw-description" + depends on ENCRYPTED_SW_DESCRIPTION + select SIGALG_ASYM_DEC_CMS + default n + help + This option enables support for asymmetrical encrypted sw-description, + making it possible to decrypt images device specific. + +menu "Asymmetric decryption algorithm" + depends on ASYM_ENCRYPTED_SW_DESCRIPTION + + config SIGALG_ASYM_DEC_CMS + bool "Cryptographic Message Syntax (CMS) / PKCS#7" + default n + depends on SSL_IMPL_OPENSSL +endmenu + +config ENCRYPTED_IMAGES_HARDEN_LOGGING + bool "Harden logging for encrypted images" + default n + depends on ENCRYPTED_IMAGES + help + This option addresses a theoretical weakness of the AES-CBC encryption in + combination with streamed images. An adversary can target each 16-byte + block of encrypted data within an image and decrypt it, if they can apply a + huge amount of manipulated firmware updates and observe the logged + messages. On average, 2048 update attempts are needed for each block. + Select if this scenario poses a risk. If set, log messages related to a + hash mismatch and errors in the decryption finalization (padding) of a + streamed image are suppressed. + +config PKCS11 + bool "Enable PKCS#11 cryptographic operations" + default n + depends on SSL_IMPL_WOLFSSL && HAVE_P11KIT && ENCRYPTED_IMAGES + help + Enable using PKCS#11 for AES decryption instead of having the plain + key available in a file. This is implemented with wolfSSL independent + from the SSL implementation and replaces the plain key method. +endmenu + diff -Nru swupdate-2024.12.1+dfsg/crypto/Makefile swupdate-2025.12+dfsg/crypto/Makefile --- swupdate-2024.12.1+dfsg/crypto/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/Makefile 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,30 @@ +# Copyright (C) 2014-2018 Stefano Babic +# +# SPDX-License-Identifier: GPL-2.0-only + +ifeq ($(CONFIG_SSL_IMPL_OPENSSL),y) +obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_openssl.o +obj-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify_openssl.o +obj-$(CONFIG_SIGALG_RSAPSS) += swupdate_rsa_verify_openssl.o +obj-$(CONFIG_SIGALG_CMS) += swupdate_cms_verify_openssl.o +obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_openssl.o +obj-$(CONFIG_SIGALG_ASYM_DEC_CMS) += swupdate_decrypt_openssl_cms.o +endif + +ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) +obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_wolfssl.o +obj-$(CONFIG_SIGALG_CMS) += swupdate_pkcs7_verify_wolfssl.o +ifeq ($(CONFIG_PKCS11),y) +obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_wolfssl.o +endif +endif + +ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y) +obj-$(CONFIG_HASH_VERIFY) += swupdate_HASH_mbedtls.o +obj-$(CONFIG_ENCRYPTED_IMAGES) += swupdate_decrypt_mbedtls.o +obj-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify_mbedtls.o +obj-$(CONFIG_SIGALG_RSAPSS) += swupdate_rsa_verify_mbedtls.o +endif +ifeq ($(CONFIG_SSL_IMPL_GPGME),y) +obj-$(CONFIG_SIGALG_GPG) += swupdate_gpg_verify.o +endif diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_HASH_mbedtls.c swupdate-2025.12+dfsg/crypto/swupdate_HASH_mbedtls.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_HASH_mbedtls.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_HASH_mbedtls.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: 2019 Laszlo Ashin +// +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include + +#include "util.h" +#include "swupdate_crypto.h" +#include "swupdate_mbedtls.h" + +#define MODNAME "mbedtlsSHA256" + +static swupdate_HASH_lib hash; + +static char *algo_upper(const char *algo) +{ + static char result[16]; + unsigned i; + + for (i = 0; algo[i] && (i < sizeof(result) - 1); ++i) { + result[i] = toupper(algo[i]); + } + result[i] = '\0'; + return result; +} + +static void *mbedtls_HASH_init(const char *algo) +{ + struct mbedtls_digest *dgst; + int error; + + const mbedtls_md_info_t *info = mbedtls_md_info_from_string(algo_upper(algo)); + if (!info) { + ERROR("mbedtls_md_info_from_string(\"%s\")", algo); + return NULL; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + return NULL; + } + + mbedtls_md_init(&dgst->mbedtls_md_context); + + error = mbedtls_md_setup(&dgst->mbedtls_md_context, info, 0); + if (error) { + ERROR("mbedtls_md_setup: %d", error); + goto fail; + } + + error = mbedtls_md_starts(&dgst->mbedtls_md_context); + if (error) { + ERROR("mbedtls_md_starts: %d", error); + goto fail; + } + + return dgst; + +fail: + free(dgst); + return 0; +} + +static int mbedtls_HASH_update(void *ctx, const unsigned char *buf, + size_t len) +{ + struct mbedtls_digest *dgst = (struct mbedtls_digest *)ctx; + if (!dgst) { + return -EFAULT; + } + + const int error = mbedtls_md_update(&dgst->mbedtls_md_context, buf, len); + if (error) { + ERROR("mbedtls_md_update: %d", error); + return -EIO; + } + + return 0; +} + +static int mbedtls_HASH_final(void *ctx, unsigned char *md_value, + unsigned int *md_len) +{ + struct mbedtls_digest *dgst = (struct mbedtls_digest *)ctx; + if (!dgst) { + return -EFAULT; + } + + int error = mbedtls_md_finish(&dgst->mbedtls_md_context, md_value); + if (error) { + return -EINVAL; + } + if (md_len) { +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + *md_len = mbedtls_md_get_size(mbedtls_md_info_from_ctx(&dgst->mbedtls_md_context)); +#else + *md_len = mbedtls_md_get_size(dgst->mbedtls_md_context.md_info); +#endif + } + return 1; + +} + +static void mbedtls_HASH_cleanup(void *ctx) +{ + struct mbedtls_digest *dgst = (struct mbedtls_digest *)ctx; + if (!dgst) { + return; + } + + mbedtls_md_free(&dgst->mbedtls_md_context); + free(dgst); +} + +/* + * Just a wrap function to memcmp + */ +static int mbedtls_HASH_compare(const unsigned char *hash1, const unsigned char *hash2) +{ + return memcmp(hash1, hash2, SHA256_HASH_LENGTH) ? -1 : 0; +} + +__attribute__((constructor)) +static void openssl_hash(void) +{ + hash.HASH_init = mbedtls_HASH_init; + hash.HASH_update = mbedtls_HASH_update; + hash.HASH_final = mbedtls_HASH_final; + hash.HASH_compare = mbedtls_HASH_compare; + hash.HASH_cleanup = mbedtls_HASH_cleanup; + (void)register_hashlib(MODNAME, &hash); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_HASH_openssl.c swupdate-2025.12+dfsg/crypto/swupdate_HASH_openssl.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_HASH_openssl.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_HASH_openssl.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,124 @@ +/* + * (C) Copyright 2024 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Code mostly taken from openssl examples + */ + +#include +#include +#include +#include +#include "swupdate.h" +#if !defined(NO_INCLUDE_OPENSSL) +#define MODNAME "opensslSHA256" +#include "swupdate_openssl.h" +#endif +#include "util.h" +#include "compat.h" +#include "swupdate_crypto.h" + +static swupdate_HASH_lib hash; + +static int dgst_init(struct openssl_digest *dgst, const EVP_MD *md) +{ + int rc; + + ERR_clear_error(); + rc = EVP_DigestInit_ex(dgst->ctx, md, NULL); + if (rc != 1) { + ERROR("EVP_DigestInit_ex failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return -EINVAL; /* failed */ + } + + return 0; +} + +static void *openssl_HASH_init(const char *SHAlength) +{ + struct openssl_digest *dgst; + const EVP_MD *md; + int ret; + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + return NULL; + } + + if ((!SHAlength) || strcmp(SHAlength, "sha1")) + md = EVP_sha256(); + else + md = EVP_sha1(); + + dgst->ctx = EVP_MD_CTX_create(); + if(dgst->ctx == NULL) { + ERROR("EVP_MD_CTX_create failed, error 0x%lx", ERR_get_error()); + free(dgst); + return NULL; + } + + ret = dgst_init(dgst, md); + if (ret) { + free(dgst); + return NULL; + } + + return dgst; +} + +static int openssl_HASH_update(void *ctx, const unsigned char *buf, size_t len) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + if (!dgst) + return -EFAULT; + + if (EVP_DigestUpdate (dgst->ctx, buf, len) != 1) + return -EIO; + + return 0; +} + +static int openssl_HASH_final(void *ctx, unsigned char *md_value, + unsigned int *md_len) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + if (!dgst) + return -EFAULT; + + return EVP_DigestFinal_ex (dgst->ctx, md_value, md_len); + +} + +static void openssl_HASH_cleanup(void *ctx) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + if (dgst) { + EVP_MD_CTX_destroy(dgst->ctx); + free(dgst); + dgst = NULL; + } +} + +static int openssl_HASH_compare(const unsigned char *hash1, const unsigned char *hash2) +{ + int i; + + for (i = 0; i < SHA256_HASH_LENGTH; i++) + if (hash1[i] != hash2[i]) + return -1; + + return 0; +} + +__attribute__((constructor)) +static void openssl_hash(void) +{ + hash.HASH_init = openssl_HASH_init; + hash.HASH_update = openssl_HASH_update; + hash.HASH_final = openssl_HASH_final; + hash.HASH_compare = openssl_HASH_compare; + hash.HASH_cleanup = openssl_HASH_cleanup; + (void)register_hashlib(MODNAME, &hash); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_HASH_wolfssl.c swupdate-2025.12+dfsg/crypto/swupdate_HASH_wolfssl.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_HASH_wolfssl.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_HASH_wolfssl.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,25 @@ +/* + * (C) Copyright 2024 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Code mostly taken from openssl examples + */ + + +#include +#include +#include +#include +#include "swupdate.h" +#include "swupdate_wolfssl.h" + +/* + * Switch to WolfSSL in module + */ +#define NO_INCLUDE_OPENSSL +#define MODNAME "WolfSSL" + +#include "swupdate_HASH_openssl.c" + diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_cms_verify_openssl.c swupdate-2025.12+dfsg/crypto/swupdate_cms_verify_openssl.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_cms_verify_openssl.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_cms_verify_openssl.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,418 @@ +/* + * (C) Copyright 2019 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Code mostly taken from openssl examples + */ + +#include +#include +#include +#include +#include "swupdate.h" +#include "swupdate_openssl.h" +#include "util.h" +#include "swupdate_crypto.h" + +#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS) +#define VERIFY_UNKNOWN_SIGNER_FLAGS (CMS_NO_SIGNER_CERT_VERIFY) +#else +#define VERIFY_UNKNOWN_SIGNER_FLAGS (0) +#endif + +#define MODNAME "opensslCMS" + +static swupdate_dgst_lib libs; + +static int openssl_map_purpose [] = { + [CERT_PURPOSE_EMAIL_PROT] = X509_PURPOSE_SMIME_SIGN, + [CERT_PURPOSE_CODE_SIGN] = X509_PURPOSE_CODE_SIGN +}; + +static inline int get_x509_purpose(unsigned int purpose) { + if (purpose > CERT_PURPOSE_LAST) + purpose = CERT_PURPOSE_EMAIL_PROT; + return openssl_map_purpose[purpose]; +} + +static inline uint32_t SSL_X509_get_extension_flags(X509 *x) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + return x->ex_flags; +#else + return X509_get_extension_flags(x); +#endif +} + +static inline uint32_t SSL_X509_get_extended_key_usage(X509 *x) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + return x->ex_xkusage; +#else + return X509_get_extended_key_usage(x); +#endif +} + +#ifndef CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE +static int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca) +{ + X509 *x = (X509 *)crt; + uint32_t ex_flags = SSL_X509_get_extension_flags(x); + uint32_t ex_xkusage = SSL_X509_get_extended_key_usage(x); + + (void)xp; + + if (ca) { + int idx; + const X509_PURPOSE *pt; + + if ((ex_flags & EXFLAG_XKUSAGE) && !(ex_xkusage & XKU_CODE_SIGN)) + return 0; + + idx = X509_PURPOSE_get_by_id(X509_PURPOSE_OCSP_HELPER); + if (idx == -1) + return 0; + + pt = X509_PURPOSE_get0(idx); + return pt->check_purpose(pt, x, ca); + } + + return (ex_flags & EXFLAG_XKUSAGE) && (ex_xkusage & XKU_CODE_SIGN); +} +#endif + +static int cms_verify_callback(int ok, X509_STORE_CTX *ctx) { + int cert_error = X509_STORE_CTX_get_error(ctx); + + if (!ok) { + switch (cert_error) { +#if defined(CONFIG_CMS_IGNORE_EXPIRED_CERTIFICATE) + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CERT_NOT_YET_VALID: + ok = 1; + break; +#endif +#if defined(CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE) + case X509_V_ERR_INVALID_PURPOSE: + ok = 1; + break; +#endif + default: + break; + } + } + + return ok; +} + +static X509_STORE *load_cert_chain(const char *file) +{ + X509_STORE *castore = X509_STORE_new(); + if (!castore) { + return NULL; + } + + /* + * Set error callback function for verification of CRTs and CRLs in order + * to ignore some errors depending on configuration + */ + X509_STORE_set_verify_cb(castore, cms_verify_callback); + + BIO *castore_bio = BIO_new_file(file, "r"); + if (!castore_bio) { + TRACE("failed: BIO_new_file(%s)", file); + return NULL; + } + + int crt_count = 0; + X509 *crt = NULL; + do { + crt = PEM_read_bio_X509(castore_bio, NULL, 0, NULL); + if (crt) { + crt_count++; + char *subj = X509_NAME_oneline(X509_get_subject_name(crt), NULL, 0); + char *issuer = X509_NAME_oneline(X509_get_issuer_name(crt), NULL, 0); + TRACE("Read PEM #%d: %s %s", crt_count, issuer, subj); + free(subj); + free(issuer); + if (X509_STORE_add_cert(castore, crt) == 0) { + TRACE("Adding certificate to X509_STORE failed"); + BIO_free(castore_bio); + X509_STORE_free(castore); + return NULL; + } + } + } while (crt); + BIO_free(castore_bio); + + if (crt_count == 0) { + X509_STORE_free(castore); + return NULL; + } + + return castore; +} + +static inline int next_common_name(X509_NAME *subject, int i) +{ + return X509_NAME_get_index_by_NID(subject, NID_commonName, i); +} + +static int check_common_name(X509_NAME *subject, const char *name) +{ + int i = -1, ret = 1; + + while ((i = next_common_name(subject, i)) > -1) { + X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, i); + ASN1_STRING *d = X509_NAME_ENTRY_get_data(e); + unsigned char *cn; + size_t len = strlen(name); + bool matches = (ASN1_STRING_to_UTF8(&cn, d) == (int)len) + && (strncmp(name, (const char *)cn, len) == 0); + + OPENSSL_free(cn); + if (!matches) { + char *subj = X509_NAME_oneline(subject, NULL, 0); + + ERROR("common name of '%s' does not match expected '%s'", + subj, name); + OPENSSL_free(subj); + return 2; + } else { + ret = 0; + } + } + + if (ret == 0) { + char *subj = X509_NAME_oneline(subject, NULL, 0); + + TRACE("verified signer cert: %s", subj); + OPENSSL_free(subj); + } + + return ret; +} + +static int check_signer_name(CMS_ContentInfo *cms, const char *name) +{ + STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms); + STACK_OF(X509) *crts; + int i, ret = 1; + + if ((name == NULL) || (name[0] == '\0')) + return 0; + + crts = CMS_get1_certs(cms); + for (i = 0; i < sk_CMS_SignerInfo_num(infos); ++i) { + CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i); + int j; + + for (j = 0; j < sk_X509_num(crts); ++j) { + X509 *crt = sk_X509_value(crts, j); + + if (CMS_SignerInfo_cert_cmp(si, crt) == 0) { + ret = check_common_name( + X509_get_subject_name(crt), name); + } + } + } + sk_X509_pop_free(crts, X509_free); + + return ret; +} + +#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS) +static int check_verified_signer(CMS_ContentInfo* cms, X509_STORE* store) +{ + int i, ret = 1; + + X509_STORE_CTX *ctx = X509_STORE_CTX_new(); + STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms); + STACK_OF(X509)* cms_certs = CMS_get1_certs(cms); + + if (!ctx) { + ERROR("Failed to allocate verification context"); + return ret; + } + + for (i = 0; i < sk_CMS_SignerInfo_num(infos) && ret != 0; ++i) { + CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i); + X509 *signer = NULL; + + CMS_SignerInfo_get0_algs(si, NULL, &signer, NULL, NULL); + if (!X509_STORE_CTX_init(ctx, store, signer, cms_certs)) { + ERROR("Failed to initialize signer verification operation"); + break; + } + + X509_STORE_CTX_set_default(ctx, "smime_sign"); + if (X509_verify_cert(ctx) > 0) { + TRACE("Verified signature %d in signer sequence", i); + ret = 0; + } else { + TRACE("Failed to verify certificate %d in signer sequence", i); + } + + X509_STORE_CTX_cleanup(ctx); + } + + X509_STORE_CTX_free(ctx); + + return ret; +} +#endif + +static int openssl_cms_dgst_init(struct swupdate_cfg *sw, const char *keyfile) +{ + struct openssl_digest *dgst; + int ret; + + /* + * Check that it was not called before + */ + if (sw->dgst) { + return -EBUSY; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + ret = -ENOMEM; + goto dgst_init_error; + } + + /* + * Load certificate chain + */ + dgst->certs = load_cert_chain(keyfile); + if (!dgst->certs) { + ERROR("Error loading certificate chain from %s", keyfile); + ret = -EINVAL; + goto dgst_init_error; + } + +#ifndef CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE + { + static char code_sign_name[] = "Code signing"; + static char code_sign_sname[] = "codesign"; + + if (!X509_PURPOSE_add(X509_PURPOSE_CODE_SIGN, X509_TRUST_EMAIL, + 0, check_code_sign, code_sign_name, + code_sign_sname, NULL)) { + ERROR("failed to add code sign purpose"); + ret = -EINVAL; + goto dgst_init_error; + } + } + + if (!X509_STORE_set_purpose(dgst->certs, get_x509_purpose(sw->cert_purpose))) { + ERROR("failed to set purpose"); + ret = -EINVAL; + goto dgst_init_error; + } +#endif + + /* + * Create context + */ + dgst->ctx = EVP_MD_CTX_create(); + if(dgst->ctx == NULL) { + ERROR("EVP_MD_CTX_create failed, error 0x%lx", ERR_get_error()); + ret = -ENOMEM; + goto dgst_init_error; + } + + sw->dgst = dgst; + + return 0; + +dgst_init_error: + if (dgst) + free(dgst); + + return ret; +} + +static int openssl_cms_verify_file(void *ctx, const char *sigfile, + const char *file, const char *signer_name) +{ + int status = -EFAULT; + CMS_ContentInfo *cms = NULL; + BIO *content_bio = NULL; + + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + + /* Open CMS blob that needs to be checked */ + BIO *sigfile_bio = BIO_new_file(sigfile, "rb"); + if (!sigfile_bio) { + ERROR("%s cannot be opened", sigfile); + status = -EBADF; + goto out; + } + + /* Parse the DER-encoded CMS message */ + cms = d2i_CMS_bio(sigfile_bio, NULL); + if (!cms) { + ERROR("%s cannot be parsed as DER-encoded CMS signature blob", sigfile); + status = -EFAULT; + goto out; + } + + if (check_signer_name(cms, signer_name)) { + ERROR("failed to verify signer name"); + status = -EFAULT; + goto out; + } + + /* Open the content file (data which was signed) */ + content_bio = BIO_new_file(file, "rb"); + if (!content_bio) { + ERROR("%s cannot be opened", file); + status = -EBADF; + goto out; + } + + /* Then try to verify signature */ + if (!CMS_verify(cms, NULL, dgst->certs, content_bio, + NULL, CMS_BINARY | VERIFY_UNKNOWN_SIGNER_FLAGS)) { + ERR_print_errors_fp(stderr); + ERROR("Signature verification failed"); + status = -EBADMSG; + goto out; + } + +#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS) + /* Verify at least one signer authenticates */ + if (check_verified_signer(cms, dgst->certs)) { + ERROR("Authentication of all signatures failed"); + status = -EBADMSG; + goto out; + } +#endif + + TRACE("Verified OK"); + + /* Signature is valid */ + status = 0; +out: + + if (cms) { + CMS_ContentInfo_free(cms); + } + if (content_bio) { + BIO_free(content_bio); + } + if (sigfile_bio) { + BIO_free(sigfile_bio); + } + return status; +} + +__attribute__((constructor)) +static void openssl_dgst(void) +{ + libs.dgst_init = openssl_cms_dgst_init; + libs.verify_file = openssl_cms_verify_file; + (void)register_dgstlib(MODNAME, &libs); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_decrypt_mbedtls.c swupdate-2025.12+dfsg/crypto/swupdate_decrypt_mbedtls.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_decrypt_mbedtls.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_decrypt_mbedtls.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,161 @@ +// SPDX-FileCopyrightText: 2019 Laszlo Ashin +// +// SPDX-License-Identifier: GPL-2.0-only + +#include + +#include "util.h" +#include "swupdate_crypto.h" +#include "swupdate_mbedtls.h" + +#define MODNAME "mbedtlsAES" + +static swupdate_decrypt_lib mbedtls; + +static void *mbedtls_DECRYPT_init(unsigned char *key, char keylen, unsigned char *iv, cipher_t cipher) +{ + struct mbedtls_digest *dgst; + mbedtls_cipher_type_t cipher_type; + const mbedtls_cipher_info_t *cipher_info; + int key_bitlen; + int error; + + /* Temporary to remove warning */ + cipher = cipher; + + if ((key == NULL) || (iv == NULL)) { + ERROR("no key or iv provided for decryption!"); + return NULL; + } + + switch (keylen) { + case AES_128_KEY_LEN: + cipher_type = MBEDTLS_CIPHER_AES_128_CBC; + key_bitlen = 128; + break; + case AES_192_KEY_LEN: + cipher_type = MBEDTLS_CIPHER_AES_192_CBC; + key_bitlen = 192; + break; + case AES_256_KEY_LEN: + cipher_type = MBEDTLS_CIPHER_AES_256_CBC; + key_bitlen = 256; + break; + default: + return NULL; + } + + cipher_info = mbedtls_cipher_info_from_type(cipher_type); + if (!cipher_info) { + ERROR("mbedtls_cipher_info_from_type"); + return NULL; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + return NULL; + } + + mbedtls_cipher_init(&dgst->mbedtls_cipher_context); + + error = mbedtls_cipher_setup(&dgst->mbedtls_cipher_context, cipher_info); + if (error) { + ERROR("mbedtls_cipher_setup: %d", error); + goto fail; + } + + error = mbedtls_cipher_setkey(&dgst->mbedtls_cipher_context, key, key_bitlen, MBEDTLS_DECRYPT); + if (error) { + ERROR("mbedtls_cipher_setkey: %d", error); + goto fail; + } + +#ifdef MBEDTLS_CIPHER_MODE_WITH_PADDING + error = mbedtls_cipher_set_padding_mode(&dgst->mbedtls_cipher_context, MBEDTLS_PADDING_PKCS7); + if (error) { + ERROR("mbedtls_cipher_set_padding_mode: %d", error); + goto fail; + } +#endif + + error = mbedtls_cipher_set_iv(&dgst->mbedtls_cipher_context, iv, 16); + if (error) { + ERROR("mbedtls_cipher_set_iv: %d", error); + goto fail; + } + + error = mbedtls_cipher_reset(&dgst->mbedtls_cipher_context); + if (error) { + ERROR("mbedtls_cipher_reset: %d", error); + goto fail; + } + + return dgst; + +fail: + free(dgst); + return NULL; +} + +static int mbedtls_DECRYPT_update(void *ctx, unsigned char *buf, + int *outlen, const unsigned char *cryptbuf, int inlen) +{ + struct mbedtls_digest *dgst = (struct mbedtls_digest *)ctx; + int error; + size_t olen = *outlen; + + error = mbedtls_cipher_update(&dgst->mbedtls_cipher_context, cryptbuf, inlen, buf, &olen); + if (error) { + ERROR("mbedtls_cipher_update: %d", error); + return -EFAULT; + } + *outlen = olen; + + return 0; +} + +static int mbedtls_DECRYPT_final(void *ctx, unsigned char *buf, + int *outlen) +{ + int error; + size_t olen = *outlen; + struct mbedtls_digest *dgst = (struct mbedtls_digest *)ctx; + + if (!dgst) { + return -EINVAL; + } + + error = mbedtls_cipher_finish(&dgst->mbedtls_cipher_context, buf, &olen); + if (error) { +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING + ERROR("mbedtls_cipher_finish: %d", error); +#endif + return -EFAULT; + } + *outlen = olen; + + return 0; + +} + +static void mbedtls_DECRYPT_cleanup(void *ctx) +{ + struct mbedtls_digest *dgst = (struct mbedtls_digest *)ctx; + + if (!dgst) { + return; + } + + mbedtls_cipher_free(&dgst->mbedtls_cipher_context); + free(dgst); +} + +__attribute__((constructor)) +static void mbedtls_probe(void) +{ + mbedtls.DECRYPT_init = mbedtls_DECRYPT_init; + mbedtls.DECRYPT_update = mbedtls_DECRYPT_update; + mbedtls.DECRYPT_final = mbedtls_DECRYPT_final; + mbedtls.DECRYPT_cleanup = mbedtls_DECRYPT_cleanup; + (void)register_cryptolib(MODNAME, &mbedtls); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_decrypt_openssl.c swupdate-2025.12+dfsg/crypto/swupdate_decrypt_openssl.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_decrypt_openssl.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_decrypt_openssl.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,148 @@ +/* + * (C) Copyright 2016 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Code mostly taken from openssl examples + * + */ +#include +#include +#include +#include +#include +#include "swupdate.h" +#include "swupdate_openssl.h" +#include "util.h" +#include "swupdate_crypto.h" + +#define MODNAME "opensslAES" + +static void openssl_probe(void); + +static swupdate_decrypt_lib openssl; +static void *openssl_DECRYPT_init(unsigned char *key, char keylen, unsigned char *iv, cipher_t reqcipher) +{ + struct openssl_digest *dgst; + const EVP_CIPHER *cipher; + int ret; + + /* Temporary to remove warning */ + reqcipher = reqcipher; + + if ((key == NULL) || (iv == NULL)) { + ERROR("no key or iv provided for decryption!"); + return NULL; + } + + switch (keylen) { + case AES_128_KEY_LEN: + cipher = EVP_aes_128_cbc(); + break; + case AES_192_KEY_LEN: + cipher = EVP_aes_192_cbc(); + break; + case AES_256_KEY_LEN: + cipher = EVP_aes_256_cbc(); + break; + default: + return NULL; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + return NULL; + } + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_CIPHER_CTX_init(&dgst->ctxdec); +#else + dgst->ctxdec = EVP_CIPHER_CTX_new(); + if (dgst->ctxdec == NULL) { + ERROR("Cannot initialize cipher context."); + free(dgst); + return NULL; + } + if (EVP_CIPHER_CTX_reset(dgst->ctxdec) != 1) { + ERROR("Cannot reset cipher context."); + EVP_CIPHER_CTX_free(dgst->ctxdec); + free(dgst); + return NULL; + } +#endif + + /* + * Check openSSL documentation for return errors + */ + ret = EVP_DecryptInit_ex(SSL_GET_CTXDEC(dgst), cipher, NULL, key, iv); + if (ret != 1) { + const char *reason = ERR_reason_error_string(ERR_peek_error()); + ERROR("Decrypt Engine not initialized, error 0x%lx, reason: %s", ERR_get_error(), + reason != NULL ? reason : "unknown"); + free(dgst); + return NULL; + } + + return dgst; +} + +static int openssl_DECRYPT_update(void *ctx, unsigned char *buf, + int *outlen, const unsigned char *cryptbuf, int inlen) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + if (!dgst) + return -EINVAL; + if (EVP_DecryptUpdate(SSL_GET_CTXDEC(dgst), buf, outlen, cryptbuf, inlen) != 1) { + const char *reason = ERR_reason_error_string(ERR_peek_error()); + ERROR("Update: Decryption error 0x%lx, reason: %s", ERR_get_error(), + reason != NULL ? reason : "unknown"); + return -EFAULT; + } + + return 0; +} + +static int openssl_DECRYPT_final(void *ctx, unsigned char *buf, + int *outlen) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + if (!dgst) + return -EINVAL; + + if (EVP_DecryptFinal_ex(SSL_GET_CTXDEC(dgst), buf, outlen) != 1) { +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING + const char *reason = ERR_reason_error_string(ERR_peek_error()); + ERROR("Final: Decryption error 0x%lx, reason: %s", ERR_get_error(), + reason != NULL ? reason : "unknown"); +#endif + return -EFAULT; + } + + return 0; + +} + +static void openssl_DECRYPT_cleanup(void *ctx) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + if (dgst) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_CIPHER_CTX_cleanup(SSL_GET_CTXDEC(dgst)); +#else + EVP_CIPHER_CTX_free(SSL_GET_CTXDEC(dgst)); +#endif + free(dgst); + dgst = NULL; + } +} + +__attribute__((constructor)) +static void openssl_probe(void) +{ + openssl.DECRYPT_init = openssl_DECRYPT_init; + openssl.DECRYPT_update = openssl_DECRYPT_update; + openssl.DECRYPT_final = openssl_DECRYPT_final; + openssl.DECRYPT_cleanup = openssl_DECRYPT_cleanup; + (void)register_cryptolib(MODNAME, &openssl); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_decrypt_openssl_cms.c swupdate-2025.12+dfsg/crypto/swupdate_decrypt_openssl_cms.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_decrypt_openssl_cms.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_decrypt_openssl_cms.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,201 @@ +/* + * (C) Copyright 2024-2025 + * Michael Glembotzki, iris-GmbH infrared & intelligent sensors, michael.glembotzki@iris-sensing.com + * + * (C) Copyright 2025 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Code mostly taken from openssl examples + */ +#include +#include +#include +#include +#include +#include +#include "swupdate.h" +#include "util.h" +#include "swupdate_crypto.h" +#include "swupdate_openssl.h" + +static void openssl_cms_probe(void); +static swupdate_decrypt_lib opensslCMS; + +static void *openssl_CMS_DECRYPT_init(unsigned char *key, + __attribute__ ((__unused__)) char keylen, + __attribute__ ((__unused__)) unsigned char *iv, + __attribute__ ((__unused__)) cipher_t cipher) +{ + struct openssl_digest *dgst = NULL; + X509 *decrypt_cert = NULL; + EVP_PKEY *pkey = NULL; + BIO *tbio = NULL; + const char *keypair_file = (const char *)key; + + if (!keypair_file) { + ERROR("A PEM private key is not given !"); + return NULL; + } + + tbio = BIO_new_file(keypair_file, "r"); + if (!tbio) { + ERROR("%s cannot be opened", keypair_file); + goto err; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + ERROR("OOM, Cannot create internal crypto structure"); + goto err; + } + + dgst->cryptbuf = BIO_new(BIO_s_mem()); + if (!dgst->cryptbuf) { + ERROR("Cannot create openSSL BIO buffer"); + goto err; + } + + + pkey = PEM_read_bio_PrivateKey(tbio, NULL, 0, NULL); + if (!pkey) { + ERROR("Decryption key not found"); + goto err; + } + dgst->pkey = pkey; + BIO_reset(tbio); + + /* + * Cert is optional, but helps OpenSSL find the right key faster in + * large CMS recipient lists. + */ + decrypt_cert = PEM_read_bio_X509(tbio, NULL, 0, NULL); + if (!decrypt_cert) { + WARN("Decryption cert not found"); + } else { + dgst->decrypt_cert = decrypt_cert; + } + BIO_free(tbio); + + return (dgst); + +err: + if (tbio) + BIO_free(tbio); + if (dgst) { + if (dgst->cryptbuf) + BIO_free(dgst->cryptbuf); + free (dgst); + } + + return NULL; +} + +/* + * Decrypting CMS requires that the encrypted message is in memory. + * So store the crypted message, and really do the decryption in the final callback + */ +static int openssl_CMS_DECRYPT_update(void *ctx, + __attribute__ ((__unused__)) unsigned char *buf, + int *outlen, + const unsigned char *cryptbuf, + int inlen) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + + if (BIO_write(dgst->cryptbuf, cryptbuf, inlen) < 0) { + return -EFAULT; + } + + /* + * important: no plaintext is passed to buf during update() + */ + *outlen = 0; + + /* + * Return -EAGAIN instead of 0: + * 0 would signal EOF and stop the loop, so final() would never be called. + * -EAGAIN means: no plaintext yet, try again to fill the dgst->cryptbuf. + */ + return -EAGAIN; +} + +#define BUFSIZE 16384 +static int openssl_CMS_DECRYPT_final(void *ctx, unsigned char *buf, + int *outlen) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + + /* Decrypt only once and buffer the result in the internal plain BIO. */ + if (!dgst->plain) { + CMS_ContentInfo *cms = NULL; + BIO *out = BIO_new(BIO_s_mem()); + if (!out) { + ERROR("Cannot create openSSL BIO output buffer"); + return -ENOMEM; + } + + /* Parse message */ + cms = d2i_CMS_bio(dgst->cryptbuf, NULL); + if (!cms) { + ERROR("Cannot parse as DER-encoded CMS blob"); + BIO_free(out); + return -EFAULT; + } + + if (!CMS_decrypt(cms, dgst->pkey, dgst->decrypt_cert, NULL, out, 0)) { + ERROR("Decrypting failed"); + CMS_ContentInfo_free(cms); + BIO_free(out); + return -EFAULT; + } + CMS_ContentInfo_free(cms); + BIO_free(dgst->cryptbuf); + dgst->cryptbuf = NULL; + dgst->plain = out; + } + + int n = BIO_read(dgst->plain, buf, BUFSIZE); + if (n < 0) { + ERROR("Reading from plain BIO failed"); + return -EFAULT; + } + *outlen = n; + + if (BIO_pending(dgst->plain) > 0) { + /* more plain data is pending, let decrypt_step call final() again */ + return -EAGAIN; + } + + BIO_free(dgst->plain); + dgst->plain = NULL; + return 0; +} + +static void openssl_CMS_DECRYPT_cleanup(void *ctx) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + if (dgst) { + EVP_CIPHER_CTX_free(SSL_GET_CTXDEC(dgst)); + if (dgst->cryptbuf) + BIO_free(dgst->cryptbuf); + if (dgst->plain) + BIO_free(dgst->plain); + free(dgst); + } + + dgst = NULL; + + return; +} + +__attribute__((constructor)) +static void openssl_cms_probe(void) +{ + opensslCMS.DECRYPT_init = openssl_CMS_DECRYPT_init; + opensslCMS.DECRYPT_update = openssl_CMS_DECRYPT_update; + opensslCMS.DECRYPT_final = openssl_CMS_DECRYPT_final; + opensslCMS.DECRYPT_cleanup = openssl_CMS_DECRYPT_cleanup; + (void)register_cryptolib("opensslCMS", &opensslCMS); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_decrypt_wolfssl.c swupdate-2025.12+dfsg/crypto/swupdate_decrypt_wolfssl.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_decrypt_wolfssl.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_decrypt_wolfssl.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,212 @@ +/* + * (C) Copyright 2020, Linutronix GmbH + * Author: Bastian Germann + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include +#include +#include +#include +#include "swupdate.h" +#include "swupdate_wolfssl.h" +#include "util.h" +#include +#include +#include "swupdate_crypto.h" + +static swupdate_decrypt_lib wolfssl; + +#ifdef DEBUG_WOLFSSL +static void wolfssl_debug(int __attribute__ ((__unused__)) level, const char *const msg) +{ + DEBUG("%s", msg); +} +#endif + +static void *wolfssl_DECRYPT_init(unsigned char *key, + char __attribute__ ((__unused__)) keylen, unsigned char *iv, + cipher_t cipher) +{ + struct wolfssl_digest *dgst; + const char *library; + const char *pin; + const char *msg; + CK_ATTRIBUTE_PTR key_id; + int slot_id; + int err = 0; + int dev_id = 1; + + const char *uri = (const char *)key; + if ((uri == NULL) || (iv == NULL)) { + ERROR("PKCS#11 URI or AES IV missing for decryption!"); + return NULL; + } + + /* Temporary to remove warning */ + cipher = cipher; + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + return NULL; + } + + dgst->p11uri = p11_kit_uri_new(); + err = p11_kit_uri_parse(uri, P11_KIT_URI_FOR_ANY, dgst->p11uri); + if (err) { + msg = p11_kit_uri_message(err); + ERROR("PKCS#11 URI: %s", msg); + free(dgst); + return NULL; + } + + slot_id = p11_kit_uri_get_slot_id(dgst->p11uri); + key_id = p11_kit_uri_get_attribute(dgst->p11uri, CKA_ID); + pin = p11_kit_uri_get_pin_value(dgst->p11uri); + library = p11_kit_uri_get_module_path(dgst->p11uri); + if (slot_id == -1 || key_id == NULL || pin == NULL || library == NULL) { + ERROR("PKCS#11 URI must contain slot-id, id, pin-value, and module-path."); + goto err_free; + } + + // Set up a valid PKCS#7 block plus one state octet + for (int i = 0; i <= AES_BLK_SIZE; i++) { + dgst->last_decr[i] = AES_BLK_SIZE; + } + +#ifdef DEBUG_WOLFSSL + wolfSSL_SetLoggingCb(wolfssl_debug); + wolfSSL_Debugging_ON(); +#endif + wolfCrypt_Init(); + err = wc_Pkcs11_Initialize(&dgst->pkdev, library, NULL); + if (err) + goto err_msg; + + err = wc_Pkcs11Token_Init(&dgst->pktoken, &dgst->pkdev, slot_id, + "unspecified", (unsigned char *)pin, strlen(pin)); + if (err) + goto err_msg; + + err = wc_Pkcs11Token_Open(&dgst->pktoken, 0); + if (err) + goto err_msg; + + err = wc_CryptoCb_RegisterDevice(dev_id, wc_Pkcs11_CryptoDevCb, &dgst->pktoken); + if (err) + goto err_msg; + + err = wc_AesInit_Id(&dgst->ctxdec, key_id->pValue, key_id->ulValueLen, NULL, dev_id); + if (err) + goto err_msg; + + err = wc_AesSetIV(&dgst->ctxdec, iv); + if (err) + goto err_msg; + + INFO("PKCS#11 key set up successfully."); + return dgst; + +err_msg: + msg = wc_GetErrorString(err); + ERROR("PKCS#11 initialization failed: %s", msg); + +err_free: + wc_Pkcs11Token_Final(&dgst->pktoken); + wc_Pkcs11_Finalize(&dgst->pkdev); + + p11_kit_uri_free(dgst->p11uri); + free(dgst); + + return NULL; +} + +static int wolfssl_DECRYPT_update(void *ctx, unsigned char *buf, + int *outlen, const unsigned char *cryptbuf, int inlen) +{ + struct wolfssl_digest *dgst = (struct wolfssl_digest *)ctx; + // precondition: len(buf) >= inlen + AES_BLK_SIZE + unsigned char *pad_buf = &buf[AES_BLK_SIZE]; + const char *msg; + int err; + int one_off_sz = inlen - AES_BLK_SIZE; + + if (inlen < AES_BLK_SIZE) + return -EFAULT; + + if (dgst->last_decr[AES_BLK_SIZE]) { + // This is for the first decryption operation + pad_buf = buf; + dgst->last_decr[AES_BLK_SIZE] = 0; + *outlen = one_off_sz; + } else { + memcpy(buf, dgst->last_decr, AES_BLK_SIZE); + *outlen = inlen; + } + + err = wc_AesCbcDecrypt(&dgst->ctxdec, pad_buf, cryptbuf, inlen); + if (err) { + msg = wc_GetErrorString(err); + ERROR("PKCS#11 AES decryption failed: %s", msg); + return -EFAULT; + } + // Remember the last decrypted block which might contain padding + memcpy(dgst->last_decr, &pad_buf[one_off_sz], AES_BLK_SIZE); + + wc_AesSetIV(&dgst->ctxdec, &cryptbuf[one_off_sz]); + + return 0; +} + +// Gets rid of PKCS#7 padding +static int wolfssl_DECRYPT_final(void *ctx, unsigned char *buf, int *outlen) +{ + struct wolfssl_digest *dgst = (struct wolfssl_digest *)ctx; + unsigned char last_oct = dgst->last_decr[AES_BLK_SIZE - 1]; + if (last_oct > AES_BLK_SIZE || last_oct == 0) { +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING + ERROR("AES: Invalid PKCS#7 padding."); +#endif + return -EFAULT; + } + + for (int i = 2; i <= last_oct; i++) { + if (dgst->last_decr[AES_BLK_SIZE - i] != last_oct) { +#ifndef CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING + ERROR("AES: Invalid PKCS#7 padding."); +#endif + return -EFAULT; + } + } + + *outlen = AES_BLK_SIZE - last_oct; + memcpy(buf, dgst->last_decr, *outlen); + + return 0; +} + +static void wolfssl_DECRYPT_cleanup(void *ctx) +{ + struct wolfssl_digest *dgst = (struct wolfssl_digest *)ctx; + if (dgst) { + wc_Pkcs11Token_Final(&dgst->pktoken); + wc_Pkcs11_Finalize(&dgst->pkdev); + p11_kit_uri_free(dgst->p11uri); + + free(dgst); + dgst = NULL; + } + + wolfCrypt_Cleanup(); +} + +__attribute__((constructor)) +static void wolfssl_probe(void) +{ + wolfssl.DECRYPT_init = wolfssl_DECRYPT_init; + wolfssl.DECRYPT_update = wolfssl_DECRYPT_update; + wolfssl.DECRYPT_final = wolfssl_DECRYPT_final; + wolfssl.DECRYPT_cleanup = wolfssl_DECRYPT_cleanup; + (void)register_cryptolib("wolfssl", &wolfssl); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_gpg.h swupdate-2025.12+dfsg/crypto/swupdate_gpg.h --- swupdate-2024.12.1+dfsg/crypto/swupdate_gpg.h 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_gpg.h 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,17 @@ +/* + * (C) Copyright 2016-2024 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#pragma once + +#include +#include "util.h" + +struct gpg_digest { + char *gpg_home_directory; + bool verbose; + char *gpgme_protocol; +}; diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_gpg_verify.c swupdate-2025.12+dfsg/crypto/swupdate_gpg_verify.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_gpg_verify.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_gpg_verify.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,216 @@ +/* + * Author: Amy Fong + * Copyright (C) 2023, Siemens AG + * + * SPDX-License-Identifier: GPL-2.0-only + */ +#include +#include +#include +#include +#include "swupdate.h" +#include "util.h" + +#include +#include +#include +#include "swupdate_crypto.h" +#include "swupdate_gpg.h" + +static swupdate_dgst_lib libs; + +static gpg_error_t +status_cb(void *opaque, const char *keyword, const char *value) +{ + (void)opaque; + DEBUG("status_cb: %s %s", keyword, value); + return 0; +} + +#define MSGBUF_LEN 256 + +static int gpg_dgst_init(struct swupdate_cfg *sw, const char *keyfile) +{ + struct gpg_digest *dgst; + int ret; + + /* + * Check that it was not called before + */ + if (sw->dgst) { + return -EBUSY; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + ret = -ENOMEM; + goto dgst_init_error; + } + + dgst->gpg_home_directory = sw->gpg_home_directory; + dgst->gpgme_protocol = sw->gpgme_protocol; + dgst->verbose = sw->verbose; + + sw->dgst = dgst; + + return 0; + +dgst_init_error: + if (dgst) + free(dgst); + + return ret; +} + +static int gpg_verify_file(void *gpgdgst, const char *sigfile, + const char *file, const char *signer_name) +{ + struct gpg_digest *dgst = (struct gpg_digest *)gpgdgst; + gpgme_ctx_t ctx; + gpgme_error_t err; + gpgme_data_t image_sig, image; + FILE *fp_sig = NULL; + FILE *fp = NULL; + gpgme_signature_t sig; + int status = 0; + gpgme_protocol_t protocol; + gpgme_verify_result_t result; + char msg[MSGBUF_LEN]; + int r; + const char *tmp; + + tmp = gpgme_check_version(NULL); + if (tmp == NULL) { + ERROR("Failed to check gpgme library version"); + status = -EFAULT; + goto out; + } + + err = gpgme_new(&ctx); + if (err) { + ERROR("Failed to create new gpg context"); + r = gpgme_strerror_r(err, msg, MSGBUF_LEN); + if (r == 0) { + ERROR("Reason: %s", msg); + } + status = -EFAULT; + goto out; + } + + if (dgst->gpgme_protocol != NULL) { + DEBUG("gpg: Enabling protocol %s", dgst->gpgme_protocol); + if (!strcmp(dgst->gpgme_protocol, "openpgp")) { + TRACE("gpg: using protocol OpenPGP"); + protocol = GPGME_PROTOCOL_OpenPGP; + } else if (!strcmp(dgst->gpgme_protocol, "cms")) { + TRACE("gpg: using protocol cms"); + protocol = GPGME_PROTOCOL_CMS; + } else { + ERROR("gpg: unsupported protocol! %s", dgst->gpgme_protocol); + status = -EFAULT; + goto out; + } + } else { + ERROR("gpg protocol unspecified!"); + status = -EFAULT; + goto out; + } + + gpgme_set_protocol(ctx, protocol); + gpgme_set_status_cb(ctx, status_cb, NULL); + if (dgst->verbose) { + gpgme_set_ctx_flag(ctx, "full-status", "1"); + } + gpgme_set_locale(ctx, LC_ALL, setlocale(LC_ALL, "")); + + if (dgst->gpg_home_directory != NULL) { + err = gpgme_ctx_set_engine_info(ctx, protocol, NULL, dgst->gpg_home_directory); + if (err) { + ERROR("Something went wrong while setting the engine info"); + r = gpgme_strerror_r(err, msg, MSGBUF_LEN); + if (r == 0) { + ERROR("Reason: %s", msg); + } + status = -EFAULT; + goto out; + } + } + + fp_sig = fopen(sigfile, "rb"); + if (!fp_sig) { + ERROR("Failed to open %s", sigfile); + status = -EBADF; + goto out; + } + err = gpgme_data_new_from_stream(&image_sig, fp_sig); + if (err) { + ERROR("error allocating data object"); + r = gpgme_strerror_r(err, msg, MSGBUF_LEN); + if (r == 0) { + ERROR("Reason: %s", msg); + } + status = -ENOMEM; + goto out; + } + + fp = fopen(file, "rb"); + if (!fp) { + ERROR("Failed to open %s", file); + status = -EBADF; + goto out; + } + err = gpgme_data_new_from_stream(&image, fp); + if (err) { + ERROR("error allocating data object"); + r = gpgme_strerror_r(err, msg, MSGBUF_LEN); + if (r == 0) { + ERROR("Reason: %s", msg); + } + status = -ENOMEM; + goto out; + } + + err = gpgme_op_verify(ctx, image_sig, image, NULL); + result = gpgme_op_verify_result(ctx); + if (err) { + ERROR("verify failed"); + r = gpgme_strerror_r(err, msg, MSGBUF_LEN); + if (r == 0) { + ERROR("Reason: %s", msg); + } + status = -EBADMSG; + goto out; + } + + if (result) { + for (sig = result->signatures; sig; sig = sig->next) { + if (sig->status == GPG_ERR_NO_ERROR) { + TRACE("Verified OK"); + status = 0; + goto out; + } + } + } + TRACE("Verification failed"); + status = -EBADMSG; + + out: + gpgme_data_release(image); + gpgme_data_release(image_sig); + gpgme_release(ctx); + + if (fp) + fclose(fp); + if (fp_sig) + fclose(fp_sig); + + return status; +} + +__attribute__((constructor)) +static void gpg_dgst(void) +{ + libs.dgst_init = gpg_dgst_init; + libs.verify_file = gpg_verify_file; + (void)register_dgstlib("GPG", &libs); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_mbedtls.h swupdate-2025.12+dfsg/crypto/swupdate_mbedtls.h --- swupdate-2024.12.1+dfsg/crypto/swupdate_mbedtls.h 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_mbedtls.h 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,24 @@ +/* + * (C) Copyright 2024 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#pragma once + +#include +#include "util.h" + +#include +#include +#include +#include + +#define EVP_MAX_BLOCK_LENGTH (16) + +struct mbedtls_digest { + mbedtls_md_context_t mbedtls_md_context; + mbedtls_pk_context mbedtls_pk_context; + mbedtls_cipher_context_t mbedtls_cipher_context; +}; diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_openssl.h swupdate-2025.12+dfsg/crypto/swupdate_openssl.h --- swupdate-2024.12.1+dfsg/crypto/swupdate_openssl.h 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_openssl.h 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2016-2024 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#pragma once + +#include +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(X509_PURPOSE_CODE_SIGN) +#define X509_PURPOSE_CODE_SIGN (X509_PURPOSE_MAX + 1) +#endif + +#define SSL_PURPOSE_EMAIL_PROT X509_PURPOSE_SMIME_SIGN + +#define SSL_PURPOSE_CODE_SIGN X509_PURPOSE_CODE_SIGN +#define SSL_PURPOSE_DEFAULT SSL_PURPOSE_EMAIL_PROT + +struct openssl_digest { + EVP_PKEY *pkey; /* this is used for RSA key */ + EVP_PKEY_CTX *ckey; /* this is used for RSA key */ + X509_STORE *certs; /* this is used if CMS is set */ + X509 *decrypt_cert; + EVP_MD_CTX *ctx; +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_CIPHER_CTX ctxdec; +#else + EVP_CIPHER_CTX *ctxdec; +#endif + BIO *cryptbuf; + BIO *plain; +}; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define SSL_GET_CTXDEC(dgst) &dgst->ctxdec +#else +#define SSL_GET_CTXDEC(dgst) dgst->ctxdec +#endif + + diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_pkcs7_verify_wolfssl.c swupdate-2025.12+dfsg/crypto/swupdate_pkcs7_verify_wolfssl.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_pkcs7_verify_wolfssl.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_pkcs7_verify_wolfssl.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,238 @@ +/* + * (C) Copyright 2019 + * Stefano Babic, stefano.babic@swupdate.org. + * (C) Copyright 2023 + * Bastian Germann + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Code mostly taken from openssl examples + */ + +#include +#include +#include +#include +#include "swupdate.h" +#include "swupdate_wolfssl.h" +#include "util.h" +#include "swupdate_crypto.h" +#include + +#ifndef PKCS7_BINARY +#define PKCS7_BINARY 0x80 +#endif + +static swupdate_dgst_lib libs; + +static int store_verify_callback(int ok, X509_STORE_CTX *ctx) { + int cert_error = X509_STORE_CTX_get_error(ctx); + + if (!ok) { + switch (cert_error) { +#if defined(CONFIG_CMS_IGNORE_EXPIRED_CERTIFICATE) + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CERT_NOT_YET_VALID: + ok = 1; + break; +#endif +#if defined(CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE) + case X509_V_ERR_INVALID_PURPOSE: + ok = 1; + break; +#endif + default: + break; + } + } + + return ok; +} + +static X509_STORE *load_cert_chain(const char *file) +{ + X509_STORE *castore = X509_STORE_new(); + if (!castore) { + return NULL; + } + + /* + * Set error callback function for verification of CRTs and CRLs in order + * to ignore some errors depending on configuration + */ + X509_STORE_set_verify_cb(castore, store_verify_callback); + + BIO *castore_bio = BIO_new_file(file, "r"); + if (!castore_bio) { + TRACE("failed: BIO_new_file(%s)", file); + return NULL; + } + + int crt_count = 0; + X509 *crt = NULL; + do { + crt = PEM_read_bio_X509(castore_bio, NULL, 0, NULL); + if (crt) { + crt_count++; + char *subj = X509_NAME_oneline(X509_get_subject_name(crt), NULL, 0); + char *issuer = X509_NAME_oneline(X509_get_issuer_name(crt), NULL, 0); + TRACE("Read PEM #%d: %s %s", crt_count, issuer, subj); + free(subj); + free(issuer); + if (X509_STORE_add_cert(castore, crt) == 0) { + TRACE("Adding certificate to X509_STORE failed"); + BIO_free(castore_bio); + X509_STORE_free(castore); + return NULL; + } + } + } while (crt); + BIO_free(castore_bio); + + if (crt_count == 0) { + X509_STORE_free(castore); + return NULL; + } + + return castore; +} + +static int check_signer_name(const char *name) +{ + // TODO work around wolfSSL's PKCS7_get0_signers always returning NULL + if (name) + WARN("The X.509 common name might not be equal to %s.", name); + return 0; +} + +static int wolfssl_pkcs7_dgst_init(struct swupdate_cfg *sw, const char *keyfile) +{ + struct wolfssl_digest *dgst; + int ret; + + /* + * Check that it was not called before + */ + if (sw->dgst) { + return -EBUSY; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + ret = -ENOMEM; + goto dgst_init_error; + } + + /* + * Load certificate chain + */ + dgst->certs = load_cert_chain(keyfile); + if (!dgst->certs) { + ERROR("Error loading certificate chain from %s", keyfile); + ret = -EINVAL; + goto dgst_init_error; + } + + /* + * Create context + */ + dgst->ctx = EVP_MD_CTX_create(); + if(dgst->ctx == NULL) { + ERROR("EVP_MD_CTX_create failed, error 0x%lx", ERR_get_error()); + ret = -ENOMEM; + goto dgst_init_error; + } + + sw->dgst = dgst; + + return 0; + +dgst_init_error: + if (dgst) + free(dgst); + + return ret; +} + +static int wolfssl_pkcs7_verify_file(void *ctx, const char *sigfile, + const char *file, const char *signer_name) +{ + struct wolfssl_digest *dgst = (struct wolfssl_digest *)ctx; + int status = -EFAULT; + WOLFSSL_PKCS7* pkcs7 = (WOLFSSL_PKCS7 *)PKCS7_new(); + BIO *bio_mem = NULL; + BIO *content_bio = NULL; + + /* Open detached signature that needs to be checked */ + BIO *sigfile_bio = BIO_new_file(sigfile, "rb"); + if (!sigfile_bio) { + ERROR("%s cannot be opened", sigfile); + status = -EBADF; + goto out; + } + + pkcs7->len = wolfSSL_BIO_get_len(sigfile_bio); + pkcs7->data = calloc(1, pkcs7->len); + BIO_read(sigfile_bio, pkcs7->data, pkcs7->len); + if (!pkcs7->data) { + ERROR("%s cannot be parsed as DER-encoded PKCS#7 signature blob", sigfile); + status = -EFAULT; + goto out; + } + + /* Open the content file (data which was signed) */ + content_bio = BIO_new_file(file, "rb"); + if (!content_bio) { + ERROR("%s cannot be opened", file); + status = -EBADF; + goto out; + } + long content_len = wolfSSL_BIO_get_len(content_bio); + char *content = calloc(1, content_len); + BIO_read(content_bio, content, content_len); + bio_mem = BIO_new_mem_buf(content, content_len); + + /* Then try to verify signature. The BIO* in parameter has to be a mem BIO. + See https://github.com/wolfSSL/wolfssl/issues/6174. */ + if (!PKCS7_verify((PKCS7 *)pkcs7, NULL, dgst->certs, bio_mem, + NULL, PKCS7_BINARY)) { + ERR_print_errors_fp(stderr); + ERROR("Signature verification failed"); + status = -EBADMSG; + goto out; + } + + if (check_signer_name(signer_name)) { + ERROR("failed to verify signer name"); + status = -EFAULT; + goto out; + } + + TRACE("Verified OK"); + + /* Signature is valid */ + status = 0; +out: + + if (pkcs7) { + PKCS7_free((PKCS7 *)pkcs7); + } + if (bio_mem) { + BIO_free(bio_mem); + } + if (content_bio) { + BIO_free(content_bio); + } + if (sigfile_bio) { + BIO_free(sigfile_bio); + } + return status; +} + +__attribute__((constructor)) +static void wolfssl_dgst(void) +{ + libs.dgst_init = wolfssl_pkcs7_dgst_init; + libs.verify_file = wolfssl_pkcs7_verify_file; + (void)register_dgstlib("pkcs#7WolfSSL", &libs); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_rsa_verify_mbedtls.c swupdate-2025.12+dfsg/crypto/swupdate_rsa_verify_mbedtls.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_rsa_verify_mbedtls.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_rsa_verify_mbedtls.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,129 @@ +/* + * (C) Copyright 2019 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "swupdate.h" +#include "swupdate_crypto.h" +#include "swupdate_mbedtls.h" + +#define MODNAME "mbedtlsRSA" + +static swupdate_dgst_lib libs; + +static int read_file_into_buffer(uint8_t *buffer, int size, const char *filename) +{ + int fd; + ssize_t rd; + int result = -1; + + fd = open(filename, O_RDONLY); + if (fd == -1) { + ERROR("Failed to open file \"%s\"", filename); + return -errno; + } + + rd = read(fd, buffer, size); + if (rd != size) { + ERROR("Failed to read %d bytes from file \"%s\"", size, filename); + result = -EMSGSIZE; + goto exit; + } + + result = 0; + +exit: + close(fd); + return result; +} + +static int mbedtls_rsa_verify_file(void *ctx, const char *sigfile, + const char *file, const char *signer_name) +{ + struct mbedtls_digest *dgst = (struct mbedtls_digest *)ctx; + int error; + uint8_t hash_computed[32]; + const mbedtls_md_info_t *md_info; + mbedtls_pk_type_t pk_type = MBEDTLS_PK_RSA; + uint8_t signature[256]; + void *pss_options = NULL; +#if defined(CONFIG_SIGALG_RSAPSS) + pk_type = MBEDTLS_PK_RSASSA_PSS; + mbedtls_pk_rsassa_pss_options options = { + .mgf1_hash_id = MBEDTLS_MD_SHA256, + .expected_salt_len = MBEDTLS_RSA_SALT_LEN_ANY + }; + pss_options = &options; +#endif + + (void)signer_name; + + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + if (!md_info) { + ERROR("mbedtls_md_info_from_type"); + return -ENOENT; + } + + assert(mbedtls_md_get_size(md_info) == sizeof(hash_computed)); + + error = mbedtls_md_file(md_info, file, hash_computed); + if (error) { + ERROR("mbedtls_md_file: %d", error); + return error; + } + + error = read_file_into_buffer(signature, sizeof(signature), sigfile); + if (error) { + return error; + } + + return mbedtls_pk_verify_ext( + pk_type, pss_options, + &dgst->mbedtls_pk_context, mbedtls_md_get_type(md_info), + hash_computed, sizeof(hash_computed), + signature, sizeof(signature) + ); +} + +static int mbedtls_rsa_dgst_init(struct swupdate_cfg *sw, const char *keyfile) +{ + struct mbedtls_digest *dgst; + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + return -ENOMEM; + } + +#ifdef CONFIG_SIGNED_IMAGES + mbedtls_pk_init(&dgst->mbedtls_pk_context); + + int error = mbedtls_pk_parse_public_keyfile(&dgst->mbedtls_pk_context, keyfile); + if (error) { + ERROR("mbedtls_pk_parse_public_keyfile: %d", error); + free(dgst); + return -EIO; + } +#endif + + sw->dgst = dgst; + return 0; +} + +__attribute__((constructor)) +static void mbedtls_rsa_dgst(void) +{ + libs.dgst_init = mbedtls_rsa_dgst_init; + libs.verify_file = mbedtls_rsa_verify_file; + (void)register_dgstlib(MODNAME, &libs); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_rsa_verify_openssl.c swupdate-2025.12+dfsg/crypto/swupdate_rsa_verify_openssl.c --- swupdate-2024.12.1+dfsg/crypto/swupdate_rsa_verify_openssl.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_rsa_verify_openssl.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,266 @@ +/* + * (C) Copyright 2019 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Code mostly taken from openssl examples + */ + +#include +#include +#include +#include +#include "swupdate.h" +#include "util.h" +#include "swupdate_crypto.h" +#include "swupdate_openssl.h" + +#define BUFSIZE (1024 * 8) + +#define MODNAME "opensslRSA" + +static swupdate_dgst_lib libs; + +static EVP_PKEY *load_pubkey(const char *file) +{ + BIO *key=NULL; + EVP_PKEY *pkey=NULL; + + if (file == NULL) + { + ERROR("no keyfile specified"); + goto end; + } + + key=BIO_new(BIO_s_file()); + if (key == NULL) + { + goto end; + } + + if (BIO_read_filename(key, file) <= 0) + { + printf("Error opening %s \n", file); + goto end; + } + + pkey=PEM_read_bio_PUBKEY(key, NULL, NULL, NULL); +end: + if (key != NULL) BIO_free(key); + if (pkey == NULL) + ERROR("unable to load key filename %s", file); + return(pkey); +} + +static int dgst_verify_init(struct openssl_digest *dgst) +{ + int rc; + + rc = EVP_DigestVerifyInit(dgst->ctx, &dgst->ckey, EVP_sha256(), NULL, dgst->pkey); + if (rc != 1) { + ERROR("EVP_DigestVerifyInit failed, error 0x%lx", ERR_get_error()); + return -EFAULT; /* failed */ + } + +#if defined(CONFIG_SIGALG_RSAPSS) + rc = EVP_PKEY_CTX_set_rsa_padding(dgst->ckey, RSA_PKCS1_PSS_PADDING); + if (rc <= 0) { + ERROR("EVP_PKEY_CTX_set_rsa_padding failed, error 0x%lx", ERR_get_error()); + return -EFAULT; /* failed */ + } + rc = EVP_PKEY_CTX_set_rsa_pss_saltlen(dgst->ckey, -2); + if (rc <= 0) { + ERROR("EVP_PKEY_CTX_set_rsa_pss_saltlen failed, error 0x%lx", ERR_get_error()); + return -EFAULT; /* failed */ + } +#endif + + return 0; +} + +static int verify_update(struct openssl_digest *dgst, char *msg, unsigned int mlen) +{ + int rc; + + rc = EVP_DigestVerifyUpdate(dgst->ctx, msg, mlen); + if(rc != 1) { + ERROR("EVP_DigestVerifyUpdate failed, error 0x%lx", ERR_get_error()); + return -EFAULT; + } + + return 0; +} + +static int verify_final(struct openssl_digest *dgst, unsigned char *sig, unsigned int slen) +{ + unsigned int rc; + + /* Clear any errors for the call below */ + ERR_clear_error(); + rc = EVP_DigestVerifyFinal(dgst->ctx, sig, slen); + if(rc != 1) { + ERROR("EVP_DigestVerifyFinal failed, error 0x%lx %d", ERR_get_error(), rc); + return -1; + } + + return rc; +} + +static int openssl_rsa_verify_file(void *ctx, const char *sigfile, + const char *file, const char *signer_name) +{ + struct openssl_digest *dgst = (struct openssl_digest *)ctx; + FILE *fp = NULL; + BIO *sigbio; + int siglen = 0; + int i; + unsigned char *sigbuf = NULL; + char *msg = NULL; + int size; + size_t rbytes; + int status = 0; + + (void)signer_name; + if (!dgst) { + ERROR("Wrong crypto initialization: did you pass the key ?"); + status = -ENOKEY; + goto out; + } + + msg = malloc(BUFSIZE); + if (!msg) { + status = -ENOMEM; + goto out; + } + + sigbio = BIO_new_file(sigfile, "rb"); + siglen = EVP_PKEY_size(dgst->pkey); + sigbuf = OPENSSL_malloc(siglen); + + siglen = BIO_read(sigbio, sigbuf, siglen); + BIO_free(sigbio); + + if(siglen <= 0) { + ERROR("Error reading signature file %s", sigfile); + status = -ENOKEY; + goto out; + } + + ERR_clear_error(); + if (EVP_DigestInit_ex(dgst->ctx, EVP_sha256(), NULL) != 1) { + ERROR("EVP_DigestInit_ex failed: %s", ERR_error_string(ERR_get_error(), NULL)); + status = -ENOKEY; + goto out; + } + + if (dgst_verify_init(dgst) < 0) { + status = -ENOKEY; + goto out; + } + + fp = fopen(file, "r"); + if (!fp) { + ERROR("%s cannot be opened", file); + status = -EBADF; + goto out; + } + + size = 0; + for (;;) { + rbytes = fread(msg, 1, BUFSIZE, fp); + if (rbytes > 0) { + size += rbytes; + if (verify_update(dgst, msg, rbytes) < 0) + break; + } + if (feof(fp)) + break; + } + + TRACE("Verify signed image: Read %d bytes", size); + i = verify_final(dgst, sigbuf, (unsigned int)siglen); + if(i > 0) { + TRACE("Verified OK"); + status = 0; + } else if(i == 0) { + TRACE("Verification Failure"); + status = -EBADMSG; + } else { + TRACE("Error Verifying Data"); + status = -EFAULT; + } + +out: + if (fp) + fclose(fp); + if (msg) + free(msg); + if (sigbuf) + OPENSSL_free(sigbuf); + + return status; +} + +static int openssl_rsa_dgst_init(struct swupdate_cfg *sw, const char *keyfile) +{ + struct openssl_digest *dgst; + int ret; + + /* + * Check that it was not called before + */ + if (sw->dgst) { + return -EBUSY; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + ret = -ENOMEM; + goto dgst_init_error; + } + + /* + * Load public key + */ + dgst->pkey = load_pubkey(keyfile); + if (!dgst->pkey) { + ERROR("Error loading pub key from %s", keyfile); + ret = -EINVAL; + goto dgst_init_error; + } + dgst->ckey = EVP_PKEY_CTX_new(dgst->pkey, NULL); + if (!dgst->ckey) { + ERROR("Error creating context key for %s", keyfile); + ret = -EINVAL; + goto dgst_init_error; + } + + /* + * Create context + */ + dgst->ctx = EVP_MD_CTX_create(); + if(dgst->ctx == NULL) { + ERROR("EVP_MD_CTX_create failed, error 0x%lx", ERR_get_error()); + ret = -ENOMEM; + goto dgst_init_error; + } + + sw->dgst = dgst; + + return 0; + +dgst_init_error: + if (dgst) + free(dgst); + + return ret; +} + +__attribute__((constructor)) +static void openssl_dgst(void) +{ + libs.dgst_init = openssl_rsa_dgst_init; + libs.verify_file = openssl_rsa_verify_file; + (void)register_dgstlib(MODNAME, &libs); +} diff -Nru swupdate-2024.12.1+dfsg/crypto/swupdate_wolfssl.h swupdate-2025.12+dfsg/crypto/swupdate_wolfssl.h --- swupdate-2024.12.1+dfsg/crypto/swupdate_wolfssl.h 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/swupdate_wolfssl.h 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,56 @@ +/* + * (C) Copyright 2024 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#pragma once + +#include +#include "util.h" + +#ifdef CONFIG_PKCS11 +#include +#include +#include +#include +// Exclude p11-kit's pkcs11.h to prevent conflicting with wolfssl's +#define PKCS11_H 1 +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, len) (1) + +#define X509_PURPOSE_CODE_SIGN EXTKEYUSE_CODESIGN +#define SSL_PURPOSE_EMAIL_PROT EXTKEYUSE_EMAILPROT + +#define openssl_digest wolfssl_digest + +struct wolfssl_digest { + EVP_PKEY *pkey; /* this is used for RSA key */ + EVP_PKEY_CTX *ckey; /* this is used for RSA key */ + X509_STORE *certs; /* this is used if CMS is set */ + EVP_MD_CTX *ctx; +#ifdef CONFIG_PKCS11 + unsigned char last_decr[AES_BLOCK_SIZE + 1]; + P11KitUri *p11uri; + Aes ctxdec; + Pkcs11Dev pkdev; + Pkcs11Token pktoken; +#endif +}; diff -Nru swupdate-2024.12.1+dfsg/crypto/verify_signature.c swupdate-2025.12+dfsg/crypto/verify_signature.c --- swupdate-2024.12.1+dfsg/crypto/verify_signature.c 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/crypto/verify_signature.c 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,113 @@ +/* + * (C) Copyright 2016 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Code mostly taken from openssl examples + */ + +#include +#include +#include +#include +#include "swupdate.h" +#include "sslapi.h" +#include "util.h" +#include "compat.h" +#include "swupdate_verify_private.h" + +int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile) +{ + struct swupdate_digest *dgst; + int ret; + + /* + * Check that it was not called before + */ + if (sw->dgst) { + return -EBUSY; + } + + dgst = calloc(1, sizeof(*dgst)); + if (!dgst) { + ret = -ENOMEM; + goto dgst_init_error; + } + +#if defined(CONFIG_SIGALG_RAWRSA) || defined(CONFIG_SIGALG_RSAPSS) + /* + * Load public key + */ + dgst->pkey = load_pubkey(keyfile); + if (!dgst->pkey) { + ERROR("Error loading pub key from %s", keyfile); + ret = -EINVAL; + goto dgst_init_error; + } + dgst->ckey = EVP_PKEY_CTX_new(dgst->pkey, NULL); + if (!dgst->ckey) { + ERROR("Error creating context key for %s", keyfile); + ret = -EINVAL; + goto dgst_init_error; + } +#elif defined(CONFIG_SIGALG_CMS) + /* + * Load certificate chain + */ + dgst->certs = load_cert_chain(keyfile); + if (!dgst->certs) { + ERROR("Error loading certificate chain from %s", keyfile); + ret = -EINVAL; + goto dgst_init_error; + } + +#ifndef CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE + { + static char code_sign_name[] = "Code signing"; + static char code_sign_sname[] = "codesign"; + + if (!X509_PURPOSE_add(X509_PURPOSE_CODE_SIGN, X509_TRUST_EMAIL, + 0, check_code_sign, code_sign_name, + code_sign_sname, NULL)) { + ERROR("failed to add code sign purpose"); + ret = -EINVAL; + goto dgst_init_error; + } + } + + if (!X509_STORE_set_purpose(dgst->certs, sw->cert_purpose)) { + ERROR("failed to set purpose"); + ret = -EINVAL; + goto dgst_init_error; + } +#endif + +#elif defined(CONFIG_SIGALG_GPG) + dgst->gpg_home_directory = sw->gpg_home_directory; + dgst->gpgme_protocol = sw->gpgme_protocol; + dgst->verbose = sw->verbose; +#else + TRACE("public key / cert %s ignored, you need to set SIGALG", keyfile); +#endif + + /* + * Create context + */ + dgst->ctx = EVP_MD_CTX_create(); + if(dgst->ctx == NULL) { + ERROR("EVP_MD_CTX_create failed, error 0x%lx", ERR_get_error()); + ret = -ENOMEM; + goto dgst_init_error; + } + + sw->dgst = dgst; + + return 0; + +dgst_init_error: + if (dgst) + free(dgst); + + return ret; +} diff -Nru swupdate-2024.12.1+dfsg/debian/README.Debian swupdate-2025.12+dfsg/debian/README.Debian --- swupdate-2024.12.1+dfsg/debian/README.Debian 2025-07-14 10:54:20.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/README.Debian 2026-05-18 11:02:01.000000000 +0000 @@ -1,26 +1,15 @@ -CONNECTING CLIENTS TO THE RIGHT IPC INTERFACES -============================================== -The swupdate.service instance runs with the environment variable -RUNTIME_DIRECTORY set to /run/swupdate, which is where the IPC control and -progress interfaces generate their socket paths from. - -On using an IPC client you should set that environment variable or TMPDIR -accordingly or use the command line socket options that are available to -connect to /run/swupdate/swupdateprog or /run/swupdate/sockinstctrl. - DPKG BUILD PROFILES =================== There are some custom build profiles that are disabled by default: -pkg.swupdate.p11 -Enable using PKCS#11 for AES decryption -instead of having the plain key available in a file. +pkg.swupdate.openssl +Enable OpenSSL crypto backend. pkg.swupdate.nohwcompat Disable the comparison with the hardware compatibility file /etc/hwrevision. pkg.swupdate.nosigning -Disable Cryptographic Message Syntax (CMS) signature verification on SWU files. +Disable signature verification on SWU files. Cryptographic hash verification (integrity) is still enabled with this set. Please only use this when you can ensure authenticity in some other way. diff -Nru swupdate-2024.12.1+dfsg/debian/changelog swupdate-2025.12+dfsg/debian/changelog --- swupdate-2024.12.1+dfsg/debian/changelog 2026-05-03 19:20:46.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/changelog 2026-05-31 18:59:59.000000000 +0000 @@ -1,14 +1,115 @@ -swupdate (2024.12.1+dfsg-3+deb13u2) trixie; urgency=medium +swupdate (2025.12+dfsg-10~bpo13+2) trixie; urgency=medium + * Remove version constraints on wolfssl (Closes: #1138163) + + -- Bastian Germann Sun, 31 May 2026 20:59:59 +0200 + +swupdate (2025.12+dfsg-10~bpo13+1) trixie-backports; urgency=medium + + * Rebuild for trixie-backports + * Revert "Update to lua5.5" + + -- Bastian Germann Mon, 18 May 2026 13:02:56 +0200 + +swupdate (2025.12+dfsg-10) unstable; urgency=medium + + * Enable the eMMC handler + * Build with packaged mongoose (Closes: #1134977, + CVE-2026-2966, CVE-2026-2967, CVE-2026-2968, CVE-2026-5244, + CVE-2026-5245, CVE-2026-5246, CVE-2026-6985, CVE-2026-6986) + + -- Bastian Germann Wed, 06 May 2026 16:17:06 +0200 + +swupdate (2025.12+dfsg-9) unstable; urgency=medium + + * Use original fontawesome instead of fork awesome + * Use dh-sequence-builtusing * Apply ustream CVE-2026-28525 patch - -- Bastian Germann Sun, 03 May 2026 19:20:46 +0000 + -- Bastian Germann Sat, 02 May 2026 11:24:12 +0200 + +swupdate (2025.12+dfsg-8) unstable; urgency=medium + + * Prevent stripping the binary (Closes: #1134531) + + -- Bastian Germann Wed, 22 Apr 2026 17:08:00 +0200 + +swupdate (2025.12+dfsg-7) unstable; urgency=medium + + * Enable PKCS#11 again + * Add Build-Profile pkg.swupdate.openssl + + -- Bastian Germann Thu, 16 Apr 2026 08:48:56 +0200 + +swupdate (2025.12+dfsg-6) unstable; urgency=medium + + * Drop unnecessary CFLAGS addition + * Re-enable OpenSSL (broken PKCS#7) + + -- Bastian Germann Thu, 26 Mar 2026 17:39:32 +0100 + +swupdate (2025.12+dfsg-5) unstable; urgency=medium + + * Revert "Switch curl backend to openssl" + * Switch back from OpenSSL to wolfSSL -swupdate (2024.12.1+dfsg-3+deb13u1) trixie; urgency=medium + -- Bastian Germann Mon, 23 Mar 2026 01:53:04 +0100 +swupdate (2025.12+dfsg-4) unstable; urgency=medium + + * Update to lua5.5 + * Drop ia64 from architecture list + + -- Bastian Germann Thu, 26 Feb 2026 19:26:58 +0100 + +swupdate (2025.12+dfsg-3) unstable; urgency=medium + + * Configure the socket paths at compile time + + -- Bastian Germann Thu, 12 Feb 2026 13:17:55 +0100 + +swupdate (2025.12+dfsg-2) unstable; urgency=medium + + * Declare public key or gpg cfg mandatory + + -- Bastian Germann Fri, 19 Dec 2025 17:29:49 +0100 + +swupdate (2025.12+dfsg-1) unstable; urgency=medium + + * New upstream version 2025.12+dfsg + * Revert "Backport: suricatta/wfx: Fix rebooting (Closes: #1118485)" + * Revert "Backport: Fix out-of-tree build" + * Document symbol swupdate_set_version_range_type + * d/copyright: Add new info + * Clean more test files + * Enable xz decompression + * Enable Mbed TLS and GPGME with additional signature algorithms + + -- Bastian Germann Mon, 08 Dec 2025 19:34:23 +0100 + +swupdate (2025.05+dfsg-3) unstable; urgency=medium + + * Switch curl backend to openssl * Backport: suricatta/wfx: Fix rebooting (Closes: #1118485) - -- Bastian Germann Tue, 18 Nov 2025 08:52:59 +0100 + -- Bastian Germann Tue, 21 Oct 2025 11:36:51 +0200 + +swupdate (2025.05+dfsg-2) unstable; urgency=medium + + * Fix nodoc build + * Ignore new test fails on armhf/armel + * Backport: Fix out-of-tree build + * Remove the pkcs11 build profile pkg.swupdate.p11 + * Switch back to the OpenSSL crypto backend + + -- Bastian Germann Sun, 10 Aug 2025 12:34:17 +0200 + +swupdate (2025.05+dfsg-1) experimental; urgency=medium + + * New upstream version 2025.05+dfsg + * d/copyright: Adjust info + + -- Bastian Germann Sun, 11 May 2025 19:24:34 +0200 swupdate (2024.12.1+dfsg-3) unstable; urgency=medium diff -Nru swupdate-2024.12.1+dfsg/debian/clean swupdate-2025.12+dfsg/debian/clean --- swupdate-2024.12.1+dfsg/debian/clean 2025-07-14 10:54:20.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/clean 2026-05-18 11:02:01.000000000 +0000 @@ -2,10 +2,12 @@ doc/source/images/statemachine.png index.html test/test_crypt +test/test_flash_handler test/test_hash test/test_json test/test_server_hawkbit test/test_util +test/test_verify test/test_*.map test/test_*.out web-app/css/bootstrap.css diff -Nru swupdate-2024.12.1+dfsg/debian/configs/defconfig swupdate-2025.12+dfsg/debian/configs/defconfig --- swupdate-2024.12.1+dfsg/debian/configs/defconfig 2025-07-14 10:54:20.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/configs/defconfig 2026-05-18 11:02:01.000000000 +0000 @@ -1,11 +1,18 @@ +CONFIG_SOCKET_CTRL_PATH="/run/swupdate/sockinstctrl" +CONFIG_SOCKET_PROGRESS_PATH="/run/swupdate/swupdateprog" CONFIG_SYSTEMD=y CONFIG_BOOTLOADER_GRUB=y CONFIG_GRUBENV_PATH="/boot/grub/grubenv" CONFIG_BOOTLOADER_DEFAULT_NONE=y +CONFIG_PKCS11=y +CONFIG_SSL_IMPL_GPGME=y +CONFIG_SSL_IMPL_MBEDTLS=y CONFIG_SSL_IMPL_WOLFSSL=y CONFIG_DOWNLOAD=y CONFIG_DOWNLOAD_SSL=y +CONFIG_SIGALG_RAWRSA=y CONFIG_SIGALG_CMS=y +CONFIG_SIGALG_GPG=y CONFIG_CMS_SKIP_UNKNOWN_SIGNERS=y CONFIG_ENCRYPTED_IMAGES=y CONFIG_ENCRYPTED_IMAGES_HARDEN_LOGGING=y @@ -16,9 +23,11 @@ CONFIG_UPDATE_STATE_CHOICE_BOOTLOADER=y CONFIG_WEBSERVER=y CONFIG_MONGOOSESSL=y +CONFIG_XZ=y CONFIG_ZSTD=y CONFIG_COPY=y CONFIG_DISKPART=y +CONFIG_EMMC_HANDLER=y CONFIG_DISKPART_FORMAT=y CONFIG_DISKFORMAT=y CONFIG_DISKFORMAT_HANDLER=y diff -Nru swupdate-2024.12.1+dfsg/debian/control swupdate-2025.12+dfsg/debian/control --- swupdate-2024.12.1+dfsg/debian/control 2025-07-14 10:55:24.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/control 2026-05-31 18:59:59.000000000 +0000 @@ -6,7 +6,9 @@ Nobuhiro Iwamatsu Build-Depends: debhelper (>= 13.3), debhelper-compat (= 13), + dh-sequence-builtusing, dh-sequence-lua:native , + python3, python3-sphinx:native , Build-Depends-Arch: liblua5.4-dev , @@ -23,18 +25,23 @@ librsync-dev, libsystemd-dev, uuid-dev, + liblzma-dev, zlib1g-dev, libzstd-dev, libzck-dev (>= 1.3), - libp11-kit-dev , - libwolfssl-dev (>= 5), + libp11-kit-dev, + libmbedtls-dev, + libssl-dev , + libwolfssl-dev, + libgpgme-dev, libmtd-dev [linux-any], libubi-dev [linux-any], libwebsockets-dev (>= 3.2.0) , liburiparser-dev, libubootenv-dev (>= 0.3.5) [linux-any], - libebgenv-dev [any-amd64 any-i386 any-arm64 armhf any-loong64 any-riscv64 any-ia64], + libebgenv-dev [any-amd64 any-i386 any-arm64 armhf any-loong64 any-riscv64], libcmocka-dev, + mongoose-webserver-source, pkgconf, gawk, Build-Depends-Indep: @@ -43,10 +50,11 @@ latexmk , libjs-dropzone, node-bootstrap, + node-fortawesome-fontawesome-free, node-jquery, sassc, texlive-latex-recommended , - texlive-fonts-recommended , + fonts-freefont-otf , texlive-formats-extra , tex-gyre , Standards-Version: 4.6.0.1 @@ -59,7 +67,10 @@ Architecture: linux-any Pre-Depends: ${misc:Pre-Depends} Depends: ${shlibs:Depends}, ${misc:Depends} -Built-Using: ${Built-Using} +Built-Using: + ${dh-builtusing:libmtd-dev}, + ${dh-builtusing:libubi-dev}, + ${dh-builtusing:mongoose-webserver-source} Recommends: libebgenv0 Description: Software update framework for embedded systems swupdate is a Linux update agent with the goal to provide an efficient and @@ -79,11 +90,11 @@ Package: swupdate-www Architecture: all Depends: ${misc:Depends}, - fonts-fork-awesome, libjs-dropzone, node-bootstrap, + node-fortawesome-fontawesome-free, node-jquery -Built-Using: ${B-U-www} +Built-Using: ${dh-builtusing:node-bootstrap} Description: Web Application for swupdate This package contains a web application for swupdate. . diff -Nru swupdate-2024.12.1+dfsg/debian/copyright swupdate-2025.12+dfsg/debian/copyright --- swupdate-2024.12.1+dfsg/debian/copyright 2025-07-14 10:55:24.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/copyright 2026-05-18 11:02:01.000000000 +0000 @@ -5,7 +5,7 @@ Files-Excluded: examples/www Files: * -Copyright: 2013-2024 Stefano Babic +Copyright: 2013-2025 Stefano Babic License: GPL-2 Files: .checkpatch.conf @@ -16,6 +16,11 @@ Copyright: 2019 Adrian Freihofer License: CC0-1.0 +Files: .github/workflows/*.yml + scripts/gen_extraversion +Copyright: 2025 Daniel Braunwarth +License: MIT + Files: .github/workflows/contributing.yml Copyright: 2021 James Hilliard License: MIT @@ -64,12 +69,21 @@ Copyright: 2016, Denis Osterland, Diehl Connectivity Solutions GmbH, Denis.Osterland@diehl.com. License: GPL-2 +Files: crypto/swupdate_decrypt_openssl_cms.c +Copyright: (C) Copyright 2024-2025 + Michael Glembotzki, iris-GmbH infrared & intelligent sensors, michael.glembotzki@iris-sensing.com + . + (C) Copyright 2025 + Stefano Babic, stefano.babic@swupdate.org. +License: GPL-2 + Files: mongoose/mongoose* Copyright: 2004-2013 Sergey Lyubka - 2013-2024 Cesanta Software Limited + 2007, Cameron Rich + 2013-2025 Cesanta Software Limited 2014-2015, Kenneth MacKay 2015-2016 Cryptography Research, Inc. -License: GPL-2 and BSD-2-Clause and MIT +License: GPL-2 and BSD-2-Clause and MIT and BSD-3-Clause-axTLS Files: mongoose/mongoose_interface.c Copyright: 2004-2013 Sergey Lyubka @@ -88,7 +102,7 @@ License: MIT Files: fs/* - containers/Config.in + containers/Kconfig Copyright: 2021 Weidmueller Interface GmbH & Co. KG License: GPL-2 @@ -126,11 +140,11 @@ Copyright: 1998, 2015 Todd C. Miller License: ISC -Files: corelib/swupdate_decrypt_pkcs11.c +Files: crypto/swupdate_decrypt_wolfssl.c Copyright: 2020, Linutronix GmbH License: GPL-2 -Files: corelib/swupdate_pkcs7_verify.c +Files: crypto/swupdate_pkcs7_verify_wolfssl.c Copyright: 2019 Stefano Babic, DENX Software Engineering, stefano.babic@swupdate.org. 2023 Bastian Germann License: GPL-2 @@ -160,7 +174,7 @@ Copyright: 2023 Felix Moessbauer License: GPL-2 -Files: suricatta/Config.in +Files: suricatta/Kconfig Copyright: 2016 Christian Storm License: GPL-2 @@ -192,8 +206,8 @@ Copyright: 2022 Kyle Russell License: GPL-2+ -Files: corelib/swupdate_decrypt_mbedtls.c - corelib/verify_signature_mbedtls.c +Files: crypto/swupdate_decrypt_mbedtls.c + crypto/swupdate_HASH_mbedtls.c Copyright: 2019 Laszlo Ashin License: GPL-2 @@ -202,37 +216,17 @@ Copyright: 2016 Diehl Connectivity Solutions GmbH, Denis Osterland License: GPL-2+ -Files: scripts/checkpatch.pl -Copyright: 2001, Dave Jones. (the file handling bit) - 2005, Joel Schopp (the ugly bit) - 2007,2008, Andy Whitcroft (new conditions, test suite) - 2008-2010 Andy Whitcroft - 2010-2018 Joe Perches -License: GPL-2 - -Files: scripts/kconfig/* -Copyright: 2002 Roman Zippel -License: GPL-2 - -Files: scripts/kconfig/lxdialog/checklist.c -Copyright: Savio Lam - Stuart Herbert - Alessandro Rubini - William Roadcap -License: GPL-2+ +Files: scripts/Kconfiglib/README.md +Copyright: 2025 Daniel Braunwarth +License: GPL-2 -Files: scripts/kconfig/lxdialog/dialog.h -Copyright: Savio Lam -License: GPL-2+ +Files: scripts/Kconfiglib/*.py +Copyright: (c) 2011-2019, Ulf Magnusson +License: ISC -Files: scripts/kconfig/lxdialog/inputbox.c - scripts/kconfig/lxdialog/textbox.c - scripts/kconfig/lxdialog/util.c - scripts/kconfig/lxdialog/yesno.c - scripts/kconfig/lxdialog/menubox.c -Copyright: Savio Lam - William Roadcap -License: GPL-2+ +Files: scripts/Kconfiglib/menuconfig.py +Copyright: (c) 2018-2019, Nordic Semiconductor ASA and Ulf Magnusson +License: ISC Files: scripts/mkconfigs Copyright: 2002 Al Stone @@ -253,24 +247,6 @@ Copyright: 2017-2018 Weidmüller Interface GmbH & Co. KG, Stefan Herbrechtsmeier License: MIT -Files: scripts/kconfig/mconf.c -Copyright: 2002 Roman Zippel - 2002-11-06 Petr Baudis - 2005, Arnaldo Carvalho de Melo -License: GPL-2 - -Files: scripts/kconfig/kxgettext.c -Copyright: Arnaldo Carvalho de Melo , 2005 -License: GPL-2 - -Files: scripts/kconfig/gconf.c -Copyright: 2002-2003 Romain Lievin -License: GPL-2 - -Files: scripts/kconfig/streamline_config.pl -Copyright: 2005-2009 Steven Rostedt -License: GPL-2 - Files: handlers/diskformat_handler.c Copyright: 2021 Weidmueller Interface GmbH & Co. KG License: GPL-2 @@ -282,32 +258,18 @@ 2014-2016 Stefano Babic License: GPL-2 -Files: scripts/kconfig/merge_config.sh -Copyright: 2009-2010 Wind River Systems, Inc. - 2011 Linaro -License: GPL-2 - -Files: scripts/kconfig/nconf.* -Copyright: 2008 Nir Tzachar - 2002-2005 Sam Ravnborg -License: GPL-2 - Files: scripts/basic/fixdep.c Copyright: 2002 Kai Germaschewski License: GPL-2 -Files: scripts/kconfig/zconf.tab.c_shipped -Copyright: 1984-2012 Free Software Foundation, Inc. -License: GPL-3+ WITH BISON EXCEPTION - Files: include/bsdqueue.h Copyright: 1991, 1993 The Regents of the University of California License: BSD-3-Clause +Files: test/test_flash_handler.c +Copyright: Viacheslav Volkov +License: GPL-2 + Files: tools/swupdate-ipc.c Copyright: 2021 Stefano Babic, DENX Software Engineering, stefano.babic@swupdate.org. License: GPL-2 @@ -360,6 +322,10 @@ Copyright: 2013-2021 Stefano Babic License: GPL-2 +Files: doc/source/asym_encrypted_images.rst +Copyright: 2025 Michael Glembotzki +License: GPL-2 + Files: handlers/zchunk_range.c Copyright: 2021 Stefano Babic, stefano.babic@swupdate.org. 2018 Jonathan Dieter @@ -394,8 +360,7 @@ Lesser General Public License for more details. . You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + License along with this library; if not, see . On Debian systems, the complete text of the GNU Lesser General Public License Version 2.1 can be found in `/usr/share/common-licenses/LGPL-2.1'. @@ -510,6 +475,31 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +License: BSD-3-Clause-axTLS + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + . + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the axTLS project nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + License: ISC Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -523,33 +513,6 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -License: GPL-3+ WITH BISON EXCEPTION - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - . - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - As a special exception, you may create a larger work that contains - part or all of the Bison parser skeleton and distribute that work - under terms of your choice, so long as that work isn't itself a - parser generator using the skeleton or a modified version thereof - as a parser skeleton. Alternatively, if you modify or redistribute - the parser skeleton itself, you may (at your option) remove this - special exception, which will cause the skeleton and the resulting - Bison output files to be licensed under the GNU General Public - License without this special exception. - . - This special exception was added by the Free Software Foundation in - version 2.2 of Bison. - License: CC0-1.0 On Debian systems, the complete text of the CC0 1.0 Universal license can be found in "/usr/share/common-licenses/CC0-1.0". diff -Nru swupdate-2024.12.1+dfsg/debian/libswupdate0.1.symbols swupdate-2025.12+dfsg/debian/libswupdate0.1.symbols --- swupdate-2024.12.1+dfsg/debian/libswupdate0.1.symbols 2025-07-14 10:54:20.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/libswupdate0.1.symbols 2026-05-18 11:02:01.000000000 +0000 @@ -24,3 +24,4 @@ swupdate_prepare_req@Base 2020.11 swupdate_set_aes@Base 2020.11 swupdate_set_version_range@Base 2021.04 + swupdate_set_version_range_type@Base 2025.12 diff -Nru swupdate-2024.12.1+dfsg/debian/patches/Declare-public-key-or-gpg-cfg-mandatory.diff swupdate-2025.12+dfsg/debian/patches/Declare-public-key-or-gpg-cfg-mandatory.diff --- swupdate-2024.12.1+dfsg/debian/patches/Declare-public-key-or-gpg-cfg-mandatory.diff 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/patches/Declare-public-key-or-gpg-cfg-mandatory.diff 2026-05-18 11:02:01.000000000 +0000 @@ -0,0 +1,54 @@ +Origin: https://patchwork.ozlabs.org/project/swupdate/patch/20251219143651.308138-1-bage@debian.org/ +From: Bastian Germann +Subject: crypto: Declare public key or gpg cfg mandatory +Date: Fri, 19 Dec 2025 15:36:27 +0100 + +The command line parsing currently insists on the gpg config options +when GPG is configured at build time. With the runtime-configurable +crypto the check for mandatory options has to be changed to be correct. + +Signed-off-by: Bastian Germann +--- + core/swupdate.c | 11 ++++++----- + 1 file changed, 6 insertions(+), 5 deletions(-) + +diff --git a/core/swupdate.c b/core/swupdate.c +index be027dcb..4c17c2d4 100644 +--- a/core/swupdate.c ++++ b/core/swupdate.c +@@ -624,9 +624,6 @@ int main(int argc, char **argv) + #endif + #ifdef CONFIG_SIGNED_IMAGES + strcat(main_options, "k:"); +-#ifndef CONFIG_SIGALG_GPG +- public_key_mandatory = 1; +-#endif + #endif + #ifdef CONFIG_ENCRYPTED_IMAGES + strcat(main_options, "K:"); +@@ -949,6 +946,10 @@ int main(int argc, char **argv) + exit(EXIT_FAILURE); + } + ++#ifdef CONFIG_SIGNED_IMAGES ++ public_key_mandatory = strcmp(get_dgstlib(), "GPG"); ++#endif ++ + /* + * Parameters are parsed: now performs plausibility + * tests before starting processes and threads +@@ -960,12 +961,12 @@ int main(int argc, char **argv) + } + + #ifdef CONFIG_SIGALG_GPG +- if (!strlen(swcfg.gpg_home_directory)) { ++ if (!public_key_mandatory && !strlen(swcfg.gpg_home_directory)) { + fprintf(stderr, + "Error: SWUpdate is built for signed images, provide a GnuPG home directory.\n"); + exit(EXIT_FAILURE); + } +- if (!strlen(swcfg.gpgme_protocol)) { ++ if (!public_key_mandatory && !strlen(swcfg.gpgme_protocol)) { + fprintf(stderr, + "Error: SWUpdate is built for signed images, please specify GnuPG protocol.\n"); + exit(EXIT_FAILURE); diff -Nru swupdate-2024.12.1+dfsg/debian/patches/Remove-element-of-char-failing-the-doc-build.diff swupdate-2025.12+dfsg/debian/patches/Remove-element-of-char-failing-the-doc-build.diff --- swupdate-2024.12.1+dfsg/debian/patches/Remove-element-of-char-failing-the-doc-build.diff 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/patches/Remove-element-of-char-failing-the-doc-build.diff 2026-05-18 11:02:01.000000000 +0000 @@ -0,0 +1,15 @@ +From: Bastian Germann +Date: Mon, 12 May 2025 11:59:25 +0200 +Subject: Remove element-of char failing the doc build +--- +--- a/suricatta/suricatta.lua ++++ b/suricatta/suricatta.lua +@@ -269,7 +269,7 @@ suricatta.channel = { + --- @class suricatta.operation_channel + --- @field channel suricatta.open_channel Channel table as returned by `suricatta.channel.open()` + --- @field drain_messages boolean | nil Whether to flush all progress messages or only those while in-flight operation (default) +---- @field ∈ suricatta.channel.options | nil Channel options to override for this operation ++--- @field suricatta.channel.options | nil Channel options to override for this operation + + --- Install an update artifact from remote server or local file. + -- diff -Nru swupdate-2024.12.1+dfsg/debian/patches/Remove-tabularcolumns-that-crashes-latex.diff swupdate-2025.12+dfsg/debian/patches/Remove-tabularcolumns-that-crashes-latex.diff --- swupdate-2024.12.1+dfsg/debian/patches/Remove-tabularcolumns-that-crashes-latex.diff 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/patches/Remove-tabularcolumns-that-crashes-latex.diff 2026-05-18 11:02:01.000000000 +0000 @@ -0,0 +1,18 @@ +From: Bastian Germann +Date: Mon, 8 Dec 2025 19:11:42 +0100 +Forwarded: not-needed +Subject: doc: Remove tabularcolumns that crashes latex + +sphinx generates latex code that ends up with error: +Missing # inserted in alignment preamble. +--- +--- a/doc/source/sw-description.rst ++++ b/doc/source/sw-description.rst +@@ -1408,7 +1408,6 @@ There are 4 main sections inside sw-description: + value. + + +-.. tabularcolumns:: |p{1.5cm}|p{1.5cm}|p{1.5cm}|L| + .. table:: Attributes in sw-description + + diff -Nru swupdate-2024.12.1+dfsg/debian/patches/Replace-Font-Awesome-5-with-Fork-Awesome.diff swupdate-2025.12+dfsg/debian/patches/Replace-Font-Awesome-5-with-Fork-Awesome.diff --- swupdate-2024.12.1+dfsg/debian/patches/Replace-Font-Awesome-5-with-Fork-Awesome.diff 2025-07-14 10:54:20.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/patches/Replace-Font-Awesome-5-with-Fork-Awesome.diff 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -From: Bastian Germann -Date: Fri, 25 Nov 2022 00:11:52 +0000 -Forwarded: not-needed -Subject: Replace Font Awesome 5 with Fork Awesome - ---- - web-app/index.html | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/web-app/index.html b/web-app/index.html -index 641cb8c..03252cf 100755 ---- a/web-app/index.html -+++ b/web-app/index.html -@@ -10,8 +10,8 @@ - - - -- -- -+ -+ - - - diff -Nru swupdate-2024.12.1+dfsg/debian/patches/series swupdate-2025.12+dfsg/debian/patches/series --- swupdate-2024.12.1+dfsg/debian/patches/series 2026-05-03 19:20:46.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/patches/series 2026-05-18 11:02:01.000000000 +0000 @@ -1,5 +1,6 @@ CVE-2026-28525.diff +Declare-public-key-or-gpg-cfg-mandatory.diff Link-config-to-swupdate-www-path.diff -Replace-Font-Awesome-5-with-Fork-Awesome.diff +Remove-element-of-char-failing-the-doc-build.diff +Remove-tabularcolumns-that-crashes-latex.diff use-gcc-compiler.diff -suricatta-wfx-Fix-rebooting.diff diff -Nru swupdate-2024.12.1+dfsg/debian/patches/suricatta-wfx-Fix-rebooting.diff swupdate-2025.12+dfsg/debian/patches/suricatta-wfx-Fix-rebooting.diff --- swupdate-2024.12.1+dfsg/debian/patches/suricatta-wfx-Fix-rebooting.diff 2025-11-18 07:52:59.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/patches/suricatta-wfx-Fix-rebooting.diff 1970-01-01 00:00:00.000000000 +0000 @@ -1,126 +0,0 @@ -Origin: upstream, 6281f3783a303904981523ed8388b468d58eb5a0 -From: "Storm, Christian" -Date: Tue, 15 Jul 2025 08:59:07 +0000 -Subject: suricatta/wfx: Fix rebooting via tools/swupdate-progress.c - -The changes leading to commit 077ef4f broke rebooting via -tools/swupdate-progress.c. Hence, adapt the C Lua bridge -lua_notify_progress() and update its invocation in -suricatta/server_wfx.lua as well as updating / fixing -Lua annotations. - -Signed-off-by: Christian Storm ---- - corelib/lua_interface.c | 17 +++++++++-------- - doc/source/suricatta.rst | 8 ++++---- - handlers/swupdate.lua | 5 +++-- - suricatta/server_wfx.lua | 6 ++++-- - suricatta/suricatta.lua | 5 +++-- - 5 files changed, 23 insertions(+), 18 deletions(-) - -diff --git a/corelib/lua_interface.c b/corelib/lua_interface.c -index 8d57aaa9..1ff3e5e4 100644 ---- a/corelib/lua_interface.c -+++ b/corelib/lua_interface.c -@@ -1232,17 +1232,18 @@ static int l_stat(lua_State *L) - * @brief Dispatch a message to the progress interface. - * - * @param [Lua] Message to dispatch to progress interface. -+ * @param [Lua] progress_cause_t number (optional), default: CAUSE_NONE - * @return [Lua] nil. - */ - int lua_notify_progress(lua_State *L) { -- /* -- * NOTE: level is INFOLEVEL for the sake of specifying a level. -- * It is unused in core/notifier.c :: progress_notifier() as the -- * progress emitter doesn't know about log levels. -- */ -- notify(PROGRESS, RECOVERY_NO_ERROR, INFOLEVEL, luaL_checkstring(L, -1)); -- lua_pop(L, 1); -- return 0; -+ lua_Number cause = CAUSE_NONE; -+ if (lua_isnumber(L, -1) == 1) { -+ cause = lua_tonumber(L, -1); -+ lua_pop(L, 1); -+ } -+ notify(PROGRESS, (progress_cause_t)cause, INFOLEVEL, luaL_checkstring(L, -1)); -+ lua_pop(L, 1); -+ return 0; - } - - /** -diff --git a/doc/source/suricatta.rst b/doc/source/suricatta.rst -index 474d02cc..a4081a63 100644 ---- a/doc/source/suricatta.rst -+++ b/doc/source/suricatta.rst -@@ -557,10 +557,10 @@ The ``suricatta.status`` table exposes the ``server_op_res_t`` enum values defin - The ``suricatta.notify`` table provides the usual logging functions to the Lua - suricatta module matching their uppercase-named pendants available in the C realm. - --One notable exception is ``suricatta.notify.progress(message)`` which dispatches the --message to the progress interface (see :doc:`progress`). Custom progress client --implementations listening and acting on custom progress messages can be realized --using this function. -+One notable exception is ``suricatta.notify.progress(message, cause)`` which -+dispatches the message to the progress interface (see :doc:`progress`). Custom -+progress client implementations listening and acting on custom progress messages -+can be realized using this function. - - All notify functions return ``nil``. - -diff --git a/handlers/swupdate.lua b/handlers/swupdate.lua -index aa8a31d0..d32f83b9 100644 ---- a/handlers/swupdate.lua -+++ b/handlers/swupdate.lua -@@ -63,8 +63,9 @@ swupdate.warn = function(format, ...) end - swupdate.debug = function(format, ...) end - - --- Lua equivalent of `notify(PROGRESS, ..., msg)`. ----- @param msg string Message to send to progress interface --swupdate.progress = function(msg) end -+--- @param msg string Message to send to progress interface -+--- @param cause number | nil `progress_cause_t` value as defined in `include/progress_ipc.h` -+swupdate.progress = function(msg, cause) end - - --- Lua equivalent of `notify(status, error, INFOLEVEL, msg)`. - --- @param status swupdate.RECOVERY_STATUS Current status, one of `swupdate.RECOVERY_STATUS`'s values -diff --git a/suricatta/server_wfx.lua b/suricatta/server_wfx.lua -index 0978ed50..d0b01bad 100644 ---- a/suricatta/server_wfx.lua -+++ b/suricatta/server_wfx.lua -@@ -1471,8 +1471,10 @@ M.job.workflow.dispatch:set( - suricatta.notify.warn("Cannot initialize progress reporting channel, won't send progress.") - end - -- suricatta.notify.progress(M.utils.string.escape([[{"%s": { "reboot-mode" : "no-reboot"}}]]) -- :format(suricatta.ipc.progress_cause.CAUSE_REBOOT_MODE)) -+ suricatta.notify.progress( -+ M.utils.string.escape([[{ "reboot-mode" : "no-reboot"}]]), -+ suricatta.ipc.progress_cause.CAUSE_REBOOT_MODE -+ ) - - suricatta.notify.debug( - "%s Version '%s' (Type: %s).", -diff --git a/suricatta/suricatta.lua b/suricatta/suricatta.lua -index 24d6eb8f..b8707a25 100644 ---- a/suricatta/suricatta.lua -+++ b/suricatta/suricatta.lua -@@ -47,6 +47,7 @@ suricatta.status = { - -- - -- Translates to `notify(string.format(message, ...))`, - -- @see `corelib/lua_interface.c` -+-- except for `suricatta.notify.progress()`. - -- - --- @class suricatta.notify - suricatta.notify = { -@@ -60,8 +61,8 @@ suricatta.notify = { - info = function(message, ...) end, - --- @type fun(message: string, ...: any) - warn = function(message, ...) end, -- --- @type fun(message: string, ...: any) -- progress = function(message, ...) end, -+ --- @type fun(message: string, cause:suricatta.ipc.progress_cause?) -+ progress = function(message, cause) end, - } - - diff -Nru swupdate-2024.12.1+dfsg/debian/rules swupdate-2025.12+dfsg/debian/rules --- swupdate-2024.12.1+dfsg/debian/rules 2025-07-14 10:55:24.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/rules 2026-05-18 11:02:21.000000000 +0000 @@ -6,7 +6,6 @@ endif export DEB_BUILD_MAINT_OPTIONS = hardening=+all -export DEB_CFLAGS_MAINT_APPEND = -I/usr/include/wolfssl -I/usr/include/p11-kit-1 export LIBDIR=usr/lib/${DEB_HOST_MULTIARCH} export HAVE_LUA=n ifeq (,$(filter nolua,$(DEB_BUILD_PROFILES))) @@ -16,6 +15,9 @@ export HAVE_LUA=y endif +execute_before_dh_auto_clean: + -mv debian/mongoose.? mongoose/ + override_dh_auto_configure-arch: cp debian/configs/defconfig configs/debian_defconfig ifeq (linux,$(DEB_HOST_ARCH_OS)) @@ -29,9 +31,14 @@ else echo "# CONFIG_MTD is not set" >> configs/debian_defconfig endif -ifneq (,$(findstring $(DEB_HOST_ARCH),amd64 i386 arm64 armhf loong64 riscv64 ia64)) +ifneq (,$(findstring $(DEB_HOST_ARCH),amd64 i386 arm64 armhf loong64 riscv64)) echo CONFIG_BOOTLOADER_EBG=y >> configs/debian_defconfig endif +ifeq (,$(filter pkg.swupdate.openssl,$(DEB_BUILD_PROFILES))) + echo "# CONFIG_SSL_IMPL_OPENSSL is not set" >> configs/debian_defconfig +else + echo CONFIG_SSL_IMPL_OPENSSL=y >> configs/debian_defconfig +endif ifeq (,$(filter pkg.swupdate.nohwcompat,$(DEB_BUILD_PROFILES))) echo CONFIG_HW_COMPATIBILITY=y >> configs/debian_defconfig endif @@ -41,15 +48,13 @@ ifeq (,$(filter pkg.swupdate.noswuforwarder,$(DEB_BUILD_PROFILES))) echo CONFIG_SWUFORWARDER_HANDLER=y >> configs/debian_defconfig endif -ifneq (,$(filter pkg.swupdate.p11,$(DEB_BUILD_PROFILES))) - echo CONFIG_PKCS11=y >> configs/debian_defconfig -endif ifneq (,$(LUA_VERSION)) echo CONFIG_LUAPKG=\"lua$(LUA_VERSION)\" >> configs/debian_defconfig echo CONFIG_LUASCRIPTHANDLER=y >> configs/debian_defconfig echo CONFIG_SURICATTA_LUA=y >> configs/debian_defconfig echo CONFIG_SURICATTA_WFX=y >> configs/debian_defconfig echo CONFIG_EMBEDDED_SURICATTA_LUA=y >> configs/debian_defconfig + echo CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE=\"suricatta/server_wfx.lua\" >> configs/debian_defconfig endif echo CONFIG_DELTA=y >> configs/debian_defconfig echo CONFIG_EXTRA_CFLAGS=\"$(CFLAGS) $(CPPFLAGS)\" >> configs/debian_defconfig @@ -71,18 +76,23 @@ sed -i 's|node_modules/bootstrap/dist/js|node_modules/bootstrap/js|' index.html override_dh_auto_build-arch: + mv mongoose/mongoose.? debian/ + cp /usr/src/mongoose.c mongoose/ ifeq (,$(filter nodoc,$(DEB_BUILD_OPTIONS) $(DEB_BUILD_PROFILES))) $(MAKE) man mv doc/build/man/client.1 doc/build/man/swupdate-client.1 mv doc/build/man/ipc.1 doc/build/man/swupdate-ipc.1 mv doc/build/man/progress.1 doc/build/man/swupdate-progress.1 endif - dh_auto_build -- V=1 + dh_auto_build -- V=1 SKIP_STRIP=y override_dh_auto_test-arch: ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS) $(DEB_BUILD_PROFILES))) -ifeq (,$(findstring mips,$(DEB_HOST_ARCH))) +ifeq (,$(filter armhf armel,$(DEB_HOST_ARCH))) dh_auto_test -- V=1 +else + # test_flash_handler/mtd-utils has issues on arm 32 bit, so ignore test failures + -dh_auto_test -- V=1 endif endif @@ -101,20 +111,8 @@ dh_installsystemd --name=swupdate-progress dh_installsystemd --no-start --name=swupdate-usb@ -ifeq (linux,$(DEB_HOST_ARCH_OS)) -override_dh_gencontrol-arch: - dh_gencontrol -- -VBuilt-Using="$(shell dpkg-query -f '$${source:Package} (= $${source:Version}), ' -W libmtd-dev libubi-dev)" -endif - -override_dh_gencontrol-indep: - # Bootstrap's scss file is derived from - dh_gencontrol -- -VB-U-www="$(shell dpkg-query -f '$${source:Package} (= $${source:Version}), ' -W node-bootstrap)" - override_dh_dwz: dh_dwz --no-dwz-multifile -override_dh_missing: - dh_missing --fail-missing - %: dh $@ diff -Nru swupdate-2024.12.1+dfsg/debian/swupdate-www.links swupdate-2025.12+dfsg/debian/swupdate-www.links --- swupdate-2024.12.1+dfsg/debian/swupdate-www.links 2025-07-14 10:54:20.000000000 +0000 +++ swupdate-2025.12+dfsg/debian/swupdate-www.links 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -/usr/share/fonts-fork-awesome /usr/share/swupdate/www/awesome diff -Nru swupdate-2024.12.1+dfsg/doc/source/asym_encrypted_images.rst swupdate-2025.12+dfsg/doc/source/asym_encrypted_images.rst --- swupdate-2024.12.1+dfsg/doc/source/asym_encrypted_images.rst 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/asym_encrypted_images.rst 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,165 @@ +.. SPDX-FileCopyrightText: 2025 Michael Glembotzki +.. SPDX-License-Identifier: GPL-2.0-only + +Asymmetrically Encrypted Update Images +====================================== + +Asymmetrically encrypted update images are realized by an asymmetrical +encrypted sw-description, making it possible to decrypt images device specific. +The artifacts themselves are still encrypted symmetrically. An AES key must be +provided in the sw-description. At the moment only Cryptographic Message Syntax +(CMS) is available for asymmetic decryption. + + +Use Cases +--------- + +- Asymmetrically encrypted update images, with individual device key pairs, are + inherently more secure than a purely symmetrical solution, because one + compromised private device key does not affect the security of the others. +- If ``CONFIG_SIGNED_IMAGES`` is enabled too and a device's private key is + compromised, the key pair can be excluded from the list of eligible devices + for receiving new update images. +- The AES key can be securely **exchanged** with each new update image, as it is + part of the sw-description, even in the absence of direct access to the + device. + + +Create a Self-Signed Device Key Pair +------------------------------------ + +As an example, an elliptic curve key pair (PEM) is generated for a single +device. These steps must be repeated for all other devices. An RSA key pair +could be used in the same way. + +:: + + # Create a private key and a self-signed certificate + openssl ecparam -name secp521r1 -genkey -noout -out device-key-001.pem + openssl req -new -x509 -key device-key-001.pem -out device-cert-001.pem -subj "/O=SWUpdate /CN=target" + + # Combine the private key and the certificate into a single file + # Note: The certificate is optional, but it speeds up key matching, + # especially when there are many different device keys. + cat device-key-001.pem device-cert-001.pem > device-001.pem + + +Symmetric Encryption of Artifacts +--------------------------------- + +Generate an AES key and IV, as familiar from +:ref:`symmetric image encryption `. The encryption +process for the artifacts remains unchanged. + + +Encryption of sw-description for Multiple Devices +------------------------------------------------- + +All device certificates togther are used for encryption. + +:: + + # Encrypt sw-description for multiple devices + openssl cms -encrypt -aes-256-cbc -in sw-description.in -out sw-description -outform DER $(printf ' -recip %q' certs/device-cert-*.pem) + +Assume `sw-description.in` is the plaintext input and `sw-description` is the +encrypted output. After `-recip`, a list of all device certificates is expected. +One for each device you want to encrypt for. + + +Decryption of sw-description for a Single Device +------------------------------------------------ + +The combined key pair (private key and certificate) is used for decryption. +SWUpdate handles the decryption process autonomously. Manually executing this +step is not necessary and is provided here solely for development purposes. + +:: + + # Decrypt sw-description for a single device + openssl cms -decrypt -in sw-description -out sw-description.out -inform DER -inkey device-privatekey-001.pem -recip device-cert-001.pem + +Assume `sw-description` is the encrypted sw-description and `sw-description.out` +is the plaintext output. The device private key is required for decryption, +while the certificate itself is optional, but helps speed up the key matching +process. + + +Example Asymmetrically Encrypted Image +-------------------------------------- + +The image artifacts should be symmetrically encrypted and signed in advance. +Now, create a plain `sw-description.in` file. The attributes: ``encrypted``, +``aes-key`` and ``ivt`` are necessary for artifact decryption. It is okay that +the AES key is included here, because the sw-description file will be encrypted +afterwards. + +:: + + software = + { + version = "1.0.0"; + images: ({ + filename = "rootfs.ext4.enc"; + device = "/dev/mmcblk0p3"; + sha256 = "131159df3a4efaa890ff80173664a125c496c458dd432a8a6acae18872e35822"; + encrypted = "aes-cbc"; // former: encrypted = true; + aes-key = "ed73b9d3bf9c655d5a0b04836d8be48660a4a4bb6f4aa07c6778e00e342881ac"; + ivt = "ea34a55a0c3476ed78f238ac87a7970c"; + }); + } + + +:ref:`Sign` `sw-description.in` and create the signature file `sw-description.sig`: +:: + + openssl cms -sign -in sw-description.in -out sw-description.sig -signer signing-cert.pem -inkey signing-key.pem -outform DER -nosmimecap -binary + + +Asymmetrically encrypt the `sw-description` for multiple devices: +:: + + openssl cms -encrypt -aes-256-cbc -in sw-description.in -out sw-description -outform DER $(printf ' -recip %q' certs/device-cert-*.pem) + + +Create the new update image (SWU): + +:: + + #!/bin/sh + + FILES="sw-description sw-description.sig rootfs.ext4.enc" + + for i in $FILES; do + echo $i;done | cpio -ov -H crc > firmware.swu + + +Running SWUpdate with Asymmetrically Encrypted Images +----------------------------------------------------- + +Asymmetric encryption support can be enabled by configuring the compile-time +option ``CONFIG_ASYM_ENCRYPTED_SW_DESCRIPTION``, which depends on +``CONFIG_ENCRYPTED_SW_DESCRIPTION``. To pass the combined key pair +(PEM) generated earlier to SWUpdate, use the ``-K`` argument. Alternatively, +use the ``decryption-key`` parameter in the ``swupdate.cfg``. + + +Security Considerations +----------------------- +- Ideally, generate the private key on the device during factory provisioning, + ensuring it never leaves the device. Only the public certificate leaves the + device for encrypting future update packages. +- This feature should be used in conjunction with signature verification + (``CONFIG_SIGNED_IMAGES``) to ensure data integrity. In principle, anyone + with the corresponding device certificate can create update packages. +- As a side effect, the size of the update package may significantly increase + in a large-scale deployment. To enhance scalability, consider using group + keys. Smaller groups should be preferred over larger ones. For example, + 1000 device keys (using secp521r1) increase the sw-description size to + 0.35 MB. This means that by forming groups of 1000, it is possible to support + 1 million devices. Alternatively, groups of 100 increase the sw-description + size to 3.5 MB accordingly. +- Exchange the AES key in the sw-description with each update package. +- Avoid encrypting new update packages for compromised devices, if there is no + direct access to the device or if unauthorized users have access to new update + packages. diff -Nru swupdate-2024.12.1+dfsg/doc/source/bindings.rst swupdate-2025.12+dfsg/doc/source/bindings.rst --- swupdate-2024.12.1+dfsg/doc/source/bindings.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/bindings.rst 2025-12-03 11:32:03.000000000 +0000 @@ -27,8 +27,10 @@ The ``lua_swupdate`` Lua module interface specification that details what functionality is made available by ``bindings/lua_swupdate.c`` is found -in ``bindings/lua_swupdate.lua``. It serves as reference, for mocking -purposes, and type checking thanks to the EmmyLua-inspired annotations. +in ``bindings/lua_swupdate.lua``, reproduced for convenience in +Section :ref:`bindings.lua.specification`. +It serves as reference, for mocking purposes, and type checking thanks to +the EmmyLua-inspired annotations. Note that, depending on the filesystem location of the Lua binding's shared library, Lua's ``package.cpath`` may have to be adapted by setting the @@ -117,3 +119,13 @@ For convenience, the ``lua_swupdate`` module provides the ``ipv4()`` method returning a table holding the local network interfaces as the table's keys and their space-separated IP addresses plus subnet masks as respective values. + +.. _bindings.lua.specification: + +Lua Language Binding Interface Specification +............................................ + +.. literalinclude:: ../../bindings/lua_swupdate.lua + :linenos: + :tab-width: 3 + :language: lua diff -Nru swupdate-2024.12.1+dfsg/doc/source/building-with-yocto.rst swupdate-2025.12+dfsg/doc/source/building-with-yocto.rst --- swupdate-2024.12.1+dfsg/doc/source/building-with-yocto.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/building-with-yocto.rst 2025-12-03 11:32:03.000000000 +0000 @@ -265,6 +265,69 @@ SWUPDATE_IMAGES_FSTYPES[] = inherit swupdate-image + +SWU image content's encryption +------------------------------ + +The swupdate class is able to encrypt the contents of the SWU image. In order to do +so first check that SWUpdate is compiled with ``CONFIG_ENCRYPTED_IMAGES=y`` +enabled. Then set ``SWUPDATE_AES_FILE`` to the full path of the key. Finally, +for each content you want to encrypt, on your SWU image recipe add: + +:: + + + SWUPDATE_IMAGES_ENCRYPTED[content] = "1" + + +Where ``content`` matches the files described on ``sw-description``. + +If you want to encrypt sw-description check that SWUpdate is being compiled with +``CONFIG_ENCRYPTED_SW_DESCRIPTION=y`` and then set the following on your SWU +image recipe: + +:: + + + SWUPDATE_ENCRYPT_SWDESC = "1" + + +Do not forget to add ``encrypted = true;`` on each of the contents that will require +decryption on the ``sw-description`` file. + +Expanding on the previous example: + +:: + + DESCRIPTION = "Example recipe generating SWU image" + SECTION = "" + + LICENSE = "" + + # Add all local files to be added to the SWU + # sw-description must always be in the list. + # You can extend with scripts or whatever you need + SRC_URI = " \ + file://sw-description \ + " + + # images to build before building swupdate image + IMAGE_DEPENDS = "core-image-full-cmdline virtual/kernel" + + # images and files that will be included in the .swu image + SWUPDATE_IMAGES = "core-image-full-cmdline uImage" + + # a deployable image can have multiple format, choose one + SWUPDATE_IMAGES_FSTYPES[core-image-full-cmdline] = ".ubifs" + SWUPDATE_IMAGES_FSTYPES[uImage] = ".bin" + + SWUPDATE_IMAGES_ENCRYPTED[core-image-full-cmdline.ubifs] = "1" + SWUPDATE_IMAGES_ENCRYPTED[uImage] = "1" + SWUPDATE_ENCRYPT_SWDESC = "1" + + inherit swupdate + + What about grub ? ================= In order to use swupdate with grub, swupdate needs to be configured to use grub. Some of diff -Nru swupdate-2024.12.1+dfsg/doc/source/conf.py swupdate-2025.12+dfsg/doc/source/conf.py --- swupdate-2024.12.1+dfsg/doc/source/conf.py 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/conf.py 2025-12-03 11:32:03.000000000 +0000 @@ -50,7 +50,7 @@ # General information about the project. project = u'Embedded Software Update Documentation' -copyright = u'2013-2024, Stefano Babic' +copyright = u'2013-2025, Stefano Babic' # The version info for the project you're documenting, acts as replacement for @@ -58,10 +58,10 @@ # built documents. # # The short X.Y version. -version = u'2024.12' +version = u'2025.12' # The full version, including alpha/beta/rc tags. -release = u'2024.12' +release = u'2025.12' # The language for content autogenerated by Sphinx. Refer to documentation @@ -185,6 +185,8 @@ # -- Options for LaTeX output -------------------------------------------------- +latex_engine = 'xelatex' + # The paper size ('letter' or 'a4'). latex_paper_size = u'a4' diff -Nru swupdate-2024.12.1+dfsg/doc/source/delta-update.rst swupdate-2025.12+dfsg/doc/source/delta-update.rst --- swupdate-2024.12.1+dfsg/doc/source/delta-update.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/delta-update.rst 2025-12-03 11:32:03.000000000 +0000 @@ -220,6 +220,17 @@ and sends them back to the caller. The delta handler is then responsible to parse the answer, and to retrieve the missing chunks from the multipart HTTP body. +Configuration_ +.............. + +The delta handler is configured in the runtime configuration file (swupdate.cfg). The configuration is done in the +`delta` section. See also example/configuration/swupdate.cfg for details. + +API key authentication can be setup by providing ``api_key_header`` and ``api_key`` in the configuration file. +``api_key_header`` is the header name that is used to send the ``api_key``. +``api_key`` is the value that is sent in the header. + + Creation of ZCK Header and ZCK file for SWUpdate ------------------------------------------------ diff -Nru swupdate-2024.12.1+dfsg/doc/source/encrypted_images.rst swupdate-2025.12+dfsg/doc/source/encrypted_images.rst --- swupdate-2024.12.1+dfsg/doc/source/encrypted_images.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/encrypted_images.rst 2025-12-03 11:32:03.000000000 +0000 @@ -1,6 +1,8 @@ .. SPDX-FileCopyrightText: 2013-2021 Stefano Babic .. SPDX-License-Identifier: GPL-2.0-only +.. _sym-encrypted-images: + Symmetrically Encrypted Update Images ===================================== diff -Nru swupdate-2024.12.1+dfsg/doc/source/handlers.rst swupdate-2025.12+dfsg/doc/source/handlers.rst --- swupdate-2024.12.1+dfsg/doc/source/handlers.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/handlers.rst 2025-12-03 11:32:03.000000000 +0000 @@ -118,6 +118,13 @@ saves in the handlers' list and pass to the handler when it will be executed. +Dummy Handler +------------- + +The always built-in and aptly named dummy handler copies the artifact +to ``/dev/null``, effectively skipping it and advancing to the next +artifact, if any. + UBI Volume Handler ------------------ @@ -183,7 +190,7 @@ There is a handler ubiswap that allow one to do an atomic swap for several ubi volume after all the images were flashed. This handler is a script -for the point of view of swudate, so the node that provide it the data +for the point of view of SWUpdate, so the node that provide it the data should be added in the section scripts. :: @@ -309,7 +316,8 @@ where ``image`` is a Lua table (with attributes according to :ref:`sw-description's attribute reference `) that describes a single artifact to be processed by the handler -(also see the Lua Handler Interface Specification in ``handlers/swupdate.lua``). +(also see the Lua Handler Interface Specification in ``handlers/swupdate.lua``, +reproduced for convenience in Section :ref:`handlers.lua.specification`). Note that dashes in the attributes' names are replaced with underscores for the Lua domain to make them idiomatic, e.g., @@ -457,6 +465,17 @@ implications since the behavior of SWUpdate is changed dynamically. +.. _handlers.lua.specification: + +Lua Handler Interface Specification +................................... + +.. literalinclude:: ../../handlers/swupdate.lua + :linenos: + :tab-width: 3 + :language: lua + + Shell script handler -------------------- @@ -474,7 +493,7 @@ - local: the script runs in own (isolated) Lua state that is created for the script. The script has access only to function defined inside the script or functions - provided by external libraries, like the internal swupdate library called via + provided by external libraries, like the internal SWUpdate library called via "require(swupdate)". - global: SWUpdate create a Lua state at the beginning of an Update and this is valid until the update is terminated. In this case, the script has access to any function @@ -482,7 +501,7 @@ can be defined inside sw-description, and the script can call it. As default, each script runs in isolated / local Lua state. If the property "global-state" is set, -then the common LUa state used for each Update transaction is taken. +then the common Lua state used for each Update transaction is taken. Scripts ran in isolated context in previous versions. SWUpdate allocates a new Lua state, and import the basic libraries before loading the script. A @@ -984,7 +1003,7 @@ Bootloader handler ------------------ -The bootloader handler allows to set bootloader's environment with a file. The file shold have the format: +The bootloader handler allows to set bootloader's environment with a file. The file should have the format: :: @@ -1021,13 +1040,13 @@ --------------- The archive handler extracts an archive to a destination path. -It supports whatever format libarchive has been compiled to support, for example even if swupdate +It supports whatever format libarchive has been compiled to support, for example even if SWUpdate itself has no direct support for xz it can be possible to extract tar.xz files with it. -The attribute `preserve-attributes` must be set to preserve timestamps. uid/gid (numeric), -permissions (except +x, always preserved) and extended attributes. +The attribute `preserve-attributes` must be set to preserve timestamps. uid/gid (numeric) and +extended attributes. File modes (such as chmod 0755/0600) are always preserved. -The property `create-destination` can be set to the string `true` to have swupdate create +The property `create-destination` can be set to the string `true` to have SWUpdate create the destination path before extraction. :: @@ -1185,8 +1204,11 @@ } } -gpt partition installer ------------------------ +GPT Partition Handler +--------------------- + +GPT Partition Installer +....................... There is a handler gptpart that allows writing an image into a gpt partition selected by the name. This handler do not modify the gpt partition (type, size, ...), it just writes @@ -1210,12 +1232,12 @@ }, ); -gpt partition swap ------------------- +GPT Partition Swap +.................. There is a handler gptswap that allow to swap gpt partitions after all the images were flashed. -This handler only swaps the name of the partition. It coud be useful for a dual bank strategy. -This handler is a script for the point of view of swupdate, so the node that provides it should +This handler only swaps the name of the partition. It could be useful for a dual bank strategy. +This handler is a script for the point of view of SWUpdate, so the node that provides it should be added in the section scripts. Simple example: @@ -1278,8 +1300,11 @@ } }); +BTRFS Handler +------------- + BTRFS Partition Handler ------------------------ +....................... This handler is activated if support for BTRFS is on. It allows to created and delete subvolumes during an update. @@ -1304,13 +1329,13 @@ mountpoint used with mount. If device is already mounted, path is the absolute path. BTRFS Snapshot Handler ----------------------- +...................... The handler allows to install a BTRFS snapshot created with the "btrfs send" command. SWUpdate is using the external "btrfs" utility, that must be installed on the target, and "btrfs receive" is executed by sending the stream to the command. -All generic features are avaiulable, that means that an srtifact can be streamed by +All generic features are available, that means that an artifact can be streamed by using the "installed-directly" attribute. @@ -1331,12 +1356,12 @@ If `tomount` is set, SWUpdate will temporary mount "device" as BTRFS filesystem and will try to install the snapshot to `path`. `btrfs-cmd` is optional, fallback is /usr/bin/btrfs. -Generic Executor handler +Generic Executor Handler ------------------------ The BTRFS snapshot handler requires to stream an artifact after normal handling (decompression, decryption, etc.) to the external command "btrfs" without any temporary copy. -The same infrastucture can be used to stream any artifact to any arbitrary external command +The same infrastructure can be used to stream any artifact to any arbitrary external command that accepts the stream as stdin. This is done with the "executor" handler. @@ -1361,11 +1386,11 @@ after generating the corresponding header of the running artifact to be updated. The handler uses just a couple of attributes from the main setup, and gets more information from the properties. The attributes are then passed to a secondary handler that -will install the artefact after the delta handler has assembled it. +will install the artifact after the delta handler has assembled it. The handler requires ZST because this is the compression format for Zchunk. The SWU must just contain the ZCK's header, while the ZCK file is put as it is on the server. -The utilities in Zchunk project are used to build the zck file. +The utilities in Zchunk project are used to build the ZCK file. :: @@ -1440,7 +1465,7 @@ } Memory issue with zchunk ------------------------- +........................ SWUpdate will create the header from the current version, often from a block partition. As default, Zchunk creates a temporary file with all chunks in /tmp, that is at the end concatenated to the @@ -1448,19 +1473,19 @@ partition (SWUpdate does not compress the chunks) is required. This was solved with later version of Zchunk - check inside zchunk code if ZCK_NO_WRITE is supported. -Docker handlers ----------------- +Docker Handlers +--------------- To improve containers update, a docker set of handlers implements part of the API to communicate with the docker daemon. Podman (another container solution) has a compatibility layer for docker REST API and could be used as well, but SWUpdate is currently not checking if a podman daemon must be started. -Goal of these handlers is just to provice the few API to update images and start containers - it +Goal of these handlers is just to provide the few API to update images and start containers - it does not implement the full API. Docker Load Image ------------------ +................. This handler allow to load an image without copying temporarily and push it to the docker daemon. It implements the /images/load entry point., and it is implemented as "image" handler. The file @@ -1497,7 +1522,7 @@ Docker Remove Image -------------------- +................... It is implemented as script (post install). Example: @@ -1512,7 +1537,7 @@ }); Docker Delete Unused Images ---------------------------- +........................... It is implemented as script (post install). Example: @@ -1525,7 +1550,7 @@ Docker: container create ------------------------- +........................ It is implemented as post-install script. The script itself is the json file passed to the daemon to configure and set the container. The container is just created, not started. @@ -1557,7 +1582,7 @@ }); Docker Remove Container ------------------------ +....................... It is implemented as script (post install). Example: @@ -1572,7 +1597,7 @@ }); Docker : Start / Stop containers --------------------------------- +................................ Examples: Binary files /srv/release.debian.org/tmp/fypScWwhR3/swupdate-2024.12.1+dfsg/doc/source/images/crypto_architecture.png and /srv/release.debian.org/tmp/48LOKlTuXA/swupdate-2025.12+dfsg/doc/source/images/crypto_architecture.png differ diff -Nru swupdate-2024.12.1+dfsg/doc/source/improvement_proposals.rst swupdate-2025.12+dfsg/doc/source/improvement_proposals.rst --- swupdate-2024.12.1+dfsg/doc/source/improvement_proposals.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/improvement_proposals.rst 2025-12-03 11:32:03.000000000 +0000 @@ -115,17 +115,6 @@ * Request for Support : Sponsor * Priority : High -Parser -====== - -SWUpdate supports two parsers : libconfig and JSON. It would be nice if tools can -be used to convert from one format to the other one. Currently, due to some specialties -in libconfig, a manual conversion is still required. - -* Status: Wait -* Request for Support : None -* Priority : Medium - Fetcher and interfaces ====================== @@ -189,6 +178,15 @@ * Request for Support : Sponsor * Priority : Medium +LZMA support to SWUGenerator +---------------------------- + +XZ (LZMA) decompression was added to SWUpdate, but SWUGenerator is not able to create XZ compressed images. + +* Status: Wait +* Request for Support : Not required +* Priority : Medium + swupdate-progress start up -------------------------- @@ -234,36 +232,6 @@ * Request for Support : None * Priority : Low -Handlers installable as plugin at runtime ------------------------------------------- - -The project supports Lua as script language for pre- and postinstall -script. It will be easy to add a way for installing a handler at run-time -written in Lua, allowing to expand SWUpdate to the cases not covered -in the design phase of a product. - -Of course, this issue is related to the security features: it must be -ensured that only verified handlers can be added to the system to avoid -that malware can get the control of the target. - -Current release supports verified images. That means that a handler -written in Lua could be now be part of the compound image, because -a unauthenticated handler cannot run. - -* Status : Running -* Request for Support : None - -Support for BTRFS snapshot --------------------------- - -BTRFS supports subvolume and delta backup for volumes - supporting subvolumes is a way -to move the delta approach to filesystems, while SWUpdate should apply the deltas -generated by BTRFS utilities. - -* Status: Design -* Request for Support : Planned -* Priority : Medium - Internal Webserver ================== @@ -281,15 +249,19 @@ ========================= - add support for asymmetric decryption -- rework support for crypto engine - let possible to load multiple libraries at - the same time. Currently, there is support for openSSL, WolfSSL and mbedTLS. - However, WolfSSL are missing together. There should be a way to select one or more - libraries and independently the algorithms that SWUpdate should support. - Some hacks are currently built to avoid conflicts (pkcs#7 and CMS are the same - thing, but supported by different libraries), and they should be solved. + +* Status: Wait +* Request for Support : Sponsor +* Priority : High + - add more algorithms for decryption, as AES-CTR can be very useful to decrypt chunks in delta updates. -- Support for TPM2 to store secrets (requires rework above). + +* Status: Wait +* Request for Support : Sponsor +* Priority : High + +- Support for TPM2 / HSM to store secrets (requires rework above). * Status: Wait * Request for Support : Sponsor @@ -356,8 +328,8 @@ is performed by the backend connector, but the download of .zck files is done by a different process ("downloader") that don't use the setup from suricatta. -* Status: Wait -* Request for Support : Sponsor +* Status: In progress +* Request for Support : Sponsored by Orica Digital Solutions * Priority : Medium Back-end: support for generic down-loader @@ -398,18 +370,43 @@ * Request for Support : Sponsor * Priority : Medium +Bootloader interface +==================== + +SWUpdate has several interfaces to bootloader, but support for handling UEFI variables is still +missing. It is required to set UEFI variable exactly as done for other bootloader (like U-Boot) +via sw-description. Lua code can profit, too, becaause variables can be retrieved using the "get" +function. + +* Status: Wait +* Request for Support : Sponsor +* Priority : High + Binding to languages ==================== libswupdate allows to write an application that can control SWUpdate's behavior and be informed about a running update. There are bindings for C/C++, Lua and nodejs (just progress). +Use a JSON interface to exchange IPC messeges. +---------------------------------------------- + +Instead of using binary message, use JSON to exchange messages between a client and +SWUpdate. This makes adding new binding very easy, and often not necessary. + +* Status: Wait +* Request for Support : Sponsor +* Priority : Low + +Bindings for other languages +---------------------------- + Applications can be written in other languages, and binding to Python and Rust can be implemented, too. * Status: Wait * Request for Support : Sponsor -* Priority : Medium +* Priority : Low Documentation ============= @@ -420,3 +417,73 @@ * Status : Running * Request for Support : None + +Already completed and mainlined +=============================== + +Following items were already implemented and are supported in mainlined - thanks for whom +sponsored them. + +Support for BTRFS snapshot +-------------------------- + +BTRFS supports subvolume and delta backup for volumes - supporting subvolumes is a way +to move the delta approach to filesystems, while SWUpdate should apply the deltas +generated by BTRFS utilities. + +* Status: since 2024.12 + +Parser +====== + +SWUpdate supports two parsers : libconfig and JSON. It would be nice if tools can +be used to convert from one format to the other one. Currently, due to some specialties +in libconfig, a manual conversion is still required. + +* Status: since 2024.12 + +Support for different Update Types +---------------------------------- + +Update can be split and group in several components. If updating everything in one shot +is the preferred method, there are use case where different updates are provided, for example +OS and Application updates, and different SWU are generated. +SWUpdate does not recognize which is the type of the update, and uses global rules to check +the version. The feature here is thought to introduce update-types, and a set of per type setup +(min / max version, downgrading options,..) that are be used if the update of that type is recognized. +Types are not limited to a selected list, but they can be free set inside swupdate.cfg. +A flag can require that the update-type is mandatory, and SWUpdate will always check +if the type is one of the supported. + +* Status: since 2025.05+ +* Sponsored by iris-GmbH infrared & intelligent sensors + +Handlers installable as plugin at runtime +------------------------------------------ + +The project supports Lua as script language for pre- and postinstall +script. It will be easy to add a way for installing a handler at run-time +written in Lua, allowing to expand SWUpdate to the cases not covered +in the design phase of a product. + +Of course, this issue is related to the security features: it must be +ensured that only verified handlers can be added to the system to avoid +that malware can get the control of the target. + +Current release supports verified images. That means that a handler +written in Lua could be now be part of the compound image, because +a unauthenticated handler cannot run. + +* Status : since 2024.05 + +Security subsystem and support multiple engines +----------------------------------------------- + +Rework support for crypto engine - let possible to load multiple libraries at +the same time. Currently, there is support for openSSL, WolfSSL and mbedTLS. +However, WolfSSL are missing together. There should be a way to select one or more +libraries and independently the algorithms that SWUpdate should support. +Some hacks are currently built to avoid conflicts (pkcs#7 and CMS are the same +thing, but supported by different libraries), and they should be solved. + +* Status: after 2025.05 diff -Nru swupdate-2024.12.1+dfsg/doc/source/index.rst swupdate-2025.12+dfsg/doc/source/index.rst --- swupdate-2024.12.1+dfsg/doc/source/index.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/index.rst 2025-12-03 11:32:03.000000000 +0000 @@ -41,6 +41,7 @@ sw-description.rst signed_images.rst encrypted_images.rst + asym_encrypted_images.rst handlers.rst mongoose.rst suricatta.rst diff -Nru swupdate-2024.12.1+dfsg/doc/source/licensing.rst swupdate-2025.12+dfsg/doc/source/licensing.rst --- swupdate-2024.12.1+dfsg/doc/source/licensing.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/licensing.rst 2025-12-03 11:32:03.000000000 +0000 @@ -15,28 +15,11 @@ To make this easier, license headers in the source files will be replaced with a single line reference to Unique License Identifiers -as defined by the Linux Foundation's SPDX project [1]. For example, -in a source file the full "GPL v2.0 only" header text will be -replaced by a single line: - -:: - - SPDX-License-Identifier: GPL-2.0-only - +as defined by the Linux Foundation's SPDX project [1]. Ideally, the license terms of all files in the source tree should be defined by such License Identifiers; in no case a file can contain more than one such License Identifier list. -If a "SPDX-License-Identifier:" line references more than one Unique -License Identifier, then this means that the respective file can be -used under the terms of either of these licenses, i. e. with - -:: - - SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause - -you can choose between GPL-2.0-only and BSD-3-Clause licensing. - We use the SPDX_ Unique License Identifiers (SPDX-Identifiers_) .. _SPDX: http://spdx.org/ diff -Nru swupdate-2024.12.1+dfsg/doc/source/previous-releases.rst swupdate-2025.12+dfsg/doc/source/previous-releases.rst --- swupdate-2024.12.1+dfsg/doc/source/previous-releases.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/previous-releases.rst 2025-12-03 11:32:03.000000000 +0000 @@ -5,6 +5,8 @@ Documentation for previous releases =================================== +- `2025.05 <./2025.05/index.html>`_ +- `2024.12 <./2024.12/index.html>`_ - `2024.05 <./2024.05/index.html>`_ - `2023.12 <./2023.12/index.html>`_ - `2023.05 <./2023.05/index.html>`_ diff -Nru swupdate-2024.12.1+dfsg/doc/source/signed_images.rst swupdate-2025.12+dfsg/doc/source/signed_images.rst --- swupdate-2024.12.1+dfsg/doc/source/signed_images.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/signed_images.rst 2025-12-03 11:32:03.000000000 +0000 @@ -1,6 +1,8 @@ .. SPDX-FileCopyrightText: 2013-2021 Stefano Babic .. SPDX-License-Identifier: GPL-2.0-only +.. _signed-images: + Update images from verified source ================================== @@ -58,6 +60,20 @@ For RSA and CMS algorithms, key or certificate is passed to SWUpdate with the `-k` parameter. +Architecture of crypto subsystem +-------------------------------- + +The subszstem consists of crypto "providers", services and modules. +A provider is a library that allows to build services. It is an external library +like openSSL. Modules will use one of the provider to implement one service. +SWUpdate defines three type of services : Verification, Hashing and Decryption. + +The modules are the specific implementation of one of the services with +an algorythm supported by the provided. It can be CMS, AES, pkcs#11, etc. +Multiple modules can be installed, and the choice can be done at runtime. + +.. image:: images/crypto_architecture.png + Tool to generate keys / certificates ------------------------------------ @@ -124,21 +140,24 @@ :: - openssl req -x509 -newkey rsa:4096 -nodes -keyout mycert.key.pem \ - -out mycert.cert.pem -subj "/O=SWUpdate /CN=target" - + openssl req -x509 -newkey rsa:4096 -noenc -keyout mycert.key.pem \ + -out mycert.cert.pem -subj "/O=SWUpdate/CN=target" \ + -addext extendedKeyUsage=1.3.6.1.5.5.7.3.4 -addext keyUsage=digitalSignature Check the documentation for more information about parameters. The "mycert.key.pem" contains the private key and it is used for signing. It is *not* delivered on the target. The target must have "mycert.cert.pem" installed - this is used by SWUpdate for verification. +.. note:: + The extendedKeyUsage value of "1.3.6.1.5.5.7.3.4" refers to "emailProtection". For "codeSigning" + use "1.3.6.1.5.5.7.3.3". Using PKI issued certificates ............................. It is also possible to use PKI issued code signing certificates. However, -SWUpdate uses OpenSSL library for handling CMS signatures and the library +SWUpdate uses OpenSSL library for handling CMS signatures and by default the library requires the following attributes to be set on the signing certificate: :: @@ -146,6 +165,9 @@ keyUsage=digitalSignature extendedKeyUsage=emailProtection +It is possible to change the default extendedKeyUsage value by setting the cert-purpose parameter +in swupdate.cfg. + It is also possible to completely disable signing certificate key usage checking if this requirement cannot be satisfied. This is controlled by `CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE` configuration option. @@ -283,7 +305,7 @@ it is not possible to disable the check at runtime. For RSA and CMS signing, the -k parameter (public key file) is mandatory and the program stops -if the public key is not passed. +if the public key is not passed. For CMS signing, CONFIG_SIGALG_CMS needs to be enabled. For GPG signing, CONFIG_SIGALG_GPG needs to be enabled. The GPG key will need to be imported to the device's GnuPG home directory. To do this, the diff -Nru swupdate-2024.12.1+dfsg/doc/source/suricatta.rst swupdate-2025.12+dfsg/doc/source/suricatta.rst --- swupdate-2024.12.1+dfsg/doc/source/suricatta.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/suricatta.rst 2025-12-03 11:32:03.000000000 +0000 @@ -485,8 +485,8 @@ ``CONFIG_EMBEDDED_SURICATTA_LUA_SOURCE`` accordingly. The interface specification in terms of a Lua (suricatta) module is found in -``suricatta/suricatta.lua``. - +``suricatta/suricatta.lua``, reproduced for convenience in +Section :ref:`suricatta.lua.specification`. `suricatta` ........... @@ -557,10 +557,10 @@ The ``suricatta.notify`` table provides the usual logging functions to the Lua suricatta module matching their uppercase-named pendants available in the C realm. -One notable exception is ``suricatta.notify.progress(message)`` which dispatches the -message to the progress interface (see :doc:`progress`). Custom progress client -implementations listening and acting on custom progress messages can be realized -using this function. +One notable exception is ``suricatta.notify.progress(message, cause)`` which +dispatches the message to the progress interface (see :doc:`progress`). Custom +progress client implementations listening and acting on custom progress messages +can be realized using this function. All notify functions return ``nil``. @@ -719,3 +719,13 @@ The function ``suricatta.bootloader.env.apply(filename)`` applies all key=value lines of a local file ``filename`` to the currently selected bootloader's environment. + +.. _suricatta.lua.specification: + +Lua Suricatta Interface Specification +..................................... + +.. literalinclude:: ../../suricatta/suricatta.lua + :linenos: + :tab-width: 3 + :language: lua diff -Nru swupdate-2024.12.1+dfsg/doc/source/sw-description.rst swupdate-2025.12+dfsg/doc/source/sw-description.rst --- swupdate-2024.12.1+dfsg/doc/source/sw-description.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/sw-description.rst 2025-12-03 11:32:03.000000000 +0000 @@ -86,7 +86,7 @@ filename = "README"; path = "/README"; device = "/dev/mmcblk0p1"; - filesystem = "vfat" + filesystem = "vfat"; } ); @@ -108,7 +108,7 @@ }, { name = "addfb"; - value = "setenv bootargs ${bootargs} omapfb.vram=1:2M,2:2M,3:2M omapdss.def_disp=lcd" + value = "setenv bootargs ${bootargs} omapfb.vram=1:2M,2:2M,3:2M omapdss.def_disp=lcd"; } ); } @@ -1247,6 +1247,59 @@ numbers (major, minor, patch) and the fourth number will be silently dropped if present. +Update Types +------------ + +There are use cases where distinct updates are preferred, for example an OS update +or an Application update. SWUpdate is unaware of this, and rules are defined +globally into the configuration file (swupdate.cfg) or in the description file +as part of SWU. However, it could be required to overwrite some global rules, +and specific updates require different setups. For example, an application update +could require a different pre- or post- update script, and the accepted versions +generally differ from the ones for OS. For this reason "update-types" are introduced. +They must be listed in swupdate.cfg (see example for this file), and sw-description will +identify which is the type of the SWU. For example: + +:: + + update-type = "application"; + +To make it valid, swupdate.cfg must have an entry to identify it, else the update will fail +because the specific typoe was not found. In swupdate.cfg there will be something like: + +:: + + update-types: ( + { + name = "OS"; + postupdatecmd = "/usr/bin/echo Finished !"; + no-downgrading = "5.0"; + preupdatecmd = "/usr/bin/echo Hello"; + max-version = "8.99"; + no-reinstalling = "5.5"; + reboot = "enabled"; + }, + { + name = "Application"; + postupdatecmd = "/usr/bin/echo Finished !"; + no-downgrading = "3.0"; + preupdatecmd = "/usr/bin/echo Hello"; + max-version = "5.99"; + no-reinstalling = "4.5"; + reboot = "disabled"; + } + ) + +Further releases could add new parameters that are specific for an update. If a type is specified, +SWUpdate will overwrite the rules set in the "global" section of swupdate.cfg with the specific one. +Values in the "globals" are still identified with a type, that SWUpdate calls "default". + +A flag into swupdate.cfg (update-type-required, globals section) will force that "update-type" is +mandatory, and SWU without the type set will be simply rejected. + +The flag reboot identifies if the update will be followed by a reboot or it is marked as "no rebootable". +This can still be overwritten by the global attribute "reboot" in sw-description. + Embedded Script --------------- @@ -1415,8 +1468,8 @@ | | | files | compressed and must be decompressed | | | | | before being installed. the value | | | | | denotes the compression type. | - | | | | currently supported values are "zlib" | - | | | | and "zstd". | + | | | | currently supported values are "xz", | + | | | | "zlib" and "zstd". | +-------------+----------+------------+---------------------------------------+ | compressed | bool (dep| images | Deprecated. Use the string form. true | | | recated) | files | is equal to 'compressed = "zlib"'. | @@ -1458,13 +1511,26 @@ | | | | compared with the entries in | | | | | sw-versions | +-------------+----------+------------+---------------------------------------+ - | encrypted | bool | images | flag | - | | | files | if set, file is encrypted | - | | | scripts | and must be decrypted before | - | | | | installing. | + | encrypted | string | images | string to indicate the artefact is | + | | | files | encrypted with this cipher. | + | | | scripts | e.g 'encrypted = "aes-cbc"'. | + | | | | See swupdate_aes.h for supported | + | | | | values. | + +-------------+----------+------------+---------------------------------------+ + | encrypted | bool | images | Use the string form, if the key is | + | | | files | provided within the sw-description. | + | | | scripts | true is equal to | + | | | | 'encrypted = "aes-cbc"'. | + +-------------+----------+------------+---------------------------------------+ + | aes-key | string | images | AES key for encrypted artifacts | + | | | files | Note: This key should only be used if | + | | | scripts | sw-description is encrypted | + | | | | (symmetrically or asymmetrically). It | + | | | | must be provided as an ASCII hex | + | | | | string of 16, 24, or 32 characters. | +-------------+----------+------------+---------------------------------------+ - | ivt | string | images | IVT in case of encrypted artefact | - | | | files | It has no value if "encrypted" is not | + | ivt | string | images | Optional IVT for encrypted artefacts. | + | | | files | It has no effect if "encrypted" is not| | | | scripts | set. Each artefact can have an own | | | | | IVT to avoid attacker can guess the | | | | | the key. | diff -Nru swupdate-2024.12.1+dfsg/doc/source/swupdate-client.rst swupdate-2025.12+dfsg/doc/source/swupdate-client.rst --- swupdate-2024.12.1+dfsg/doc/source/swupdate-client.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/swupdate-client.rst 2025-12-03 11:32:03.000000000 +0000 @@ -23,6 +23,10 @@ ask the server to only perform a dry-run -e , select software image set and source (for example: stable,main) +-s + path to SWUpdate's IPC socket +-g + path to SWUpdate's progress socket -q go quiet, resets verbosity -v diff -Nru swupdate-2024.12.1+dfsg/doc/source/swupdate.rst swupdate-2025.12+dfsg/doc/source/swupdate.rst --- swupdate-2024.12.1+dfsg/doc/source/swupdate.rst 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/doc/source/swupdate.rst 2025-12-03 11:32:03.000000000 +0000 @@ -57,7 +57,7 @@ SWUpdate can recreate UBI volumes, resizing them and copying the new software. -- support for compressed images, using the zlib and zstd library. +- support for compressed images, using the xz, zlib and zstd library. tarball (tgz file) are supported. - support for partitioned USB-pen or unpartitioned (mainly @@ -529,6 +529,27 @@ | | | it can be set multiple times | +-------------+----------+--------------------------------------------+ +Crypto selection parameters +--------------------------- + ++------------------------+----------+--------------------------------------------+ +| Parameter | Type | Description | ++========================+==========+============================================+ +| --digest-provider | string | Select the implementation for signature | +| | | verification. The list of implementation | +| | | is written at start. Examples: opensslCMS | +| | | opensslRSA, etc. | ++------------------------+----------+--------------------------------------------+ +| --decrpyt-provider | string | Select the implementation for decryption | +| | | The list of implementation | +| | | is written at start. Examples: openssl | +| | | wolfssl, etc. | ++------------------------+----------+--------------------------------------------+ +| --hash-provider | string | Select the implementation for hash | +| | | computation. The list of implementation | +| | | is written at start. Examples: openssl | ++------------------------+----------+--------------------------------------------+ + Downloader command line parameters .................................. diff -Nru swupdate-2024.12.1+dfsg/examples/configuration/swupdate.cfg swupdate-2025.12+dfsg/examples/configuration/swupdate.cfg --- swupdate-2024.12.1+dfsg/examples/configuration/swupdate.cfg 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/examples/configuration/swupdate.cfg 2025-12-03 11:32:03.000000000 +0000 @@ -12,6 +12,20 @@ # # verbose : boolean # set verbose mode (Default: false) +# +# decrypt-provider : string +# if multiple algos are regitered +# choose which one should be used (mbedTLS, openssl,wolfssl) +# +# hash-provider : string +# if multiple algos are regitered +# choose which one should be used (mbedTLS, openssl,wolfssl) +# +# digest-provider : string +# if multiple algos are regitered +# choose which one should be used +# (opensslCMS, GPG, pkcs#7WolfSSL, mbedTLSRSA, opensslRSA) +# # loglevel : integer # level for logging from 1 (no log) to 6 # syslog : boolean @@ -22,8 +36,8 @@ # mtd-blacklist : list integers # MTD devices where SWUpdate # must not try to check for UBI filesystem. -# aes-key-file : string -# file containing the symmetric key for +# aes-key-file or : string +# decryption-key-file file containing the decryption key for # image decryption # preupdatecmd : string # command to be executed right before the update @@ -48,11 +62,16 @@ # Possible values are ebg, grub, uboot, and none for # EFI Boot Guard, U-Boot, GRUB, and the Environment in RAM bootloader, # respectively, given the respective bootloader support is compiled-in. -# namespace-vars : namespace used by libubootenv for application/SWUpdate persistent +# namespace-vars : string +# namespace used by libubootenv for application/SWUpdate persistent # variables. This can be overridden in sw-description. # It is one set in libubootenv configuration file. -# fwenv-config-location : path of the configuration file for libubootenv -# gen-swversions : generate a version file containing all installed (versioned) images. +# fwenv-config-location : string +# path of the configuration file for libubootenv +# gen-swversions : string +# path of a generated version file containing all installed (versioned) images. +# update-type-required : boolean +# strict requires that each SWU has an update type. globals : { @@ -63,6 +82,49 @@ mtd-blacklist = "0 1 2 3 4 5 6"; }; +# update-types : accepted update-type and setup +# This allows to select per type configuration +# for example: Application, OS, Configuration, etc. +# +# name : string (mandatory) +# +# preupdatecmd : string +# command to be executed right before the update +# is installed +# postupdatecmd : string +# command to be executed after a successful update +# no-downgrading : string +# not install a release older as +# no-reinstalling : string +# not install a release same as + +update-types: ( + { + name = "OS"; + postupdatecmd = "/usr/bin/echo Finished !"; + no-downgrading = "5.0"; + preupdatecmd = "/usr/bin/echo Hello"; + max-version = "8.99"; + no-reinstalling = "5.5"; + }, + { + name = "Application"; + postupdatecmd = "/usr/bin/echo Finished !"; + no-downgrading = "3.0"; + preupdatecmd = "/usr/bin/echo Hello"; + max-version = "5.99"; + no-reinstalling = "4.5"; + }, + { + name = "Configuration"; + postupdatecmd = "/usr/bin/echo Configuration Finished !"; + no-downgrading = "1.0"; + preupdatecmd = "/usr/bin/echo Hello"; + max-version = "7.99"; + no-reinstalling = "1.1"; + } +); + # logcolors : set colors for output to stdout / stderr # color is set indivisually for each level # each entry is in the format @@ -282,6 +344,10 @@ # sslcert : string # path of the file containing the certificate for SSL connection or pkcs11 URI # (ex. "pkcs11:model=ATECC608B;token=0ABC;serial=0123456789abcdef;object=device;type=cert") +# api_key_header : string +# HTTP header name for the API key +# api_key : string +# API key for the delta update server delta : { sslkey = "/etc/ssl/sslkey"; diff -Nru swupdate-2024.12.1+dfsg/handlers/archive_handler.c swupdate-2025.12+dfsg/handlers/archive_handler.c --- swupdate-2024.12.1+dfsg/handlers/archive_handler.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/archive_handler.c 2025-12-03 11:32:03.000000000 +0000 @@ -161,9 +161,10 @@ archive_error_string(a), archive_entry_pathname(entry), strerror(archive_errno(a))); } else { - ERROR("archive_read_next_header(): %s: %s", - archive_error_string(a), - strerror(archive_errno(a))); + ERROR("archive_read_next_header(): %s for '%s': %s", + archive_error_string(a), + archive_entry_pathname(entry), + strerror(archive_errno(a))); goto out; } } @@ -173,8 +174,9 @@ r = archive_write_header(ext, entry); if (r != ARCHIVE_OK) { - ERROR("archive_write_header(): %s: %s", + ERROR("archive_write_header(): %s for '%s': %s", archive_error_string(ext), + archive_entry_pathname(entry), strerror(archive_errno(ext))); goto out; } diff -Nru swupdate-2024.12.1+dfsg/handlers/copy_handler.c swupdate-2025.12+dfsg/handlers/copy_handler.c --- swupdate-2024.12.1+dfsg/handlers/copy_handler.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/copy_handler.c 2025-12-03 11:32:03.000000000 +0000 @@ -15,7 +15,9 @@ #include #include #include +#ifdef CONFIG_MTD #include +#endif #include #ifdef __FreeBSD__ #include @@ -24,12 +26,27 @@ #else #include #endif +/* + * Though the glibc-specific flag `FTW_ACTIONRETVAL` wasn't passed to `nftw()`, + * the specific return codes are used nonetheless. As they happen to match + * the defaults, just define them for musl and non-Linux systems. + */ +#ifndef FTW_CONTINUE +#define FTW_CONTINUE 0 +#endif +#ifndef FTW_STOP +#define FTW_STOP 1 +#endif +#include #include #include "swupdate_image.h" #include "progress.h" #include "handler.h" #include "util.h" +#ifdef CONFIG_MTD +#include "flash.h" +#endif #include "handler_helpers.h" #include "installer.h" @@ -47,7 +64,9 @@ { int fdout, fdin, ret; struct stat statbuf; - struct mtd_info_user mtdinfo; +#ifdef CONFIG_MTD + struct mtd_info_user mtdinfo; +#endif struct chain_handler_data priv; uint32_t checksum; int pipes[2]; @@ -80,7 +99,9 @@ else if (S_ISBLK(statbuf.st_mode) && (ioctl(fdin, BLKGETSIZE64, &size) < 0)) { ERROR("Cannot get size of Block Device %s", path); size = -1; - } else if (S_ISCHR(statbuf.st_mode)) { + } +#ifdef CONFIG_MTD + else if (S_ISCHR(statbuf.st_mode)) { /* it is maybe a MTD device, just try */ ret = ioctl(fdin, MEMGETINFO, &mtdinfo); if (!ret) { @@ -89,6 +110,10 @@ size = -1; } } +#endif + else { + size = -1; + } } if (size < 0) { @@ -213,7 +238,17 @@ script_data = data; base_img = img; - +#ifdef CONFIG_MTD + if(strlen(img->mtdname) ){ + int mtdnum = get_mtd_from_name(img->mtdname); + if (mtdnum < 0) { + ERROR("Wrong MTD name in description: %s", + img->mtdname); + return -1; + } + snprintf(img->device, sizeof(img->device), "/dev/mtdblock%d", mtdnum); + } +#endif proplist = dict_get_list(&img->properties, "type"); if (proplist) { entry = LIST_FIRST(proplist); @@ -236,7 +271,19 @@ ERROR("Missing source device, no copyfrom property"); return -EINVAL; } - +#ifdef CONFIG_MTD + if (!strncmp("mtd:", entry->value, 4)) { + int mtdnum = get_mtd_from_name(entry->value + 4); + if (mtdnum < 0) { + ERROR("Wrong MTD name in copyfrom: %s", + entry->value); + return -1; + } + free(entry->value); + entry->value = malloc(20+1); /* to hold "/dev/mtdblockXX */ + snprintf(entry->value, 20, "/dev/mtdblock%d", mtdnum); + } +#endif copyfrom = realpath(entry->value, NULL); if (!copyfrom) { ERROR("%s cannot be resolved", entry->value); @@ -273,15 +320,24 @@ recursive = strtobool(dict_get_value(&img->properties, "recursive")); createdest = strtobool(dict_get_value(&img->properties, "create-destination")); - if (createdest) { - ret = mkpath(recursive ? base_img->path : dirname(base_img->path), 0755); + if (!strlen(base_img->path)) { + ERROR("Destination must be created, but no path set"); + return -EINVAL; + } + char *tmppath = strdup(img->path); + if (!tmppath) { + ERROR("OOM creating local image path"); + return -ENOMEM; + } + ret = mkpath(recursive ? tmppath : dirname(tmppath), 0755); if (ret < 0) { - ERROR("I cannot create path %s: %s", - recursive ? base_img->path : dirname(base_img->path), + ERROR("I cannot create path %s: %s", tmppath, strerror(errno)); + free(tmppath); ret = -EFAULT; } + free(tmppath); } if (!ret) { diff -Nru swupdate-2024.12.1+dfsg/handlers/delta_handler.c swupdate-2025.12+dfsg/handlers/delta_handler.c --- swupdate-2024.12.1+dfsg/handlers/delta_handler.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/delta_handler.c 2025-12-03 11:32:03.000000000 +0000 @@ -642,15 +642,13 @@ range_answer_t *answer = priv->answer; answer->data[answer->len] = '\0'; - /* Converto to lower case to make comparison easier */ - string_tolower(answer->data); /* Check for multipart */ nconv = sscanf(answer->data, "%ms %ms %ms", &header, &value, &boundary_string); if (nconv == 3) { - if (!strncmp(header, "content-type", strlen("content-type")) && - !strncmp(boundary_string, "boundary", strlen("boundary"))) { + if (!strncasecmp(header, "content-type", strlen("content-type")) && + !strncasecmp(boundary_string, "boundary", strlen("boundary"))) { pair = string_split(boundary_string, '='); cnt = count_string_array((const char **)pair); if (cnt == 2) { @@ -661,8 +659,8 @@ free_string_array(pair); } - if (!strncmp(header, "content-range", strlen("content-range")) && - !strncmp(value, "bytes", strlen("bytes"))) { + if (!strncasecmp(header, "content-range", strlen("content-range")) && + !strncasecmp(value, "bytes", strlen("bytes"))) { pair = string_split(boundary_string, '-'); priv->range_type = SINGLE_RANGE; size_t start = strtoul(pair[0], NULL, 10); @@ -701,7 +699,7 @@ } s = answer->data; for (i = 0; i < answer->len; i++, s++) { - if (!strncmp(s, priv->boundary, strlen(priv->boundary))) { + if (!strncasecmp(s, priv->boundary, strlen(priv->boundary))) { DEBUG("Boundary found in body"); /* Reset buffer to start from here */ if (i != 0) diff -Nru swupdate-2024.12.1+dfsg/handlers/diskpart_handler.c swupdate-2025.12+dfsg/handlers/diskpart_handler.c --- swupdate-2024.12.1+dfsg/handlers/diskpart_handler.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/diskpart_handler.c 2025-12-03 11:32:03.000000000 +0000 @@ -841,9 +841,11 @@ return 0; } -static int diskpart_write_table(struct fdisk_context *cxt, struct create_table *createtable, bool nolock, bool noinuse) +static int diskpart_write_table(struct fdisk_context *cxt, struct create_table *createtable, + struct diskpart_table *oldtb, bool nolock, bool noinuse) { int ret = 0; + int fdisk_ret = 0; if (createtable->parent || createtable->child) { TRACE("Partitions on disk differ, write to disk;"); @@ -865,7 +867,11 @@ ret = fdisk_write_disklabel(cxt); if (ret) ERROR("Nested partition table cannot be written on disk"); - if (fdisk_reread_partition_table(cxt)) + if (oldtb) + fdisk_ret = fdisk_reread_changes(cxt, oldtb->child); + else + fdisk_ret = fdisk_reread_partition_table(cxt); + if (fdisk_ret) WARN("Nested partition table cannot be reread from the disk, be careful !"); if (ret) return ret; @@ -878,7 +884,11 @@ ret = fdisk_write_disklabel(PARENT(cxt)); if (ret) ERROR("Partition table cannot be written on disk"); - if (fdisk_reread_partition_table(PARENT(cxt))) + if (oldtb) + fdisk_ret = fdisk_reread_changes(cxt, oldtb->parent); + else + fdisk_ret = fdisk_reread_partition_table(cxt); + if (fdisk_ret) WARN("Table cannot be reread from the disk, be careful !"); if (ret) return ret; @@ -1443,7 +1453,7 @@ if (ret) goto handler_exit; - ret = diskpart_write_table(cxt, createtable, priv.nolock, priv.noinuse); + ret = diskpart_write_table(cxt, createtable, oldtb, priv.nolock, priv.noinuse); handler_exit: if (tb) @@ -1708,7 +1718,7 @@ } /* Write table */ - ret = diskpart_write_table(cxt, createtable, priv.nolock, priv.noinuse); + ret = diskpart_write_table(cxt, createtable, (struct diskpart_table *)NULL, priv.nolock, priv.noinuse); if (ret) { ERROR("Can't write table (err = %d)", ret); goto handler_exit; diff -Nru swupdate-2024.12.1+dfsg/handlers/emmc_csd_handler.c swupdate-2025.12+dfsg/handlers/emmc_csd_handler.c --- swupdate-2024.12.1+dfsg/handlers/emmc_csd_handler.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/emmc_csd_handler.c 2025-12-03 11:32:03.000000000 +0000 @@ -43,7 +43,7 @@ */ static int emmc_csd_set(struct img_type *img, void *data, bool toggle) { - int active, ret; + int active = -1, ret; struct script_handler_data *script_data = data; char tmpdev[SWUPDATE_GENERAL_STRING_SIZE]; diff -Nru swupdate-2024.12.1+dfsg/handlers/flash_hamming1_handler.c swupdate-2025.12+dfsg/handlers/flash_hamming1_handler.c --- swupdate-2024.12.1+dfsg/handlers/flash_hamming1_handler.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/flash_hamming1_handler.c 2025-12-03 11:32:03.000000000 +0000 @@ -172,7 +172,7 @@ unsigned char *page; unsigned char code[3]; unsigned char ecc[12]; - int cnt; + int cnt = -1; int i, j; int len; long long imglen = 0; diff -Nru swupdate-2024.12.1+dfsg/handlers/flash_handler.c swupdate-2025.12+dfsg/handlers/flash_handler.c --- swupdate-2024.12.1+dfsg/handlers/flash_handler.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/flash_handler.c 2025-12-03 11:32:03.000000000 +0000 @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,30 @@ #define PROCMTD "/proc/mtd" #define LINESIZE 80 +static inline int mtd_error(int mtdnum, const char *func, int eb) +{ + int code = errno; + ERROR("mtd%d: mtd_%s() failed at block %d: %s (errno = %d)", mtdnum, + func, eb, STRERROR(code), code); + return -code; +} +#define MTD_ERROR(func) mtd_error(priv->mtdnum, (func), priv->eb) + +#define MTD_TRACE(func, msg) do { \ + int _code = errno; \ + TRACE("mtd%d: mtd_%s() %s at block %d: %s (errno = %d)", \ + priv->mtdnum, (func), (msg), priv->eb, \ + STRERROR(_code), _code); \ + } while (0) +#define MTD_TRACE_FAILED(func) MTD_TRACE((func), "failed") +#define MTD_TRACE_NOT_SUPPORTED(func) MTD_TRACE((func), "not supported") + +static inline int too_many_bad_blocks(int mtdnum) +{ + ERROR("mtd%d: too many bad blocks", mtdnum); + return -ENOSPC; +} + void flash_handler(void); /* Check whether buffer is filled with character 'pattern' */ @@ -69,293 +94,443 @@ * The function reassembles nandwrite from mtd-utils * dropping all options that are not required here. */ - static void erase_buffer(void *buffer, size_t size) { - const uint8_t kEraseByte = 0xff; - if (buffer != NULL && size > 0) - memset(buffer, kEraseByte, size); + memset(buffer, FLASH_EMPTY_BYTE, size); } -static int flash_write_nand(int mtdnum, struct img_type *img) -{ - char mtd_device[LINESIZE]; - struct flash_description *flash = get_flash_info(); - struct mtd_dev_info *mtd = &flash->mtd_info[mtdnum].mtd; - int pagelen; - bool baderaseblock = false; - long long imglen = 0; - long long blockstart = -1; - long long offs; - unsigned char *filebuf = NULL; - size_t filebuf_max = 0; - size_t filebuf_len = 0; - long long mtdoffset = img->seek; - int ifd = img->fdin; - int fd = -1; - bool failed = true; - int ret; - unsigned char *writebuf = NULL; - +struct flash_priv { + /* File descriptor of a flash device (/dev/mtdX) to write into: */ + int fdout; + /* Index of a flash device (X in /dev/mtdX): */ + int mtdnum; + /* Number of image bytes we still need to write into flash: */ + long long imglen; + /* A buffer for caching data soon to be written to flash: */ + unsigned char *filebuf; + /* Amount of bytes we've read from image file into filebuf: */ + int filebuf_len; + /* An offset within filebuf to write to flash starting from: */ + int writebuf_offset; + /* Current flash erase block: */ + int eb; /* - * if nothing to do, returns without errors + * The following data is kept here only to speed-up execution: */ - if (!img->size) - return 0; + int eb_end; /* First invalid erase block (after flash memory end). */ + struct mtd_dev_info *mtd; + libmtd_t libmtd; + bool is_nand; /* is it NAND or some other (e.g. NOR) flash type? */ + bool check_bad; /* do we need to check for bad blocks? */ + bool check_locked; /* do we need to check whether a block is locked? */ + bool do_unlock; /* do we need to try to unlock a block? */ + unsigned char *readout_buf; /* a buffer to read erase block into */ +}; - if (mtdoffset & (mtd->min_io_size - 1)) { - ERROR("The start address is not page-aligned !\n" - "The pagesize of this NAND Flash is 0x%x.\n", - mtd->min_io_size); - return -EIO; +static int erase_block(struct flash_priv *priv) +{ + int ret; + ret = mtd_erase(priv->libmtd, priv->mtd, priv->fdout, priv->eb); + if (ret) { + if (is_not_supported(errno)) { + /* Some MTD drivers in linux kernel may not initialize + * mtd->erasesize or master->_erase, hence we expect + * these errors in such cases. */ + MTD_TRACE_NOT_SUPPORTED("erase"); + return 0; + } else if (errno == EIO) { /* May happen for a bad block. */ + MTD_TRACE_FAILED("erase"); + return -EIO; + } else + return MTD_ERROR("erase"); } + return 0; +} - pagelen = mtd->min_io_size; - imglen = img->size; - snprintf(mtd_device, sizeof(mtd_device), "/dev/mtd%d", mtdnum); - - if ((imglen / pagelen) * mtd->min_io_size > mtd->size - mtdoffset) { - ERROR("Image %s does not fit into mtd%d", img->fname, mtdnum); - return -EIO; +static int mark_bad_block(struct flash_priv *priv) +{ + int ret; + TRACE("mtd%d: marking block %d (offset %lld) as bad", priv->mtdnum, + priv->eb, (long long)(priv->eb) * priv->mtd->eb_size); + ret = mtd_mark_bad(priv->mtd, priv->fdout, priv->eb); + if (ret) { + if (is_not_supported(errno)) { + /* Some MTD drivers in linux kernel may not initialize + * master->_block_markbad, hence we expect these errors + * in such cases. */ + MTD_TRACE_NOT_SUPPORTED("mark_bad"); + } else + return MTD_ERROR("mark_bad"); } + return 0; +} - /* Flashing to NAND is currently not streamable */ - if (img->install_directly) { - ERROR("Raw NAND not streamable"); - return -EINVAL; - } +/* + * Read input data from (*p_in_buf) (size = (*p_in_len)), adjust both + * (*p_in_buf) and (*p_in_len) while reading. Write input data to a proper + * offset within priv->filebuf. + * + * On success return 0 and initialize output arguments: + * - (*p_wbuf) (a pointer within priv->filebuf to write data into flash + * starting from). + * - (*p_to_write) (number of bytes from (*p_wbuf) to write into flash). + * If necessary append padding (FLASH_EMPTY_BYTE) to the data to be written. + * (*p_to_write) is guaranteed to be multiple of priv->mtd->min_io_size. + * + * On failure return -1. In this case there is nothing to write to flash, all + * data from (*p_in_buf), has been read and stored in priv->filebuf. + * Need to wait for next flash_write() call to get more data. + */ +static int read_data(struct flash_priv *priv, const unsigned char **p_in_buf, + size_t *p_in_len, unsigned char **p_wbuf, int *p_to_write) +{ + int read_available, to_read, write_available, to_write; + size_t in_len = *p_in_len; - if(flash_erase_sector(mtdnum, img->seek, img->size)) { - ERROR("I cannot erasing %s", - img->device); - return -1; + assert(priv->filebuf_len <= priv->mtd->eb_size); + assert(priv->writebuf_offset <= priv->filebuf_len); + assert((priv->writebuf_offset % priv->mtd->min_io_size) == 0); + assert(priv->imglen >= in_len); + + /* Read as much as possible data from (*p_in_buf) to priv->filebuf: */ + read_available = priv->mtd->eb_size - priv->filebuf_len; + assert(read_available >= 0); + to_read = (in_len > read_available) ? read_available : in_len; + assert(to_read <= read_available); + assert(to_read <= in_len); + memcpy(priv->filebuf + priv->filebuf_len, *p_in_buf, to_read); + *p_in_buf += to_read; + (*p_in_len) -= to_read; + priv->filebuf_len += to_read; + priv->imglen -= to_read; + + if (priv->imglen == 0) { + /* We've read all image data available. + * Add padding to priv->filebuf if necessary: */ + int len, pad_bytes; + len = ROUND_UP(priv->filebuf_len, priv->mtd->min_io_size); + assert(len >= priv->filebuf_len); + assert(len <= priv->mtd->eb_size); + pad_bytes = len - priv->filebuf_len; + assert(pad_bytes >= 0); + erase_buffer(priv->filebuf + priv->filebuf_len, pad_bytes); + priv->filebuf_len = len; + } + + write_available = priv->filebuf_len - priv->writebuf_offset; + assert(write_available >= 0); + + to_write = ROUND_DOWN(write_available, priv->mtd->min_io_size); + assert(to_write <= write_available); + assert((to_write % priv->mtd->min_io_size) == 0); + + if (to_write == 0) { + /* Got not enough data to write. */ + return -1; /* Wait for more data in next flash_write() call. */ + } + + if (priv->is_nand) { + /* For NAND flash limit amount of data to be written to a + * single page. This allows us to skip writing "empty" pages + * (filled with FLASH_EMPTY_BYTE). + * + * Note: for NOR flash typical min_io_size is 1. Writing 1 byte + * at time is not practical. */ + to_write = priv->mtd->min_io_size; + } + + *p_wbuf = priv->filebuf + priv->writebuf_offset; + *p_to_write = to_write; + return 0; /* Need to write some data. */ +} + +/* + * Check and process current erase block. Return: + * - 0 if proper erase block has been found. + * - 1 if current erase block is bad and need to continue the search. + * - Negative errno code on system error (in this case error reporting is + * already done in this function). + */ +static int process_new_erase_block(struct flash_priv *priv) +{ + int ret; + + if (priv->check_bad) { + int is_bad = mtd_is_bad(priv->mtd, priv->fdout, priv->eb); + if (is_bad > 0) + return 1; + if (is_bad < 0) { + if (is_not_supported(errno)) { + /* I don't know whether such cases really + * exist.. Let's handle them just in case (as + * it is currently implemented in mtd-utils and + * in previous version of swupdate). */ + MTD_TRACE_NOT_SUPPORTED("is_bad"); + priv->check_bad = false; + } else + return MTD_ERROR("is_bad"); + } } - if ((fd = open(mtd_device, O_RDWR)) < 0) { - ERROR( "%s: %s: %s", __func__, mtd_device, strerror(errno)); - return -ENODEV; + if (priv->check_locked) { + int is_locked = mtd_is_locked(priv->mtd, priv->fdout, + priv->eb); + if (is_locked < 0) { + if (is_not_supported(errno)) { + /* Some MTD drivers in linux kernel may not + * initialize mtd->_is_locked, hence we expect + * these errors in such cases. At the same time + * the driver can initialize mtd->_unlock, + * hence we shall try to execute mtd_unlock() + * in such cases. */ + MTD_TRACE_NOT_SUPPORTED("is_locked"); + priv->check_locked = false; + priv->do_unlock = true; + } else + return MTD_ERROR("is_locked"); + } else + priv->do_unlock = (bool)is_locked; } - filebuf_max = mtd->eb_size / mtd->min_io_size * pagelen; - filebuf = calloc(1, filebuf_max); - erase_buffer(filebuf, filebuf_max); + if (priv->do_unlock) { + ret = mtd_unlock(priv->mtd, priv->fdout, priv->eb); + if (ret) { + if (is_not_supported(errno)) { + /* Some MTD drivers in linux kernel may not + * initialize mtd->_unlock, hence we expect + * these errors in such cases. */ + MTD_TRACE_NOT_SUPPORTED("unlock"); + priv->check_locked = false; + priv->do_unlock = false; + } else + return MTD_ERROR("unlock"); + } + } /* - * Get data from input and write to the device while there is - * still input to read and we are still within the device - * bounds. Note that in the case of standard input, the input - * length is simply a quasi-boolean flag whose values are page - * length or zero. + * NAND flash should always be erased (follow "write once rule"). + * For other flash types check if the flash is already empty. + * In case of NOR flash, it can save a significant amount of time + * because erasing a NOR flash is very time expensive. */ - while ((imglen > 0 || writebuf < filebuf + filebuf_len) - && mtdoffset < mtd->size) { - /* - * New eraseblock, check for bad block(s) - * Stay in the loop to be sure that, if mtdoffset changes because - * of a bad block, the next block that will be written to - * is also checked. Thus, we avoid errors if the block(s) after the - * skipped block(s) is also bad - */ - while (blockstart != (mtdoffset & (~mtd->eb_size + 1))) { - blockstart = mtdoffset & (~mtd->eb_size + 1); - offs = blockstart; - - /* - * if writebuf == filebuf, we are rewinding so we must - * not reset the buffer but just replay it - */ - if (writebuf != filebuf) { - erase_buffer(filebuf, filebuf_len); - filebuf_len = 0; - writebuf = filebuf; - } - - baderaseblock = false; - - do { - ret = mtd_is_bad(mtd, fd, offs / mtd->eb_size); - if (ret < 0) { - ERROR("mtd%d: MTD get bad block failed", mtdnum); - goto closeall; - } else if (ret == 1) { - baderaseblock = true; - } - - if (baderaseblock) { - mtdoffset = blockstart + mtd->eb_size; - - if (mtdoffset > mtd->size) { - ERROR("too many bad blocks, cannot complete request"); - goto closeall; - } - } - - offs += mtd->eb_size; - } while (offs < blockstart + mtd->eb_size); + if (!priv->is_nand) { + ret = mtd_read(priv->mtd, priv->fdout, priv->eb, 0, + priv->readout_buf, priv->mtd->eb_size); + if (ret) + return MTD_ERROR("read"); + /* Check if already empty: */ + if (buffer_check_pattern(priv->readout_buf, priv->mtd->eb_size, + FLASH_EMPTY_BYTE)) { + return 0; } + } - /* Read more data from the input if there isn't enough in the buffer */ - if (writebuf + mtd->min_io_size > filebuf + filebuf_len) { - size_t readlen = mtd->min_io_size; - size_t alreadyread = (filebuf + filebuf_len) - writebuf; - size_t tinycnt = alreadyread; - ssize_t cnt = 0; - - while (tinycnt < readlen) { - cnt = read(ifd, writebuf + tinycnt, readlen - tinycnt); - if (cnt == 0) { /* EOF */ - break; - } else if (cnt < 0) { - ERROR("File I/O error on input"); - goto closeall; - } - tinycnt += cnt; - } - - /* No padding needed - we are done */ - if (tinycnt == 0) { - imglen = 0; - break; - } + ret = erase_block(priv); + if (ret) { + switch (ret) { + case -EIO: + ret = mark_bad_block(priv); + if (ret) + return ret; + return 1; + default: + return ret; + } + } + return 0; +} - /* Padding */ - if (tinycnt < readlen) { - erase_buffer(writebuf + tinycnt, readlen - tinycnt); - } +/* + * Find a non-bad erase block (starting from priv->eb) we can write data into. + * Unlock the block and erase it if necessary. + * As a result update (if necessary) priv->eb to point to a block we can write + * data into. + */ +static int prepare_new_erase_block(struct flash_priv *priv) +{ + for (; priv->eb < priv->eb_end; priv->eb++) { + int ret = process_new_erase_block(priv); + if (ret < 0) + return ret; + else if (ret == 0) + return 0; + } + return too_many_bad_blocks(priv->mtdnum); +} - filebuf_len += readlen - alreadyread; +/* + * A callback to be passed to copyimage(). + * Write as much input data to flash as possible at this time. + * Skip existing and detect new bad blocks (mark them as bad) while writing. + * Store leftover data (if any) in private context (priv). The leftover data + * will be written into flash during next flash_write() executions. + * During last flash_write() execution all image data should be written to + * flash (the data should be padded with FLASH_EMPTY_BYTE if necessary). + */ +static int flash_write(void *out, const void *buf, size_t len) +{ + int ret; + struct flash_priv *priv = (struct flash_priv *)out; + const unsigned char **pbuf = (const unsigned char**)&buf; - imglen -= tinycnt - alreadyread; + while ((len > 0) || (priv->writebuf_offset < priv->filebuf_len)) { + unsigned char *wbuf; + int to_write; + + assert(priv->eb <= priv->eb_end); + if (priv->eb == priv->eb_end) + return too_many_bad_blocks(priv->mtdnum); + + ret = read_data(priv, pbuf, &len, &wbuf, &to_write); + if (ret < 0) { + /* Wait for more data to be written in next + * flash_write() call. */ + break; + } + /* We've got some data to be written to flash. */ + if (priv->writebuf_offset == 0) { + /* Start of a new erase block. */ + ret = prepare_new_erase_block(priv); + if (ret) + return ret; } + /* Now priv->eb points to a valid erased block we can write + * data into. */ - ret =0; - if (!buffer_check_pattern(writebuf, mtd->min_io_size, 0xff)) { - /* Write out data */ - ret = mtd_write(flash->libmtd, mtd, fd, mtdoffset / mtd->eb_size, - mtdoffset % mtd->eb_size, - writebuf, - mtd->min_io_size, - NULL, - 0, - MTD_OPS_PLACE_OOB); + ret = buffer_check_pattern(wbuf, to_write, FLASH_EMPTY_BYTE); + if (ret) { + ret = 0; /* There is no need to write "empty" bytes. */ + } else { + /* Write data to flash: */ + ret = mtd_write(priv->libmtd, priv->mtd, priv->fdout, + priv->eb, priv->writebuf_offset, wbuf, + to_write, NULL, 0, MTD_OPS_PLACE_OOB); } if (ret) { - long long i; - if (errno != EIO) { - ERROR("mtd%d: MTD write failure", mtdnum); - goto closeall; - } - - /* Must rewind to blockstart if we can */ - writebuf = filebuf; - - for (i = blockstart; i < blockstart + mtd->eb_size; i += mtd->eb_size) { - if (mtd_erase(flash->libmtd, mtd, fd, i / mtd->eb_size)) { - int errno_tmp = errno; - TRACE("mtd%d: MTD Erase failure", mtdnum); - if (errno_tmp != EIO) - goto closeall; - } - } + if (errno != EIO) + return MTD_ERROR("write"); - TRACE("Marking block at %08llx bad", - mtdoffset & (~mtd->eb_size + 1)); - if (mtd_mark_bad(mtd, fd, mtdoffset / mtd->eb_size)) { - ERROR("mtd%d: MTD Mark bad block failure", mtdnum); - goto closeall; + ret = erase_block(priv); + if (ret) { + if (ret != -EIO) + return ret; } - mtdoffset = blockstart + mtd->eb_size; + ret = mark_bad_block(priv); + if (ret) + return ret; + /* Rewind to erase block start. */ + priv->writebuf_offset = 0; + priv->eb++; continue; } - /* - * this handler does not use copyfile() - * and must update itself the progress bar - */ - swupdate_progress_update((img->size - imglen) * 100 / img->size); - - mtdoffset += mtd->min_io_size; - writebuf += pagelen; - } - failed = false; - -closeall: - free(filebuf); - close(fd); - - if (failed) { - ERROR("Installing image %s into mtd%d failed", - img->fname, - mtdnum); - return -1; + priv->writebuf_offset += to_write; + assert(priv->writebuf_offset <= priv->filebuf_len); + assert(priv->filebuf_len <= priv->mtd->eb_size); + if (priv->writebuf_offset == priv->mtd->eb_size) { + priv->eb++; + priv->writebuf_offset = 0; + priv->filebuf_len = 0; + } } return 0; } -static int flash_write_nor(int mtdnum, struct img_type *img) +static int flash_write_image(int mtdnum, struct img_type *img) { - int fdout; + struct flash_priv priv; char mtd_device[LINESIZE]; int ret; struct flash_description *flash = get_flash_info(); + priv.mtd = &flash->mtd_info[mtdnum].mtd; + assert((priv.mtd->eb_size % priv.mtd->min_io_size) == 0); - if (!mtd_dev_present(flash->libmtd, mtdnum)) { + if (!mtd_dev_present(flash->libmtd, mtdnum)) { ERROR("MTD %d does not exist", mtdnum); return -ENODEV; } - long long size = get_output_size(img, true); - if (size < 0) { - size = get_mtd_size(mtdnum); - if (size < 0) { + if (img->seek & (priv.mtd->eb_size - 1)) { + ERROR("The start address is not erase-block-aligned!\n" + "The erase block of this flash is 0x%x.\n", + priv.mtd->eb_size); + return -EINVAL; + } + + priv.imglen = get_output_size(img, true); + if (priv.imglen < 0) { + WARN("Failed to determine output size, getting MTD size."); + priv.imglen = get_mtd_size(mtdnum); + if (priv.imglen < 0) { ERROR("Could not get MTD %d device size", mtdnum); return -ENODEV; } - - WARN("decompression-size not set, erasing flash device %s from %lld to %lld", - img->device, img->seek, size); } - if (flash_erase_sector(mtdnum, img->seek, size)) { - ERROR("Failed to erase sectors on /dev/mtd%d (start: %llu, size: %lld)", - mtdnum, img->seek, size); - return -1; + + if (!priv.imglen) + return 0; + + if (priv.imglen > priv.mtd->size - img->seek) { + ERROR("Image %s does not fit into mtd%d", img->fname, mtdnum); + return -ENOSPC; } snprintf(mtd_device, sizeof(mtd_device), "/dev/mtd%d", mtdnum); - if ((fdout = open(mtd_device, O_RDWR)) < 0) { - ERROR( "%s: %s: %s", __func__, mtd_device, strerror(errno)); - return -1; + priv.fdout = open(mtd_device, O_RDWR); + if (priv.fdout < 0) { + ret = errno; + ERROR( "%s: %s: %s", __func__, mtd_device, STRERROR(ret)); + return -ret; + } + + priv.mtdnum = mtdnum; + priv.filebuf_len = 0; + priv.writebuf_offset = 0; + priv.eb = (int)(img->seek / priv.mtd->eb_size); + priv.eb_end = (int)(priv.mtd->size / priv.mtd->eb_size); + priv.libmtd = flash->libmtd; + priv.is_nand = isNand(flash, mtdnum); + priv.check_bad = true; + priv.check_locked = true; + + ret = priv.mtd->eb_size; + if (!priv.is_nand) + ret += priv.mtd->eb_size; + priv.filebuf = malloc(ret); + if (!priv.filebuf) { + ERROR("Failed to allocate %d bytes of memory", ret); + ret = -ENOMEM; + goto end; + } + if (!priv.is_nand) + priv.readout_buf = priv.filebuf + priv.mtd->eb_size; + + ret = copyimage(&priv, img, flash_write); + free(priv.filebuf); + +end: + if (close(priv.fdout)) { + if (!ret) + ret = -errno; + ERROR("close() failed: %s", STRERROR(errno)); } - ret = copyimage(&fdout, img, NULL); - close(fdout); - /* tell 'nbytes == 0' (EOF) from 'nbytes < 0' (read error) */ if (ret < 0) { ERROR("Failure installing into: %s", img->device); - return -1; + return ret; } return 0; } -static int flash_write_image(int mtdnum, struct img_type *img) -{ - struct flash_description *flash = get_flash_info(); - - if (!isNand(flash, mtdnum)) - return flash_write_nor(mtdnum, img); - else - return flash_write_nand(mtdnum, img); -} - static int install_flash_image(struct img_type *img, void __attribute__ ((__unused__)) *data) { - int mtdnum; + int ret, mtdnum; if (strlen(img->mtdname)) mtdnum = get_mtd_from_name(img->mtdname); @@ -364,15 +539,16 @@ if (mtdnum < 0) { ERROR("Wrong MTD device in description: %s", strlen(img->mtdname) ? img->mtdname : img->device); - return -1; + return -EINVAL; } TRACE("Copying %s into /dev/mtd%d", img->fname, mtdnum); - if (flash_write_image(mtdnum, img)) { + ret = flash_write_image(mtdnum, img); + if (ret) { ERROR("I cannot copy %s into %s partition", img->fname, img->device); - return -1; + return ret; } return 0; diff -Nru swupdate-2024.12.1+dfsg/handlers/raw_handler.c swupdate-2025.12+dfsg/handlers/raw_handler.c --- swupdate-2024.12.1+dfsg/handlers/raw_handler.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/raw_handler.c 2025-12-03 11:32:03.000000000 +0000 @@ -9,19 +9,11 @@ #include #include #include -#include -#ifdef __FreeBSD__ -#include -// the ioctls are almost identical except for the name, just alias it -#define BLKGETSIZE64 DIOCGMEDIASIZE -#else -#include -#endif - -#include -#include #include #include +#if defined(__linux__) +#include +#endif #include "swupdate_image.h" #include "handler.h" diff -Nru swupdate-2024.12.1+dfsg/handlers/remote_handler.c swupdate-2025.12+dfsg/handlers/remote_handler.c --- swupdate-2024.12.1+dfsg/handlers/remote_handler.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/remote_handler.c 2025-12-03 11:32:03.000000000 +0000 @@ -197,6 +197,7 @@ ret = copyimage(request, img, forward_data); cleanup: + free(connect_string); zmq_close(request); zmq_ctx_destroy(context); diff -Nru swupdate-2024.12.1+dfsg/handlers/swupdate.lua swupdate-2025.12+dfsg/handlers/swupdate.lua --- swupdate-2024.12.1+dfsg/handlers/swupdate.lua 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/handlers/swupdate.lua 2025-12-03 11:32:03.000000000 +0000 @@ -63,8 +63,9 @@ swupdate.debug = function(format, ...) end --- Lua equivalent of `notify(PROGRESS, ..., msg)`. ---- @param msg string Message to send to progress interface -swupdate.progress = function(msg) end +--- @param msg string Message to send to progress interface +--- @param cause number | nil `progress_cause_t` value as defined in `include/progress_ipc.h` +swupdate.progress = function(msg, cause) end --- Lua equivalent of `notify(status, error, INFOLEVEL, msg)`. --- @param status swupdate.RECOVERY_STATUS Current status, one of `swupdate.RECOVERY_STATUS`'s values @@ -73,6 +74,12 @@ swupdate.notify = function(status, error, msg) end +--- Report whether dry-run installation is performed. +-- +--- @return boolean # `true` if dry-run installation is performed, `false` otherwise +swupdate.is_dryrun = function() end + + --- Update progress. -- --- @param percent number Progress percent to set @@ -83,13 +90,13 @@ -- --- @param device string Device to mount --- @param filesystem string Device's filesystem ---- @return string # Mountpoint for use with `swupdate.umount(target)` +--- @return string | nil # Mountpoint for use with `swupdate.umount(target)`, `nil` on error swupdate.mount = function(device, filesystem) end ---- Unmount a mountpoint. +--- Unmount a mountpoint returned by `swupdate.mount()`. -- --- @param target string Mountpoint to unmount ---- @return boolean | nil # true if successful, nil on error +--- @return boolean | nil # `true` if successful, `nil` on error swupdate.umount = function(target) end @@ -114,6 +121,12 @@ --- @return swupdate.rootdev # Table containing type, and (full) path to root device swupdate.getroot = function() end +--- Get the HW boot device of an eMMC. +-- +--- @param devicepath string Fully qualified device path +--- @return integer # eMMC boot device number or `-1` on error +swupdate.emmcbootpart = function(devicepath) end + --- Table with major/minor numbers of the device on which the swupdate.stat()'d file resides. --- @class swupdate.stat_dev @@ -185,6 +198,10 @@ --- @return string # Mode swupdate.get_selection = function() end +--- Get Linux Command Line Parameters +-- +--- @return table # Table with keys and values fields +swupdate.get_cmdline = function() end --- Set Bootloader environment key=value. -- diff -Nru swupdate-2024.12.1+dfsg/include/channel_curl.h swupdate-2025.12+dfsg/include/channel_curl.h --- swupdate-2024.12.1+dfsg/include/channel_curl.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/channel_curl.h 2025-12-03 11:32:03.000000000 +0000 @@ -86,7 +86,7 @@ int read_fifo; size_t (*headers)(char *streamdata, size_t size, size_t nmemb, void *data); - struct swupdate_digest *dgst; + void *dgst; char sha1hash[SWUPDATE_SHA_DIGEST_LENGTH * 2 + 1]; sourcetype source; struct dict *headers_to_send; @@ -95,4 +95,6 @@ size_t upload_filesize; char *range; /* Range request for get_file in any */ void *user; + char *api_key_header; + char *api_key; } channel_data_t; diff -Nru swupdate-2024.12.1+dfsg/include/flash.h swupdate-2025.12+dfsg/include/flash.h --- swupdate-2024.12.1+dfsg/include/flash.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/flash.h 2025-12-03 11:32:03.000000000 +0000 @@ -14,6 +14,7 @@ #include "bsdqueue.h" #define DEFAULT_CTRL_DEV "/dev/ubi_ctrl" +#define FLASH_EMPTY_BYTE 0xFF struct ubi_part { struct ubi_vol_info vol_info; diff -Nru swupdate-2024.12.1+dfsg/include/globals.h swupdate-2025.12+dfsg/include/globals.h --- swupdate-2024.12.1+dfsg/include/globals.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/globals.h 2025-12-03 11:32:03.000000000 +0000 @@ -7,7 +7,17 @@ #pragma once -#define BANNER "SWUpdate v" SWU_VER "\n" +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +#if defined(VERSION_SUBLEVEL) && VERSION_SUBLEVEL > 0 +#define SUBLEVEL_STR "." STR(VERSION_SUBLEVEL) +#else +#define SUBLEVEL_STR "" +#endif + +#define BANNER "SWUpdate v" \ + STR(VERSION) "." STR(VERSION_PATCHLEVEL) SUBLEVEL_STR STR(VERSION_EXTRAVERSION) #define SWUPDATE_GENERAL_STRING_SIZE 256 #define SWUPDATE_UPDATE_DESCRIPTION_STRING_SIZE 512 diff -Nru swupdate-2024.12.1+dfsg/include/installer.h swupdate-2025.12+dfsg/include/installer.h --- swupdate-2024.12.1+dfsg/include/installer.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/installer.h 2025-12-03 11:32:03.000000000 +0000 @@ -23,3 +23,5 @@ int preupdatecmd(struct swupdate_cfg *swcfg); int run_prepost_scripts(struct imglist *list, script_fn type); void cleanup_files(struct swupdate_cfg *software); +int update_installed_image_version(struct swver *sw_ver_list, + struct img_type *img); diff -Nru swupdate-2024.12.1+dfsg/include/network_ipc.h swupdate-2025.12+dfsg/include/network_ipc.h --- swupdate-2024.12.1+dfsg/include/network_ipc.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/network_ipc.h 2025-12-03 11:32:03.000000000 +0000 @@ -116,6 +116,7 @@ char minimum_version[256]; char maximum_version[256]; char current_version[256]; + char update_type[256]; } versions; struct { char boardname[256]; @@ -159,6 +160,10 @@ int swupdate_set_version_range(const char *minversion, const char *maxversion, const char *currentversion); +int swupdate_set_version_range_type(const char *updatetype, + const char *minversion, + const char *maxversion, + const char *currentversion); #ifdef __cplusplus } // extern "C" #endif diff -Nru swupdate-2024.12.1+dfsg/include/parselib-private.h swupdate-2025.12+dfsg/include/parselib-private.h --- swupdate-2024.12.1+dfsg/include/parselib-private.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/parselib-private.h 2025-12-03 11:32:03.000000000 +0000 @@ -11,8 +11,9 @@ #include #include -#ifdef CONFIG_LIBCONFIG bool is_field_numeric_cfg(config_setting_t *e, const char *path); +bool is_field_bool_cfg(config_setting_t *e, const char *path); +bool is_field_string_cfg(config_setting_t *e, const char *path); void get_field_cfg(config_setting_t *e, const char *path, void *dest, field_type_t type); void *get_child_libconfig(void *e, const char *name); void iterate_field_libconfig(config_setting_t *e, iterate_callback cb, @@ -21,28 +22,12 @@ void *find_root_libconfig(config_t *cfg, const char **nodes, unsigned int depth); void *get_node_libconfig(config_t *cfg, const char **nodes); -#else -#define config_setting_get_elem(a,b) (NULL) -#define config_setting_length(a) (0) -#define config_setting_lookup_string(a, b, str) (0) -#define config_setting_type(path) (0) -#define find_node_libconfig(cfg, field, swcfg) (NULL) -#define get_field_string_libconfig(e, path) (NULL) -#define get_child_libconfig(e, name) (NULL) -#define iterate_field_libconfig(e, cb, data) { } -#define get_field_cfg(e, path, dest, type) -#define find_root_libconfig(cfg, nodes, depth) (NULL) -#define get_node_libconfig(cfg, nodes) (NULL) -#define is_field_numeric_cfg(e, path) (false) -#define CONFIG_TYPE_GROUP 1 -#define CONFIG_TYPE_ARRAY 7 -#define CONFIG_TYPE_LIST 8 -#endif - /* * JSON implementation for parselib */ bool is_field_numeric_json(json_object *e, const char *path); +bool is_field_bool_json(json_object *e, const char *path); +bool is_field_string_json(json_object *e, const char *path); const char *get_field_string_json(json_object *e, const char *path); void get_field_json(json_object *e, const char *path, void *dest, field_type_t type); void *get_child_json(json_object *e, const char *name); diff -Nru swupdate-2024.12.1+dfsg/include/parselib.h swupdate-2025.12+dfsg/include/parselib.h --- swupdate-2024.12.1+dfsg/include/parselib.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/parselib.h 2025-12-03 11:32:03.000000000 +0000 @@ -10,14 +10,12 @@ #include #include #include -#ifdef CONFIG_LIBCONFIG #include #define LIBCONFIG_VERSION ((LIBCONFIG_VER_MAJOR << 16) | \ (LIBCONFIG_VER_MINOR << 8) | LIBCONFIG_VER_REVISION) #if LIBCONFIG_VERSION < 0x10500 #define config_setting_lookup config_lookup_from #endif -#endif typedef enum { LIBCFG_PARSER, @@ -50,6 +48,8 @@ * Parselib interface */ bool is_field_numeric(parsertype p, void *e, const char *path); +bool is_field_bool(parsertype p, void *e, const char *path); +bool is_field_string(parsertype p, void *e, const char *path); const char *get_field_string(parsertype p, void *e, const char *path); void get_field_string_with_size(parsertype p, void *e, const char *path, char *d, size_t n); @@ -63,7 +63,7 @@ void check_field_string(const char *src, char *dst, const size_t max_len); void *find_root(parsertype p, void *root, const char **nodes); void *get_node(parsertype p, void *root, const char **nodes); -bool set_find_path(const char **nodes, const char *newpath, char ***tmp); +bool set_find_path(const char **nodes, const char *newpath); static inline void get_field_bool(parsertype p, void *e, const char *path, bool *dest) { diff -Nru swupdate-2024.12.1+dfsg/include/progress_ipc.h swupdate-2025.12+dfsg/include/progress_ipc.h --- swupdate-2024.12.1+dfsg/include/progress_ipc.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/progress_ipc.h 2025-12-03 11:32:03.000000000 +0000 @@ -8,6 +8,7 @@ #pragma once #include +#include #include #ifdef __cplusplus @@ -19,6 +20,7 @@ typedef enum progress_cause { CAUSE_NONE, CAUSE_REBOOT_MODE, + CAUSE_DRY_RUN_MODE, } progress_cause_t; extern char* SOCKET_PROGRESS_PATH; @@ -51,23 +53,23 @@ * Data is sent in LE if required. */ struct progress_msg { - unsigned int apiversion; /* API Version for compatibility check */ - RECOVERY_STATUS status; /* Update Status (Running, Failure) */ - unsigned int dwl_percent; /* % downloaded data */ + uint32_t apiversion; /* API Version for compatibility check */ + uint32_t status; /* Update Status (Running, Failure) */ + uint32_t dwl_percent; /* % downloaded data */ unsigned long long dwl_bytes; /* total of bytes to be downloaded */ - unsigned int nsteps; /* No. total of steps */ - unsigned int cur_step; /* Current step index */ - unsigned int cur_percent; /* % in current step */ + uint32_t nsteps; /* No. total of steps */ + uint32_t cur_step; /* Current step index */ + uint32_t cur_percent; /* % in current step */ char cur_image[256]; /* Name of image to be installed */ char hnd_name[64]; /* Name of running handler */ - sourcetype source; /* Interface that triggered the update */ - unsigned int infolen; /* Len of data valid in info */ + uint32_t source; /* Interface that triggered the update */ + uint32_t infolen; /* Len of data valid in info */ char info[PRINFOSIZE]; /* additional information about install */ -}; +} __attribute__ ((__packed__));; #define PROGRESS_CONNECT_ACK_MAGIC "ACK" struct progress_connect_ack { - unsigned int apiversion; /* API Version for compatibility check */ + uint32_t apiversion; /* API Version for compatibility check */ char magic[4]; /* null-terminated string */ }; diff -Nru swupdate-2024.12.1+dfsg/include/sslapi.h swupdate-2025.12+dfsg/include/sslapi.h --- swupdate-2024.12.1+dfsg/include/sslapi.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/sslapi.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,231 +0,0 @@ -/* - * (C) Copyright 2016 - * Stefano Babic, stefano.babic@swupdate.org. - * - * SPDX-License-Identifier: GPL-2.0-only - */ - -#pragma once - -#include -#include "util.h" - -#define SHA_DEFAULT "sha256" - -/* - * openSSL is not mandatory - * Let compile when openSSL is not activated - */ -#if defined(CONFIG_HASH_VERIFY) || defined(CONFIG_ENCRYPTED_IMAGES) - -#ifdef CONFIG_PKCS11 -#include -#include -#include -#include -// Exclude p11-kit's pkcs11.h to prevent conflicting with wolfssl's -#define PKCS11_H 1 -#include -#endif - -#ifdef CONFIG_SSL_IMPL_OPENSSL -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#elif defined(CONFIG_SSL_IMPL_WOLFSSL) -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#endif - -#if defined(CONFIG_SSL_IMPL_OPENSSL) || defined(CONFIG_SSL_IMPL_WOLFSSL) - -#ifdef CONFIG_SIGALG_CMS - -static inline uint32_t SSL_X509_get_extension_flags(X509 *x) -{ -#if OPENSSL_VERSION_NUMBER < 0x10100000L - return x->ex_flags; -#else - return X509_get_extension_flags(x); -#endif -} - -static inline uint32_t SSL_X509_get_extended_key_usage(X509 *x) -{ -#if OPENSSL_VERSION_NUMBER < 0x10100000L - return x->ex_xkusage; -#else - return X509_get_extended_key_usage(x); -#endif -} - -#endif /* CONFIG_SIGALG_CMS */ - -#ifdef CONFIG_SSL_IMPL_WOLFSSL -#define EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, len) (1) - -#define X509_PURPOSE_CODE_SIGN EXTKEYUSE_CODESIGN -#define SSL_PURPOSE_EMAIL_PROT EXTKEYUSE_EMAILPROT -#else -#if !defined(X509_PURPOSE_CODE_SIGN) -#define X509_PURPOSE_CODE_SIGN (X509_PURPOSE_MAX + 1) -#endif -#define SSL_PURPOSE_EMAIL_PROT X509_PURPOSE_SMIME_SIGN -#endif -#define SSL_PURPOSE_CODE_SIGN X509_PURPOSE_CODE_SIGN -#define SSL_PURPOSE_DEFAULT SSL_PURPOSE_EMAIL_PROT - -struct swupdate_digest { - EVP_PKEY *pkey; /* this is used for RSA key */ - EVP_PKEY_CTX *ckey; /* this is used for RSA key */ - X509_STORE *certs; /* this is used if CMS is set */ - EVP_MD_CTX *ctx; -#ifdef CONFIG_PKCS11 - unsigned char last_decr[AES_BLOCK_SIZE + 1]; - P11KitUri *p11uri; - Aes ctxdec; - Pkcs11Dev pkdev; - Pkcs11Token pktoken; -#elif OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_CIPHER_CTX ctxdec; -#else - EVP_CIPHER_CTX *ctxdec; -#endif -#ifdef CONFIG_SIGALG_GPG - char *gpg_home_directory; - bool verbose; - char *gpgme_protocol; -#endif -}; - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define SSL_GET_CTXDEC(dgst) &dgst->ctxdec -#else -#define SSL_GET_CTXDEC(dgst) dgst->ctxdec -#endif - -/* - * This just initialize globally the openSSL - * library - * It must be called just once - */ -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define swupdate_crypto_init() { \ - do { \ - CRYPTO_malloc_init(); \ - OpenSSL_add_all_algorithms(); \ - ERR_load_crypto_strings(); \ - } while (0); \ -} -#else -#define swupdate_crypto_init() -#endif - -#elif defined(CONFIG_SSL_IMPL_MBEDTLS) -#include -#include -#include - -#define EVP_MAX_BLOCK_LENGTH (16) -#define swupdate_crypto_init() - -struct swupdate_digest { -#ifdef CONFIG_HASH_VERIFY - mbedtls_md_context_t mbedtls_md_context; -#endif /* CONFIG_HASH_VERIFY */ -#ifdef CONFIG_SIGNED_IMAGES - mbedtls_pk_context mbedtls_pk_context; -#endif /* CONFIG_SIGNED_IMAGES */ -#ifdef CONFIG_PKCS11 - unsigned char last_decr[AES_BLOCK_SIZE + 1]; - P11KitUri *p11uri; - Aes ctxdec; - Pkcs11Dev pkdev; - Pkcs11Token pktoken; -#elif defined(CONFIG_ENCRYPTED_IMAGES) - mbedtls_cipher_context_t mbedtls_cipher_context; -#endif /* CONFIG_PKCS11 */ -#ifdef CONFIG_SIGALG_GPG - char *gpg_home_directory; - int verbose; - char *gpgme_protocol; -#endif -}; - -#else /* CONFIG_SSL_IMPL */ -#error unknown SSL implementation -#endif /* CONFIG_SSL_IMPL */ -#else -#define swupdate_crypto_init() -#endif - -#if defined(CONFIG_HASH_VERIFY) -struct swupdate_cfg; - -int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile); -struct swupdate_digest *swupdate_HASH_init(const char *SHALength); -int swupdate_HASH_update(struct swupdate_digest *dgst, const unsigned char *buf, - size_t len); -int swupdate_HASH_final(struct swupdate_digest *dgst, unsigned char *md_value, - unsigned int *md_len); -void swupdate_HASH_cleanup(struct swupdate_digest *dgst); -int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, - const char *file, const char *signer_name); -int swupdate_HASH_compare(const unsigned char *hash1, const unsigned char *hash2); - - -#else -#define swupdate_dgst_init(sw, keyfile) ( 0 ) -#define swupdate_HASH_init(p) ( NULL ) -#define swupdate_verify_file(dgst, sigfile, file) ( 0 ) -#define swupdate_HASH_update(p, buf, len) (-1) -#define swupdate_HASH_final(p, result, len) (-1) -#define swupdate_HASH_cleanup(sw) -#define swupdate_HASH_compare(hash1,hash2) (0) -#endif - -#ifdef CONFIG_ENCRYPTED_IMAGES -struct swupdate_digest *swupdate_DECRYPT_init(unsigned char *key, char keylen, unsigned char *iv); -int swupdate_DECRYPT_update(struct swupdate_digest *dgst, unsigned char *buf, - int *outlen, const unsigned char *cryptbuf, int inlen); -int swupdate_DECRYPT_final(struct swupdate_digest *dgst, unsigned char *buf, - int *outlen); -void swupdate_DECRYPT_cleanup(struct swupdate_digest *dgst); -#else -UNUSED static inline struct swupdate_digest *swupdate_DECRYPT_init( - unsigned char UNUSED *key, - char UNUSED keylen, - unsigned char UNUSED *iv) -{ - ERROR("SWUpdate was built without support for encrypted images"); - return NULL; -} -#define swupdate_DECRYPT_update(p, buf, len, cbuf, inlen) (-1) -#define swupdate_DECRYPT_final(p, buf, len) (-1) -#define swupdate_DECRYPT_cleanup(p) -#endif - -#ifndef SSL_PURPOSE_DEFAULT -#define SSL_PURPOSE_EMAIL_PROT -1 -#define SSL_PURPOSE_CODE_SIGN -1 -#define SSL_PURPOSE_DEFAULT -1 -#endif diff -Nru swupdate-2024.12.1+dfsg/include/swupdate.h swupdate-2025.12+dfsg/include/swupdate.h --- swupdate-2024.12.1+dfsg/include/swupdate.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/swupdate.h 2025-12-03 11:32:03.000000000 +0000 @@ -27,6 +27,15 @@ INSTALL_FROM_STREAM } swupdate_file_t; +/* + * Type of reboot after an update + */ +typedef enum { + REBOOT_UNSET, + REBOOT_ENABLED, + REBOOT_DISABLED +} swupdate_reboot_t; + struct extproc { char name[SWUPDATE_GENERAL_STRING_SIZE]; char exec[SWUPDATE_GENERAL_STRING_SIZE]; @@ -36,44 +45,60 @@ LIST_HEAD(proclist, extproc); +/* + * This is used for per type configuration + * if no type is set, the type "default" (always set) + * is taken. + */ +struct swupdate_type_cfg { + char type_name[SWUPDATE_GENERAL_STRING_SIZE]; + char minimum_version[SWUPDATE_GENERAL_STRING_SIZE]; + char maximum_version[SWUPDATE_GENERAL_STRING_SIZE]; + char current_version[SWUPDATE_GENERAL_STRING_SIZE]; + char postupdatecmd[SWUPDATE_GENERAL_STRING_SIZE]; + char preupdatecmd[SWUPDATE_GENERAL_STRING_SIZE]; + bool no_downgrading; + bool no_reinstalling; + bool check_max_version; + swupdate_reboot_t reboot_enabled; + LIST_ENTRY(swupdate_type_cfg) next; +}; +LIST_HEAD(swupdate_type_list, swupdate_type_cfg); + struct swupdate_parms { bool dry_run; char software_set[SWUPDATE_GENERAL_STRING_SIZE]; char running_mode[SWUPDATE_GENERAL_STRING_SIZE]; + struct swupdate_type_cfg *type; }; struct swupdate_cfg { char name[SWUPDATE_GENERAL_STRING_SIZE]; char description[SWUPDATE_UPDATE_DESCRIPTION_STRING_SIZE]; + char update_type_name[SWUPDATE_GENERAL_STRING_SIZE]; char version[SWUPDATE_GENERAL_STRING_SIZE]; bool bootloader_transaction_marker; bool bootloader_state_marker; + bool update_type_required; char output[SWUPDATE_GENERAL_STRING_SIZE]; char output_swversions[SWUPDATE_GENERAL_STRING_SIZE]; char publickeyfname[SWUPDATE_GENERAL_STRING_SIZE]; char aeskeyfname[SWUPDATE_GENERAL_STRING_SIZE]; - char postupdatecmd[SWUPDATE_GENERAL_STRING_SIZE]; - char preupdatecmd[SWUPDATE_GENERAL_STRING_SIZE]; - char minimum_version[SWUPDATE_GENERAL_STRING_SIZE]; - char maximum_version[SWUPDATE_GENERAL_STRING_SIZE]; - char current_version[SWUPDATE_GENERAL_STRING_SIZE]; char mtdblacklist[SWUPDATE_GENERAL_STRING_SIZE]; char forced_signer_name[SWUPDATE_GENERAL_STRING_SIZE]; char namespace_for_vars[SWUPDATE_GENERAL_STRING_SIZE]; void *lua_state; bool syslog_enabled; - bool no_downgrading; - bool no_reinstalling; - bool no_transaction_marker; - bool no_state_marker; - bool reboot_required; - bool check_max_version; bool verbose; int loglevel; int cert_purpose; + bool no_transaction_marker; + bool no_state_marker; + swupdate_reboot_t reboot_enabled; struct hw_type hw; struct hwlist hardware; struct swver installed_sw_list; + struct swupdate_type_list swupdate_types; struct imglist images; struct imglist scripts; struct dict bootloader; @@ -82,11 +107,20 @@ struct proclist extprocs; void *dgst; /* Structure for signed images */ struct swupdate_parms parms; + struct swupdate_type_cfg *update_type; const char *embscript; char gpg_home_directory[SWUPDATE_GENERAL_STRING_SIZE]; char gpgme_protocol[SWUPDATE_GENERAL_STRING_SIZE]; int swdesc_max_size; + /* + * Select which provider is used in case of multiple + * crypto libraries + */ + char hash_provider[SWUPDATE_GENERAL_STRING_SIZE]; + char decrypt_provider[SWUPDATE_GENERAL_STRING_SIZE]; + char digest_provider[SWUPDATE_GENERAL_STRING_SIZE]; }; struct swupdate_cfg *get_swupdate_cfg(void); void free_image(struct img_type *img); +struct swupdate_type_cfg *swupdate_find_update_type(struct swupdate_type_list *list, const char *name); diff -Nru swupdate-2024.12.1+dfsg/include/swupdate_aes.h swupdate-2025.12+dfsg/include/swupdate_aes.h --- swupdate-2024.12.1+dfsg/include/swupdate_aes.h 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/include/swupdate_aes.h 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2025 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#pragma once +#include +#include + +/* + * Add global definitions to set the cipher. + * Each implementation must map the generic enum + * to the library specific code + */ + +typedef enum { + AES_CBC, + AES_CBC_128, + AES_CBC_192, + AES_CBC_256, + AES_CTR, + AES_CTR_128, + AES_CTR_192, + AES_CTR_256, + CMS, + AES_UNKNOWN +} cipher_t; + +typedef struct { + cipher_t cipher; + const char *ciphername; +} map_cipher_t; + +static map_cipher_t map_cipher[] = { + {AES_CBC, "aes-cbc"}, + {AES_CBC_128, "aes-cbc-128"}, + {AES_CBC_192, "aes-cbc-192"}, + {AES_CBC_256, "aes-cbc-256"}, + {AES_CBC_128, "aes-cbc-128"}, + {AES_CTR_128, "aes-ctr-128"}, + {AES_CTR_192, "aes-ctr-192"}, + {AES_CTR_256, "aes-ctr-256"}, + {AES_CTR_128, "aes-ctr-128"} +}; + +static inline cipher_t map_name_cipher (const char *name) { + for (int count = 0; count < sizeof(map_cipher)/sizeof(map_cipher_t); count++) { + if (!strcmp(map_cipher[count].ciphername, name)) + return map_cipher[count].cipher; + } + return AES_UNKNOWN; +} diff -Nru swupdate-2024.12.1+dfsg/include/swupdate_crypto.h swupdate-2025.12+dfsg/include/swupdate_crypto.h --- swupdate-2024.12.1+dfsg/include/swupdate_crypto.h 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/include/swupdate_crypto.h 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,125 @@ +/* + * (C) Copyright 2024 + * Stefano Babic, stefano.babic@swupdate.org. + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#pragma once +#include +#include + +#define SHA_DEFAULT "sha256" + +/* + * This just initialize globally the openSSL + * library + * It must be called just once + */ +#if defined (OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L +#define swupdate_crypto_init() { \ + do { \ + CRYPTO_malloc_init(); \ + OpenSSL_add_all_algorithms(); \ + ERR_load_crypto_strings(); \ + } while (0); \ +} +#else +#define swupdate_crypto_init() +#endif + +struct swupdate_cfg; + +typedef enum { + CERT_PURPOSE_EMAIL_PROT, + CERT_PURPOSE_CODE_SIGN, + CERT_PURPOSE_LAST = CERT_PURPOSE_CODE_SIGN +} ssl_cert_purpose_t; + +typedef struct { + void *(*DECRYPT_init)(unsigned char *key, char keylen, unsigned char *iv, cipher_t cipher); + int (*DECRYPT_update)(void *ctx, unsigned char *buf, + int *outlen, const unsigned char *cryptbuf, int inlen); + + int (*DECRYPT_final)(void *ctx, unsigned char *buf, int *outlen); + void (*DECRYPT_cleanup)(void *ctx); +} swupdate_decrypt_lib; + +typedef struct { + void *(*HASH_init)(const char *SHAlength); + int (*HASH_update)(void *ctx, const unsigned char *buf, size_t len); + int (*HASH_final)(void *ctx, unsigned char *md_value, unsigned int *md_len); + int (*HASH_compare)(const unsigned char *hash1, const unsigned char *hash2); + void (*HASH_cleanup)(void *ctx); +} swupdate_HASH_lib; + +typedef struct { + int (*dgst_init)(struct swupdate_cfg *sw, const char *keyfile); + int (*verify_file)(void *ctx, const char *sigfile, const char *file, const char *signer_name); +} swupdate_dgst_lib; + +/* + * register_cryptolib - register a crypto engine / library + * + * @name : cryptolib's name to register. + * @swupdate_crypto_lib : structure with crypto engine functions + * + * Return: + * 0 on success, -ENOMEM on error. + */ + +int register_cryptolib(const char *name, swupdate_decrypt_lib *lib); +int register_hashlib(const char *name, swupdate_HASH_lib *lib); +int register_dgstlib(const char *name, swupdate_dgst_lib *lib); + +/* + * set_cryptolib - set current crypto library + * + * @name : cryptolib's name to register. + * + * Return: + * 0 on success, -ENOENT on error. + */ +int set_cryptolib(const char *name); +int set_HASHlib(const char *name); +int set_dgstlib(const char *name); + +/* + * get_cryptolib - return name of current cryptolib + * + * + * Return: + * 0 on success, NULL on error. + */ +const char* get_cryptolib(void); +const char* get_HASHlib(void); +const char* get_dgstlib(void); + +/* + * print_registered_cryptolib - list supported crypto libraries + * + * + * Return: + */ +void print_registered_cryptolib(void); + +struct swupdate_cfg; + +int swupdate_dgst_init(struct swupdate_cfg *sw, const char *keyfile); +void *swupdate_HASH_init(const char *SHALength); +int swupdate_HASH_update(void *ctx, const unsigned char *buf, + size_t len); +int swupdate_HASH_final(void *ctx, unsigned char *md_value, + unsigned int *md_len); +void swupdate_HASH_cleanup(void *ctx); +int swupdate_verify_file(void *ctx, const char *sigfile, + const char *file, const char *signer_name); +int swupdate_HASH_compare(const unsigned char *hash1, const unsigned char *hash2); + +void *swupdate_DECRYPT_init(unsigned char *key, char keylen, unsigned char *iv, cipher_t cipher); +int swupdate_DECRYPT_update(void *ctx, unsigned char *buf, + int *outlen, const unsigned char *cryptbuf, int inlen); +int swupdate_DECRYPT_final(void *ctx, unsigned char *buf, + int *outlen); +void swupdate_DECRYPT_cleanup(void *ctx); diff -Nru swupdate-2024.12.1+dfsg/include/swupdate_image.h swupdate-2025.12+dfsg/include/swupdate_image.h --- swupdate-2024.12.1+dfsg/include/swupdate_image.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/swupdate_image.h 2025-12-03 11:32:03.000000000 +0000 @@ -13,6 +13,7 @@ #include "globals.h" #include "swupdate_dict.h" #include "lua_util.h" +#include "swupdate_aes.h" typedef enum { FLASH, @@ -56,7 +57,9 @@ int compressed; bool preserve_attributes; /* whether to preserve attributes in archives */ bool is_encrypted; + cipher_t cipher; char ivt_ascii[33]; + char aes_ascii[65]; /* AES_256_KEY_LEN*2+1 */ bool install_directly; int is_script; int is_partitioner; diff -Nru swupdate-2024.12.1+dfsg/include/swupdate_settings.h swupdate-2025.12+dfsg/include/swupdate_settings.h --- swupdate-2024.12.1+dfsg/include/swupdate_settings.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/swupdate_settings.h 2025-12-03 11:32:03.000000000 +0000 @@ -11,8 +11,6 @@ typedef int (*settings_callback)(void *elem, void *data); -#ifdef CONFIG_LIBCONFIG - #include typedef struct { @@ -26,45 +24,3 @@ int read_module_settings(swupdate_cfg_handle *handle, const char *module, settings_callback fcn, void *data); int read_settings_user_id(swupdate_cfg_handle *handle, const char *module, uid_t *userid, gid_t *groupid); int settings_into_dict(void *settings, void *data); -#else - -typedef struct {} swupdate_cfg_handle; - -static inline void swupdate_cfg_init(swupdate_cfg_handle __attribute__ ((__unused__))*handle) { } - -static inline int swupdate_cfg_read_file(swupdate_cfg_handle __attribute__ ((__unused__))*handle, - const char __attribute__ ((__unused__))*filename) -{ - return -1; -} - -static inline void swupdate_cfg_destroy(swupdate_cfg_handle __attribute__ ((__unused__))*handle) { - return; -} - -static inline int read_module_settings(swupdate_cfg_handle __attribute__ ((__unused__))*handle, - const char __attribute__ ((__unused__))*module, - settings_callback __attribute__ ((__unused__))fcn, - void __attribute__ ((__unused__))*data) { - return -1; -}; - -/* - * Without LIBCONFIG, let run with current user - */ -static inline int read_settings_user_id(swupdate_cfg_handle __attribute__ ((__unused__))*handle, - const char __attribute__ ((__unused__))*module, - uid_t *userid, gid_t *groupid) -{ - *userid = getuid(); - *groupid = getgid(); - - return 0; -} - -static inline int settings_into_dict(void __attribute__ ((__unused__)) *settings, - void __attribute__ ((__unused__))*data) -{ - return -1; -} -#endif diff -Nru swupdate-2024.12.1+dfsg/include/util.h swupdate-2025.12+dfsg/include/util.h --- swupdate-2024.12.1+dfsg/include/util.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/include/util.h 2025-12-03 11:32:03.000000000 +0000 @@ -13,6 +13,7 @@ #include #include #include +#include #include #if defined(__linux__) #include @@ -22,6 +23,7 @@ #include "swupdate_status.h" #include "swupdate_dict.h" #include "compat.h" +#include "swupdate_aes.h" #define NOTIFY_BUF_SIZE 2048 #define ENOMEM_ASPRINTF -1 @@ -60,6 +62,7 @@ COMPRESSED_FALSE, COMPRESSED_TRUE, COMPRESSED_ZLIB, + COMPRESSED_XZ, COMPRESSED_ZSTD, }; @@ -90,6 +93,8 @@ /* encryption */ bool encrypted; const char *imgivt; + const char *imgaes; + cipher_t cipher; }; /* @@ -211,6 +216,33 @@ #define max_t(type,x,y) \ ({ type __x = (x); type __y = (y); __x > __y ? __x: __y; }) +#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S)) +#define ROUND_DOWN(N, S) (((N) / (S)) * (S)) + +/* According to linux kernel ENOTSUPP is not a standard error code and should + * be avoided in new patches. EOPNOTSUPP should be used instead. Unfortunately + * some drivers still return ENOTSUPP. Do not confuse with ENOTSUP! See also: + * https://lists.gnu.org/archive/html/bug-glibc/2002-08/msg00017.html + * https://linux-fsdevel.vger.kernel.narkive.com/pERNbmWG/kernel-glibc-eopnotsupp-vs-enotsup-vs-enotsupp-also-rfc-posix-acl-kernel-infrastructure + */ +#ifndef ENOTSUPP +#define ENOTSUPP 524 /* Operation is not supported */ +UNUSED static inline const char *strerror_enotsupp(int code) +{ + if (code == ENOTSUPP) + return "Operation is not supported"; + return strerror(code); +} +#define STRERROR(code) strerror_enotsupp(code) +#else +#define STRERROR(code) strerror(code) +#endif + +UNUSED static inline bool is_not_supported(int code) +{ + return (code == EOPNOTSUPP) || (code == ENOTSUP) || (code == ENOTSUPP); +} + bool strtobool(const char *s); /* @@ -260,20 +292,25 @@ /* Decryption key functions */ int load_decryption_key(char *fname); -unsigned char *get_aes_key(void); -char get_aes_keylen(void); +char *swupdate_get_decrypt_key(void); +char swupdate_get_decrypt_keylen(void); unsigned char *get_aes_ivt(void); int set_aes_key(const char *key, const char *ivt); +int set_filename_as_key(const char *fname, cipher_t cipher); +cipher_t swupdate_get_decrypt_cipher(void); /* Getting global information */ int get_install_info(char *buf, size_t len); sourcetype get_install_source(void); void get_install_swset(char *buf, size_t len); void get_install_running_mode(char *buf, size_t len); +bool is_dryrun_install(void); char *get_root_device(void); +char **parse_linux_cmdline(void); /* Setting global information */ -void set_version_range(const char *minversion, +int set_version_range(const char *updatetype, + const char *minversion, const char *maxversion, const char *current); @@ -299,3 +336,12 @@ /* eMMC functions */ int emmc_write_bootpart(int fd, int bootpart); int emmc_get_active_bootpart(int fd); + +/* used to compare if an attribute is set to enabled or disabled */ + +static inline bool is_enabled_or_disabled(const char *s) { + return (!(strcmp(s, "enabled") && strcmp(s, "disabled"))); +} +static inline bool is_enabled(const char *s) { + return (!strcmp(s, "enabled")); +} diff -Nru swupdate-2024.12.1+dfsg/ipc/network_ipc-if.c swupdate-2025.12+dfsg/ipc/network_ipc-if.c --- swupdate-2024.12.1+dfsg/ipc/network_ipc-if.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/ipc/network_ipc-if.c 2025-12-03 11:32:03.000000000 +0000 @@ -12,6 +12,7 @@ #include #include #include +#include #include "network_ipc.h" #include "progress_ipc.h" @@ -356,7 +357,8 @@ * Set via IPC the range of accepted versions * Versions are string and they can use semver */ -int swupdate_set_version_range(const char *minversion, +int swupdate_set_version_range_type(const char *updatetype, + const char *minversion, const char *maxversion, const char *currentversion) { @@ -384,9 +386,25 @@ sizeof(msg.data.versions.maximum_version) - 1); } + if (updatetype) { + strncpy(msg.data.versions.update_type, + updatetype, + sizeof(msg.data.versions.update_type) - 1); + } return ipc_send_cmd(&msg); } +int swupdate_set_version_range(const char *minversion, + const char *maxversion, + const char *currentversion) + +{ + return swupdate_set_version_range_type(minversion, + maxversion, + currentversion, + NULL); +} + void swupdate_prepare_req(struct swupdate_request *req) { if (!req) return; diff -Nru swupdate-2024.12.1+dfsg/ipc/network_ipc.c swupdate-2025.12+dfsg/ipc/network_ipc.c --- swupdate-2024.12.1+dfsg/ipc/network_ipc.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/ipc/network_ipc.c 2025-12-03 11:32:03.000000000 +0000 @@ -315,7 +315,7 @@ ssize_t len = size; while (len) { - ret = write(connfd, buf, (size_t)size); + ret = write(connfd, buf, (size_t)len); if (ret < 0) return ret; len -= ret; diff -Nru swupdate-2024.12.1+dfsg/ipc/progress_ipc.c swupdate-2025.12+dfsg/ipc/progress_ipc.c --- swupdate-2024.12.1+dfsg/ipc/progress_ipc.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/ipc/progress_ipc.c 2025-12-03 11:32:03.000000000 +0000 @@ -16,6 +16,10 @@ #include #include #include +#if defined(__FreeBSD__) +#define ENODATA ENOATTR +#define ETIME ETIMEDOUT +#endif #ifdef CONFIG_SOCKET_PROGRESS_PATH char *SOCKET_PROGRESS_PATH = (char*)CONFIG_SOCKET_PROGRESS_PATH; diff -Nru swupdate-2024.12.1+dfsg/mongoose/Kconfig swupdate-2025.12+dfsg/mongoose/Kconfig --- swupdate-2024.12.1+dfsg/mongoose/Kconfig 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/mongoose/Kconfig 2025-12-03 11:32:03.000000000 +0000 @@ -4,7 +4,6 @@ menuconfig WEBSERVER bool "Web Server" - select MONGOOSE help Enable update from remote using a web server on the target. diff -Nru swupdate-2024.12.1+dfsg/mongoose/Makefile swupdate-2025.12+dfsg/mongoose/Makefile --- swupdate-2024.12.1+dfsg/mongoose/Makefile 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/mongoose/Makefile 2025-12-03 11:32:03.000000000 +0000 @@ -16,12 +16,10 @@ ifneq ($(CONFIG_MONGOOSESSL),) ifeq ($(CONFIG_SSL_IMPL_OPENSSL),y) KBUILD_CFLAGS += -DMG_TLS=2 -endif -ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) -KBUILD_CFLAGS += -DMG_TLS=5 -endif -ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y) +else ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y) KBUILD_CFLAGS += -DMG_TLS=1 +else ifeq ($(CONFIG_SSL_IMPL_WOLFSSL),y) +KBUILD_CFLAGS += -DMG_TLS=5 endif endif endif diff -Nru swupdate-2024.12.1+dfsg/mongoose/mongoose.c swupdate-2025.12+dfsg/mongoose/mongoose.c --- swupdate-2024.12.1+dfsg/mongoose/mongoose.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/mongoose/mongoose.c 2025-12-03 11:32:03.000000000 +0000 @@ -1,5 +1,5 @@ // Copyright (c) 2004-2013 Sergey Lyubka -// Copyright (c) 2013-2024 Cesanta Software Limited +// Copyright (c) 2013-2025 Cesanta Software Limited // All rights reserved // // This software is dual-licensed: you can redistribute it and/or modify @@ -117,947 +117,6 @@ } #ifdef MG_ENABLE_LINES -#line 1 "src/device_ch32v307.c" -#endif - - - -#if MG_DEVICE == MG_DEVICE_CH32V307 -// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html - -#define FLASH_BASE 0x40022000 -#define FLASH_ACTLR (FLASH_BASE + 0) -#define FLASH_KEYR (FLASH_BASE + 4) -#define FLASH_OBKEYR (FLASH_BASE + 8) -#define FLASH_STATR (FLASH_BASE + 12) -#define FLASH_CTLR (FLASH_BASE + 16) -#define FLASH_ADDR (FLASH_BASE + 20) -#define FLASH_OBR (FLASH_BASE + 28) -#define FLASH_WPR (FLASH_BASE + 32) - -void *mg_flash_start(void) { - return (void *) 0x08000000; -} -size_t mg_flash_size(void) { - return 480 * 1024; // First 320k is 0-wait -} -size_t mg_flash_sector_size(void) { - return 4096; -} -size_t mg_flash_write_align(void) { - return 4; -} -int mg_flash_bank(void) { - return 0; -} -void mg_device_reset(void) { - *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() -} -static void flash_unlock(void) { - static bool unlocked; - if (unlocked == false) { - MG_REG(FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_KEYR) = 0xcdef89ab; - unlocked = true; - } -} -static void flash_wait(void) { - while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; -} - -bool mg_flash_erase(void *addr) { - //MG_INFO(("%p", addr)); - flash_unlock(); - flash_wait(); - MG_REG(FLASH_ADDR) = (uint32_t) addr; - MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT; - flash_wait(); - return true; -} - -static bool is_page_boundary(const void *addr) { - uint32_t val = (uint32_t) addr; - return (val & (mg_flash_sector_size() - 1)) == 0; -} - -bool mg_flash_write(void *addr, const void *buf, size_t len) { - //MG_INFO(("%p %p %lu", addr, buf, len)); - //mg_hexdump(buf, len); - flash_unlock(); - const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2]; - uint16_t *dst = (uint16_t *) addr; - MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG - //MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR))); - while (src < end) { - if (is_page_boundary(dst)) mg_flash_erase(dst); - *dst++ = *src++; - flash_wait(); - } - MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG - return true; -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_dummy.c" -#endif - - -#if MG_DEVICE == MG_DEVICE_NONE -void *mg_flash_start(void) { - return NULL; -} -size_t mg_flash_size(void) { - return 0; -} -size_t mg_flash_sector_size(void) { - return 0; -} -size_t mg_flash_write_align(void) { - return 0; -} -int mg_flash_bank(void) { - return 0; -} -bool mg_flash_erase(void *location) { - (void) location; - return false; -} -bool mg_flash_swap_bank(void) { - return true; -} -bool mg_flash_write(void *addr, const void *buf, size_t len) { - (void) addr, (void) buf, (void) len; - return false; -} -void mg_device_reset(void) { -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_flash.c" -#endif - - -#if MG_DEVICE == MG_DEVICE_STM32H7 || MG_DEVICE == MG_DEVICE_STM32H5 || \ - MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 -// Flash can be written only if it is erased. Erased flash is 0xff (all bits 1) -// Writes must be mg_flash_write_align() - aligned. Thus if we want to save an -// object, we pad it at the end for alignment. -// -// Objects in the flash sector are stored sequentially: -// | 32-bit size | 32-bit KEY | ..data.. | ..pad.. | 32-bit size | ...... -// -// In order to get to the next object, read its size, then align up. - -// Traverse the list of saved objects -size_t mg_flash_next(char *p, char *end, uint32_t *key, size_t *size) { - size_t aligned_size = 0, align = mg_flash_write_align(), left = end - p; - uint32_t *p32 = (uint32_t *) p, min_size = sizeof(uint32_t) * 2; - if (p32[0] != 0xffffffff && left > MG_ROUND_UP(min_size, align)) { - if (size) *size = (size_t) p32[0]; - if (key) *key = p32[1]; - aligned_size = MG_ROUND_UP(p32[0] + sizeof(uint32_t) * 2, align); - if (left < aligned_size) aligned_size = 0; // Out of bounds, fail - } - return aligned_size; -} - -// Return the last sector of Bank 2 -static char *flash_last_sector(void) { - size_t ss = mg_flash_sector_size(), size = mg_flash_size(); - char *base = (char *) mg_flash_start(), *last = base + size - ss; - if (mg_flash_bank() == 2) last -= size / 2; - return last; -} - -// Find a saved object with a given key -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { - char *base = (char *) mg_flash_start(), *s = (char *) sector, *res = NULL; - size_t ss = mg_flash_sector_size(), ofs = 0, n, sz; - bool ok = false; - if (s == NULL) s = flash_last_sector(); - if (s < base || s >= base + mg_flash_size()) { - MG_ERROR(("%p is outsize of flash", sector)); - } else if (((s - base) % ss) != 0) { - MG_ERROR(("%p is not a sector boundary", sector)); - } else { - uint32_t k, scanned = 0; - while ((n = mg_flash_next(s + ofs, s + ss, &k, &sz)) > 0) { - // MG_DEBUG((" > obj %lu, ofs %lu, key %x/%x", scanned, ofs, k, key)); - // mg_hexdump(s + ofs, n); - if (k == key && sz == len) { - res = s + ofs + sizeof(uint32_t) * 2; - memcpy(buf, res, len); // Copy object - ok = true; // Keep scanning for the newer versions of it - } - ofs += n, scanned++; - } - MG_DEBUG(("Scanned %u objects, key %x is @ %p", scanned, key, res)); - } - return ok; -} - -// For all saved objects in the sector, delete old versions of objects -static void mg_flash_sector_cleanup(char *sector) { - // Buffer all saved objects into an IO buffer (backed by RAM) - // erase sector, and re-save them. - struct mg_iobuf io = {0, 0, 0, 2048}; - size_t ss = mg_flash_sector_size(); - size_t n, size, size2, ofs = 0, hs = sizeof(uint32_t) * 2; - uint32_t key; - // Traverse all objects - MG_DEBUG(("Cleaning up sector %p", sector)); - while ((n = mg_flash_next(sector + ofs, sector + ss, &key, &size)) > 0) { - // Delete an old copy of this object in the cache - for (size_t o = 0; o < io.len; o += size2 + hs) { - uint32_t k = *(uint32_t *) (io.buf + o + sizeof(uint32_t)); - size2 = *(uint32_t *) (io.buf + o); - if (k == key) { - mg_iobuf_del(&io, o, size2 + hs); - break; - } - } - // And add the new copy - mg_iobuf_add(&io, io.len, sector + ofs, size + hs); - ofs += n; - } - // All objects are cached in RAM now - if (mg_flash_erase(sector)) { // Erase sector. If successful, - for (ofs = 0; ofs < io.len; ofs += size + hs) { // Traverse cached objects - size = *(uint32_t *) (io.buf + ofs); - key = *(uint32_t *) (io.buf + ofs + sizeof(uint32_t)); - mg_flash_save(sector, key, io.buf + ofs + hs, size); // Save to flash - } - } - mg_iobuf_free(&io); -} - -// Save an object with a given key - append to the end of an object list -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { - char *base = (char *) mg_flash_start(), *s = (char *) sector; - size_t ss = mg_flash_sector_size(), ofs = 0, n; - bool ok = false; - if (s == NULL) s = flash_last_sector(); - if (s < base || s >= base + mg_flash_size()) { - MG_ERROR(("%p is outsize of flash", sector)); - } else if (((s - base) % ss) != 0) { - MG_ERROR(("%p is not a sector boundary", sector)); - } else { - char ab[mg_flash_write_align()]; // Aligned write block - uint32_t hdr[2] = {(uint32_t) len, key}; - size_t needed = sizeof(hdr) + len; - size_t needed_aligned = MG_ROUND_UP(needed, sizeof(ab)); - while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; - - // If there is not enough space left, cleanup sector and re-eval ofs - if (ofs + needed_aligned >= ss) { - mg_flash_sector_cleanup(s); - ofs = 0; - while ((n = mg_flash_next(s + ofs, s + ss, NULL, NULL)) > 0) ofs += n; - } - - if (ofs + needed_aligned <= ss) { - // Enough space to save this object - if (sizeof(ab) < sizeof(hdr)) { - // Flash write granularity is 32 bit or less, write with no buffering - ok = mg_flash_write(s + ofs, hdr, sizeof(hdr)); - if (ok) mg_flash_write(s + ofs + sizeof(hdr), buf, len); - } else { - // Flash granularity is sizeof(hdr) or more. We need to save in - // 3 chunks: initial block, bulk, rest. This is because we have - // two memory chunks to write: hdr and buf, on aligned boundaries. - n = sizeof(ab) - sizeof(hdr); // Initial chunk that we write - if (n > len) n = len; // is - memset(ab, 0xff, sizeof(ab)); // initialized to all-one - memcpy(ab, hdr, sizeof(hdr)); // contains the header (key + size) - memcpy(ab + sizeof(hdr), buf, n); // and an initial part of buf - MG_INFO(("saving initial block of %lu", sizeof(ab))); - ok = mg_flash_write(s + ofs, ab, sizeof(ab)); - if (ok && len > n) { - size_t n2 = MG_ROUND_DOWN(len - n, sizeof(ab)); - if (n2 > 0) { - MG_INFO(("saving bulk, %lu", n2)); - ok = mg_flash_write(s + ofs + sizeof(ab), (char *) buf + n, n2); - } - if (ok && len > n) { - size_t n3 = len - n - n2; - if (n3 > sizeof(ab)) n3 = sizeof(ab); - memset(ab, 0xff, sizeof(ab)); - memcpy(ab, (char *) buf + n + n2, n3); - MG_INFO(("saving rest, %lu", n3)); - ok = mg_flash_write(s + ofs + sizeof(ab) + n2, ab, sizeof(ab)); - } - } - } - MG_DEBUG(("Saved %lu/%lu bytes @ %p, key %x: %d", len, needed_aligned, - s + ofs, key, ok)); - MG_DEBUG(("Sector space left: %lu bytes", ss - ofs - needed_aligned)); - } else { - MG_ERROR(("Sector is full")); - } - } - return ok; -} -#else -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len) { - (void) sector, (void) key, (void) buf, (void) len; - return false; -} -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len) { - (void) sector, (void) key, (void) buf, (void) len; - return false; -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_imxrt.c" -#endif - - - -#if MG_DEVICE == MG_DEVICE_RT1020 || MG_DEVICE == MG_DEVICE_RT1060 - -struct mg_flexspi_lut_seq { - uint8_t seqNum; - uint8_t seqId; - uint16_t reserved; -}; - -struct mg_flexspi_mem_config { - uint32_t tag; - uint32_t version; - uint32_t reserved0; - uint8_t readSampleClkSrc; - uint8_t csHoldTime; - uint8_t csSetupTime; - uint8_t columnAddressWidth; - uint8_t deviceModeCfgEnable; - uint8_t deviceModeType; - uint16_t waitTimeCfgCommands; - struct mg_flexspi_lut_seq deviceModeSeq; - uint32_t deviceModeArg; - uint8_t configCmdEnable; - uint8_t configModeType[3]; - struct mg_flexspi_lut_seq configCmdSeqs[3]; - uint32_t reserved1; - uint32_t configCmdArgs[3]; - uint32_t reserved2; - uint32_t controllerMiscOption; - uint8_t deviceType; - uint8_t sflashPadType; - uint8_t serialClkFreq; - uint8_t lutCustomSeqEnable; - uint32_t reserved3[2]; - uint32_t sflashA1Size; - uint32_t sflashA2Size; - uint32_t sflashB1Size; - uint32_t sflashB2Size; - uint32_t csPadSettingOverride; - uint32_t sclkPadSettingOverride; - uint32_t dataPadSettingOverride; - uint32_t dqsPadSettingOverride; - uint32_t timeoutInMs; - uint32_t commandInterval; - uint16_t dataValidTime[2]; - uint16_t busyOffset; - uint16_t busyBitPolarity; - uint32_t lookupTable[64]; - struct mg_flexspi_lut_seq lutCustomSeq[12]; - uint32_t reserved4[4]; -}; - -struct mg_flexspi_nor_config { - struct mg_flexspi_mem_config memConfig; - uint32_t pageSize; - uint32_t sectorSize; - uint8_t ipcmdSerialClkFreq; - uint8_t isUniformBlockSize; - uint8_t reserved0[2]; - uint8_t serialNorType; - uint8_t needExitNoCmdMode; - uint8_t halfClkForNonReadCmd; - uint8_t needRestoreNoCmdMode; - uint32_t blockSize; - uint32_t reserve2[11]; -}; - -/* FLEXSPI memory config block related defintions */ -#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian -#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 - -#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ - (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | MG_FLEXSPI_LUT_OPCODE0(cmd0) | \ - MG_FLEXSPI_LUT_OPERAND1(op1) | MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1)) - -#define MG_CMD_SDR 0x01 -#define MG_CMD_DDR 0x21 -#define MG_DUMMY_SDR 0x0C -#define MG_DUMMY_DDR 0x2C -#define MG_RADDR_SDR 0x02 -#define MG_RADDR_DDR 0x22 -#define MG_READ_SDR 0x09 -#define MG_READ_DDR 0x29 -#define MG_WRITE_SDR 0x08 -#define MG_WRITE_DDR 0x28 -#define MG_STOP 0 - -#define MG_FLEXSPI_1PAD 0 -#define MG_FLEXSPI_2PAD 1 -#define MG_FLEXSPI_4PAD 2 -#define MG_FLEXSPI_8PAD 3 - -#define MG_FLEXSPI_QSPI_LUT \ - { \ - [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, MG_FLEXSPI_4PAD, \ - 0x18), \ - [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, MG_FLEXSPI_4PAD, \ - 0x04), \ - [4 * 1 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \ - [4 * 3 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, MG_RADDR_SDR, \ - MG_FLEXSPI_1PAD, 0x18), \ - [4 * 9 + 1] = \ - MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - [4 * 11 + 0] = \ - MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ - } - -#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 0xFFU) -#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U) -#define MG_FLEXSPI_LUT_OPCODE0(x) (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U) -#define MG_FLEXSPI_LUT_OPERAND1(x) (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U) -#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U) -#define MG_FLEXSPI_LUT_OPCODE1(x) (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U) - -#define FLEXSPI_NOR_INSTANCE 0 - -#if MG_DEVICE == MG_DEVICE_RT1020 -struct mg_flexspi_nor_driver_interface { - uint32_t version; - int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, - const uint32_t *src); - uint32_t reserved; - int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, - uint32_t lengthInBytes); - uint32_t reserved2; - int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, - uint32_t seqNumber); - int (*xfer)(uint32_t instance, char *xfer); - void (*clear_cache)(uint32_t instance); -}; -#elif MG_DEVICE == MG_DEVICE_RT1060 -struct mg_flexspi_nor_driver_interface { - uint32_t version; - int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t dst_addr, - const uint32_t *src); - int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config); - int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t start, - uint32_t lengthInBytes); - int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *dst, uint32_t addr, - uint32_t lengthInBytes); - void (*clear_cache)(uint32_t instance); - int (*xfer)(uint32_t instance, char *xfer); - int (*update_lut)(uint32_t instance, uint32_t seqIndex, const uint32_t *lutBase, - uint32_t seqNumber); - int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, uint32_t *option); -}; -#endif - -#define flexspi_nor (*((struct mg_flexspi_nor_driver_interface**) \ - (*(uint32_t*)0x0020001c + 16))) - -static bool s_flash_irq_disabled; - -MG_IRAM void *mg_flash_start(void) { - return (void *) 0x60000000; -} -MG_IRAM size_t mg_flash_size(void) { - return 8 * 1024 * 1024; -} -MG_IRAM size_t mg_flash_sector_size(void) { - return 4 * 1024; // 4k -} -MG_IRAM size_t mg_flash_write_align(void) { - return 256; -} -MG_IRAM int mg_flash_bank(void) { - return 0; -} - -MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); - volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; -} - -// Note: the get_config function below works both for RT1020 and 1060 -#if MG_DEVICE == MG_DEVICE_RT1020 -MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { - struct mg_flexspi_nor_config default_config = { - .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG, - .version = MG_FLEXSPI_CFG_BLK_VERSION, - .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad - .csHoldTime = 3, - .csSetupTime = 3, - .controllerMiscOption = MG_BIT(4), - .deviceType = 1, // serial NOR - .sflashPadType = 4, - .serialClkFreq = 7, // 133MHz - .sflashA1Size = 8 * 1024 * 1024, - .lookupTable = MG_FLEXSPI_QSPI_LUT}, - .pageSize = 256, - .sectorSize = 4 * 1024, - .ipcmdSerialClkFreq = 1, - .blockSize = 64 * 1024, - .isUniformBlockSize = false}; - - *config = default_config; - return 0; -} -#else -MG_IRAM static int flexspi_nor_get_config(struct mg_flexspi_nor_config *config) { - uint32_t options[] = {0xc0000000, 0x00}; - - MG_ARM_DISABLE_IRQ(); - uint32_t status = - flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, config, options); - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); - } - if (status) { - MG_ERROR(("Failed to extract flash configuration: status %u", status)); - } - return status; -} -#endif - -MG_IRAM bool mg_flash_erase(void *addr) { - struct mg_flexspi_nor_config config; - if (flexspi_nor_get_config(&config) != 0) { - return false; - } - if (flash_page_start(addr) == false) { - MG_ERROR(("%p is not on a sector boundary", addr)); - return false; - } - - void *dst = (void *)((char *) addr - (char *) mg_flash_start()); - - // Note: Interrupts must be disabled before any call to the ROM API on RT1020 - // and 1060 - MG_ARM_DISABLE_IRQ(); - bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, &config, (uint32_t) dst, - mg_flash_sector_size()) == 0); - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); // Reenable them after the call - } - MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); - return ok; -} - -MG_IRAM bool mg_flash_swap_bank(void) { - return true; -} - -static inline void spin(volatile uint32_t count) { - while (count--) (void) 0; -} - -static inline void flash_wait(void) { - while ((*((volatile uint32_t *)(0x402A8000 + 0xE0)) & MG_BIT(1)) == 0) - spin(1); -} - -MG_IRAM static void *flash_code_location(void) { - return (void *) ((char *) mg_flash_start() + 0x2000); -} - -MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { - struct mg_flexspi_nor_config config; - if (flexspi_nor_get_config(&config) != 0) { - return false; - } - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; - } - - if ((char *) addr < (char *) mg_flash_start()) { - MG_ERROR(("Invalid flash write address: %p", addr)); - return false; - } - - uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - - // Note: If we overwrite the flash irq section of the image, we must also - // make sure interrupts are disabled and are not reenabled until we write - // this sector with another irq table. - if ((char *) addr == (char *) flash_code_location()) { - s_flash_irq_disabled = true; - MG_ARM_DISABLE_IRQ(); - } - - while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) { - break; - } - uint32_t status; - uint32_t dst_ofs = (uint32_t) dst - (uint32_t) mg_flash_start(); - if ((char *) buf >= (char *) mg_flash_start()) { - // If we copy from FLASH to FLASH, then we first need to copy the source - // to RAM - size_t tmp_buf_size = mg_flash_write_align() / sizeof(uint32_t); - uint32_t tmp[tmp_buf_size]; - - for (size_t i = 0; i < tmp_buf_size; i++) { - flash_wait(); - tmp[i] = src[i]; - } - MG_ARM_DISABLE_IRQ(); - status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, - (uint32_t) dst_ofs, tmp); - } else { - MG_ARM_DISABLE_IRQ(); - status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, &config, - (uint32_t) dst_ofs, src); - } - if (!s_flash_irq_disabled) { - MG_ARM_ENABLE_IRQ(); - } - src = (uint32_t *) ((char *) src + mg_flash_write_align()); - dst = (uint32_t *) ((char *) dst + mg_flash_write_align()); - if (status != 0) { - ok = false; - } - } - MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); - return ok; -} - -MG_IRAM void mg_device_reset(void) { - MG_DEBUG(("Resetting device...")); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; -} - -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_stm32h5.c" -#endif - - - -#if MG_DEVICE == MG_DEVICE_STM32H5 - -#define FLASH_BASE 0x40022000 // Base address of the flash controller -#define FLASH_KEYR (FLASH_BASE + 0x4) // See RM0481 7.11 -#define FLASH_OPTKEYR (FLASH_BASE + 0xc) -#define FLASH_OPTCR (FLASH_BASE + 0x1c) -#define FLASH_NSSR (FLASH_BASE + 0x20) -#define FLASH_NSCR (FLASH_BASE + 0x28) -#define FLASH_NSCCR (FLASH_BASE + 0x30) -#define FLASH_OPTSR_CUR (FLASH_BASE + 0x50) -#define FLASH_OPTSR_PRG (FLASH_BASE + 0x54) - -void *mg_flash_start(void) { - return (void *) 0x08000000; -} -size_t mg_flash_size(void) { - return 2 * 1024 * 1024; // 2Mb -} -size_t mg_flash_sector_size(void) { - return 8 * 1024; // 8k -} -size_t mg_flash_write_align(void) { - return 16; // 128 bit -} -int mg_flash_bank(void) { - return MG_REG(FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; -} - -static void flash_unlock(void) { - static bool unlocked = false; - if (unlocked == false) { - MG_REG(FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_KEYR) = 0Xcdef89ab; - MG_REG(FLASH_OPTKEYR) = 0x08192a3b; - MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f; - unlocked = true; - } -} - -static int flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); - volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; -} - -static bool flash_is_err(void) { - return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9 -} - -static void flash_wait(void) { - while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) && - (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) { - (void) 0; - } -} - -static void flash_clear_err(void) { - flash_wait(); // Wait until ready - MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all errors -} - -static bool flash_bank_is_swapped(void) { - return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 -} - -bool mg_flash_erase(void *location) { - bool ok = false; - if (flash_page_start(location) == false) { - MG_ERROR(("%p is not on a sector boundary")); - } else { - uintptr_t diff = (char *) location - (char *) mg_flash_start(); - uint32_t sector = diff / mg_flash_sector_size(); - uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value - flash_unlock(); - flash_clear_err(); - MG_REG(FLASH_NSCR) = 0; - if ((sector < 128 && flash_bank_is_swapped()) || - (sector > 127 && !flash_bank_is_swapped())) { - MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL - } - if (sector > 127) sector -= 128; - MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase | sector_num - MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing - flash_wait(); - ok = !flash_is_err(); - MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, - ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); - // mg_hexdump(location, 32); - MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR - } - return ok; -} - -bool mg_flash_swap_bank(void) { - uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); - flash_unlock(); - flash_clear_err(); - // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); - MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired); - // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); - MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART - while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; - return true; -} - -bool mg_flash_write(void *addr, const void *buf, size_t len) { - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; - } - uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - flash_unlock(); - flash_clear_err(); - MG_ARM_DISABLE_IRQ(); - // MG_DEBUG(("Starting flash write %lu bytes @ %p", len, addr)); - MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag - while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; - *(volatile uint32_t *) dst++ = *src++; - flash_wait(); - if (flash_is_err()) ok = false; - } - MG_ARM_ENABLE_IRQ(); - MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, - flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), - MG_REG(FLASH_NSSR))); - MG_REG(FLASH_NSCR) = 0; // Clear flags - return ok; -} - -void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; -} -#endif - -#ifdef MG_ENABLE_LINES -#line 1 "src/device_stm32h7.c" -#endif - - - -#if MG_DEVICE == MG_DEVICE_STM32H7 - -#define FLASH_BASE1 0x52002000 // Base address for bank1 -#define FLASH_BASE2 0x52002100 // Base address for bank2 -#define FLASH_KEYR 0x04 // See RM0433 4.9.2 -#define FLASH_OPTKEYR 0x08 -#define FLASH_OPTCR 0x18 -#define FLASH_SR 0x10 -#define FLASH_CR 0x0c -#define FLASH_CCR 0x14 -#define FLASH_OPTSR_CUR 0x1c -#define FLASH_OPTSR_PRG 0x20 -#define FLASH_SIZE_REG 0x1ff1e880 - -MG_IRAM void *mg_flash_start(void) { - return (void *) 0x08000000; -} -MG_IRAM size_t mg_flash_size(void) { - return MG_REG(FLASH_SIZE_REG) * 1024; -} -MG_IRAM size_t mg_flash_sector_size(void) { - return 128 * 1024; // 128k -} -MG_IRAM size_t mg_flash_write_align(void) { - return 32; // 256 bit -} -MG_IRAM int mg_flash_bank(void) { - if (mg_flash_size() < 2 * 1024 * 1024) return 0; // No dual bank support - return MG_REG(FLASH_BASE1 + FLASH_OPTCR) & MG_BIT(31) ? 2 : 1; -} - -MG_IRAM static void flash_unlock(void) { - static bool unlocked = false; - if (unlocked == false) { - MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; - if (mg_flash_bank() > 0) { - MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; - MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; - } - MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" - MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once - unlocked = true; - } -} - -MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { - char *base = (char *) mg_flash_start(), *end = base + mg_flash_size(); - volatile char *p = (char *) dst; - return p >= base && p < end && ((p - base) % mg_flash_sector_size()) == 0; -} - -MG_IRAM static bool flash_is_err(uint32_t bank) { - return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 -} - -MG_IRAM static void flash_wait(uint32_t bank) { - while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; -} - -MG_IRAM static void flash_clear_err(uint32_t bank) { - flash_wait(bank); // Wait until ready - MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors -} - -MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) { - return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 -} - -// Figure out flash bank based on the address -MG_IRAM static uint32_t flash_bank(void *addr) { - size_t ofs = (char *) addr - (char *) mg_flash_start(); - if (mg_flash_bank() == 0) return FLASH_BASE1; - return ofs < mg_flash_size() / 2 ? FLASH_BASE1 : FLASH_BASE2; -} - -MG_IRAM bool mg_flash_erase(void *addr) { - bool ok = false; - if (flash_page_start(addr) == false) { - MG_ERROR(("%p is not on a sector boundary", addr)); - } else { - uintptr_t diff = (char *) addr - (char *) mg_flash_start(); - uint32_t sector = diff / mg_flash_sector_size(); - uint32_t bank = flash_bank(addr); - uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value - - flash_unlock(); - if (sector > 7) sector -= 8; - - flash_clear_err(bank); - MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism - MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase - MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit - MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing - ok = !flash_is_err(bank); - MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, - ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), - MG_REG(bank + FLASH_SR))); - MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR - } - return ok; -} - -MG_IRAM bool mg_flash_swap_bank(void) { - if (mg_flash_bank() == 0) return true; - uint32_t bank = FLASH_BASE1; - uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); - flash_unlock(); - flash_clear_err(bank); - // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); - MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired); - // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); - MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART - while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; - return true; -} - -MG_IRAM bool mg_flash_write(void *addr, const void *buf, size_t len) { - if ((len % mg_flash_write_align()) != 0) { - MG_ERROR(("%lu is not aligned to %lu", len, mg_flash_write_align())); - return false; - } - uint32_t bank = flash_bank(addr); - uint32_t *dst = (uint32_t *) addr; - uint32_t *src = (uint32_t *) buf; - uint32_t *end = (uint32_t *) ((char *) buf + len); - bool ok = true; - flash_unlock(); - flash_clear_err(bank); - MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag - MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism - MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len)); - MG_ARM_DISABLE_IRQ(); - while (ok && src < end) { - if (flash_page_start(dst) && mg_flash_erase(dst) == false) break; - *(volatile uint32_t *) dst++ = *src++; - flash_wait(bank); - if (flash_is_err(bank)) ok = false; - } - MG_ARM_ENABLE_IRQ(); - MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, - ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), - MG_REG(bank + FLASH_SR))); - MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag - return ok; -} - -MG_IRAM void mg_device_reset(void) { - // SCB->AIRCR = ((0x5fa << SCB_AIRCR_VECTKEY_Pos)|SCB_AIRCR_SYSRESETREQ_Msk); - *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; -} -#endif - -#ifdef MG_ENABLE_LINES #line 1 "src/dns.c" #endif @@ -1080,7 +139,7 @@ static void mg_dns_free(struct dns_data **head, struct dns_data *d) { LIST_DELETE(struct dns_data, head, d); - free(d); + mg_free(d); } void mg_resolve_cancel(struct mg_connection *c) { @@ -1162,6 +221,7 @@ const struct mg_dns_header *h = (struct mg_dns_header *) buf; struct mg_dns_rr rr; size_t i, n, num_answers, ofs = sizeof(*h); + bool is_response; memset(dm, 0, sizeof(*dm)); if (len < sizeof(*h)) return 0; // Too small, headers dont fit @@ -1172,12 +232,22 @@ num_answers = 10; // Sanity cap } dm->txnid = mg_ntohs(h->txnid); + is_response = mg_ntohs(h->flags) & 0x8000; for (i = 0; i < mg_ntohs(h->num_questions); i++) { if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass)); + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); ofs += n; } + + if (!is_response) { + // For queries, there is no need to parse the answers. In this way, + // we also ensure the domain name (dm->name) is parsed from + // the question field. + return true; + } + for (i = 0; i < num_answers; i++) { if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass, @@ -1283,21 +353,26 @@ return mg_send(c, &pkt, sizeof(pkt.header) + n); } -static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, - struct mg_dns *dnsc, bool ipv6) { - struct dns_data *d = NULL; +bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc); +bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc) { if (dnsc->url == NULL) { - mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()"); - } else if (dnsc->c == NULL) { - dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL); - if (dnsc->c != NULL) { - dnsc->c->pfn = dns_cb; - // dnsc->c->is_hexdumping = 1; - } + mg_error(0, "DNS server URL is NULL. Call mg_mgr_init()"); + return false; } if (dnsc->c == NULL) { + dnsc->c = mg_connect(mgr, dnsc->url, NULL, NULL); + if (dnsc->c == NULL) return false; + dnsc->c->pfn = dns_cb; + } + return true; +} + +static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, + struct mg_dns *dnsc, bool ipv6) { + struct dns_data *d = NULL; + if (!mg_dnsc_init(c->mgr, dnsc)) { mg_error(c, "resolver"); - } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { + } else if ((d = (struct dns_data *) mg_calloc(1, sizeof(*d))) == NULL) { mg_error(c, "resolve OOM"); } else { struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests; @@ -1328,6 +403,76 @@ } } +static const uint8_t mdns_answer[] = { + 0, 1, // 2 bytes - record type, A + 0, 1, // 2 bytes - address class, INET + 0, 0, 0, 120, // 4 bytes - TTL + 0, 4 // 2 bytes - address length +}; + +static void mdns_cb(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_READ) { + struct mg_dns_header *qh = (struct mg_dns_header *) c->recv.buf; + if (c->recv.len > 12 && (qh->flags & mg_htons(0xF800)) == 0) { + // flags -> !resp, opcode=0 => query; ignore other opcodes and responses + struct mg_dns_rr rr; // Parse first question, offset 12 is header size + size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr); + MG_VERBOSE(("mDNS request parsed, result=%d", (int) n)); + if (n > 0) { + // RFC-6762 Appendix C, RFC2181 11: m(n + 1-63), max 255 + 0x0 + // buf and h declared here to ease future expansion to DNS-SD + uint8_t buf[sizeof(struct mg_dns_header) + 256 + sizeof(mdns_answer) + 4]; + struct mg_dns_header *h = (struct mg_dns_header *) buf; + char local_name[63 + 7]; // name label + '.' + local label + '\0' + uint8_t name_len = (uint8_t) strlen((char *)c->fn_data); + struct mg_dns_message dm; + bool unicast = (rr.aclass & MG_BIT(15)) != 0; // QU + // uint16_t q = mg_ntohs(qh->num_questions); + rr.aclass &= (uint16_t) ~MG_BIT(15); // remove "QU" (unicast response) + qh->num_questions = mg_htons(1); // parser sanity + mg_dns_parse(c->recv.buf, c->recv.len, &dm); + if (name_len > (sizeof(local_name) - 7)) // leave room for .local\0 + name_len = sizeof(local_name) - 7; + memcpy(local_name, c->fn_data, name_len); + strcpy(local_name + name_len, ".local"); // ensure proper name.local\0 + if (strcmp(local_name, dm.name) == 0) { + uint8_t *p = &buf[sizeof(*h)]; + memset(h, 0, sizeof(*h)); // clear header + h->txnid = unicast ? qh->txnid : 0; // RFC-6762 18.1 + // RFC-6762 6: 0 questions, 1 Answer, 0 Auth, 0 Additional RRs + h->num_answers = mg_htons(1); // only one answer + h->flags = mg_htons(0x8400); // Authoritative response + *p++ = name_len; // label 1 + memcpy(p, c->fn_data, name_len), p += name_len; + *p++ = 5; // label 2 + memcpy(p, "local", 5), p += 5; + *p++ = 0; // no more labels + memcpy(p, mdns_answer, sizeof(mdns_answer)), p += sizeof(mdns_answer); +#if MG_ENABLE_TCPIP + memcpy(p, &c->mgr->ifp->ip, 4), p += 4; +#else + memcpy(p, c->data, 4), p += 4; +#endif + if (!unicast) memcpy(&c->rem, &c->loc, sizeof(c->rem)); + mg_send(c, buf, (size_t)(p - buf)); // And send it! + MG_DEBUG(("mDNS %c response sent", unicast ? 'U' : 'M')); + } + } + } + mg_iobuf_del(&c->recv, 0, c->recv.len); + } + (void) ev_data; +} + +void mg_multicast_add(struct mg_connection *c, char *ip); +struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, char *name) { + struct mg_connection *c = + mg_listen(mgr, "udp://224.0.0.251:5353", mdns_cb, name); + if (c != NULL) mg_multicast_add(c, (char *)"224.0.0.251"); + return c; +} + + #ifdef MG_ENABLE_LINES #line 1 "src/event.c" #endif @@ -1366,6 +511,79 @@ } #ifdef MG_ENABLE_LINES +#line 1 "src/flash.c" +#endif + + + + + +#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM + +static char *s_addr; // Current address to write to +static size_t s_size; // Firmware size to flash. In-progress indicator +static uint32_t s_crc32; // Firmware checksum + +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash) { + bool ok = false; + if (s_size) { + MG_ERROR(("OTA already in progress. Call mg_ota_end()")); + } else { + size_t half = flash->size / 2; + s_crc32 = 0; + s_addr = (char *) flash->start + half; + MG_DEBUG(("FW %lu bytes, max %lu", new_firmware_size, half)); + if (new_firmware_size < half) { + ok = true; + s_size = new_firmware_size; + MG_INFO(("Starting OTA, firmware size %lu", s_size)); + } else { + MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, half)); + } + } + return ok; +} + +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash) { + bool ok = false; + if (s_size == 0) { + MG_ERROR(("OTA is not started, call mg_ota_begin()")); + } else { + size_t len_aligned_down = MG_ROUND_DOWN(len, flash->align); + if (len_aligned_down) ok = flash->write_fn(s_addr, buf, len_aligned_down); + if (len_aligned_down < len) { + size_t left = len - len_aligned_down; + char tmp[flash->align]; + memset(tmp, 0xff, sizeof(tmp)); + memcpy(tmp, (char *) buf + len_aligned_down, left); + ok = flash->write_fn(s_addr + len_aligned_down, tmp, sizeof(tmp)); + } + s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC + MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); + s_addr += len; + } + return ok; +} + +bool mg_ota_flash_end(struct mg_flash *flash) { + char *base = (char *) flash->start + flash->size / 2; + bool ok = false; + if (s_size) { + size_t size = (size_t) (s_addr - base); + uint32_t crc32 = mg_crc32(0, base, s_size); + if (size == s_size && crc32 == s_crc32) ok = true; + MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, + size, ok ? "ok" : "fail")); + s_size = 0; + if (ok) ok = flash->swap_fn(); + } + MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); + return ok; +} + +#endif + +#ifdef MG_ENABLE_LINES #line 1 "src/fmt.c" #endif @@ -1418,28 +636,36 @@ // Round saved = d; - mul = 1.0; - while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; + if (tz) { + mul = 1.0; + while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; + } else { + mul = 0.1; + } + while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0; for (i = 0, t = mul * 5; i < width; i++) t /= 10.0; + d += t; + // Calculate exponent, and 'mul' for scientific representation mul = 1.0; while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++; while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--; // printf(" --> %g %d %g %g\n", saved, e, t, mul); - if (e >= width && width > 1) { + if (tz && e >= width && width > 1) { n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf); n += addexp(buf + s + n, e, '+'); return mg_snprintf(dst, dstlen, "%.*s", n, buf); - } else if (e <= -width && width > 1) { + } else if (tz && e <= -width && width > 1) { n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz); // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf); n += addexp(buf + s + n, -e, '-'); return mg_snprintf(dst, dstlen, "%.*s", n, buf); } else { + int targ_width = width; for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) { int ch = (int) (d / t); if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0'); @@ -1451,15 +677,17 @@ while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0; if (s + n < (int) sizeof(buf)) buf[n + s++] = '.'; // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf); - for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) { + if (!tz && n > 0) targ_width = width + n; + for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < targ_width; i++) { int ch = (int) (d / t); buf[s + n++] = (char) (ch + '0'); d -= ch * t; t /= 10.0; } } + while (tz && n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeroes - if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot + if (tz && n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot n += s; if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1; buf[n] = '\0'; @@ -1599,13 +827,14 @@ + struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) { - struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd)); + struct mg_fd *fd = (struct mg_fd *) mg_calloc(1, sizeof(*fd)); if (fd != NULL) { fd->fd = fs->op(path, flags); fd->fs = fs; if (fd->fd == NULL) { - free(fd); + mg_free(fd); fd = NULL; } } @@ -1615,7 +844,7 @@ void mg_fs_close(struct mg_fd *fd) { if (fd != NULL) { fd->fs->cl(fd->fd); - free(fd); + mg_free(fd); } } @@ -1624,10 +853,10 @@ void *fp; fs->st(path, &result.len, NULL); if ((fp = fs->op(path, MG_FS_READ)) != NULL) { - result.buf = (char *) calloc(1, result.len + 1); + result.buf = (char *) mg_calloc(1, result.len + 1); if (result.buf != NULL && fs->rd(fp, (void *) result.buf, result.len) != result.len) { - free((void *) result.buf); + mg_free((void *) result.buf); result.buf = NULL; } fs->cl(fp); @@ -1663,7 +892,7 @@ data = mg_vmprintf(fmt, &ap); va_end(ap); result = mg_file_write(fs, path, data, strlen(data)); - free(data); + mg_free(data); return result; } @@ -1766,7 +995,7 @@ if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND; if (f_open(&f, path, mode) == 0) { FIL *fp; - if ((fp = calloc(1, sizeof(*fp))) != NULL) { + if ((fp = mg_calloc(1, sizeof(*fp))) != NULL) { memcpy(fp, &f, sizeof(*fp)); return fp; } @@ -1777,7 +1006,7 @@ static void ff_close(void *fp) { if (fp != NULL) { f_close((FIL *) fp); - free(fp); + mg_free(fp); } } @@ -1826,6 +1055,7 @@ + struct packed_file { const char *data; size_t size; @@ -1835,7 +1065,8 @@ #if MG_ENABLE_PACKED_FS #else const char *mg_unpack(const char *path, size_t *size, time_t *mtime) { - *size = 0, *mtime = 0; + if (size != NULL) *size = 0; + if (mtime != NULL) *mtime = 0; (void) path; return NULL; } @@ -1895,7 +1126,7 @@ struct packed_file *fp = NULL; if (data == NULL) return NULL; if (flags & MG_FS_WRITE) return NULL; - if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) != NULL) { + if ((fp = (struct packed_file *) mg_calloc(1, sizeof(*fp))) != NULL) { fp->size = size; fp->data = data; } @@ -1903,7 +1134,7 @@ } static void packed_close(void *fp) { - if (fp != NULL) free(fp); + if (fp != NULL) mg_free(fp); } static size_t packed_read(void *fd, void *buf, size_t len) { @@ -2049,7 +1280,7 @@ if (name == NULL) { SetLastError(ERROR_BAD_ARGUMENTS); - } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { + } else if ((d = (DIR *) mg_calloc(1, sizeof(*d))) == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); } else { to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); @@ -2059,7 +1290,7 @@ d->handle = FindFirstFileW(wpath, &d->info); d->result.d_name[0] = '\0'; } else { - free(d); + mg_free(d); d = NULL; } } @@ -2071,7 +1302,7 @@ if (d != NULL) { if (d->handle != INVALID_HANDLE_VALUE) result = FindClose(d->handle) ? 0 : -1; - free(d); + mg_free(d); } else { result = -1; SetLastError(ERROR_BAD_ARGUMENTS); @@ -2401,7 +1632,7 @@ } static bool isok(uint8_t c) { - return c == '\n' || c == '\r' || c >= ' '; + return c == '\n' || c == '\r' || c == '\t' || c >= ' '; } int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) { @@ -2432,7 +1663,7 @@ static size_t clen(const char *s, const char *end) { const unsigned char *u = (unsigned char *) s, c = *u; long n = (long) (end - s); - if (c > ' ' && c < '~') return 1; // Usual ascii printed char + if (c > ' ' && c <= '~') return 1; // Usual ascii printed char if ((c & 0xe0) == 0xc0 && n > 1 && vcb(u[1])) return 2; // 2-byte UTF8 if ((c & 0xf0) == 0xe0 && n > 2 && vcb(u[1]) && vcb(u[2])) return 3; if ((c & 0xf8) == 0xf0 && n > 3 && vcb(u[1]) && vcb(u[2]) && vcb(u[3])) @@ -2463,9 +1694,11 @@ if (s >= end || clen(s, end) == 0) return false; // Invalid UTF-8 if (*s++ != ':') return false; // Invalid, not followed by : // if (clen(s, end) == 0) return false; // Invalid UTF-8 - while (s < end && s[0] == ' ') s++; // Skip spaces + while (s < end && (s[0] == ' ' || s[0] == '\t')) s++; // Skip spaces if ((s = skiptorn(s, end, &v)) == NULL) return false; - while (v.len > 0 && v.buf[v.len - 1] == ' ') v.len--; // Trim spaces + while (v.len > 0 && (v.buf[v.len - 1] == ' ' || v.buf[v.len - 1] == '\t')) { + v.len--; // Trim spaces + } // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.buf, (int) v.len, v.buf)); h[i].name = k, h[i].value = v; // Success. Assign values } @@ -2477,6 +1710,7 @@ const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL const struct mg_str *cl; size_t n; + bool version_prefix_valid; memset(hm, 0, sizeof(*hm)); if (req_len <= 0) return req_len; @@ -2493,7 +1727,19 @@ hm->uri.buf = (char *) s; while (s < end && (n = clen(s, end)) > 0) s += n, hm->uri.len += n; while (s < end && s[0] == ' ') s++; // Skip spaces + is_response = + hm->method.len > 5 && (mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0); if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false; + // If we're given a version, check that it is HTTP/x.x + version_prefix_valid = + hm->proto.len > 5 && (mg_ncasecmp(hm->proto.buf, "HTTP/", 5) == 0); + if (!is_response && hm->proto.len > 0 && + (!version_prefix_valid || hm->proto.len != 8 || + (hm->proto.buf[5] < '0' || hm->proto.buf[5] > '9') || + (hm->proto.buf[6] != '.') || + (hm->proto.buf[7] < '0' || hm->proto.buf[7] > '9'))) { + return -1; + } // If URI contains '?' character, setup query string if ((qs = (const char *) memchr(hm->uri.buf, '?', hm->uri.len)) != NULL) { @@ -2526,7 +1772,6 @@ // // So, if it is HTTP request, and Content-Length is not set, // and method is not (PUT or POST) then reset body length to zero. - is_response = mg_ncasecmp(hm->method.buf, "HTTP/", 5) == 0; if (hm->body.len == (size_t) ~0 && !is_response && mg_strcasecmp(hm->method, mg_str("PUT")) != 0 && mg_strcasecmp(hm->method, mg_str("POST")) != 0) { @@ -2548,14 +1793,14 @@ static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt, va_list *ap) { size_t len = c->send.len; - mg_send(c, " \r\n", 10); + if (!mg_send(c, " \r\n", 10)) mg_error(c, "OOM"); mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); if (c->send.len >= len + 10) { mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10); c->send.buf[len + 8] = '\r'; if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset marker } - mg_send(c, "\r\n", 2); + if (!mg_send(c, "\r\n", 2)) mg_error(c, "OOM"); } void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) { @@ -2567,8 +1812,7 @@ void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { mg_printf(c, "%lx\r\n", (unsigned long) len); - mg_send(c, buf, len); - mg_send(c, "\r\n", 2); + if (!mg_send(c, buf, len) || !mg_send(c, "\r\n", 2)) mg_error(c, "OOM"); if (len == 0) c->is_resp = 0; } @@ -2704,6 +1948,7 @@ MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"), MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"), MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"), + MG_C_STR("mjs"), MG_C_STR("text/javascript; charset=utf-8"), MG_C_STR("gif"), MG_C_STR("image/gif"), MG_C_STR("png"), MG_C_STR("image/png"), MG_C_STR("jpg"), MG_C_STR("image/jpeg"), @@ -2735,7 +1980,7 @@ // clang-format on static struct mg_str guess_content_type(struct mg_str path, const char *extra) { - struct mg_str entry, k, v, s = mg_str(extra); + struct mg_str entry, k, v, s = mg_str(extra), asterisk = mg_str_n("*", 1); size_t i = 0; // Shrink path to its extension only @@ -2745,7 +1990,9 @@ // Process user-provided mime type overrides, if any while (mg_span(s, &entry, &s, ',')) { - if (mg_span(entry, &k, &v, '=') && mg_strcmp(path, k) == 0) return v; + if (mg_span(entry, &k, &v, '=') && + (mg_strcmp(asterisk, k) == 0 || mg_strcmp(path, k) == 0)) + return v; } // Process built-in mime types @@ -2788,13 +2035,11 @@ // If a browser sends us "Accept-Encoding: gzip", try to open .gz first struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding"); if (ae != NULL) { - char *ae_ = mg_mprintf("%.*s", ae->len, ae->buf); - if (ae_ != NULL && strstr(ae_, "gzip") != NULL) { + if (mg_match(*ae, mg_str("*gzip*"), NULL)) { mg_snprintf(tmp, sizeof(tmp), "%s.gz", path); fd = mg_fs_open(fs, tmp, MG_FS_READ); if (fd != NULL) gzip = true, path = tmp; } - free(ae_); } // No luck opening .gz? Open what we've told to open if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ); @@ -2851,7 +2096,6 @@ etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "", range, opts->extra_headers ? opts->extra_headers : ""); if (mg_strcasecmp(hm->method, mg_str("HEAD")) == 0) { - c->is_draining = 1; c->is_resp = 0; mg_fs_close(fd); } else { @@ -2886,7 +2130,7 @@ sizeof(path)) { MG_ERROR(("%s truncated", name)); } else if ((flags = fs->st(path, &size, &t)) == 0) { - MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno)); + MG_ERROR(("%lu stat(%s)", d->c->id, path)); } else { const char *slash = flags & MG_FS_DIR ? "/" : ""; if (flags & MG_FS_DIR) { @@ -3177,7 +2421,7 @@ mg_http_reply(c, 400, "", "%s: offset mismatch", path); res = -5; } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { - mg_http_reply(c, 400, "", "open(%s): %d", path, errno); + mg_http_reply(c, 400, "", "open(%s)", path); res = -6; } else { res = offset + (long) fs->wr(fd->fd, hm->body.buf, hm->body.len); @@ -3214,7 +2458,9 @@ } static void http_cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ || ev == MG_EV_CLOSE) { + if (ev == MG_EV_READ || ev == MG_EV_CLOSE || + (ev == MG_EV_POLL && c->is_accepted && !c->is_draining && + c->recv.len > 0)) { // see #2796 struct mg_http_message hm; size_t ofs = 0; // Parsing offset while (c->is_resp == 0 && ofs < c->recv.len) { @@ -3222,6 +2468,7 @@ int n = mg_http_parse(buf, c->recv.len - ofs, &hm); struct mg_str *te; // Transfer - encoding header bool is_chunked = false; + size_t old_len = c->recv.len; if (n < 0) { // We don't use mg_error() here, to avoid closing pipelined requests // prematurely, see #2592 @@ -3233,7 +2480,13 @@ } if (n == 0) break; // Request is not buffered yet mg_call(c, MG_EV_HTTP_HDRS, &hm); // Got all HTTP headers - if (ev == MG_EV_CLOSE) { // If client did not set Content-Length + if (c->recv.len != old_len) { + // User manipulated received data. Wash our hands + MG_DEBUG(("%lu detaching HTTP handler", c->id)); + c->pfn = NULL; + return; + } + if (ev == MG_EV_CLOSE) { // If client did not set Content-Length hm.message.len = c->recv.len - ofs; // and closes now, deliver MSG hm.body.len = hm.message.len - (size_t) (hm.body.buf - hm.message.buf); } @@ -3255,6 +2508,7 @@ // contain a Content-length header. Other requests can also contain a // body, but their content has no defined semantics (RFC 7231) require_content_len = true; + ofs += (size_t) n; // this request has been processed } else if (is_response) { // HTTP spec 7.2 Entity body: All other responses must include a body // or Content-Length header field defined with a value of 0. @@ -3262,8 +2516,9 @@ require_content_len = status >= 200 && status != 204 && status != 304; } if (require_content_len) { - mg_http_reply(c, 411, "", ""); - MG_ERROR(("%s", "Content length missing from request")); + if (!c->is_client) mg_http_reply(c, 411, "", ""); + MG_ERROR(("Content length missing from %s", + is_response ? "response" : "request")); } } @@ -3297,45 +2552,22 @@ if (c->is_accepted) c->is_resp = 1; // Start generating response mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp + if (c->is_accepted && !c->is_resp) { + struct mg_str *cc = mg_http_get_header(&hm, "Connection"); + if (cc != NULL && mg_strcasecmp(*cc, mg_str("close")) == 0) { + c->is_draining = 1; // honor "Connection: close" + break; + } + } } if (ofs > 0) mg_iobuf_del(&c->recv, 0, ofs); // Delete processed data } (void) ev_data; } -static void mg_hfn(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_HTTP_MSG) { - struct mg_http_message *hm = (struct mg_http_message *) ev_data; - if (mg_match(hm->uri, mg_str("/quit"), NULL)) { - mg_http_reply(c, 200, "", "ok\n"); - c->is_draining = 1; - c->data[0] = 'X'; - } else if (mg_match(hm->uri, mg_str("/debug"), NULL)) { - int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG); - mg_log_set(level); - mg_http_reply(c, 200, "", "Debug level set to %d\n", level); - } else { - mg_http_reply(c, 200, "", "hi\n"); - } - } else if (ev == MG_EV_CLOSE) { - if (c->data[0] == 'X') *(bool *) c->fn_data = true; - } -} - -void mg_hello(const char *url) { - struct mg_mgr mgr; - bool done = false; - mg_mgr_init(&mgr); - if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true; - while (done == false) mg_mgr_poll(&mgr, 100); - mg_mgr_free(&mgr); -} - struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); - if (c != NULL) c->pfn = http_cb; - return c; + return mg_connect_svc(mgr, url, fn, fn_data, http_cb, NULL); } struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, @@ -3357,34 +2589,34 @@ return align == 0 ? size : (size + align - 1) / align * align; } -int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { - int ok = 1; +bool mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { + bool ok = true; new_size = roundup(new_size, io->align); if (new_size == 0) { mg_bzero(io->buf, io->size); - free(io->buf); + mg_free(io->buf); io->buf = NULL; io->len = io->size = 0; } else if (new_size != io->size) { - // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the - // porting to some obscure platforms like FreeRTOS - void *p = calloc(1, new_size); + // NOTE(lsm): do not use realloc here. Use mg_calloc/mg_free only + void *p = mg_calloc(1, new_size); if (p != NULL) { size_t len = new_size < io->len ? new_size : io->len; if (len > 0 && io->buf != NULL) memmove(p, io->buf, len); mg_bzero(io->buf, io->size); - free(io->buf); + mg_free(io->buf); io->buf = (unsigned char *) p; io->size = new_size; + io->len = len; } else { - ok = 0; + ok = false; MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size)); } } return ok; } -int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) { +bool mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) { io->buf = NULL; io->align = align; io->size = io->len = 0; @@ -3423,6 +2655,7 @@ + static const char *escapeseq(int esc) { return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\""; } @@ -3741,10 +2974,10 @@ char *result = NULL; int len = 0, off = mg_json_get(json, path, &len); if (off >= 0 && len > 1 && json.buf[off] == '"') { - if ((result = (char *) calloc(1, (size_t) len)) != NULL && + if ((result = (char *) mg_calloc(1, (size_t) len)) != NULL && !mg_json_unescape(mg_str_n(json.buf + off + 1, (size_t) (len - 2)), result, (size_t) len)) { - free(result); + mg_free(result); result = NULL; } } @@ -3755,7 +2988,7 @@ char *result = NULL; int len = 0, off = mg_json_get(json, path, &len); if (off >= 0 && json.buf[off] == '"' && len > 1 && - (result = (char *) calloc(1, (size_t) len)) != NULL) { + (result = (char *) mg_calloc(1, (size_t) len)) != NULL) { size_t k = mg_base64_decode(json.buf + off + 1, (size_t) (len - 2), result, (size_t) len); if (slen != NULL) *slen = (int) k; @@ -3767,7 +3000,7 @@ char *result = NULL; int len = 0, off = mg_json_get(json, path, &len); if (off >= 0 && json.buf[off] == '"' && len > 1 && - (result = (char *) calloc(1, (size_t) len / 2)) != NULL) { + (result = (char *) mg_calloc(1, (size_t) len / 2)) != NULL) { int i; for (i = 0; i < len - 2; i += 2) { mg_str_to_num(mg_str_n(json.buf + off + 1 + i, 2), 16, &result[i >> 1], @@ -3794,7 +3027,7 @@ -int mg_log_level = MG_LL_INFO; +int mg_log_level = MG_LL_DEBUG; static mg_pfn_t s_log_func = mg_pfn_stdout; static void *s_log_func_param = NULL; @@ -3848,7 +3081,8 @@ for (i = 0; i < len; i++) { if ((i % 16) == 0) { // Print buffered ascii chars - if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0; + if (i > 0) + logs(" ", 2), logs((char *) ascii, 16), logs("\r\n", 2), alen = 0; // Print hex address, then \t logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)), logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3); @@ -3858,7 +3092,7 @@ ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii buf } while (alen < 16) logs(" ", 3), ascii[alen++] = ' '; - logs(" ", 2), logs((char *) ascii, 16), logc('\n'); + logs(" ", 2), logs((char *) ascii, 16), logs("\r\n", 2); } #ifdef MG_ENABLE_LINES @@ -4118,8 +3352,8 @@ {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE, MQTT_PROP_TYPE_BYTE}, {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}}; -void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, - uint32_t len) { +static bool mqtt_send_header(struct mg_connection *c, uint8_t cmd, + uint8_t flags, uint32_t len) { uint8_t buf[1 + sizeof(len)], *vlen = &buf[1]; buf[0] = (uint8_t) ((cmd << 4) | flags); do { @@ -4128,15 +3362,20 @@ if (len > 0) *vlen |= 0x80; vlen++; } while (len > 0 && vlen < &buf[sizeof(buf)]); - mg_send(c, buf, (size_t) (vlen - buf)); + return mg_send(c, buf, (size_t) (vlen - buf)); } -static void mg_send_u16(struct mg_connection *c, uint16_t value) { - mg_send(c, &value, sizeof(value)); +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, + uint32_t len) { + if (!mqtt_send_header(c, cmd, flags, len)) mg_error(c, "OOM"); +} + +static bool mg_send_u16(struct mg_connection *c, uint16_t value) { + return mg_send(c, &value, sizeof(value)); } -static void mg_send_u32(struct mg_connection *c, uint32_t value) { - mg_send(c, &value, sizeof(value)); +static bool mg_send_u32(struct mg_connection *c, uint32_t value) { + return mg_send(c, &value, sizeof(value)); } static uint8_t varint_size(size_t length) { @@ -4229,46 +3468,50 @@ return size; } -static void mg_send_mqtt_properties(struct mg_connection *c, +static bool mg_send_mqtt_properties(struct mg_connection *c, struct mg_mqtt_prop *props, size_t nprops) { size_t total_size = get_properties_length(props, nprops); uint8_t buf_v[4] = {0, 0, 0, 0}; uint8_t buf[4] = {0, 0, 0, 0}; size_t i, len = encode_varint(buf, total_size); - mg_send(c, buf, (size_t) len); + if (!mg_send(c, buf, (size_t) len)) return false; for (i = 0; i < nprops; i++) { - mg_send(c, &props[i].id, sizeof(props[i].id)); + if (!mg_send(c, &props[i].id, sizeof(props[i].id))) return false; switch (mqtt_prop_type_by_id(props[i].id)) { case MQTT_PROP_TYPE_STRING_PAIR: - mg_send_u16(c, mg_htons((uint16_t) props[i].key.len)); - mg_send(c, props[i].key.buf, props[i].key.len); - mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.buf, props[i].val.len); + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].key.len)) || + !mg_send(c, props[i].key.buf, props[i].key.len) || + !mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)) || + !mg_send(c, props[i].val.buf, props[i].val.len)) + return false; break; case MQTT_PROP_TYPE_BYTE: - mg_send(c, &props[i].iv, sizeof(uint8_t)); + if (!mg_send(c, &props[i].iv, sizeof(uint8_t))) return false; break; case MQTT_PROP_TYPE_SHORT: - mg_send_u16(c, mg_htons((uint16_t) props[i].iv)); + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].iv))) return false; break; case MQTT_PROP_TYPE_INT: - mg_send_u32(c, mg_htonl((uint32_t) props[i].iv)); + if (!mg_send_u32(c, mg_htonl((uint32_t) props[i].iv))) return false; break; case MQTT_PROP_TYPE_STRING: - mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.buf, props[i].val.len); + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)) || + !mg_send(c, props[i].val.buf, props[i].val.len)) + return false; break; case MQTT_PROP_TYPE_BINARY_DATA: - mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)); - mg_send(c, props[i].val.buf, props[i].val.len); + if (!mg_send_u16(c, mg_htons((uint16_t) props[i].val.len)) || + !mg_send(c, props[i].val.buf, props[i].val.len)) + return false; break; case MQTT_PROP_TYPE_VARIABLE_INT: len = encode_varint(buf_v, props[i].iv); - mg_send(c, buf_v, (size_t) len); + if (!mg_send(c, buf_v, (size_t) len)) return false; break; } } + return true; } size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop, @@ -4348,7 +3591,7 @@ total_len += 2 + (uint32_t) opts->pass.len; hdr[7] |= MQTT_HAS_PASSWORD; } - if (opts->topic.len > 0) { // allow zero-length msgs, message.len is size_t + if (opts->topic.len > 0) { // allow zero-length msgs, message.len is size_t total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) opts->message.len; hdr[7] |= MQTT_HAS_WILL; } @@ -4361,60 +3604,77 @@ total_len += get_props_size(opts->will_props, opts->num_will_props); } - mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len); - mg_send(c, hdr, sizeof(hdr)); // keepalive == 0 means "do not disconnect us!" - mg_send_u16(c, mg_htons((uint16_t) opts->keepalive)); + if (!mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len) || + !mg_send(c, hdr, sizeof(hdr)) || + !mg_send_u16(c, mg_htons((uint16_t) opts->keepalive))) + goto fail; - if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); + if (c->is_mqtt5 && !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; - mg_send_u16(c, mg_htons((uint16_t) cid.len)); - mg_send(c, cid.buf, cid.len); + if (!mg_send_u16(c, mg_htons((uint16_t) cid.len)) || + !mg_send(c, cid.buf, cid.len)) + goto fail; if (hdr[7] & MQTT_HAS_WILL) { - if (c->is_mqtt5) - mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props); + if (c->is_mqtt5 && + !mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props)) + goto fail; - mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.buf, opts->topic.len); - mg_send_u16(c, mg_htons((uint16_t) opts->message.len)); - mg_send(c, opts->message.buf, opts->message.len); - } - if (opts->user.len > 0) { - mg_send_u16(c, mg_htons((uint16_t) opts->user.len)); - mg_send(c, opts->user.buf, opts->user.len); - } - if (opts->pass.len > 0) { - mg_send_u16(c, mg_htons((uint16_t) opts->pass.len)); - mg_send(c, opts->pass.buf, opts->pass.len); + if (!mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)) || + !mg_send(c, opts->topic.buf, opts->topic.len) || + !mg_send_u16(c, mg_htons((uint16_t) opts->message.len)) || + !mg_send(c, opts->message.buf, opts->message.len)) + goto fail; } + if (opts->user.len > 0 && + (!mg_send_u16(c, mg_htons((uint16_t) opts->user.len)) || + !mg_send(c, opts->user.buf, opts->user.len))) + goto fail; + if (opts->pass.len > 0 && + (!mg_send_u16(c, mg_htons((uint16_t) opts->pass.len)) || + !mg_send(c, opts->pass.buf, opts->pass.len))) + goto fail; + return; +fail: + mg_error(c, "OOM"); } uint16_t mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) { uint16_t id = opts->retransmit_id; uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 : 0)); size_t len = 2 + opts->topic.len + opts->message.len; - MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len, - (char *) opts->topic.buf, (int) opts->message.len, - (char *) opts->message.buf)); + MG_DEBUG(("%lu [%.*s] <- [%.*s%c", c->id, (int) opts->topic.len, + (char *) opts->topic.buf, + (int) (opts->message.len <= 10 ? opts->message.len : 10), + (char *) opts->message.buf, opts->message.len <= 10 ? ']' : ' ')); if (opts->qos > 0) len += 2; if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props); if (opts->qos > 0 && id != 0) flags |= 1 << 3; - mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len); - mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.buf, opts->topic.len); - if (opts->qos > 0) { // need to send 'id' field - if (id == 0) { // generate new one if not resending + if (!mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len) || + !mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)) || + !mg_send(c, opts->topic.buf, opts->topic.len)) + goto fail; + if (opts->qos > 0) { // need to send 'id' field + if (id == 0) { // generate new one if not resending if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; id = c->mgr->mqtt_id; } - mg_send_u16(c, mg_htons(id)); + if (!mg_send_u16(c, mg_htons(id))) goto fail; } - if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); + if (c->is_mqtt5 && !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; - if (opts->message.len > 0) mg_send(c, opts->message.buf, opts->message.len); + if (opts->message.len > 0 && + !mg_send(c, opts->message.buf, opts->message.len)) + goto fail; + return id; + +fail: + mg_error(c, "OOM"); return id; } @@ -4423,14 +3683,20 @@ size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0; size_t len = 2 + opts->topic.len + 2 + 1 + plen; - mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len); + if (!mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) len)) goto fail; if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; - mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); - if (c->is_mqtt5) mg_send_mqtt_properties(c, opts->props, opts->num_props); + if (!mg_send_u16(c, mg_htons(c->mgr->mqtt_id))) goto fail; + + if (c->is_mqtt5 && !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; - mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)); - mg_send(c, opts->topic.buf, opts->topic.len); - mg_send(c, &qos_, sizeof(qos_)); + if (!mg_send_u16(c, mg_htons((uint16_t) opts->topic.len)) || + !mg_send(c, opts->topic.buf, opts->topic.len) || + !mg_send(c, &qos_, sizeof(qos_))) + goto fail; + return; +fail: + mg_error(c, "OOM"); } int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version, @@ -4528,22 +3794,25 @@ } break; case MQTT_CMD_PUBLISH: { - /*MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len, - mm.topic.buf, (int) mm.data.len, mm.data.buf));*/ + MG_DEBUG(("%lu [%.*s] -> [%.*s%c", c->id, (int) mm.topic.len, + mm.topic.buf, + (int) (mm.data.len <= 10 ? mm.data.len : 10), mm.data.buf, + mm.data.len <= 10 ? ']' : ' ')); if (mm.qos > 0) { uint16_t id = mg_ntohs(mm.id); uint32_t remaining_len = sizeof(id); if (c->is_mqtt5) remaining_len += 2; // 3.4.2 - mg_mqtt_send_header( - c, - (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK), - 0, remaining_len); - mg_send(c, &id, sizeof(id)); + if (!mqtt_send_header(c, + (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC + : MQTT_CMD_PUBACK), + 0, remaining_len) || + !mg_send(c, &id, sizeof(id))) + goto fail; if (c->is_mqtt5) { uint16_t zero = 0; - mg_send(c, &zero, sizeof(zero)); + if (!mg_send(c, &zero, sizeof(zero))) goto fail; } } mg_call(c, MG_EV_MQTT_MSG, &mm); // let the app handle qos stuff @@ -4552,15 +3821,18 @@ case MQTT_CMD_PUBREC: { // MQTT5: 3.5.2-1 TODO(): variable header rc uint16_t id = mg_ntohs(mm.id); uint32_t remaining_len = sizeof(id); // MQTT5 3.6.2-1 - mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len); - mg_send(c, &id, sizeof(id)); // MQTT5 3.6.1-1, flags = 2 + if (!mqtt_send_header(c, MQTT_CMD_PUBREL, 2, + remaining_len) // MQTT5 3.6.1-1, flags = 2 + || !mg_send(c, &id, sizeof(id))) + goto fail; break; } case MQTT_CMD_PUBREL: { // MQTT5: 3.6.2-1 TODO(): variable header rc uint16_t id = mg_ntohs(mm.id); uint32_t remaining_len = sizeof(id); // MQTT5 3.7.2-1 - mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len); - mg_send(c, &id, sizeof(id)); + if (!mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len) || + !mg_send(c, &id, sizeof(id))) + goto fail; break; } } @@ -4572,6 +3844,9 @@ } } (void) ev_data; + return; +fail: + mg_error(c, "OOM"); } void mg_mqtt_ping(struct mg_connection *nc) { @@ -4586,24 +3861,28 @@ const struct mg_mqtt_opts *opts) { size_t len = 0; if (c->is_mqtt5) len = 1 + get_props_size(opts->props, opts->num_props); - mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len); + if (!mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len)) goto fail; if (c->is_mqtt5) { uint8_t zero = 0; - mg_send(c, &zero, sizeof(zero)); // reason code - mg_send_mqtt_properties(c, opts->props, opts->num_props); + if (!mg_send(c, &zero, sizeof(zero)) // reason code + || !mg_send_mqtt_properties(c, opts->props, opts->num_props)) + goto fail; } + return; +fail: + mg_error(c, "OOM"); } struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url, const struct mg_mqtt_opts *opts, mg_event_handler_t fn, void *fn_data) { - struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + struct mg_connection *c = + mg_connect_svc(mgr, url, fn, fn_data, mqtt_cb, NULL); if (c != NULL) { struct mg_mqtt_opts empty; memset(&empty, 0, sizeof(empty)); mg_mqtt_login(c, opts == NULL ? &empty : opts); - c->pfn = mqtt_cb; } return c; } @@ -4629,8 +3908,14 @@ size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) { size_t old = c->send.len; - mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); - return c->send.len - old; + size_t expected = mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap); + size_t actual = c->send.len - old; + if (actual != expected) { + mg_error(c, "OOM"); + c->send.len = old; + actual = 0; + } + return actual; } size_t mg_printf(struct mg_connection *c, const char *fmt, ...) { @@ -4705,7 +3990,7 @@ if ((str.buf[i] >= '0' && str.buf[i] <= '9') || (str.buf[i] >= 'a' && str.buf[i] <= 'f') || (str.buf[i] >= 'A' && str.buf[i] <= 'F')) { - unsigned long val; // TODO(): This loops on chars, refactor + unsigned long val = 0; // TODO(): This loops on chars, refactor if (i > j + 3) return false; // MG_DEBUG(("%lu %lu [%.*s]", i, j, (int) (i - j + 1), &str.buf[j])); mg_str_to_num(mg_str_n(&str.buf[j], i - j + 1), 16, &val, sizeof(val)); @@ -4746,7 +4031,7 @@ struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) { struct mg_connection *c = - (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize); + (struct mg_connection *) mg_calloc(1, sizeof(*c) + mgr->extraconnsize); if (c != NULL) { c->mgr = mgr; c->send.align = c->recv.align = c->rtls.align = MG_IO_SIZE; @@ -4773,11 +4058,12 @@ mg_iobuf_free(&c->send); mg_iobuf_free(&c->rtls); mg_bzero((unsigned char *) c, sizeof(*c)); - free(c); + mg_free(c); } -struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fn_data) { +struct mg_connection *mg_connect_svc(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data, + mg_event_handler_t pfn, void *pfn_data) { struct mg_connection *c = NULL; if (url == NULL || url[0] == '\0') { MG_ERROR(("null url")); @@ -4790,22 +4076,30 @@ c->fn = fn; c->is_client = true; c->fn_data = fn_data; - MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); + c->is_tls = (mg_url_is_ssl(url) != 0); + c->pfn = pfn; + c->pfn_data = pfn_data; mg_call(c, MG_EV_OPEN, (void *) url); + MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); mg_resolve(c, url); } return c; } +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + return mg_connect_svc(mgr, url, fn, fn_data, NULL, NULL); +} + struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data) { struct mg_connection *c = NULL; if ((c = mg_alloc_conn(mgr)) == NULL) { MG_ERROR(("OOM %s", url)); } else if (!mg_open_listener(c, url)) { - MG_ERROR(("Failed: %s, errno %d", url, errno)); + MG_ERROR(("Failed: %s", url)); MG_PROF_FREE(c); - free(c); + mg_free(c); c = NULL; } else { c->is_listening = 1; @@ -4813,8 +4107,8 @@ LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); c->fn = fn; c->fn_data = fn_data; + c->is_tls = (mg_url_is_ssl(url) != 0); mg_call(c, MG_EV_OPEN, NULL); - if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must MG_DEBUG(("%lu %ld %s", c->id, c->fd, url)); } return c; @@ -4836,10 +4130,10 @@ struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, unsigned flags, void (*fn)(void *), void *arg) { - struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t)); + struct mg_timer *t = (struct mg_timer *) mg_calloc(1, sizeof(*t)); if (t != NULL) { + flags |= MG_TIMER_AUTODELETE; // We have alloc'ed it, so autodelete mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg); - t->id = mgr->timerid++; } return t; } @@ -4855,7 +4149,7 @@ void mg_mgr_free(struct mg_mgr *mgr) { struct mg_connection *c; struct mg_timer *tmp, *t = mgr->timers; - while (t != NULL) tmp = t->next, free(t), t = tmp; + while (t != NULL) tmp = t->next, mg_free(t), t = tmp; mgr->timers = NULL; // Important. Next call to poll won't touch timers for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1; mg_mgr_poll(mgr, 0); @@ -4867,6 +4161,9 @@ if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1; #endif mg_tls_ctx_free(mgr); +#if MG_ENABLE_TCPIP + if (mgr->ifp) mg_tcpip_free(mgr->ifp); +#endif } void mg_mgr_init(struct mg_mgr *mgr) { @@ -4883,7 +4180,7 @@ // clang-format on #elif MG_ENABLE_FREERTOS_TCP mgr->ss = FreeRTOS_CreateSocketSet(); -#elif defined(__unix) || defined(__unix__) || defined(__APPLE__) +#elif MG_ARCH == MG_ARCH_UNIX // Ignore SIGPIPE signal, so if client cancels the request, it // won't kill the whole process. signal(SIGPIPE, SIG_IGN); @@ -4895,6 +4192,13 @@ mgr->dns4.url = "udp://8.8.8.8:53"; mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; mg_tls_ctx_init(mgr); + MG_DEBUG(("MG_IO_SIZE: %lu, TLS: %s", MG_IO_SIZE, + MG_TLS == MG_TLS_NONE ? "none" + : MG_TLS == MG_TLS_MBED ? "MbedTLS" + : MG_TLS == MG_TLS_OPENSSL ? "OpenSSL" + : MG_TLS == MG_TLS_BUILTIN ? "builtin" + : MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL" + : "custom")); } #ifdef MG_ENABLE_LINES @@ -4902,7 +4206,8 @@ #endif -#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP + +#if MG_ENABLE_TCPIP #define MG_EPHEMERAL_PORT_BASE 32768 #define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) @@ -4911,18 +4216,19 @@ #endif #define MIP_TCP_ACK_MS 150 // Timeout for ACKing -#define MIP_TCP_ARP_MS 100 // Timeout for ARP response +#define MIP_ARP_RESP_MS 100 // Timeout for ARP response #define MIP_TCP_SYN_MS 15000 // Timeout for connection establishment #define MIP_TCP_FIN_MS 1000 // Timeout for closing connection #define MIP_TCP_WIN 6000 // TCP window size struct connstate { uint32_t seq, ack; // TCP seq/ack counters - uint64_t timer; // TCP keep-alive / ACK timer + uint64_t timer; // TCP timer (see 'ttype' below) uint32_t acked; // Last ACK-ed number size_t unacked; // Not acked bytes + uint16_t dmss; // destination MSS (from TCP opts) uint8_t mac[6]; // Peer MAC address - uint8_t ttype; // Timer type. 0: ack, 1: keep-alive + uint8_t ttype; // Timer type: #define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive #define MIP_TTYPE_ACK 1 // Peer sent us data, we have to ack it soon #define MIP_TTYPE_ARP 2 // ARP resolve sent, waiting for response @@ -4930,6 +4236,8 @@ #define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection uint8_t tmiss; // Number of keep-alive misses struct mg_iobuf raw; // For TLS only. Incoming raw data + bool fin_rcvd; // We have received FIN from the peer + bool twclosure; // 3-way closure done }; #pragma pack(push, 1) @@ -4947,11 +4255,11 @@ struct ip { uint8_t ver; // Version uint8_t tos; // Unused - uint16_t len; // Length + uint16_t len; // Datagram length uint16_t id; // Unused uint16_t frag; // Fragmentation -#define IP_FRAG_OFFSET_MSK 0xFF1F -#define IP_MORE_FRAGS_MSK 0x20 +#define IP_FRAG_OFFSET_MSK 0x1fff +#define IP_MORE_FRAGS_MSK 0x2000 uint8_t ttl; // Time to live uint8_t proto; // Upper level protocol uint16_t csum; // Checksum @@ -4960,13 +4268,13 @@ }; struct ip6 { - uint8_t ver; // Version - uint8_t opts[3]; // Options - uint16_t len; // Length - uint8_t proto; // Upper level protocol - uint8_t ttl; // Time to live - uint8_t src[16]; // Source IP - uint8_t dst[16]; // Destination IP + uint8_t ver; // Version + uint8_t label[3]; // Flow label + uint16_t plen; // Payload length + uint8_t next; // Upper level protocol + uint8_t hops; // Hop limit + uint64_t src[2]; // Source IP + uint64_t dst[2]; // Destination IP }; struct icmp { @@ -4975,6 +4283,25 @@ uint16_t csum; }; +struct icmp6 { + uint8_t type; + uint8_t code; + uint16_t csum; +}; + +struct ndp_na { + uint8_t res[4]; // R S O, reserved + uint64_t addr[2]; // Target address +}; + +struct ndp_ra { + uint8_t cur_hop_limit; + uint8_t flags; // M,O,Prf,Resvd + uint16_t router_lifetime; + uint32_t reachable_time; + uint32_t retrans_timer; +}; + struct arp { uint16_t fmt; // Format of hardware address uint16_t pro; // Format of protocol address @@ -5021,11 +4348,21 @@ uint32_t ciaddr, yiaddr, siaddr, giaddr; uint8_t hwaddr[208]; uint32_t magic; - uint8_t options[32]; + uint8_t options[30 + sizeof(((struct mg_tcpip_if *) 0)->dhcp_name)]; +}; + +struct dhcp6 { + union { + uint8_t type; + uint32_t xid; + }; + uint8_t options[30 + sizeof(((struct mg_tcpip_if *) 0)->dhcp_name)]; }; #pragma pack(pop) +// pkt is 8-bit aligned, pointers to headers hint compilers to generate +// byte-copy code for micros with alignment constraints struct pkt { struct mg_str raw; // Raw packet data struct mg_str pay; // Payload data @@ -5035,28 +4372,46 @@ struct ip *ip; struct ip6 *ip6; struct icmp *icmp; + struct icmp6 *icmp6; struct tcp *tcp; struct udp *udp; struct dhcp *dhcp; + struct dhcp6 *dhcp6; }; +static void mg_tcpip_call(struct mg_tcpip_if *ifp, int ev, void *ev_data) { +#if MG_ENABLE_PROFILE + const char *names[] = {"TCPIP_EV_ST_CHG", "TCPIP_EV_DHCP_DNS", + "TCPIP_EV_DHCP_SNTP", "TCPIP_EV_ARP", + "TCPIP_EV_TIMER_1S", "TCPIP_EV_WIFI_SCAN_RESULT", + "TCPIP_EV_WIFI_SCAN_END", "TCPIP_EV_WIFI_CONNECT_ERR", + "TCPIP_EV_DRIVER", "TCPIP_EV_USER"}; + if (ev != MG_TCPIP_EV_POLL && ev < (int) (sizeof(names) / sizeof(names[0]))) { + MG_PROF_ADD(c, names[ev]); + } +#endif + // Fire protocol handler first, user handler second. See #2559 + if (ifp->pfn != NULL) ifp->pfn(ifp, ev, ev_data); + if (ifp->fn != NULL) ifp->fn(ifp, ev, ev_data); +} + static void send_syn(struct mg_connection *c); static void mkpay(struct pkt *pkt, void *p) { pkt->pay = - mg_str_n((char *) p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (char *) p)); + mg_str_n((char *) p, (size_t) (&pkt->pay.buf[pkt->pay.len] - (char *) p)); } static uint32_t csumup(uint32_t sum, const void *buf, size_t len) { size_t i; const uint8_t *p = (const uint8_t *) buf; - for (i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8); + for (i = 0; i < len; i++) sum += i & 1 ? p[i] : ((uint32_t) p[i]) << 8; return sum; } static uint16_t csumfin(uint32_t sum) { while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); - return mg_htons(~sum & 0xffff); + return mg_htons((uint16_t) ((uint16_t) ~sum & 0xffff)); } static uint16_t ipcsum(const void *buf, size_t len) { @@ -5064,14 +4419,70 @@ return csumfin(sum); } +#if MG_ENABLE_IPV6 +static void meui64(uint8_t *addr, uint8_t *mac) { + *addr++ = *mac++ ^ (uint8_t) 0x02, *addr++ = *mac++, *addr++ = *mac++; + *addr++ = 0xff, *addr++ = 0xfe; + *addr++ = *mac++, *addr++ = *mac++, *addr = *mac; +} +static void ip6gen(uint8_t *addr, uint8_t *prefix, uint8_t *mac) { + memcpy(addr, prefix, 8); // RFC-4291 2.5.4 + meui64(addr + 8, mac); // 2.5.1 +} +static void ip6genll(uint8_t *addr, uint8_t *mac) { + uint8_t prefix[8] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0}; // RFC-4291 2.5.6 + ip6gen(addr, prefix, mac); // 2.5.1 +} +static void ip6sn(uint64_t *addr, uint64_t *sn_addr) { + // Build solicited-node multicast address from a given unicast IP + // RFC-4291 2.7 + uint8_t *sn = (uint8_t *) sn_addr; + memset(sn_addr, 0, 16); + sn[0] = 0xff; + sn[1] = 0x02; + sn[11] = 0x01; + sn[12] = 0xff; + sn[13] = ((uint8_t *) addr)[13]; + sn[14] = ((uint8_t *) addr)[14]; + sn[15] = ((uint8_t *) addr)[15]; +} +static void ip6_mcastmac(uint8_t *mac, uint64_t *ip6) { + // Build multicast MAC address from a solicited-node + // multicast IPv6 address, RFC-4291 2.7 + uint8_t *ip = (uint8_t *) ip6; + mac[0] = 0x33; + mac[1] = 0x33; + mac[2] = ip[12]; + mac[3] = ip[13]; + mac[4] = ip[14]; + mac[5] = ip[15]; +} + +union ip6addr { + uint64_t u[2]; + uint8_t b[16]; +}; + +static const union ip6addr ip6_allrouters = { + .b = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}}; +static const union ip6addr ip6_allnodes = { + .b = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}}; +static const uint8_t ip6mac_allnodes[6] = {0x33, 0x33, 0x00, 0x00, 0x00, 0x01}; +static const uint8_t ip6mac_allrouters[6] = {0x33, 0x33, 0x00, + 0x00, 0x00, 0x02}; + +#define MG_IP6MATCH(a, b) (a[0] == b[0] && a[1] == b[1]) +#endif + static void settmout(struct mg_connection *c, uint8_t type) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + struct mg_tcpip_if *ifp = c->mgr->ifp; struct connstate *s = (struct connstate *) (c + 1); unsigned n = type == MIP_TTYPE_ACK ? MIP_TCP_ACK_MS - : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS + : type == MIP_TTYPE_ARP ? MIP_ARP_RESP_MS : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS : MIP_TCP_KEEPALIVE_MS; + if (s->ttype == MIP_TTYPE_FIN) return; // skip if 3-way closing s->timer = ifp->now + n; s->ttype = type; MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer)); @@ -5083,7 +4494,7 @@ return n; } -static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) { +void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac) { struct eth *eth = (struct eth *) ifp->tx.buf; struct arp *arp = (struct arp *) (eth + 1); memset(eth->dst, 255, sizeof(eth->dst)); @@ -5094,6 +4505,7 @@ arp->plen = 4; arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip; memcpy(arp->sha, ifp->mac, sizeof(arp->sha)); + if (mac != NULL) memcpy(arp->tha, mac, sizeof(arp->tha)); ether_output(ifp, PDIFF(eth, arp + 1)); } @@ -5102,26 +4514,28 @@ MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip)); MG_INFO((" GW: %M", mg_print_ip4, &ifp->gw)); MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac)); - arp_ask(ifp, ifp->gw); + } else if (ifp->state == MG_TCPIP_STATE_IP) { + mg_tcpip_arp_request(ifp, ifp->gw, NULL); // unsolicited GW ARP request } else if (ifp->state == MG_TCPIP_STATE_UP) { - MG_ERROR(("Link up")); srand((unsigned int) mg_millis()); } else if (ifp->state == MG_TCPIP_STATE_DOWN) { MG_ERROR(("Link down")); } + mg_tcpip_call(ifp, MG_TCPIP_EV_ST_CHG, &ifp->state); } static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint8_t proto, uint32_t ip_src, uint32_t ip_dst, size_t plen) { + // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt struct eth *eth = (struct eth *) ifp->tx.buf; struct ip *ip = (struct ip *) (eth + 1); memcpy(eth->dst, mac_dst, sizeof(eth->dst)); memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC eth->type = mg_htons(0x800); memset(ip, 0, sizeof(*ip)); - ip->ver = 0x45; // Version 4, header length 5 words - ip->frag = 0x40; // Don't fragment + ip->ver = 0x45; // Version 4, header length 5 words + ip->frag = mg_htons(0x4000); // Don't fragment ip->len = mg_htons((uint16_t) (sizeof(*ip) + plen)); ip->ttl = 64; ip->proto = proto; @@ -5131,26 +4545,67 @@ return ip; } -static void tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, - uint16_t sport, uint32_t ip_dst, uint16_t dport, +#if MG_ENABLE_IPV6 +static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + uint8_t next, uint64_t *ip_src, uint64_t *ip_dst, + size_t plen); +#endif + +static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + struct mg_addr *ip_src, struct mg_addr *ip_dst, const void *buf, size_t len) { - struct ip *ip = - tx_ip(ifp, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp)); - struct udp *udp = (struct udp *) (ip + 1); - // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len)); - udp->sport = sport; - udp->dport = dport; + struct ip *ip = NULL; + struct udp *udp; + size_t eth_len; + uint32_t cs; +#if MG_ENABLE_IPV6 + struct ip6 *ip6 = NULL; + if (ip_dst->is_ip6) { + ip6 = tx_ip6(ifp, mac_dst, 17, ip_src->ip6, ip_dst->ip6, + len + sizeof(struct udp)); + udp = (struct udp *) (ip6 + 1); + eth_len = sizeof(struct eth) + sizeof(*ip6) + sizeof(*udp) + len; + } else +#endif + { + ip = tx_ip(ifp, mac_dst, 17, ip_src->ip4, ip_dst->ip4, + len + sizeof(struct udp)); + udp = (struct udp *) (ip + 1); + eth_len = sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len; + } + udp->sport = ip_src->port; + udp->dport = ip_dst->port; udp->len = mg_htons((uint16_t) (sizeof(*udp) + len)); udp->csum = 0; - uint32_t cs = csumup(0, udp, sizeof(*udp)); + cs = csumup(0, udp, sizeof(*udp)); cs = csumup(cs, buf, len); - cs = csumup(cs, &ip->src, sizeof(ip->src)); - cs = csumup(cs, &ip->dst, sizeof(ip->dst)); - cs += (uint32_t) (ip->proto + sizeof(*udp) + len); +#if MG_ENABLE_IPV6 + if (ip_dst->is_ip6) { + cs = csumup(cs, &ip6->src, sizeof(ip6->src)); + cs = csumup(cs, &ip6->dst, sizeof(ip6->dst)); + } else +#endif + { + cs = csumup(cs, &ip->src, sizeof(ip->src)); + cs = csumup(cs, &ip->dst, sizeof(ip->dst)); + } + cs += (uint32_t) (17 + sizeof(*udp) + len); udp->csum = csumfin(cs); memmove(udp + 1, buf, len); - // MG_DEBUG(("UDP LEN %d %d", (int) len, (int) ifp->frame_len)); - ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len); + return (ether_output(ifp, eth_len) == eth_len); +} + +static bool tx_udp4(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, + uint16_t sport, uint32_t ip_dst, uint16_t dport, + const void *buf, size_t len) { + struct mg_addr ips, ipd; + memset(&ips, 0, sizeof(ips)); + ips.ip4 = ip_src; + ips.port = sport; + memset(&ipd, 0, sizeof(ipd)); + ipd.ip4 = ip_dst; + ipd.port = dport; + return tx_udp(ifp, mac_dst, &ips, &ipd, buf, len); } static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src, @@ -5163,26 +4618,35 @@ memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid)); memcpy(&dhcp.options, opts, optslen); if (ciaddr) dhcp.ciaddr = ip_src; - tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp, - sizeof(dhcp)); + tx_udp4(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp, + sizeof(dhcp)); } static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255}; -// RFC-2131 #4.3.6, #4.4.1 +// RFC-2131 #4.3.6, #4.4.1; RFC-2132 #9.8 static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req, uint32_t ip_srv) { - uint8_t opts[] = { - 53, 1, 3, // Type: DHCP request - 55, 2, 1, 3, // GW and mask - 12, 3, 'm', 'i', 'p', // Host name: "mip" - 54, 4, 0, 0, 0, 0, // DHCP server ID - 50, 4, 0, 0, 0, 0, // Requested IP - 255 // End of options - }; - memcpy(opts + 14, &ip_srv, sizeof(ip_srv)); - memcpy(opts + 20, &ip_req, sizeof(ip_req)); - tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false); + uint8_t extra = (uint8_t) ((ifp->enable_req_dns ? 1 : 0) + + (ifp->enable_req_sntp ? 1 : 0)); + size_t len = strlen(ifp->dhcp_name); + size_t olen = 21 + len + extra + 2 + 1; // Total length of options +#define OPTS_MAXLEN (21 + sizeof(ifp->dhcp_name) + 2 + 2 + 1) + uint8_t opts[OPTS_MAXLEN]; // Allocate options (max size possible) + uint8_t *p = opts; + assert(olen <= sizeof(opts)); + memset(opts, 0, sizeof(opts)); + *p++ = 53, *p++ = 1, *p++ = 3; // Type: DHCP request + *p++ = 54, *p++ = 4, memcpy(p, &ip_srv, 4), p += 4; // DHCP server ID + *p++ = 50, *p++ = 4, memcpy(p, &ip_req, 4), p += 4; // Requested IP + *p++ = 12, *p++ = (uint8_t) (len & 255); // DHCP host + memcpy(p, ifp->dhcp_name, len), p += len; // name + *p++ = 55, *p++ = 2 + extra, *p++ = 1, *p++ = 3; // GW, MASK + if (ifp->enable_req_dns) *p++ = 6; // DNS + if (ifp->enable_req_sntp) *p++ = 42; // SNTP + *p++ = 255; // End of options + // assert((size_t) (p - opts) < olen); + tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, olen, 0); MG_DEBUG(("DHCP req sent")); } @@ -5211,22 +4675,30 @@ bool lsn) { struct mg_connection *c = NULL; for (c = mgr->conns; c != NULL; c = c->next) { - if (c->is_arplooking && pkt->arp && - memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0) + if (c->is_arplooking && pkt->arp && pkt->arp->spa == c->rem.ip4) break; +#if MG_ENABLE_IPV6 + if (c->is_arplooking && pkt->icmp6 && pkt->icmp6->type == 136) { + struct ndp_na *na = (struct ndp_na *) (pkt->icmp6 + 1); + if (MG_IP6MATCH(na->addr, c->rem.ip6)) break; + } +#endif + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport && + (!c->loc.is_ip6 || pkt->ip6)) break; - if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break; if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport && - lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport)) + (!c->loc.is_ip6 || pkt->ip6) && lsn == (bool) c->is_listening && + (lsn || c->rem.port == pkt->tcp->sport)) break; } return c; } +static void mac_resolved(struct mg_connection *c); + static void rx_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) { if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) { // ARP request. Make a response, then send - // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4, - // &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa)); + // MG_VERBOSE(("ARP req from %M", mg_print_ip4, &pkt->arp->spa)); struct eth *eth = (struct eth *) ifp->tx.buf; struct arp *arp = (struct arp *) (eth + 1); memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst)); @@ -5243,9 +4715,14 @@ ether_output(ifp, PDIFF(eth, arp + 1)); } else if (pkt->arp->op == mg_htons(2)) { if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; + // MG_VERBOSE(("ARP resp from %M", mg_print_ip4, &pkt->arp->spa)); if (pkt->arp->spa == ifp->gw) { - // Got response for the GW ARP request. Set ifp->gwmac + // Got response for the GW ARP request. Set ifp->gwmac and IP -> READY memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac)); + if (ifp->state == MG_TCPIP_STATE_IP) { + ifp->state = MG_TCPIP_STATE_READY; + onstatechange(ifp); + } } else { struct mg_connection *c = getpeer(ifp->mgr, pkt, false); if (c != NULL && c->is_arplooking) { @@ -5254,35 +4731,37 @@ MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4, c->rem.ip, mg_print_mac, s->mac)); c->is_arplooking = 0; - send_syn(c); - settmout(c, MIP_TTYPE_SYN); + mac_resolved(c); } } } } static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - // MG_DEBUG(("ICMP %d", (int) len)); if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) { size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp); size_t space = ifp->tx.len - hlen, plen = pkt->pay.len; + struct ip *ip; + struct icmp *icmp; if (plen > space) plen = space; - struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src, - sizeof(struct icmp) + plen); - struct icmp *icmp = (struct icmp *) (ip + 1); - memset(icmp, 0, sizeof(*icmp)); // Set csum to 0 + ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src, + sizeof(*icmp) + plen); + icmp = (struct icmp *) (ip + 1); + memset(icmp, 0, sizeof(*icmp)); // Set csum, type, code to 0 memcpy(icmp + 1, pkt->pay.buf, plen); // Copy RX payload to TX icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen); ether_output(ifp, hlen + plen); } } +static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip); + static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) { - uint32_t ip = 0, gw = 0, mask = 0, lease = 0; + uint32_t ip = 0, gw = 0, mask = 0, lease = 0, dns = 0, sntp = 0; uint8_t msgtype = 0, state = ifp->state; // perform size check first, then access fields uint8_t *p = pkt->dhcp->options, - *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; + *end = (uint8_t *) &pkt->pay.buf[pkt->pay.len]; if (end < (uint8_t *) (pkt->dhcp + 1)) return; if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return; while (p + 1 < end && p[0] != 255) { // Parse options RFC-1533 #9 @@ -5291,6 +4770,12 @@ } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) { // GW memcpy(&gw, p + 2, sizeof(gw)); ip = pkt->dhcp->yiaddr; + } else if (ifp->enable_req_dns && p[0] == 6 && p[1] == sizeof(dns) && + p + 6 < end) { // DNS + memcpy(&dns, p + 2, sizeof(dns)); + } else if (ifp->enable_req_sntp && p[0] == 42 && p[1] == sizeof(sntp) && + p + 6 < end) { // SNTP + memcpy(&sntp, p + 2, sizeof(sntp)); } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) { // Lease memcpy(&lease, p + 2, sizeof(lease)); lease = mg_ntohl(lease); @@ -5310,15 +4795,21 @@ ifp->state = MG_TCPIP_STATE_REQ; // REQUESTING state } else if (msgtype == 5) { // DHCPACK if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) { // got an IP + uint64_t rand; ifp->lease_expire = ifp->now + lease * 1000; MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); // assume DHCP server = router until ARP resolves memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); ifp->ip = ip, ifp->gw = gw, ifp->mask = mask; - ifp->state = MG_TCPIP_STATE_READY; // BOUND state - uint64_t rand; + ifp->state = MG_TCPIP_STATE_IP; // BOUND state mg_random(&rand, sizeof(rand)); srand((unsigned int) (rand + mg_millis())); + if (ifp->enable_req_dns && dns != 0) { + setdns4(ifp, &dns); + mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_DNS, &dns); + } + if (ifp->enable_req_sntp && sntp != 0) + mg_tcpip_call(ifp, MG_TCPIP_EV_DHCP_SNTP, &sntp); } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) { // renew ifp->lease_expire = ifp->now + lease * 1000; MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000)); @@ -5330,10 +4821,10 @@ // Simple DHCP server that assigns a next IP address: ifp->ip + 1 static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) { uint8_t op = 0, *p = pkt->dhcp->options, - *end = (uint8_t *) &pkt->raw.buf[pkt->raw.len]; - if (end < (uint8_t *) (pkt->dhcp + 1)) return; + *end = (uint8_t *) &pkt->pay.buf[pkt->pay.len]; // struct dhcp *req = pkt->dhcp; struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}}; + if (end < (uint8_t *) (pkt->dhcp + 1)) return; res.yiaddr = ifp->ip; ((uint8_t *) (&res.yiaddr))[3]++; // Offer our IP + 1 while (p + 1 < end && p[0] != 255) { // Parse options @@ -5359,118 +4850,460 @@ res.magic = pkt->dhcp->magic; res.xid = pkt->dhcp->xid; if (ifp->enable_get_gateway) { - ifp->gw = res.yiaddr; + ifp->gw = res.yiaddr; // set gw IP, best-effort gwmac as DHCP server's memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac)); } - tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67), - op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res)); + tx_udp4(ifp, pkt->eth->src, ifp->ip, mg_htons(67), + op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res)); } } -static void rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { - struct mg_connection *c = getpeer(ifp->mgr, pkt, true); - if (c == NULL) { - // No UDP listener on this port. Should send ICMP, but keep silent. +#if MG_ENABLE_IPV6 +static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + uint8_t next, uint64_t *ip_src, uint64_t *ip_dst, + size_t plen) { + // ifp->tx.buf is 8-bit aligned, keep other headers as pointers, see pkt + struct eth *eth = (struct eth *) ifp->tx.buf; + struct ip6 *ip6 = (struct ip6 *) (eth + 1); + memcpy(eth->dst, mac_dst, sizeof(eth->dst)); + memcpy(eth->src, ifp->mac, sizeof(eth->src)); // Use our MAC + eth->type = mg_htons(0x86dd); + memset(ip6, 0, sizeof(*ip6)); + ip6->ver = 0x60; // Version 6, traffic class 0 + ip6->plen = mg_htons((uint16_t) plen); + ip6->next = next; + ip6->hops = 255; // NDP requires max + ip6->src[0] = *ip_src++; + ip6->src[1] = *ip_src; + ip6->dst[0] = *ip_dst++; + ip6->dst[1] = *ip_dst; + return ip6; +} + +static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + uint64_t *ip_src, uint64_t *ip_dst, uint8_t type, + uint8_t code, const void *buf, size_t len) { + struct ip6 *ip6; + struct icmp6 *icmp6; + uint32_t cs; + ip6 = tx_ip6(ifp, mac_dst, 58, ip_src, ip_dst, sizeof(*icmp6) + len); + icmp6 = (struct icmp6 *) (ip6 + 1); + memset(icmp6, 0, sizeof(*icmp6)); // Set csum to 0 + icmp6->type = type; + icmp6->code = code; + memcpy(icmp6 + 1, buf, len); // Copy payload + icmp6->csum = 0; // RFC-4443 2.3, RFC-8200 8.1 + cs = csumup(0, icmp6, sizeof(*icmp6)); + cs = csumup(cs, buf, len); + cs = csumup(cs, ip_src, 16); + cs = csumup(cs, ip_dst, 16); + cs += (uint32_t) (58 + sizeof(*icmp6) + len); + icmp6->csum = csumfin(cs); + ether_output(ifp, sizeof(struct eth) + sizeof(*ip6) + sizeof(*icmp6) + len); +} + +// Neighbor Discovery Protocol, RFC-4861 +// Neighbor Advertisement, 4.4 +static void tx_ndp_na(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + uint64_t *ip_src, uint64_t *ip_dst, bool solicited, + uint8_t *mac) { + uint8_t data[28]; + memset(data, 0, sizeof(data)); + data[0] = solicited ? 0x60 : 0x20; // O + S + memcpy(data + 4, ip_src, 16); // Target address + data[20] = 2, data[21] = 1; // 4.6.1, target MAC + memcpy(data + 22, mac, 6); + tx_icmp6(ifp, mac_dst, ip_src, ip_dst, 136, 0, data, sizeof(data)); +} + +static void onstate6change(struct mg_tcpip_if *ifp); + +static void rx_ndp_na(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct ndp_na *na = (struct ndp_na *) (pkt->icmp6 + 1); + uint8_t *opts = (uint8_t *) (na + 1); + if ((na->res[0] & 0x40) == 0) return; // not "solicited" + if (*opts++ != 2) return; // no target hwaddr + if (*opts++ != 1) return; // don't know what to do with this layer 2 + MG_VERBOSE(("NDP NA resp from %M", mg_print_ip6, (char *) &na->addr)); + if (MG_IP6MATCH(na->addr, ifp->gw6)) { + // Got response for the GW NS request. Set ifp->gw6mac and IP6 -> READY + memcpy(ifp->gw6mac, opts, sizeof(ifp->gw6mac)); + if (ifp->state6 == MG_TCPIP_STATE_IP) { + ifp->state6 = MG_TCPIP_STATE_READY; + onstate6change(ifp); + } } else { - c->rem.port = pkt->udp->sport; - memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); - struct connstate *s = (struct connstate *) (c + 1); - memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); - if (c->recv.len >= MG_MAX_RECV_SIZE) { - mg_error(c, "max_recv_buf_size reached"); - } else if (c->recv.size - c->recv.len < pkt->pay.len && - !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { - mg_error(c, "oom"); - } else { - memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); - c->recv.len += pkt->pay.len; - mg_call(c, MG_EV_READ, &pkt->pay.len); + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + if (c != NULL && c->is_arplooking) { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, opts, sizeof(s->mac)); + MG_DEBUG(("%lu NDP resolved %M -> %M", c->id, mg_print_ip6, c->rem.ip, + mg_print_mac, s->mac)); + c->is_arplooking = 0; + mac_resolved(c); } } } -static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, uint32_t dst_ip, - uint8_t flags, uint16_t sport, uint16_t dport, - uint32_t seq, uint32_t ack, const void *buf, size_t len) { -#if 0 - uint8_t opts[] = {2, 4, 5, 0xb4, 4, 2, 0, 0}; // MSS = 1460, SACK permitted - if (flags & TH_SYN) { - // Handshake? Set MSS +// Neighbor Solicitation, 4.3 +static void rx_ndp_ns(struct mg_tcpip_if *ifp, struct pkt *pkt) { + uint64_t target[2]; + if (pkt->pay.len < sizeof(target)) return; + memcpy(target, pkt->pay.buf + 4, sizeof(target)); + if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) + tx_ndp_na(ifp, (uint8_t *) pkt->pay.buf + 22, target, pkt->ip6->src, true, + ifp->mac); +} + +static void tx_ndp_ns(struct mg_tcpip_if *ifp, uint64_t *ip_dst, uint8_t *mac) { + uint8_t payload[4 + 16 + 8]; + uint64_t unspec_ip[2] = {0, 0}; + size_t payload_len; + bool mcast_ns = true; + uint64_t mcast_ip[2] = {0, 0}; + uint8_t mcast_mac[6]; + + memset(payload, 0, sizeof(payload)); + memcpy(payload + 4, ip_dst, 16); + for (int i = 0; i < 6; i++) { + if (mac[i] != 0xff) { + mcast_ns = false; + break; + } + } + if (mcast_ns) { + ip6sn(ip_dst, mcast_ip); + ip6_mcastmac(mcast_mac, mcast_ip); + } + // TODO(robertc2000): using only link-local IP addr for now + // We might consider to add an option to use either link-local or global IP + if (!MG_IP6MATCH(ifp->ip6ll, unspec_ip)) { + payload[20] = payload[21] = 1; // Type = 1; Length = 1 + memcpy(payload + 22, ifp->mac, 6); + payload_len = sizeof(payload); + } else { + payload_len = sizeof(payload) - 8; + } + tx_icmp6(ifp, mcast_ns ? mcast_mac : mac, ifp->ip6ll, + mcast_ns ? mcast_ip : ip_dst, 135, 0, payload, payload_len); +} + +// Router Solicitation, 4.1 +static void tx_ndp_rs(struct mg_tcpip_if *ifp, uint64_t *ip_dst, uint8_t *mac) { + // Note: currently, this function only sends multicast RS packets + (void) ip_dst; + (void) mac; + uint8_t payload[4 + 8]; // reserved + optional source MAC addr + size_t payload_len = 4; + uint64_t unspec_ip[2] = {0, 0}; + + memset(payload, 0, sizeof(payload)); + if (!MG_IP6MATCH(ifp->ip6ll, unspec_ip)) { + payload[4] = payload[5] = 1; // Type = 1; Length = 1 + memcpy(payload + 6, ifp->mac, 6); + payload_len += 8; + } + tx_icmp6(ifp, (uint8_t *) ip6mac_allrouters, ifp->ip6ll, + (uint64_t *) ip6_allrouters.u, 133, 0, payload, payload_len); + MG_DEBUG(("NDP Router Solicitation sent")); +} + +static bool fill_global(uint64_t *ip6, uint8_t *prefix, uint8_t prefix_len, + uint8_t *mac) { + uint8_t full = prefix_len / 8; + uint8_t rem = prefix_len % 8; + if (full >= 8 && rem != 0) { + MG_ERROR(("Prefix length > 64, UNSUPPORTED")); + return false; + } else if (full == 8 && rem == 0) { + ip6gen((uint8_t *) ip6, prefix, mac); + } else { + ip6[0] = ip6[1] = 0; // already zeroed before firing RS... + if (full) memcpy(ip6, prefix, full); + if (rem) { + uint8_t mask = (uint8_t) (0xFF << (8 - rem)); + ((uint8_t *) ip6)[full] = prefix[full] & mask; + } + meui64(((uint8_t *) &ip6[1]), mac); // RFC-4291 2.5.4, 2.5.1 + } + return true; +} + +// Router Advertisement, 4.2 +static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) { + if (pkt->pay.len < 12) return; + struct ndp_ra *ra = (struct ndp_ra *) (pkt->icmp6 + 1); + uint8_t *opts = (uint8_t *) (ra + 1); + size_t opt_left = pkt->pay.len - 12; + + if (ifp->state6 == MG_TCPIP_STATE_UP) { + MG_DEBUG(("Received NDP RA")); + memcpy(ifp->gw6, (uint8_t *) pkt->ip6->src, 16); // fill gw6 address + // parse options + while (opt_left >= 2) { + uint8_t type = opts[0], len = opts[1]; + size_t length = (size_t) len * 8; + if (length == 0 || length > opt_left) break; // malformed + if (type == 1 && length >= 8) { + // Received router's MAC address + ifp->state6 = MG_TCPIP_STATE_READY; + memcpy(ifp->gw6mac, opts + 2, 6); + } else if (type == 5 && length >= 8) { + // process MTU if available + uint32_t mtu = mg_ntohl(*(uint32_t *) (opts + 4)); + MG_INFO(("got a nice MTU: %u, do you care ?", mtu)); + // TODO(): **** This is an IPv6-given MTU, ifp->MTU is a LINK MTU, are + // we talkin'bout the same ? *** + } else if (type == 3 && length >= 32) { + // process prefix, 4.6.2 + uint8_t prefix_len = opts[2]; + uint8_t pfx_flags = opts[3]; // L=0x80, A=0x40 + uint32_t valid = mg_ntohl(*(uint32_t *) (opts + 4)); + uint32_t pref_lifetime = mg_ntohl(*(uint32_t *) (opts + 8)); + uint8_t *prefix = opts + 16; + + // TODO (robertc2000): handle prefix options if necessary + (void) prefix_len; + (void) pfx_flags; + (void) valid; + (void) pref_lifetime; + (void) prefix; + + // fill prefix length and global + ifp->prefix_len = prefix_len; + if (!fill_global(ifp->ip6, prefix, prefix_len, ifp->mac)) return; + } + opts += length; + opt_left -= length; + } + + if (ifp->state6 != MG_TCPIP_STATE_READY) { + tx_ndp_ns(ifp, ifp->gw6, ifp->gw6mac); // unsolicited GW MAC resolution + ifp->state6 = MG_TCPIP_STATE_IP; + } + onstate6change(ifp); + } +} + +static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) { + switch (pkt->icmp6->type) { + case 128: { // Echo Request, RFC-4443 4.1 + uint64_t target[2]; + memcpy(target, pkt->ip6->dst, sizeof(target)); + if (MG_IP6MATCH(target, ifp->ip6ll) || MG_IP6MATCH(target, ifp->ip6)) { + size_t hlen = + sizeof(struct eth) + sizeof(struct ip6) + sizeof(struct icmp6); + size_t space = ifp->tx.len - hlen, plen = pkt->pay.len; + if (plen > space) plen = space; // Copy (truncated) RX payload to TX + // Echo Reply, 4.2 + tx_icmp6(ifp, pkt->eth->src, pkt->ip6->dst, pkt->ip6->src, 129, 0, + pkt->pay.buf, plen); + } + } break; + case 134: // Router Advertisement + rx_ndp_ra(ifp, pkt); + break; + case 135: // Neighbor Solicitation + rx_ndp_ns(ifp, pkt); + break; + case 136: // Neighbor Advertisement + rx_ndp_na(ifp, pkt); + break; + } +} + +static void onstate6change(struct mg_tcpip_if *ifp) { + if (ifp->state6 == MG_TCPIP_STATE_READY) { + MG_INFO(("READY, IP: %M", mg_print_ip6, &ifp->ip6)); + MG_INFO((" GW: %M", mg_print_ip6, &ifp->gw6)); + MG_INFO((" MAC: %M", mg_print_mac, &ifp->mac)); + } else if (ifp->state6 == MG_TCPIP_STATE_IP) { + tx_ndp_ns(ifp, ifp->gw6, ifp->gw6mac); // unsolicited GW MAC resolution + } else if (ifp->state6 == MG_TCPIP_STATE_UP) { + MG_INFO(("IP: %M", mg_print_ip6, &ifp->ip6ll)); + } + if (ifp->state6 != MG_TCPIP_STATE_UP && ifp->state6 != MG_TCPIP_STATE_DOWN) + mg_tcpip_call(ifp, MG_TCPIP_EV_ST6_CHG, &ifp->state6); +} +#endif + +static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) { + struct mg_connection *c = getpeer(ifp->mgr, pkt, true); + struct connstate *s; + if (c == NULL) return false; // No UDP listener on this port + s = (struct connstate *) (c + 1); + c->rem.port = pkt->udp->sport; +#if MG_ENABLE_IPV6 + if (c->loc.is_ip6) { + c->rem.ip6[0] = pkt->ip6->src[0], c->rem.ip6[1] = pkt->ip6->src[1], + c->rem.is_ip6 = true; + } else +#endif + { + c->rem.ip4 = pkt->ip->src; + } + memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); + if (c->recv.len >= MG_MAX_RECV_SIZE) { + mg_error(c, "max_recv_buf_size reached"); + } else if (c->recv.size - c->recv.len < pkt->pay.len && + !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { + mg_error(c, "oom"); + } else { + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); + c->recv.len += pkt->pay.len; + mg_call(c, MG_EV_READ, &pkt->pay.len); + } + return true; +} + +static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, + struct mg_addr *ip_src, struct mg_addr *ip_dst, + uint8_t flags, uint32_t seq, uint32_t ack, const void *buf, + size_t len) { + struct ip *ip = NULL; + struct tcp *tcp; + uint16_t opts[4 / 2], mss; +#if MG_ENABLE_IPV6 + struct ip6 *ip6 = NULL; + mss = (uint16_t) (ifp->mtu - 60); // RFC-9293 3.7.1; RFC-6691 2 +#else + mss = (uint16_t) (ifp->mtu - 40); // RFC-9293 3.7.1; RFC-6691 2 +#endif + if (flags & TH_SYN) { // Send MSS + opts[0] = mg_htons(0x0204); // RFC-9293 3.2 + opts[1] = mg_htons(mss); buf = opts; len = sizeof(opts); } +#if MG_ENABLE_IPV6 + if (ip_dst->is_ip6) { + ip6 = tx_ip6(ifp, mac_dst, 6, ip_src->ip6, ip_dst->ip6, + sizeof(struct tcp) + len); + tcp = (struct tcp *) (ip6 + 1); + } else #endif - struct ip *ip = - tx_ip(ifp, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len); - struct tcp *tcp = (struct tcp *) (ip + 1); + { + ip = tx_ip(ifp, mac_dst, 6, ip_src->ip4, ip_dst->ip4, + sizeof(struct tcp) + len); + tcp = (struct tcp *) (ip + 1); + } memset(tcp, 0, sizeof(*tcp)); if (buf != NULL && len) memmove(tcp + 1, buf, len); - tcp->sport = sport; - tcp->dport = dport; + tcp->sport = ip_src->port; + tcp->dport = ip_dst->port; tcp->seq = seq; tcp->ack = ack; tcp->flags = flags; tcp->win = mg_htons(MIP_TCP_WIN); tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4); - // if (flags & TH_SYN) tcp->off = 0x70; // Handshake? header size 28 bytes - - uint32_t cs = 0; - uint16_t n = (uint16_t) (sizeof(*tcp) + len); - uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)}; - cs = csumup(cs, tcp, n); - cs = csumup(cs, &ip->src, sizeof(ip->src)); - cs = csumup(cs, &ip->dst, sizeof(ip->dst)); - cs = csumup(cs, pseudo, sizeof(pseudo)); - tcp->csum = csumfin(cs); - MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src, - mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst, - mg_ntohs(tcp->dport), tcp->flags, len)); - // mg_hexdump(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len); + if (flags & TH_SYN) tcp->off += (uint8_t) (sizeof(opts) / 4 << 4); + { + uint32_t cs = 0; + uint16_t n = (uint16_t) (sizeof(*tcp) + len); + cs = csumup(cs, tcp, n); +#if MG_ENABLE_IPV6 + if (ip_dst->is_ip6) { + cs = csumup(cs, &ip6->src, sizeof(ip6->src)); + cs = csumup(cs, &ip6->dst, sizeof(ip6->dst)); + cs += (uint32_t) (6 + n); + MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip6, &ip6->src, + mg_ntohs(tcp->sport), mg_print_ip6, &ip6->dst, + mg_ntohs(tcp->dport), tcp->flags, len)); + } else +#endif + { + uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), + (uint8_t) (n & 255)}; + cs = csumup(cs, &ip->src, sizeof(ip->src)); + cs = csumup(cs, &ip->dst, sizeof(ip->dst)); + cs = csumup(cs, pseudo, sizeof(pseudo)); + MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src, + mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst, + mg_ntohs(tcp->dport), tcp->flags, len)); + } + tcp->csum = csumfin(cs); + } return ether_output(ifp, PDIFF(ifp->tx.buf, tcp + 1) + len); } -static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt, - uint8_t flags, uint32_t seq, const void *buf, - size_t len) { - uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0; - return tx_tcp(ifp, pkt->eth->src, pkt->ip->src, flags, pkt->tcp->dport, - pkt->tcp->sport, seq, mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), - buf, len); +static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt, + uint8_t flags, uint32_t seqno) { + uint32_t ackno = mg_htonl(mg_ntohl(pkt->tcp->seq) + (uint32_t) pkt->pay.len + + ((pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0)); + struct mg_addr ips, ipd; + memset(&ips, 0, sizeof(ips)); + memset(&ipd, 0, sizeof(ipd)); + if (pkt->ip != NULL) { + ips.ip4 = pkt->ip->dst; + ipd.ip4 = pkt->ip->src; + } else { + ips.ip6[0] = pkt->ip6->dst[0], ips.ip6[1] = pkt->ip6->dst[1]; + ipd.ip6[0] = pkt->ip6->src[0], ipd.ip6[1] = pkt->ip6->src[1]; + ips.is_ip6 = true; + ipd.is_ip6 = true; + } + ips.port = pkt->tcp->dport; + ipd.port = pkt->tcp->sport; + return tx_tcp(ifp, pkt->eth->src, &ips, &ipd, flags, seqno, ackno, NULL, 0); +} + +static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) { + return tx_tcp_ctrlresp(ifp, pkt, toack ? TH_RST : (TH_RST | TH_ACK), + toack ? pkt->tcp->ack : 0); } static struct mg_connection *accept_conn(struct mg_connection *lsn, - struct pkt *pkt) { + struct pkt *pkt, uint16_t mss) { struct mg_connection *c = mg_alloc_conn(lsn->mgr); + struct connstate *s; if (c == NULL) { MG_ERROR(("OOM")); return NULL; } - struct connstate *s = (struct connstate *) (c + 1); + s = (struct connstate *) (c + 1); + s->dmss = mss; // from options in client SYN s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); settmout(c, MIP_TTYPE_KEEPALIVE); - memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t)); +#if MG_ENABLE_IPV6 + if (lsn->loc.is_ip6) { + c->rem.ip6[0] = pkt->ip6->src[0], c->rem.ip6[1] = pkt->ip6->src[1], + c->rem.is_ip6 = true; + c->loc.ip6[0] = c->mgr->ifp->ip6[0], c->loc.ip6[1] = c->mgr->ifp->ip6[1], + c->loc.is_ip6 = true; + // TODO(): compare lsn to link-local, or rem as link-local: use ll instead + } else +#endif + { + c->rem.ip4 = pkt->ip->src; + c->loc.ip4 = c->mgr->ifp->ip; + } c->rem.port = pkt->tcp->sport; + c->loc.port = lsn->loc.port; MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem)); LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c); c->is_accepted = 1; c->is_hexdumping = lsn->is_hexdumping; c->pfn = lsn->pfn; - c->loc = lsn->loc; c->pfn_data = lsn->pfn_data; c->fn = lsn->fn; c->fn_data = lsn->fn_data; + c->is_tls = lsn->is_tls; mg_call(c, MG_EV_OPEN, NULL); mg_call(c, MG_EV_ACCEPT, NULL); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() return c; } static size_t trim_len(struct mg_connection *c, size_t len) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8; + struct mg_tcpip_if *ifp = c->mgr->ifp; + size_t eth_h_len = 14, tcp_max_h_len = 60, udp_h_len = 8; + size_t ip_max_h_len = c->rem.is_ip6 ? 40 : 24; // we don't send options size_t max_headers_len = eth_h_len + ip_max_h_len + (c->is_udp ? udp_h_len : tcp_max_h_len); - size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len; + size_t min_mtu = c->rem.is_ip6 ? 1280 + : c->is_udp ? 68 /* RFC-791 */ + : max_headers_len - eth_h_len; // If the frame exceeds the available buffer, trim the length if (len + max_headers_len > ifp->tx.len) { @@ -5492,17 +5325,35 @@ return len; } +static bool udp_send(struct mg_connection *c, const void *buf, size_t len) { + struct mg_tcpip_if *ifp = c->mgr->ifp; + struct connstate *s = (struct connstate *) (c + 1); + struct mg_addr ips; + memset(&ips, 0, sizeof(ips)); +#if MG_ENABLE_IPV6 + if (c->loc.is_ip6) { + ips.ip6[0] = ifp->ip6[0], ips.ip6[1] = ifp->ip6[1], ips.is_ip6 = true; + // TODO(): detect link-local (c->rem) and use it + } else +#endif + { + ips.ip4 = ifp->ip; + } + ips.port = c->loc.port; + return tx_udp(ifp, s->mac, &ips, &c->rem, buf, len); +} + long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; struct connstate *s = (struct connstate *) (c + 1); - uint32_t dst_ip = *(uint32_t *) c->rem.ip; len = trim_len(c, len); if (c->is_udp) { - tx_udp(ifp, s->mac, ifp->ip, c->loc.port, dst_ip, c->rem.port, buf, len); - } else { - size_t sent = - tx_tcp(ifp, s->mac, dst_ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq), mg_htonl(s->ack), buf, len); + if (!udp_send(c, buf, len)) return MG_IO_WAIT; + } else { // TCP, cap to peer's MSS + struct mg_tcpip_if *ifp = c->mgr->ifp; + size_t sent; + if (len > s->dmss) len = s->dmss; // RFC-6691: reduce if sending opts + sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_PUSH | TH_ACK, + mg_htonl(s->seq), mg_htonl(s->ack), buf, len); if (sent == 0) { return MG_IO_WAIT; } else if (sent == (size_t) -1) { @@ -5515,14 +5366,23 @@ return (long) len; } -static void handle_tls_recv(struct mg_connection *c, struct mg_iobuf *io) { - long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len); - if (n == MG_IO_ERR) { - mg_error(c, "TLS recv error"); - } else if (n > 0) { - // Decrypted successfully - trigger MG_EV_READ - io->len += (size_t) n; - mg_call(c, MG_EV_READ, &n); +static void handle_tls_recv(struct mg_connection *c) { + size_t avail = mg_tls_pending(c); + size_t min = avail > MG_MAX_RECV_SIZE ? MG_MAX_RECV_SIZE : avail; + struct mg_iobuf *io = &c->recv; + if (io->size - io->len < min && !mg_iobuf_resize(io, io->len + min)) { + mg_error(c, "oom"); + } else { + // Decrypt data directly into c->recv + long n = mg_tls_recv(c, io->buf != NULL ? &io->buf[io->len] : io->buf, + io->size - io->len); + if (n == MG_IO_ERR) { + mg_error(c, "TLS recv error"); + } else if (n > 0) { + // Decrypted successfully - trigger MG_EV_READ + io->len += (size_t) n; + mg_call(c, MG_EV_READ, &n); + } // else n < 0: outstanding data to be moved to c->recv } } @@ -5530,153 +5390,264 @@ struct connstate *s = (struct connstate *) (c + 1); struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv; uint32_t seq = mg_ntohl(pkt->tcp->seq); - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); if (pkt->tcp->flags & TH_FIN) { + uint8_t flags = TH_ACK; + if (mg_ntohl(pkt->tcp->seq) != s->ack) { + MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), "", 0); + return; + } // If we initiated the closure, we reply with ACK upon receiving FIN // If we didn't initiate it, we reply with FIN as part of the normal TCP // closure process - uint8_t flags = TH_ACK; s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1); + s->fin_rcvd = true; if (c->is_draining && s->ttype == MIP_TTYPE_FIN) { if (s->seq == mg_htonl(pkt->tcp->ack)) { // Simultaneous closure ? s->seq++; // Yes. Increment our SEQ } else { // Otherwise, s->seq = mg_htonl(pkt->tcp->ack); // Set to peer's ACK } + s->twclosure = true; } else { flags |= TH_FIN; c->is_draining = 1; settmout(c, MIP_TTYPE_FIN); } - tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags, - c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); - } else if (pkt->pay.len == 0) { - // TODO(cpq): handle this peer's ACK + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, flags, mg_htonl(s->seq), + mg_htonl(s->ack), "", 0); + if (pkt->pay.len == 0) return; // if no data, we're done + } else if (pkt->pay.len <= 1 && mg_ntohl(pkt->tcp->seq) == s->ack - 1) { + // Keep-Alive (RFC-9293 3.8.4, allow erroneous implementations) + MG_VERBOSE(("%lu keepalive ACK", c->id)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), NULL, 0); + return; // no data to process + } else if (pkt->pay.len == 0) { // this is an ACK + if (s->fin_rcvd && s->ttype == MIP_TTYPE_FIN) s->twclosure = true; + return; // no data to process } else if (seq != s->ack) { uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); if (s->ack == ack) { MG_VERBOSE(("ignoring duplicate pkt")); } else { MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack)); - tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, - c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", - 0); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), "", 0); } + return; // drop it } else if (io->size - io->len < pkt->pay.len && !mg_iobuf_resize(io, io->len + pkt->pay.len)) { mg_error(c, "oom"); - } else { - // Copy TCP payload into the IO buffer. If the connection is plain text, - // we copy to c->recv. If the connection is TLS, this data is encrypted, - // therefore we copy that encrypted data to the c->rtls iobuffer instead, - // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will - // call back mg_io_recv() which grabs raw data from c->rtls - memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len); - io->len += pkt->pay.len; - - MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack)); - // Advance ACK counter - s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); - s->unacked += pkt->pay.len; - // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack; - if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) { - // Send ACK immediately - MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked)); - tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, - c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), NULL, - 0); - s->unacked = 0; - s->acked = s->ack; - if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE); - } else { - // if not already running, setup a timer to send an ACK later - if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK); - } + return; // drop it + } + // Copy TCP payload into the IO buffer. If the connection is plain text, + // we copy to c->recv. If the connection is TLS, this data is encrypted, + // therefore we copy that encrypted data to the c->rtls iobuffer instead, + // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will + // call back mg_io_recv() which grabs raw data from c->rtls + memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len); + io->len += pkt->pay.len; + MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack)); + // Advance ACK counter + s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); + s->unacked += pkt->pay.len; + // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack; + if (s->unacked > MIP_TCP_WIN / 2 && s->acked != s->ack) { + // Send ACK immediately + MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked)); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), NULL, 0); + s->unacked = 0; + s->acked = s->ack; + if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE); + } else { + // if not already running, setup a timer to send an ACK later + if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK); + } + if (c->is_tls) { + c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c); + } else { + // Plain text connection, data is already in c->recv, trigger MG_EV_READ + mg_call(c, MG_EV_READ, &pkt->pay.len); + } +} - if (c->is_tls && c->is_tls_hs) { - mg_tls_handshake(c); - } else if (c->is_tls) { - // TLS connection. Make room for decrypted data in c->recv - io = &c->recv; - if (io->size - io->len < pkt->pay.len && - !mg_iobuf_resize(io, io->len + pkt->pay.len)) { - mg_error(c, "oom"); - } else { - // Decrypt data directly into c->recv - handle_tls_recv(c, io); - } - } else { - // Plain text connection, data is already in c->recv, trigger - // MG_EV_READ - mg_call(c, MG_EV_READ, &pkt->pay.len); - } +// TCP backlog +struct mg_backlog { + uint16_t port, mss; // use port=0 for available entries + uint8_t age; +}; + +static int backlog_insert(struct mg_connection *c, uint16_t port, + uint16_t mss) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port != 0) continue; + p[i].age = 2; // remove after two calls, average 1.5 call rate + p[i].port = port, p[i].mss = mss; + return (int) i; + } + return -1; +} + +static struct mg_backlog *backlog_retrieve(struct mg_connection *c, + uint16_t key, uint16_t port) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + if (key >= sizeof(c->data) / sizeof(*p)) return NULL; + if (p[key].port != port) return NULL; + p += key; + return p; +} + +static void backlog_remove(struct mg_connection *c, uint16_t key) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + p[key].port = 0; +} + +static void backlog_maintain(struct mg_connection *c) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; // dec age and remove those where it reaches 0 + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port == 0) continue; + if (p[i].age != 0) --p[i].age; + if (p[i].age == 0) p[i].port = 0; + } +} + +static void backlog_poll(struct mg_mgr *mgr) { + struct mg_connection *c = NULL; + for (c = mgr->conns; c != NULL; c = c->next) { + if (!c->is_udp && c->is_listening) backlog_maintain(c); + } +} + +// process options (MSS) +static void handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) { + uint8_t *opts = (uint8_t *) (tcp + 1); + int len = 4 * ((int) (tcp->off >> 4) - ((int) sizeof(*tcp) / 4)); + s->dmss = ip6 ? 1220 : 536; // assume default, RFC-9293 3.7.1 + while (len > 0) { // RFC-9293 3.1 3.2 + uint8_t kind = opts[0], optlen = 1; + if (kind != 1) { // No-Operation + if (kind == 0) break; // End of Option List + optlen = opts[1]; + if (kind == 2 && optlen == 4) // set received MSS + s->dmss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]); + } + MG_VERBOSE(("kind: %u, optlen: %u, len: %d\n", kind, optlen, len)); + opts += optlen; + len -= optlen; } } static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) { struct mg_connection *c = getpeer(ifp->mgr, pkt, false); struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1); -#if 0 - MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len)); -#endif + // Order is VERY important; RFC-9293 3.5.2 + // - check clients (Group 1) and established connections (Group 3) if (c != NULL && c->is_connecting && pkt->tcp->flags == (TH_SYN | TH_ACK)) { + // client got a server connection accept + handle_opt(s, pkt->tcp, pkt->ip6 != NULL); // process options (MSS) s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1; - tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0); + tx_tcp_ctrlresp(ifp, pkt, TH_ACK, pkt->tcp->ack); c->is_connecting = 0; // Client connected settmout(c, MIP_TTYPE_KEEPALIVE); mg_call(c, MG_EV_CONNECT, NULL); // Let user know + if (c->is_tls_hs) mg_tls_handshake(c); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() } else if (c != NULL && c->is_connecting && pkt->tcp->flags != TH_ACK) { - // mg_hexdump(pkt->raw.buf, pkt->raw.len); - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); + mg_error(c, "connection refused"); } else if (c != NULL && pkt->tcp->flags & TH_RST) { + // TODO(): validate RST is within window (and optional with proper ACK) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 } else if (c != NULL) { -#if 0 - MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len, - mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport), - mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport))); - mg_hexdump(pkt->pay.buf, pkt->pay.len); -#endif + // process segment s->tmiss = 0; // Reset missed keep-alive counter if (s->ttype == MIP_TTYPE_KEEPALIVE) // Advance keep-alive timer settmout(c, MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending read_conn(c, pkt); // Override timer with ACK timeout if needed - } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) { - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (pkt->tcp->flags & TH_RST) { - if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 - // ignore RST if not connected - } else if (pkt->tcp->flags & TH_SYN) { - // Use peer's source port as ISN, in order to recognise the handshake - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport)); - tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); - } else if (pkt->tcp->flags & TH_FIN) { - tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) { - accept_conn(c, pkt); - } else if (!c->is_accepted) { // no peer - tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); - } else { - // MG_VERBOSE(("dropped silently..")); - } + } else + // - we don't listen on that port; RFC-9293 3.5.2 Group 1 + // - check listening connections; RFC-9293 3.5.2 Group 2 + if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) { + // not listening on that port + if (!(pkt->tcp->flags & TH_RST)) { + tx_tcp_rst(ifp, pkt, pkt->tcp->flags & TH_ACK); + } // else silently discard + } else if (pkt->tcp->flags == TH_SYN) { + // listener receives a connection request + struct connstate cs; // At this point, s = NULL, there is no connection + int key; + uint32_t isn; + if (pkt->tcp->sport != 0) { + handle_opt(&cs, pkt->tcp, pkt->ip6 != NULL); // process options (MSS) + key = backlog_insert(c, pkt->tcp->sport, + cs.dmss); // backlog options (MSS) + if (key < 0) return; // no room in backlog, discard SYN, client retries + // Use peer's src port and bl key as ISN, to later identify the + // handshake + isn = (mg_htonl(((uint32_t) key << 16) | mg_ntohs(pkt->tcp->sport))); + tx_tcp_ctrlresp(ifp, pkt, TH_SYN | TH_ACK, isn); + } // what should we do when port=0 ? Linux takes port 0 as any other + // port + } else if (pkt->tcp->flags == TH_ACK) { + // listener receives an ACK + struct mg_backlog *b = NULL; + if ((uint16_t) (mg_htonl(pkt->tcp->ack) - 1) == + mg_htons(pkt->tcp->sport)) { + uint16_t key = (uint16_t) ((mg_htonl(pkt->tcp->ack) - 1) >> 16); + b = backlog_retrieve(c, key, pkt->tcp->sport); + if (b != NULL) { // ACK is a response to a SYN+ACK + accept_conn(c, pkt, b->mss); // pass options + backlog_remove(c, key); + } // else not an actual match, reset + } + if (b == NULL) tx_tcp_rst(ifp, pkt, true); + } else if (pkt->tcp->flags & TH_RST) { + // silently discard + } else if (pkt->tcp->flags & TH_ACK) { // ACK + something else != RST + tx_tcp_rst(ifp, pkt, true); + } else if (pkt->tcp->flags & TH_SYN) { // SYN + something else != ACK + tx_tcp_rst(ifp, pkt, false); + } // else silently discard } static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) { - if (pkt->ip->frag & IP_MORE_FRAGS_MSK || pkt->ip->frag & IP_FRAG_OFFSET_MSK) { - if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->ip + 1); - if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->ip + 1); - struct mg_connection *c = getpeer(ifp->mgr, pkt, false); + uint8_t ihl; + uint16_t frag, len; + if (pkt->pay.len < sizeof(*pkt->ip)) return; // Truncated + if ((pkt->ip->ver >> 4) != 4) return; // Not IP + ihl = pkt->ip->ver & 0x0F; + if (ihl < 5) return; // bad IHL + if (pkt->pay.len < (uint16_t) (ihl * 4)) return; // Truncated / malformed + // There can be link padding, take length from IP header + len = mg_ntohs(pkt->ip->len); // IP datagram length + if (len < (uint16_t) (ihl * 4) || len > pkt->pay.len) return; // malformed + pkt->pay.len = len; // strip padding + mkpay(pkt, (uint32_t *) pkt->ip + ihl); // account for opts + frag = mg_ntohs(pkt->ip->frag); + if (frag & IP_MORE_FRAGS_MSK || frag & IP_FRAG_OFFSET_MSK) { + struct mg_connection *c; + if (pkt->ip->proto == 17) pkt->udp = (struct udp *) (pkt->pay.buf); + if (pkt->ip->proto == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf); + c = getpeer(ifp->mgr, pkt, false); if (c) mg_error(c, "Received fragmented packet"); } else if (pkt->ip->proto == 1) { - pkt->icmp = (struct icmp *) (pkt->ip + 1); + pkt->icmp = (struct icmp *) (pkt->pay.buf); if (pkt->pay.len < sizeof(*pkt->icmp)) return; mkpay(pkt, pkt->icmp + 1); rx_icmp(ifp, pkt); } else if (pkt->ip->proto == 17) { - pkt->udp = (struct udp *) (pkt->ip + 1); - if (pkt->pay.len < sizeof(*pkt->udp)) return; + pkt->udp = (struct udp *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->udp)) return; // truncated + // Take length from UDP header + len = mg_ntohs(pkt->udp->len); // UDP datagram length + if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return; // malformed + pkt->pay.len = len; // strip excess data mkpay(pkt, pkt->udp + 1); MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst, @@ -5689,76 +5660,152 @@ pkt->dhcp = (struct dhcp *) (pkt->udp + 1); mkpay(pkt, pkt->dhcp + 1); rx_dhcp_server(ifp, pkt); - } else { - rx_udp(ifp, pkt); + } else if (!rx_udp(ifp, pkt)) { + // Should send ICMP Destination Unreachable for unicasts, but keep + // silent } } else if (pkt->ip->proto == 6) { - pkt->tcp = (struct tcp *) (pkt->ip + 1); + uint8_t off; + pkt->tcp = (struct tcp *) (pkt->pay.buf); if (pkt->pay.len < sizeof(*pkt->tcp)) return; - mkpay(pkt, pkt->tcp + 1); - uint16_t iplen = mg_ntohs(pkt->ip->len); - uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U)); - if (iplen >= off) pkt->pay.len = (size_t) (iplen - off); + off = pkt->tcp->off >> 4; // account for opts + if (pkt->pay.len < (uint16_t) (4 * off)) return; + mkpay(pkt, (uint32_t *) pkt->tcp + off); MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len)); rx_tcp(ifp, pkt); + } else { + MG_DEBUG(("Unknown IP proto %x", (int) pkt->ip->proto)); + if (mg_log_level >= MG_LL_VERBOSE) + mg_hexdump(pkt->ip, pkt->pay.len >= 32 ? 32 : pkt->pay.len); } } +#if MG_ENABLE_IPV6 static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) { - // MG_DEBUG(("IP %d", (int) len)); - if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) { - pkt->icmp = (struct icmp *) (pkt->ip6 + 1); - if (pkt->pay.len < sizeof(*pkt->icmp)) return; - mkpay(pkt, pkt->icmp + 1); - rx_icmp(ifp, pkt); - } else if (pkt->ip6->proto == 17) { - pkt->udp = (struct udp *) (pkt->ip6 + 1); + uint16_t len = 0, plen; + uint8_t next, *nhdr; + bool loop = true; + if (pkt->pay.len < sizeof(*pkt->ip6)) return; // Truncated + if ((pkt->ip6->ver >> 4) != 0x6) return; // Not IPv6 + plen = mg_ntohs(pkt->ip6->plen); + if (plen > (pkt->pay.len - sizeof(*pkt->ip6))) return; // malformed + next = pkt->ip6->next; + nhdr = (uint8_t *) (pkt->ip6 + 1); + while (loop) { + switch (next) { + case 0: // Hop-by-Hop 4.3 + case 43: // Routing 4.4 + case 60: // Destination Options 4.6 + case 51: // Authentication RFC-4302 + MG_INFO(("IPv6 extension header %d", (int) next)); + next = nhdr[0]; + len += (uint16_t) (8 * (nhdr[1] + 1)); + nhdr += 8 * (nhdr[1] + 1); + break; + case 44: // Fragment 4.5 + { + struct mg_connection *c; + if (nhdr[0] == 17) pkt->udp = (struct udp *) (pkt->pay.buf); + if (nhdr[0] == 6) pkt->tcp = (struct tcp *) (pkt->pay.buf); + c = getpeer(ifp->mgr, pkt, false); + if (c) mg_error(c, "Received fragmented packet"); + } + return; + case 59: // No Next Header 4.7 + return; + case 50: // IPsec ESP RFC-4303, unsupported + default: + loop = false; + break; + } + } + if (len >= plen) return; + // There can be link padding, take payload length from IPv6 header - options + pkt->pay.buf = (char *) nhdr; + pkt->pay.len = plen - len; + if (next == 58) { + pkt->icmp6 = (struct icmp6 *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->icmp6)) return; + mkpay(pkt, pkt->icmp6 + 1); + MG_DEBUG(("ICMPv6 %M -> %M len %u", mg_print_ip6, &pkt->ip6->src, + mg_print_ip6, &pkt->ip6->dst, (int) pkt->pay.len)); + rx_icmp6(ifp, pkt); + } else if (next == 17) { + pkt->udp = (struct udp *) (pkt->pay.buf); if (pkt->pay.len < sizeof(*pkt->udp)) return; - // MG_DEBUG((" UDP %u %u -> %u", len, mg_htons(udp->sport), - // mg_htons(udp->dport))); + // Take length from UDP header + len = mg_ntohs(pkt->udp->len); // UDP datagram length + if (len < sizeof(*pkt->udp) || len > pkt->pay.len) return; // malformed + pkt->pay.len = len; // strip excess data mkpay(pkt, pkt->udp + 1); + MG_DEBUG(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src, + mg_ntohs(pkt->udp->sport), mg_print_ip6, &pkt->ip6->dst, + mg_ntohs(pkt->udp->dport), (int) pkt->pay.len)); + if (ifp->enable_dhcp6_client && pkt->udp->dport == mg_htons(546)) { + pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp6 + 1); + // rx_dhcp6_client(ifp, pkt); +#if 0 + } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(547)) { + pkt->dhcp6 = (struct dhcp6 *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp6 + 1); + rx_dhcp6_server(ifp, pkt); +#endif + } else if (!rx_udp(ifp, pkt)) { + // Should send ICMPv6 Destination Unreachable for unicasts, keep silent + } + } else if (next == 6) { + uint8_t off; + pkt->tcp = (struct tcp *) (pkt->pay.buf); + if (pkt->pay.len < sizeof(*pkt->tcp)) return; + off = pkt->tcp->off >> 4; // account for opts + if (pkt->pay.len < (uint16_t) (4 * off)) return; + mkpay(pkt, (uint32_t *) pkt->tcp + off); + MG_DEBUG(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src, + mg_ntohs(pkt->tcp->sport), mg_print_ip6, &pkt->ip6->dst, + mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len)); + rx_tcp(ifp, pkt); + } else { + MG_DEBUG(("Unknown IPv6 next hdr %x", (int) next)); + if (mg_log_level >= MG_LL_VERBOSE) + mg_hexdump(pkt->ip6, pkt->pay.len >= 32 ? 32 : pkt->pay.len); } } +#else +#define rx_ip6(x, y) +#endif static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) { struct pkt pkt; memset(&pkt, 0, sizeof(pkt)); - pkt.raw.buf = (char *) buf; - pkt.raw.len = len; - pkt.eth = (struct eth *) buf; - // mg_hexdump(buf, len > 16 ? 16: len); + pkt.pay.buf = pkt.raw.buf = (char *) buf; + pkt.pay.len = pkt.raw.len = len; // payload = raw + pkt.eth = (struct eth *) buf; // Ethernet = raw if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt? if (ifp->enable_mac_check && memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 && memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) return; if (ifp->enable_crc32_check && len > 4) { + uint32_t crc; len -= 4; // TODO(scaprile): check on bigendian - uint32_t crc = mg_crc32(0, (const char *) buf, len); + crc = mg_crc32(0, (const char *) buf, len); if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return; + pkt.pay.len = len; } + mkpay(&pkt, pkt.eth + 1); if (pkt.eth->type == mg_htons(0x806)) { - pkt.arp = (struct arp *) (pkt.eth + 1); - if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // Truncated + pkt.arp = (struct arp *) (pkt.pay.buf); + if (pkt.pay.len < sizeof(*pkt.arp)) return; // Truncated + mg_tcpip_call(ifp, MG_TCPIP_EV_ARP, &pkt.raw); rx_arp(ifp, &pkt); } else if (pkt.eth->type == mg_htons(0x86dd)) { - pkt.ip6 = (struct ip6 *) (pkt.eth + 1); - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return; // Truncated - if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP - mkpay(&pkt, pkt.ip6 + 1); + pkt.ip6 = (struct ip6 *) (pkt.pay.buf); rx_ip6(ifp, &pkt); } else if (pkt.eth->type == mg_htons(0x800)) { - pkt.ip = (struct ip *) (pkt.eth + 1); - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated - // Truncate frame to what IP header tells us - if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) { - pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth); - } - if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated - if ((pkt.ip->ver >> 4) != 4) return; // Not IP - mkpay(&pkt, pkt.ip + 1); + pkt.ip = (struct ip *) (pkt.pay.buf); rx_ip(ifp, &pkt); } else { MG_DEBUG(("Unknown eth type %x", mg_htons(pkt.eth->type))); @@ -5766,58 +5813,124 @@ } } +static void mg_ip_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->state == MG_TCPIP_STATE_DOWN) return; + // DHCP RFC-2131 (4.4) + if (ifp->enable_dhcp_client && s1) { + if (ifp->state == MG_TCPIP_STATE_UP) { + tx_dhcp_discover(ifp); // INIT (4.4.1) + } else if (ifp->state == MG_TCPIP_STATE_READY && + ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING + if (ifp->now >= ifp->lease_expire) { + ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP + onstatechange(ifp); + } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire && + ((ifp->now / 1000) % 60) == 0) { + // hack: 30 min before deadline, try to rebind (4.3.6) every min + tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff); + } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5) + } + } +} +static void mg_ip_link(struct mg_tcpip_if *ifp, bool up) { + bool current = ifp->state != MG_TCPIP_STATE_DOWN; + if (!up && ifp->enable_dhcp_client) ifp->ip = 0; + if (up != current) { // link state has changed + ifp->state = up == false ? MG_TCPIP_STATE_DOWN + : ifp->enable_dhcp_client || ifp->ip == 0 ? MG_TCPIP_STATE_UP + : MG_TCPIP_STATE_IP; + onstatechange(ifp); + } else if (!ifp->enable_dhcp_client && ifp->state == MG_TCPIP_STATE_UP && + ifp->ip) { + ifp->state = MG_TCPIP_STATE_IP; // ifp->fn has set an IP + onstatechange(ifp); + } +} + +#if MG_ENABLE_IPV6 +static void mg_ip6_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->state6 == MG_TCPIP_STATE_DOWN) return; + if (ifp->enable_slaac && s1 && ifp->state6 == MG_TCPIP_STATE_UP) + tx_ndp_rs(ifp, ifp->gw6, ifp->gw6mac); +} +static void mg_ip6_link(struct mg_tcpip_if *ifp, bool up) { + bool current = ifp->state6 != MG_TCPIP_STATE_DOWN; + if (!up && ifp->enable_slaac) ifp->ip6[0] = ifp->ip6[1] = 0; + if (up != current) { // link state has changed + ifp->state6 = !up ? MG_TCPIP_STATE_DOWN + : ifp->enable_slaac || ifp->ip6[0] == 0 ? MG_TCPIP_STATE_UP + : MG_TCPIP_STATE_IP; + onstate6change(ifp); + } else if (!ifp->enable_slaac && ifp->state6 == MG_TCPIP_STATE_UP && + ifp->ip6[0]) { + ifp->state6 = MG_TCPIP_STATE_IP; // ifp->fn has set an IP + onstate6change(ifp); + } +} +#else +#define mg_ip6_poll(x, y) +#define mg_ip6_link(x, y) +#endif + static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { struct mg_connection *c; bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, now); ifp->now = now; -#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS if (expired_1000ms) { - const char *names[] = {"down", "up", "req", "ready"}; - MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", - names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, - ifp->ndrop, ifp->nerr)); - } -#endif - // Handle physical interface up/down status - if (expired_1000ms && ifp->driver->up) { - bool up = ifp->driver->up(ifp); - bool current = ifp->state != MG_TCPIP_STATE_DOWN; - if (up != current) { - ifp->state = up == false ? MG_TCPIP_STATE_DOWN - : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP - : MG_TCPIP_STATE_READY; - if (!up && ifp->enable_dhcp_client) ifp->ip = 0; - onstatechange(ifp); - } - if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down")); +#if MG_ENABLE_TCPIP_PRINT_DEBUG_STATS + const char *names[] = {"down", "up", "req", "ip", "ready"}; + size_t max = sizeof(names) / sizeof(char *); + unsigned int state = ifp->state >= max ? max - 1 : ifp->state; + MG_INFO(("Status: %s, IP: %M, rx:%u, tx:%u, dr:%u, er:%u", names[state], + mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, ifp->ndrop, + ifp->nerr)); +#if MG_ENABLE_IPV6 + state = ifp->state6 >= max ? max - 1 : ifp->state6; + if (state > MG_TCPIP_STATE_UP) + MG_INFO(("Status: %s, IPv6: %M", names[state], mg_print_ip6, &ifp->ip6)); +#endif +#endif + backlog_poll(ifp->mgr); } - if (ifp->state == MG_TCPIP_STATE_DOWN) return; + // Handle gw ARP request timeout, order is important + if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) { + ifp->state = MG_TCPIP_STATE_READY; // keep best-effort MAC + onstatechange(ifp); + } +#if MG_ENABLE_IPV6 + // Handle gw NS/NA req/resp timeout, order is important + if (expired_1000ms && ifp->state6 == MG_TCPIP_STATE_IP) { + ifp->state6 = MG_TCPIP_STATE_READY; // keep best-effort MAC + onstate6change(ifp); + } +#endif - // DHCP RFC-2131 (4.4) - if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) { - tx_dhcp_discover(ifp); // INIT (4.4.1) - } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY && - ifp->lease_expire > 0) { // BOUND / RENEWING / REBINDING - if (ifp->now >= ifp->lease_expire) { - ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0; // expired, release IP - onstatechange(ifp); - } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire && - ((ifp->now / 1000) % 60) == 0) { - // hack: 30 min before deadline, try to rebind (4.3.6) every min - tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff); - } // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5) + // poll driver + if (ifp->driver->poll) { + bool up = ifp->driver->poll(ifp, expired_1000ms); + // Handle physical interface up/down status, ifp->state rules over state6 + if (expired_1000ms) { + mg_ip_link(ifp, up); // Handle IPv4 + mg_ip6_link(ifp, up); // Handle IPv6 + if (ifp->state == MG_TCPIP_STATE_DOWN) MG_ERROR(("Network is down")); + mg_tcpip_call(ifp, MG_TCPIP_EV_TIMER_1S, NULL); + } } + mg_ip_poll(ifp, expired_1000ms); // Handle IPv4 + mg_ip6_poll(ifp, expired_1000ms); // Handle IPv6 + + if (ifp->state == MG_TCPIP_STATE_DOWN) return; // Read data from the network - if (ifp->driver->rx != NULL) { // Polling driver. We must call it + if (ifp->driver->rx != NULL) { // Simple polling driver, returns one frame size_t len = ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp); if (len > 0) { ifp->nrecv++; mg_tcpip_rx(ifp, ifp->recv_queue.buf, len); } - } else { // Interrupt-based driver. Fills recv queue itself + } else { // Complex poll / Interrupt-based driver. Queues recvd frames char *buf; size_t len = mg_queue_next(&ifp->recv_queue, &buf); if (len > 0) { @@ -5828,18 +5941,19 @@ // Process timeouts for (c = ifp->mgr->conns; c != NULL; c = c->next) { - if (c->is_udp || c->is_listening || c->is_resolving) continue; struct connstate *s = (struct connstate *) (c + 1); - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - if (now > s->timer) { - if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) { + if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving) + continue; + if (ifp->now > s->timer) { + if (s->ttype == MIP_TTYPE_ARP) { + mg_error(c, "ARP timeout"); + } else if (c->is_udp) { + continue; + } else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) { MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack)); - tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); + tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq), + mg_htonl(s->ack), NULL, 0); s->acked = s->ack; - } else if (s->ttype == MIP_TTYPE_ARP) { - mg_error(c, "ARP timeout"); } else if (s->ttype == MIP_TTYPE_SYN) { mg_error(c, "Connection timeout"); } else if (s->ttype == MIP_TTYPE_FIN) { @@ -5850,8 +5964,8 @@ mg_error(c, "keepalive"); } else { MG_VERBOSE(("%lu keepalive", c->id)); - tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, - mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0); + tx_tcp(ifp, s->mac, &c->loc, &c->rem, TH_ACK, mg_htonl(s->seq - 1), + mg_htonl(s->ack), NULL, 0); } } @@ -5883,91 +5997,156 @@ MG_INFO(("MAC not set. Generated random: %M", mg_print_mac, ifp->mac)); } + // If DHCP name is not set, use "mip" + if (ifp->dhcp_name[0] == '\0') { + memcpy(ifp->dhcp_name, "mip", 4); + } + ifp->dhcp_name[sizeof(ifp->dhcp_name) - 1] = '\0'; // Just in case + if (ifp->driver->init && !ifp->driver->init(ifp)) { MG_ERROR(("driver init failed")); } else { size_t framesize = 1540; - ifp->tx.buf = (char *) calloc(1, framesize), ifp->tx.len = framesize; + ifp->tx.buf = (char *) mg_calloc(1, framesize), ifp->tx.len = framesize; if (ifp->recv_queue.size == 0) ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192; - ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size); + ifp->recv_queue.buf = (char *) mg_calloc(1, ifp->recv_queue.size); ifp->timer_1000ms = mg_millis(); - mgr->priv = ifp; + mgr->ifp = ifp; ifp->mgr = mgr; ifp->mtu = MG_TCPIP_MTU_DEFAULT; mgr->extraconnsize = sizeof(struct connstate); if (ifp->ip == 0) ifp->enable_dhcp_client = true; - memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set to broadcast + memset(ifp->gwmac, 255, sizeof(ifp->gwmac)); // Set best-effort to bcast mg_random(&ifp->eport, sizeof(ifp->eport)); // Random from 0 to 65535 ifp->eport |= MG_EPHEMERAL_PORT_BASE; // Random from // MG_EPHEMERAL_PORT_BASE to 65535 if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); +#if MG_ENABLE_IPV6 + // If static configuration is used, link-local and global addresses, + // prefix length, and gw are already filled at this point. + if (ifp->ip6[0] == 0 && ifp->ip6[1] == 0) { + ifp->enable_slaac = true; + ip6genll((uint8_t *) ifp->ip6ll, ifp->mac); // build link-local address + } + memset(ifp->gw6mac, 255, sizeof(ifp->gw6mac)); // Set best-effort to bcast +#endif } } void mg_tcpip_free(struct mg_tcpip_if *ifp) { - free(ifp->recv_queue.buf); - free(ifp->tx.buf); + mg_free(ifp->recv_queue.buf); + mg_free(ifp->tx.buf); + mg_free(ifp->dns4_url); } static void send_syn(struct mg_connection *c) { struct connstate *s = (struct connstate *) (c + 1); uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port)); - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL, - 0); + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_SYN, isn, 0, NULL, 0); +} + +static void mac_resolved(struct mg_connection *c) { + if (c->is_udp) { + c->is_connecting = 0; + mg_call(c, MG_EV_CONNECT, NULL); + } else { + send_syn(c); + settmout(c, MIP_TTYPE_SYN); + } +} + +static void ip4_mcastmac(uint8_t *mac, uint32_t *ip) { + uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group MAC + memcpy(mac, mcastp, 3); + memcpy(mac + 3, ((uint8_t *) ip) + 1, 3); // 23 LSb + mac[3] &= 0x7F; } void mg_connect_resolved(struct mg_connection *c) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); + struct mg_tcpip_if *ifp = c->mgr->ifp; c->is_resolving = 0; if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; - memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t)); c->loc.port = mg_htons(ifp->eport++); +#if MG_ENABLE_IPV6 + if (c->rem.is_ip6) { + c->loc.ip6[0] = ifp->ip6[0], c->loc.ip6[1] = ifp->ip6[1], + c->loc.is_ip6 = true; + } else +#endif + { + c->loc.ip4 = ifp->ip; + } MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port, &c->rem)); mg_call(c, MG_EV_RESOLVE, NULL); - if (c->is_udp && (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) { - struct connstate *s = (struct connstate *) (c + 1); - memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast - } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask))) { - // If we're in the same LAN, fire an ARP lookup. - MG_DEBUG(("%lu ARP lookup...", c->id)); - arp_ask(ifp, rem_ip); - settmout(c, MIP_TTYPE_ARP); - c->is_arplooking = 1; - c->is_connecting = 1; - } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) { - struct connstate *s = (struct connstate *) (c + 1); // 224 to 239, E0 to EF - uint8_t mcastp[3] = {0x01, 0x00, 0x5E}; // multicast group - memcpy(s->mac, mcastp, 3); - memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3); // 23 LSb - s->mac[3] &= 0x7F; - } else { - struct connstate *s = (struct connstate *) (c + 1); - memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac)); - if (c->is_udp) { - mg_call(c, MG_EV_CONNECT, NULL); + c->is_connecting = 1; +#if MG_ENABLE_IPV6 + if (c->rem.is_ip6) { + if (c->is_udp && + MG_IP6MATCH(c->rem.ip6, ip6_allnodes.u)) { // local broadcast + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, ip6mac_allnodes, sizeof(s->mac)); + mac_resolved(c); + } else if (c->rem.ip6[0] == ifp->ip6[0] && + !MG_IP6MATCH(c->rem.ip6, + ifp->gw6)) { // skip if gw (onstate6change -> NS) + // If we're in the same LAN, fire a Neighbor Solicitation + MG_DEBUG(("%lu NS lookup...", c->id)); + tx_ndp_ns(ifp, c->rem.ip6, ifp->mac); + settmout(c, MIP_TTYPE_ARP); + c->is_arplooking = 1; + } else if (*((uint8_t *) c->rem.ip6) == 0xFF) { // multicast + struct connstate *s = (struct connstate *) (c + 1); + ip6_mcastmac(s->mac, c->rem.ip6); + mac_resolved(c); } else { - send_syn(c); - settmout(c, MIP_TTYPE_SYN); - c->is_connecting = 1; + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, ifp->gw6mac, sizeof(s->mac)); + mac_resolved(c); + } + } else +#endif + { + uint32_t rem_ip = c->rem.ip4; + if (c->is_udp && + (rem_ip == 0xffffffff || rem_ip == (ifp->ip | ~ifp->mask))) { + struct connstate *s = (struct connstate *) (c + 1); + memset(s->mac, 0xFF, sizeof(s->mac)); // global or local broadcast + mac_resolved(c); + } else if (ifp->ip && ((rem_ip & ifp->mask) == (ifp->ip & ifp->mask)) && + rem_ip != ifp->gw) { // skip if gw (onstatechange -> ARP) + // If we're in the same LAN, fire an ARP lookup. + MG_DEBUG(("%lu ARP lookup...", c->id)); + mg_tcpip_arp_request(ifp, rem_ip, NULL); + settmout(c, MIP_TTYPE_ARP); + c->is_arplooking = 1; + } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) { + struct connstate *s = + (struct connstate *) (c + 1); // 224 to 239, E0 to EF + ip4_mcastmac(s->mac, &rem_ip); // multicast group + mac_resolved(c); + } else { + struct connstate *s = (struct connstate *) (c + 1); + memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac)); + mac_resolved(c); } } } bool mg_open_listener(struct mg_connection *c, const char *url) { c->loc.port = mg_htons(mg_url_port(url)); + if (!mg_aton(mg_url_host(url), &c->loc)) { + MG_ERROR(("invalid listening URL: %s", url)); + return false; + } return true; } static void write_conn(struct mg_connection *c) { long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len) : mg_io_send(c, c->send.buf, c->send.len); + // TODO(): mg_tls_send() may return 0 forever on steady OOM if (len == MG_IO_ERR) { mg_error(c, "tx err"); } else if (len > 0) { @@ -5980,11 +6159,7 @@ struct connstate *s = (struct connstate *) (c + 1); if (c->is_udp == false && c->is_listening == false && c->is_connecting == false) { // For TCP conns, - struct mg_tcpip_if *ifp = - (struct mg_tcpip_if *) c->mgr->priv; // send TCP FIN - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port, + tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, TH_FIN | TH_ACK, mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); settmout(c, MIP_TTYPE_FIN); } @@ -6002,49 +6177,202 @@ } void mg_mgr_poll(struct mg_mgr *mgr, int ms) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) mgr->priv; struct mg_connection *c, *tmp; uint64_t now = mg_millis(); mg_timer_poll(&mgr->timers, now); - if (ifp == NULL || ifp->driver == NULL) return; - mg_tcpip_poll(ifp, now); + if (mgr->ifp == NULL || mgr->ifp->driver == NULL) return; + mg_tcpip_poll(mgr->ifp, now); for (c = mgr->conns; c != NULL; c = tmp) { - tmp = c->next; struct connstate *s = (struct connstate *) (c + 1); + bool is_tls = c->is_tls && !c->is_resolving && !c->is_arplooking && + !c->is_listening && !c->is_connecting; + tmp = c->next; mg_call(c, MG_EV_POLL, &now); - MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, c->is_tls ? 'T' : 't', + MG_VERBOSE(("%lu .. %c%c%c%c%c %lu %lu", c->id, c->is_tls ? 'T' : 't', c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h', - c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); - if (c->is_tls && mg_tls_pending(c) > 0) - handle_tls_recv(c, (struct mg_iobuf *) &c->rtls); + c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c', + mg_tls_pending(c), c->rtls.len)); + // order is important, TLS conn close with > 1 record in buffer (below) + if (is_tls && (c->rtls.len > 0 || mg_tls_pending(c) > 0)) + c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c); if (can_write(c)) write_conn(c); + if (is_tls && c->send.len == 0) mg_tls_flush(c); if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN) init_closure(c); + // For non-TLS, close immediately upon completing the 3-way closure + // For TLS, handle any pending data (above) until MIP_TTYPE_FIN expires + if (s->twclosure && + (!c->is_tls || (c->rtls.len == 0 && mg_tls_pending(c) == 0))) + c->is_closing = 1; if (c->is_closing) close_conn(c); } (void) ms; } bool mg_send(struct mg_connection *c, const void *buf, size_t len) { - struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv; + struct mg_tcpip_if *ifp = c->mgr->ifp; bool res = false; - uint32_t rem_ip; - memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t)); - if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) { + if (!c->loc.is_ip6 && (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY)) { + mg_error(c, "net down"); +#if MG_ENABLE_IPV6 + } else if (c->loc.is_ip6 && ifp->state6 != MG_TCPIP_STATE_READY) { mg_error(c, "net down"); +#endif + } else if (c->is_udp && (c->is_arplooking || c->is_resolving)) { + // Fail to send, no target MAC or IP + MG_VERBOSE(("still resolving...")); } else if (c->is_udp) { - struct connstate *s = (struct connstate *) (c + 1); len = trim_len(c, len); // Trimming length if necessary - tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len); - res = true; + res = udp_send(c, buf, len); } else { - res = mg_iobuf_add(&c->send, c->send.len, buf, len); + res = len == 0 || mg_iobuf_add(&c->send, c->send.len, buf, len) > 0; + // returning 0 means an OOM condition (iobuf couldn't resize), yet this is + // so far recoverable, let the caller decide } return res; } + +uint8_t mcast_addr[6] = {0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb}; +void mg_multicast_add(struct mg_connection *c, char *ip) { + (void) ip; // ip4/6_mcastmac(mcast_mac, &ip); ipv6 param + // TODO(): actual IP -> MAC; check database, update + c->mgr->ifp->update_mac_hash_table = true; // mark dirty +} + +bool mg_dnsc_init(struct mg_mgr *mgr, struct mg_dns *dnsc); + +static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip) { + struct mg_dns *dnsc; + mg_free(ifp->dns4_url); + ifp->dns4_url = mg_mprintf("udp://%M:53", mg_print_ip4, ip); + dnsc = &ifp->mgr->dns4; + dnsc->url = (const char *) ifp->dns4_url; + MG_DEBUG(("Set DNS URL to %s", dnsc->url)); + if (ifp->mgr->use_dns6) return; + if (dnsc->c != NULL) mg_close_conn(dnsc->c); + if (!mg_dnsc_init(ifp->mgr, dnsc)) // create DNS connection + MG_ERROR(("DNS connection creation failed")); +} + #endif // MG_ENABLE_TCPIP #ifdef MG_ENABLE_LINES +#line 1 "src/ota_ch32v307.c" +#endif + + + + +#if MG_OTA == MG_OTA_CH32V307 +// RM: https://www.wch-ic.com/downloads/CH32FV2x_V3xRM_PDF.html + +static bool mg_ch32v307_write(void *, const void *, size_t); +static bool mg_ch32v307_swap(void); + +static struct mg_flash s_mg_flash_ch32v307 = { + (void *) 0x08000000, // Start + 480 * 1024, // Size, first 320k is 0-wait + 4 * 1024, // Sector size, 4k + 4, // Align, 32 bit + mg_ch32v307_write, + mg_ch32v307_swap, +}; + +#define FLASH_BASE 0x40022000 +#define FLASH_ACTLR (FLASH_BASE + 0) +#define FLASH_KEYR (FLASH_BASE + 4) +#define FLASH_OBKEYR (FLASH_BASE + 8) +#define FLASH_STATR (FLASH_BASE + 12) +#define FLASH_CTLR (FLASH_BASE + 16) +#define FLASH_ADDR (FLASH_BASE + 20) +#define FLASH_OBR (FLASH_BASE + 28) +#define FLASH_WPR (FLASH_BASE + 32) + +MG_IRAM static void flash_unlock(void) { + static bool unlocked; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0xcdef89ab; + unlocked = true; + } +} + +MG_IRAM static void flash_wait(void) { + while (MG_REG(FLASH_STATR) & MG_BIT(0)) (void) 0; +} + +MG_IRAM static void mg_ch32v307_erase(void *addr) { + // MG_INFO(("%p", addr)); + flash_unlock(); + flash_wait(); + MG_REG(FLASH_ADDR) = (uint32_t) addr; + MG_REG(FLASH_CTLR) |= MG_BIT(1) | MG_BIT(6); // PER | STRT; + flash_wait(); +} + +MG_IRAM static bool is_page_boundary(const void *addr) { + uint32_t val = (uint32_t) addr; + return (val & (s_mg_flash_ch32v307.secsz - 1)) == 0; +} + +MG_IRAM static bool mg_ch32v307_write(void *addr, const void *buf, size_t len) { + // MG_INFO(("%p %p %lu", addr, buf, len)); + // mg_hexdump(buf, len); + flash_unlock(); + const uint16_t *src = (uint16_t *) buf, *end = &src[len / 2]; + uint16_t *dst = (uint16_t *) addr; + MG_REG(FLASH_CTLR) |= MG_BIT(0); // Set PG + // MG_INFO(("CTLR: %#lx", MG_REG(FLASH_CTLR))); + while (src < end) { + if (is_page_boundary(dst)) mg_ch32v307_erase(dst); + *dst++ = *src++; + flash_wait(); + } + MG_REG(FLASH_CTLR) &= ~MG_BIT(0); // Clear PG + return true; +} + +MG_IRAM bool mg_ch32v307_swap(void) { + return true; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_ch32v307_write(p1 + ofs, p2 + ofs, ss); + } + *((volatile uint32_t *) 0xbeef0000) |= 1U << 7; // NVIC_SystemReset() +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_ch32v307); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_ch32v307); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_ch32v307)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_ch32v307.size, + s_mg_flash_ch32v307.size / s_mg_flash_ch32v307.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + // TODO() disable IRQ, s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_ch32v307.start, + (char *) s_mg_flash_ch32v307.start + s_mg_flash_ch32v307.size / 2, + s_mg_flash_ch32v307.size / 2, s_mg_flash_ch32v307.secsz); + } + return false; +} +#endif + +#ifdef MG_ENABLE_LINES #line 1 "src/ota_dummy.c" #endif @@ -6062,30 +6390,6 @@ bool mg_ota_end(void) { return true; } -bool mg_ota_commit(void) { - return true; -} -bool mg_ota_rollback(void) { - return true; -} -int mg_ota_status(int fw) { - (void) fw; - return 0; -} -uint32_t mg_ota_crc32(int fw) { - (void) fw; - return 0; -} -uint32_t mg_ota_timestamp(int fw) { - (void) fw; - return 0; -} -size_t mg_ota_size(int fw) { - (void) fw; - return 0; -} -MG_IRAM void mg_ota_boot(void) { -} #endif #ifdef MG_ENABLE_LINES @@ -6145,233 +6449,1992 @@ #endif #ifdef MG_ENABLE_LINES -#line 1 "src/ota_flash.c" +#line 1 "src/ota_frdm.c" #endif +#if MG_OTA == MG_OTA_FRDM -// This OTA implementation uses the internal flash API outlined in device.h -// It splits flash into 2 equal partitions, and stores OTA status in the -// last sector of the partition. +MG_IRAM static bool mg_frdm_write(void *, const void *, size_t); +static bool mg_frdm_swap(void); -#if MG_OTA == MG_OTA_FLASH +static struct mg_flash s_mg_flash_frdm = {(void *) 0x08000000, // Start, + 0x200000, // Size + 0x1000, // Sector size + 0x100, // Align + mg_frdm_write, + mg_frdm_swap}; -#define MG_OTADATA_KEY 0xb07afed0 +struct mg_flexspi_lut_seq { + uint8_t seqNum; + uint8_t seqId; + uint16_t reserved; +}; -static char *s_addr; // Current address to write to -static size_t s_size; // Firmware size to flash. In-progress indicator -static uint32_t s_crc32; // Firmware checksum +struct mg_flexspi_mem_config { + uint32_t tag; + uint32_t version; + uint32_t reserved0; + uint8_t readSampleClkSrc; + uint8_t csHoldTime; + uint8_t csSetupTime; + uint8_t columnAddressWidth; + uint8_t deviceModeCfgEnable; + uint8_t deviceModeType; + uint16_t waitTimeCfgCommands; + struct mg_flexspi_lut_seq deviceModeSeq; + uint32_t deviceModeArg; + uint8_t configCmdEnable; + uint8_t configModeType[3]; + struct mg_flexspi_lut_seq configCmdSeqs[3]; + uint32_t reserved1; + uint32_t configCmdArgs[3]; + uint32_t reserved2; + uint32_t controllerMiscOption; + uint8_t deviceType; + uint8_t sflashPadType; + uint8_t serialClkFreq; + uint8_t lutCustomSeqEnable; + uint32_t reserved3[2]; + uint32_t sflashA1Size; + uint32_t sflashA2Size; + uint32_t sflashB1Size; + uint32_t sflashB2Size; + uint32_t csPadSettingOverride; + uint32_t sclkPadSettingOverride; + uint32_t dataPadSettingOverride; + uint32_t dqsPadSettingOverride; + uint32_t timeoutInMs; + uint32_t commandInterval; + uint16_t dataValidTime[2]; + uint16_t busyOffset; + uint16_t busyBitPolarity; + uint32_t lookupTable[64]; + struct mg_flexspi_lut_seq lutCustomSeq[12]; + uint32_t reserved4[4]; +}; -struct mg_otadata { - uint32_t crc32, size, timestamp, status; +struct mg_flexspi_nor_config { + struct mg_flexspi_mem_config memConfig; + uint32_t pageSize; + uint32_t sectorSize; + uint8_t ipcmdSerialClkFreq; + uint8_t isUniformBlockSize; + uint8_t isDataOrderSwapped; + uint8_t reserved0[1]; + uint8_t serialNorType; + uint8_t needExitNoCmdMode; + uint8_t halfClkForNonReadCmd; + uint8_t needRestoreNoCmdMode; + uint32_t blockSize; + uint32_t flashStateCtx; + uint32_t reserve2[10]; }; +struct mg_flexspi_nor_driver_interface { + uint32_t version; + uint32_t (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + uint32_t (*wait_busy)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t address, bool keepState); + uint32_t (*page_program)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t dstAddr, const uint32_t *src, + bool keepState); + uint32_t (*erase_all)(uint32_t instance, + struct mg_flexspi_nor_config *config); + uint32_t (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t length); + uint32_t (*erase_sector)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t address); + uint32_t (*erase_block)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t address); + uint32_t (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *dst, uint32_t start, uint32_t bytes); + void (*config_clock)(uint32_t instance, uint32_t freqOption, + uint32_t sampleClkMode); + uint32_t (*set_clock_source)(uint32_t clockSrc); + uint32_t (*get_config)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t *option); + void (*hw_reset)(uint32_t instance, uint32_t reset_logic); + uint32_t (*xfer)(uint32_t instance, char *xfer); + uint32_t (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t numberOfSeq); + uint32_t (*partial_program)(uint32_t instance, + struct mg_flexspi_nor_config *config, + uint32_t dstAddr, const uint32_t *src, + uint32_t length, bool keepState); +}; + +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) +#define MG_FLEXSPI_BASE 0x40134000UL + +#define MG_CMD_SDR 0x01 +#define MG_RADDR_SDR 0x02 +#define MG_WRITE_SDR 0x08 +#define MG_READ_SDR 0x09 +#define MG_DUMMY_SDR 0x0C +#define MG_STOP_EXE 0 + +#define MG_FLEXSPI_1PAD 0 +#define MG_FLEXSPI_4PAD 2 + +#define MG_FLEXSPI_LUT_OPERAND0(x) (((x) &0xFF) << 0) +#define MG_FLEXSPI_LUT_NUM_PADS0(x) (((x) &0x3) << 8) +#define MG_FLEXSPI_LUT_OPCODE0(x) (((x) &0x3F) << 10) +#define MG_FLEXSPI_LUT_OPERAND1(x) (((x) &0xFF) << 16) +#define MG_FLEXSPI_LUT_NUM_PADS1(x) (((x) &0x3) << 24) +#define MG_FLEXSPI_LUT_OPCODE1(x) (((x) &0x3F) << 26) + +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | \ + MG_FLEXSPI_LUT_OPCODE0(cmd0) | MG_FLEXSPI_LUT_OPERAND1(op1) | \ + MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1)) + +struct mg_flexspi_nor_config default_config = { + .memConfig = + { + .tag = MG_FLEXSPI_CFG_BLK_TAG, + .version = 0, + .readSampleClkSrc = 1, + .csHoldTime = 3, + .csSetupTime = 3, + .deviceModeCfgEnable = 1, + .deviceModeSeq = {.seqNum = 1, .seqId = 2}, + .deviceModeArg = 0x0740, + .configCmdEnable = 0, + .deviceType = 0x1, + .sflashPadType = 4, + .serialClkFreq = 4, + .sflashA1Size = 0x4000000U, + .sflashA2Size = 0, + .sflashB1Size = 0, + .sflashB2Size = 0, + .lookupTable = + { + [0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, + MG_RADDR_SDR, MG_FLEXSPI_4PAD, 0x18), + [1] = + MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, + MG_READ_SDR, MG_FLEXSPI_4PAD, 0x04), + [4 * 1 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, + MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), + [4 * 2 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x01, + MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x02), + [4 * 3 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, + MG_STOP_EXE, MG_FLEXSPI_1PAD, 0x00), + [4 * 5 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), + [4 * 8 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x52, + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), + [4 * 9 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), + [4 * 9 + 1] = + MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x00, + MG_STOP_EXE, MG_FLEXSPI_1PAD, 0x00), + [4 * 11 + 0] = + MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, + MG_STOP_EXE, MG_FLEXSPI_1PAD, 0x00), + }, + }, + .pageSize = 0x100, + .sectorSize = 0x1000, + .ipcmdSerialClkFreq = 0, + .blockSize = 0x8000, +}; + +#define MG_FLEXSPI_NOR_INSTANCE 0 +#define MG_ROMAPI_ADDRESS 0x13030000U +#define flexspi_nor \ + ((struct mg_flexspi_nor_driver_interface *) (( \ + (uint32_t *) MG_ROMAPI_ADDRESS)[5])) + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_frdm.start, + *end = base + s_mg_flash_frdm.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_frdm.secsz) == 0; +} + +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config *config) { + uint32_t option = 0xc0000004; + return flexspi_nor->get_config(MG_FLEXSPI_NOR_INSTANCE, config, &option); +} + +MG_IRAM static int flash_init(void) { + static bool initialized = false; + if (!initialized) { + struct mg_flexspi_nor_config config; + memset(&config, 0, sizeof(config)); + flexspi_nor->set_clock_source(0); + flexspi_nor->config_clock(MG_FLEXSPI_NOR_INSTANCE, 1, 0); + if (flexspi_nor->init(MG_FLEXSPI_NOR_INSTANCE, &default_config)) { + return 1; + } + flexspi_nor_get_config(&config); + if (flexspi_nor->init(MG_FLEXSPI_NOR_INSTANCE, &config)) { + return 1; + } + initialized = true; + } + return 0; +} + +MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config, + void *addr) { + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_frdm.start); + bool ok = (flexspi_nor->erase_sector(MG_FLEXSPI_NOR_INSTANCE, config, + (uint32_t) dst) == 0); + MG_INFO(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +MG_IRAM bool mg_frdm_swap(void) { + return true; +} + +MG_IRAM static void flash_wait(void) { + while ((*((volatile uint32_t *) (MG_FLEXSPI_BASE + 0xE0)) & MG_BIT(1)) == 0) + (void) 0; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_frdm_write(void *addr, const void *buf, size_t len) { + struct mg_flexspi_nor_config config; + bool ok = false; + MG_ARM_DISABLE_IRQ(); + if (flash_init() != 0) goto fwxit; + if (flexspi_nor_get_config(&config) != 0) goto fwxit; + if ((len % s_mg_flash_frdm.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_frdm.align)); + goto fwxit; + } + if ((char *) addr < (char *) s_mg_flash_frdm.start) { + MG_ERROR(("Invalid flash write address: %p", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + while (ok && src < end) { + if (flash_page_start(dst) && flash_erase(&config, dst) == false) { + ok = false; + break; + } + uint32_t status; + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_frdm.start; + if ((char *) buf >= (char *) s_mg_flash_frdm.start && + (char *) buf < + (char *) (s_mg_flash_frdm.start + s_mg_flash_frdm.size)) { + // If we copy from FLASH to FLASH, then we first need to copy the source + // to RAM + size_t tmp_buf_size = s_mg_flash_frdm.align / sizeof(uint32_t); + uint32_t tmp[tmp_buf_size]; + + for (size_t i = 0; i < tmp_buf_size; i++) { + flash_wait(); + tmp[i] = src[i]; + } + status = flexspi_nor->page_program(MG_FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, tmp, false); + } else { + status = flexspi_nor->page_program(MG_FLEXSPI_NOR_INSTANCE, &config, + (uint32_t) dst_ofs, src, false); + } + src = (uint32_t *) ((char *) src + s_mg_flash_frdm.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_frdm.align); + if (status != 0) { + ok = false; + } + } + MG_INFO(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); +fwxit: + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + return ok; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_frdm_write(p1 + ofs, p2 + ofs, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_frdm); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_frdm); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_frdm)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_frdm.size, + s_mg_flash_frdm.size / s_mg_flash_frdm.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_frdm.start, + (char *) s_mg_flash_frdm.start + s_mg_flash_frdm.size / 2, + s_mg_flash_frdm.size / 2, s_mg_flash_frdm.secsz); + } + } + return false; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_imxrt.c" +#endif + + + + +#if MG_OTA >= MG_OTA_RT1020 && MG_OTA <= MG_OTA_RT1170 + +static bool mg_imxrt_write(void *, const void *, size_t); +static bool mg_imxrt_swap(void); + +#if MG_OTA <= MG_OTA_RT1060 +#define MG_IMXRT_FLASH_START 0x60000000 +#define FLEXSPI_NOR_INSTANCE 0 +#elif MG_OTA == MG_OTA_RT1064 +#define MG_IMXRT_FLASH_START 0x70000000 +#define FLEXSPI_NOR_INSTANCE 1 +#else // RT1170 +#define MG_IMXRT_FLASH_START 0x30000000 +#define FLEXSPI_NOR_INSTANCE 1 +#endif + +#if MG_OTA == MG_OTA_RT1050 +#define MG_IMXRT_SECTOR_SIZE (256 * 1024) +#define MG_IMXRT_PAGE_SIZE 512 +#else +#define MG_IMXRT_SECTOR_SIZE (4 * 1024) +#define MG_IMXRT_PAGE_SIZE 256 +#endif + +// TODO(): fill at init, support more devices in a dynamic way +// TODO(): then, check alignment is <= 256, see Wizard's #251 +static struct mg_flash s_mg_flash_imxrt = { + (void *) MG_IMXRT_FLASH_START, // Start, + 4 * 1024 * 1024, // Size, 4mb + MG_IMXRT_SECTOR_SIZE, // Sector size + MG_IMXRT_PAGE_SIZE, // Align + mg_imxrt_write, + mg_imxrt_swap, +}; + +struct mg_flexspi_lut_seq { + uint8_t seqNum; + uint8_t seqId; + uint16_t reserved; +}; + +struct mg_flexspi_mem_config { + uint32_t tag; + uint32_t version; + uint32_t reserved0; + uint8_t readSampleClkSrc; + uint8_t csHoldTime; + uint8_t csSetupTime; + uint8_t columnAddressWidth; + uint8_t deviceModeCfgEnable; + uint8_t deviceModeType; + uint16_t waitTimeCfgCommands; + struct mg_flexspi_lut_seq deviceModeSeq; + uint32_t deviceModeArg; + uint8_t configCmdEnable; + uint8_t configModeType[3]; + struct mg_flexspi_lut_seq configCmdSeqs[3]; + uint32_t reserved1; + uint32_t configCmdArgs[3]; + uint32_t reserved2; + uint32_t controllerMiscOption; + uint8_t deviceType; + uint8_t sflashPadType; + uint8_t serialClkFreq; + uint8_t lutCustomSeqEnable; + uint32_t reserved3[2]; + uint32_t sflashA1Size; + uint32_t sflashA2Size; + uint32_t sflashB1Size; + uint32_t sflashB2Size; + uint32_t csPadSettingOverride; + uint32_t sclkPadSettingOverride; + uint32_t dataPadSettingOverride; + uint32_t dqsPadSettingOverride; + uint32_t timeoutInMs; + uint32_t commandInterval; + uint16_t dataValidTime[2]; + uint16_t busyOffset; + uint16_t busyBitPolarity; + uint32_t lookupTable[64]; + struct mg_flexspi_lut_seq lutCustomSeq[12]; + uint32_t reserved4[4]; +}; + +struct mg_flexspi_nor_config { + struct mg_flexspi_mem_config memConfig; + uint32_t pageSize; + uint32_t sectorSize; + uint8_t ipcmdSerialClkFreq; + uint8_t isUniformBlockSize; + uint8_t reserved0[2]; + uint8_t serialNorType; + uint8_t needExitNoCmdMode; + uint8_t halfClkForNonReadCmd; + uint8_t needRestoreNoCmdMode; + uint32_t blockSize; + uint32_t reserve2[11]; +}; + +/* FLEXSPI memory config block related defintions */ +#define MG_FLEXSPI_CFG_BLK_TAG (0x42464346UL) // ascii "FCFB" Big Endian +#define MG_FLEXSPI_CFG_BLK_VERSION (0x56010400UL) // V1.4.0 + +#define MG_FLEXSPI_LUT_SEQ(cmd0, pad0, op0, cmd1, pad1, op1) \ + (MG_FLEXSPI_LUT_OPERAND0(op0) | MG_FLEXSPI_LUT_NUM_PADS0(pad0) | \ + MG_FLEXSPI_LUT_OPCODE0(cmd0) | MG_FLEXSPI_LUT_OPERAND1(op1) | \ + MG_FLEXSPI_LUT_NUM_PADS1(pad1) | MG_FLEXSPI_LUT_OPCODE1(cmd1)) + +#define MG_CMD_SDR 0x01 +#define MG_CMD_DDR 0x21 +#define MG_DUMMY_SDR 0x0C +#define MG_DUMMY_DDR 0x2C +#define MG_DUMMY_RWDS_DDR 0x2D +#define MG_RADDR_SDR 0x02 +#define MG_RADDR_DDR 0x22 +#define MG_CADDR_DDR 0x23 +#define MG_READ_SDR 0x09 +#define MG_READ_DDR 0x29 +#define MG_WRITE_SDR 0x08 +#define MG_WRITE_DDR 0x28 +#define MG_STOP 0 + +#define MG_FLEXSPI_1PAD 0 +#define MG_FLEXSPI_2PAD 1 +#define MG_FLEXSPI_4PAD 2 +#define MG_FLEXSPI_8PAD 3 + +#define MG_FLEXSPI_QSPI_LUT \ + { \ + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xEB, MG_RADDR_SDR, \ + MG_FLEXSPI_4PAD, 0x18), \ + [1] = MG_FLEXSPI_LUT_SEQ(MG_DUMMY_SDR, MG_FLEXSPI_4PAD, 0x06, MG_READ_SDR, \ + MG_FLEXSPI_4PAD, 0x04), \ + [4 * 1 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x05, \ + MG_READ_SDR, MG_FLEXSPI_1PAD, 0x04), \ + [4 * 3 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x06, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x20, \ + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), \ + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0xD8, \ + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), \ + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x02, \ + MG_RADDR_SDR, MG_FLEXSPI_1PAD, 0x18), \ + [4 * 9 + 1] = MG_FLEXSPI_LUT_SEQ(MG_WRITE_SDR, MG_FLEXSPI_1PAD, 0x04, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 11 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_SDR, MG_FLEXSPI_1PAD, 0x60, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + } + +#define MG_FLEXSPI_HYPER_LUT \ + { \ + [0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xA0, MG_RADDR_DDR, \ + MG_FLEXSPI_8PAD, 0x18), \ + [1] = MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_DUMMY_DDR, MG_FLEXSPI_8PAD, 0x0C), \ + [2] = MG_FLEXSPI_LUT_SEQ(MG_READ_DDR, MG_FLEXSPI_8PAD, 0x04, MG_STOP, \ + MG_FLEXSPI_1PAD, 0x0), \ + [4 * 1 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 1 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 1 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 1 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x70), \ + [4 * 2 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xA0, \ + MG_RADDR_DDR, MG_FLEXSPI_8PAD, 0x18), \ + [4 * 2 + 1] = \ + MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_DUMMY_RWDS_DDR, MG_FLEXSPI_8PAD, 0x0B), \ + [4 * 2 + 2] = MG_FLEXSPI_LUT_SEQ(MG_READ_DDR, MG_FLEXSPI_8PAD, 0x4, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 3 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 3 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 3 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 3 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 4 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 4 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 4 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x02), \ + [4 * 4 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 5 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 5 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 5 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 5 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x80), \ + [4 * 6 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 6 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 6 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 6 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 7 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 7 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 7 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x02), \ + [4 * 7 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 8 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_RADDR_DDR, MG_FLEXSPI_8PAD, 0x18), \ + [4 * 8 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 8 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x30, \ + MG_STOP, MG_FLEXSPI_1PAD, 0x0), \ + [4 * 9 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 9 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 9 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 9 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xA0), \ + [4 * 10 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_RADDR_DDR, MG_FLEXSPI_8PAD, 0x18), \ + [4 * 10 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CADDR_DDR, MG_FLEXSPI_8PAD, 0x10, \ + MG_WRITE_DDR, MG_FLEXSPI_8PAD, 0x80), \ + [4 * 11 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 11 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 11 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 11 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x80), \ + [4 * 12 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 12 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 12 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 12 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 13 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 13 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 13 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x02), \ + [4 * 13 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x55), \ + [4 * 14 + 0] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0), \ + [4 * 14 + 1] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0xAA), \ + [4 * 14 + 2] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x05), \ + [4 * 14 + 3] = MG_FLEXSPI_LUT_SEQ(MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x0, \ + MG_CMD_DDR, MG_FLEXSPI_8PAD, 0x10), \ + } + +#define MG_LUT_CUSTOM_SEQ \ + { \ + {.seqNum = 0, .seqId = 0, .reserved = 0}, \ + {.seqNum = 2, .seqId = 1, .reserved = 0}, \ + {.seqNum = 2, .seqId = 3, .reserved = 0}, \ + {.seqNum = 4, .seqId = 5, .reserved = 0}, \ + {.seqNum = 2, .seqId = 9, .reserved = 0}, \ + {.seqNum = 4, .seqId = 11, .reserved = 0}, \ + } + +#define MG_FLEXSPI_LUT_OPERAND0(x) (((uint32_t) (((uint32_t) (x)))) & 0xFFU) +#define MG_FLEXSPI_LUT_NUM_PADS0(x) \ + (((uint32_t) (((uint32_t) (x)) << 8U)) & 0x300U) +#define MG_FLEXSPI_LUT_OPCODE0(x) \ + (((uint32_t) (((uint32_t) (x)) << 10U)) & 0xFC00U) +#define MG_FLEXSPI_LUT_OPERAND1(x) \ + (((uint32_t) (((uint32_t) (x)) << 16U)) & 0xFF0000U) +#define MG_FLEXSPI_LUT_NUM_PADS1(x) \ + (((uint32_t) (((uint32_t) (x)) << 24U)) & 0x3000000U) +#define MG_FLEXSPI_LUT_OPCODE1(x) \ + (((uint32_t) (((uint32_t) (x)) << 26U)) & 0xFC000000U) + +#if MG_OTA == MG_OTA_RT1020 || MG_OTA == MG_OTA_RT1050 +// RT102X and RT105x boards support ROM API version 1.4 +struct mg_flexspi_nor_driver_interface { + uint32_t version; + int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t dst_addr, const uint32_t *src); + uint32_t reserved; + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t lengthInBytes); + uint32_t reserved2; + int (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t seqNumber); + int (*xfer)(uint32_t instance, char *xfer); + void (*clear_cache)(uint32_t instance); +}; +#elif MG_OTA <= MG_OTA_RT1064 +// RT104x and RT106x support ROM API version 1.5 +struct mg_flexspi_nor_driver_interface { + uint32_t version; + int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t dst_addr, const uint32_t *src); + int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t lengthInBytes); + int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *dst, uint32_t addr, uint32_t lengthInBytes); + void (*clear_cache)(uint32_t instance); + int (*xfer)(uint32_t instance, char *xfer); + int (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t seqNumber); + int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *option); +}; +#else +// RT117x support ROM API version 1.7 +struct mg_flexspi_nor_driver_interface { + uint32_t version; + int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*program)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t dst_addr, const uint32_t *src); + int (*erase_all)(uint32_t instance, struct mg_flexspi_nor_config *config); + int (*erase)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t start, uint32_t lengthInBytes); + int (*read)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *dst, uint32_t addr, uint32_t lengthInBytes); + uint32_t reserved; + int (*xfer)(uint32_t instance, char *xfer); + int (*update_lut)(uint32_t instance, uint32_t seqIndex, + const uint32_t *lutBase, uint32_t seqNumber); + int (*get_config)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t *option); + int (*erase_sector)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t address); + int (*erase_block)(uint32_t instance, struct mg_flexspi_nor_config *config, + uint32_t address); + void (*hw_reset)(uint32_t instance, uint32_t resetLogic); + int (*wait_busy)(uint32_t instance, struct mg_flexspi_nor_config *config, + bool isParallelMode, uint32_t address); + int (*set_clock_source)(uint32_t instance, uint32_t clockSrc); + void (*config_clock)(uint32_t instance, uint32_t freqOption, + uint32_t sampleClkMode); +}; +#endif + +#if MG_OTA <= MG_OTA_RT1064 +#define MG_FLEXSPI_BASE 0x402A8000 +#define flexspi_nor \ + (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0020001c + \ + 16))) +#else +#define MG_FLEXSPI_BASE 0x400CC000 +#define flexspi_nor \ + (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0021001c + \ + 12))) +#endif + +static bool s_flash_irq_disabled; + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_imxrt.start, + *end = base + s_mg_flash_imxrt.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_imxrt.secsz) == 0; +} + +#if MG_OTA == MG_OTA_RT1050 +// Configuration for Hyper flash memory +static struct mg_flexspi_nor_config default_config = { + .memConfig = + { + .tag = MG_FLEXSPI_CFG_BLK_TAG, + .version = MG_FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = 3, // ReadSampleClk_LoopbackFromDqsPad + .csHoldTime = 3, + .csSetupTime = 3, + .columnAddressWidth = 3u, + .controllerMiscOption = + MG_BIT(6) | MG_BIT(4) | MG_BIT(3) | MG_BIT(0), + .deviceType = 1, // serial NOR + .sflashPadType = 8, + .serialClkFreq = 7, // 133MHz + .sflashA1Size = 64 * 1024 * 1024, + .dataValidTime = {15, 0}, + .busyOffset = 15, + .busyBitPolarity = 1, + .lutCustomSeqEnable = 0x1, + .lookupTable = MG_FLEXSPI_HYPER_LUT, + .lutCustomSeq = MG_LUT_CUSTOM_SEQ, + }, + .pageSize = 512, + .sectorSize = 256 * 1024, + .ipcmdSerialClkFreq = 1, + .serialNorType = 1u, + .blockSize = 256 * 1024, + .isUniformBlockSize = true}; +#else +// Note: this QSPI configuration works for RTs supporting QSPI +// Configuration for QSPI memory +static struct mg_flexspi_nor_config default_config = { + .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG, + .version = MG_FLEXSPI_CFG_BLK_VERSION, + .readSampleClkSrc = 1, // ReadSampleClk_LoopbackFromDqsPad + .csHoldTime = 3, + .csSetupTime = 3, + .controllerMiscOption = MG_BIT(4), + .deviceType = 1, // serial NOR + .sflashPadType = 4, + .serialClkFreq = 7, // 133MHz + .sflashA1Size = 8 * 1024 * 1024, + .lookupTable = MG_FLEXSPI_QSPI_LUT}, + .pageSize = 256, + .sectorSize = 4 * 1024, + .ipcmdSerialClkFreq = 1, + .blockSize = 64 * 1024, + .isUniformBlockSize = false}; +#endif + +// must reside in RAM, as flash will be erased +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config **config) { + *config = &default_config; + return 0; +} + +#if 0 +// ROM API get_config call (ROM version >= 1.5) +MG_IRAM static int flexspi_nor_get_config( + struct mg_flexspi_nor_config **config) { + uint32_t options[] = {0xc0000000, 0x00}; + + MG_ARM_DISABLE_IRQ(); + uint32_t status = + flexspi_nor->get_config(FLEXSPI_NOR_INSTANCE, *config, options); + if (!s_flash_irq_disabled) { + MG_ARM_ENABLE_IRQ(); + } + if (status) { + MG_ERROR(("Failed to extract flash configuration: status %u", status)); + } + return status; +} +#endif + +MG_IRAM static void mg_spin(volatile uint32_t count) { + while (count--) (void) 0; +} + +MG_IRAM static void flash_wait(void) { + while ((*((volatile uint32_t *) (MG_FLEXSPI_BASE + 0xE0)) & MG_BIT(1)) == 0) + mg_spin(1); +} + +MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config, + void *addr) { + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_imxrt.start); + + bool ok = (flexspi_nor->erase(FLEXSPI_NOR_INSTANCE, config, (uint32_t) dst, + s_mg_flash_imxrt.secsz) == 0); + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +#if 0 +// standalone erase call +MG_IRAM static bool mg_imxrt_erase(void *addr) { + struct mg_flexspi_nor_config config, *config_ptr = &config; + bool ret; + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + ret = (flexspi_nor_get_config(&config_ptr) == 0); + if (ret) ret = flash_erase(config_ptr, addr); + MG_ARM_ENABLE_IRQ(); + return ret; +} +#endif + +MG_IRAM bool mg_imxrt_swap(void) { + return true; +} + +MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) { + struct mg_flexspi_nor_config config, *config_ptr = &config; bool ok = false; - if (s_size) { - MG_ERROR(("OTA already in progress. Call mg_ota_end()")); - } else { - size_t half = mg_flash_size() / 2, max = half - mg_flash_sector_size(); - s_crc32 = 0; - s_addr = (char *) mg_flash_start() + half; - MG_DEBUG(("Firmware %lu bytes, max %lu", new_firmware_size, max)); - if (new_firmware_size < max) { - ok = true; - s_size = new_firmware_size; - MG_INFO(("Starting OTA, firmware size %lu", s_size)); + // Interrupts must be disabled before calls to ROM API in RT1020 and 1060 + MG_ARM_DISABLE_IRQ(); + if (flexspi_nor_get_config(&config_ptr) != 0) goto fwxit; + if ((len % s_mg_flash_imxrt.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_imxrt.align)); + goto fwxit; + } + if ((char *) addr < (char *) s_mg_flash_imxrt.start) { + MG_ERROR(("Invalid flash write address: %p", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + while (ok && src < end) { + if (flash_page_start(dst) && flash_erase(config_ptr, dst) == false) { + ok = false; + break; + } + uint32_t status; + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_imxrt.start; + if ((char *) buf >= (char *) s_mg_flash_imxrt.start) { + // If we copy from FLASH to FLASH, then we first need to copy the source + // to RAM + size_t tmp_buf_size = s_mg_flash_imxrt.align / sizeof(uint32_t); + uint32_t tmp[tmp_buf_size]; + + for (size_t i = 0; i < tmp_buf_size; i++) { + flash_wait(); + tmp[i] = src[i]; + } + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr, + (uint32_t) dst_ofs, tmp); } else { - MG_ERROR(("Firmware %lu is too big to fit %lu", new_firmware_size, max)); + status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr, + (uint32_t) dst_ofs, src); + } + src = (uint32_t *) ((char *) src + s_mg_flash_imxrt.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_imxrt.align); + if (status != 0) { + ok = false; } } + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); +fwxit: + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); return ok; } +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_imxrt_write(p1 + ofs, p2 + ofs, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_imxrt); +} + bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_imxrt); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_imxrt)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_imxrt.size, + s_mg_flash_imxrt.size / s_mg_flash_imxrt.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_imxrt.start, + (char *) s_mg_flash_imxrt.start + s_mg_flash_imxrt.size / 2, + s_mg_flash_imxrt.size / 2, s_mg_flash_imxrt.secsz); + } + } + return false; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_mcxn.c" +#endif + + + + +#if MG_OTA == MG_OTA_MCXN + +// - Flash phrase: 16 bytes; smallest portion programmed in one operation. +// - Flash page: 128 bytes; largest portion programmed in one operation. +// - Flash sector: 8 KB; smallest portion that can be erased in one operation. +// - Flash API mg_flash_driver->program: "start" and "len" must be page-size +// aligned; to use 'phrase', FMU register access is needed. Using ROM + +static bool mg_mcxn_write(void *, const void *, size_t); +static bool mg_mcxn_swap(void); + +static struct mg_flash s_mg_flash_mcxn = { + (void *) 0, // Start, filled at init + 0, // Size, filled at init + 0, // Sector size, filled at init + 0, // Align, filled at init + mg_mcxn_write, + mg_mcxn_swap, +}; + +struct mg_flash_config { + uint32_t addr; + uint32_t size; + uint32_t blocks; + uint32_t page_size; + uint32_t sector_size; + uint32_t ffr[6]; + uint32_t reserved0[5]; + uint32_t *bootctx; + bool useahb; +}; + +struct mg_flash_driver_interface { + uint32_t version; + uint32_t (*init)(struct mg_flash_config *); + uint32_t (*erase)(struct mg_flash_config *, uint32_t start, uint32_t len, + uint32_t key); + uint32_t (*program)(struct mg_flash_config *, uint32_t start, uint8_t *src, + uint32_t len); + uint32_t (*verify_erase)(struct mg_flash_config *, uint32_t start, + uint32_t len); + uint32_t (*verify_program)(struct mg_flash_config *, uint32_t start, + uint32_t len, const uint8_t *expected, + uint32_t *addr, uint32_t *failed); + uint32_t reserved1[12]; + uint32_t (*read)(struct mg_flash_config *, uint32_t start, uint8_t *dest, + uint32_t len); + uint32_t reserved2[4]; + uint32_t (*deinit)(struct mg_flash_config *); +}; +#define mg_flash_driver \ + ((struct mg_flash_driver_interface *) (*((uint32_t *) 0x1303fc00 + 4))) +#define MG_MCXN_FLASK_KEY (('k' << 24) | ('e' << 16) | ('f' << 8) | 'l') + +MG_IRAM static bool flash_sector_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_mcxn.start, + *end = base + s_mg_flash_mcxn.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_mcxn.secsz) == 0; +} + +MG_IRAM static bool flash_erase(struct mg_flash_config *config, void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + uint32_t dst = + (uint32_t) addr - (uint32_t) s_mg_flash_mcxn.start; // future-proof + uint32_t status = mg_flash_driver->erase(config, dst, s_mg_flash_mcxn.secsz, + MG_MCXN_FLASK_KEY); + bool ok = (status == 0); + if (!ok) MG_ERROR(("Flash write error: %lu", status)); + MG_DEBUG(("Sector starting at %p erasure: %s", addr, ok ? "ok" : "fail")); + return ok; +} + +#if 0 +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_mcxn_erase(void *addr) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + bool ok = flash_erase(&config, addr); + mg_flash_driver->deinit(&config); + return ok; +} +#endif + +MG_IRAM static bool mg_mcxn_swap(void) { + // TODO(): no devices so far + return true; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_mcxn_write(void *addr, const void *buf, size_t len) { bool ok = false; - if (s_size == 0) { - MG_ERROR(("OTA is not started, call mg_ota_begin()")); - } else { - size_t align = mg_flash_write_align(); - size_t len_aligned_down = MG_ROUND_DOWN(len, align); - if (len_aligned_down) ok = mg_flash_write(s_addr, buf, len_aligned_down); - if (len_aligned_down < len) { - size_t left = len - len_aligned_down; - char tmp[align]; - memset(tmp, 0xff, sizeof(tmp)); - memcpy(tmp, (char *) buf + len_aligned_down, left); - ok = mg_flash_write(s_addr + len_aligned_down, tmp, sizeof(tmp)); + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + if ((len % s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_mcxn.align)); + goto fwxit; + } + if ((((size_t) addr - (size_t) s_mg_flash_mcxn.start) % + s_mg_flash_mcxn.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + goto fwxit; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + ok = true; + + MG_ARM_DISABLE_IRQ(); + while (ok && src < end) { + if (flash_sector_start(dst) && flash_erase(&config, dst) == false) { + ok = false; + break; + } + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_mcxn.start; + // assume source is in RAM or in a different bank or read-while-write + status = mg_flash_driver->program(&config, dst_ofs, (uint8_t *) src, + s_mg_flash_mcxn.align); + src = (uint32_t *) ((char *) src + s_mg_flash_mcxn.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_mcxn.align); + if (status != 0) { + MG_ERROR(("Flash write error: %lu", status)); + ok = false; } - s_crc32 = mg_crc32(s_crc32, (char *) buf, len); // Update CRC - MG_DEBUG(("%#x %p %lu -> %d", s_addr - len, buf, len, ok)); - s_addr += len; } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s.", len, dst, ok ? "ok" : "fail")); + +fwxit: + mg_flash_driver->deinit(&config); return ok; } -MG_IRAM static uint32_t mg_fwkey(int fw) { - uint32_t key = MG_OTADATA_KEY + fw; - int bank = mg_flash_bank(); - if (bank == 2 && fw == MG_FIRMWARE_PREVIOUS) key--; - if (bank == 2 && fw == MG_FIRMWARE_CURRENT) key++; - return key; +// try to swap (honor dual image), otherwise just overwrite +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + char *tmp = mg_calloc(1, ss); + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + if (tmp != NULL) + for (size_t i = 0; i < ss; i++) tmp[i] = p1[ofs + i]; + mg_mcxn_write(p1 + ofs, p2 + ofs, ss); + if (tmp != NULL) mg_mcxn_write(p2 + ofs, tmp, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + uint32_t status; + struct mg_flash_config config; + if ((status = mg_flash_driver->init(&config)) != 0) { + MG_ERROR(("Flash driver init error: %lu", status)); + return false; + } + s_mg_flash_mcxn.start = (void *) config.addr; + s_mg_flash_mcxn.size = config.size; + s_mg_flash_mcxn.secsz = config.sector_size; + s_mg_flash_mcxn.align = config.page_size; + mg_flash_driver->deinit(&config); + MG_DEBUG( + ("%lu-byte flash @%p, using %lu-byte sectors with %lu-byte-aligned pages", + s_mg_flash_mcxn.size, s_mg_flash_mcxn.start, s_mg_flash_mcxn.secsz, + s_mg_flash_mcxn.align)); + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_mcxn); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_mcxn); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_mcxn)) { + if (0) { // is_dualbank() + // TODO(): no devices so far + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_mcxn.size, + s_mg_flash_mcxn.size / s_mg_flash_mcxn.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_mcxn.start, + (char *) s_mg_flash_mcxn.start + s_mg_flash_mcxn.size / 2, + s_mg_flash_mcxn.size / 2, s_mg_flash_mcxn.secsz); + } + } + return false; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_picosdk.c" +#endif + + + + +#if MG_OTA == MG_OTA_PICOSDK + +// Both RP2040 and RP2350 have no flash, low-level flash access support in +// bootrom, and high-level support in Pico-SDK (2.0+ for the RP2350) +// - The RP2350 in RISC-V mode is not tested +// NOTE(): See OTA design notes + +static bool mg_picosdk_write(void *, const void *, size_t); +static bool mg_picosdk_swap(void); + +static struct mg_flash s_mg_flash_picosdk = { + (void *) 0x10000000, // Start; functions handle offset +#ifdef PICO_FLASH_SIZE_BYTES + PICO_FLASH_SIZE_BYTES, // Size, from board definitions +#else + 0x200000, // Size, guess... is 2M enough ? +#endif + FLASH_SECTOR_SIZE, // Sector size, from hardware_flash + FLASH_PAGE_SIZE, // Align, from hardware_flash + mg_picosdk_write, mg_picosdk_swap, +}; + +#define MG_MODULO2(x, m) ((x) & ((m) -1)) + +static bool __no_inline_not_in_flash_func(flash_sector_start)( + volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_picosdk.start, + *end = base + s_mg_flash_picosdk.size; + volatile char *p = (char *) dst; + return p >= base && p < end && + MG_MODULO2(p - base, s_mg_flash_picosdk.secsz) == 0; +} + +static bool __no_inline_not_in_flash_func(flash_erase)(void *addr) { + if (flash_sector_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + return false; + } + void *dst = (void *) ((char *) addr - (char *) s_mg_flash_picosdk.start); + flash_range_erase((uint32_t) dst, s_mg_flash_picosdk.secsz); + MG_DEBUG(("Sector starting at %p erasure", addr)); + return true; +} + +static bool __no_inline_not_in_flash_func(mg_picosdk_swap)(void) { + // TODO(): RP2350 might have some A/B functionality (DS 5.1) + return true; +} + +static bool s_flash_irq_disabled; + +static bool __no_inline_not_in_flash_func(mg_picosdk_write)(void *addr, + const void *buf, + size_t len) { + if ((len % s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_picosdk.align)); + return false; + } + if ((((size_t) addr - (size_t) s_mg_flash_picosdk.start) % + s_mg_flash_picosdk.align) != 0) { + MG_ERROR(("%p is not on a page boundary", addr)); + return false; + } + + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + +#ifndef __riscv + MG_ARM_DISABLE_IRQ(); +#else + asm volatile("csrrc zero, mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + while (src < end) { + uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_picosdk.start; + if (flash_sector_start(dst) && flash_erase(dst) == false) break; + // flash_range_program() runs in RAM and handles writing up to + // FLASH_PAGE_SIZE bytes. Source must not be in flash + flash_range_program((uint32_t) dst_ofs, (uint8_t *) src, + s_mg_flash_picosdk.align); + src = (uint32_t *) ((char *) src + s_mg_flash_picosdk.align); + dst = (uint32_t *) ((char *) dst + s_mg_flash_picosdk.align); + } + if (!s_flash_irq_disabled) { +#ifndef __riscv + MG_ARM_ENABLE_IRQ(); +#else + asm volatile("csrrs mstatus, %0" : : "i"(1 << 3) : "memory"); +#endif + } + MG_DEBUG(("Flash write %lu bytes @ %p.", len, dst)); + return true; +} + +// just overwrite instead of swap +static void __no_inline_not_in_flash_func(single_bank_swap)(char *p1, char *p2, + size_t s, + size_t ss) { + char *tmp = mg_calloc(1, ss); + if (tmp == NULL) return; +#if PICO_RP2040 + uint32_t xip[256 / sizeof(uint32_t)]; + void *dst = (void *) ((char *) p1 - (char *) s_mg_flash_picosdk.start); + size_t count = MG_ROUND_UP(s, ss); + // use SDK function calls to get BootROM function pointers + rom_connect_internal_flash_fn connect = (rom_connect_internal_flash_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH); + rom_flash_exit_xip_fn xit = (rom_flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); + rom_flash_range_program_fn program = (rom_flash_range_program_fn) rom_func_lookup(ROM_FUNC_FLASH_RANGE_PROGRAM); + rom_flash_flush_cache_fn flush = (rom_flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); + // no stdlib calls here. + MG_ARM_DISABLE_IRQ(); + // 2nd bootloader (XIP) is in flash, SDK functions copy it to RAM on entry + for (size_t i = 0; i < 256 / sizeof(uint32_t); i++) + xip[i] = ((uint32_t *) (s_mg_flash_picosdk.start))[i]; + flash_range_erase((uint32_t) dst, count); + // flash has been erased, no XIP to copy. Only BootROM calls possible + for (uint32_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) tmp[i] = p2[ofs + i]; + __compiler_memory_barrier(); + connect(); + xit(); + program((uint32_t) dst + ofs, tmp, ss); + flush(); + ((void (*)(void))((intptr_t) xip + 1))(); // enter XIP again + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; // AIRCR = SYSRESETREQ +#else + // RP2350 has BootRAM and copies second bootloader there, SDK uses that copy, + // It might also be able to take advantage of partition swapping + rom_reboot_fn reboot = (rom_reboot_fn) rom_func_lookup(ROM_FUNC_REBOOT); + for (size_t ofs = 0; ofs < s; ofs += ss) { + for (size_t i = 0; i < ss; i++) tmp[i] = p2[ofs + i]; + mg_picosdk_write(p1 + ofs, tmp, ss); + } + reboot(BOOT_TYPE_NORMAL | 0x100, 1, 0, 0); // 0x100: NO_RETURN_ON_SUCCESS +#endif +} + +bool mg_ota_begin(size_t new_firmware_size) { + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_picosdk); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_picosdk); } bool mg_ota_end(void) { - char *base = (char *) mg_flash_start() + mg_flash_size() / 2; + if (mg_ota_flash_end(&s_mg_flash_picosdk)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_picosdk.size, + s_mg_flash_picosdk.size / s_mg_flash_picosdk.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished or return on failure + single_bank_swap( + (char *) s_mg_flash_picosdk.start, + (char *) s_mg_flash_picosdk.start + s_mg_flash_picosdk.size / 2, + s_mg_flash_picosdk.size / 2, s_mg_flash_picosdk.secsz); + } + return false; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_stm32f.c" +#endif + + + + +#if MG_OTA == MG_OTA_STM32F + +static bool mg_stm32f_write(void *, const void *, size_t); +static bool mg_stm32f_swap(void); + +static struct mg_flash s_mg_flash_stm32f = { + (void *) 0x08000000, // Start + 0, // Size, FLASH_SIZE_REG + 0, // Irregular sector size + 32, // Align, 256 bit + mg_stm32f_write, + mg_stm32f_swap, +}; + +#define MG_FLASH_BASE 0x40023c00 +#define MG_FLASH_KEYR 0x04 +#define MG_FLASH_SR 0x0c +#define MG_FLASH_CR 0x10 +#define MG_FLASH_OPTCR 0x14 +#define MG_FLASH_SIZE_REG_F7 0x1FF0F442 +#define MG_FLASH_SIZE_REG_F4 0x1FFF7A22 + +#define STM_DBGMCU_IDCODE 0xE0042000 +#define STM_DEV_ID (MG_REG(STM_DBGMCU_IDCODE) & (MG_BIT(12) - 1)) +#define SYSCFG_MEMRMP 0x40013800 + +#define MG_FLASH_SIZE_REG_LOCATION \ + ((STM_DEV_ID >= 0x449) ? MG_FLASH_SIZE_REG_F7 : MG_FLASH_SIZE_REG_F4) + +static size_t flash_size(void) { + return (MG_REG(MG_FLASH_SIZE_REG_LOCATION) & 0xFFFF) * 1024; +} + +MG_IRAM static int is_dualbank(void) { + // only F42x/F43x series (0x419) support dual bank + return STM_DEV_ID == 0x419; +} + +MG_IRAM static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(MG_FLASH_BASE + MG_FLASH_KEYR) = 0x45670123; + MG_REG(MG_FLASH_BASE + MG_FLASH_KEYR) = 0xcdef89ab; + unlocked = true; + } +} + +#define MG_FLASH_CONFIG_16_64_128 1 // used by STM32F7 +#define MG_FLASH_CONFIG_32_128_256 2 // used by STM32F4 and F2 + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_stm32f.start; + char *end = base + s_mg_flash_stm32f.size; + + if (is_dualbank() && dst >= (uint32_t *) (base + (end - base) / 2)) { + dst = (uint32_t *) ((uint32_t) dst - (end - base) / 2); + } + + uint32_t flash_config = MG_FLASH_CONFIG_16_64_128; + if (STM_DEV_ID >= 0x449) { + flash_config = MG_FLASH_CONFIG_32_128_256; + } + + volatile char *p = (char *) dst; + if (p >= base && p < end) { + if (p < base + 16 * 1024 * 4 * flash_config) { + if ((p - base) % (16 * 1024 * flash_config) == 0) return true; + } else if (p == base + 16 * 1024 * 4 * flash_config) { + return true; + } else if ((p - base) % (128 * 1024 * flash_config) == 0) { + return true; + } + } + return false; +} + +MG_IRAM static int flash_sector(volatile uint32_t *addr) { + char *base = (char *) s_mg_flash_stm32f.start; + char *end = base + s_mg_flash_stm32f.size; + bool addr_in_bank_2 = false; + if (is_dualbank() && addr >= (uint32_t *) (base + (end - base) / 2)) { + addr = (uint32_t *) ((uint32_t) addr - (end - base) / 2); + addr_in_bank_2 = true; + } + volatile char *p = (char *) addr; + uint32_t flash_config = MG_FLASH_CONFIG_16_64_128; + if (STM_DEV_ID >= 0x449) { + flash_config = MG_FLASH_CONFIG_32_128_256; + } + int sector = -1; + if (p >= base && p < end) { + if (p < base + 16 * 1024 * 4 * flash_config) { + sector = (p - base) / (16 * 1024 * flash_config); + } else if (p >= base + 64 * 1024 * flash_config && + p < base + 128 * 1024 * flash_config) { + sector = 4; + } else { + sector = (p - base) / (128 * 1024 * flash_config) + 4; + } + } + if (sector == -1) return -1; + if (addr_in_bank_2) sector += 12; // a bank has 12 sectors + return sector; +} + +MG_IRAM static bool flash_is_err(void) { + return MG_REG(MG_FLASH_BASE + MG_FLASH_SR) & ((MG_BIT(7) - 1) << 1); +} + +MG_IRAM static void flash_wait(void) { + while (MG_REG(MG_FLASH_BASE + MG_FLASH_SR) & (MG_BIT(16))) (void) 0; +} + +MG_IRAM static void flash_clear_err(void) { + flash_wait(); // Wait until ready + MG_REG(MG_FLASH_BASE + MG_FLASH_SR) = 0xf2; // Clear all errors +} + +MG_IRAM static bool mg_stm32f_erase(void *addr) { bool ok = false; - if (s_size) { - size_t size = s_addr - base; - uint32_t crc32 = mg_crc32(0, base, s_size); - if (size == s_size && crc32 == s_crc32) { - uint32_t now = (uint32_t) (mg_now() / 1000); - struct mg_otadata od = {crc32, size, now, MG_OTA_FIRST_BOOT}; - uint32_t key = mg_fwkey(MG_FIRMWARE_PREVIOUS); - ok = mg_flash_save(NULL, key, &od, sizeof(od)); + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); + } else { + int sector = flash_sector(addr); + if (sector < 0) return false; + uint32_t sector_reg = sector; + if (is_dualbank() && sector >= 12) { + // 3.9.8 Flash control register (FLASH_CR) for F42xxx and F43xxx + // BITS[7:3] + sector_reg -= 12; + sector_reg |= MG_BIT(4); } - MG_DEBUG(("CRC: %x/%x, size: %lu/%lu, status: %s", s_crc32, crc32, s_size, - size, ok ? "ok" : "fail")); - s_size = 0; - if (ok) ok = mg_flash_swap_bank(); + flash_unlock(); + flash_wait(); + uint32_t cr = MG_BIT(1); // SER + cr |= MG_BIT(16); // STRT + cr |= (sector_reg & 31) << 3; // sector + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) = cr; + ok = !flash_is_err(); + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, + ok ? "ok" : "fail", MG_REG(MG_FLASH_BASE + MG_FLASH_CR), + MG_REG(MG_FLASH_BASE + MG_FLASH_SR))); + // After we have erased the sector, set CR flags for programming + // 2 << 8 is word write parallelism, bit(0) is PG. RM0385, section 3.7.5 + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) = MG_BIT(0) | (2 << 8); + flash_clear_err(); } - MG_INFO(("Finishing OTA: %s", ok ? "ok" : "fail")); return ok; } -MG_IRAM static struct mg_otadata mg_otadata(int fw) { - uint32_t key = mg_fwkey(fw); - struct mg_otadata od = {}; - MG_INFO(("Loading %s OTA data", fw == MG_FIRMWARE_CURRENT ? "curr" : "prev")); - mg_flash_load(NULL, key, &od, sizeof(od)); - // MG_DEBUG(("Loaded OTA data. fw %d, bank %d, key %p", fw, bank, key)); - // mg_hexdump(&od, sizeof(od)); - return od; -} - -int mg_ota_status(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.status; -} -uint32_t mg_ota_crc32(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.crc32; -} -uint32_t mg_ota_timestamp(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.timestamp; -} -size_t mg_ota_size(int fw) { - struct mg_otadata od = mg_otadata(fw); - return od.size; +MG_IRAM static bool mg_stm32f_swap(void) { + // STM32 F42x/F43x support dual bank, however, the memory mapping + // change will not be carried through a hard reset. Therefore, we will use + // the single bank approach for this family as well. + return true; } -MG_IRAM bool mg_ota_commit(void) { +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_stm32f_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32f.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32f.align)); + return false; + } + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); bool ok = true; - struct mg_otadata od = mg_otadata(MG_FIRMWARE_CURRENT); - if (od.status != MG_OTA_COMMITTED) { - od.status = MG_OTA_COMMITTED; - MG_INFO(("Committing current firmware, OD size %lu", sizeof(od))); - ok = mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &od, sizeof(od)); + MG_ARM_DISABLE_IRQ(); + flash_unlock(); + flash_clear_err(); + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) = MG_BIT(0) | MG_BIT(9); // PG, 32-bit + flash_wait(); + MG_DEBUG(("Writing flash @ %p, %lu bytes", addr, len)); + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32f_erase(dst) == false) break; + *(volatile uint32_t *) dst++ = *src++; + MG_DSB(); // ensure flash is written with no errors + flash_wait(); + if (flash_is_err()) ok = false; } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + ok ? "ok" : "fail", MG_REG(MG_FLASH_BASE + MG_FLASH_CR), + MG_REG(MG_FLASH_BASE + MG_FLASH_SR))); + MG_REG(MG_FLASH_BASE + MG_FLASH_CR) &= ~MG_BIT(0); // Clear programming flag return ok; } -bool mg_ota_rollback(void) { - MG_DEBUG(("Rolling firmware back")); - if (mg_flash_bank() == 0) { - // No dual bank support. Mark previous firmware as FIRST_BOOT - struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); - prev.status = MG_OTA_FIRST_BOOT; - return mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &prev, - sizeof(prev)); - } else { - return mg_flash_swap_bank(); - } +// just overwrite instead of swap +MG_IRAM void single_bank_swap(char *p1, char *p2, size_t size) { + // no stdlib calls here + mg_stm32f_write(p1, p2, size); + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; } -MG_IRAM void mg_ota_boot(void) { - MG_INFO(("Booting. Flash bank: %d", mg_flash_bank())); - struct mg_otadata curr = mg_otadata(MG_FIRMWARE_CURRENT); - struct mg_otadata prev = mg_otadata(MG_FIRMWARE_PREVIOUS); - - if (curr.status == MG_OTA_FIRST_BOOT) { - if (prev.status == MG_OTA_UNAVAILABLE) { - MG_INFO(("Setting previous firmware state to committed")); - prev.status = MG_OTA_COMMITTED; - mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_PREVIOUS), &prev, sizeof(prev)); - } - curr.status = MG_OTA_UNCOMMITTED; - MG_INFO(("First boot, setting status to UNCOMMITTED")); - mg_flash_save(NULL, mg_fwkey(MG_FIRMWARE_CURRENT), &curr, sizeof(curr)); - } else if (prev.status == MG_OTA_FIRST_BOOT && mg_flash_bank() == 0) { - // Swap paritions. Pray power does not disappear - size_t fs = mg_flash_size(), ss = mg_flash_sector_size(); - char *partition1 = mg_flash_start(); - char *partition2 = mg_flash_start() + fs / 2; - size_t ofs, max = fs / 2 - ss; // Set swap size to the whole partition - - if (curr.status != MG_OTA_UNAVAILABLE && - prev.status != MG_OTA_UNAVAILABLE) { - // We know exact sizes of both firmwares. - // Shrink swap size to the MAX(firmware1, firmware2) - size_t sz = curr.size > prev.size ? curr.size : prev.size; - if (sz > 0 && sz < max) max = sz; - } - - // MG_OTA_FIRST_BOOT -> MG_OTA_UNCOMMITTED - prev.status = MG_OTA_UNCOMMITTED; - mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_CURRENT, &prev, - sizeof(prev)); - mg_flash_save(NULL, MG_OTADATA_KEY + MG_FIRMWARE_PREVIOUS, &curr, - sizeof(curr)); +bool mg_ota_begin(size_t new_firmware_size) { + s_mg_flash_stm32f.size = flash_size(); +#ifdef __ZEPHYR__ + *((uint32_t *)0xE000ED94) = 0; + MG_DEBUG(("Jailbreak %s", *((uint32_t *)0xE000ED94) == 0 ? "successful" : "failed")); +#endif + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32f); +} - MG_INFO(("Swapping partitions, size %u (%u sectors)", max, max / ss)); +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32f); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_stm32f)) { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_stm32f.size, STM_DEV_ID == 0x449 ? 8 : 12)); MG_INFO(("Do NOT power off...")); mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + char *p1 = (char *) s_mg_flash_stm32f.start; + char *p2 = p1 + s_mg_flash_stm32f.size / 2; + size_t size = s_mg_flash_stm32f.size / 2; + // Runs in RAM, will reset when finished + single_bank_swap(p1, p2, size); + } + return false; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ota_stm32h5.c" +#endif + + + + +#if MG_OTA == MG_OTA_STM32H5 + +static bool mg_stm32h5_write(void *, const void *, size_t); +static bool mg_stm32h5_swap(void); + +static struct mg_flash s_mg_flash_stm32h5 = { + (void *) 0x08000000, // Start + 2 * 1024 * 1024, // Size, 2Mb + 8 * 1024, // Sector size, 8k + 16, // Align, 128 bit + mg_stm32h5_write, + mg_stm32h5_swap, +}; + +#define MG_FLASH_BASE 0x40022000 // Base address of the flash controller +#define FLASH_KEYR (MG_FLASH_BASE + 0x4) // See RM0481 7.11 +#define FLASH_OPTKEYR (MG_FLASH_BASE + 0xc) +#define FLASH_OPTCR (MG_FLASH_BASE + 0x1c) +#define FLASH_NSSR (MG_FLASH_BASE + 0x20) +#define FLASH_NSCR (MG_FLASH_BASE + 0x28) +#define FLASH_NSCCR (MG_FLASH_BASE + 0x30) +#define FLASH_OPTSR_CUR (MG_FLASH_BASE + 0x50) +#define FLASH_OPTSR_PRG (MG_FLASH_BASE + 0x54) + +static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_KEYR) = 0Xcdef89ab; + MG_REG(FLASH_OPTKEYR) = 0x08192a3b; + MG_REG(FLASH_OPTKEYR) = 0x4c5d6e7f; + unlocked = true; + } +} + +static int flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_stm32h5.start, + *end = base + s_mg_flash_stm32h5.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h5.secsz) == 0; +} + +static bool flash_is_err(void) { + return MG_REG(FLASH_NSSR) & ((MG_BIT(8) - 1) << 17); // RM0481 7.11.9 +} - // We use the last sector of partition2 for OTA data/config storage - // Therefore we can use last sector of partition1 for swapping - char *tmpsector = partition1 + fs / 2 - ss; // Last sector of partition1 - (void) tmpsector; - for (ofs = 0; ofs < max; ofs += ss) { - // mg_flash_erase(tmpsector); - mg_flash_write(tmpsector, partition1 + ofs, ss); - // mg_flash_erase(partition1 + ofs); - mg_flash_write(partition1 + ofs, partition2 + ofs, ss); - // mg_flash_erase(partition2 + ofs); - mg_flash_write(partition2 + ofs, tmpsector, ss); +static void flash_wait(void) { + while ((MG_REG(FLASH_NSSR) & MG_BIT(0)) && + (MG_REG(FLASH_NSSR) & MG_BIT(16)) == 0) { + (void) 0; + } +} + +static void flash_clear_err(void) { + flash_wait(); // Wait until ready + MG_REG(FLASH_NSCCR) = ((MG_BIT(9) - 1) << 16U); // Clear all errors +} + +static bool flash_bank_is_swapped(void) { + return MG_REG(FLASH_OPTCR) & MG_BIT(31); // RM0481 7.11.8 +} + +static bool mg_stm32h5_erase(void *location) { + bool ok = false; + if (flash_page_start(location) == false) { + MG_ERROR(("%p is not on a sector boundary")); + } else { + uintptr_t diff = (char *) location - (char *) s_mg_flash_stm32h5.start; + uint32_t sector = diff / s_mg_flash_stm32h5.secsz; + uint32_t saved_cr = MG_REG(FLASH_NSCR); // Save CR value + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = 0; + if ((sector < 128 && flash_bank_is_swapped()) || + (sector > 127 && !flash_bank_is_swapped())) { + MG_REG(FLASH_NSCR) |= MG_BIT(31); // Set FLASH_CR_BKSEL + } + if (sector > 127) sector -= 128; + MG_REG(FLASH_NSCR) |= MG_BIT(2) | (sector << 6); // Erase | sector_num + MG_REG(FLASH_NSCR) |= MG_BIT(5); // Start erasing + flash_wait(); + ok = !flash_is_err(); + MG_DEBUG(("Erase sector %lu @ %p: %s. CR %#lx SR %#lx", sector, location, + ok ? "ok" : "fail", MG_REG(FLASH_NSCR), MG_REG(FLASH_NSSR))); + // mg_hexdump(location, 32); + MG_REG(FLASH_NSCR) = saved_cr; // Restore saved CR + } + return ok; +} + +static bool mg_stm32h5_swap(void) { + uint32_t desired = flash_bank_is_swapped() ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +static bool mg_stm32h5_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32h5.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h5.align)); + return false; + } + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + MG_ARM_DISABLE_IRQ(); + flash_unlock(); + flash_clear_err(); + MG_REG(FLASH_NSCR) = MG_BIT(1); // Set programming flag + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32h5_erase(dst) == false) { + ok = false; + break; } - mg_device_reset(); + *(volatile uint32_t *) dst++ = *src++; + flash_wait(); + if (flash_is_err()) ok = false; } + MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + flash_is_err() ? "fail" : "ok", MG_REG(FLASH_NSCR), + MG_REG(FLASH_NSSR))); + MG_REG(FLASH_NSCR) = 0; // Clear flags + return ok; +} + +bool mg_ota_begin(size_t new_firmware_size) { +#ifdef __ZEPHYR__ + *((uint32_t *)0xE000ED94) = 0; + MG_DEBUG(("Jailbreak %s", *((uint32_t *)0xE000ED94) == 0 ? "successful" : "failed")); +#endif + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h5); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h5); +} + +// Actual bank swap is deferred until reset, it is safe to execute in flash +bool mg_ota_end(void) { + if(!mg_ota_flash_end(&s_mg_flash_stm32h5)) return false; + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + return true; } #endif #ifdef MG_ENABLE_LINES -#line 1 "src/printf.c" +#line 1 "src/ota_stm32h7.c" #endif -size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt, va_list *ap) { - size_t len = mg_snprintf(NULL, 0, fmt, ap); - char *buf; - if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) { - len = 0; // Nah. Not enough space +#if MG_OTA == MG_OTA_STM32H7 || MG_OTA == MG_OTA_STM32H7_DUAL_CORE + +// - H723/735 RM 4.3.3: Note: The application can simultaneously request a read +// and a write operation through the AXI interface. +// - We only need IRAM for partition swapping in the H723, however, all +// related functions must reside in IRAM for this to be possible. +// - Linker files for other devices won't define a .iram section so there's no +// associated penalty + +static bool mg_stm32h7_write(void *, const void *, size_t); +static bool mg_stm32h7_swap(void); + +static struct mg_flash s_mg_flash_stm32h7 = { + (void *) 0x08000000, // Start + 0, // Size, FLASH_SIZE_REG + 128 * 1024, // Sector size, 128k + 32, // Align, 256 bit + mg_stm32h7_write, + mg_stm32h7_swap, +}; + +#define FLASH_BASE1 0x52002000 // Base address for bank1 +#define FLASH_BASE2 0x52002100 // Base address for bank2 +#define FLASH_KEYR 0x04 // See RM0433 4.9.2 +#define FLASH_OPTKEYR 0x08 +#define FLASH_OPTCR 0x18 +#define FLASH_SR 0x10 +#define FLASH_CR 0x0c +#define FLASH_CCR 0x14 +#define FLASH_OPTSR_CUR 0x1c +#define FLASH_OPTSR_PRG 0x20 +#define FLASH_SIZE_REG 0x1ff1e880 + +#define IS_DUALCORE() (MG_OTA == MG_OTA_STM32H7_DUAL_CORE) + +MG_IRAM static bool is_dualbank(void) { + if (IS_DUALCORE()) { + // H745/H755 and H747/H757 are running on dual core. + // Using only the 1st bank (mapped to CM7), in order not to interfere + // with the 2nd bank (CM4), possibly causing CM4 to boot unexpectedly. + return false; + } + return (s_mg_flash_stm32h7.size < 2 * 1024 * 1024) ? false : true; +} + +MG_IRAM static void flash_unlock(void) { + static bool unlocked = false; + if (unlocked == false) { + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE1 + FLASH_KEYR) = 0xcdef89ab; + if (is_dualbank()) { + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0x45670123; + MG_REG(FLASH_BASE2 + FLASH_KEYR) = 0xcdef89ab; + } + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x08192a3b; // opt reg is "shared" + MG_REG(FLASH_BASE1 + FLASH_OPTKEYR) = 0x4c5d6e7f; // thus unlock once + unlocked = true; + } +} + +MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { + char *base = (char *) s_mg_flash_stm32h7.start, + *end = base + s_mg_flash_stm32h7.size; + volatile char *p = (char *) dst; + return p >= base && p < end && ((p - base) % s_mg_flash_stm32h7.secsz) == 0; +} + +MG_IRAM static bool flash_is_err(uint32_t bank) { + return MG_REG(bank + FLASH_SR) & ((MG_BIT(11) - 1) << 17); // RM0433 4.9.5 +} + +MG_IRAM static void flash_wait(uint32_t bank) { + while (MG_REG(bank + FLASH_SR) & (MG_BIT(0) | MG_BIT(2))) (void) 0; +} + +MG_IRAM static void flash_clear_err(uint32_t bank) { + flash_wait(bank); // Wait until ready + MG_REG(bank + FLASH_CCR) = ((MG_BIT(11) - 1) << 16U); // Clear all errors +} + +MG_IRAM static bool flash_bank_is_swapped(uint32_t bank) { + return MG_REG(bank + FLASH_OPTCR) & MG_BIT(31); // RM0433 4.9.7 +} + +// Figure out flash bank based on the address +MG_IRAM static uint32_t flash_bank(void *addr) { + size_t ofs = (char *) addr - (char *) s_mg_flash_stm32h7.start; + if (!is_dualbank()) return FLASH_BASE1; + return ofs < s_mg_flash_stm32h7.size / 2 ? FLASH_BASE1 : FLASH_BASE2; +} + +// read-while-write, no need to disable IRQs for standalone usage +MG_IRAM static bool mg_stm32h7_erase(void *addr) { + bool ok = false; + if (flash_page_start(addr) == false) { + MG_ERROR(("%p is not on a sector boundary", addr)); } else { - len = mg_vsnprintf((char *) buf, len + 1, fmt, ap); - mg_queue_add(q, len); + uintptr_t diff = (char *) addr - (char *) s_mg_flash_stm32h7.start; + uint32_t sector = diff / s_mg_flash_stm32h7.secsz; + uint32_t bank = flash_bank(addr); + uint32_t saved_cr = MG_REG(bank + FLASH_CR); // Save CR value + + flash_unlock(); + if (sector > 7) sector -= 8; + + flash_clear_err(bank); + MG_REG(bank + FLASH_CR) = MG_BIT(5); // 32-bit write parallelism + MG_REG(bank + FLASH_CR) |= (sector & 7U) << 8U; // Sector to erase + MG_REG(bank + FLASH_CR) |= MG_BIT(2); // Sector erase bit + MG_REG(bank + FLASH_CR) |= MG_BIT(7); // Start erasing + ok = !flash_is_err(bank); + MG_DEBUG(("Erase sector %lu @ %p %s. CR %#lx SR %#lx", sector, addr, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + MG_REG(bank + FLASH_CR) = saved_cr; // Restore CR } - return len; + return ok; } +MG_IRAM static bool mg_stm32h7_swap(void) { + if (!is_dualbank()) return true; + uint32_t bank = FLASH_BASE1; + uint32_t desired = flash_bank_is_swapped(bank) ? 0 : MG_BIT(31); + flash_unlock(); + flash_clear_err(bank); + // printf("OPTSR_PRG 1 %#lx\n", FLASH->OPTSR_PRG); + MG_SET_BITS(MG_REG(bank + FLASH_OPTSR_PRG), MG_BIT(31), desired); + // printf("OPTSR_PRG 2 %#lx\n", FLASH->OPTSR_PRG); + MG_REG(bank + FLASH_OPTCR) |= MG_BIT(1); // OPTSTART + while ((MG_REG(bank + FLASH_OPTSR_CUR) & MG_BIT(31)) != desired) (void) 0; + return true; +} + +static bool s_flash_irq_disabled; + +MG_IRAM static bool mg_stm32h7_write(void *addr, const void *buf, size_t len) { + if ((len % s_mg_flash_stm32h7.align) != 0) { + MG_ERROR(("%lu is not aligned to %lu", len, s_mg_flash_stm32h7.align)); + return false; + } + uint32_t bank = flash_bank(addr); + uint32_t *dst = (uint32_t *) addr; + uint32_t *src = (uint32_t *) buf; + uint32_t *end = (uint32_t *) ((char *) buf + len); + bool ok = true; + MG_ARM_DISABLE_IRQ(); + flash_unlock(); + flash_clear_err(bank); + MG_REG(bank + FLASH_CR) = MG_BIT(1); // Set programming flag + MG_REG(bank + FLASH_CR) |= MG_BIT(5); // 32-bit write parallelism + while (ok && src < end) { + if (flash_page_start(dst) && mg_stm32h7_erase(dst) == false) { + ok = false; + break; + } + *(volatile uint32_t *) dst++ = *src++; + flash_wait(bank); + if (flash_is_err(bank)) ok = false; + } + if (!s_flash_irq_disabled) MG_ARM_ENABLE_IRQ(); + MG_DEBUG(("Flash write %lu bytes @ %p: %s. CR %#lx SR %#lx", len, dst, + ok ? "ok" : "fail", MG_REG(bank + FLASH_CR), + MG_REG(bank + FLASH_SR))); + MG_REG(bank + FLASH_CR) &= ~MG_BIT(1); // Clear programming flag + return ok; +} + +// just overwrite instead of swap +MG_IRAM static void single_bank_swap(char *p1, char *p2, size_t s, size_t ss) { + // no stdlib calls here + for (size_t ofs = 0; ofs < s; ofs += ss) { + mg_stm32h7_write(p1 + ofs, p2 + ofs, ss); + } + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; +} + +bool mg_ota_begin(size_t new_firmware_size) { + s_mg_flash_stm32h7.size = MG_REG(FLASH_SIZE_REG) * 1024; + if (IS_DUALCORE()) { + // Using only the 1st bank (mapped to CM7) + s_mg_flash_stm32h7.size /= 2; + } +#ifdef __ZEPHYR__ + *((uint32_t *)0xE000ED94) = 0; + MG_DEBUG(("Jailbreak %s", *((uint32_t *)0xE000ED94) == 0 ? "successful" : "failed")); +#endif + return mg_ota_flash_begin(new_firmware_size, &s_mg_flash_stm32h7); +} + +bool mg_ota_write(const void *buf, size_t len) { + return mg_ota_flash_write(buf, len, &s_mg_flash_stm32h7); +} + +bool mg_ota_end(void) { + if (mg_ota_flash_end(&s_mg_flash_stm32h7)) { + if (is_dualbank()) { + // Bank swap is deferred until reset, been executing in flash, reset + *(volatile unsigned long *) 0xe000ed0c = 0x5fa0004; + } else { + // Swap partitions. Pray power does not go away + MG_INFO(("Swapping partitions, size %u (%u sectors)", + s_mg_flash_stm32h7.size, + s_mg_flash_stm32h7.size / s_mg_flash_stm32h7.secsz)); + MG_INFO(("Do NOT power off...")); + mg_log_level = MG_LL_NONE; + s_flash_irq_disabled = true; + // Runs in RAM, will reset when finished + single_bank_swap( + (char *) s_mg_flash_stm32h7.start, + (char *) s_mg_flash_stm32h7.start + s_mg_flash_stm32h7.size / 2, + s_mg_flash_stm32h7.size / 2, s_mg_flash_stm32h7.secsz); + } + } + return false; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/printf.c" +#endif + + + + size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) { - va_list ap; + char *buf; size_t len; - va_start(ap, fmt); - len = mg_queue_vprintf(q, fmt, &ap); - va_end(ap); + va_list ap1, ap2; + va_start(ap1, fmt); + len = mg_vsnprintf(NULL, 0, fmt, &ap1); + va_end(ap1); + if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) + return 0; // Nah. Not enough space + va_start(ap2, fmt); + len = mg_vsnprintf(buf, len + 1, fmt, &ap2); + mg_queue_add(q, len); + va_end(ap2); return len; } @@ -6629,9 +8692,10 @@ + void mg_rpc_add(struct mg_rpc **head, struct mg_str method, void (*fn)(struct mg_rpc_req *), void *fn_data) { - struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc)); + struct mg_rpc *rpc = (struct mg_rpc *) mg_calloc(1, sizeof(*rpc)); if (rpc != NULL) { rpc->method = mg_strdup(method); rpc->fn = fn; @@ -6645,8 +8709,8 @@ while ((r = *head) != NULL) { if (r->fn == fn || fn == NULL) { *head = r->next; - free((void *) r->method.buf); - free(r); + mg_free((void *) r->method.buf); + mg_free(r); } else { head = &(*head)->next; } @@ -7084,6 +9148,13 @@ } } +void mg_sha256(uint8_t dst[32], uint8_t *data, size_t datasz) { + mg_sha256_ctx ctx; + mg_sha256_init(&ctx); + mg_sha256_update(&ctx, data, datasz); + mg_sha256_final(dst, &ctx); +} + void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, size_t datasz) { mg_sha256_ctx ctx; @@ -7113,6 +9184,166 @@ mg_sha256_final(dst, &ctx); } +#define rotr64(x, n) (((x) >> (n)) | ((x) << (64 - (n)))) +#define ep064(x) (rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39)) +#define ep164(x) (rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41)) +#define sig064(x) (rotr64(x, 1) ^ rotr64(x, 8) ^ ((x) >> 7)) +#define sig164(x) (rotr64(x, 19) ^ rotr64(x, 61) ^ ((x) >> 6)) + +static const uint64_t mg_sha256_k2[80] = { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, + 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, + 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, + 0x92722c851482353b, 0xa2bfe8a14cf10364, 0xa81a664bbc423001, + 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, + 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, 0xca273eceea26619c, 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, 0x6c44198c4a475817}; + +static void mg_sha384_transform(mg_sha384_ctx *ctx, const uint8_t data[]) { + uint64_t m[80]; + uint64_t a, b, c, d, e, f, g, h; + int i, j; + + for (i = 0, j = 0; i < 16; ++i, j += 8) + m[i] = ((uint64_t) data[j] << 56) | ((uint64_t) data[j + 1] << 48) | + ((uint64_t) data[j + 2] << 40) | ((uint64_t) data[j + 3] << 32) | + ((uint64_t) data[j + 4] << 24) | ((uint64_t) data[j + 5] << 16) | + ((uint64_t) data[j + 6] << 8) | ((uint64_t) data[j + 7]); + for (; i < 80; ++i) + m[i] = sig164(m[i - 2]) + m[i - 7] + sig064(m[i - 15]) + m[i - 16]; + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 80; ++i) { + uint64_t t1 = h + ep164(e) + ch(e, f, g) + mg_sha256_k2[i] + m[i]; + uint64_t t2 = ep064(a) + maj(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +void mg_sha384_init(mg_sha384_ctx *ctx) { + ctx->datalen = 0; + ctx->bitlen[0] = 0; + ctx->bitlen[1] = 0; + ctx->state[0] = 0xcbbb9d5dc1059ed8; + ctx->state[1] = 0x629a292a367cd507; + ctx->state[2] = 0x9159015a3070dd17; + ctx->state[3] = 0x152fecd8f70e5939; + ctx->state[4] = 0x67332667ffc00b31; + ctx->state[5] = 0x8eb44a8768581511; + ctx->state[6] = 0xdb0c2e0d64f98fa7; + ctx->state[7] = 0x47b5481dbefa4fa4; +} + +void mg_sha384_update(mg_sha384_ctx *ctx, const uint8_t *data, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + ctx->buffer[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 128) { + mg_sha384_transform(ctx, ctx->buffer); + ctx->bitlen[1] += 1024; + if (ctx->bitlen[1] < 1024) ctx->bitlen[0]++; + ctx->datalen = 0; + } + } +} + +void mg_sha384_final(uint8_t hash[48], mg_sha384_ctx *ctx) { + size_t i = ctx->datalen; + + if (ctx->datalen < 112) { + ctx->buffer[i++] = 0x80; + while (i < 112) ctx->buffer[i++] = 0x00; + } else { + ctx->buffer[i++] = 0x80; + while (i < 128) ctx->buffer[i++] = 0x00; + mg_sha384_transform(ctx, ctx->buffer); + memset(ctx->buffer, 0, 112); + } + + ctx->bitlen[1] += ctx->datalen * 8; + if (ctx->bitlen[1] < ctx->datalen * 8) ctx->bitlen[0]++; + ctx->buffer[127] = (uint8_t) (ctx->bitlen[1]); + ctx->buffer[126] = (uint8_t) (ctx->bitlen[1] >> 8); + ctx->buffer[125] = (uint8_t) (ctx->bitlen[1] >> 16); + ctx->buffer[124] = (uint8_t) (ctx->bitlen[1] >> 24); + ctx->buffer[123] = (uint8_t) (ctx->bitlen[1] >> 32); + ctx->buffer[122] = (uint8_t) (ctx->bitlen[1] >> 40); + ctx->buffer[121] = (uint8_t) (ctx->bitlen[1] >> 48); + ctx->buffer[120] = (uint8_t) (ctx->bitlen[1] >> 56); + ctx->buffer[119] = (uint8_t) (ctx->bitlen[0]); + ctx->buffer[118] = (uint8_t) (ctx->bitlen[0] >> 8); + ctx->buffer[117] = (uint8_t) (ctx->bitlen[0] >> 16); + ctx->buffer[116] = (uint8_t) (ctx->bitlen[0] >> 24); + ctx->buffer[115] = (uint8_t) (ctx->bitlen[0] >> 32); + ctx->buffer[114] = (uint8_t) (ctx->bitlen[0] >> 40); + ctx->buffer[113] = (uint8_t) (ctx->bitlen[0] >> 48); + ctx->buffer[112] = (uint8_t) (ctx->bitlen[0] >> 56); + mg_sha384_transform(ctx, ctx->buffer); + + for (i = 0; i < 6; ++i) { + hash[i * 8] = (uint8_t) ((ctx->state[i] >> 56) & 0xff); + hash[i * 8 + 1] = (uint8_t) ((ctx->state[i] >> 48) & 0xff); + hash[i * 8 + 2] = (uint8_t) ((ctx->state[i] >> 40) & 0xff); + hash[i * 8 + 3] = (uint8_t) ((ctx->state[i] >> 32) & 0xff); + hash[i * 8 + 4] = (uint8_t) ((ctx->state[i] >> 24) & 0xff); + hash[i * 8 + 5] = (uint8_t) ((ctx->state[i] >> 16) & 0xff); + hash[i * 8 + 6] = (uint8_t) ((ctx->state[i] >> 8) & 0xff); + hash[i * 8 + 7] = (uint8_t) (ctx->state[i] & 0xff); + } +} + +void mg_sha384(uint8_t dst[48], uint8_t *data, size_t datasz) { + mg_sha384_ctx ctx; + mg_sha384_init(&ctx); + mg_sha384_update(&ctx, data, datasz); + mg_sha384_final(dst, &ctx); +} + #ifdef MG_ENABLE_LINES #line 1 "src/sntp.c" #endif @@ -7125,6 +9356,12 @@ #define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds #define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1 +static uint64_t s_boot_timestamp = 0; // Updated by SNTP + +uint64_t mg_now(void) { + return mg_millis() + s_boot_timestamp; +} + static int64_t gettimestamp(const uint32_t *data) { uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]); if (sec) sec -= SNTP_TIME_OFFSET; @@ -7132,7 +9369,7 @@ } int64_t mg_sntp_parse(const unsigned char *buf, size_t len) { - int64_t res = -1; + int64_t epoch_milliseconds = -1; int mode = len > 0 ? buf[0] & 7 : 0; int version = len > 0 ? (buf[0] >> 3) & 7 : 0; if (len < 48) { @@ -7143,31 +9380,36 @@ MG_ERROR(("%s", "server sent a kiss of death")); } else if (version == 4 || version == 3) { // int64_t ref = gettimestamp((uint32_t *) &buf[16]); - int64_t t0 = gettimestamp((uint32_t *) &buf[24]); - int64_t t1 = gettimestamp((uint32_t *) &buf[32]); - int64_t t2 = gettimestamp((uint32_t *) &buf[40]); - int64_t t3 = (int64_t) mg_millis(); - int64_t delta = (t3 - t0) - (t2 - t1); - MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta)); - res = t2 + delta / 2; + int64_t origin_time = gettimestamp((uint32_t *) &buf[24]); + int64_t receive_time = gettimestamp((uint32_t *) &buf[32]); + int64_t transmit_time = gettimestamp((uint32_t *) &buf[40]); + int64_t now = (int64_t) mg_millis(); + int64_t latency = (now - origin_time) - (transmit_time - receive_time); + epoch_milliseconds = transmit_time + latency / 2; + s_boot_timestamp = (uint64_t) (epoch_milliseconds - now); } else { MG_ERROR(("unexpected version: %d", version)); } - return res; + return epoch_milliseconds; } static void sntp_cb(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_READ) { + uint64_t *expiration_time = (uint64_t *) c->data; + if (ev == MG_EV_OPEN) { + *expiration_time = mg_millis() + 3000; // Store expiration time in 3s + } else if (ev == MG_EV_CONNECT) { + mg_sntp_request(c); + } else if (ev == MG_EV_READ) { int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len); if (milliseconds > 0) { - MG_DEBUG(("%lu got time: %lld ms from epoch", c->id, milliseconds)); + s_boot_timestamp = (uint64_t) milliseconds - mg_millis(); mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds); - MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000), - (unsigned) (milliseconds % 1000))); + MG_DEBUG(("%lu got time: %lld ms from epoch", c->id, milliseconds)); } - mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer - } else if (ev == MG_EV_CONNECT) { - mg_sntp_request(c); + // mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer + c->is_closing = 1; + } else if (ev == MG_EV_POLL) { + if (mg_millis() > *expiration_time) c->is_closing = 1; } else if (ev == MG_EV_CLOSE) { } (void) ev_data; @@ -7189,11 +9431,9 @@ } struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, - mg_event_handler_t fn, void *fnd) { - struct mg_connection *c = NULL; + mg_event_handler_t fn, void *fn_data) { if (url == NULL) url = "udp://time.google.com:123"; - if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb; - return c; + return mg_connect_svc(mgr, url, fn, fn_data, sntp_cb, NULL); } #ifdef MG_ENABLE_LINES @@ -7329,7 +9569,7 @@ } MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n))); if (MG_SOCK_PENDING(n)) return MG_IO_WAIT; - if (MG_SOCK_RESET(n)) return MG_IO_RESET; + if (MG_SOCK_RESET(n)) return MG_IO_RESET; // MbedTLS, see #1507 if (n <= 0) return MG_IO_ERR; return n; } @@ -7342,7 +9582,9 @@ iolog(c, (char *) buf, n, false); return n > 0; } else { - return mg_iobuf_add(&c->send, c->send.len, buf, len); + return len == 0 || mg_iobuf_add(&c->send, c->send.len, buf, len) > 0; + // returning 0 means an OOM condition (iobuf couldn't resize), yet this is + // so far recoverable, let the caller decide } } @@ -7361,8 +9603,10 @@ if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0; #elif MG_ENABLE_LWIP lwip_fcntl(fd, F_SETFL, O_NONBLOCK); -#elif MG_ARCH == MG_ARCH_AZURERTOS - fcntl(fd, F_SETFL, O_NONBLOCK); +#elif MG_ARCH == MG_ARCH_THREADX + // NetxDuo fails to send large blocks of data to the non-blocking sockets + (void) fd; + // fcntl(fd, F_SETFL, O_NONBLOCK); #elif MG_ARCH == MG_ARCH_TIRTOS int val = 0; setsockopt(fd, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val)); @@ -7377,6 +9621,30 @@ #endif } +void mg_multicast_add(struct mg_connection *c, char *ip); +void mg_multicast_add(struct mg_connection *c, char *ip) { +#if MG_ENABLE_RL + MG_ERROR(("unsupported")); +#elif MG_ENABLE_FREERTOS_TCP + // TODO(): prvAllowIPPacketIPv4() +#else + // lwIP, Unix, Windows, Zephyr 4+(, AzureRTOS ?) +#if MG_ENABLE_LWIP && !LWIP_IGMP + MG_ERROR(("LWIP_IGMP not defined, no multicast support")); +#else +#if defined(__ZEPHYR__) && ZEPHYR_VERSION_CODE < 0x40000 + MG_ERROR(("struct ip_mreq not defined")); +#else + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = inet_addr(ip); + mreq.imr_interface.s_addr = mg_htonl(INADDR_ANY); + setsockopt(FD(c), IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, + sizeof(mreq)); +#endif // !Zephyr +#endif // !lwIP +#endif +} + bool mg_open_listener(struct mg_connection *c, const char *url) { MG_SOCKET_TYPE fd = MG_INVALID_SOCKET; bool success = false; @@ -7451,7 +9719,7 @@ } MG_VERBOSE(("%lu %ld %d", c->id, n, MG_SOCK_ERR(n))); if (MG_SOCK_PENDING(n)) return MG_IO_WAIT; - if (MG_SOCK_RESET(n)) return MG_IO_RESET; + if (MG_SOCK_RESET(n)) return MG_IO_RESET; // MbedTLS, see #1507 if (n <= 0) return MG_IO_ERR; return n; } @@ -7477,17 +9745,30 @@ size_t len = c->recv.size - c->recv.len; long n = -1; if (c->is_tls) { - if (!ioalloc(c, &c->rtls)) return; - n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len], - c->rtls.size - c->rtls.len); - if (n == MG_IO_ERR && c->rtls.len == 0) { - // Close only if we have fully drained both raw (rtls) and TLS buffers - c->is_closing = 1; - } else { + // Do not read to the raw TLS buffer if it already has enough. + // This is to prevent overflowing c->rtls if our reads are slow + long m; + if (c->rtls.len < 16 * 1024 + 40) { // TLS record, header, MAC, padding + if (!ioalloc(c, &c->rtls)) return; + n = recv_raw(c, (char *) &c->rtls.buf[c->rtls.len], + c->rtls.size - c->rtls.len); if (n > 0) c->rtls.len += (size_t) n; - if (c->is_tls_hs) mg_tls_handshake(c); - n = c->is_tls_hs ? (long) MG_IO_WAIT : mg_tls_recv(c, buf, len); } + // there can still be > 16K from last iteration, always mg_tls_recv() + m = c->is_tls_hs ? (long) MG_IO_WAIT : mg_tls_recv(c, buf, len); + if (n == MG_IO_ERR || n == MG_IO_RESET) { // Windows, see #3031 + if (c->rtls.len == 0 || m < 0) { + // Close only when we have fully drained both rtls and TLS buffers + c->is_closing = 1; // or there's nothing we can do about it. + if (m < 0) m = MG_IO_ERR; // but return last record data, see #3104 + } else { // see #2885 + // TLS buffer is capped to max record size, even though, there can + // be more than one record, give TLS a chance to process them. + } + } else if (c->is_tls_hs) { + mg_tls_handshake(c); + } + n = m; } else { n = recv_raw(c, buf, len); } @@ -7501,6 +9782,7 @@ char *buf = (char *) c->send.buf; size_t len = c->send.len; long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_io_send(c, buf, len); + // TODO(): mg_tls_send() may return 0 forever on steady OOM MG_DEBUG(("%lu %ld snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd, (long) c->send.len, (long) c->send.size, (long) c->recv.len, (long) c->recv.size, n, MG_SOCK_ERR(n))); @@ -7530,13 +9812,14 @@ mg_call(c, MG_EV_CONNECT, NULL); MG_EPOLL_MOD(c, 0); if (c->is_tls_hs) mg_tls_handshake(c); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() } else { mg_error(c, "socket error"); } } static void setsockopts(struct mg_connection *c) { -#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \ +#if MG_ENABLE_FREERTOS_TCP || MG_ARCH == MG_ARCH_THREADX || \ MG_ARCH == MG_ARCH_TIRTOS (void) c; #else @@ -7554,8 +9837,9 @@ void mg_connect_resolved(struct mg_connection *c) { int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM; + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has resolved IP - c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket + c->fd = S2PTR(socket(af, type, proto)); // Create outbound socket c->is_resolving = 0; // Clear resolving flag if (FD(c) == MG_INVALID_SOCKET) { mg_error(c, "socket(): %d", MG_SOCK_ERR(-1)); @@ -7580,8 +9864,9 @@ rc = connect(FD(c), &usa.sa, slen); // Attempt to connect if (rc == 0) { // Success setlocaddr(FD(c), &c->loc); - mg_call(c, MG_EV_CONNECT, NULL); // Send MG_EV_CONNECT to the user - } else if (MG_SOCK_PENDING(rc)) { // Need to wait for TCP handshake + mg_call(c, MG_EV_CONNECT, NULL); // Send MG_EV_CONNECT to the user + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() + } else if (MG_SOCK_PENDING(rc)) { // Need to wait for TCP handshake MG_DEBUG(("%lu %ld -> %M pend", c->id, c->fd, mg_print_ip_port, &c->rem)); c->is_connecting = 1; } else { @@ -7606,8 +9891,8 @@ socklen_t sa_len = sizeof(usa); MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len); if (fd == MG_INVALID_SOCKET) { -#if MG_ARCH == MG_ARCH_AZURERTOS || defined(__ECOS) - // AzureRTOS, in non-block socket mode can mark listening socket readable +#if MG_ARCH == MG_ARCH_THREADX || defined(__ECOS) + // NetxDuo, in non-block socket mode can mark listening socket readable // even it is not. See comment for 'select' func implementation in // nx_bsd.c That's not an error, just should try later if (errno != EAGAIN) @@ -7636,10 +9921,12 @@ c->pfn_data = lsn->pfn_data; c->fn = lsn->fn; c->fn_data = lsn->fn_data; + c->is_tls = lsn->is_tls; MG_DEBUG(("%lu %ld accepted %M -> %M", c->id, c->fd, mg_print_ip_port, &c->rem, mg_print_ip_port, &c->loc)); mg_call(c, MG_EV_OPEN, NULL); mg_call(c, MG_EV_ACCEPT, NULL); + if (!c->is_tls_hs) c->is_tls = 0; // user did not call mg_tls_init() } } @@ -7708,15 +9995,15 @@ n = 0; for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { c->is_readable = c->is_writable = 0; + if (c->is_closing) ms = 1; if (skip_iotest(c)) { // Socket not valid, ignore - } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) { - ms = 1; // Don't wait if TLS is ready } else { + // Don't wait if TLS is ready + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) ms = 1; fds[n].fd = FD(c); if (can_read(c)) fds[n].events |= POLLIN; if (can_write(c)) fds[n].events |= POLLOUT; - if (c->is_closing) ms = 1; n++; } } @@ -7732,8 +10019,6 @@ for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { if (skip_iotest(c)) { // Socket not valid, ignore - } else if (c->rtls.len > 0 || mg_tls_pending(c) > 0) { - c->is_readable = 1; } else { if (fds[n].revents & POLLERR) { mg_error(c, "socket error"); @@ -7747,7 +10032,7 @@ } } #else - struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0}, *tvp; + struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_1ms = {0, 1000}, *tvp; struct mg_connection *c; fd_set rset, wset, eset; MG_SOCKET_TYPE maxfd = 0; @@ -7763,16 +10048,16 @@ FD_SET(FD(c), &eset); if (can_read(c)) FD_SET(FD(c), &rset); if (can_write(c)) FD_SET(FD(c), &wset); - if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_zero; + if (c->rtls.len > 0 || mg_tls_pending(c) > 0) tvp = &tv_1ms; if (FD(c) > maxfd) maxfd = FD(c); - if (c->is_closing) ms = 1; + if (c->is_closing) tvp = &tv_1ms; } - if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) < 0) { + if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) <= 0) { #if MG_ARCH == MG_ARCH_WIN32 if (maxfd == 0) Sleep(ms); // On Windows, select fails if no sockets #else - MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc))); + if (rc < 0) MG_ERROR(("select: %d %d", rc, MG_SOCK_ERR(rc))); #endif FD_ZERO(&rset); FD_ZERO(&wset); @@ -7781,7 +10066,12 @@ for (c = mgr->conns; c != NULL; c = c->next) { if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) { +#if MG_ARCH == MG_ARCH_THREADX + // NetxDuo stack returns exceptions for listening connection after accept + if (c->is_listening == 0) mg_error(c, "socket error"); +#else mg_error(c, "socket error"); +#endif } else { c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset); c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset); @@ -7801,8 +10091,8 @@ *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); // 127.0.0.1 usa[1] = usa[0]; - if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET && - (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != MG_INVALID_SOCKET && + if ((sp[0] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != MG_INVALID_SOCKET && + (sp[1] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != MG_INVALID_SOCKET && bind(sp[0], &usa[0].sa, n) == 0 && // bind(sp[1], &usa[1].sa, n) == 0 && // getsockname(sp[0], &usa[0].sa, &n) == 0 && // @@ -7904,11 +10194,10 @@ if (c->is_readable) accept_conn(mgr, c); } else if (c->is_connecting) { if (c->is_readable || c->is_writable) connect_conn(c); - //} else if (c->is_tls_hs) { - // if ((c->is_readable || c->is_writable)) mg_tls_handshake(c); } else { if (c->is_readable) read_conn(c); if (c->is_writable) write_conn(c); + if (c->is_tls && !c->is_tls_hs && c->send.len == 0) mg_tls_flush(c); } if (c->is_draining && c->send.len == 0) c->is_closing = 1; @@ -7924,6 +10213,7 @@ + #ifndef MG_MAX_SSI_DEPTH #define MG_MAX_SSI_DEPTH 5 #endif @@ -7945,38 +10235,42 @@ if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { buf[len++] = (char) (ch & 0xff); buf[len] = '\0'; - if (sscanf(buf, " %#x %#x", s_txdesc[s_txno][1], tsr)); if (!(s_txdesc[s_txno][1] & MG_BIT(31))) s_txdesc[s_txno][1] |= MG_BIT(31); } @@ -16278,7 +24495,180 @@ struct mg_tcpip_driver mg_tcpip_driver_same54 = { mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL, - mg_tcpip_driver_same54_up}; + mg_tcpip_driver_same54_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/sdio.c" +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// SDIO 6.9 Table 6-1 CCCR (Common Card Control Registers) +#define MG_SDIO_CCCR_SDIOREV 0x000 +#define MG_SDIO_CCCR_SDREV 0x001 +#define MG_SDIO_CCCR_IOEN 0x002 +#define MG_SDIO_CCCR_IORDY 0x003 +#define MG_SDIO_CCCR_INTEN 0x004 +#define MG_SDIO_CCCR_BIC 0x007 +#define MG_SDIO_CCCR_CCAP 0x008 +#define MG_SDIO_CCCR_CCIS 0x009 // 3 registers +#define MG_SDIO_CCCR_F0BLKSZ 0x010 // 2 registers +#define MG_SDIO_CCCR_HISPD 0x013 +// SDIO 6.10 Table 6-3 FBR (Function Basic Registers) +#define MG_SDIO_FBR_FnBLKSZ(n) (((n) &7) * 0x100 + 0x10) // 2 registers + +// SDIO 5.1 IO_RW_DIRECT Command (CMD52) +#define MG_SDIO_DATA(x) ((x) &0xFF) // bits 0-7 +#define MG_SDIO_ADDR(x) (((x) &0x1FFFF) << 9) // bits 9-25 +#define MG_SDIO_FUNC(x) (((x) &3) << 28) // bits 28-30 (30 unused here) +#define MG_SDIO_WR MG_BIT(31) + +// SDIO 5.3 IO_RW_EXTENDED Command (CMD53) +#define MG_SDIO_LEN(x) ((x) &0x1FF) // bits 0-8 +#define MG_SDIO_OPINC MG_BIT(26) +#define MG_SDIO_BLKMODE MG_BIT(27) + +// - Drivers set blocksize, drivers request transfers. Requesting a read +// transfer > blocksize means block transfer will be used. +// - To simplify the use of DMA transfers and avoid intermediate buffers, +// drivers must have room to accomodate a whole block transfer, e.g.: blocksize +// = 64, read 65 => 2 blocks = 128 bytes +// - Transfers of more than 1 byte assume (uint32_t *) data. 1-byte transfers +// use (uint8_t *) data +// - 'len' is the number of _bytes_ to transfer +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len) { + uint32_t arg, val = 0; + unsigned int blksz = 64; // TODO(): mg_sdio_set_blksz() stores in an array, + // index on f, skip if 0 + if (len == 1) { + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_FUNC(f) | MG_SDIO_ADDR(addr) | + (write ? MG_SDIO_DATA(*(uint8_t *) data) : 0); + bool res = sdio->txn(sdio, 52, arg, &val); // IO_RW_DIRECT + if (!write) *(uint8_t *) data = (uint8_t) val; + return res; + } + // IO_RW_EXTENDED + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_OPINC | MG_SDIO_FUNC(f) | + MG_SDIO_ADDR(addr); + if (len > 512 || (blksz != 0 && len > blksz)) { // SDIO 5.3 512 -> len=0 + unsigned int blkcnt; + if (blksz == 0) return false; // > 512 requires block size set + blkcnt = (len + blksz - 1) / blksz; + if (blkcnt > 511) return false; // we don't support "infinite" blocks + arg |= MG_SDIO_BLKMODE | MG_SDIO_LEN(blkcnt); // block transfer + len = blksz * blkcnt; + } else { + arg |= MG_SDIO_LEN(len); // multi-byte transfer + } + return sdio->xfr(sdio, write, arg, + (arg & MG_SDIO_BLKMODE) ? (uint16_t) blksz : 0, + (uint32_t *) data, len, &val); +} + +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz) { + uint32_t val = blksz & 0xff; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f), &val, 1)) + return false; + val = (blksz >> 8) & 0x0f; // SDIO 6.10 Table 6-4, max 2048 + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f) + 1, &val, 1)) + return false; + // TODO(): store in an array, index on f. Static 8-element array + MG_VERBOSE(("F%c block size set", (f & 7) + '0')); + return true; +} + +// Enable Fx +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7), bits; + uint32_t val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + bits = (uint8_t) val | bit; + unsigned int times = 501; + while (times--) { + val = bits; /* IOEf */ + ; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + mg_delayms(1); + val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + if (val & bit) break; + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c enabled", (f & 7) + '0')); + return true; +} + +// Wait for Fx to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7); + unsigned int times = 501; + while (times--) { + uint32_t val; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IORDY, &val, 1)) + return false; + if (val & bit) break; // IORf + mg_delayms(1); + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c ready", (f & 7) + '0')); + return true; +} + +// SDIO 6.14 Bus State Diagram +bool mg_sdio_init(struct mg_tcpip_sdio *sdio) { + uint32_t val = 0; + if (!sdio->txn(sdio, 0, 0, NULL)) return false; // GO_IDLE_STATE + sdio->txn(sdio, 5, 0, &val); // IO_SEND_OP_COND, no CRC + MG_VERBOSE(("IO Functions: %u, Memory: %c", 1 + ((val >> 28) & 7), + (val & MG_BIT(27)) ? 'Y' : 'N')); + if (!sdio->txn(sdio, 3, 0, &val)) return false; // SEND_RELATIVE_ADDR + val = ((uint32_t) val) >> 16; // RCA + if (!sdio->txn(sdio, 7, val << 16, &val)) + return false; // SELECT/DESELECT_CARD + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDIOREV, &val, 1); + MG_DEBUG(("CCCR: %u.%u, SDIO: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3, + 1 + ((val >> 6) & 3), (val >> 4) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDREV, &val, 1); + MG_VERBOSE(("SD: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_BIC, &val, 1); + MG_SET_BITS(val, 3, + MG_BIT(7) | MG_BIT(1)); // SDIO 6.9 Tables 6-1 6-2, 4-bit bus + mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_BIC, &val, 1); + // All Full-Speed SDIO cards support a 4-bit bus. Skip for Low-Speed SDIO + // cards, we don't provide separate low-level functions for width and speed + sdio->cfg(sdio, 0); // set DS; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + if (val & MG_BIT(0) /* SHS */) { + val = MG_BIT(1); /* EHS */ + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + sdio->cfg(sdio, 1); // set HS; + MG_VERBOSE(("Bus set to 4-bit @50MHz")); + } else { + MG_VERBOSE(("Bus set to 4-bit @25MHz")); + } + return true; +} + +// - 6.11 Card Information Structure (CIS): 0x0001000-0x017FF; for card common +// and all functions +// - 16.5 SDIO Card Metaformat +// - 16.7.2 CISTPL_FUNCE (0x22): Function Extension Tuple, provides standard +// information about the card (common) and each individual function. One +// CISTPL_FUNCE in each function’s CIS, immediately following the CISTPL_FUNCID +// tuple +// - 16.7.3 CISTPL_FUNCE Tuple for Function 0 (common) +// - 16.7.4 CISTPL_FUNCE Tuple for Function 1-7 + #endif #ifdef MG_ENABLE_LINES @@ -16308,10 +24698,10 @@ #define ETH_DESC_CNT 4 // Descriptors count #define ETH_DS 4 // Descriptor size (words) -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM; // TX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; // TX ethernet buffers static uint8_t s_txno; // Current TX descriptor static uint8_t s_rxno; // Current RX descriptor @@ -16422,7 +24812,7 @@ // MG_BIT(25); ETH->MACIMR = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT ETH->MACFCR = MG_BIT(7); // Disable zero quarta pause - // ETH->MACFFR = MG_BIT(31); // Receive all + ETH->MACFFR = MG_BIT(10); // Perfect filtering struct mg_phy phy = {eth_read_phy, eth_write_phy}; mg_phy_init(&phy, phy_addr, MG_PHY_CLOCKS_MAC); ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors @@ -16464,7 +24854,22 @@ return len; } -static bool mg_tcpip_driver_stm32f_up(struct mg_tcpip_if *ifp) { +static void mg_tcpip_driver_stm32f_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + ETH->MACA1LR = (uint32_t) mcast_addr[3] << 24 | + (uint32_t) mcast_addr[2] << 16 | + (uint32_t) mcast_addr[1] << 8 | (uint32_t) mcast_addr[0]; + ETH->MACA1HR = (uint32_t) mcast_addr[5] << 8 | (uint32_t) mcast_addr[4]; + ETH->MACA1HR |= MG_BIT(31); // AE + (void) ifp; +} + +static bool mg_tcpip_driver_stm32f_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_stm32f_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; struct mg_tcpip_driver_stm32f_data *d = (struct mg_tcpip_driver_stm32f_data *) ifp->driver_data; uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; @@ -16515,7 +24920,7 @@ struct mg_tcpip_driver mg_tcpip_driver_stm32f = { mg_tcpip_driver_stm32f_init, mg_tcpip_driver_stm32f_tx, NULL, - mg_tcpip_driver_stm32f_up}; + mg_tcpip_driver_stm32f_poll}; #endif #ifdef MG_ENABLE_LINES @@ -16523,9 +24928,12 @@ #endif -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \ - MG_ENABLE_DRIVER_STM32H -struct stm32h_eth { +#if MG_ENABLE_TCPIP && (MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || \ + MG_ENABLE_DRIVER_STM32N) +// STM32H: vendor modded single-queue Synopsys v4.2 +// STM32N, MCXNx4x: dual-queue Synopsys v5.2 with no hash table option +// RT1170 ENET_QOS: quad-queue Synopsys v5.1 +struct synopsys_enet_qos { volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R, RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR, MACIVIR, RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7], MACISR, @@ -16556,18 +24964,25 @@ DMACMFCR; }; #undef ETH -#define ETH \ - ((struct stm32h_eth *) (uintptr_t) (0x40000000UL + 0x00020000UL + 0x8000UL)) +#if MG_ENABLE_DRIVER_STM32H +#define ETH \ + ((struct synopsys_enet_qos *) (uintptr_t) (0x40000000UL + 0x00020000UL + \ + 0x8000UL)) +#elif MG_ENABLE_DRIVER_MCXN +#define ETH ((struct synopsys_enet_qos *) (uintptr_t) 0x40100000UL) +#elif MG_ENABLE_DRIVER_STM32N +#define ETH ((struct synopsys_enet_qos *) (uintptr_t) 0x48036000UL) +#endif #define ETH_PKT_SIZE 1540 // Max frame size #define ETH_DESC_CNT 4 // Descriptors count #define ETH_DS 4 // Descriptor size (words) -static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors -static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // RX ethernet buffers -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; // TX ethernet buffers -static struct mg_tcpip_if *s_ifp; // MIP interface +static volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM MG_8BYTE_ALIGNED; +static volatile uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM MG_8BYTE_ALIGNED; +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM MG_8BYTE_ALIGNED; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM MG_8BYTE_ALIGNED; +static struct mg_tcpip_if *s_ifp; // MIP interface static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { ETH->MACMDIOAR &= (0xF << 8); @@ -16585,82 +25000,6 @@ while (ETH->MACMDIOAR & MG_BIT(0)) (void) 0; } -static uint32_t get_hclk(void) { - struct rcc { - volatile uint32_t CR, HSICFGR, CRRCR, CSICFGR, CFGR, RESERVED1, D1CFGR, - D2CFGR, D3CFGR, RESERVED2, PLLCKSELR, PLLCFGR, PLL1DIVR, PLL1FRACR, - PLL2DIVR, PLL2FRACR, PLL3DIVR, PLL3FRACR, RESERVED3, D1CCIPR, D2CCIP1R, - D2CCIP2R, D3CCIPR, RESERVED4, CIER, CIFR, CICR, RESERVED5, BDCR, CSR, - RESERVED6, AHB3RSTR, AHB1RSTR, AHB2RSTR, AHB4RSTR, APB3RSTR, APB1LRSTR, - APB1HRSTR, APB2RSTR, APB4RSTR, GCR, RESERVED8, D3AMR, RESERVED11[9], - RSR, AHB3ENR, AHB1ENR, AHB2ENR, AHB4ENR, APB3ENR, APB1LENR, APB1HENR, - APB2ENR, APB4ENR, RESERVED12, AHB3LPENR, AHB1LPENR, AHB2LPENR, - AHB4LPENR, APB3LPENR, APB1LLPENR, APB1HLPENR, APB2LPENR, APB4LPENR, - RESERVED13[4]; - } *rcc = ((struct rcc *) (0x40000000 + 0x18020000 + 0x4400)); - uint32_t clk = 0, hsi = 64000000 /* 64 MHz */, hse = 8000000 /* 8MHz */, - csi = 4000000 /* 4MHz */; - unsigned int sel = (rcc->CFGR & (7 << 3)) >> 3; - - if (sel == 1) { - clk = csi; - } else if (sel == 2) { - clk = hse; - } else if (sel == 3) { - uint32_t vco, m, n, p; - unsigned int src = (rcc->PLLCKSELR & (3 << 0)) >> 0; - m = ((rcc->PLLCKSELR & (0x3F << 4)) >> 4); - n = ((rcc->PLL1DIVR & (0x1FF << 0)) >> 0) + 1 + - ((rcc->PLLCFGR & MG_BIT(0)) ? 1 : 0); // round-up in fractional mode - p = ((rcc->PLL1DIVR & (0x7F << 9)) >> 9) + 1; - if (src == 1) { - clk = csi; - } else if (src == 2) { - clk = hse; - } else { - clk = hsi; - clk >>= ((rcc->CR & 3) >> 3); - } - vco = (uint32_t) ((uint64_t) clk * n / m); - clk = vco / p; - } else { - clk = hsi; - clk >>= ((rcc->CR & 3) >> 3); - } - const uint8_t cptab[12] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div) - uint32_t d1cpre = (rcc->D1CFGR & (0x0F << 8)) >> 8; - if (d1cpre >= 8) clk >>= cptab[d1cpre - 8]; - MG_DEBUG(("D1 CLK: %u", clk)); - uint32_t hpre = (rcc->D1CFGR & (0x0F << 0)) >> 0; - if (hpre < 8) return clk; - return ((uint32_t) clk) >> cptab[hpre - 8]; -} - -// Guess CR from AHB1 clock. MDC clock is generated from the ETH peripheral -// clock (AHB1); as per 802.3, it must not exceed 2. As the AHB clock can -// be derived from HSI or CSI (internal RC) clocks, and those can go above -// specs, the datasheets specify a range of frequencies and activate one of a -// series of dividers to keep the MDC clock safely below 2.5MHz. We guess a -// divider setting based on HCLK with some drift. If the user uses a different -// clock from our defaults, needs to set the macros on top. Valid for -// STM32H74xxx/75xxx (58.11.4)(4.5% worst case drift)(CSI clock has a 7.5 % -// worst case drift @ max temp) -static int guess_mdc_cr(void) { - const uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMDIOAR::CR values - const uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK dividers - uint32_t hclk = get_hclk(); // Guess system HCLK - int result = -1; // Invalid CR value - for (int i = 0; i < 6; i++) { - if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { - result = crs[i]; - break; - } - } - if (result < 0) MG_ERROR(("HCLK too high")); - MG_DEBUG(("HCLK: %u, CR: %d", hclk, result)); - return result; -} - static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_stm32h_data *d = (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data; @@ -16669,21 +25008,25 @@ uint8_t phy_conf = d == NULL ? MG_PHY_CLOCKS_MAC : d->phy_conf; // Init RX descriptors + memset((char *) s_rxdesc, 0, sizeof(s_rxdesc)); // manual init for (int i = 0; i < ETH_DESC_CNT; i++) { s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data buffer s_rxdesc[i][3] = MG_BIT(31) | MG_BIT(30) | MG_BIT(24); // OWN, IOC, BUF1V } // Init TX descriptors + memset((char *) s_txdesc, 0, sizeof(s_txdesc)); // manual init for (int i = 0; i < ETH_DESC_CNT; i++) { s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer } - ETH->DMAMR |= MG_BIT(0); // Software reset + ETH->DMAMR |= MG_BIT(0); // Software reset + for (int i = 0; i < 4; i++) + (void) 0; // wait at least 4 clocks before reading while ((ETH->DMAMR & MG_BIT(0)) != 0) (void) 0; // Wait until done - // Set MDC clock divider. If user told us the value, use it. Otherwise, guess - int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr; + // Set MDC clock divider. Get user value, else, assume max freq + int cr = (d == NULL || d->mdc_cr < 0) ? 7 : d->mdc_cr; ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8; // NOTE(scaprile): We do not use timing facilities so the DMA engine does not @@ -16692,7 +25035,9 @@ ETH->DMASBMR |= MG_BIT(12); // AAL NOTE(scaprile): is this actually needed ETH->MACIER = 0; // Do not enable additional irq sources (reset value) ETH->MACTFCR = MG_BIT(7); // Disable zero-quanta pause - // ETH->MACPFR = MG_BIT(31); // Receive all +#if MG_ENABLE_DRIVER_STM32H + ETH->MACPFR = MG_BIT(10); // Perfect filtering +#endif struct mg_phy phy = {eth_read_phy, eth_write_phy}; mg_phy_init(&phy, phy_addr, phy_conf); ETH->DMACRDLAR = @@ -16707,13 +25052,23 @@ ETH->DMACTDTPR = (uint32_t) (uintptr_t) s_txdesc; // first available descriptor address ETH->DMACCR = 0; // DSL = 0 (contiguous descriptor table) (reset value) +#if !MG_ENABLE_DRIVER_STM32H + MG_SET_BITS(ETH->DMACTCR, 0x3F << 16, MG_BIT(16)); + MG_SET_BITS(ETH->DMACRCR, 0x3F << 16, MG_BIT(16)); +#endif ETH->DMACIER = MG_BIT(6) | MG_BIT(15); // RIE, NIE ETH->MACCR = MG_BIT(0) | MG_BIT(1) | MG_BIT(13) | MG_BIT(14) | - MG_BIT(15); // RE, TE, Duplex, Fast, Reserved + MG_BIT(15); // RE, TE, Duplex, Fast, Reserved +#if MG_ENABLE_DRIVER_STM32H ETH->MTLTQOMR |= MG_BIT(1); // TSF ETH->MTLRQOMR |= MG_BIT(5); // RSF - ETH->DMACTCR |= MG_BIT(0); // ST - ETH->DMACRCR |= MG_BIT(0); // SR +#else + ETH->MTLTQOMR |= (7 << 16) | MG_BIT(3) | MG_BIT(1); // 2KB Q0, TSF + ETH->MTLRQOMR |= (7 << 20) | MG_BIT(5); // 2KB Q, RSF + MG_SET_BITS(ETH->RESERVED6[3], 3, 2); // Enable RxQ0 (MAC_RXQ_CTRL0) +#endif + ETH->DMACTCR |= MG_BIT(0); // ST + ETH->DMACRCR |= MG_BIT(0); // SR // MAC address filtering ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; @@ -16748,7 +25103,27 @@ (void) ifp; } -static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) { +static void mg_tcpip_driver_stm32h_update_hash_table(struct mg_tcpip_if *ifp) { +#if MG_ENABLE_DRIVER_MCXN || MG_ENABLE_DRIVER_STM32N + ETH->MACPFR = MG_BIT(4); // Pass Multicast (pass all multicast frames) +#else + // TODO(): read database, rebuild hash table + // add mDNS / DNS-SD multicast address + ETH->MACA1LR = (uint32_t) mcast_addr[3] << 24 | + (uint32_t) mcast_addr[2] << 16 | + (uint32_t) mcast_addr[1] << 8 | (uint32_t) mcast_addr[0]; + ETH->MACA1HR = (uint32_t) mcast_addr[5] << 8 | (uint32_t) mcast_addr[4]; + ETH->MACA1HR |= MG_BIT(31); // AE +#endif + (void) ifp; +} + +static bool mg_tcpip_driver_stm32h_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_stm32h_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; struct mg_tcpip_driver_stm32h_data *d = (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data; uint8_t phy_addr = d == NULL ? 0 : d->phy_addr; @@ -16770,9 +25145,17 @@ return up; } -void ETH_IRQHandler(void); static uint32_t s_rxno; +#if MG_ENABLE_DRIVER_MCXN +void ETHERNET_IRQHandler(void); +void ETHERNET_IRQHandler(void) { +#elif MG_ENABLE_DRIVER_STM32H +void ETH_IRQHandler(void); void ETH_IRQHandler(void) { +#else +void ETH1_IRQHandler(void); +void ETH1_IRQHandler(void) { +#endif if (ETH->DMACSR & MG_BIT(6)) { // Frame received, loop ETH->DMACSR = MG_BIT(15) | MG_BIT(6); // Clear flag for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not forever @@ -16798,7 +25181,7 @@ struct mg_tcpip_driver mg_tcpip_driver_stm32h = { mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL, - mg_tcpip_driver_stm32h_up}; + mg_tcpip_driver_stm32h_poll}; #endif #ifdef MG_ENABLE_LINES @@ -16861,7 +25244,8 @@ static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { EMAC->EMACMIIDATA = val; EMAC->EMACMIIADDR &= (0xf << 2); - EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1); + EMAC->EMACMIIADDR |= + ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | MG_BIT(1); EMAC->EMACMIIADDR |= MG_BIT(0); while (EMAC->EMACMIIADDR & MG_BIT(0)) tm4cspin(1); } @@ -16916,11 +25300,11 @@ uint8_t crs[] = {2, 3, 0, 1}; // EMAC->MACMIIAR::CR values uint8_t div[] = {16, 26, 42, 62}; // Respective HCLK dividers uint32_t sysclk = get_sysclk(); // Guess system SYSCLK - int result = -1; // Invalid CR value + int i, result = -1; // Invalid CR value if (sysclk < 25000000) { MG_ERROR(("SYSCLK too low")); } else { - for (int i = 0; i < 4; i++) { + for (i = 0; i < 4; i++) { if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { result = crs[i]; break; @@ -16935,12 +25319,13 @@ static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) { struct mg_tcpip_driver_tm4c_data *d = (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data; + int i; s_ifp = ifp; // Init RX descriptors - for (int i = 0; i < ETH_DESC_CNT; i++) { - s_rxdesc[i][0] = MG_BIT(31); // Own - s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd address chained + for (i = 0; i < ETH_DESC_CNT; i++) { + s_rxdesc[i][0] = MG_BIT(31); // Own + s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | MG_BIT(14); // 2nd address chained s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i]; // Point to data buffer s_rxdesc[i][3] = (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT]; // Chain @@ -16948,14 +25333,15 @@ } // Init TX descriptors - for (int i = 0; i < ETH_DESC_CNT; i++) { + for (i = 0; i < ETH_DESC_CNT; i++) { s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i]; // Buf pointer s_txdesc[i][3] = (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT]; // Chain } - EMAC->EMACDMABUSMOD |= MG_BIT(0); // Software reset - while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) tm4cspin(1); // Wait until done + EMAC->EMACDMABUSMOD |= MG_BIT(0); // Software reset + while ((EMAC->EMACDMABUSMOD & MG_BIT(0)) != 0) + tm4cspin(1); // Wait until done // Set MDC clock divider. If user told us the value, use it. Otherwise, guess int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr; @@ -16963,25 +25349,25 @@ // NOTE(cpq): we do not use extended descriptor bit 7, and do not use // hardware checksum. Therefore, descriptor size is 4, not 8 - // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) | MG_BIT(25); + // EMAC->EMACDMABUSMOD = MG_BIT(13) | MG_BIT(16) | MG_BIT(22) | MG_BIT(23) | + // MG_BIT(25); EMAC->EMACIM = MG_BIT(3) | MG_BIT(9); // Mask timestamp & PMT IT - EMAC->EMACFLOWCTL = MG_BIT(7); // Disable zero-quanta pause - // EMAC->EMACFRAMEFLTR = MG_BIT(31); // Receive all + EMAC->EMACFLOWCTL = MG_BIT(7); // Disable zero-quanta pause + EMAC->EMACFRAMEFLTR = MG_BIT(10); // Perfect filtering // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(15)); // Reset internal PHY (EPHY) emac_write_phy(EPHY_ADDR, EPHYBMCR, MG_BIT(12)); // Set autonegotiation EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc; // RX descriptors EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc; // TX descriptors - EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16); // RIE, NIE - EMAC->EMACCFG = MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE, Duplex, Fast + EMAC->EMACDMAIM = MG_BIT(6) | MG_BIT(16); // RIE, NIE + EMAC->EMACCFG = + MG_BIT(2) | MG_BIT(3) | MG_BIT(11) | MG_BIT(14); // RE, TE, Duplex, Fast EMAC->EMACDMAOPMODE = MG_BIT(1) | MG_BIT(13) | MG_BIT(21) | MG_BIT(25); // SR, ST, TSF, RSF EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4]; EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) | ((uint32_t) ifp->mac[2] << 16) | ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0]; - // NOTE(scaprile) There are 3 additional slots for filtering, disabled by - // default. This also applies to the STM32 driver (at least for F7) return true; } @@ -17006,12 +25392,27 @@ if (++s_txno >= ETH_DESC_CNT) s_txno = 0; } EMAC->EMACDMARIS = MG_BIT(2) | MG_BIT(5); // Clear any prior TU/UNF - EMAC->EMACTXPOLLD = 0; // and resume + EMAC->EMACTXPOLLD = 0; // and resume return len; +} + +static void mg_tcpip_driver_tm4c_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // add mDNS / DNS-SD multicast address + EMAC->EMACADDR1L = (uint32_t) mcast_addr[3] << 24 | + (uint32_t) mcast_addr[2] << 16 | + (uint32_t) mcast_addr[1] << 8 | (uint32_t) mcast_addr[0]; + EMAC->EMACADDR1H = (uint32_t) mcast_addr[5] << 8 | (uint32_t) mcast_addr[4]; + EMAC->EMACADDR1H |= MG_BIT(31); // AE (void) ifp; } -static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) { +static bool mg_tcpip_driver_tm4c_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_tm4c_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR); bool up = (bmsr & MG_BIT(2)) ? 1 : 0; if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up @@ -17019,9 +25420,10 @@ // tmp = reg with flags set to the most likely situation: 100M full-duplex // if(link is slow or half) set flags otherwise // reg = tmp - uint32_t emaccfg = EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11); // 100M, Full-duplex - if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14); // 10M - if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11); // Half-duplex + uint32_t emaccfg = + EMAC->EMACCFG | MG_BIT(14) | MG_BIT(11); // 100M, Full-duplex + if (sts & MG_BIT(1)) emaccfg &= ~MG_BIT(14); // 10M + if ((sts & MG_BIT(2)) == 0) emaccfg &= ~MG_BIT(11); // Half-duplex EMAC->EMACCFG = emaccfg; // IRQ handler does not fiddle with this register MG_DEBUG(("Link is %uM %s-duplex", emaccfg & MG_BIT(14) ? 100 : 10, emaccfg & MG_BIT(11) ? "full" : "half")); @@ -17032,11 +25434,13 @@ void EMAC0_IRQHandler(void); static uint32_t s_rxno; void EMAC0_IRQHandler(void) { - if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop + int i; + if (EMAC->EMACDMARIS & MG_BIT(6)) { // Frame received, loop EMAC->EMACDMARIS = MG_BIT(16) | MG_BIT(6); // Clear flag - for (uint32_t i = 0; i < 10; i++) { // read as they arrive but not forever + for (i = 0; i < 10; i++) { // read as they arrive but not forever if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; // exit when done - if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) == (MG_BIT(8) | MG_BIT(9))) && + if (((s_rxdesc[s_rxno][0] & (MG_BIT(8) | MG_BIT(9))) == + (MG_BIT(8) | MG_BIT(9))) && !(s_rxdesc[s_rxno][0] & MG_BIT(15))) { // skip partial/errored frames uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (MG_BIT(14) - 1)); // printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0], @@ -17048,12 +25452,375 @@ } } EMAC->EMACDMARIS = MG_BIT(7); // Clear possible RU while processing - EMAC->EMACRXPOLLD = 0; // and resume RX + EMAC->EMACRXPOLLD = 0; // and resume RX } struct mg_tcpip_driver mg_tcpip_driver_tm4c = {mg_tcpip_driver_tm4c_init, mg_tcpip_driver_tm4c_tx, NULL, - mg_tcpip_driver_tm4c_up}; + mg_tcpip_driver_tm4c_poll}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/tms570.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TMS570) && MG_ENABLE_DRIVER_TMS570 +struct tms570_emac_ctrl { + volatile uint32_t REVID, SOFTRESET, RESERVED1[1], INTCONTROL, C0RXTHRESHEN, + C0RXEN, C0TXEN, C0MISCEN, RESERVED2[8], + C0RXTHRESHSTAT, C0RXSTAT, C0TXSTAT, C0MISCSTAT, + RESERVED3[8], + C0RXIMAX, C0TXIMAX; +}; +struct tms570_emac { + volatile uint32_t TXREVID, TXCONTROL, TXTEARDOWN, RESERVED1[1], RXREVID, + RXCONTROL, RXTEARDOWN, RESERVED2[25], TXINTSTATRAW,TXINTSTATMASKED, + TXINTMASKSET, TXINTMASKCLEAR, MACINVECTOR, MACEOIVECTOR, RESERVED8[2], RXINTSTATRAW, + RXINTSTATMASKED, RXINTMASKSET, RXINTMASKCLEAR, MACINTSTATRAW, MACINTSTATMASKED, + MACINTMASKSET, MACINTMASKCLEAR, RESERVED3[16], RXMBPENABLE, RXUNICASTSET, + RXUNICASTCLEAR, RXMAXLEN, RXBUFFEROFFSET, RXFILTERLOWTHRESH, RESERVED9[2], RXFLOWTHRESH[8], + RXFREEBUFFER[8], MACCONTROL, MACSTATUS, EMCONTROL, FIFOCONTROL, MACCONFIG, + SOFTRESET, RESERVED4[22], MACSRCADDRLO, MACSRCADDRHI, MACHASH1, MACHASH2, + BOFFTEST, TPACETEST, RXPAUSE, TXPAUSE, RESERVED5[4], RXGOODFRAMES, RXBCASTFRAMES, + RXMCASTFRAMES, RXPAUSEFRAMES, RXCRCERRORS, RXALIGNCODEERRORS, RXOVERSIZED, + RXJABBER, RXUNDERSIZED, RXFRAGMENTS, RXFILTERED, RXQOSFILTERED, RXOCTETS, + TXGOODFRAMES, TXBCASTFRAMES, TXMCASTFRAMES, TXPAUSEFRAMES, TXDEFERRED, + TXCOLLISION, TXSINGLECOLL, TXMULTICOLL, TXEXCESSIVECOLL, TXLATECOLL, + TXUNDERRUN, TXCARRIERSENSE, TXOCTETS, FRAME64, FRAME65T127, FRAME128T255, + FRAME256T511, FRAME512T1023, FRAME1024TUP, NETOCTETS, RXSOFOVERRUNS, + RXMOFOVERRUNS, RXDMAOVERRUNS, RESERVED6[156], MACADDRLO, MACADDRHI, + MACINDEX, RESERVED7[61], TXHDP[8], RXHDP[8], TXCP[8], RXCP[8]; +}; +struct tms570_mdio { + volatile uint32_t REVID, CONTROL, ALIVE, LINK, LINKINTRAW, LINKINTMASKED, + RESERVED1[2], USERINTRAW, USERINTMASKED, USERINTMASKSET, USERINTMASKCLEAR, + RESERVED2[20], USERACCESS0, USERPHYSEL0, USERACCESS1, USERPHYSEL1; +}; +#define SWAP32(x) ( (((x) & 0x000000FF) << 24) | \ + (((x) & 0x0000FF00) << 8) | \ + (((x) & 0x00FF0000) >> 8) | \ + (((x) & 0xFF000000) >> 24) ) +#undef EMAC +#undef EMAC_CTRL +#undef MDIO +#define EMAC ((struct tms570_emac *) (uintptr_t) 0xFCF78000) +#define EMAC_CTRL ((struct tms570_emac_ctrl *) (uintptr_t) 0xFCF78800) +#define MDIO ((struct tms570_mdio *) (uintptr_t) 0xFCF78900) +#define ETH_PKT_SIZE 1540 // Max frame size +#define ETH_DESC_CNT 4 // Descriptors count +#define ETH_DS 4 // Descriptor size (words) +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS] + __attribute__((section(".ETH_CPPI"), aligned(4))); // TX descriptors +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS] + __attribute__((section(".ETH_CPPI"), aligned(4))); // RX descriptors +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] + __attribute__((aligned(4))); // RX ethernet buffers +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] + __attribute__((aligned(4))); // TX ethernet buffers +static struct mg_tcpip_if *s_ifp; // MIP interface +static uint16_t emac_read_phy(uint8_t addr, uint8_t reg) { + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; + MDIO->USERACCESS0 = MG_BIT(31) | ((reg & 0x1f) << 21) | + ((addr & 0x1f) << 16); + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; + return MDIO->USERACCESS0 & 0xffff; +} +static void emac_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; + MDIO->USERACCESS0 = MG_BIT(31) | MG_BIT(30) | ((reg & 0x1f) << 21) | + ((addr & 0x1f) << 16) | (val & 0xffff); + while(MDIO->USERACCESS0 & MG_BIT(31)) (void) 0; +} +static bool mg_tcpip_driver_tms570_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_driver_tms570_data *d = + (struct mg_tcpip_driver_tms570_data *) ifp->driver_data; + s_ifp = ifp; + EMAC_CTRL->SOFTRESET = MG_BIT(0); // Reset the EMAC Control Module + while(EMAC_CTRL->SOFTRESET & MG_BIT(0)) (void) 0; // wait + EMAC->SOFTRESET = MG_BIT(0); // Reset the EMAC Module + while(EMAC->SOFTRESET & MG_BIT(0)) (void) 0; + EMAC->MACCONTROL = 0; + EMAC->RXCONTROL = 0; + EMAC->TXCONTROL = 0; + // Initialize all the header descriptor pointer registers + uint32_t i; + for(i = 0; i < ETH_DESC_CNT; i++) { + EMAC->RXHDP[i] = 0; + EMAC->TXHDP[i] = 0; + EMAC->RXCP[i] = 0; + EMAC->TXCP[i] = 0; + ///EMAC->RXFREEBUFFER[i] = 0xff; + } + // Clear the interrupt enable for all the channels + EMAC->TXINTMASKCLEAR = 0xff; + EMAC->RXINTMASKCLEAR = 0xff; + EMAC->MACHASH1 = 0; + EMAC->MACHASH2 = 0; + EMAC->RXBUFFEROFFSET = 0; + EMAC->RXUNICASTCLEAR = 0xff; + EMAC->RXUNICASTSET = 0; + EMAC->RXMBPENABLE = 0; + // init MDIO + // MDIO_CLK frequency = VCLK3/(CLKDIV + 1). (MDIO must be between 1.0 - 2.5Mhz) + uint32_t clkdiv = 75; // VCLK is configured to 75Mhz + // CLKDIV, ENABLE, PREAMBLE, FAULTENB + MDIO->CONTROL = (clkdiv - 1) | MG_BIT(30) | MG_BIT(20) | MG_BIT(18); + volatile int delay = 0xfff; + while (delay-- != 0) (void) 0; + struct mg_phy phy = {emac_read_phy, emac_write_phy}; + mg_phy_init(&phy, d->phy_addr, MG_PHY_CLOCKS_MAC); + uint32_t channel; + for (channel = 0; channel < 8; channel++) { + EMAC->MACINDEX = channel; + EMAC->MACADDRHI = ifp->mac[0] | (ifp->mac[1] << 8) | (ifp->mac[2] << 16) | + (ifp->mac[3] << 24); + EMAC->MACADDRLO = ifp->mac[4] | (ifp->mac[5] << 8) | MG_BIT(20) | + MG_BIT(19) | (channel << 16); + } + EMAC->RXUNICASTSET = 1; // accept unicast frames; + + EMAC->RXMBPENABLE |= MG_BIT(30) | MG_BIT(13); // CRC, broadcast + + // Initialize the descriptors + for (i = 0; i < ETH_DESC_CNT; i++) { + if (i < ETH_DESC_CNT - 1) { + s_txdesc[i][0] = 0; + s_rxdesc[i][0] = SWAP32(((uint32_t) &s_rxdesc[i + 1][0])); + } + s_txdesc[i][1] = SWAP32(((uint32_t) s_txbuf[i])); + s_rxdesc[i][1] = SWAP32(((uint32_t) s_rxbuf[i])); + s_txdesc[i][2] = 0; + s_rxdesc[i][2] = SWAP32(ETH_PKT_SIZE); + s_txdesc[i][3] = 0; + s_rxdesc[i][3] = SWAP32(MG_BIT(29)); // OWN + } + s_txdesc[ETH_DESC_CNT - 1][0] = 0; + s_rxdesc[ETH_DESC_CNT - 1][0] = 0; + + EMAC->MACCONTROL = MG_BIT(5) | MG_BIT(0); // Enable MII, Full-duplex + //EMAC->TXINTMASKSET = 1; // Enable TX interrupt + EMAC->RXINTMASKSET = 1; // Enable RX interrupt + //EMAC_CTRL->C0TXEN = 1; // TX completion interrupt + EMAC_CTRL->C0RXEN = 1; // RX completion interrupt + EMAC->TXCONTROL = 1; // TXEN + EMAC->RXCONTROL = 1; // RXEN + EMAC->RXHDP[0] = (uint32_t) &s_rxdesc[0][0]; + return true; +} +static uint32_t s_txno; +static size_t mg_tcpip_driver_tms570_tx(const void *buf, size_t len, + struct mg_tcpip_if *ifp) { + if (len > sizeof(s_txbuf[s_txno])) { + MG_ERROR(("Frame too big, %ld", (long) len)); + len = 0; // fail + } else if ((s_txdesc[s_txno][3] & SWAP32(MG_BIT(29)))) { + ifp->nerr++; + MG_ERROR(("No descriptors available")); + len = 0; // fail + } else { + memcpy(s_txbuf[s_txno], buf, len); // Copy data + if (len < 128) len = 128; + s_txdesc[s_txno][2] = SWAP32((uint32_t) len); // Set data len + s_txdesc[s_txno][3] = + SWAP32(MG_BIT(31) | MG_BIT(30) | MG_BIT(29) | len); // SOP, EOP, OWN, length + + while(EMAC->TXHDP[0] != 0) (void) 0; + EMAC->TXHDP[0] = (uint32_t) &s_txdesc[s_txno][0]; + if(++s_txno == ETH_DESC_CNT) { + s_txno = 0; + } + } + return len; + (void) ifp; +} + +static void mg_tcpip_driver_tms570_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // Setting Hash Index for 01:00:5e:00:00:fb (multicast) + // using TMS570 XOR method (32.5.37). + // computed hash is 55, which means bit 23 (55 - 32) in + // HASH2 register must be set + EMAC->MACHASH2 = MG_BIT(23); + EMAC->RXMBPENABLE = MG_BIT(5); // enable hash filtering + (void) ifp; +} + +static bool mg_tcpip_driver_tms570_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_tms570_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; + struct mg_tcpip_driver_tms570_data *d = + (struct mg_tcpip_driver_tms570_data *) ifp->driver_data; + uint8_t speed = MG_PHY_SPEED_10M; + bool up = false, full_duplex = false; + struct mg_phy phy = {emac_read_phy, emac_write_phy}; + if (!s1) return false; + up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed); + if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { + // link state just went up + MG_DEBUG(("Link is %uM %s-duplex", speed == MG_PHY_SPEED_10M ? 10 : 100, + full_duplex ? "full" : "half")); + } + return up; +} + +#pragma CODE_STATE(EMAC_TX_IRQHandler, 32) +#pragma INTERRUPT(EMAC_TX_IRQHandler, IRQ) +void EMAC_TX_IRQHandler(void) { + uint32_t status = EMAC_CTRL->C0TXSTAT; + if (status & 1) { // interrupt caused on channel 0 + while(s_txdesc[s_txno][3] & SWAP32(MG_BIT(29))) (void) 0; + EMAC->TXCP[0] = (uint32_t) &s_txdesc[s_txno][0]; + } + //Write the DMA end of interrupt vector + EMAC->MACEOIVECTOR = 2; +} +static uint32_t s_rxno; +#pragma CODE_STATE(EMAC_RX_IRQHandler, 32) +#pragma INTERRUPT(EMAC_RX_IRQHandler, IRQ) +void EMAC_RX_IRQHandler(void) { + uint32_t status = EMAC_CTRL->C0RXSTAT; + if (status & 1) { // Frame received, loop + uint32_t i; + //MG_INFO(("RX interrupt")); + for (i = 0; i < 10; i++) { // read as they arrive but not forever + if (s_rxdesc[s_rxno][3] & SWAP32(MG_BIT(29))) break; + uint32_t len = SWAP32(s_rxdesc[s_rxno][3]) & 0xffff; + //MG_INFO(("recv len: %d", len)); + //mg_hexdump(s_rxbuf[s_rxno], len); + mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp); + uint32_t flags = s_rxdesc[s_rxno][3]; + s_rxdesc[s_rxno][3] = SWAP32(MG_BIT(29)); + s_rxdesc[s_rxno][2] = SWAP32(ETH_PKT_SIZE); + EMAC->RXCP[0] = (uint32_t) &s_rxdesc[s_rxno][0]; + if (flags & SWAP32(MG_BIT(28))) { + //MG_INFO(("EOQ detected")); + EMAC->RXHDP[0] = (uint32_t) &s_rxdesc[0][0]; + } + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; + } + } + //Write the DMA end of interrupt vector + EMAC->MACEOIVECTOR = 1; +} +struct mg_tcpip_driver mg_tcpip_driver_tms570 = {mg_tcpip_driver_tms570_init, + mg_tcpip_driver_tms570_tx, NULL, + mg_tcpip_driver_tms570_poll}; +#endif + + +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/w5100.c" +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5100) && MG_ENABLE_DRIVER_W5100 + +static void w5100_txn(struct mg_tcpip_spi *s, uint16_t addr, bool wr, void *buf, + size_t len) { + size_t i; + uint8_t *p = (uint8_t *) buf; + uint8_t control = wr ? 0xF0 : 0x0F; + uint8_t cmd[] = {control, (uint8_t) (addr >> 8), (uint8_t) (addr & 255)}; + s->begin(s->spi); + for (i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]); + for (i = 0; i < len; i++) { + uint8_t r = s->txn(s->spi, p[i]); + if (!wr) p[i] = r; + } + s->end(s->spi); +} + +// clang-format off +static void w5100_wn(struct mg_tcpip_spi *s, uint16_t addr, void *buf, size_t len) { w5100_txn(s, addr, true, buf, len); } +static void w5100_w1(struct mg_tcpip_spi *s, uint16_t addr, uint8_t val) { w5100_wn(s, addr, &val, 1); } +static void w5100_w2(struct mg_tcpip_spi *s, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5100_wn(s, addr, buf, sizeof(buf)); } +static void w5100_rn(struct mg_tcpip_spi *s, uint16_t addr, void *buf, size_t len) { w5100_txn(s, addr, false, buf, len); } +static uint8_t w5100_r1(struct mg_tcpip_spi *s, uint16_t addr) { uint8_t r = 0; w5100_rn(s, addr, &r, 1); return r; } +static uint16_t w5100_r2(struct mg_tcpip_spi *s, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5100_rn(s, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); } +// clang-format on + +static size_t w5100_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2; // Read recv len + while ((n2 = w5100_r2(s, 0x426)) > n) n = n2; // Until it is stable + if (n > 0) { + uint16_t ptr = w5100_r2(s, 0x428); // Get read pointer + if (n <= len + 2 && n > 1) { + r = (uint16_t) (n - 2); + } + uint16_t rxbuf_size = (1 << (w5100_r1(s, 0x1a) & 3)) * 1024; + uint16_t rxbuf_addr = 0x6000; + uint16_t ptr_ofs = (ptr + 2) & (rxbuf_size - 1); + if (ptr_ofs + r < rxbuf_size) { + w5100_rn(s, rxbuf_addr + ptr_ofs, buf, r); + } else { + uint16_t remaining_len = rxbuf_size - ptr_ofs; + w5100_rn(s, rxbuf_addr + ptr_ofs, buf, remaining_len); + w5100_rn(s, rxbuf_addr, buf + remaining_len, n - remaining_len); + } + w5100_w2(s, 0x428, (uint16_t) (ptr + n)); + w5100_w1(s, 0x401, 0x40); // Sock0 CR -> RECV + } + return r; +} + +static size_t w5100_tx(const void *buf, size_t buflen, + struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + uint16_t i, n = 0, ptr = 0, len = (uint16_t) buflen; + while (n < len) n = w5100_r2(s, 0x420); // Wait for space + ptr = w5100_r2(s, 0x424); // Get write pointer + uint16_t txbuf_size = (1 << (w5100_r1(s, 0x1b) & 3)) * 1024; + uint16_t ptr_ofs = ptr & (txbuf_size - 1); + uint16_t txbuf_addr = 0x4000; + if (ptr_ofs + len > txbuf_size) { + uint16_t size = txbuf_size - ptr_ofs; + w5100_wn(s, txbuf_addr + ptr_ofs, (char *) buf, size); + w5100_wn(s, txbuf_addr, (char *) buf + size, len - size); + } else { + w5100_wn(s, txbuf_addr + ptr_ofs, (char *) buf, len); + } + w5100_w2(s, 0x424, (uint16_t) (ptr + len)); // Advance write pointer + w5100_w1(s, 0x401, 0x20); // Sock0 CR -> SEND + for (i = 0; i < 40; i++) { + uint8_t ir = w5100_r1(s, 0x402); // Read S0 IR + if (ir == 0) continue; + // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr); + w5100_w1(s, 0x402, ir); // Write S0 IR: clear it! + if (ir & 8) len = 0; // Timeout. Report error + if (ir & (16 | 8)) break; // Stop on SEND_OK or timeout + } + return len; +} + +static bool w5100_init(struct mg_tcpip_if *ifp) { + struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data; + s->end(s->spi); + w5100_w1(s, 0, 0x80); // Reset chip: CR -> 0x80 + w5100_w1(s, 0x72, 0x53); // CR PHYLCKR -> unlock PHY + w5100_w1(s, 0x46, 0); // CR PHYCR0 -> autonegotiation + w5100_w1(s, 0x47, 0); // CR PHYCR1 -> reset + w5100_w1(s, 0x72, 0x00); // CR PHYLCKR -> lock PHY + w5100_wn(s, 0x09, ifp->mac, 6); // SHAR + w5100_w1(s, 0x1a, 6); // Sock0 RX buf size - 4KB + w5100_w1(s, 0x1b, 6); // Sock0 TX buf size - 4KB + w5100_w1(s, 0x400, 0x44); // Sock0 MR -> MACRAW, MAC filter + w5100_w1(s, 0x401, 1); // Sock0 CR -> OPEN + return w5100_r1(s, 0x403) == 0x42; // Sock0 SR == MACRAW +} + +static bool w5100_poll(struct mg_tcpip_if *ifp, bool s1) { + struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data; + return s1 ? w5100_r1(spi, 0x3c /* PHYSR */) & 1 + : false; // Bit 0 of PHYSR is LNK (0 - down, 1 - up) +} + +struct mg_tcpip_driver mg_tcpip_driver_w5100 = {w5100_init, w5100_tx, w5100_rx, + w5100_poll}; #endif #ifdef MG_ENABLE_LINES @@ -17142,14 +25909,14 @@ return w5500_r1(s, W5500_S0, 3) == 0x42; // Sock0 SR == MACRAW } -static bool w5500_up(struct mg_tcpip_if *ifp) { +static bool w5500_poll(struct mg_tcpip_if *ifp, bool s1) { struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data; - uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e); - return phycfgr & 1; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up) + return s1 ? w5500_r1(spi, W5500_CR, 0x2e /* PHYCFGR */) & 1 + : false; // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up) } struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx, - w5500_up}; + w5500_poll}; #endif #ifdef MG_ENABLE_LINES @@ -17161,60 +25928,60 @@ struct ETH_GLOBAL_TypeDef { volatile uint32_t MAC_CONFIGURATION, MAC_FRAME_FILTER, HASH_TABLE_HIGH, - HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL, VLAN_TAG, VERSION, - DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS, RESERVED[2], - INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH, MAC_ADDRESS0_LOW, - MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH, MAC_ADDRESS2_LOW, - MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL, - MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT, MMC_RECEIVE_INTERRUPT_MASK, - MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2, - RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK, - RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5, RX_STATISTICS_2[30], - RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT, - SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS, - SYSTEM_TIME_SECONDS_UPDATE, SYSTEM_TIME_NANOSECONDS_UPDATE, - TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, TARGET_TIME_NANOSECONDS, - SYSTEM_TIME_HIGHER_WORD_SECONDS, TIMESTAMP_STATUS, - PPS_CONTROL, RESERVED8[564], BUS_MODE, TRANSMIT_POLL_DEMAND, - RECEIVE_POLL_DEMAND, RECEIVE_DESCRIPTOR_LIST_ADDRESS, - TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, OPERATION_MODE, - INTERRUPT_ENABLE, MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER, - RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS, - RESERVED10[6], CURRENT_HOST_TRANSMIT_DESCRIPTOR, - CURRENT_HOST_RECEIVE_DESCRIPTOR, CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS, - CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, HW_FEATURE; + HASH_TABLE_LOW, GMII_ADDRESS, GMII_DATA, FLOW_CONTROL, VLAN_TAG, VERSION, + DEBUG, REMOTE_WAKE_UP_FRAME_FILTER, PMT_CONTROL_STATUS, RESERVED[2], + INTERRUPT_STATUS, INTERRUPT_MASK, MAC_ADDRESS0_HIGH, MAC_ADDRESS0_LOW, + MAC_ADDRESS1_HIGH, MAC_ADDRESS1_LOW, MAC_ADDRESS2_HIGH, MAC_ADDRESS2_LOW, + MAC_ADDRESS3_HIGH, MAC_ADDRESS3_LOW, RESERVED1[40], MMC_CONTROL, + MMC_RECEIVE_INTERRUPT, MMC_TRANSMIT_INTERRUPT, MMC_RECEIVE_INTERRUPT_MASK, + MMC_TRANSMIT_INTERRUPT_MASK, TX_STATISTICS[26], RESERVED2, + RX_STATISTICS_1[26], RESERVED3[6], MMC_IPC_RECEIVE_INTERRUPT_MASK, + RESERVED4, MMC_IPC_RECEIVE_INTERRUPT, RESERVED5, RX_STATISTICS_2[30], + RESERVED7[286], TIMESTAMP_CONTROL, SUB_SECOND_INCREMENT, + SYSTEM_TIME_SECONDS, SYSTEM_TIME_NANOSECONDS, SYSTEM_TIME_SECONDS_UPDATE, + SYSTEM_TIME_NANOSECONDS_UPDATE, TIMESTAMP_ADDEND, TARGET_TIME_SECONDS, + TARGET_TIME_NANOSECONDS, SYSTEM_TIME_HIGHER_WORD_SECONDS, + TIMESTAMP_STATUS, PPS_CONTROL, RESERVED8[564], BUS_MODE, + TRANSMIT_POLL_DEMAND, RECEIVE_POLL_DEMAND, + RECEIVE_DESCRIPTOR_LIST_ADDRESS, TRANSMIT_DESCRIPTOR_LIST_ADDRESS, STATUS, + OPERATION_MODE, INTERRUPT_ENABLE, + MISSED_FRAME_AND_BUFFER_OVERFLOW_COUNTER, + RECEIVE_INTERRUPT_WATCHDOG_TIMER, RESERVED9, AHB_STATUS, RESERVED10[6], + CURRENT_HOST_TRANSMIT_DESCRIPTOR, CURRENT_HOST_RECEIVE_DESCRIPTOR, + CURRENT_HOST_TRANSMIT_BUFFER_ADDRESS, CURRENT_HOST_RECEIVE_BUFFER_ADDRESS, + HW_FEATURE; }; #undef ETH0 -#define ETH0 ((struct ETH_GLOBAL_TypeDef*) 0x5000C000UL) +#define ETH0 ((struct ETH_GLOBAL_TypeDef *) 0x5000C000UL) -#define ETH_PKT_SIZE 1536 // Max frame size +#define ETH_PKT_SIZE 1536 // Max frame size #define ETH_DESC_CNT 4 // Descriptors count #define ETH_DS 4 // Descriptor size (words) -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors -static uint8_t s_txno; // Current TX descriptor -static uint8_t s_rxno; // Current RX descriptor +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; +static uint32_t s_rxdesc[ETH_DESC_CNT] + [ETH_DS] MG_ETH_RAM; // RX descriptors +static uint32_t s_txdesc[ETH_DESC_CNT] + [ETH_DS] MG_ETH_RAM; // TX descriptors +static uint8_t s_txno; // Current TX descriptor +static uint8_t s_rxno; // Current RX descriptor static struct mg_tcpip_if *s_ifp; // MIP interface enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 }; static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) { - ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) | - ((uint32_t)addr << 11) | - ((uint32_t)reg << 6) | 1; + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) | ((uint32_t) addr << 11) | + ((uint32_t) reg << 6) | 1; while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0; - return (uint16_t)(ETH0->GMII_DATA & 0xffff); + return (uint16_t) (ETH0->GMII_DATA & 0xffff); } static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) { - ETH0->GMII_DATA = val; - ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) | - ((uint32_t)addr << 11) | - ((uint32_t)reg << 6) | 3; + ETH0->GMII_DATA = val; + ETH0->GMII_ADDRESS = (ETH0->GMII_ADDRESS & 0x3c) | ((uint32_t) addr << 11) | + ((uint32_t) reg << 6) | 3; while ((ETH0->GMII_ADDRESS & 1) != 0) (void) 0; } @@ -17249,22 +26016,22 @@ // set the MAC address ETH0->MAC_ADDRESS0_HIGH = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]); - ETH0->MAC_ADDRESS0_LOW = - MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]); + ETH0->MAC_ADDRESS0_LOW = + MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]); // Configure the receive filter - ETH0->MAC_FRAME_FILTER = MG_BIT(10) | MG_BIT(2); // HFP, HMC + ETH0->MAC_FRAME_FILTER = MG_BIT(10); // Perfect filter // Disable flow control ETH0->FLOW_CONTROL = 0; // Enable store and forward mode - ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF + ETH0->OPERATION_MODE = MG_BIT(25) | MG_BIT(21); // RSF, TSF // Configure DMA bus mode (AAL, USP, RPBL, PBL) - ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) | (32 << 8); + ETH0->BUS_MODE = MG_BIT(25) | MG_BIT(23) | (32 << 17) | (32 << 8); // init RX descriptors for (int i = 0; i < ETH_DESC_CNT; i++) { - s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor + s_rxdesc[i][0] = MG_BIT(31); // OWN descriptor s_rxdesc[i][1] = MG_BIT(14) | ETH_PKT_SIZE; s_rxdesc[i][2] = (uint32_t) s_rxbuf[i]; if (i == ETH_DESC_CNT - 1) { @@ -17294,9 +26061,9 @@ ETH0->MMC_TRANSMIT_INTERRUPT_MASK = 0xFFFFFFFF; ETH0->MMC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF; ETH0->MMC_IPC_RECEIVE_INTERRUPT_MASK = 0xFFFFFFFF; - ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM + ETH0->INTERRUPT_MASK = MG_BIT(9) | MG_BIT(3); // TSIM, PMTIM - //Enable interrupts (NIE, RIE, TIE) + // Enable interrupts (NIE, RIE, TIE) ETH0->INTERRUPT_ENABLE = MG_BIT(16) | MG_BIT(6) | MG_BIT(0); // Enable MAC transmission and reception (TE, RE) @@ -17307,7 +26074,7 @@ } static size_t mg_tcpip_driver_xmc_tx(const void *buf, size_t len, - struct mg_tcpip_if *ifp) { + struct mg_tcpip_if *ifp) { if (len > sizeof(s_txbuf[s_txno])) { MG_ERROR(("Frame too big, %ld", (long) len)); len = 0; // Frame is too big @@ -17325,12 +26092,27 @@ } // Resume processing - ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable + ETH0->STATUS = MG_BIT(2); // clear Transmit unavailable ETH0->TRANSMIT_POLL_DEMAND = 0; return len; } -static bool mg_tcpip_driver_xmc_up(struct mg_tcpip_if *ifp) { +static void mg_tcpip_driver_xmc_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // set the multicast address filter + ETH0->MAC_ADDRESS1_HIGH = + MG_U32(0, 0, mcast_addr[5], mcast_addr[4]) | MG_BIT(31); + ETH0->MAC_ADDRESS1_LOW = + MG_U32(mcast_addr[3], mcast_addr[2], mcast_addr[1], mcast_addr[0]); + (void) ifp; +} + +static bool mg_tcpip_driver_xmc_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_xmc_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; struct mg_tcpip_driver_xmc_data *d = (struct mg_tcpip_driver_xmc_data *) ifp->driver_data; uint8_t speed = MG_PHY_SPEED_10M; @@ -17344,22 +26126,21 @@ return up; } -void ETH0_IRQHandler(void); -void ETH0_IRQHandler(void) { +void ETH0_0_IRQHandler(void); +void ETH0_0_IRQHandler(void) { uint32_t irq_status = ETH0->STATUS; // check if a frame was received if (irq_status & MG_BIT(6)) { - for (uint8_t i = 0; i < ETH_DESC_CNT; i++) { - if ((s_rxdesc[s_rxno][0] & MG_BIT(31)) == 0) { - size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16; - mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); - s_rxdesc[s_rxno][0] = MG_BIT(31); // OWN bit: handle control to DMA - // Resume processing - ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI - ETH0->RECEIVE_POLL_DEMAND = 0; - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; - } + for (uint8_t i = 0; i < 10; i++) { // read as they arrive, but not forever + if (s_rxdesc[s_rxno][0] & MG_BIT(31)) break; + size_t len = (s_rxdesc[s_rxno][0] & 0x3fff0000) >> 16; + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); + s_rxdesc[s_rxno][0] = MG_BIT(31); // OWN bit: handle control to DMA + // Resume processing + ETH0->STATUS = MG_BIT(7) | MG_BIT(6); // clear RU and RI + ETH0->RECEIVE_POLL_DEMAND = 0; + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; } ETH0->STATUS = MG_BIT(6); } @@ -17375,9 +26156,9 @@ } } -struct mg_tcpip_driver mg_tcpip_driver_xmc = { - mg_tcpip_driver_xmc_init, mg_tcpip_driver_xmc_tx, NULL, - mg_tcpip_driver_xmc_up}; +struct mg_tcpip_driver mg_tcpip_driver_xmc = {mg_tcpip_driver_xmc_init, + mg_tcpip_driver_xmc_tx, NULL, + mg_tcpip_driver_xmc_poll}; #endif #ifdef MG_ENABLE_LINES @@ -17444,12 +26225,12 @@ #define ETH_DESC_CNT 4 // Descriptors count #define ETH_DS 2 // Descriptor size (words) -static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; -static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE]; -static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS]; // RX descriptors -static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS]; // TX descriptors -static uint8_t s_txno; // Current TX descriptor -static uint8_t s_rxno; // Current RX descriptor +static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; +static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM; +static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM MG_8BYTE_ALIGNED; +static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS] MG_ETH_RAM MG_8BYTE_ALIGNED; +static uint8_t s_txno; // Current TX descriptor +static uint8_t s_rxno; // Current RX descriptor static struct mg_tcpip_if *s_ifp; // MIP interface enum { MG_PHY_ADDR = 0, MG_PHYREG_BCR = 0, MG_PHYREG_BSR = 1 }; @@ -17480,14 +26261,13 @@ s_ifp = ifp; // enable controller, set RGMII mode - ETH0->CTL = MG_BIT(31) | 2; + ETH0->CTL = MG_BIT(31) | (4 << 8) | 2; uint32_t cr = get_clock_rate(d); // set NSP change, ignore RX FCS, data bus width, clock rate // frame length 1536, full duplex, speed ETH0->NETWORK_CONFIG = MG_BIT(29) | MG_BIT(26) | MG_BIT(21) | - ((cr & 7) << 18) | MG_BIT(8) | MG_BIT(4) | - MG_BIT(1) | MG_BIT(0); + ((cr & 7) << 18) | MG_BIT(8) | MG_BIT(1) | MG_BIT(0); // config DMA settings: Force TX burst, Discard on Error, set RX buffer size // to 1536, TX_PBUF_SIZE, RX_PBUF_SIZE, AMBA_BURST_LENGTH @@ -17516,8 +26296,8 @@ ETH0->RECEIVE_Q2_PTR = 1; ETH0->RECEIVE_Q1_PTR = 1; - // enable interrupts (TX and RX complete) - ETH0->INT_ENABLE = MG_BIT(7) | MG_BIT(1); + // enable interrupts (RX complete) + ETH0->INT_ENABLE = MG_BIT(1); // set MAC address ETH0->SPEC_ADD1_BOTTOM = @@ -17563,7 +26343,21 @@ return len; } -static bool mg_tcpip_driver_xmc7_up(struct mg_tcpip_if *ifp) { +static void mg_tcpip_driver_xmc7_update_hash_table(struct mg_tcpip_if *ifp) { + // TODO(): read database, rebuild hash table + // set multicast MAC address + ETH0->SPEC_ADD2_BOTTOM = mcast_addr[3] << 24 | mcast_addr[2] << 16 | + mcast_addr[1] << 8 | mcast_addr[0]; + ETH0->SPEC_ADD2_TOP = mcast_addr[5] << 8 | mcast_addr[4]; + (void) ifp; +} + +static bool mg_tcpip_driver_xmc7_poll(struct mg_tcpip_if *ifp, bool s1) { + if (ifp->update_mac_hash_table) { + mg_tcpip_driver_xmc7_update_hash_table(ifp); + ifp->update_mac_hash_table = false; + } + if (!s1) return false; struct mg_tcpip_driver_xmc7_data *d = (struct mg_tcpip_driver_xmc7_data *) ifp->driver_data; uint8_t speed = MG_PHY_SPEED_10M; @@ -17571,29 +26365,42 @@ struct mg_phy phy = {eth_read_phy, eth_write_phy}; up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed); if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) { // link state just went up + // tmp = reg with flags set to the most likely situation: 100M full-duplex + // if(link is slow or half) set flags otherwise + // reg = tmp + uint32_t netconf = ETH0->NETWORK_CONFIG; + MG_SET_BITS(netconf, MG_BIT(10), + MG_BIT(1) | MG_BIT(0)); // 100M, Full-duplex + uint32_t ctl = ETH0->CTL; + MG_SET_BITS(ctl, 0xFF00, 4 << 8); // /5 for 25M clock if (speed == MG_PHY_SPEED_1000M) { - ETH0->NETWORK_CONFIG |= MG_BIT(10); - } + netconf |= MG_BIT(10); // 1000M + MG_SET_BITS(ctl, 0xFF00, 0); // /1 for 125M clock TODO() IS THIS NEEDED ? + } else if (speed == MG_PHY_SPEED_10M) { + netconf &= ~MG_BIT(0); // 10M + MG_SET_BITS(ctl, 0xFF00, 49); // /50 for 2.5M clock + } + if (full_duplex == false) netconf &= ~MG_BIT(1); // Half-duplex + ETH0->NETWORK_CONFIG = netconf; // IRQ handler does not fiddle with these + ETH0->CTL = ctl; MG_DEBUG(("Link is %uM %s-duplex", - speed == MG_PHY_SPEED_10M ? 10 : - (speed == MG_PHY_SPEED_100M ? 100 : 1000), + speed == MG_PHY_SPEED_10M + ? 10 + : (speed == MG_PHY_SPEED_100M ? 100 : 1000), full_duplex ? "full" : "half")); } - (void) d; return up; } void ETH_IRQHandler(void) { uint32_t irq_status = ETH0->INT_STATUS; if (irq_status & MG_BIT(1)) { - for (uint8_t i = 0; i < ETH_DESC_CNT; i++) { - if (s_rxdesc[s_rxno][0] & MG_BIT(0)) { - size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1); - //MG_INFO(("Receive complete: %ld bytes", len)); - mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); - s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // OWN bit: handle control to DMA - if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; - } + for (uint8_t i = 0; i < 10; i++) { // read as they arrive, but not forever + if ((s_rxdesc[s_rxno][0] & MG_BIT(0)) == 0) break; + size_t len = s_rxdesc[s_rxno][1] & (MG_BIT(13) - 1); + mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp); + s_rxdesc[s_rxno][0] &= ~MG_BIT(0); // OWN bit: handle control to DMA + if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0; } } @@ -17602,5 +26409,5 @@ struct mg_tcpip_driver mg_tcpip_driver_xmc7 = {mg_tcpip_driver_xmc7_init, mg_tcpip_driver_xmc7_tx, NULL, - mg_tcpip_driver_xmc7_up}; + mg_tcpip_driver_xmc7_poll}; #endif diff -Nru swupdate-2024.12.1+dfsg/mongoose/mongoose.h swupdate-2025.12+dfsg/mongoose/mongoose.h --- swupdate-2024.12.1+dfsg/mongoose/mongoose.h 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/mongoose/mongoose.h 2025-12-03 11:32:03.000000000 +0000 @@ -1,5 +1,5 @@ // Copyright (c) 2004-2013 Sergey Lyubka -// Copyright (c) 2013-2024 Cesanta Software Limited +// Copyright (c) 2013-2025 Cesanta Software Limited // All rights reserved // // This software is dual-licensed: you can redistribute it and/or modify @@ -20,7 +20,7 @@ #ifndef MONGOOSE_H #define MONGOOSE_H -#define MG_VERSION "7.14" +#define MG_VERSION "7.20" #ifdef __cplusplus extern "C" { @@ -33,15 +33,19 @@ #define MG_ARCH_ESP32 3 // ESP32 #define MG_ARCH_ESP8266 4 // ESP8266 #define MG_ARCH_FREERTOS 5 // FreeRTOS -#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS +#define MG_ARCH_THREADX 6 // Eclipse ThreadX (former MS Azure RTOS) #define MG_ARCH_ZEPHYR 7 // Zephyr RTOS -#define MG_ARCH_NEWLIB 8 // Bare metal ARM +#define MG_ARCH_ARMGCC 8 // Plain ARM GCC #define MG_ARCH_CMSIS_RTOS1 9 // CMSIS-RTOS API v1 (Keil RTX) #define MG_ARCH_TIRTOS 10 // Texas Semi TI-RTOS -#define MG_ARCH_RP2040 11 // Raspberry Pi RP2040 +#define MG_ARCH_PICOSDK 11 // Raspberry Pi Pico-SDK (RP2040, RP2350) #define MG_ARCH_ARMCC 12 // Keil MDK-Core with Configuration Wizard #define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS) #define MG_ARCH_RTTHREAD 14 // RT-Thread RTOS +#define MG_ARCH_ARMCGT 15 // Texas Semi ARM-CGT +#define MG_ARCH_CUBE 16 // STM32Cube environment + +#define MG_ARCH_NEWLIB MG_ARCH_ARMGCC // Alias, deprecate in 2025 #if !defined(MG_ARCH) #if defined(__unix__) || defined(__APPLE__) @@ -56,7 +60,7 @@ #endif #if !defined(MG_ARCH) -#error "MG_ARCH is not specified and we couldn't guess it. Define MG_ARCH=... in your compiler" +#error "MG_ARCH is not specified and we couldn't guess it. Define MG_ARCH=... in mongoose_config.h" #endif // http://esr.ibiblio.org/?p=5095 @@ -76,29 +80,131 @@ -#if MG_ARCH == MG_ARCH_AZURERTOS + +#if MG_ARCH == MG_ARCH_ARMCGT + +#include +#include #include #include -#include #include +#include +#include +#include #include -#include -#include +#define MG_PATH_MAX 100 +#define MG_ENABLE_SOCKET 0 +#define MG_ENABLE_DIRLIST 0 -#include -#include -#include -#include +#endif -#define PATH_MAX FX_MAXIMUM_PATH -#define MG_DIRSEP '\\' -#define socklen_t int -#define closesocket(x) soc_close(x) +#if MG_ARCH == MG_ARCH_ARMGCC +#define _POSIX_TIMERS -#undef FOPEN_MAX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MG_PATH_MAX 100 +#define MG_ENABLE_SOCKET 0 +#define MG_ENABLE_DIRLIST 0 + +#endif + + +#if MG_ARCH == MG_ARCH_CUBE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Cube-generated header, includes ST Cube HAL +// NOTE: use angle brackets to prevent amalgamator ditching it +#include + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX 100 +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 0 +#endif + +#ifndef MG_ENABLE_SOCKET +#define MG_ENABLE_SOCKET 0 +#endif + +#ifndef MG_ENABLE_TCPIP +#define MG_ENABLE_TCPIP 1 // Enable built-in TCP/IP stack +#endif + +#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_STM32F) && !defined(MG_ENABLE_DRIVER_STM32H) +#if defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7) +#define MG_ENABLE_DRIVER_STM32F 1 +#elif defined(STM32H5) || defined(STM32H7) +#define MG_ENABLE_DRIVER_STM32H 1 +#else +#error Select a driver in mongoose_config.h +#endif +#endif + +#ifndef MG_TLS +#define MG_TLS MG_TLS_BUILTIN +#endif + +#if !defined(MG_OTA) && defined(STM32F1) || defined(STM32F2) || defined(STM32F4) || defined(STM32F7) +#define MG_OTA MG_OTA_STM32F +#elif !defined(MG_OTA) && defined(STM32H5) +#define MG_OTA MG_OTA_STM32H5 +#elif !defined(MG_OTA) && defined(STM32H7) +#define MG_OTA MG_OTA_STM32H7 +#endif +// use HAL-defined execute-in-ram section +#define MG_IRAM __attribute__((section(".RamFunc"))) + +#ifndef HAL_ICACHE_MODULE_ENABLED +#define HAL_ICACHE_IsEnabled() 0 +#define HAL_ICACHE_Enable() (void) 0 +#define HAL_ICACHE_Disable() (void) 0 +#endif + +#ifndef MG_SET_MAC_ADDRESS +// Construct MAC address from UUID +#define MGUID ((uint32_t *) UID_BASE) // Unique 96-bit chip ID +#define MG_SET_MAC_ADDRESS(mac) \ + do { \ + int icache_enabled_ = HAL_ICACHE_IsEnabled(); \ + if (icache_enabled_) HAL_ICACHE_Disable(); \ + mac[0] = 42; \ + mac[1] = ((MGUID[0] >> 0) & 255) ^ ((MGUID[2] >> 19) & 255); \ + mac[2] = ((MGUID[0] >> 10) & 255) ^ ((MGUID[1] >> 10) & 255); \ + mac[3] = (MGUID[0] >> 19) & 255; \ + mac[4] = ((MGUID[1] >> 0) & 255) ^ ((MGUID[2] >> 10) & 255); \ + mac[5] = ((MGUID[2] >> 0) & 255) ^ ((MGUID[1] >> 19) & 255); \ + if (icache_enabled_) HAL_ICACHE_Enable(); \ + } while (0) +#endif #endif @@ -125,6 +231,14 @@ #define MG_PATH_MAX 128 +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 1 +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 +#endif + #endif @@ -171,6 +285,7 @@ #define mode_t size_t #include #include +#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) #elif defined(__CCRH__) #else #include @@ -179,14 +294,11 @@ #include #include -#ifndef MG_IO_SIZE -#define MG_IO_SIZE 512 -#endif +#define MG_ENABLE_CUSTOM_CALLOC 1 -#define calloc(a, b) mg_calloc(a, b) -#define free(a) vPortFree(a) -#define malloc(a) pvPortMalloc(a) -#define strdup(s) ((char *) mg_strdup(mg_str(s)).buf) +static inline void mg_free(void *ptr) { + vPortFree(ptr); +} // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib static inline void *mg_calloc(size_t cnt, size_t size) { @@ -195,40 +307,22 @@ return p; } +#if !defined(MG_ENABLE_POSIX_FS) || !MG_ENABLE_POSIX_FS +#else #define mkdir(a, b) mg_mkdir(a, b) static inline int mg_mkdir(const char *path, mode_t mode) { (void) path, (void) mode; return -1; } +#endif #endif // MG_ARCH == MG_ARCH_FREERTOS -#if MG_ARCH == MG_ARCH_NEWLIB -#define _POSIX_TIMERS - -#include +#if MG_ARCH == MG_ARCH_PICOSDK +#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MG_PATH_MAX 100 -#define MG_ENABLE_SOCKET 0 -#define MG_ENABLE_DIRLIST 0 - #endif - - -#if MG_ARCH == MG_ARCH_RP2040 -#include #include #include #include @@ -238,7 +332,14 @@ #include #include +#include int mkdir(const char *, mode_t); + +#if MG_OTA == MG_OTA_PICOSDK +#include +#include +#endif + #endif @@ -311,6 +412,49 @@ #endif +#if MG_ARCH == MG_ARCH_THREADX + +#include +#include +#include +#include + +// Do not include time.h and stdlib.h, since they conflict with nxd_bsd.h +// extern time_t time(time_t *); +#include + +#define MG_DIRSEP '\\' +#undef FOPEN_MAX + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX 32 +#endif + +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE +#define MG_SOCK_LISTEN_BACKLOG_SIZE 3 +#endif + +#ifndef MG_ENABLE_IPV6 +#define MG_ENABLE_IPV6 0 +#endif + +#define socklen_t int +#define closesocket(x) soc_close(x) + +// In order to enable BSD support in NetxDuo, do the following (assuming Cube): +// 1. Add nxd_bsd.h and nxd_bsd.c to the repo: +// https://github.com/eclipse-threadx/netxduo/blob/v6.1.12_rel/addons/BSD/nxd_bsd.c +// https://github.com/eclipse-threadx/netxduo/blob/v6.1.12_rel/addons/BSD/nxd_bsd.h +// 2. Add to tx_user.h +// #define TX_THREAD_USER_EXTENSION int bsd_errno; +// 3. Add to nx_user.h +// #define NX_ENABLE_EXTENDED_NOTIFY_SUPPORT +// 4. Add __CCRX__ build preprocessor constant +// Project -> Properties -> C/C++ -> Settings -> MCU Compiler -> Preprocessor + +#endif + + #if MG_ARCH == MG_ARCH_TIRTOS #include @@ -386,11 +530,46 @@ #define MG_PATH_MAX FILENAME_MAX #endif +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 1 +#endif + +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 16384 +#endif + #endif #if MG_ARCH == MG_ARCH_WIN32 +// Avoid name clashing; (macro expansion producing 'defined' has undefined +// behaviour). See config.h for user options +#ifndef MG_ENABLE_WINSOCK +#if (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP) && \ + (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) && \ + (!defined(MG_ENABLE_FREERTOS_TCP) || !MG_ENABLE_FREERTOS_TCP) +#define MG_ENABLE_WINSOCK 1 +#else +#define MG_ENABLE_WINSOCK 0 +#endif +#endif + +#ifndef _CRT_RAND_S +#define _CRT_RAND_S +#endif + +#ifndef _WIN32_WINNT +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define _WIN32_WINNT 0x0400 // Let vc98 pick up wincrypt.h +#else +#define _WIN32_WINNT 0x0600 +#endif +#endif +#ifndef WINVER +#define WINVER _WIN32_WINNT +#endif + #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif @@ -418,6 +597,8 @@ #include #include +#include // fix missing macros and types + #if defined(_MSC_VER) && _MSC_VER < 1700 #define __func__ "" typedef __int64 int64_t; @@ -432,12 +613,33 @@ #else #include #include +#if MG_ENABLE_WINSOCK #include #endif +#endif #include #include -#include + +// For mg_random() +#if defined(_MSC_VER) && _MSC_VER < 1700 +#include +#pragma comment(lib, "advapi32.lib") +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1200 + #ifndef IPPROTO_IP + #define IPPROTO_IP 0 + #endif + + #ifndef IP_ADD_MEMBERSHIP + struct ip_mreq { + struct in_addr imr_multiaddr; + struct in_addr imr_interface; + }; + #define IP_ADD_MEMBERSHIP 12 + #endif +#endif // Protect from calls like std::snprintf in app code // See https://github.com/cesanta/mongoose/issues/1047 @@ -449,25 +651,30 @@ #endif #endif -#define MG_INVALID_SOCKET INVALID_SOCKET -#define MG_SOCKET_TYPE SOCKET typedef unsigned long nfds_t; #if defined(_MSC_VER) +#if MG_ENABLE_WINSOCK #pragma comment(lib, "ws2_32.lib") +#endif #ifndef alloca #define alloca(a) _alloca(a) #endif #endif -#define poll(a, b, c) WSAPoll((a), (b), (c)) -#define closesocket(x) closesocket(x) -typedef int socklen_t; #define MG_DIRSEP '\\' #ifndef MG_PATH_MAX #define MG_PATH_MAX FILENAME_MAX #endif +#if MG_ENABLE_WINSOCK + +#define MG_INVALID_SOCKET INVALID_SOCKET +#define MG_SOCKET_TYPE SOCKET +#define poll(a, b, c) WSAPoll((a), (b), (c)) +#define closesocket(x) closesocket(x) +typedef int socklen_t; + #ifndef SO_EXCLUSIVEADDRUSE #define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR)) #endif @@ -482,6 +689,8 @@ #define MG_SOCK_RESET(errcode) \ (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET)) +#endif // MG_ENABLE_WINSOCK + #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) #define sleep(x) Sleep((x) *1000) #define mkdir(a, b) _mkdir(a) @@ -499,12 +708,21 @@ #define SIGPIPE 0 #endif +#ifndef MG_ENABLE_POSIX_FS +#define MG_ENABLE_POSIX_FS 1 +#endif + +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 16384 +#endif + #endif #if MG_ARCH == MG_ARCH_ZEPHYR #include +#include #include #include @@ -549,7 +767,6 @@ #include #include -#include // contents to be moved and file removed, some day #define MG_SOCKET_TYPE Socket_t #define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET @@ -581,6 +798,9 @@ #define sockaddr_in freertos_sockaddr #define sockaddr freertos_sockaddr +#if ipFR_TCP_VERSION_MAJOR >= 4 +#define sin_addr sin_address.ulIP_IPv4 +#endif #define accept(a, b, c) FreeRTOS_accept((a), (b), (c)) #define connect(a, b, c) FreeRTOS_connect((a), (b), (c)) #define bind(a, b, c) FreeRTOS_bind((a), (b), (c)) @@ -666,6 +886,10 @@ #define MG_ENABLE_LOG 1 #endif +#ifndef MG_ENABLE_CUSTOM_CALLOC +#define MG_ENABLE_CUSTOM_CALLOC 0 +#endif + #ifndef MG_ENABLE_CUSTOM_LOG #define MG_ENABLE_CUSTOM_LOG 0 // Let user define their own MG_LOG #endif @@ -718,7 +942,8 @@ #define MG_ENABLE_MD5 1 #endif -// Set MG_ENABLE_WINSOCK=0 for Win32 builds with external IP stack (like LWIP) +// Set MG_ENABLE_WINSOCK=0 for Win32 builds with other external IP stack not +// mentioned in arch_win32.h #ifndef MG_ENABLE_WINSOCK #define MG_ENABLE_WINSOCK 1 #endif @@ -744,7 +969,7 @@ #endif #ifndef MG_IO_SIZE -#define MG_IO_SIZE 2048 // Granularity of the send/recv IO buffer growth +#define MG_IO_SIZE 512 // Granularity of the send/recv IO buffer growth #endif #ifndef MG_MAX_RECV_SIZE @@ -780,12 +1005,8 @@ #endif #ifndef MG_ENABLE_POSIX_FS -#if defined(FOPEN_MAX) -#define MG_ENABLE_POSIX_FS 1 -#else #define MG_ENABLE_POSIX_FS 0 #endif -#endif #ifndef MG_INVALID_SOCKET #define MG_INVALID_SOCKET (-1) @@ -836,14 +1057,120 @@ #define MG_TCPIP_GW MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) #endif +#if MG_ENABLE_IPV6 + +#ifndef MG_TCPIP_GLOBAL +#define MG_TCPIP_GLOBAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0) +#endif + +#ifndef MG_TCPIP_IPV6_LINKLOCAL +#define MG_TCPIP_IPV6_LINKLOCAL MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0) +#endif + +#ifndef MG_TCPIP_PREFIX_LEN +#define MG_TCPIP_PREFIX_LEN 0 +#endif + +#ifndef MG_TCPIP_GW6 +#define MG_TCPIP_GW6 MG_IPV6(0, 0, 0, 0, 0, 0, 0, 0) +#endif + +#endif + #ifndef MG_SET_MAC_ADDRESS #define MG_SET_MAC_ADDRESS(mac) #endif +#ifndef MG_TCPIP_DHCPNAME_SIZE +#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size +#endif + +#ifndef MG_SET_WIFI_CONFIG +#define MG_SET_WIFI_CONFIG(data) +#endif + #ifndef MG_ENABLE_TCPIP_PRINT_DEBUG_STATS #define MG_ENABLE_TCPIP_PRINT_DEBUG_STATS 0 #endif +#ifndef MG_ENABLE_CHACHA20 +#define MG_ENABLE_CHACHA20 1 // When set to 0, GCM is used. For MG_TLS_BUILTIN +#endif + + + +// Macros to record timestamped events that happens with a connection. +// They are saved into a c->prof IO buffer, each event is a name and a 32-bit +// timestamp in milliseconds since connection init time. +// +// Test (run in two separate terminals): +// make -C tutorials/http/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1 +// curl localhost:8000 +// Output: +// 1ea1f1e7 2 net.c:150:mg_close_conn 3 profile: +// 1ea1f1e8 2 net.c:150:mg_close_conn 1ea1f1e6 init +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_OPEN +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_ACCEPT +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_READ +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_HTTP_MSG +// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_WRITE +// 1ea1f1e8 2 net.c:150:mg_close_conn 1 EV_CLOSE +// +// Usage: +// Enable profiling by setting MG_ENABLE_PROFILE=1 +// Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like to measure + +#if MG_ENABLE_PROFILE +struct mg_profitem { + const char *name; // Event name + uint32_t timestamp; // Milliseconds since connection creation (MG_EV_OPEN) +}; + +#define MG_PROFILE_ALLOC_GRANULARITY 256 // Can save 32 items wih to realloc + +// Adding a profile item to the c->prof. Must be as fast as possible. +// Reallocation of the c->prof iobuf is not desirable here, that's why we +// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY. +// This macro just inits and copies 8 bytes, and calls mg_millis(), +// which should be fast enough. +#define MG_PROF_ADD(c, name_) \ + do { \ + struct mg_iobuf *io = &c->prof; \ + uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp; \ + struct mg_profitem item = {name_, (uint32_t) mg_millis() - inittime}; \ + mg_iobuf_add(io, io->len, &item, sizeof(item)); \ + } while (0) + +// Initialising profile for a new connection. Not time sensitive +#define MG_PROF_INIT(c) \ + do { \ + struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \ + mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY); \ + mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first)); \ + } while (0) + +#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof) + +// Dumping the profile. Not time sensitive +#define MG_PROF_DUMP(c) \ + do { \ + struct mg_iobuf *io = &c->prof; \ + struct mg_profitem *p = (struct mg_profitem *) io->buf; \ + struct mg_profitem *e = &p[io->len / sizeof(*p)]; \ + MG_INFO(("%lu profile:", c->id)); \ + while (p < e) { \ + MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \ + p++; \ + } \ + } while (0) + +#else +#define MG_PROF_INIT(c) +#define MG_PROF_FREE(c) +#define MG_PROF_ADD(c, name) +#define MG_PROF_DUMP(c) +#endif + @@ -904,7 +1231,6 @@ size_t mg_snprintf(char *, size_t, const char *fmt, ...); char *mg_vmprintf(const char *fmt, va_list *ap); char *mg_mprintf(const char *fmt, ...); -size_t mg_queue_vprintf(struct mg_queue *, const char *fmt, va_list *); size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...); // %M print helper functions @@ -964,16 +1290,17 @@ struct mg_timer { - unsigned long id; // Timer ID - uint64_t period_ms; // Timer period in milliseconds - uint64_t expire; // Expiration timestamp in milliseconds - unsigned flags; // Possible flags values below -#define MG_TIMER_ONCE 0 // Call function once -#define MG_TIMER_REPEAT 1 // Call function periodically -#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set - void (*fn)(void *); // Function to call - void *arg; // Function argument - struct mg_timer *next; // Linkage + uint64_t period_ms; // Timer period in milliseconds + uint64_t expire; // Expiration timestamp in milliseconds + unsigned flags; // Possible flags values below +#define MG_TIMER_ONCE 0 // Call function once +#define MG_TIMER_REPEAT 1 // Call function periodically +#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set +#define MG_TIMER_CALLED 4 // Timer function was called at least once +#define MG_TIMER_AUTODELETE 8 // mg_free() timer when done + void (*fn)(void *); // Function to call + void *arg; // Function argument + struct mg_timer *next; // Linkage }; void mg_timer_init(struct mg_timer **head, struct mg_timer *timer, @@ -1012,7 +1339,7 @@ }; extern struct mg_fs mg_fs_posix; // POSIX open/close/read/write/seek -extern struct mg_fs mg_fs_packed; // Packed FS, see examples/device-dashboard +extern struct mg_fs mg_fs_packed; // see tutorials/core/embedded-filesystem extern struct mg_fs mg_fs_fat; // FAT FS // File descriptor @@ -1045,30 +1372,91 @@ #define assert(x) #endif +void *mg_calloc(size_t count, size_t size); +void mg_free(void *ptr); void mg_bzero(volatile unsigned char *buf, size_t len); -void mg_random(void *buf, size_t len); +bool mg_random(void *buf, size_t len); char *mg_random_str(char *buf, size_t len); -uint16_t mg_ntohs(uint16_t net); -uint32_t mg_ntohl(uint32_t net); uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); uint64_t mg_millis(void); // Return milliseconds since boot -uint64_t mg_now(void); // Return milliseconds since Epoch bool mg_path_is_sane(const struct mg_str path); +void mg_delayms(unsigned int ms); -#define mg_htons(x) mg_ntohs(x) -#define mg_htonl(x) mg_ntohl(x) - -#define MG_U32(a, b, c, d) \ - (((uint32_t) ((a) & 255) << 24) | ((uint32_t) ((b) & 255) << 16) | \ - ((uint32_t) ((c) & 255) << 8) | (uint32_t) ((d) & 255)) +#define MG_U32(a, b, c, d) \ + (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \ + ((uint32_t) ((c) &255) << 8) | (uint32_t) ((d) &255)) #define MG_IPV4(a, b, c, d) mg_htonl(MG_U32(a, b, c, d)) +#define MG_IPV6(a, b, c, d, e, f, g ,h) \ + { (uint8_t)((a)>>8),(uint8_t)(a), \ + (uint8_t)((b)>>8),(uint8_t)(b), \ + (uint8_t)((c)>>8),(uint8_t)(c), \ + (uint8_t)((d)>>8),(uint8_t)(d), \ + (uint8_t)((e)>>8),(uint8_t)(e), \ + (uint8_t)((f)>>8),(uint8_t)(f), \ + (uint8_t)((g)>>8),(uint8_t)(g), \ + (uint8_t)((h)>>8),(uint8_t)(h) } + // For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip)) #define MG_U8P(ADDR) ((uint8_t *) (ADDR)) #define MG_IPADDR_PARTS(ADDR) \ MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3] +#define MG_LOAD_BE16(p) \ + ((uint16_t) (((uint16_t) MG_U8P(p)[0] << 8U) | MG_U8P(p)[1])) +#define MG_LOAD_BE24(p) \ + ((uint32_t) (((uint32_t) MG_U8P(p)[0] << 16U) | \ + ((uint32_t) MG_U8P(p)[1] << 8U) | MG_U8P(p)[2])) +#define MG_LOAD_BE32(p) \ + ((uint32_t) (((uint32_t) MG_U8P(p)[0] << 24U) | \ + ((uint32_t) MG_U8P(p)[1] << 16U) | \ + ((uint32_t) MG_U8P(p)[2] << 8U) | MG_U8P(p)[3])) +#define MG_LOAD_BE64(p) \ + ((uint64_t) (((uint64_t) MG_U8P(p)[0] << 56U) | \ + ((uint64_t) MG_U8P(p)[1] << 48U) | \ + ((uint64_t) MG_U8P(p)[2] << 40U) | \ + ((uint64_t) MG_U8P(p)[3] << 32U) | \ + ((uint64_t) MG_U8P(p)[4] << 24U) | \ + ((uint64_t) MG_U8P(p)[5] << 16U) | \ + ((uint64_t) MG_U8P(p)[6] << 8U) | MG_U8P(p)[7])) +#define MG_STORE_BE16(p, n) \ + do { \ + MG_U8P(p)[0] = ((n) >> 8U) & 255; \ + MG_U8P(p)[1] = (n) &255; \ + } while (0) +#define MG_STORE_BE24(p, n) \ + do { \ + MG_U8P(p)[0] = ((n) >> 16U) & 255; \ + MG_U8P(p)[1] = ((n) >> 8U) & 255; \ + MG_U8P(p)[2] = (n) &255; \ + } while (0) +#define MG_STORE_BE32(p, n) \ + do { \ + MG_U8P(p)[0] = ((n) >> 24U) & 255; \ + MG_U8P(p)[1] = ((n) >> 16U) & 255; \ + MG_U8P(p)[2] = ((n) >> 8U) & 255; \ + MG_U8P(p)[3] = (n) &255; \ + } while (0) +#define MG_STORE_BE64(p, n) \ + do { \ + MG_U8P(p)[0] = ((n) >> 56U) & 255; \ + MG_U8P(p)[1] = ((n) >> 48U) & 255; \ + MG_U8P(p)[2] = ((n) >> 40U) & 255; \ + MG_U8P(p)[3] = ((n) >> 32U) & 255; \ + MG_U8P(p)[4] = ((n) >> 24U) & 255; \ + MG_U8P(p)[5] = ((n) >> 16U) & 255; \ + MG_U8P(p)[6] = ((n) >> 8U) & 255; \ + MG_U8P(p)[7] = (n) &255; \ + } while (0) + +uint16_t mg_ntohs(uint16_t net); +uint32_t mg_ntohl(uint32_t net); +uint64_t mg_ntohll(uint64_t net); +#define mg_htons(x) mg_ntohs(x) +#define mg_htonl(x) mg_ntohl(x) +#define mg_htonll(x) mg_ntohll(x) + #define MG_REG(x) ((volatile uint32_t *) (x))[0] #define MG_BIT(x) (((uint32_t) 1U) << (x)) #define MG_SET_BITS(R, CLRMASK, SETMASK) (R) = ((R) & ~(CLRMASK)) | (SETMASK) @@ -1076,9 +1464,14 @@ #define MG_ROUND_UP(x, a) ((a) == 0 ? (x) : ((((x) + (a) -1) / (a)) * (a))) #define MG_ROUND_DOWN(x, a) ((a) == 0 ? (x) : (((x) / (a)) * (a))) -#if defined(__GNUC__) +#if defined(__GNUC__) && defined(__arm__) +#ifdef __ZEPHYR__ +#define MG_ARM_DISABLE_IRQ() __asm__ __volatile__("cpsid i" : : : "memory") +#define MG_ARM_ENABLE_IRQ() __asm__ __volatile__("cpsie i" : : : "memory") +#else #define MG_ARM_DISABLE_IRQ() asm volatile("cpsid i" : : : "memory") #define MG_ARM_ENABLE_IRQ() asm volatile("cpsie i" : : : "memory") +#endif // !ZEPHYR #elif defined(__CCRH__) #define MG_RH850_DISABLE_IRQ() __DI() #define MG_RH850_ENABLE_IRQ() __EI() @@ -1092,7 +1485,11 @@ #elif defined(__ARMCC_VERSION) #define MG_DSB() __builtin_arm_dsb(0xf) #elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__) +#ifdef __ZEPHYR__ +#define MG_DSB() __asm__("DSB 0xf") +#else #define MG_DSB() asm("DSB 0xf") +#endif // !ZEPHYR #elif defined(__ICCARM__) #define MG_DSB() __iar_builtin_DSB() #else @@ -1142,8 +1539,8 @@ size_t align; // Alignment during allocation }; -int mg_iobuf_init(struct mg_iobuf *, size_t, size_t); -int mg_iobuf_resize(struct mg_iobuf *, size_t); +bool mg_iobuf_init(struct mg_iobuf *, size_t, size_t); +bool mg_iobuf_resize(struct mg_iobuf *, size_t); void mg_iobuf_free(struct mg_iobuf *); size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t); size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len); @@ -1196,11 +1593,279 @@ unsigned char buffer[64]; } mg_sha256_ctx; + void mg_sha256_init(mg_sha256_ctx *); void mg_sha256_update(mg_sha256_ctx *, const unsigned char *data, size_t len); void mg_sha256_final(unsigned char digest[32], mg_sha256_ctx *); +void mg_sha256(uint8_t dst[32], uint8_t *data, size_t datasz); void mg_hmac_sha256(uint8_t dst[32], uint8_t *key, size_t keysz, uint8_t *data, size_t datasz); + +typedef struct { + uint64_t state[8]; + uint8_t buffer[128]; + uint64_t bitlen[2]; + uint32_t datalen; +} mg_sha384_ctx; +void mg_sha384_init(mg_sha384_ctx *ctx); +void mg_sha384_update(mg_sha384_ctx *ctx, const uint8_t *data, size_t len); +void mg_sha384_final(uint8_t digest[48], mg_sha384_ctx *ctx); +void mg_sha384(uint8_t dst[48], uint8_t *data, size_t datasz); + + + +struct mg_connection; +typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, + void *ev_data); +void mg_call(struct mg_connection *c, int ev, void *ev_data); +void mg_error(struct mg_connection *c, const char *fmt, ...); + +enum { + MG_EV_ERROR, // Error char *error_message + MG_EV_OPEN, // Connection created NULL + MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis + MG_EV_RESOLVE, // Host name is resolved NULL + MG_EV_CONNECT, // Connection established NULL + MG_EV_ACCEPT, // Connection accepted NULL + MG_EV_TLS_HS, // TLS handshake succeeded NULL + MG_EV_READ, // Data received from socket long *bytes_read + MG_EV_WRITE, // Data written to socket long *bytes_written + MG_EV_CLOSE, // Connection closed NULL + MG_EV_HTTP_HDRS, // HTTP headers struct mg_http_message * + MG_EV_HTTP_MSG, // Full HTTP request/response struct mg_http_message * + MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message * + MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message * + MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message * + MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message * + MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message * + MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code + MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis + MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data + MG_EV_USER // Starting ID for user events +}; + + + + + + + + + + +struct mg_dns { + const char *url; // DNS server URL + struct mg_connection *c; // DNS server connection +}; + +struct mg_addr { + union { // Holds IPv4 or IPv6 address, in network byte order + uint8_t ip[16]; + uint32_t ip4; + uint64_t ip6[2]; + }; + uint16_t port; // TCP or UDP port in network byte order + uint8_t scope_id; // IPv6 scope ID + bool is_ip6; // True when address is IPv6 address +}; + +struct mg_mgr { + struct mg_connection *conns; // List of active connections + struct mg_dns dns4; // DNS for IPv4 + struct mg_dns dns6; // DNS for IPv6 + int dnstimeout; // DNS resolve timeout in milliseconds + bool use_dns6; // Use DNS6 server by default, see #1532 + unsigned long nextid; // Next connection ID + void *userdata; // Arbitrary user data pointer + void *tls_ctx; // TLS context shared by all TLS sessions + uint16_t mqtt_id; // MQTT IDs for pub/sub + void *active_dns_requests; // DNS requests in progress + struct mg_timer *timers; // Active timers + int epoll_fd; // Used when MG_EPOLL_ENABLE=1 + struct mg_tcpip_if *ifp; // Builtin TCP/IP stack only. Interface pointer + size_t extraconnsize; // Builtin TCP/IP stack only. Extra space + MG_SOCKET_TYPE pipe; // Socketpair end for mg_wakeup() +#if MG_ENABLE_FREERTOS_TCP + SocketSet_t ss; // NOTE(lsm): referenced from socket struct +#endif +}; + +struct mg_connection { + struct mg_connection *next; // Linkage in struct mg_mgr :: connections + struct mg_mgr *mgr; // Our container + struct mg_addr loc; // Local address + struct mg_addr rem; // Remote address + void *fd; // Connected socket, or LWIP data + unsigned long id; // Auto-incrementing unique connection ID + struct mg_iobuf recv; // Incoming data + struct mg_iobuf send; // Outgoing data + struct mg_iobuf prof; // Profile data enabled by MG_ENABLE_PROFILE + struct mg_iobuf rtls; // TLS only. Incoming encrypted data + mg_event_handler_t fn; // User-specified event handler function + void *fn_data; // User-specified function parameter + mg_event_handler_t pfn; // Protocol-specific handler function + void *pfn_data; // Protocol-specific function parameter + char data[MG_DATA_SIZE]; // Arbitrary connection data + void *tls; // TLS specific data + unsigned is_listening : 1; // Listening connection + unsigned is_client : 1; // Outbound (client) connection + unsigned is_accepted : 1; // Accepted (server) connection + unsigned is_resolving : 1; // Non-blocking DNS resolution is in progress + unsigned is_arplooking : 1; // Non-blocking ARP resolution is in progress + unsigned is_connecting : 1; // Non-blocking connect is in progress + unsigned is_tls : 1; // TLS-enabled connection + unsigned is_tls_hs : 1; // TLS handshake is in progress + unsigned is_udp : 1; // UDP connection + unsigned is_websocket : 1; // WebSocket connection + unsigned is_mqtt5 : 1; // For MQTT connection, v5 indicator + unsigned is_hexdumping : 1; // Hexdump in/out traffic + unsigned is_draining : 1; // Send remaining data, then close and free + unsigned is_closing : 1; // Close and free the connection immediately + unsigned is_full : 1; // Stop reads, until cleared + unsigned is_tls_throttled : 1; // Last TLS write: MG_SOCK_PENDING() was true + unsigned is_resp : 1; // Response is still being generated + unsigned is_readable : 1; // Connection is ready to read + unsigned is_writable : 1; // Connection is ready to write +}; + +void mg_mgr_poll(struct mg_mgr *, int ms); +void mg_mgr_init(struct mg_mgr *); +void mg_mgr_free(struct mg_mgr *); + +struct mg_connection *mg_listen(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); +struct mg_connection *mg_connect(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, + mg_event_handler_t fn, void *fn_data); +void mg_connect_resolved(struct mg_connection *); +bool mg_send(struct mg_connection *, const void *, size_t); +size_t mg_printf(struct mg_connection *, const char *fmt, ...); +size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap); +bool mg_aton(struct mg_str str, struct mg_addr *addr); + +// These functions are used to integrate with custom network stacks +struct mg_connection *mg_alloc_conn(struct mg_mgr *); +void mg_close_conn(struct mg_connection *c); +bool mg_open_listener(struct mg_connection *c, const char *url); + +// Utility functions +bool mg_wakeup(struct mg_mgr *, unsigned long id, const void *buf, size_t len); +bool mg_wakeup_init(struct mg_mgr *); +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, + unsigned flags, void (*fn)(void *), void *arg); +struct mg_connection *mg_connect_svc(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data, + mg_event_handler_t pfn, void *pfn_data); + + + + + + + + +struct mg_http_header { + struct mg_str name; // Header name + struct mg_str value; // Header value +}; + +struct mg_http_message { + struct mg_str method, uri, query, proto; // Request/response line + struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers + struct mg_str body; // Body + struct mg_str head; // Request + headers + struct mg_str message; // Request + headers + body +}; + +// Parameter for mg_http_serve_dir() +struct mg_http_serve_opts { + const char *root_dir; // Web root directory, must be non-NULL + const char *ssi_pattern; // SSI file name pattern, e.g. #.shtml + const char *extra_headers; // Extra HTTP headers to add in responses + const char *mime_types; // Extra mime types, ext1=type1,ext2=type2,.. + const char *page404; // Path to the 404 page, or NULL by default + struct mg_fs *fs; // Filesystem implementation. Use NULL for POSIX +}; + +// Parameter for mg_http_next_multipart +struct mg_http_part { + struct mg_str name; // Form field name + struct mg_str filename; // Filename for file uploads + struct mg_str body; // Part contents +}; + +int mg_http_parse(const char *s, size_t len, struct mg_http_message *); +int mg_http_get_request_len(const unsigned char *buf, size_t buf_len); +void mg_http_printf_chunk(struct mg_connection *cnn, const char *fmt, ...); +void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len); +struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); +struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, + mg_event_handler_t fn, void *fn_data); +void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, + const struct mg_http_serve_opts *); +void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, + const char *path, const struct mg_http_serve_opts *); +void mg_http_reply(struct mg_connection *, int status_code, const char *headers, + const char *body_fmt, ...); +struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); +struct mg_str mg_http_var(struct mg_str buf, struct mg_str name); +int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t); +int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); +size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); +void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); +long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *dir, size_t max_size); +void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); +size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *); +int mg_http_status(const struct mg_http_message *hm); + + +void mg_http_serve_ssi(struct mg_connection *c, const char *root, + const char *fullpath); + + +#define MG_TLS_NONE 0 // No TLS support +#define MG_TLS_MBED 1 // mbedTLS +#define MG_TLS_OPENSSL 2 // OpenSSL +#define MG_TLS_WOLFSSL 5 // WolfSSL (based on OpenSSL) +#define MG_TLS_BUILTIN 3 // Built-in +#define MG_TLS_CUSTOM 4 // Custom implementation + +#ifndef MG_TLS +#define MG_TLS MG_TLS_NONE +#endif + + + + + +struct mg_tls_opts { + struct mg_str ca; // PEM or DER + struct mg_str cert; // PEM or DER + struct mg_str key; // PEM or DER + struct mg_str name; // If not empty, enable host name verification + int skip_verification; // Skip certificate and host name verification +}; + +void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *opts); +void mg_tls_free(struct mg_connection *); +long mg_tls_send(struct mg_connection *, const void *buf, size_t len); +long mg_tls_recv(struct mg_connection *, void *buf, size_t len); +size_t mg_tls_pending(struct mg_connection *); +void mg_tls_flush(struct mg_connection *); +void mg_tls_handshake(struct mg_connection *); + +// Private +void mg_tls_ctx_init(struct mg_mgr *); +void mg_tls_ctx_free(struct mg_mgr *); + +// Low-level IO primives used by TLS layer +enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 }; +long mg_io_send(struct mg_connection *c, const void *buf, size_t len); +long mg_io_recv(struct mg_connection *c, void *buf, size_t len); #ifndef TLS_X15519_H #define TLS_X15519_H @@ -1240,9 +1905,6 @@ #ifndef TLS_AES128_H #define TLS_AES128_H -typedef unsigned char uchar; // add some convienent shorter types -typedef unsigned int uint; - /****************************************************************************** * AES_CONTEXT : cipher context / holds inter-call data ******************************************************************************/ @@ -1665,6 +2327,111 @@ /* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ +#ifndef _UECC_TYPES_H_ +#define _UECC_TYPES_H_ + +#ifndef MG_UECC_PLATFORM +#if defined(__AVR__) && __AVR__ +#define MG_UECC_PLATFORM mg_uecc_avr +#elif defined(__thumb2__) || \ + defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */ +#define MG_UECC_PLATFORM mg_uecc_arm_thumb2 +#elif defined(__thumb__) +#define MG_UECC_PLATFORM mg_uecc_arm_thumb +#elif defined(__arm__) || defined(_M_ARM) +#define MG_UECC_PLATFORM mg_uecc_arm +#elif defined(__aarch64__) +#define MG_UECC_PLATFORM mg_uecc_arm64 +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \ + defined(__I86__) +#define MG_UECC_PLATFORM mg_uecc_x86 +#elif defined(__amd64__) || defined(_M_X64) +#define MG_UECC_PLATFORM mg_uecc_x86_64 +#else +#define MG_UECC_PLATFORM mg_uecc_arch_other +#endif +#endif + +#ifndef MG_UECC_ARM_USE_UMAAL +#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6) +#define MG_UECC_ARM_USE_UMAAL 1 +#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >= 6) && \ + (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__) +#define MG_UECC_ARM_USE_UMAAL 1 +#else +#define MG_UECC_ARM_USE_UMAAL 0 +#endif +#endif + +#ifndef MG_UECC_WORD_SIZE +#if MG_UECC_PLATFORM == mg_uecc_avr +#define MG_UECC_WORD_SIZE 1 +#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM == mg_uecc_arm64) +#define MG_UECC_WORD_SIZE 8 +#else +#define MG_UECC_WORD_SIZE 4 +#endif +#endif + +#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \ + (MG_UECC_WORD_SIZE != 8) +#error "Unsupported value for MG_UECC_WORD_SIZE" +#endif + +#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1)) +#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR") +#undef MG_UECC_WORD_SIZE +#define MG_UECC_WORD_SIZE 1 +#endif + +#if ((MG_UECC_PLATFORM == mg_uecc_arm || \ + MG_UECC_PLATFORM == mg_uecc_arm_thumb || \ + MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \ + (MG_UECC_WORD_SIZE != 4)) +#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM") +#undef MG_UECC_WORD_SIZE +#define MG_UECC_WORD_SIZE 4 +#endif + +typedef int8_t wordcount_t; +typedef int16_t bitcount_t; +typedef int8_t cmpresult_t; + +#if (MG_UECC_WORD_SIZE == 1) + +typedef uint8_t mg_uecc_word_t; +typedef uint16_t mg_uecc_dword_t; + +#define HIGH_BIT_SET 0x80 +#define MG_UECC_WORD_BITS 8 +#define MG_UECC_WORD_BITS_SHIFT 3 +#define MG_UECC_WORD_BITS_MASK 0x07 + +#elif (MG_UECC_WORD_SIZE == 4) + +typedef uint32_t mg_uecc_word_t; +typedef uint64_t mg_uecc_dword_t; + +#define HIGH_BIT_SET 0x80000000 +#define MG_UECC_WORD_BITS 32 +#define MG_UECC_WORD_BITS_SHIFT 5 +#define MG_UECC_WORD_BITS_MASK 0x01F + +#elif (MG_UECC_WORD_SIZE == 8) + +typedef uint64_t mg_uecc_word_t; + +#define HIGH_BIT_SET 0x8000000000000000U +#define MG_UECC_WORD_BITS 64 +#define MG_UECC_WORD_BITS_SHIFT 6 +#define MG_UECC_WORD_BITS_MASK 0x03F + +#endif /* MG_UECC_WORD_SIZE */ + +#endif /* _UECC_TYPES_H_ */ + +/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ + #ifndef _UECC_VLI_H_ #define _UECC_VLI_H_ @@ -1831,358 +2598,127 @@ #endif /* _UECC_VLI_H_ */ -/* Copyright 2015, Kenneth MacKay. Licensed under the BSD 2-clause license. */ - -#ifndef _UECC_TYPES_H_ -#define _UECC_TYPES_H_ +// End of uecc BSD-2 +// portable8439 v1.0.1 +// Source: https://github.com/DavyLandman/portable8439 +// Licensed under CC0-1.0 +// Contains poly1305-donna e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781 (Public +// Domain) -#ifndef MG_UECC_PLATFORM -#if defined(__AVR__) && __AVR__ -#define MG_UECC_PLATFORM mg_uecc_avr -#elif defined(__thumb2__) || \ - defined(_M_ARMT) /* I think MSVC only supports Thumb-2 targets */ -#define MG_UECC_PLATFORM mg_uecc_arm_thumb2 -#elif defined(__thumb__) -#define MG_UECC_PLATFORM mg_uecc_arm_thumb -#elif defined(__arm__) || defined(_M_ARM) -#define MG_UECC_PLATFORM mg_uecc_arm -#elif defined(__aarch64__) -#define MG_UECC_PLATFORM mg_uecc_arm64 -#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \ - defined(__I86__) -#define MG_UECC_PLATFORM mg_uecc_x86 -#elif defined(__amd64__) || defined(_M_X64) -#define MG_UECC_PLATFORM mg_uecc_x86_64 -#else -#define MG_UECC_PLATFORM mg_uecc_arch_other -#endif -#endif -#ifndef MG_UECC_ARM_USE_UMAAL -#if (MG_UECC_PLATFORM == mg_uecc_arm) && (__ARM_ARCH >= 6) -#define MG_UECC_ARM_USE_UMAAL 1 -#elif (MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && (__ARM_ARCH >= 6) && \ - (!defined(__ARM_ARCH_7M__) || !__ARM_ARCH_7M__) -#define MG_UECC_ARM_USE_UMAAL 1 -#else -#define MG_UECC_ARM_USE_UMAAL 0 -#endif -#endif -#ifndef MG_UECC_WORD_SIZE -#if MG_UECC_PLATFORM == mg_uecc_avr -#define MG_UECC_WORD_SIZE 1 -#elif (MG_UECC_PLATFORM == mg_uecc_x86_64 || MG_UECC_PLATFORM == mg_uecc_arm64) -#define MG_UECC_WORD_SIZE 8 -#else -#define MG_UECC_WORD_SIZE 4 -#endif -#endif -#if (MG_UECC_WORD_SIZE != 1) && (MG_UECC_WORD_SIZE != 4) && \ - (MG_UECC_WORD_SIZE != 8) -#error "Unsupported value for MG_UECC_WORD_SIZE" -#endif -#if ((MG_UECC_PLATFORM == mg_uecc_avr) && (MG_UECC_WORD_SIZE != 1)) -#pragma message("MG_UECC_WORD_SIZE must be 1 for AVR") -#undef MG_UECC_WORD_SIZE -#define MG_UECC_WORD_SIZE 1 +#if MG_TLS == MG_TLS_BUILTIN +#ifndef __PORTABLE_8439_H +#define __PORTABLE_8439_H +#if defined(__cplusplus) +extern "C" { #endif -#if ((MG_UECC_PLATFORM == mg_uecc_arm || \ - MG_UECC_PLATFORM == mg_uecc_arm_thumb || \ - MG_UECC_PLATFORM == mg_uecc_arm_thumb2) && \ - (MG_UECC_WORD_SIZE != 4)) -#pragma message("MG_UECC_WORD_SIZE must be 4 for ARM") -#undef MG_UECC_WORD_SIZE -#define MG_UECC_WORD_SIZE 4 +// provide your own decl specificier like -DPORTABLE_8439_DECL=ICACHE_RAM_ATTR +#ifndef PORTABLE_8439_DECL +#define PORTABLE_8439_DECL #endif -typedef int8_t wordcount_t; -typedef int16_t bitcount_t; -typedef int8_t cmpresult_t; - -#if (MG_UECC_WORD_SIZE == 1) - -typedef uint8_t mg_uecc_word_t; -typedef uint16_t mg_uecc_dword_t; - -#define HIGH_BIT_SET 0x80 -#define MG_UECC_WORD_BITS 8 -#define MG_UECC_WORD_BITS_SHIFT 3 -#define MG_UECC_WORD_BITS_MASK 0x07 - -#elif (MG_UECC_WORD_SIZE == 4) - -typedef uint32_t mg_uecc_word_t; -typedef uint64_t mg_uecc_dword_t; - -#define HIGH_BIT_SET 0x80000000 -#define MG_UECC_WORD_BITS 32 -#define MG_UECC_WORD_BITS_SHIFT 5 -#define MG_UECC_WORD_BITS_MASK 0x01F - -#elif (MG_UECC_WORD_SIZE == 8) - -typedef uint64_t mg_uecc_word_t; - -#define HIGH_BIT_SET 0x8000000000000000U -#define MG_UECC_WORD_BITS 64 -#define MG_UECC_WORD_BITS_SHIFT 6 -#define MG_UECC_WORD_BITS_MASK 0x03F - -#endif /* MG_UECC_WORD_SIZE */ +/* + This library implements RFC 8439 a.k.a. ChaCha20-Poly1305 AEAD -#endif /* _UECC_TYPES_H_ */ -// End of uecc BSD-2 - - -struct mg_connection; -typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, - void *ev_data); -void mg_call(struct mg_connection *c, int ev, void *ev_data); -void mg_error(struct mg_connection *c, const char *fmt, ...); - -enum { - MG_EV_ERROR, // Error char *error_message - MG_EV_OPEN, // Connection created NULL - MG_EV_POLL, // mg_mgr_poll iteration uint64_t *uptime_millis - MG_EV_RESOLVE, // Host name is resolved NULL - MG_EV_CONNECT, // Connection established NULL - MG_EV_ACCEPT, // Connection accepted NULL - MG_EV_TLS_HS, // TLS handshake succeeded NULL - MG_EV_READ, // Data received from socket long *bytes_read - MG_EV_WRITE, // Data written to socket long *bytes_written - MG_EV_CLOSE, // Connection closed NULL - MG_EV_HTTP_HDRS, // HTTP headers struct mg_http_message * - MG_EV_HTTP_MSG, // Full HTTP request/response struct mg_http_message * - MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message * - MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message * - MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message * - MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message * - MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message * - MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code - MG_EV_SNTP_TIME, // SNTP time received uint64_t *epoch_millis - MG_EV_WAKEUP, // mg_wakeup() data received struct mg_str *data - MG_EV_USER // Starting ID for user events -}; - - - - - - - - - -struct mg_dns { - const char *url; // DNS server URL - struct mg_connection *c; // DNS server connection -}; - -struct mg_addr { - uint8_t ip[16]; // Holds IPv4 or IPv6 address, in network byte order - uint16_t port; // TCP or UDP port in network byte order - uint8_t scope_id; // IPv6 scope ID - bool is_ip6; // True when address is IPv6 address -}; + You can use this library to avoid attackers mutating or reusing your + encrypted messages. This does assume you never reuse a nonce+key pair and, + if possible, carefully pick your associated data. +*/ -struct mg_mgr { - struct mg_connection *conns; // List of active connections - struct mg_dns dns4; // DNS for IPv4 - struct mg_dns dns6; // DNS for IPv6 - int dnstimeout; // DNS resolve timeout in milliseconds - bool use_dns6; // Use DNS6 server by default, see #1532 - unsigned long nextid; // Next connection ID - unsigned long timerid; // Next timer ID - void *userdata; // Arbitrary user data pointer - void *tls_ctx; // TLS context shared by all TLS sessions - uint16_t mqtt_id; // MQTT IDs for pub/sub - void *active_dns_requests; // DNS requests in progress - struct mg_timer *timers; // Active timers - int epoll_fd; // Used when MG_EPOLL_ENABLE=1 - void *priv; // Used by the MIP stack - size_t extraconnsize; // Used by the MIP stack - MG_SOCKET_TYPE pipe; // Socketpair end for mg_wakeup() -#if MG_ENABLE_FREERTOS_TCP - SocketSet_t ss; // NOTE(lsm): referenced from socket struct +/* Make sure we are either nested in C++ or running in a C99+ compiler +#if !defined(__cplusplus) && !defined(_MSC_VER) && \ + (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) +#error "C99 or newer required" +#endif */ + +// #if CHAR_BIT > 8 +// # error "Systems without native octals not suppoted" +// #endif + +#if defined(_MSC_VER) || defined(__cplusplus) +// add restrict support is possible +#if (defined(_MSC_VER) && _MSC_VER >= 1900) || defined(__clang__) || \ + defined(__GNUC__) +#define restrict __restrict +#else +#define restrict #endif -}; - -struct mg_connection { - struct mg_connection *next; // Linkage in struct mg_mgr :: connections - struct mg_mgr *mgr; // Our container - struct mg_addr loc; // Local address - struct mg_addr rem; // Remote address - void *fd; // Connected socket, or LWIP data - unsigned long id; // Auto-incrementing unique connection ID - struct mg_iobuf recv; // Incoming data - struct mg_iobuf send; // Outgoing data - struct mg_iobuf prof; // Profile data enabled by MG_ENABLE_PROFILE - struct mg_iobuf rtls; // TLS only. Incoming encrypted data - mg_event_handler_t fn; // User-specified event handler function - void *fn_data; // User-specified function parameter - mg_event_handler_t pfn; // Protocol-specific handler function - void *pfn_data; // Protocol-specific function parameter - char data[MG_DATA_SIZE]; // Arbitrary connection data - void *tls; // TLS specific data - unsigned is_listening : 1; // Listening connection - unsigned is_client : 1; // Outbound (client) connection - unsigned is_accepted : 1; // Accepted (server) connection - unsigned is_resolving : 1; // Non-blocking DNS resolution is in progress - unsigned is_arplooking : 1; // Non-blocking ARP resolution is in progress - unsigned is_connecting : 1; // Non-blocking connect is in progress - unsigned is_tls : 1; // TLS-enabled connection - unsigned is_tls_hs : 1; // TLS handshake is in progress - unsigned is_udp : 1; // UDP connection - unsigned is_websocket : 1; // WebSocket connection - unsigned is_mqtt5 : 1; // For MQTT connection, v5 indicator - unsigned is_hexdumping : 1; // Hexdump in/out traffic - unsigned is_draining : 1; // Send remaining data, then close and free - unsigned is_closing : 1; // Close and free the connection immediately - unsigned is_full : 1; // Stop reads, until cleared - unsigned is_resp : 1; // Response is still being generated - unsigned is_readable : 1; // Connection is ready to read - unsigned is_writable : 1; // Connection is ready to write -}; - -void mg_mgr_poll(struct mg_mgr *, int ms); -void mg_mgr_init(struct mg_mgr *); -void mg_mgr_free(struct mg_mgr *); - -struct mg_connection *mg_listen(struct mg_mgr *, const char *url, - mg_event_handler_t fn, void *fn_data); -struct mg_connection *mg_connect(struct mg_mgr *, const char *url, - mg_event_handler_t fn, void *fn_data); -struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, - mg_event_handler_t fn, void *fn_data); -void mg_connect_resolved(struct mg_connection *); -bool mg_send(struct mg_connection *, const void *, size_t); -size_t mg_printf(struct mg_connection *, const char *fmt, ...); -size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap); -bool mg_aton(struct mg_str str, struct mg_addr *addr); - -// These functions are used to integrate with custom network stacks -struct mg_connection *mg_alloc_conn(struct mg_mgr *); -void mg_close_conn(struct mg_connection *c); -bool mg_open_listener(struct mg_connection *c, const char *url); - -// Utility functions -bool mg_wakeup(struct mg_mgr *, unsigned long id, const void *buf, size_t len); -bool mg_wakeup_init(struct mg_mgr *); -struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, - unsigned flags, void (*fn)(void *), void *arg); - - - - - - - - -struct mg_http_header { - struct mg_str name; // Header name - struct mg_str value; // Header value -}; - -struct mg_http_message { - struct mg_str method, uri, query, proto; // Request/response line - struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers - struct mg_str body; // Body - struct mg_str head; // Request + headers - struct mg_str message; // Request + headers + body -}; - -// Parameter for mg_http_serve_dir() -struct mg_http_serve_opts { - const char *root_dir; // Web root directory, must be non-NULL - const char *ssi_pattern; // SSI file name pattern, e.g. #.shtml - const char *extra_headers; // Extra HTTP headers to add in responses - const char *mime_types; // Extra mime types, ext1=type1,ext2=type2,.. - const char *page404; // Path to the 404 page, or NULL by default - struct mg_fs *fs; // Filesystem implementation. Use NULL for POSIX -}; - -// Parameter for mg_http_next_multipart -struct mg_http_part { - struct mg_str name; // Form field name - struct mg_str filename; // Filename for file uploads - struct mg_str body; // Part contents -}; - -int mg_http_parse(const char *s, size_t len, struct mg_http_message *); -int mg_http_get_request_len(const unsigned char *buf, size_t buf_len); -void mg_http_printf_chunk(struct mg_connection *cnn, const char *fmt, ...); -void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len); -void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm); -struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url, - mg_event_handler_t fn, void *fn_data); -struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, - mg_event_handler_t fn, void *fn_data); -void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, - const struct mg_http_serve_opts *); -void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, - const char *path, const struct mg_http_serve_opts *); -void mg_http_reply(struct mg_connection *, int status_code, const char *headers, - const char *body_fmt, ...); -struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); -struct mg_str mg_http_var(struct mg_str buf, struct mg_str name); -int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t); -int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); -size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); -void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); -long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, - struct mg_fs *fs, const char *dir, size_t max_size); -void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); -struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); -size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *); -int mg_http_status(const struct mg_http_message *hm); -void mg_hello(const char *url); - - -void mg_http_serve_ssi(struct mg_connection *c, const char *root, - const char *fullpath); - - -#define MG_TLS_NONE 0 // No TLS support -#define MG_TLS_MBED 1 // mbedTLS -#define MG_TLS_OPENSSL 2 // OpenSSL -#define MG_TLS_WOLFSSL 5 // WolfSSL (based on OpenSSL) -#define MG_TLS_BUILTIN 3 // Built-in -#define MG_TLS_CUSTOM 4 // Custom implementation - -#ifndef MG_TLS -#define MG_TLS MG_TLS_NONE #endif +#define RFC_8439_TAG_SIZE (16) +#define RFC_8439_KEY_SIZE (32) +#define RFC_8439_NONCE_SIZE (12) + +/* + Encrypt/Seal plain text bytes into a cipher text that can only be + decrypted by knowing the key, nonce and associated data. + + input: + - key: RFC_8439_KEY_SIZE bytes that all parties have agreed + upon beforehand + - nonce: RFC_8439_NONCE_SIZE bytes that should never be repeated + for the same key. A counter or a pseudo-random value are fine. + - ad: associated data to include with calculating the tag of the + cipher text. Can be null for empty. + - plain_text: data to be encrypted, pointer + size should not overlap + with cipher_text pointer + + output: + - cipher_text: encrypted plain_text with a tag appended. Make sure to + allocate at least plain_text_size + RFC_8439_TAG_SIZE + + returns: + - size of bytes written to cipher_text, can be -1 if overlapping + pointers are passed for plain_text and cipher_text +*/ +PORTABLE_8439_DECL size_t mg_chacha20_poly1305_encrypt( + uint8_t *restrict cipher_text, const uint8_t key[RFC_8439_KEY_SIZE], + const uint8_t nonce[RFC_8439_NONCE_SIZE], const uint8_t *restrict ad, + size_t ad_size, const uint8_t *restrict plain_text, size_t plain_text_size); + +/* + Decrypt/unseal cipher text given the right key, nonce, and additional data. + + input: + - key: RFC_8439_KEY_SIZE bytes that all parties have agreed + upon beforehand + - nonce: RFC_8439_NONCE_SIZE bytes that should never be repeated for + the same key. A counter or a pseudo-random value are fine. + - ad: associated data to include with calculating the tag of the + cipher text. Can be null for empty. + - cipher_text: encrypted message. + + output: + - plain_text: data to be encrypted, pointer + size should not overlap + with cipher_text pointer, leave at least enough room for + cipher_text_size - RFC_8439_TAG_SIZE + + returns: + - size of bytes written to plain_text, -1 signals either: + - incorrect key/nonce/ad + - corrupted cipher_text + - overlapping pointers are passed for plain_text and cipher_text +*/ +PORTABLE_8439_DECL size_t mg_chacha20_poly1305_decrypt( + uint8_t *restrict plain_text, const uint8_t key[RFC_8439_KEY_SIZE], + const uint8_t nonce[RFC_8439_NONCE_SIZE], + const uint8_t *restrict cipher_text, size_t cipher_text_size); +#if defined(__cplusplus) +} +#endif +#endif +#endif +#ifndef TLS_RSA_H +#define TLS_RSA_H +int mg_rsa_mod_pow(const uint8_t *mod, size_t modsz, const uint8_t *exp, size_t expsz, const uint8_t *msg, size_t msgsz, uint8_t *out, size_t outsz); - -struct mg_tls_opts { - struct mg_str ca; // PEM or DER - struct mg_str cert; // PEM or DER - struct mg_str key; // PEM or DER - struct mg_str name; // If not empty, enable host name verification - int skip_verification; // Skip certificate and host name verification -}; - -void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *opts); -void mg_tls_free(struct mg_connection *); -long mg_tls_send(struct mg_connection *, const void *buf, size_t len); -long mg_tls_recv(struct mg_connection *, void *buf, size_t len); -size_t mg_tls_pending(struct mg_connection *); -void mg_tls_handshake(struct mg_connection *); - -// Private -void mg_tls_ctx_init(struct mg_mgr *); -void mg_tls_ctx_free(struct mg_mgr *); - -// Low-level IO primives used by TLS layer -enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 }; -long mg_io_send(struct mg_connection *c, const void *buf, size_t len); -long mg_io_recv(struct mg_connection *c, void *buf, size_t len); +#endif // TLS_RSA_H @@ -2192,6 +2728,7 @@ #if MG_TLS == MG_TLS_MBED #include +#include #include #include #include @@ -2212,6 +2749,9 @@ #ifdef MBEDTLS_SSL_SESSION_TICKETS mbedtls_ssl_ticket_context ticket; // Session tickets context #endif + // https://github.com/Mbed-TLS/mbedtls/blob/3b3c652d/include/mbedtls/ssl.h#L5071C18-L5076C29 + unsigned char *throttled_buf; // see #3074 + size_t throttled_len; }; #endif @@ -2262,6 +2802,8 @@ void mg_sntp_request(struct mg_connection *c); int64_t mg_sntp_parse(const unsigned char *buf, size_t len); +uint64_t mg_now(void); // Return milliseconds since Epoch + @@ -2413,6 +2955,8 @@ bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *); size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, bool is_question, struct mg_dns_rr *); + +struct mg_connection *mg_mdns_listen(struct mg_mgr *mgr, char *name); @@ -2477,95 +3021,131 @@ -#define MG_OTA_NONE 0 // No OTA support -#define MG_OTA_FLASH 1 // OTA via an internal flash -#define MG_OTA_ESP32 2 // ESP32 OTA implementation -#define MG_OTA_CUSTOM 100 // Custom implementation +#define MG_OTA_NONE 0 // No OTA support +#define MG_OTA_STM32H5 1 // STM32 H5 +#define MG_OTA_STM32H7 2 // STM32 H7 +#define MG_OTA_STM32H7_DUAL_CORE 3 // STM32 H7 dual core +#define MG_OTA_STM32F 4 // STM32 F7/F4/F2 +#define MG_OTA_CH32V307 100 // WCH CH32V307 +#define MG_OTA_U2A 200 // Renesas U2A16, U2A8, U2A6 +#define MG_OTA_RT1020 300 // IMXRT1020 +#define MG_OTA_RT1050 301 // IMXRT1050 +#define MG_OTA_RT1060 302 // IMXRT1060 +#define MG_OTA_RT1064 303 // IMXRT1064 +#define MG_OTA_RT1170 304 // IMXRT1170 +#define MG_OTA_MCXN 310 // MCXN947 +#define MG_OTA_FRDM 320 // FRDM-RW612 +#define MG_OTA_FLASH 900 // OTA via an internal flash +#define MG_OTA_ESP32 910 // ESP32 OTA implementation +#define MG_OTA_PICOSDK 920 // RP2040/2350 using Pico-SDK hardware_flash +#define MG_OTA_CUSTOM 1000 // Custom implementation #ifndef MG_OTA #define MG_OTA MG_OTA_NONE -#endif - -#if defined(__GNUC__) && !defined(__APPLE__) -#define MG_IRAM __attribute__((section(".iram"))) +#else +#ifndef MG_IRAM +#if defined(__GNUC__) +#define MG_IRAM __attribute__((noinline, section(".iram"))) #else #define MG_IRAM -#endif +#endif // compiler +#endif // IRAM +#endif // OTA // Firmware update API bool mg_ota_begin(size_t new_firmware_size); // Start writing bool mg_ota_write(const void *buf, size_t len); // Write chunk, aligned to 1k bool mg_ota_end(void); // Stop writing -enum { - MG_OTA_UNAVAILABLE = 0, // No OTA information is present - MG_OTA_FIRST_BOOT = 1, // Device booting the first time after the OTA - MG_OTA_UNCOMMITTED = 2, // Ditto, but marking us for the rollback - MG_OTA_COMMITTED = 3 // The firmware is good -}; -enum { MG_FIRMWARE_CURRENT = 0, MG_FIRMWARE_PREVIOUS = 1 }; - -int mg_ota_status(int firmware); // Return firmware status MG_OTA_* -uint32_t mg_ota_crc32(int firmware); // Return firmware checksum -uint32_t mg_ota_timestamp(int firmware); // Firmware timestamp, UNIX UTC epoch -size_t mg_ota_size(int firmware); // Firmware size - -bool mg_ota_commit(void); // Commit current firmware -bool mg_ota_rollback(void); // Rollback to the previous firmware -MG_IRAM void mg_ota_boot(void); // Bootloader function -// Copyright (c) 2023 Cesanta Software Limited -// All rights reserved +#if MG_OTA != MG_OTA_NONE && MG_OTA != MG_OTA_CUSTOM +struct mg_flash { + void *start; // Address at which flash starts + size_t size; // Flash size + size_t secsz; // Sector size + size_t align; // Write alignment + bool (*write_fn)(void *, const void *, size_t); // Write function + bool (*swap_fn)(void); // Swap partitions +}; +bool mg_ota_flash_begin(size_t new_firmware_size, struct mg_flash *flash); +bool mg_ota_flash_write(const void *buf, size_t len, struct mg_flash *flash); +bool mg_ota_flash_end(struct mg_flash *flash); -#define MG_DEVICE_NONE 0 // Dummy system +#endif -#define MG_DEVICE_STM32H5 1 // STM32 H5 -#define MG_DEVICE_STM32H7 2 // STM32 H7 -#define MG_DEVICE_CH32V307 100 // WCH CH32V307 -#define MG_DEVICE_U2A 200 // Renesas U2A16, U2A8, U2A6 -#define MG_DEVICE_RT1020 300 // IMXRT1020 -#define MG_DEVICE_RT1060 301 // IMXRT1060 -#define MG_DEVICE_CUSTOM 1000 // Custom implementation -#ifndef MG_DEVICE -#define MG_DEVICE MG_DEVICE_NONE -#endif -// Flash information -void *mg_flash_start(void); // Return flash start address -size_t mg_flash_size(void); // Return flash size -size_t mg_flash_sector_size(void); // Return flash sector size -size_t mg_flash_write_align(void); // Return flash write align, minimum 4 -int mg_flash_bank(void); // 0: not dual bank, 1: bank1, 2: bank2 -// Write, erase, swap bank -bool mg_flash_write(void *addr, const void *buf, size_t len); -bool mg_flash_erase(void *sector); -bool mg_flash_swap_bank(void); -// Convenience functions to store data on a flash sector with wear levelling -// If `sector` is NULL, then the last sector of flash is used -bool mg_flash_load(void *sector, uint32_t key, void *buf, size_t len); -bool mg_flash_save(void *sector, uint32_t key, const void *buf, size_t len); -void mg_device_reset(void); // Reboot device immediately +struct mg_wifi_data { + char *ssid, *pass; // STA mode, SSID to connect to + char *apssid, *appass; // AP mode, our SSID + uint32_t apip, apmask; // AP mode, our IP address and mask + uint8_t security; // STA mode, TBD + uint8_t apsecurity; // AP mode, TBD + uint8_t apchannel; // AP mode, channel to use + bool apmode; // start in AP mode; 'false' -> connect to 'ssid' != NULL +}; + +struct mg_wifi_scan_bss_data { + struct mg_str SSID; + char *BSSID; + int16_t RSSI; + uint8_t security; +#define MG_WIFI_SECURITY_OPEN 0 +#define MG_WIFI_SECURITY_WEP MG_BIT(0) +#define MG_WIFI_SECURITY_WPA MG_BIT(1) +#define MG_WIFI_SECURITY_WPA2 MG_BIT(2) +#define MG_WIFI_SECURITY_WPA3 MG_BIT(3) + uint8_t channel; + unsigned band : 2; +#define MG_WIFI_BAND_2G 0 +#define MG_WIFI_BAND_5G 1 + unsigned has_n : 1; +}; + +bool mg_wifi_scan(void); +bool mg_wifi_connect(struct mg_wifi_data *); +bool mg_wifi_disconnect(void); +bool mg_wifi_ap_start(struct mg_wifi_data *); +bool mg_wifi_ap_stop(void); + + +#if MG_ENABLE_TCPIP -#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP struct mg_tcpip_if; // Mongoose TCP/IP network interface struct mg_tcpip_driver { bool (*init)(struct mg_tcpip_if *); // Init driver size_t (*tx)(const void *, size_t, struct mg_tcpip_if *); // Transmit frame size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); // Receive frame - bool (*up)(struct mg_tcpip_if *); // Up/down status + bool (*poll)(struct mg_tcpip_if *, bool); // Poll, return Up/down status +}; + +typedef void (*mg_tcpip_event_handler_t)(struct mg_tcpip_if *ifp, int ev, + void *ev_data); + +enum { + MG_TCPIP_EV_ST_CHG, // state change uint8_t * (&ifp->state) + MG_TCPIP_EV_DHCP_DNS, // DHCP DNS assignment uint32_t *ipaddr + MG_TCPIP_EV_DHCP_SNTP, // DHCP SNTP assignment uint32_t *ipaddr + MG_TCPIP_EV_ARP, // Got ARP packet struct mg_str * + MG_TCPIP_EV_TIMER_1S, // 1 second timer NULL + MG_TCPIP_EV_WIFI_SCAN_RESULT, // Wi-Fi scan results struct mg_wifi_scan_bss_data * + MG_TCPIP_EV_WIFI_SCAN_END, // Wi-Fi scan has finished NULL + MG_TCPIP_EV_WIFI_CONNECT_ERR, // Wi-Fi connect has failed driver and chip specific + MG_TCPIP_EV_DRIVER, // Driver event driver specific + MG_TCPIP_EV_ST6_CHG, // state6 change uint8_t * (&ifp->state6) + MG_TCPIP_EV_USER // Starting ID for user events }; // Network interface @@ -2576,17 +3156,31 @@ bool enable_dhcp_client; // Enable DCHP client bool enable_dhcp_server; // Enable DCHP server bool enable_get_gateway; // DCHP server sets client as gateway + bool enable_req_dns; // DCHP client requests DNS server + bool enable_req_sntp; // DCHP client requests SNTP server bool enable_crc32_check; // Do a CRC check on RX frames and strip it bool enable_mac_check; // Do a MAC check on RX frames + bool update_mac_hash_table; // Signal drivers to update MAC controller struct mg_tcpip_driver *driver; // Low level driver void *driver_data; // Driver-specific data + mg_tcpip_event_handler_t pfn; // Driver-specific event handler function + mg_tcpip_event_handler_t fn; // User-specified event handler function struct mg_mgr *mgr; // Mongoose event manager struct mg_queue recv_queue; // Receive queue - uint16_t mtu; // Interface MTU + char dhcp_name[MG_TCPIP_DHCPNAME_SIZE]; // Name for DHCP, "mip" if unset + uint16_t mtu; // Interface MTU #define MG_TCPIP_MTU_DEFAULT 1500 +#if MG_ENABLE_IPV6 + uint64_t ip6ll[2], ip6[2]; // IPv6 link-local and global addresses + uint8_t prefix_len; // Prefix length + uint64_t gw6[2]; // Default gateway + bool enable_slaac; // Enable IPv6 address autoconfiguration + bool enable_dhcp6_client; // Enable DCHPv6 client +#endif // Internal state, user can use it but should not change it uint8_t gwmac[6]; // Router's MAC + char *dns4_url; // DNS server URL uint64_t now; // Current time uint64_t timer_1000ms; // 1000 ms timer: for DHCP and link state uint64_t lease_expire; // Lease expiration time, in ms @@ -2595,20 +3189,27 @@ volatile uint32_t nrecv; // Number of received frames volatile uint32_t nsent; // Number of transmitted frames volatile uint32_t nerr; // Number of driver errors - uint8_t state; // Current state + uint8_t state; // Current link and IPv4 state #define MG_TCPIP_STATE_DOWN 0 // Interface is down #define MG_TCPIP_STATE_UP 1 // Interface is up -#define MG_TCPIP_STATE_REQ 2 // Interface is up and has requested an IP -#define MG_TCPIP_STATE_READY 3 // Interface is up and has an IP assigned +#define MG_TCPIP_STATE_REQ 2 // Interface is up, DHCP REQUESTING state +#define MG_TCPIP_STATE_IP 3 // Interface is up and has an IP assigned +#define MG_TCPIP_STATE_READY 4 // Interface has fully come up, ready to work +#if MG_ENABLE_IPV6 + uint8_t gw6mac[6]; // IPv6 Router's MAC + uint8_t state6; // Current IPv6 state +#endif }; - void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *); void mg_tcpip_free(struct mg_tcpip_if *); void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp); +void mg_tcpip_arp_request(struct mg_tcpip_if *ifp, uint32_t ip, uint8_t *mac); extern struct mg_tcpip_driver mg_tcpip_driver_stm32f; extern struct mg_tcpip_driver mg_tcpip_driver_w5500; +extern struct mg_tcpip_driver mg_tcpip_driver_w5100; extern struct mg_tcpip_driver mg_tcpip_driver_tm4c; +extern struct mg_tcpip_driver mg_tcpip_driver_tms570; extern struct mg_tcpip_driver mg_tcpip_driver_stm32h; extern struct mg_tcpip_driver mg_tcpip_driver_imxrt; extern struct mg_tcpip_driver mg_tcpip_driver_same54; @@ -2616,6 +3217,11 @@ extern struct mg_tcpip_driver mg_tcpip_driver_ra; extern struct mg_tcpip_driver mg_tcpip_driver_xmc; extern struct mg_tcpip_driver mg_tcpip_driver_xmc7; +extern struct mg_tcpip_driver mg_tcpip_driver_ppp; +extern struct mg_tcpip_driver mg_tcpip_driver_pico_w; +extern struct mg_tcpip_driver mg_tcpip_driver_rw612; +extern struct mg_tcpip_driver mg_tcpip_driver_cyw; +extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi; // Drivers that require SPI, can use this SPI abstraction struct mg_tcpip_spi { @@ -2624,80 +3230,45 @@ void (*end)(void *); // SPI end: slave select high uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1 byte, read reply }; -#endif - - - -// Macros to record timestamped events that happens with a connection. -// They are saved into a c->prof IO buffer, each event is a name and a 32-bit -// timestamp in milliseconds since connection init time. -// -// Test (run in two separate terminals): -// make -C examples/http-server/ CFLAGS_EXTRA=-DMG_ENABLE_PROFILE=1 -// curl localhost:8000 -// Output: -// 1ea1f1e7 2 net.c:150:mg_close_conn 3 profile: -// 1ea1f1e8 2 net.c:150:mg_close_conn 1ea1f1e6 init -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_OPEN -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_ACCEPT -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_READ -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_HTTP_MSG -// 1ea1f1e8 2 net.c:150:mg_close_conn 0 EV_WRITE -// 1ea1f1e8 2 net.c:150:mg_close_conn 1 EV_CLOSE -// -// Usage: -// Enable profiling by setting MG_ENABLE_PROFILE=1 -// Invoke MG_PROF_ADD(c, "MY_EVENT_1") in the places you'd like to measure -#if MG_ENABLE_PROFILE -struct mg_profitem { - const char *name; // Event name - uint32_t timestamp; // Milliseconds since connection creation (MG_EV_OPEN) -}; -#define MG_PROFILE_ALLOC_GRANULARITY 256 // Can save 32 items wih to realloc +// Alignment and memory section requirements +#ifndef MG_8BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_8BYTE_ALIGNED __attribute__((aligned((8U)))) +#else +#define MG_8BYTE_ALIGNED +#endif // compiler +#endif // 8BYTE_ALIGNED -// Adding a profile item to the c->prof. Must be as fast as possible. -// Reallocation of the c->prof iobuf is not desirable here, that's why we -// pre-allocate c->prof with MG_PROFILE_ALLOC_GRANULARITY. -// This macro just inits and copies 8 bytes, and calls mg_millis(), -// which should be fast enough. -#define MG_PROF_ADD(c, name_) \ - do { \ - struct mg_iobuf *io = &c->prof; \ - uint32_t inittime = ((struct mg_profitem *) io->buf)->timestamp; \ - struct mg_profitem item = {name_, (uint32_t) mg_millis() - inittime}; \ - mg_iobuf_add(io, io->len, &item, sizeof(item)); \ - } while (0) +#ifndef MG_16BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_16BYTE_ALIGNED __attribute__((aligned((16U)))) +#else +#define MG_16BYTE_ALIGNED +#endif // compiler +#endif // 16BYTE_ALIGNED -// Initialising profile for a new connection. Not time sensitive -#define MG_PROF_INIT(c) \ - do { \ - struct mg_profitem first = {"init", (uint32_t) mg_millis()}; \ - mg_iobuf_init(&(c)->prof, 0, MG_PROFILE_ALLOC_GRANULARITY); \ - mg_iobuf_add(&c->prof, c->prof.len, &first, sizeof(first)); \ - } while (0) +#ifndef MG_32BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_32BYTE_ALIGNED __attribute__((aligned((32U)))) +#else +#define MG_32BYTE_ALIGNED +#endif // compiler +#endif // 32BYTE_ALIGNED -#define MG_PROF_FREE(c) mg_iobuf_free(&(c)->prof) +#ifndef MG_64BYTE_ALIGNED +#if defined(__GNUC__) +#define MG_64BYTE_ALIGNED __attribute__((aligned((64U)))) +#else +#define MG_64BYTE_ALIGNED +#endif // compiler +#endif // 64BYTE_ALIGNED -// Dumping the profile. Not time sensitive -#define MG_PROF_DUMP(c) \ - do { \ - struct mg_iobuf *io = &c->prof; \ - struct mg_profitem *p = (struct mg_profitem *) io->buf; \ - struct mg_profitem *e = &p[io->len / sizeof(*p)]; \ - MG_INFO(("%lu profile:", c->id)); \ - while (p < e) { \ - MG_INFO(("%5lx %s", (unsigned long) p->timestamp, p->name)); \ - p++; \ - } \ - } while (0) +#ifndef MG_ETH_RAM +#define MG_ETH_RAM +#endif -#else -#define MG_PROF_INIT(c) -#define MG_PROF_FREE(c) -#define MG_PROF_ADD(c, name) -#define MG_PROF_DUMP(c) #endif @@ -2709,7 +3280,57 @@ #endif -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT) && MG_ENABLE_DRIVER_IMXRT +#if MG_ENABLE_TCPIP && \ + ((defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW) || \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO)) + +struct mg_tcpip_spi_ { + void *spi; // Opaque SPI bus descriptor + void (*begin)(void *); // SPI begin: slave select low + void (*end)(void *); // SPI end: slave select high + void (*txn)(void *, uint8_t *, uint8_t *, + size_t len); // SPI transaction: write-read len bytes +}; + +struct mg_tcpip_driver_cyw_firmware { + const uint8_t *code_addr; + size_t code_len; + const uint8_t *nvram_addr; + size_t nvram_len; + const uint8_t *clm_addr; + size_t clm_len; +}; + +struct mg_tcpip_driver_cyw_data { + struct mg_wifi_data wifi; + void *bus; + struct mg_tcpip_driver_cyw_firmware *fw; + bool hs; // use chip "high-speed" mode; otherwise SPI CPOL0 CPHA0 (DS 4.2.3 Table 6) +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_cyw_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_cyw; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: cyw, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_IMXRT10) && MG_ENABLE_DRIVER_IMXRT10) || \ + (defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11) || \ + (defined(MG_ENABLE_DRIVER_MCXE) && MG_ENABLE_DRIVER_MCXE) struct mg_tcpip_driver_imxrt_data { // MDC clock divider. MDC clock is derived from IPS Bus clock (ipg_clk), @@ -2755,6 +3376,34 @@ #endif +#if MG_ENABLE_TCPIP && \ + defined(MG_ENABLE_DRIVER_NXP_WIFI) && MG_ENABLE_DRIVER_NXP_WIFI + + +struct mg_tcpip_driver_nxp_wifi_data { + struct mg_wifi_data wifi; +}; + + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_nxp_wifi_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_nxp_wifi; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: nxp wifi, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + struct mg_phy { @@ -2764,10 +3413,10 @@ // PHY configuration settings, bitmask enum { - MG_PHY_LEDS_ACTIVE_HIGH = - (1 << 0), // Set if PHY LEDs are connected to ground - MG_PHY_CLOCKS_MAC = - (1 << 1) // Set when PHY clocks MAC. Otherwise, MAC clocks PHY + // Set if PHY LEDs are connected to ground + MG_PHY_LEDS_ACTIVE_HIGH = (1 << 0), + // Set when PHY clocks MAC. Otherwise, MAC clocks PHY + MG_PHY_CLOCKS_MAC = (1 << 1) }; enum { MG_PHY_SPEED_10M, MG_PHY_SPEED_100M, MG_PHY_SPEED_1000M }; @@ -2777,7 +3426,50 @@ uint8_t *speed); -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RA) && MG_ENABLE_DRIVER_RA +#if MG_ENABLE_TCPIP && MG_ARCH == MG_ARCH_PICOSDK && \ + defined(MG_ENABLE_DRIVER_PICO_W) && MG_ENABLE_DRIVER_PICO_W + +#include "cyw43.h" // keep this include +#include "pico/cyw43_arch.h" // keep this include +#include "pico/unique_id.h" // keep this include + +struct mg_tcpip_driver_pico_w_data { + struct mg_wifi_data wifi; +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_pico_w_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + MG_SET_WIFI_CONFIG(&driver_data_); \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_pico_w; \ + mif_.driver_data = &driver_data_; \ + mif_.recv_queue.size = 8192; \ + mif_.mac[0] = 2; /* MAC read from OTP at driver init */ \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: pico-w, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +struct mg_tcpip_driver_ppp_data { + void *uart; // Opaque UART bus descriptor + void (*reset)(void *); // Modem hardware reset + void (*tx)(void *, uint8_t); // UART transmit single byte + int (*rx)(void *); // UART receive single byte + const char **script; // List of AT commands and expected replies + int script_index; // Index of the current AT command in the list + uint64_t deadline; // AT command deadline in ms +}; + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_RA6) && MG_ENABLE_DRIVER_RA6) || \ + (defined(MG_ENABLE_DRIVER_RA8) && MG_ENABLE_DRIVER_RA8) struct mg_tcpip_driver_ra_data { // MDC clock "divider". MDC clock is software generated, @@ -2786,19 +3478,155 @@ uint8_t phy_addr; // PHY address }; +#ifndef MG_DRIVER_CLK_FREQ +#define MG_DRIVER_CLK_FREQ 100000000UL #endif +#ifndef MG_DRIVER_IRQ_NO +#define MG_DRIVER_IRQ_NO 0 +#endif -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54 +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif -struct mg_tcpip_driver_same54_data { - int mdc_cr; +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_ra_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.clock = MG_DRIVER_CLK_FREQ; \ + driver_data_.irqno = MG_DRIVER_IRQ_NO; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_ra; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: ra, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_RW612) && MG_ENABLE_DRIVER_RW612 + +struct mg_tcpip_driver_rw612_data { + // 38.1.8 MII Speed Control Register (MSCR) + // MDC clock frequency must not exceed 2.5 MHz and is calculated as follows: + // MDC_freq = P_clock / ((mdc_cr + 1) * 2), where P_clock is the + // peripheral bus clock. + // IEEE802.3 clause 22 defines a minimum of 10 ns for the hold time on the + // MDIO output. Depending on the host bus frequency, the setting may need + // to be increased. + int mdc_cr; + int mdc_holdtime; // Valid values: [0-7] + uint8_t phy_addr; }; +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 2 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 51 +#endif + +#ifndef MG_DRIVER_MDC_HOLDTIME +#define MG_DRIVER_MDC_HOLDTIME 3 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_rw612_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_rw612; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: rw612, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && \ + MG_ENABLE_DRIVER_SAME54 + #ifndef MG_DRIVER_MDC_CR #define MG_DRIVER_MDC_CR 5 #endif +#ifndef MG_DRIVER_PHY_ADDR +#define MG_DRIVER_PHY_ADDR 0 +#endif + +struct mg_tcpip_driver_same54_data { + int mdc_cr; + uint8_t phy_addr; +}; + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_same54_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_DRIVER_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_same54; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: same54, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// Specific chip/card driver --> SDIO driver --> HAL --> SDIO hw controller + +// API with HAL for hardware controller +// - Provide a function to init the controller (external) +// - Provide these functions: +struct mg_tcpip_sdio { + void *sdio; // Opaque SDIO bus descriptor + void (*cfg)(void *, uint8_t); // select operating parameters + // SDIO transaction: send cmd with a possible 1-byte read or write + bool (*txn)(void *, uint8_t cmd, uint32_t arg, uint32_t *r); + // SDIO extended transaction: write or read len bytes, using blksz blocks + bool (*xfr)(void *, bool write, uint32_t arg, uint16_t blksz, uint32_t *, + uint32_t len, uint32_t *r); +}; + +// API with driver (e.g.: cyw.c) +// Once the hardware controller has been initialized: +// - Init card: selects the card, sets F0 block size, sets bus width and speed +bool mg_sdio_init(struct mg_tcpip_sdio *sdio); +// - Enable other possible functions (F1 to F7) +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Wait for them to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Set their transfer block length +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz); +// - Transfer data to/from a function (abstracts from transaction type) +// - Requesting a read transfer > blocksize means block transfer will be used. +// - Drivers must have room to accomodate a whole block transfer, see sdio.c +// - Transfers of > 1 byte --> (uint32_t *) data. 1-byte --> (uint8_t *) data +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len); + #endif @@ -2830,6 +3658,12 @@ #define MG_DRIVER_MDC_CR 4 #endif +#if MG_ARCH == MG_ARCH_CUBE +#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn) +#else +#define MG_ENABLE_ETH_IRQ() +#endif + #define MG_TCPIP_DRIVER_INIT(mgr) \ do { \ static struct mg_tcpip_driver_stm32f_data driver_data_; \ @@ -2842,6 +3676,7 @@ mif_.driver = &mg_tcpip_driver_stm32f; \ mif_.driver_data = &driver_data_; \ MG_SET_MAC_ADDRESS(mif_.mac); \ + MG_ENABLE_ETH_IRQ(); \ mg_tcpip_init(mgr, &mif_); \ MG_INFO(("Driver: stm32f, MAC: %M", mg_print_mac, mif_.mac)); \ } while (0) @@ -2849,8 +3684,17 @@ #endif -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \ - MG_ENABLE_DRIVER_STM32H +#if MG_ENABLE_TCPIP +#if !defined(MG_ENABLE_DRIVER_STM32H) +#define MG_ENABLE_DRIVER_STM32H 0 +#endif +#if !defined(MG_ENABLE_DRIVER_MCXN) +#define MG_ENABLE_DRIVER_MCXN 0 +#endif +#if !defined(MG_ENABLE_DRIVER_STM32N) +#define MG_ENABLE_DRIVER_STM32N 0 +#endif +#if MG_ENABLE_DRIVER_STM32H || MG_ENABLE_DRIVER_MCXN || MG_ENABLE_DRIVER_STM32N struct mg_tcpip_driver_stm32h_data { // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz @@ -2863,13 +3707,18 @@ // 35-60 MHz HCLK/26 3 // 150-250 MHz HCLK/102 4 <-- value for max speed HSI // 250-300 MHz HCLK/124 5 <-- value for Nucleo-H* on CSI - // 110, 111 Reserved + // 300-500 MHz HCLK/204 6 + // 500-800 MHz HCLK/324 7 int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 uint8_t phy_addr; // PHY address uint8_t phy_conf; // PHY config }; +#ifndef MG_TCPIP_PHY_CONF +#define MG_TCPIP_PHY_CONF MG_PHY_CLOCKS_MAC +#endif + #ifndef MG_TCPIP_PHY_ADDR #define MG_TCPIP_PHY_ADDR 0 #endif @@ -2878,23 +3727,45 @@ #define MG_DRIVER_MDC_CR 4 #endif +#if MG_ENABLE_DRIVER_STM32H && MG_ARCH == MG_ARCH_CUBE +#define MG_ENABLE_ETH_IRQ() NVIC_EnableIRQ(ETH_IRQn) +#else +#define MG_ENABLE_ETH_IRQ() +#endif + +#if MG_ENABLE_IPV6 +#define MG_IPV6_INIT(mif) \ + do { \ + memcpy(mif.ip6ll, (uint8_t[16]) MG_TCPIP_IPV6_LINKLOCAL, 16); \ + memcpy(mif.ip6, (uint8_t[16]) MG_TCPIP_GLOBAL, 16); \ + memcpy(mif.gw6, (uint8_t[16]) MG_TCPIP_GW6, 16); \ + mif.prefix_len = MG_TCPIP_PREFIX_LEN; \ + } while(0) +#else +#define MG_IPV6_INIT(mif) +#endif + #define MG_TCPIP_DRIVER_INIT(mgr) \ do { \ static struct mg_tcpip_driver_stm32h_data driver_data_; \ static struct mg_tcpip_if mif_; \ driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + driver_data_.phy_conf = MG_TCPIP_PHY_CONF; \ mif_.ip = MG_TCPIP_IP; \ mif_.mask = MG_TCPIP_MASK; \ mif_.gw = MG_TCPIP_GW; \ mif_.driver = &mg_tcpip_driver_stm32h; \ mif_.driver_data = &driver_data_; \ MG_SET_MAC_ADDRESS(mif_.mac); \ + MG_IPV6_INIT(mif_); \ mg_tcpip_init(mgr, &mif_); \ + MG_ENABLE_ETH_IRQ(); \ MG_INFO(("Driver: stm32h, MAC: %M", mg_print_mac, mif_.mac)); \ } while (0) #endif +#endif #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C @@ -2916,17 +3787,57 @@ #define MG_DRIVER_MDC_CR 1 #endif +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_tm4c_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_tm4c; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: tm4c, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + #endif -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_W5500) && MG_ENABLE_DRIVER_W5500 +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TMS570) && MG_ENABLE_DRIVER_TMS570 +struct mg_tcpip_driver_tms570_data { + int mdc_cr; + int phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif -#undef MG_ENABLE_TCPIP_DRIVER_INIT -#define MG_ENABLE_TCPIP_DRIVER_INIT 0 +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 1 +#endif +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_tms570_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_tms570; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: tms570, MAC: %M", mg_print_mac, mif_.mac));\ + } while (0) #endif + #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7 struct mg_tcpip_driver_xmc7_data { diff -Nru swupdate-2024.12.1+dfsg/mongoose/mongoose_interface.c swupdate-2025.12+dfsg/mongoose/mongoose_interface.c --- swupdate-2024.12.1+dfsg/mongoose/mongoose_interface.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/mongoose/mongoose_interface.c 2025-12-03 11:32:03.000000000 +0000 @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -66,9 +67,10 @@ struct parent_connection_info { struct mg_mgr *mgr; - unsigned long conn_id; + unsigned long id; }; +static struct parent_connection_info conn_info = {0}; static bool run_postupdate; static unsigned int watchdog_conn = 0; static struct mg_http_serve_opts s_http_server_opts; @@ -239,7 +241,7 @@ mg_strcmp(auth_domain, mg_str(f_domain)) == 0) { /* Username and domain matched, check the password */ mg_mkmd5resp(method, uri, mg_str_s(f_ha1), nonce, nc, cnonce, qop, exp_resp); - return strncmp(response.buf, exp_resp, strlen(exp_resp)) == 0; + return strncasecmp(response.buf, exp_resp, strlen(exp_resp)) == 0; } } @@ -377,18 +379,16 @@ } } -static void broadcast(struct parent_connection_info *p, char *str) +static void broadcast(char *str) { - mg_wakeup(p->mgr, p->conn_id, str, strlen(str)); + if (conn_info.mgr && conn_info.id) { + mg_wakeup(conn_info.mgr, conn_info.id, str, strlen(str)); + } } -static void *broadcast_message_thread(void *data) +static void *broadcast_message_thread(void __attribute__ ((__unused__)) *data) { int fd = -1; - struct parent_connection_info *p = (struct parent_connection_info *) data; - - if(!p) - return NULL; for (;;) { ipc_message msg; @@ -424,24 +424,20 @@ level_to_rfc_5424(msg.data.notify.level), /* RFC 5424 */ text); - broadcast(p, str); + broadcast(str); } } - free(p); + return NULL; } -static void *broadcast_progress_thread(void *data) +static void *broadcast_progress_thread(void __attribute__ ((__unused__)) *data) { RECOVERY_STATUS status = -1; sourcetype source = -1; unsigned int step = 0; uint8_t percent = 0; int fd = -1; - struct parent_connection_info *p = (struct parent_connection_info *) data; - - if(!p) - return NULL; for (;;) { struct progress_msg msg; @@ -475,7 +471,7 @@ "\t\"status\": \"%s\"\r\n" "}\r\n", escaped); - broadcast(p, str); + broadcast(str); } if (msg.source != source) { @@ -487,7 +483,7 @@ "\t\"source\": \"%s\"\r\n" "}\r\n", get_source_string(msg.source)); - broadcast(p, str); + broadcast(str); } if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER && run_postupdate) { @@ -505,7 +501,7 @@ "\t\"source\": \"%s\"\r\n" "}\r\n", escaped); - broadcast(p, str); + broadcast(str); } if ((msg.cur_step != step || msg.cur_percent != percent) && @@ -527,10 +523,10 @@ msg.cur_step, escaped, msg.cur_percent); - broadcast(p, str); + broadcast(str); } } - free(p); + return NULL; } @@ -684,31 +680,29 @@ { static uint64_t last_io_time = 0; if (ev == MG_EV_OPEN && nc->is_listening) { - struct parent_connection_info *data = calloc(2, sizeof(struct parent_connection_info)); - if (!data) { - ERROR("Error: Memory allocation failed for parent_connection_info"); - return; - } - data[0].mgr = nc->mgr; - data[0].conn_id = nc->id; - memcpy(&data[1], &data[0], sizeof(struct parent_connection_info)); - start_thread(broadcast_message_thread, &data[0]); - start_thread(broadcast_progress_thread, &data[1]); + conn_info.mgr = nc->mgr; + conn_info.id = nc->id; + start_thread(broadcast_message_thread, NULL); + start_thread(broadcast_progress_thread, NULL); } else if (nc->data[0] != 'M' && nc->data[0] != 'W' && ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; if (!mg_http_is_authorized(hm, global_auth_domain, global_auth_file)) mg_http_send_digest_auth_request(nc, global_auth_domain); else if (mg_http_get_header(hm, "Sec-WebSocket-Key") != NULL) websocket_handler(nc, ev_data); - else if (mg_match(hm->uri, mg_str("/restart"), NULL)) + else if (mg_match(hm->uri, mg_str("#/restart"), NULL)) restart_handler(nc, ev_data); - else + else if (mg_match(hm->uri, mg_str("#/upload"), NULL)) { + nc->pfn = upload_handler; + nc->pfn_data = NULL; + multipart_upload_handler(nc, ev, hm); + } else mg_http_serve_dir(nc, ev_data, &s_http_server_opts); } else if (nc->data[0] != 'M' && nc->data[0] != 'W' && ev == MG_EV_READ) { struct mg_http_message hm; int hlen = mg_http_parse((char *) nc->recv.buf, nc->recv.len, &hm); if (hlen > 0) { - if (mg_match(hm.uri, mg_str("/upload"), NULL)) { + if (mg_match(hm.uri, mg_str("#/upload"), NULL)) { if (!mg_http_is_authorized(&hm, global_auth_domain, global_auth_file)) { if (nc->pfn != NULL) mg_http_send_digest_auth_request(nc, global_auth_domain); @@ -919,6 +913,12 @@ } } + if (optind < argc) { + ERROR("Non-option or unrecognized argument(s) given to webserver (-w), see --help."); + mongoose_print_help(); + return -EINVAL; + } + s_http_server_opts.root_dir = opts.root ? opts.root : MG_ROOT; if (!opts.listing) diff -Nru swupdate-2024.12.1+dfsg/mongoose/mongoose_multipart.c swupdate-2025.12+dfsg/mongoose/mongoose_multipart.c --- swupdate-2024.12.1+dfsg/mongoose/mongoose_multipart.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/mongoose/mongoose_multipart.c 2025-12-03 11:32:03.000000000 +0000 @@ -20,6 +20,7 @@ */ #include "mongoose_multipart.h" +#include enum mg_http_multipart_stream_state { MPS_BEGIN, @@ -203,7 +204,7 @@ (line_len = mg_get_line_len(block_begin, data_size)) != 0) { mp_stream->len -= (line_len + 2); if (line_len > sizeof(CONTENT_DISPOSITION) && - strncmp(block_begin, CONTENT_DISPOSITION, + strncasecmp(block_begin, CONTENT_DISPOSITION, sizeof(CONTENT_DISPOSITION) - 1) == 0) { struct mg_str header; @@ -219,7 +220,7 @@ continue; } - if (line_len == 2 && strncmp(block_begin, "\r\n", 2) == 0) { + if (line_len == 2 && strncasecmp(block_begin, "\r\n", 2) == 0) { if (mp_stream->processing_part != 0) { mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0); } @@ -323,7 +324,7 @@ struct mg_str *s; if (mp_stream != NULL && mp_stream->boundary.len != 0) { - if (ev == MG_EV_READ || (ev == MG_EV_POLL && mp_stream->data_avail)) { + if (ev == MG_EV_READ || ev == MG_EV_HTTP_MSG || (ev == MG_EV_POLL && mp_stream->data_avail)) { mg_http_multipart_continue(c); } else if (ev == MG_EV_CLOSE) { /* @@ -336,7 +337,7 @@ return; } - if (ev == MG_EV_READ) { + if (ev == MG_EV_READ || ev == MG_EV_HTTP_MSG) { if(mg_strcasecmp(hm->method, mg_str("POST")) != 0) { mg_http_reply(c, 405, "", "%s", "Method Not Allowed\n"); c->is_draining = 1; diff -Nru swupdate-2024.12.1+dfsg/parser/Kconfig swupdate-2025.12+dfsg/parser/Kconfig --- swupdate-2024.12.1+dfsg/parser/Kconfig 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/parser/Kconfig 2025-12-03 11:32:03.000000000 +0000 @@ -7,14 +7,10 @@ config LIBCONFIG bool "libconfig" default y - depends on HAVE_LIBCONFIG help Use libconfig to parse the configuration file. This is the default parser. -comment "Default config parser support needs libconfig" - depends on !HAVE_LIBCONFIG - config PARSERROOT string "Name of root node" help diff -Nru swupdate-2024.12.1+dfsg/parser/parse_external.c swupdate-2025.12+dfsg/parser/parse_external.c --- swupdate-2024.12.1+dfsg/parser/parse_external.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/parser/parse_external.c 2025-12-03 11:32:03.000000000 +0000 @@ -87,6 +87,8 @@ if (value != NULL) { if (!strcmp(value, "zlib")) { img->compressed = COMPRESSED_ZLIB; + } else if (!strcmp(value, "xz")) { + img->compressed = COMPRESSED_XZ; } else if (!strcmp(value, "zstd")) { img->compressed = COMPRESSED_ZSTD; } else { diff -Nru swupdate-2024.12.1+dfsg/parser/parser.c swupdate-2025.12+dfsg/parser/parser.c --- swupdate-2024.12.1+dfsg/parser/parser.c 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/parser/parser.c 2025-12-03 11:32:03.000000000 +0000 @@ -24,6 +24,7 @@ #include "parselib.h" #include "parsers.h" #include "swupdate_dict.h" +#include "swupdate_aes.h" #include "lua_util.h" #define MODULE_NAME "PARSER" @@ -132,7 +133,6 @@ { const char *ref; void *link; - char **tmp = NULL; /* to store temporary link path */ const char **linknodes; int result = 0; @@ -144,7 +144,7 @@ for (int j = 0; j < count_string_array(nodes); j++) { linknodes[j] = nodes[j]; } - if (!set_find_path(linknodes, ref, &tmp)) { + if (!set_find_path(linknodes, ref)) { free(linknodes); return -1; } @@ -153,7 +153,6 @@ if (link) { result = fn(p, cfg, link, linknodes, swcfg, L); } - free_string_array(tmp); free(linknodes); return result; } @@ -190,6 +189,13 @@ TRACE("Description %s", swcfg->description); } + swcfg->update_type_name[0] = '\0'; + + if((setting = find_node(p, cfg, "update-type", swcfg)) != NULL) { + GET_FIELD_STRING(p, setting, NULL, swcfg->update_type_name); + TRACE("Update Type set to %s", swcfg->update_type_name); + } + if(swcfg->no_state_marker) { swcfg->bootloader_state_marker = false; } else { @@ -215,12 +221,29 @@ /* * As default, reboot is initiated */ - swcfg->reboot_required = true; + swcfg->reboot_enabled = REBOOT_UNSET; if((setting = find_node(p, cfg, "reboot", swcfg)) != NULL) { - GET_FIELD_BOOL(p, setting, NULL, &swcfg->reboot_required); + if (is_field_bool(p, setting, NULL)) { + bool must_reboot = true; + GET_FIELD_BOOL(p, setting, NULL, &must_reboot); + swcfg->reboot_enabled = must_reboot ? REBOOT_ENABLED : REBOOT_DISABLED; + } else if (is_field_string(p, setting, NULL)) { + char tmp[SWUPDATE_GENERAL_STRING_SIZE] = ""; + GET_FIELD_STRING(p, setting, NULL, tmp); + + if (tmp[0] != '\0') { + if (!is_enabled_or_disabled(tmp)) { + WARN("Possible mismatch for reboot attribute, it is neither enabled nor disabled"); + } else if (is_enabled(tmp)) { + swcfg->reboot_enabled = REBOOT_ENABLED; + } else { + swcfg->reboot_enabled = REBOOT_DISABLED; + } + } + } } - TRACE("reboot_required %d", swcfg->reboot_required); + TRACE("reboot_enabled %d", swcfg->reboot_enabled != REBOOT_DISABLED); /* * Check if SWU should be cached @@ -398,10 +421,48 @@ return lua_parser_fn(L, embfcn, img); } +static void get_ivt_value(parsertype p, void *elem, char *ivt_ascii) +{ + size_t ivtlen; + const char *s = NULL; + + s = get_field_string(p, elem, "ivt"); + if (s) { + ivtlen = strnlen(s, SWUPDATE_GENERAL_STRING_SIZE); + if (ivtlen != (AES_BLK_SIZE * 2)) { + ERROR("Invalid ivt length"); + return; + } + strncpy(ivt_ascii, s, ivtlen); + } +} + +static void get_aes_value(parsertype p, void *elem, char *aes_ascii) +{ + size_t keylen; + const char *s = NULL; + s = get_field_string(p, elem, "aes-key"); + if (s) { + keylen = strnlen(s, SWUPDATE_GENERAL_STRING_SIZE); + + switch (keylen) { + case AES_128_KEY_LEN * 2: + case AES_192_KEY_LEN * 2: + case AES_256_KEY_LEN * 2: + // valid hex string size for AES 128/192/256 + break; + default: + ERROR("Invalid aes-key length"); + return; + } + get_field_string_with_size(p, elem, "aes-key", aes_ascii, keylen); + } +} + static int parse_common_attributes(parsertype p, void *elem, struct img_type *image, struct swupdate_cfg *cfg) { char seek_str[MAX_SEEK_STRING_SIZE]; - const char* compressed; + const char *compressed, *encrypted; unsigned long offset = 0; /* @@ -443,6 +504,8 @@ if ((compressed = get_field_string(p, elem, "compressed")) != NULL) { if (!strcmp(compressed, "zlib")) { image->compressed = COMPRESSED_ZLIB; + } else if (!strcmp(compressed, "xz")) { + image->compressed = COMPRESSED_XZ; } else if (!strcmp(compressed, "zstd")) { image->compressed = COMPRESSED_ZSTD; } else { @@ -458,8 +521,16 @@ GET_FIELD_BOOL(p, elem, "preserve-attributes", &image->preserve_attributes); GET_FIELD_BOOL(p, elem, "install-if-different", &image->id.install_if_different); GET_FIELD_BOOL(p, elem, "install-if-higher", &image->id.install_if_higher); - GET_FIELD_BOOL(p, elem, "encrypted", &image->is_encrypted); - GET_FIELD_STRING(p, elem, "ivt", image->ivt_ascii); + + if ((encrypted = get_field_string(p, elem, "encrypted")) != NULL) { + image->is_encrypted = true; + image->cipher = map_name_cipher(encrypted); + } else { + GET_FIELD_BOOL(p, elem, "encrypted", &image->is_encrypted); + image->cipher = AES_CBC; + } + get_ivt_value(p, elem, image->ivt_ascii); + get_aes_value(p, elem, image->aes_ascii); if (is_image_installed(&cfg->installed_sw_list, image)) { image->skip = SKIP_SAME; @@ -1044,7 +1115,6 @@ return ret; } -#ifdef CONFIG_LIBCONFIG int parse_cfg(struct swupdate_cfg *swcfg, const char *filename, char **error) { config_t cfg; @@ -1075,14 +1145,6 @@ return ret; } -#else -int parse_cfg(struct swupdate_cfg __attribute__((__unused__)) *swcfg, - const char __attribute__((__unused__)) *filename, - char __attribute__((__unused__)) **error) -{ - return -1; -} -#endif #define JSON_OBJECT_FREED 1 diff -Nru swupdate-2024.12.1+dfsg/scripts/Kbuild.src swupdate-2025.12+dfsg/scripts/Kbuild.src --- swupdate-2024.12.1+dfsg/scripts/Kbuild.src 2025-01-22 15:34:06.000000000 +0000 +++ swupdate-2025.12+dfsg/scripts/Kbuild.src 2025-12-03 11:32:03.000000000 +0000 @@ -4,4 +4,4 @@ # --------------------------------------------------------------------------- # Let clean descend into subdirs -subdir- += basic kconfig +subdir- += basic diff -Nru swupdate-2024.12.1+dfsg/scripts/Kconfiglib/README.md swupdate-2025.12+dfsg/scripts/Kconfiglib/README.md --- swupdate-2024.12.1+dfsg/scripts/Kconfiglib/README.md 1970-01-01 00:00:00.000000000 +0000 +++ swupdate-2025.12+dfsg/scripts/Kconfiglib/README.md 2025-12-03 11:32:03.000000000 +0000 @@ -0,0 +1,21 @@ + + +# Kconfiglib + +This directory contains the Python Kconfig library Kconfiglib. + +The library is maintained by the Zephyr project at + + +## Update instructions + +Just copy all `kconfiglib.py` and the scripts `*config.py` script we need from the official repository to this directory. +Then, apply commit [4160407](https://github.com/sbabic/swupdate/commit/4160407e1e0dbe292ead72dfce5ea347410a4212) to prevent serializing Buildroot environment variables (`HAVE_