├── .editorconfig ├── .github └── workflows │ ├── artifact-release.yml │ ├── lint-commits.yml │ ├── release-please.yml │ └── softsim-build-using-docker.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── Kconfig ├── README.md ├── boards ├── thingy91_pm_static.yml └── thingy91x_nrf9151_pm_static.yml ├── lib ├── CMakeLists.txt ├── build_asserts.c ├── include │ ├── nrf_softsim.h │ └── onomondo │ │ └── softsim │ │ ├── file.h │ │ ├── fs_port.h │ │ ├── list.h │ │ ├── log.h │ │ ├── mem.h │ │ ├── softsim.h │ │ ├── storage.h │ │ └── utils.h ├── libcrypto.a ├── libmilenage.a ├── libstorage.a ├── libuicc.a ├── nrf_softsim.c ├── profile │ └── template.bin ├── ss_cache.c ├── ss_cache.h ├── ss_crypto.c ├── ss_crypto.h ├── ss_fs.c ├── ss_heap.c ├── ss_profile.c ├── ss_profile.h ├── ss_provision.c └── ss_provision.h ├── overlay-softsim.conf ├── samples ├── softsim_external_profile │ ├── CMakeLists.txt │ ├── pm_static.yml │ ├── prj.conf │ ├── src │ │ └── main.c │ └── sysbuild.conf └── softsim_static_profile │ ├── CMakeLists.txt │ ├── pm_static.yml │ ├── prj.conf │ ├── src │ └── main.c │ └── sysbuild.conf ├── sysbuild ├── CMakeLists.txt └── Kconfig ├── west.yml └── zephyr └── module.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # All (Defaults) 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | # Assembly 12 | [*.S] 13 | indent_style = tab 14 | indent_size = 8 15 | 16 | # C 17 | [*.{c,h}] 18 | indent_style = tab 19 | indent_size = 8 20 | 21 | # C++ 22 | [*.{cpp,hpp}] 23 | indent_style = tab 24 | indent_size = 8 25 | 26 | # YAML 27 | [*.{yml,yaml}] 28 | indent_style = space 29 | indent_size = 2 30 | 31 | # CMake 32 | [{CMakeLists.txt,*.cmake}] 33 | indent_style = space 34 | indent_size = 2 35 | -------------------------------------------------------------------------------- /.github/workflows/artifact-release.yml: -------------------------------------------------------------------------------- 1 | name: Release Artifacts 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | description: 'Release tag' 8 | required: true 9 | default: v5.0.0 10 | run_id: 11 | description: 'Run ID' 12 | required: true 13 | default: '13908029974' 14 | 15 | jobs: 16 | release-artifacts: 17 | runs-on: ubuntu-24.04 18 | timeout-minutes: 30 19 | 20 | strategy: 21 | matrix: 22 | board: [nrf9160dk/nrf9160/ns, nrf9161dk/nrf9161/ns, nrf9151dk/nrf9151/ns, thingy91/nrf9160/ns, thingy91x/nrf9151/ns] 23 | 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Artifact name generator 31 | id: artifact_name 32 | run: echo "artifact_name=$(echo ${{ matrix.board }} | tr "/" "_")" >> $GITHUB_OUTPUT 33 | 34 | - name: Download Build Artifacts 35 | uses: actions/download-artifact@v4 36 | with: 37 | name: ${{ steps.artifact_name.outputs.artifact_name }} 38 | run-id: ${{ github.event.inputs.run_id }} 39 | github-token: ${{ secrets.GITHUB_TOKEN }} 40 | 41 | - name: Upload Release Artifact 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | run: | 45 | zip -v ${{ steps.artifact_name.outputs.artifact_name }}.zip merged.hex 46 | gh release upload ${{ github.event.inputs.release_tag }} ${{ steps.artifact_name.outputs.artifact_name }}.zip 47 | -------------------------------------------------------------------------------- /.github/workflows/lint-commits.yml: -------------------------------------------------------------------------------- 1 | name: Lint Commit Messages 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | commitlint: 7 | runs-on: ubuntu-24.04 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | 15 | - name: Linting commit messages 16 | uses: wagoid/commitlint-github-action@v5 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: Release Please 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | jobs: 13 | create-release: 14 | runs-on: ubuntu-24.04 15 | timeout-minutes: 30 16 | 17 | steps: 18 | - uses: googleapis/release-please-action@v4 19 | id: release 20 | with: 21 | release-type: simple 22 | target-branch: master 23 | -------------------------------------------------------------------------------- /.github/workflows/softsim-build-using-docker.yml: -------------------------------------------------------------------------------- 1 | name: SoftSIM for nRF91 Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build-in-docker-container: 15 | runs-on: ubuntu-22.04 16 | timeout-minutes: 60 17 | container: ghcr.io/nrfconnect/sdk-nrf-toolchain:v2.9.1 18 | defaults: 19 | run: 20 | # Bash shell is needed to set toolchain related environment variables in docker container 21 | # It is a workaround for GitHub Actions limitation https://github.com/actions/runner/issues/1964 22 | shell: bash 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | board: [nrf9160dk/nrf9160/ns, nrf9161dk/nrf9161/ns, nrf9151dk/nrf9151/ns, thingy91/nrf9160/ns, thingy91x/nrf9151/ns] 28 | 29 | steps: 30 | # Initialize a Zephyr West workspace in its default format 31 | - name: Initialize workspace 32 | run: | 33 | west init -m https://github.com/${GITHUB_REPOSITORY} 34 | 35 | # Ensure that the checkout follows only this build sha and not the master branch 36 | - name: Checkout 37 | uses: actions/checkout@v4 38 | with: 39 | path: modules/lib/onomondo-softsim 40 | 41 | # Update dependencies and submodules using west 42 | - name: Fetch nRF and Zephyr workspaces and dependencies 43 | run: | 44 | west update -o=--depth=1 --narrow 45 | 46 | - name: Build firmware of static profile sample 47 | working-directory: modules/lib/onomondo-softsim/samples/softsim_static_profile 48 | run: | 49 | west build --sysbuild --pristine=always --board ${{ matrix.board }} 50 | 51 | - name: Build firmware of external profile sample 52 | working-directory: modules/lib/onomondo-softsim/samples/softsim_external_profile 53 | run: | 54 | west build --sysbuild --pristine=always --board ${{ matrix.board }} 55 | 56 | - name: Archive name generator 57 | id: artifact_name 58 | run: echo "artifact_name=$(echo ${{ matrix.board }} | tr "/" "_")" >> $GITHUB_OUTPUT 59 | 60 | - name: Archive SoftSIM External Profile Build 61 | uses: actions/upload-artifact@v4 62 | with: 63 | name: ${{ steps.artifact_name.outputs.artifact_name }} 64 | path: modules/lib/onomondo-softsim/samples/softsim_external_profile/build/merged.hex 65 | overwrite: true 66 | if-no-files-found: warn 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | build 55 | 56 | lib/onomondo 57 | .aconf 58 | .sh 59 | 60 | !lib/libcrypto.a 61 | !lib/libmilenage.a 62 | !lib/libuicc.a 63 | !lib/libstorage.a 64 | 65 | .vscode 66 | *debug* 67 | .DS_Store 68 | 69 | commitlint.config.js 70 | .husky 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [5.1.0](https://github.com/onomondo/nrf-softsim/compare/v5.0.0...v5.1.0) (2025-04-02) 4 | 5 | 6 | ### Features 7 | 8 | * optimize FLASH and RAM ([#80](https://github.com/onomondo/nrf-softsim/issues/80)) ([a92db60](https://github.com/onomondo/nrf-softsim/commit/a92db60fc3be34e3ce56e6b9ed8f8555f0799afe)) 9 | 10 | ## [5.0.0](https://github.com/onomondo/nrf-softsim/compare/v4.0.2...v5.0.0) (2025-03-19) 11 | 12 | 13 | ### ⚠ BREAKING CHANGES 14 | 15 | * bump nrf sdk support from v2.6.2 to v2.9.1 ([#56](https://github.com/onomondo/nrf-softsim/issues/56)) 16 | 17 | ### Features 18 | 19 | * bump nrf sdk support from v2.6.2 to v2.9.1 ([#56](https://github.com/onomondo/nrf-softsim/issues/56)) ([a393f70](https://github.com/onomondo/nrf-softsim/commit/a393f70a2742fa22419f11ccbd41d4ef998c6a07)) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * remove the fixed 190 character profile size ([#61](https://github.com/onomondo/nrf-softsim/issues/61)) ([2eaa8e0](https://github.com/onomondo/nrf-softsim/commit/2eaa8e0e76c1a70ae56b79ffc1896ff70d9dce44)) 25 | 26 | ## [4.0.2](https://github.com/onomondo/nrf-softsim/compare/v4.0.1...v4.0.2) (2024-07-09) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * improve support for Thingy91 ([#50](https://github.com/onomondo/nrf-softsim/issues/50)) ([f7cc739](https://github.com/onomondo/nrf-softsim/commit/f7cc739ba261ada0df4f551d0f2af87a37897e1e)) 32 | 33 | ## [4.0.1](https://github.com/onomondo/nrf-softsim/compare/v4.0.0...v4.0.1) (2024-06-05) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * softsim filename updates ([#44](https://github.com/onomondo/nrf-softsim/issues/44)) ([ac91364](https://github.com/onomondo/nrf-softsim/commit/ac9136409f2a1d4991685ff57bad014f24f4a472)) 39 | 40 | ## [4.0.0](https://github.com/onomondo/nrf-softsim/compare/v3.0.1...v4.0.0) (2024-04-30) 41 | 42 | 43 | ### ⚠ BREAKING CHANGES 44 | 45 | * bump to ncs 2.6.1 (CEL-138) ([#42](https://github.com/onomondo/nrf-softsim/issues/42)) 46 | 47 | ### Features 48 | 49 | * bump to ncs 2.6.1 (CEL-138) ([#42](https://github.com/onomondo/nrf-softsim/issues/42)) ([4d0e56b](https://github.com/onomondo/nrf-softsim/commit/4d0e56b9a7c4348b9a09640695d5383232ec5e48)) 50 | 51 | ## [3.0.1](https://github.com/onomondo/nrf-softsim/compare/v3.0.0...v3.0.1) (2024-02-13) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * auto append softsim overlay fragment to the samples ([#36](https://github.com/onomondo/nrf-softsim/issues/36)) ([e8230c7](https://github.com/onomondo/nrf-softsim/commit/e8230c7d416b66624231a0d87065f536d81be439)) 57 | 58 | ## [3.0.0](https://github.com/onomondo/nrf-softsim/compare/v2.2.2...v3.0.0) (2024-02-08) 59 | 60 | 61 | ### ⚠ BREAKING CHANGES 62 | 63 | * tfm reference implementation flash usage optimization ([#32](https://github.com/onomondo/nrf-softsim/issues/32)) 64 | 65 | ### Features 66 | 67 | * tfm reference implementation flash usage optimization ([#32](https://github.com/onomondo/nrf-softsim/issues/32)) ([00576ac](https://github.com/onomondo/nrf-softsim/commit/00576ac9421b5362ee5430572db401fea28d664f)) 68 | 69 | ## [2.2.2](https://github.com/onomondo/nrf-softsim/compare/v2.2.1...v2.2.2) (2024-01-30) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * disable TFM logs to reduce PSM Sleep Current ([c202fe4](https://github.com/onomondo/nrf-softsim/commit/c202fe4c772fc44a11704160492e5d248fc40656)) 75 | 76 | ## [2.2.1](https://github.com/onomondo/nrf-softsim/compare/v2.2.0...v2.2.1) (2023-12-14) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * 🐛 Heap corrupted by de-initializing softsim many times ([00bcdab](https://github.com/onomondo/nrf-softsim/commit/00bcdab2ee12965ca9cd690b2bb77c76bfca034c)) 82 | 83 | ## [2.2.0](https://github.com/onomondo/nrf-softsim/compare/v2.1.0...v2.2.0) (2023-11-14) 84 | 85 | 86 | ### Features 87 | 88 | * add missed Zephyr compatible asserts ([09be236](https://github.com/onomondo/nrf-softsim/commit/09be2366a1a26e4dfbaabc636b66aa5460dfeb7a)) 89 | 90 | 91 | ### Bug Fixes 92 | 93 | * address compilation warning for `main` return ([1a348e8](https://github.com/onomondo/nrf-softsim/commit/1a348e8d52b8611e8cd24d9a07b9bee10637148a)) 94 | * address provision check inconsistencies ([9a28958](https://github.com/onomondo/nrf-softsim/commit/9a289585e8c6188295bdab5ab36921517555102b)) 95 | * samples: change TCP to UDP ([49c9974](https://github.com/onomondo/nrf-softsim/commit/49c9974c82ed6a6f0435c12cf045d77da67c33f1)) 96 | 97 | ## [2.1.0](https://github.com/onomondo/nrf-softsim/compare/v2.0.0...v2.1.0) (2023-11-06) 98 | 99 | 100 | ### Features 101 | 102 | * make asserts Zephyr compatible ([#12](https://github.com/onomondo/nrf-softsim/issues/12)) ([bfc607b](https://github.com/onomondo/nrf-softsim/commit/bfc607b404112174002397b504b4541c970922e6)) 103 | 104 | ## [2.0.0](https://github.com/onomondo/nrf-softsim/compare/v1.0.0...v2.0.0) (2023-10-10) 105 | 106 | 107 | ### ⚠ BREAKING CHANGES 108 | 109 | * 🧨 nrf-sdk main branch is used. When v2.5 is released a tag/release for nrf-softsim is created. For now this pin to latest dev. can break your current setup. 110 | 111 | ### Features 112 | 113 | * 🎸 Follow nrf-sdk main branch as default ([711615c](https://github.com/onomondo/nrf-softsim/commit/711615c7a248352f79a04dcb9c906d175182a26c)) 114 | 115 | ## 1.0.0 (2023-09-29) 116 | 117 | 118 | ### ⚠ BREAKING CHANGES 119 | 120 | * 🧨 ALL PREVIOS DEPLOYMENTS WILL NOT BE COMPATIBLE WITH THE LIBSTORAGE.a AND NEW PROFILE! 121 | * NCS 2.4 integration ([#1](https://github.com/onomondo/nrf-softsim/issues/1)) 122 | * Use KMU for authentication and key management. 123 | 124 | ### Features 125 | 126 | * 🎸 Switch profile to compact storage ([b382ecb](https://github.com/onomondo/nrf-softsim/commit/b382ecb72f9c10bb433960b9c54779a6d0030560)) 127 | * 🎸 Wow a meaningful commit ([#9](https://github.com/onomondo/nrf-softsim/issues/9)) ([e03cb18](https://github.com/onomondo/nrf-softsim/commit/e03cb18a9dd7eb072309729857851411a94bcfa5)) 128 | * Add support for dynamic template ([#4](https://github.com/onomondo/nrf-softsim/issues/4)) ([ddfac8a](https://github.com/onomondo/nrf-softsim/commit/ddfac8a3155a0dfb02a192985712110afde42afa)) 129 | * add support for static profile ([#3](https://github.com/onomondo/nrf-softsim/issues/3)) ([aca75ad](https://github.com/onomondo/nrf-softsim/commit/aca75ad8865e805269857bf4fda6db086948e02f)) 130 | * Additional features for provisioning and init bootstrap ([8eb3289](https://github.com/onomondo/nrf-softsim/commit/8eb3289b8b105dc50a57e47e70d5ed7dd1100188)) 131 | * NCS 2.4 integration ([#1](https://github.com/onomondo/nrf-softsim/issues/1)) ([dfb7864](https://github.com/onomondo/nrf-softsim/commit/dfb78649acbbc4269ec7327c88a662768aca7dca)) 132 | * Use KMU for authentication and key management. ([ffb197a](https://github.com/onomondo/nrf-softsim/commit/ffb197a6a8ca17df65dfd6bf3c292f50d2bf4f89)) 133 | 134 | 135 | ### Bug Fixes 136 | 137 | * 🐛 Add return code to provision ([8eb3289](https://github.com/onomondo/nrf-softsim/commit/8eb3289b8b105dc50a57e47e70d5ed7dd1100188)) 138 | * 🐛 Fixed issue where SoftSIM re-init would fail ([5df72b4](https://github.com/onomondo/nrf-softsim/commit/5df72b4106821eb63f516f87cbbb616a2cb3ac57)) 139 | * Bug/fix padding in cmac ota ([#5](https://github.com/onomondo/nrf-softsim/issues/5)) ([830259d](https://github.com/onomondo/nrf-softsim/commit/830259d2a5e3ed7d830a2da8f12404eca261fd2e)) 140 | 141 | 142 | ### Performance Improvements 143 | 144 | * ⚡️ Recompile w. -Os over -O2/3 ([5319011](https://github.com/onomondo/nrf-softsim/commit/5319011de8e641b68f16b9f52e2be9d9bd657b31)) 145 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory_ifdef(CONFIG_SOFTSIM lib) 2 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | menu "Onomondo SoftSIM Options" 2 | 3 | config SOFTSIM 4 | bool "SoftSIM" 5 | depends on NVS 6 | depends on FLASH 7 | depends on FLASH_MAP 8 | depends on FLASH_PAGE_LAYOUT 9 | depends on MPU_ALLOW_FLASH_WRITE 10 | depends on BUILD_WITH_TFM 11 | 12 | if SOFTSIM 13 | 14 | config SOFTSIM_AUTO_INIT 15 | bool "Automatically initialize SoftSIM" 16 | default y 17 | help 18 | Automatically initialize SoftSIM when the modem is powered on. 19 | 20 | config SOFTSIM_DEBUG 21 | bool "Enable debug messages for SoftSIM" 22 | default n 23 | 24 | config SOFTSIM_STATIC_PROFILE_ENABLE 25 | bool "Enable static profile" 26 | default n 27 | help 28 | Enable static profile to use for SoftSIM. Developement only. 29 | 30 | config SOFTSIM_STATIC_PROFILE 31 | string "Static profile" 32 | depends on SOFTSIM_STATIC_PROFILE_ENABLE 33 | help 34 | Static profile to use for SoftSIM. If empty, the profile should be passed during runtime (just once via nrf_softsim_provision) 35 | 36 | endif #SOFTSIM 37 | 38 | endmenu 39 | 40 | module = SOFTSIM 41 | module-str = Onomondo-SoftSIM 42 | source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Onomondo SoftSIM for Nordic nRF91 Series 2 | 3 | The Onomondo SoftSIM is an [Open Source](https://github.com/onomondo/onomondo-uicc) C based UICC implementation, allowing new and innovative cellular device designs to see the light of day in the ever-growing landscape of IoT! 4 | 5 | To integrate this awesome new SoftSIM UICC form factor, we have partnered with Nordic Semiconductor to develop and distribute a new SoftSIM modem interface that supports APDU exchange between the modem and the application processor. For more details and an in-depth explenation, refer to Nordic Semiconductor's [documentation](https://docs.nordicsemi.com/bundle/ncs-latest/page/nrfxlib/nrf_modem/doc/softsim_interface.html). 6 | 7 | 8 | ## Quick Setup Guide 9 | 10 | The Onomondo SoftSIM samples for nRF91 Series SiP's can be imported as a Zephyr module within the [nRF Connect SDK](https://www.nordicsemi.com/Products/Development-software/nrf-connect-sdk). 11 | 12 | A new SDK can be initiated with the following two commands if you are already a user of west and nrf: 13 | 14 | ``` 15 | west init -m https://github.com/onomondo/nrf-softsim.git 16 | west update 17 | ``` 18 | 19 | Getting started with the external profile sample: 20 | ``` 21 | cd modules/lib/onomondo-softsim/samples/softsim_external_profile 22 | west build --sysbuild -b nrf9151dk/nrf9151/ns 23 | west flash 24 | ``` 25 | 26 | > [!IMPORTANT] 27 | > This repository have transitioned to Zephyr Sysbuild. 28 | > 29 | > Please ensure that West is configured accordingly or that you build the project using the --sysbuild argument. 30 | > 31 | > Feel free to raise an issue or contribute to this repository if you experience any issue with the migration to Sysbuild. 32 | 33 | ## Prerequisites 34 | ### Get access to your free Onomondo SoftSIM profile 35 | SoftSIM profiles are delivered through our API. As this can be a bit cumbersome, we've developed a small tool to make this process easier. The tool is available at [sofsim-cli](https://github.com/onomondo/onomondo-softsim-cli). Additional instructions can be found in the CLI repository. 36 | 37 | 1. Generate an API key on [app.onomondo.com/api-keys](https://app.onomondo.com/api-keys). Follow the instructions on the app. 38 | 2. Download the `softsim` cli tool for your platform. 39 | 3. Fetch your profile: `./softsim fetch --api-key = -n 1`. This will create a `profiles` directory for you with `1` encrypted profile. 40 | 41 | Every time you require a new profile, simply use the `./softsim next --key=`. It will look in the `./profiles` folder and decrypt and format a profile. _This command guarantees that a new profile is given each time._ 42 | 43 | 44 | ## General Setup Guide 45 | ### Table of Contents 46 | #### Quickstart 47 | 1. [Configure NCS to include SoftSIM libraries in your build system](#setup) 48 | 2. [Set-up your API key to get access to SoftSIM profiles through our API](#get-access-to-your-free-softsim-profiles) 49 | 3. [Configure your project to build with SoftSIM](#configure-and-build) 50 | 4. [Configuring SoftSIM in NCS samples](#general-usage) 51 | 52 | #### General 53 | - [Building and running](#building-flashing-and-running) 54 | - [SoftSIM and physical SIM selection](#software-sim-selection) 55 | - [Understanding the SIM - why SoftSIM is possible](#understanding-the-sim---why-softsim-is-possible) 56 | - [Details on provisioning](#provisioning) 57 | - [Details on kConfig options](#kconfig-options) 58 | 59 | ### Getting Started 60 | 61 | For existing toolchains and build systems it is sufficient to update the manifest to point to `west.yml` inside this repository: 62 | 63 | ``` 64 | cd 65 | git clone https://github.com/onomondo/nrf-softsim.git modules/lib/onomondo-softsim 66 | west config manifest.path modules/lib/onomondo-softsim/ 67 | west update 68 | ``` 69 | 70 | First time setting it up? We recommend using the [nRF Connect for Desktop](https://www.nordicsemi.com/Products/Development-tools/nrf-connect-for-desktop) to get the build system correctly set up. Once done, configure the manifest as described above. 71 | 72 | 73 | Bonus tip: The Toolchain Manager allows you to easily generate the correct environment variables. Click the small arrow and select `Generate environment script`. The output file contains everything you need to set up the new toolchain. 74 | 75 | Your folder structure should look something like: 76 | ``` 77 | ncs 78 | |___ .west 79 | |___ nrf 80 | |___ nrfxlib 81 | |___ modules 82 | |___lib 83 | |___onomondo-softsim 84 | |___ ... 85 | |___ ... 86 | |___ zephyr 87 | |___ ... 88 | ``` 89 | 90 | ### Configure and Build 91 | 92 | There are currently two samples included in this project to showcase how a SoftSIM profile can be provisioned. It is recommended to start with the *static profile* sample. 93 | 94 | #### Static profile sample 95 | `samples/softsim_static_profile` will provision a profile during the first system initialization. The profile is configured in the `prj.conf` 96 | 97 | In the `prj.conf` you'll find the following options related to the SoftSIM statically provisioned profile. This setup is useful for development, as the profile doesn't have to be re-provisioned every time the device is flashed. 98 | 99 | ``` 100 | CONFIG_SOFTSIM_STATIC_PROFILE_ENABLE=y 101 | CONFIG_SOFTSIM_STATIC_PROFILE="011208091..." 102 | ``` 103 | 104 | Run `./softsim next --key=~/myPrivateKey` (with path to your private key) and grab the output. By default it formats the profile to be accepted by any nRF91 series devices. The profile will look similar to `01120...`. Replace the `CONFIG_SOFTSIM_STATIC_PROFILE` value with your SoftSIM profile. 105 | 106 | 107 | Build and flash the sample and the device will attach and send data right away. 108 | 109 | ``` 110 | west build 111 | west flash 112 | ``` 113 | 114 | #### External profile sample 115 | `samples/softsim_external_profile` will wait for a profile supplied via UART. After receiving it will be provisioned and the device will reboot to free up the UART port for AT commands. 116 | 117 | ``` 118 | echo "" > /dev/tty.usbmodem 119 | ``` 120 | 121 | Which results in: 122 | ``` 123 | *** Booting Zephyr OS build v3.2.99-ncs1 *** 124 | [00:00:00.610,198] softsim_sample: SoftSIM sample started. 125 | [00:00:00.610,656] softsim_sample: Transfer SoftSIM profile using serial COM port, terminate by newline character (return key) 126 | *** Booting Zephyr OS build v3.2.99-ncs1 *** 127 | [00:00:00.555,664] softsim_sample: SoftSIM sample started. 128 | [00:00:00.615,875] softsim_sample: Waiting for LTE connect event. 129 | [00:00:00.744,140] softsim_sample: LTE cell changed: Cell ID: -1, Tracking area: -1 130 | [00:00:01.185,760] softsim_sample: LTE cell changed: Cell ID: 13358642, Tracking area: 2000 131 | [00:00:01.308,349] softsim_sample: RRC mode: Connected``` 132 | +CEREG: 5,"07D0","00CBD632",7,,,"00100011","11100000" 133 | [00:00:07.096,221] softsim_sample: Network registration status: Connected - roaming 134 | [00:00:07.096,405] softsim_sample: LTE connected! 135 | ``` 136 | ### General usage 137 | 138 | For most samples and applications, it's sufficient to build by executing the following command: 139 | ```_ 140 | west build -b nrf9151dk/nrf9151/ns -- "-DOVERLAY_CONFIG=$PATH_TO_ONOMONDO_SOFTSIM/overlay-softsim.conf" 141 | ``` 142 | Where `PATH_TO_ONOMONDO_SOFTSIM` is the path of the downloaded Onomondo SoftSIM repository, for example `$HOME/ncs/nrf-softsim-dev`. 143 | 144 | #### Note 145 | SoftSIM is relying on some default data in the storage partition. This section of the flash can be generated and flashed manually (see steps below) or, as we recommend, automatically included by adding `SB_CONFIG_SOFTSIM_BUNDLE_TEMPLATE_HEX=y` to `sysbuild.conf`. 146 | 147 | Manually generating SoftSIM profile template data: 148 | 1. After building the application, generate the application-specific template profile. `west build -b nrf9151dk/nrf9151/ns -t onomondo_softsim_template` 149 | 2. Flash the application-specific template profile. `west flash --hex-file build/onomondo-softsim/template.hex` 150 | 151 | If the partition table of the application changes, for example due to another partition changing size, the template profile must be rebuilt and flashed again. 152 | The partition table can be checked at any time with: 153 | ``` 154 | west build -t partition_manager_report 155 | ``` 156 | 157 | #### Note 158 | Some applications will fail to link with error `zephyr/zephyr_pre0.elf uses VFP register arguments` (for example `modem_shell`). In this case it is required to also enable `CONFIG_FP_SOFTABI=y`. It is suggested to create an additional Kconfig overlay for application specific SoftSIM configurations and add them to `overlay-softsim.conf` inside 159 | the application directory. 160 | The application can then be built like this: 161 | ``` 162 | west build -b nrf9151dk/nrf9151/ns -- "-DOVERLAY_CONFIG=$PATH_TO_ONOMONDO_SOFTSIM/overlay-softsim.conf;overlay-softsim.conf" 163 | ``` 164 | 165 | #### Note 166 | For very large applications, it is required to disable features in order to reduce the size of the application binary and leave space on Flash for the SIM profile. 167 | During the build step, a Flash overflow error will be reported if this requirement is not satisfied. 168 | The same principle applies for RAM requirements. 169 | 170 | #### Note 171 | Onomondo SoftSIM uses the heap memory pool. It is expected that `CONFIG_HEAP_MEM_POOL_SIZE` is at least `30000`, so if the target application also uses the heap, please 172 | consider adjusting this Kconfig accordingly. 173 | 174 | #### Note 175 | Onomondo SoftSIM cannot coexist with `CONFIG_SETTINGS` with NVS backend `CONFIG_SETTINGS_NVS`. Please consider switching instead to FCB backend by enabling `CONFIG_SETTINGS_FCB`. 176 | 177 | 178 | ### Software SIM selection during runtime 179 | 180 | The Modem is runtime-configurable to use regular SIM and/or SoftSIM (or iSIM if supported). The configuration is done by the AT command `AT%CSUS=2` for Software SIM selection. The configuration can be done only when the Modem is deactivated. When reverting to physical SIM, configure with the AT command `AT%CSUS=0`. SIM selection is committed to NVM after a `AT+CFUN=0`. 181 | 182 | When enabling SoftSIM, the Software SIM will be selected automatically upon initialization. 183 | 184 | 185 | ### About Kconfig options 186 | 187 | Isn't completely finalized yet. The following fields should either be `y` selected by SoftSIM or the application developer: 188 | - Flash access 189 | - TFM for `psa_crypto` 190 | - NVS for profile 191 | 192 | `CONFIG_SOFTSIM` includes SoftSIM in the build system 193 | `CONFIG_SOFTSIM_AUTO_INIT` starts the SoftSIM task automatically. This can be omitted and done expicitly in the user application. 194 | 195 | 196 | ## Understanding the SIM - why SoftSIM is possible 197 | 198 | To be as brief as possible - a SIM is nothing more than a fancy filesystem with the ability to calculate an authentication response to a given authentication challenge. More about that later. 199 | 200 | ### Filesystem operations 201 | For details - refer to https://www.etsi.org/deliver/etsi_ts/102200_102299/102221/18.00.00_60/ts_102221v180000p.pdf 202 | 203 | The majority of commands that the SIM understands are related to the underlying filesystem. These include, but not limitied to, `SELECT`, `READ BINARY`, `UPDATE BINARY`, `READ RECORD`, `UPDATE RECORD`, `SEARCH RECORD`. You get the idea. Something about selecting files and either reading them or updating them. 204 | 205 | Not all files are free to update. For instance the `IMSI` can only be changed by the operator with the correct PINs - so a SIM also manages access rights. Some rights are unlocked with the PIN - for that `VERIFY PIN` command is issued. 206 | 207 | #### The nRF9151 SIM init sequence 208 | What happens when you 'activate' the SIM on your device (`AT+CFUN=41`)? The first many commands follows the same pattern - `00a4....` which is the `SELECT` command followed by `00b0....` which is the `READ BINARY` command. 209 | 210 | - `80f20000000168` - `STATUS` 211 | - `00a408040000022fe20168` - `SELECT` '2fe2' (EF_ICCID) 212 | - `00b000000a` - `READ BINARY` - Get the actual content of selected fine 213 | - `00a40804000002` 2f000168 - `SELECT` '2f00' (EF_DIR) 214 | - `00b2010426` - `READ RECORD` 215 | - `00a408040000022f050168` - `SELECT` '2f05' (EF_PL) which encodes the Preferred Language 216 | - `00b000000a` - - `READ BINARY` 217 | - `00a408040000022f080168` - `SELECT` '2f08' (EF_UMPC) (UICC Maximum Power Consumption) 218 | 219 | And the list goes on... 220 | 221 | 222 | ### The template SIM profile 223 | 224 | The main point here is that EF_DIR, EF_PL, EF_UMPC etc are the same for all SIMs. Only the ICCID and IMSI is different across SIMs when they are fresh out of the factory. 225 | 226 | To accomodate that we've created a bootstrapping filesystem that should be flashed together with the application. 227 | 228 |

229 | 230 |

231 | 232 | 233 | The list of files is fairly involved - but in the end only a subset of files are ever accessed. 234 | 235 | Internally this is a NVS partition which is a `key-value` store type. It is pretty compact and generally sufficient. The previous request of reading the `ICCID` internally translates to something similar to: 236 | 237 | `00a408040000022fe20168` -> `open('/3f00/2fe2)` -> `nvs_read(id=14)` 238 | 239 | We've made a caching layer as well to avoid i) slow reads and ii) excessive writes to flash. So, the SoftSIM profile data looks something like: 240 | 241 |

242 | 243 |

244 | 245 | 246 | The first entry is used to translate between paths (`'3f00/2fe2`) to an actual `NVS key`. It contains an ordered list of files sorted by frequency of access - i.e. the 'master file, 3f00' is in the top, since it is most frequently accessed. 247 | 248 | 249 | The list is read and parsed to a linked list - and this makes the base for all cached operations. The order makes the lookup very fast. 250 |

251 | 252 |

253 | 254 | 255 | ### Provisioning 256 | 257 | And that leads us to provisioning of SIMs. 258 | 259 | The `IMSI/ICCID` should't be a surprise at this point: 260 | 261 | ```c 262 | #define IMSI_PATH "/3f00/7ff0/6f07" 263 | #define ICCID_PATH "/3f00/2fe2" 264 | 265 | int write_profile(...){ 266 | 267 | ... 268 | 269 | // find the NVS key based on the cache 270 | struct cache_entry *entry = (struct cache_entry * f_cache_find_by_name(IMSI_PATH, &fs_cache); 271 | 272 | // commit directly to NVS 273 | nvs_write(&fs, entry->key, profile->IMSI, IMSI_LEN) 274 | 275 | ... // repeat for ICCID and support files 276 | 277 | } 278 | ``` 279 | 280 | Alright, that was the easy part. In reality something else happens _just_ before: 281 | 282 | ```c 283 | struct ss_profile profile = {0}; 284 | decode_profile(len, profile_r, &profile); 285 | 286 | // import to psa_crypto 287 | ss_utils_setup_key(KMU_KEY_SIZE, profile.K, KEY_ID_KI); 288 | ss_utils_setup_key(KMU_KEY_SIZE, profile.KIC, KEY_ID_KIC); 289 | ss_utils_setup_key(KMU_KEY_SIZE, profile.KID, KEY_ID_KID); 290 | ``` 291 | 3 keys are written to the KMU. These are related to the authentication and remote SIM OTA commands. 292 | 293 | When a device finds a network it wants to attach to, something like this happens (simplified version): 294 | 295 |

296 | 297 |

298 | 299 | 300 | Step 5 is a SIM command as well `AUTHENTICATE EVEN` and it is running the `milenage algorithm` that also creates session keys, checks that the network in fact isn't an imposter, etc. 301 | 302 | The milenage algorithm is `AES` based and we utilize the KMU through the `psa_crypto` API to implement this. This means that the keys are _very_ protected and once written they can't ever be extracted. Instead they are used by reference in the crypto engine. 303 | 304 | 305 | #### Provisioning SoftSIMs 306 | Is done through a pretty simple interface - 307 | `nrf_softsim_provision(uint8_t * data, size_t len)` decodes and write the profile to the appropriate places. 308 | The profile is fetched from Onomondo's API. The sample uses UART to transfer it. 309 | 310 | At any time the provisioning status can be queried with `nrf_softsim_check_provisioned()`. Handy when boothing up as the device can enter a provision mode based on this. 311 | 312 | 313 | #### Quick recap 314 | ```c 315 | struct profile 316 | { 317 | u8[] Ki, 318 | u8[] KIC, 319 | u8[] KID, 320 | u8[] IMSI, 321 | u8[] ICCID, 322 | u8[] SMSP // SMS related 323 | } 324 | ``` 325 | 326 | The profile encoding is a `TLV` like structure (Tag Length Value) to make it a bit more flexible. Each field in the profile has a tag assigned e.g. `IMSI_TAG=(0x01)`. 327 | 328 | The profile is contructed by encoding `TAG|LEN|DATA[LEN]` for each field and concatinating multple TLV fields: 329 | 330 | Encoding the IMSI is done by: `TAG: 1, LEN: 0x12`: 331 | 332 | IMSI_TLV: `0112082943051220434955` 333 | 334 | Where the first 4 bytes are recognized as `01(TAG) 12(LEN)` and indeed 18 bytes follow. 335 | 336 | 337 | ## Some more details 338 | 339 | #### Memory Heaps 340 | `mem.h`/`ss_heap.c` can now be optionally used to implement custom allocators - i.e. `k_malloc()` to avoid conflict with stdc heap pools. We default to `k_malloc()/k_free()` 341 | 342 | #### Non-Volatile Storage (NVS) 343 | The main challenge here is that the NVS is a "key-value" store i.e. `UINT16 -> void *`. SoftSIM needs a `char * path -> void * data` map. 344 | 345 | To overcome this, the first entry (ID 1) in the NVS will store a mapping from paths to actual ID's. 346 | 347 | The ID is leveraged to encode additional information, i.e. if content is in protected storage instead. 348 | 349 | ``` 350 | Dir entry: 351 | 352 | [[ID_1, PATH_LEN, PATH], [ID_n, PATH_LEN, PATH], ... ] 353 | 354 | Where ID is the actual ID where DATA is stored. 355 | 356 | ID & 0xFF00 (upper bits) are used for flags, i.e. 357 | 358 | #define FS_READ_ONLY (1UL << 8) 359 | #define FS_COMMIT_ON_CLOSE (1UL << 7) // commit changes to NVS on close 360 | ``` 361 | The inital DIR is designed to prioritize most accessed files as well. Internally, files are cached and in general kept in memory. 362 | 363 | `FS_COMMIT_ON_CLOSE` is used for SEQ numbers that should be committed to flash directly to reduce attack vectors. 364 | 365 | 366 | #### SoftSIM integration in application code 367 | Either call `nrf_softsim_init()` explicitly or let the kernel do it on boot with the config option. 368 | 369 | SoftSIM entrypoint starts its own work queue and returns immediately after. The handler installed with `nrf_modem_softsim_req_handler_set()` will enqueue requests, as they come and the work queue will unblock and handle the request. The SoftSIM context will be blocked most of the time. The main interaction happens on boot. 370 | 371 | ![softsim_nrf_flow](https://github.com/onomondo/nrf-softsim/assets/46489969/7513bb06-99b3-4de4-95bb-34884a9726ed) 372 | 373 | Please note that SoftSIM internally need access to a storage partition. This should be pre-populated with the `template.hex` provided in the samples. The adress in the `template.hex` is hardcoded but can be moved around freely as pleased with an appropriate tool. The location is derived from the devicetree at compile time (` FIXED_PARTITION_DEVICE(NVS_PARTITION)`) 374 | -------------------------------------------------------------------------------- /boards/thingy91_pm_static.yml: -------------------------------------------------------------------------------- 1 | EMPTY_0: 2 | address: 0xfc000 3 | end_address: 0xfe000 4 | region: flash_primary 5 | size: 0x2000 6 | EMPTY_1: 7 | address: 0x6a000 8 | end_address: 0x70000 9 | placement: 10 | after: 11 | - tfm_its 12 | region: flash_primary 13 | size: 0x6000 14 | EMPTY_2: 15 | address: 0x72000 16 | end_address: 0x75000 17 | size: 0x3000 18 | app: 19 | address: 0x20000 20 | end_address: 0x60000 21 | region: flash_primary 22 | size: 0x3e000 23 | mcuboot: 24 | address: 0x0 25 | end_address: 0xc000 26 | placement: 27 | before: 28 | - mcuboot_primary 29 | region: flash_primary 30 | size: 0xc000 31 | mcuboot_pad: 32 | address: 0xc000 33 | end_address: 0xc200 34 | placement: 35 | align: 36 | start: 0x1000 37 | before: 38 | - mcuboot_primary_app 39 | region: flash_primary 40 | size: 0x200 41 | mcuboot_primary: 42 | address: 0xc000 43 | end_address: 0x75000 44 | region: flash_primary 45 | size: 0x69000 46 | span: 47 | - tfm 48 | - mcuboot_pad 49 | - app 50 | mcuboot_primary_app: 51 | address: 0xc200 52 | end_address: 0x75000 53 | region: flash_primary 54 | size: 0x68e00 55 | span: 56 | - app 57 | - tfm 58 | mcuboot_scratch: 59 | address: 0xde000 60 | end_address: 0xfc000 61 | placement: 62 | after: 63 | - app 64 | align: 65 | start: 0x1000 66 | region: flash_primary 67 | size: 0x1e000 68 | mcuboot_secondary: 69 | address: 0x75000 70 | end_address: 0xde000 71 | placement: 72 | after: 73 | - mcuboot_primary 74 | align: 75 | start: 0x1000 76 | region: flash_primary 77 | share_size: 78 | - mcuboot_primary 79 | size: 0x69000 80 | mcuboot_sram: 81 | address: 0x20000000 82 | end_address: 0x20016000 83 | orig_span: &id001 84 | - tfm_sram 85 | region: sram_primary 86 | size: 0x16000 87 | span: *id001 88 | nonsecure_storage: 89 | address: 0xfe000 90 | end_address: 0x100000 91 | region: flash_primary 92 | size: 0x2000 93 | span: 94 | - settings_storage 95 | nrf_modem_lib_ctrl: 96 | address: 0x20016000 97 | end_address: 0x200164e8 98 | inside: 99 | - sram_nonsecure 100 | placement: 101 | after: 102 | - tfm_sram 103 | - start 104 | region: sram_primary 105 | size: 0x4e8 106 | nrf_modem_lib_rx: 107 | address: 0x20018568 108 | end_address: 0x2001a568 109 | inside: 110 | - sram_nonsecure 111 | placement: 112 | after: 113 | - nrf_modem_lib_tx 114 | region: sram_primary 115 | size: 0x2000 116 | nrf_modem_lib_sram: 117 | address: 0x20016000 118 | end_address: 0x2001a568 119 | orig_span: &id002 120 | - nrf_modem_lib_ctrl 121 | - nrf_modem_lib_tx 122 | - nrf_modem_lib_rx 123 | region: sram_primary 124 | size: 0x4568 125 | span: *id002 126 | nrf_modem_lib_tx: 127 | address: 0x200164e8 128 | end_address: 0x20018568 129 | inside: 130 | - sram_nonsecure 131 | placement: 132 | after: 133 | - nrf_modem_lib_ctrl 134 | region: sram_primary 135 | size: 0x2080 136 | nvs_storage: 137 | address: 0x60000 138 | end_address: 0x68000 139 | placement: 140 | align: 141 | start: 0x8000 142 | before: 143 | - tfm_storage 144 | - end 145 | region: flash_primary 146 | size: 0x8000 147 | otp: 148 | address: 0xff8108 149 | end_address: 0xff83fc 150 | region: otp 151 | size: 0x2f4 152 | settings_storage: 153 | address: 0xfe000 154 | end_address: 0x100000 155 | placement: 156 | after: 157 | - mcuboot_scratch 158 | region: flash_primary 159 | size: 0x2000 160 | sram_nonsecure: 161 | address: 0x20016000 162 | end_address: 0x20040000 163 | orig_span: &id003 164 | - sram_primary 165 | - nrf_modem_lib_ctrl 166 | - nrf_modem_lib_tx 167 | - nrf_modem_lib_rx 168 | region: sram_primary 169 | size: 0x2a000 170 | span: *id003 171 | sram_primary: 172 | address: 0x2001a568 173 | end_address: 0x20040000 174 | region: sram_primary 175 | size: 0x25a98 176 | sram_secure: 177 | address: 0x20000000 178 | end_address: 0x20016000 179 | orig_span: &id004 180 | - tfm_sram 181 | region: sram_primary 182 | size: 0x16000 183 | span: *id004 184 | tfm: 185 | address: 0xc200 186 | end_address: 0x20000 187 | region: flash_primary 188 | size: 0x13e00 189 | tfm_its: 190 | address: 0x68000 191 | end_address: 0x6a000 192 | inside: 193 | - tfm_storage 194 | placement: 195 | align: 196 | start: 0x8000 197 | before: 198 | - tfm_otp_nv_counters 199 | region: flash_primary 200 | size: 0x2000 201 | tfm_nonsecure: 202 | address: 0x20000 203 | end_address: 0x73000 204 | region: flash_primary 205 | size: 0x53000 206 | span: 207 | - app 208 | tfm_otp_nv_counters: 209 | address: 0x70000 210 | end_address: 0x72000 211 | inside: 212 | - tfm_storage 213 | placement: 214 | align: 215 | start: 0x8000 216 | before: 217 | - end 218 | region: flash_primary 219 | size: 0x2000 220 | tfm_secure: 221 | address: 0xc000 222 | end_address: 0x20000 223 | region: flash_primary 224 | size: 0x14000 225 | span: 226 | - mcuboot_pad 227 | - tfm 228 | tfm_sram: 229 | address: 0x20000000 230 | end_address: 0x20016000 231 | inside: 232 | - sram_secure 233 | placement: 234 | after: 235 | - start 236 | region: sram_primary 237 | size: 0x16000 238 | tfm_storage: 239 | address: 0x68000 240 | end_address: 0x72000 241 | orig_span: &id005 242 | - tfm_its 243 | - tfm_otp_nv_counters 244 | region: flash_primary 245 | size: 0xa000 246 | span: *id005 247 | -------------------------------------------------------------------------------- /boards/thingy91x_nrf9151_pm_static.yml: -------------------------------------------------------------------------------- 1 | b0: 2 | address: 0x0 3 | size: 0x8000 4 | region: flash_primary 5 | b0_container: 6 | address: 0x0 7 | size: 0x8000 8 | region: flash_primary 9 | span: [b0] 10 | s0: 11 | address: 0x8000 12 | size: 0x14000 13 | span: [mcuboot, s0_pad] 14 | region: flash_primary 15 | s0_pad: 16 | address: 0x8000 17 | size: 0x200 18 | share_size: [mcuboot_pad] 19 | region: flash_primary 20 | s0_image: 21 | address: 0x8200 22 | size: 0x13e00 23 | span: [mcuboot] 24 | region: flash_primary 25 | mcuboot: 26 | address: 0x8200 27 | size: 0x13e00 28 | region: flash_primary 29 | s1: 30 | address: 0x1c000 31 | size: 0x14000 32 | span: [s1_pad, s1_image] 33 | region: flash_primary 34 | s1_pad: 35 | address: 0x1c000 36 | size: 0x200 37 | region: flash_primary 38 | share_size: [mcuboot_pad] 39 | s1_image: 40 | address: 0x1c200 41 | size: 0x13e00 42 | share_size: [mcuboot] 43 | region: flash_primary 44 | mcuboot_primary: 45 | address: 0x30000 46 | size: 0xc0000 47 | span: [mcuboot_pad, app, tfm] 48 | region: flash_primary 49 | tfm_secure: 50 | address: 0x30000 51 | size: 0x20000 52 | span: [mcuboot_pad, tfm] 53 | region: flash_primary 54 | mcuboot_pad: 55 | address: 0x30000 56 | size: 0x200 57 | region: flash_primary 58 | mcuboot_primary_app: 59 | address: 0x30200 60 | size: 0xbfe00 61 | span: [tfm, app] 62 | region: flash_primary 63 | app_image: 64 | address: 0x30200 65 | size: 0xbfe00 66 | span: [tfm, app] 67 | region: flash_primary 68 | tfm: 69 | address: 0x30200 70 | size: 0x1fe00 71 | region: flash_primary 72 | tfm_nonsecure: 73 | address: 0x50000 74 | size: 0xb0000 75 | span: [app] 76 | region: flash_primary 77 | nonsecure_storage: 78 | address: 0xf0000 79 | size: 0x8000 80 | span: [ nvs_storage ] 81 | app: 82 | address: 0x50000 83 | size: 0xa0000 84 | region: flash_primary 85 | nvs_storage: 86 | address: 0xf0000 87 | size: 0x8000 88 | region: flash_primary 89 | tfm_its: 90 | address: 0xf8000 91 | size: 0x2000 92 | region: flash_primary 93 | tfm_otp_nv_counters: 94 | address: 0xfa000 95 | size: 0x2000 96 | region: flash_primary 97 | tfm_ps: 98 | address: 0xfc000 99 | size: 0x4000 100 | region: flash_primary 101 | tfm_storage: 102 | address: 0xf8000 103 | size: 0x8000 104 | span: [ tfm_its, tfm_otp_nv_counters, tfm_ps ] 105 | region: flash_primary 106 | 107 | mcuboot_secondary: 108 | device: DT_CHOSEN(nordic_pm_ext_flash) 109 | address: 0x0 110 | size: 0xD0000 111 | share_size: [mcuboot_primary] 112 | region: external_flash 113 | fmfu_storage: 114 | address: 0xD0000 115 | device: DT_CHOSEN(nordic_pm_ext_flash) 116 | region: external_flash 117 | size: 0x400000 118 | settings_storage: 119 | device: DT_CHOSEN(nordic_pm_ext_flash) 120 | address: 0x4D0000 121 | size: 0x2000 122 | region: external_flash 123 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | zephyr_library() 2 | 3 | include_directories(include) 4 | 5 | zephyr_library_sources(nrf_softsim.c ss_fs.c ss_provision.c ss_cache.c ss_heap.c ss_profile.c ss_crypto.c build_asserts.c ) 6 | 7 | zephyr_library_import(crypto ${CMAKE_CURRENT_SOURCE_DIR}/libcrypto.a) 8 | zephyr_library_import(milenage ${CMAKE_CURRENT_SOURCE_DIR}/libmilenage.a) 9 | zephyr_library_import(uicc ${CMAKE_CURRENT_SOURCE_DIR}/libuicc.a) 10 | zephyr_library_import(storage ${CMAKE_CURRENT_SOURCE_DIR}/libstorage.a) 11 | 12 | 13 | zephyr_include_directories(include) 14 | 15 | add_definitions( -DCONFIG_CUSTOM_HEAP ) 16 | target_link_libraries(${ZEPHYR_CURRENT_LIBRARY} PRIVATE crypto milenage storage uicc) 17 | -------------------------------------------------------------------------------- /lib/build_asserts.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define EXPECTED_PARTITION_SIZE 0x8000 5 | #define EXPECTED_MIN_HEAP_SIZE 30000 6 | 7 | BUILD_ASSERT(CONFIG_HEAP_MEM_POOL_SIZE >= EXPECTED_MIN_HEAP_SIZE, 8 | "SoftSIM: " 9 | "Heap memory pool size is not valid. " 10 | "Please reconfigure the project."); 11 | 12 | /* In NCS, when NVS backend for Settings is chosen, `nvs_partition` partition is not included by 13 | * the Partition Manager. 14 | * `nvs_storage`partition is required by SoftSIM. FCB backend for Settings should be used instead 15 | * of NVS backend. 16 | */ 17 | #if CONFIG_SETTINGS_NVS 18 | BUILD_ASSERT(0, "Softsim: Please disable CONFIG_SETTINGS_NVS. Choose CONFIG_SETTINGS_FCB instead."); 19 | #else 20 | BUILD_ASSERT(CONFIG_PM_PARTITION_SIZE_NVS_STORAGE == EXPECTED_PARTITION_SIZE, 21 | "SoftSIM: " 22 | "nvs_partition size is not valid. " 23 | "Please reconfigure the project."); 24 | #endif 25 | -------------------------------------------------------------------------------- /lib/include/nrf_softsim.h: -------------------------------------------------------------------------------- 1 | #ifndef _NRF_SOFTSIM_H_ 2 | #define _NRF_SOFTSIM_H_ 3 | #include 4 | 5 | /** 6 | * @brief Initialize the SoftSIM library and install handlers. Only use if 7 | * CONFIG_NRF_SOFTSIM_AUTO_INIT is not set. 8 | * 9 | * 10 | * @return 0 on success 11 | */ 12 | int nrf_softsim_init(void); 13 | 14 | /** 15 | * @brief 16 | * 17 | * Provision a SoftSIM profile to protected storage. 18 | * len must be exactly 328 bytes long. 19 | * 20 | * 21 | * @param profile String representing a SoftSIM profile. This encodes IMSI, 22 | * ICCID and necessary keys. 23 | * @param len Length of profile passed 24 | * @return 0 on success 25 | */ 26 | int nrf_softsim_provision(uint8_t *profile, size_t len); 27 | 28 | /** 29 | * @brief Check if a SoftSIM profile is provisioned in protected storage. 30 | * 31 | * 32 | * @return 1 if provisioned, 0 if not 33 | */ 34 | int nrf_softsim_check_provisioned(void); 35 | 36 | #endif /* _NRF_SOFTSIM_H_ */ 37 | -------------------------------------------------------------------------------- /lib/include/onomondo/softsim/file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | struct ss_buf; 6 | struct ss_fcp_file_descr; 7 | 8 | /** A handle to a file that has been accessed far enough to ensure that it is 9 | * present and has its `fcp_file_descr` loaded. 10 | * 11 | * Initialized handles are, through their lists, always part of a chain down to 12 | * the MF (in their .list.previous). 13 | * 14 | * When an `ss_file` is created through a "select" operation, all its indirect 15 | * details (`aid`, `fci`, `fcp_decoded`) are present, both in the file and its 16 | * parents. (`fcp_file_descr` is always present). 17 | * 18 | * The `access` property is not populated in "select" operations, but through 19 | * the dedicated \ref ss_access_populate, which is invoked from the commands 20 | * implementing selection for the terminal. 21 | * 22 | * Handles created through other operations (mainly in internal use) may have 23 | * NULL pointers in these places. 24 | */ 25 | struct ss_file { 26 | struct ss_list list; 27 | uint32_t fid; 28 | struct ss_buf *aid; /* also called 'DF name' */ 29 | struct ss_buf *fci; /* The full file control information (FCI) in encoded form. 30 | * 31 | * This is the full data sent in response to SELECT 32 | * calls; while ISO-IEC 7816-4 allows different 33 | * information in here (eg. FMD), TS 102 221 usually only 34 | * expects FCP data here. */ 35 | struct ss_list *fcp_decoded; /* The decoded form of the FCP that is part of the file control information */ 36 | struct ss_fcp_file_descr *fcp_file_descr; 37 | struct ss_list *access; /**< The access rule governing file operations for 38 | * the SE that selected the file (or general access if 39 | * no SE apply). A value of NULL indicates that no 40 | * access rules have (or could be) loaded. */ 41 | }; 42 | 43 | struct ss_file *ss_get_file_from_path(const struct ss_list *path); 44 | struct ss_file *ss_get_parent_file_from_path(const struct ss_list *path); 45 | -------------------------------------------------------------------------------- /lib/include/onomondo/softsim/fs_port.h: -------------------------------------------------------------------------------- 1 | #ifndef FS_PORT_H 2 | #define FS_PORT_H 3 | 4 | #include 5 | 6 | typedef void *port_FILE; 7 | 8 | size_t port_fread(void *ptr, size_t size, size_t nmemb, port_FILE fp); 9 | char *port_fgets(char *str, int n, port_FILE fp); 10 | int port_fclose(port_FILE); 11 | port_FILE port_fopen(char *path, char *mode); 12 | int port_fseek(port_FILE fp, long offset, int whence); 13 | long port_ftell(port_FILE fp); 14 | int port_fputc(int c, port_FILE); 15 | int port_access(const char *path, int amode); 16 | size_t port_fwrite(const void *prt, size_t size, size_t count, port_FILE f); 17 | int port_mkdir(const char *, int); 18 | int port_remove(const char *); 19 | int port_rmdir(const char *); 20 | int ss_init_fs(); 21 | int ss_deinit_fs(); 22 | 23 | #endif /* FS_PORT_H */ 24 | -------------------------------------------------------------------------------- /lib/include/onomondo/softsim/list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /*! List element of a double linked list */ 7 | struct ss_list { 8 | /*! reference to the previous element */ 9 | struct ss_list *previous; 10 | /*! referenc to the next element */ 11 | struct ss_list *next; 12 | }; 13 | 14 | /*! Initialize list (pre and nxt point to themselves). 15 | * \param[out] list pointer to the begin of the list. */ 16 | static inline void ss_list_init(struct ss_list *list) 17 | { 18 | list->previous = list; 19 | list->next = list->previous; 20 | } 21 | 22 | /*! Check if list properly initialized (next/previous pointer are not NULL). 23 | * \param[in] list pointer to the begin of the list. 24 | * \returns true when initialized, false otherwise. */ 25 | static inline bool ss_list_initialized(const struct ss_list *list) 26 | { 27 | return list->next != NULL && list->previous != NULL; 28 | } 29 | 30 | /*! Put an element on the end of the list. 31 | * \param[inout] list pointer to the begin of the list. 32 | * \param[inout] elem pointer to the list member of the element to add. */ 33 | static inline void ss_list_put(struct ss_list *list, struct ss_list *elem) 34 | { 35 | struct ss_list *old_list_end = list->previous; 36 | 37 | /* Connect element to the end of the list */ 38 | list->previous = elem; 39 | elem->next = list; 40 | 41 | /* Connect old end to the new element */ 42 | old_list_end->next = elem; 43 | elem->previous = old_list_end; 44 | } 45 | 46 | /*! Push an element on the beginning of the list. 47 | * \param[inout] list pointer to the begin of the list. 48 | * \param[inout] elem pointer to the list member of the element to add. */ 49 | static inline void ss_list_push(struct ss_list *list, struct ss_list *elem) 50 | { 51 | struct ss_list *old_list_begin = list->next; 52 | 53 | /* Connect element to the beginning of the list */ 54 | list->next = elem; 55 | elem->previous = list; 56 | 57 | /* Connect old beginning to the new element */ 58 | old_list_begin->previous = elem; 59 | elem->next = old_list_begin; 60 | } 61 | 62 | /*! Get the next struct for the next list element (helper macro for SS_LIST_FOR_EACH). 63 | * \param[in] list pointer to the begin of the list. 64 | * \param[in] struct_type type description of the element struct. 65 | * \param[in] struct_member name of the list begin member inside the element struct. 66 | * \returns pointer to list element. */ 67 | #define SS_LIST_GET_NEXT(list, struct_type, struct_member) (void*)((list)->next - offsetof(struct_type, struct_member)) 68 | 69 | /*! Get the struct for the current list element (helper macro for SS_LIST_FOR_EACH). 70 | * \param[in] list pointer to the begin of the list. 71 | * \param[in] struct_type type description of the element struct. 72 | * \param[in] struct_member name of the list begin member inside the element struct. 73 | * \returns pointer to list element. */ 74 | #define SS_LIST_GET(list, struct_type, struct_member) (void*)(list - offsetof(struct_type, struct_member)) 75 | 76 | /* iterate over all structs managed by the list */ 77 | #define SS_LIST_FOR_EACH(list_begin, struct_cursor, struct_type, struct_member) \ 78 | for (struct_cursor = SS_LIST_GET_NEXT(list_begin, struct_type, struct_member); \ 79 | struct_cursor != SS_LIST_GET(list_begin, struct_type, struct_member); \ 80 | struct_cursor = SS_LIST_GET_NEXT(&(struct_cursor)->struct_member, struct_type, struct_member)) 81 | 82 | /* iterate over all structs managed by the list, but keep a precursor in case 83 | * the cursor gets freed during the iteration. */ 84 | #define SS_LIST_FOR_EACH_SAVE(list_begin, struct_cursor, struct_precursor, struct_type, struct_member) \ 85 | for (struct_cursor = SS_LIST_GET_NEXT(list_begin, struct_type, struct_member), \ 86 | struct_precursor = SS_LIST_GET_NEXT((list_begin)->next, struct_type, struct_member); \ 87 | struct_cursor != SS_LIST_GET(list_begin, struct_type, struct_member); \ 88 | struct_cursor = struct_precursor, \ 89 | struct_precursor = SS_LIST_GET_NEXT(&(struct_precursor)->struct_member, struct_type, struct_member)) 90 | 91 | /*! Remove an element from the list by unlinking it from the list. 92 | * \param[inout] elem pointer to the list member of the element to remove. */ 93 | static inline void ss_list_remove(struct ss_list *elem) 94 | { 95 | struct ss_list *next_elem = elem->next; 96 | struct ss_list *previous_elem = elem->previous; 97 | 98 | next_elem->previous = previous_elem; 99 | previous_elem->next = next_elem; 100 | 101 | elem->next = NULL; 102 | elem->previous = NULL; 103 | } 104 | 105 | /*! Check whether the list is empty or not. 106 | * \param[in] list pointer to the begin of the list. 107 | * \returns true when the list is empty, false otherwise. */ 108 | static inline bool ss_list_empty(const struct ss_list *list) 109 | { 110 | return list->next == list; 111 | } 112 | -------------------------------------------------------------------------------- /lib/include/onomondo/softsim/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define SS_LOGP(subsys, level, fmt, args...) \ 7 | ss_logp(subsys, level, __FILE__, __LINE__, fmt, ## args) 8 | 9 | void ss_logp(uint32_t subsys, uint32_t level, const char *file, int line, const char *format, ...) 10 | __attribute__ ((format (printf, 5, 6))); 11 | 12 | 13 | enum log_subsys { 14 | SBTLV, 15 | SCTLV, 16 | SVPCD, 17 | SIFACE, 18 | SUICC, 19 | SCMD, 20 | SLCHAN, 21 | SFS, 22 | SSTORAGE, 23 | SACCESS, 24 | SADMIN, 25 | SSFI, 26 | SDFNAME, 27 | SFILE, 28 | SPIN, 29 | SAUTH, 30 | SPROACT, 31 | STLV8, 32 | SSMS, 33 | SREMOTECMD, 34 | SREFRESH, 35 | _NUM_LOG_SUBSYS 36 | }; 37 | 38 | enum log_level { 39 | LERROR, 40 | LINFO, 41 | LDEBUG, 42 | _NUM_LOG_LEVEL 43 | }; 44 | -------------------------------------------------------------------------------- /lib/include/onomondo/softsim/mem.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef MEM_H 4 | #define MEM_H 5 | 6 | #if CONFIG_CUSTOM_HEAP 7 | 8 | #if CONFIG_SOFTSIM_DEBUG 9 | #pragma message "Using custom heap" 10 | #endif 11 | 12 | void *port_malloc(size_t); 13 | void port_free(void *); 14 | 15 | #define SS_ALLOC(obj) port_malloc(sizeof(obj)); 16 | #define SS_ALLOC_N(n) port_malloc(n); 17 | #define SS_FREE(obj) port_free(obj); 18 | 19 | #else // default 20 | #include 21 | 22 | #define SS_ALLOC(obj) malloc(sizeof(obj)); 23 | #define SS_ALLOC_N(n) malloc(n); 24 | #define SS_FREE(obj) free(obj) 25 | 26 | #endif // CONFIG_CUSTOM_HEAP 27 | #endif // MEM_H 28 | -------------------------------------------------------------------------------- /lib/include/onomondo/softsim/softsim.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | struct ss_context; 6 | 7 | struct ss_context *ss_new_ctx(void); 8 | void ss_free_ctx(struct ss_context *ctx); 9 | void ss_reset(struct ss_context *ctx); 10 | void ss_poll(struct ss_context *ctx); 11 | size_t ss_atr(struct ss_context *ctx, uint8_t *atr_buf, size_t atr_buf_len); 12 | size_t ss_transact(struct ss_context *ctx, uint8_t *response_buf, size_t response_buf_len, uint8_t *request_buf, 13 | size_t *request_len); 14 | size_t ss_command_apdu_transact(struct ss_context *ctx, uint8_t *response_buf, size_t response_buf_len, 15 | uint8_t *request_buf, size_t *request_len); 16 | uint8_t ss_is_suspended(struct ss_context *ctx); 17 | -------------------------------------------------------------------------------- /lib/include/onomondo/softsim/storage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct ss_buf; 4 | struct ss_list; 5 | 6 | int ss_storage_get_file_def(struct ss_list *path); 7 | struct ss_buf *ss_storage_read_file(const struct ss_list *path, size_t read_offset, size_t read_len); 8 | size_t ss_storage_get_file_len(const struct ss_list *path); 9 | int ss_storage_write_file(const struct ss_list *path, const uint8_t *data, size_t write_offset, size_t write_len); 10 | int ss_storage_delete(const struct ss_list *path); 11 | int ss_storage_update_def(const struct ss_list *path); 12 | int ss_storage_create_file(const struct ss_list *path, size_t file_len); 13 | int ss_storage_create_dir(const struct ss_list *path); 14 | -------------------------------------------------------------------------------- /lib/include/onomondo/softsim/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "mem.h" 7 | 8 | #define SS_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 9 | 10 | char *ss_hexdump(const uint8_t *data, size_t len); 11 | size_t ss_binary_from_hexstr(uint8_t *binary, size_t binary_len, const char *hexstr); 12 | 13 | 14 | struct ss_buf { 15 | uint8_t *data; 16 | size_t len; 17 | }; 18 | 19 | /*! Generate a hexdump string from an ss_buf object. 20 | * \param[in] buf pointer to ss_buf object. 21 | * \returns pointer to generated human readable string. */ 22 | static inline char *ss_buf_hexdump(const struct ss_buf *buf) 23 | { 24 | return ss_hexdump(buf->data, buf->len); 25 | } 26 | 27 | /*! Allocate a new ss_buf object. 28 | * \param[in] len number of bytes to allocate inside ss_buf. 29 | * \returns pointer to newly allocated ss_buf object. */ 30 | static inline struct ss_buf *ss_buf_alloc(size_t len) 31 | { 32 | struct ss_buf *sb = SS_ALLOC_N(sizeof(*sb) + len); 33 | __ASSERT_NO_MSG(sb); 34 | 35 | sb->data = (uint8_t *) sb + sizeof(*sb); 36 | sb->len = len; 37 | 38 | return sb; 39 | } 40 | 41 | /*! Allocate a new ss_buf and copy from user provided memory. 42 | * \param[in] in user provided memory to copy. 43 | * \param[in] len amount of bytes to copy from user provided memory. 44 | * \returns pointer to newly allocated ss_buf object. */ 45 | static inline struct ss_buf *ss_buf_alloc_and_cpy(const uint8_t *in, size_t len) 46 | { 47 | struct ss_buf *sb = ss_buf_alloc(len); 48 | memcpy(sb->data, in, len); 49 | return sb; 50 | } 51 | 52 | /*! Allocate a new ss_buf and copy from another ss_buf object. 53 | * \param[in] buf ss_buf object to copy from. 54 | * \returns pointer to newly allocated ss_buf object. */ 55 | static inline struct ss_buf *ss_buf_dup(const struct ss_buf *buf) 56 | { 57 | struct ss_buf *buf_dup = ss_buf_alloc(buf->len); 58 | memcpy(buf_dup->data, buf->data, buf->len); 59 | return buf_dup; 60 | } 61 | 62 | /*! Free an ss_buf object. 63 | * \param[in] pointer to ss_buf object to free. */ 64 | static inline void ss_buf_free(struct ss_buf *buf) 65 | { 66 | SS_FREE(buf); 67 | } 68 | 69 | struct ss_buf *ss_buf_from_hexstr(const char *hexstr); 70 | uint32_t ss_uint32_from_array(const uint8_t *array, size_t len); 71 | void ss_array_from_uint32(uint8_t *array, size_t len, uint32_t in); 72 | uint64_t ss_uint64_from_array(const uint8_t *array, size_t len); 73 | void ss_array_from_uint64(uint8_t *array, size_t len, uint64_t in); 74 | size_t ss_optimal_len_for_uint32(uint32_t in); 75 | 76 | uint64_t ss_uint64_load_from_be(const uint8_t *storage); 77 | void ss_uint64_store_to_be(uint8_t *storage, uint64_t number); 78 | size_t ss_strnlen(const char *s, size_t maxlen); 79 | void ss_memzero(void *ptr, size_t len); 80 | -------------------------------------------------------------------------------- /lib/libcrypto.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onomondo/nrf-softsim/0d3a45284bbd2b6dd7a9ef91a5e556dc4dd4b5c4/lib/libcrypto.a -------------------------------------------------------------------------------- /lib/libmilenage.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onomondo/nrf-softsim/0d3a45284bbd2b6dd7a9ef91a5e556dc4dd4b5c4/lib/libmilenage.a -------------------------------------------------------------------------------- /lib/libstorage.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onomondo/nrf-softsim/0d3a45284bbd2b6dd7a9ef91a5e556dc4dd4b5c4/lib/libstorage.a -------------------------------------------------------------------------------- /lib/libuicc.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onomondo/nrf-softsim/0d3a45284bbd2b6dd7a9ef91a5e556dc4dd4b5c4/lib/libuicc.a -------------------------------------------------------------------------------- /lib/nrf_softsim.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ss_profile.h" 8 | #include "ss_crypto.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | LOG_MODULE_REGISTER(softsim, CONFIG_SOFTSIM_LOG_LEVEL); 18 | 19 | #define SOFTSIM_STACK_SIZE 10000 // TODO: this is too much. Figure out some more reasonable value. 20 | #define SOFTSIM_PRIORITY 5 // TODO: What is a good balence here? 21 | K_THREAD_STACK_DEFINE(softsim_stack_area, SOFTSIM_STACK_SIZE); 22 | 23 | #define SIM_HAL_MAX_LE 260 24 | 25 | static void softsim_req_task(struct k_work *item); 26 | int port_provision(struct ss_profile *profile); 27 | int port_check_provisioned(void); 28 | 29 | static struct k_work_q softsim_work_q; 30 | static K_FIFO_DEFINE(softsim_req_fifo); 31 | static K_WORK_DEFINE(softsim_req_work, softsim_req_task); 32 | static uint8_t softsim_buffer_out[SIM_HAL_MAX_LE]; 33 | 34 | // softsim handle 35 | struct ss_context *ctx = NULL; 36 | 37 | struct payload_t { 38 | void *data; 39 | uint16_t data_len; 40 | }; 41 | 42 | struct softsim_req_node { 43 | void *fifo_reserved; 44 | enum nrf_modem_softsim_cmd req; 45 | uint16_t req_id; 46 | struct payload_t payload; 47 | }; 48 | 49 | static void softsim_req_task(struct k_work *item); 50 | 51 | static void nrf_modem_softsim_req_handler(enum nrf_modem_softsim_cmd req, uint16_t req_id, void *data, 52 | uint16_t data_len); 53 | 54 | int onomondo_init(void) 55 | { 56 | /** 57 | * Init here? 58 | * Pro: pretty smooth :) -> no need to init and deinit more than needed.. 59 | * Con: IF someone actively de-init with the intent of freeing resources, 60 | * there will be *some* wasted memory.. Actual memory held isn't too much 61 | * though Pro: With no explicit de-init we protect the flash from excessive 62 | * writes Con: EF_LOCI might not be comitted to flash on actual power off... 63 | * 64 | * Maybe just call fs_commit() of so on de-init.? 65 | */ 66 | int rc = ss_init_fs(); 67 | 68 | #ifdef CONFIG_SOFTSIM_STATIC_PROFILE_ENABLE 69 | #pragma message "Using static profile. Only for development!" 70 | size_t profile_len = strlen(CONFIG_SOFTSIM_STATIC_PROFILE); 71 | 72 | if (!nrf_softsim_check_provisioned()) { 73 | LOG_INF("Provisioning static profile"); 74 | nrf_softsim_provision((uint8_t *)CONFIG_SOFTSIM_STATIC_PROFILE, profile_len); 75 | } 76 | #endif 77 | 78 | if (rc) { 79 | LOG_ERR("FS failed to init..\n"); 80 | return -1; 81 | } 82 | 83 | if (nrf_modem_softsim_req_handler_set(nrf_modem_softsim_req_handler)) { 84 | LOG_ERR("SoftSIM fatal error: Virtual Identity And Global Roaming Aborted"); 85 | return -1; 86 | } 87 | 88 | k_work_queue_init(&softsim_work_q); 89 | 90 | k_work_queue_start(&softsim_work_q, softsim_stack_area, K_THREAD_STACK_SIZEOF(softsim_stack_area), SOFTSIM_PRIORITY, 91 | NULL); 92 | 93 | ctx = ss_new_ctx(); // TODO: consider dropping this call here 94 | 95 | LOG_INF("SoftSIM initialized"); 96 | return 0; 97 | } 98 | 99 | // public init 100 | int nrf_softsim_init(void) { return onomondo_init(); } 101 | 102 | // public provision api 103 | int nrf_softsim_provision(uint8_t *profile_r, size_t len) 104 | { 105 | struct ss_profile profile = {0}; 106 | decode_profile(len, profile_r, &profile); 107 | 108 | // import to psa_crypto 109 | ss_utils_setup_key(KMU_KEY_SIZE, profile.K, KEY_ID_KI); 110 | ss_utils_setup_key(KMU_KEY_SIZE, profile.KIC, KEY_ID_KIC); 111 | ss_utils_setup_key(KMU_KEY_SIZE, profile.KID, KEY_ID_KID); 112 | 113 | LOG_INF("SoftSIM keys written to KMU"); 114 | 115 | int status = port_provision(&profile); 116 | 117 | if (status != 0) { 118 | LOG_ERR("SoftSIM failed to update profile"); 119 | } else { 120 | LOG_INF("SoftSIM fully provisioned"); 121 | } 122 | 123 | return status; 124 | } 125 | 126 | int nrf_softsim_check_provisioned(void) 127 | { 128 | /* Check first PSA key and also first NVS key. */ 129 | return ss_utils_check_key_existence(KEY_ID_KI) && port_check_provisioned(); 130 | } 131 | 132 | // still needed? 133 | __weak void nrf_modem_softsim_reset_handler(void) { LOG_DBG("SoftSIM RESET"); } 134 | 135 | static void softsim_req_task(struct k_work *item) 136 | { 137 | int err; 138 | struct softsim_req_node *s_req; 139 | 140 | while ((s_req = k_fifo_get(&softsim_req_fifo, K_NO_WAIT))) { 141 | switch (s_req->req) { 142 | case NRF_MODEM_SOFTSIM_INIT: { 143 | LOG_DBG("SoftSIM INIT REQ"); 144 | if (!ctx) { // This check is needed since multiple INIT requests can be sent 145 | ctx = ss_new_ctx(); 146 | } 147 | 148 | if (!ss_is_suspended(ctx)) { 149 | ss_reset(ctx); 150 | ss_init_fs(); 151 | } 152 | 153 | int atr_len = ss_atr(ctx, softsim_buffer_out, SIM_HAL_MAX_LE); 154 | 155 | err = nrf_modem_softsim_res(s_req->req, s_req->req_id, softsim_buffer_out, atr_len); 156 | 157 | LOG_HEXDUMP_DBG(softsim_buffer_out, atr_len, "SoftSIM ATR"); 158 | 159 | if (err) { 160 | LOG_ERR("SoftSIM INIT response failed with err: %d", err); 161 | } 162 | 163 | break; 164 | } 165 | case NRF_MODEM_SOFTSIM_APDU: { 166 | LOG_HEXDUMP_DBG(s_req->payload.data, s_req->payload.data_len, "SoftSIM APDU request"); 167 | 168 | size_t req_len = s_req->payload.data_len; 169 | size_t rsp_len = 170 | ss_command_apdu_transact(ctx, softsim_buffer_out, SIM_HAL_MAX_LE, s_req->payload.data, &req_len); 171 | 172 | err = nrf_modem_softsim_res(s_req->req, s_req->req_id, softsim_buffer_out, rsp_len); 173 | if (err) { 174 | LOG_ERR("SoftSIM APDU response failed with err: %d", err); 175 | } 176 | 177 | LOG_HEXDUMP_DBG(softsim_buffer_out, rsp_len, "SoftSIM APDU response"); 178 | break; 179 | } 180 | case NRF_MODEM_SOFTSIM_DEINIT: { 181 | LOG_DBG("SoftSIM DEINIT REQ"); 182 | 183 | if (ctx && !ss_is_suspended(ctx)) { // ignore if suspended. Then we just keep the context around 184 | ss_free_ctx(ctx); 185 | ctx = NULL; 186 | ss_deinit_fs(); // Commit any cached changes to flash 187 | } else { 188 | LOG_DBG("SoftSIM suspended. Keeping context."); 189 | } 190 | 191 | err = nrf_modem_softsim_res(s_req->req, s_req->req_id, NULL, 0); 192 | if (err) { 193 | LOG_ERR("SoftSIM DEINIT response failed with err: %d", err); 194 | } 195 | 196 | break; 197 | } 198 | case NRF_MODEM_SOFTSIM_RESET: { 199 | LOG_DBG("SoftSIM RESET"); 200 | 201 | ss_reset(ctx); 202 | 203 | err = nrf_modem_softsim_res(s_req->req, s_req->req_id, NULL, 0); 204 | 205 | if (err) { 206 | LOG_ERR("SoftSIM RESET response failed with err: %d", err); 207 | } 208 | 209 | break; 210 | } 211 | 212 | default: 213 | break; 214 | } 215 | 216 | if (s_req->payload.data) nrf_modem_softsim_data_free(s_req->payload.data); 217 | 218 | k_free(s_req); 219 | } 220 | } 221 | 222 | void nrf_modem_softsim_req_handler(enum nrf_modem_softsim_cmd req, uint16_t req_id, void *data, uint16_t data_len) 223 | { 224 | struct softsim_req_node *req_node = NULL; 225 | 226 | req_node = k_malloc(sizeof(struct softsim_req_node)); 227 | 228 | if (!req_node) { 229 | LOG_ERR("SoftSIM req_node allocation failed"); 230 | nrf_modem_softsim_err(req, req_id); 231 | return; // not good 232 | } 233 | 234 | req_node->payload.data = data; 235 | req_node->payload.data_len = data_len; 236 | req_node->req = req; 237 | req_node->req_id = req_id; 238 | 239 | k_fifo_put(&softsim_req_fifo, req_node); 240 | k_work_submit_to_queue(&softsim_work_q, &softsim_req_work); 241 | } 242 | 243 | #ifdef CONFIG_SOFTSIM_AUTO_INIT 244 | SYS_INIT(onomondo_init, APPLICATION, 0); 245 | 246 | static void ss_on_modem_lib_init(int ret, void *ctx) 247 | { 248 | int err; 249 | 250 | err = nrf_modem_at_printf("AT%%CSUS=2"); 251 | if (err) { 252 | LOG_ERR("Failed to select software SIM."); 253 | } 254 | } 255 | NRF_MODEM_LIB_ON_INIT(sim_select_hook, ss_on_modem_lib_init, NULL); 256 | #endif 257 | -------------------------------------------------------------------------------- /lib/profile/template.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onomondo/nrf-softsim/0d3a45284bbd2b6dd7a9ef91a5e556dc4dd4b5c4/lib/profile/template.bin -------------------------------------------------------------------------------- /lib/ss_cache.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "ss_cache.h" 5 | 6 | #define SS_MAX_ENTRIES (10) 7 | 8 | // find a suitable cache entry with a buffer that can be re-used 9 | struct cache_entry *f_cache_find_buffer(struct cache_entry *entry, struct ss_list *cache) 10 | { 11 | struct cache_entry *cursor; 12 | size_t min_hits_1 = 100, min_hits_2 = 100, min_hits_3 = 100; 13 | struct cache_entry *no_hits_no_write_existing_buff = 14 | NULL; // best case, no write needed, existing buffer with size >= 15 | // min_buf_size 16 | struct cache_entry *no_hits_no_write = 17 | NULL; // no write needed, existing buffer with size < min_buf_size 18 | struct cache_entry *no_hits = NULL; // write needed but low hit count 19 | 20 | size_t min_buf_size = entry->_l; 21 | size_t cached_entries = 0; 22 | 23 | SS_LIST_FOR_EACH(cache, cursor, struct cache_entry, list) { 24 | if (cursor->buf) { 25 | if (!cursor->_b_dirty && cursor->_b_size >= min_buf_size && 26 | cursor->_cache_hits < min_hits_1) { 27 | min_hits_1 = cursor->_cache_hits; 28 | no_hits_no_write_existing_buff = cursor; 29 | } 30 | if (!cursor->_b_dirty && cursor->_cache_hits < min_hits_2) { 31 | min_hits_2 = cursor->_cache_hits; 32 | no_hits_no_write = cursor; 33 | } 34 | if (cursor->_cache_hits < min_hits_3) { 35 | min_hits_3 = cursor->_cache_hits; 36 | no_hits = cursor; 37 | } 38 | cached_entries++; 39 | } 40 | } 41 | 42 | // let cache grow to SS_MAX_ENTRIES 43 | if (cached_entries < SS_MAX_ENTRIES) 44 | return NULL; 45 | 46 | if (no_hits_no_write_existing_buff) 47 | return no_hits_no_write_existing_buff; 48 | 49 | if (no_hits_no_write) 50 | return no_hits_no_write; 51 | 52 | if (no_hits) 53 | return no_hits; 54 | 55 | return NULL; 56 | } 57 | 58 | // lookup cache entry by name 59 | struct cache_entry *f_cache_find_by_name(const char *name, struct ss_list *cache) 60 | { 61 | struct cache_entry *cursor; 62 | 63 | SS_LIST_FOR_EACH(cache, cursor, struct cache_entry, list) { 64 | if (strcmp(cursor->name, name) == 0) { 65 | return cursor; 66 | } 67 | } 68 | 69 | return NULL; 70 | } 71 | -------------------------------------------------------------------------------- /lib/ss_cache.h: -------------------------------------------------------------------------------- 1 | #ifndef _F_CACHE_H_ 2 | #define _F_CACHE_H_ 3 | 4 | #include 5 | #include 6 | 7 | #define FS_READ_ONLY (1UL << 8) 8 | #define FS_COMMIT_ON_CLOSE (1UL << 7) // commit changes to NVS on close 9 | 10 | struct cache_entry { 11 | struct ss_list list; 12 | uint16_t key; // NVS key 13 | uint8_t _flags; // part of ID is used for flags // TODO 14 | uint16_t _p; // local 'file' pointer (ftell, fseek etc) 15 | uint16_t _l; // local 'file' length 16 | uint8_t *buf; // in case content is cached 17 | uint16_t _b_size; // memory allocated for buf 18 | uint8_t _b_dirty; // buf is divergent from NVS 19 | uint8_t _cache_hits; 20 | char *name; // path // key for lookup 21 | }; 22 | 23 | /** 24 | * @brief find a suitable cache entry with a buffer that can be re-used 25 | */ 26 | struct cache_entry *f_cache_find_buffer(struct cache_entry *entry, struct ss_list *cache); 27 | struct cache_entry *f_cache_find_by_name(const char *name, struct ss_list *cache); 28 | #endif /* _F_CACHE_H_ */ 29 | -------------------------------------------------------------------------------- /lib/ss_crypto.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ss_profile.h" 10 | #include "ss_crypto.h" 11 | #include 12 | #include 13 | 14 | LOG_MODULE_REGISTER(softsim_crypto, CONFIG_SOFTSIM_LOG_LEVEL); 15 | 16 | enum key_identifier_base key_id_to_kmu_slot(uint8_t key_id) { 17 | switch (key_id) { 18 | case KI_TAG: 19 | return KEY_ID_KI; 20 | case KID_TAG: 21 | return KEY_ID_KID; 22 | case KIC_TAG: 23 | return KEY_ID_KIC; 24 | default: 25 | return KEY_ID_UNKNOWN; 26 | } 27 | } 28 | 29 | #define SHARED_BUFFER_SIZE (256) 30 | static uint8_t shared_buffer[SHARED_BUFFER_SIZE]; 31 | 32 | #define ASSERT_STATUS(actual, expected) \ 33 | do { \ 34 | if ((actual) != (expected)) { \ 35 | LOG_ERR( \ 36 | "\tassertion failed at %s:%d - " \ 37 | "actual:%d expected:%d", \ 38 | __FILE__, __LINE__, (psa_status_t)actual, (psa_status_t)expected); \ 39 | goto exit; \ 40 | } \ 41 | } while (0) 42 | 43 | static psa_status_t cipher_operation(psa_cipher_operation_t *operation, const uint8_t *input, size_t input_size, 44 | size_t part_size, uint8_t *output, size_t output_size, size_t *output_len) 45 | { 46 | psa_status_t status; 47 | size_t bytes_to_write = 0, bytes_written = 0, len = 0; 48 | 49 | *output_len = 0; 50 | while (bytes_written != input_size) { 51 | bytes_to_write = (input_size - bytes_written > part_size ? part_size : input_size - bytes_written); 52 | 53 | status = psa_cipher_update(operation, input + bytes_written, bytes_to_write, output + *output_len, 54 | output_size - *output_len, &len); 55 | ASSERT_STATUS(status, PSA_SUCCESS); 56 | 57 | bytes_written += bytes_to_write; 58 | *output_len += len; 59 | } 60 | 61 | status = psa_cipher_finish(operation, output + *output_len, output_size - *output_len, &len); 62 | ASSERT_STATUS(status, PSA_SUCCESS); 63 | *output_len += len; 64 | 65 | exit: 66 | return (status); 67 | } 68 | 69 | int ss_utils_ota_calc_cc(uint8_t *cc, size_t cc_len, uint8_t *key, size_t key_len, enum enc_algorithm alg, 70 | uint8_t *data1, size_t data1_len, uint8_t *data2, size_t data2_len) 71 | { 72 | psa_mac_operation_t operation = PSA_MAC_OPERATION_INIT; 73 | enum key_identifier_base slot_id = key_id_to_kmu_slot(key[0]); 74 | 75 | psa_key_handle_t key_handle; 76 | psa_status_t status; 77 | 78 | // ONLY SUPPORTS AES_CMAC on NRF9160 as KMU doesn't support 3DES CMAC 79 | if (alg != AES_CMAC) { 80 | LOG_DBG("Only AES_CMAC supported, supplied alg: %d", alg); 81 | return -EINVAL; 82 | } 83 | 84 | LOG_DBG("CMAC key resolved to: %d", slot_id); 85 | // has to be KID 86 | if (slot_id == KEY_ID_UNKNOWN || slot_id != KEY_ID_KID) { 87 | LOG_ERR("Unknown key id: %d", key[0]); 88 | return -EINVAL; 89 | } 90 | 91 | status = psa_open_key((psa_key_id_t)slot_id, &key_handle); 92 | 93 | if (status != PSA_SUCCESS) { 94 | LOG_ERR("psa_open_key failed! (Error: %d)", status); 95 | return -EINVAL; 96 | } 97 | 98 | __ASSERT_NO_MSG(data1_len % AES_BLOCKSIZE == 0); 99 | 100 | psa_mac_operation_t mac_op; 101 | mac_op = psa_mac_operation_init(); 102 | status = psa_mac_sign_setup(&mac_op, key_handle, PSA_ALG_CMAC); 103 | 104 | uint8_t mac_buf[16]; 105 | size_t mac_len, stream_block_size = 16, bytes_processed = 0; 106 | // Guaranteed to be multiple of 16 107 | size_t blocks_data_1 = data1_len / stream_block_size; 108 | 109 | for (size_t i = 0; i < blocks_data_1; i++) { 110 | status = psa_mac_update(&mac_op, data1 + bytes_processed, stream_block_size); 111 | bytes_processed += stream_block_size; 112 | ASSERT_STATUS(status, PSA_SUCCESS); 113 | } 114 | 115 | bytes_processed = 0; // Streaming block 2 116 | while ((data2_len - bytes_processed) > stream_block_size) { 117 | status = psa_mac_update(&mac_op, data2 + bytes_processed, stream_block_size); 118 | bytes_processed += stream_block_size; 119 | 120 | ASSERT_STATUS(status, PSA_SUCCESS); 121 | } 122 | status = psa_mac_update(&mac_op, data2 + bytes_processed, data2_len - bytes_processed); 123 | ASSERT_STATUS(status, PSA_SUCCESS); 124 | 125 | status = psa_mac_sign_finish(&mac_op, mac_buf, sizeof(mac_buf), &mac_len); 126 | ASSERT_STATUS(status, PSA_SUCCESS); 127 | 128 | memcpy(cc, mac_buf, cc_len); 129 | 130 | LOG_HEXDUMP_DBG(cc, cc_len, "CMAC result OTA SMS"); 131 | 132 | exit: 133 | psa_mac_abort(&operation); 134 | return status == PSA_SUCCESS ? 0 : -EINVAL; 135 | } 136 | 137 | // we have dropped 3DES support. AES is better in all aspects except support by other operators 138 | // might be limited. 139 | void ss_utils_3des_decrypt(uint8_t *buffer, size_t buffer_len, const uint8_t *key) { return; } 140 | void ss_utils_3des_encrypt(uint8_t *buffer, size_t buffer_len, const uint8_t *key) { return; } 141 | 142 | void ss_utils_aes_decrypt(uint8_t *buffer, size_t buffer_len, const uint8_t *key, size_t key_len) 143 | { 144 | enum key_identifier_base slot_id = key_id_to_kmu_slot(key[0]); 145 | psa_key_handle_t key_handle; 146 | psa_status_t status; 147 | 148 | LOG_DBG("AES decrypt: resolved to key id: %d", slot_id); 149 | 150 | if (slot_id == KEY_ID_UNKNOWN) { 151 | LOG_ERR("Unknown key id: %d", key[0]); 152 | return; 153 | } 154 | 155 | status = psa_open_key((psa_key_id_t)slot_id, &key_handle); 156 | 157 | if (status != PSA_SUCCESS) { 158 | LOG_ERR("ss_utils_aes_decrypt: psa_open_key failed! (Error: %d)", status); 159 | return; 160 | } 161 | 162 | psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; 163 | uint8_t iv[AES_BLOCKSIZE] = {0}; // per standards in telco.. 164 | // uint8_t *decrypted_buffer = SS_ALLOC_N(buffer_len); 165 | uint8_t *decrypted_buffer = shared_buffer; 166 | 167 | uint32_t out_len = 0; 168 | 169 | status = psa_cipher_decrypt_setup(&operation, slot_id, PSA_ALG_CBC_NO_PADDING); 170 | ASSERT_STATUS(status, PSA_SUCCESS); 171 | 172 | psa_cipher_set_iv(&operation, iv, AES_BLOCKSIZE); 173 | 174 | status = 175 | cipher_operation(&operation, buffer, buffer_len, AES_BLOCKSIZE, decrypted_buffer, SHARED_BUFFER_SIZE, &out_len); 176 | ASSERT_STATUS(status, PSA_SUCCESS); 177 | 178 | memcpy(buffer, decrypted_buffer, out_len); 179 | 180 | exit: 181 | psa_cipher_abort(&operation); 182 | return; 183 | } 184 | 185 | void ss_utils_aes_encrypt(uint8_t *buffer, size_t buffer_len, const uint8_t *key, size_t key_len) 186 | { 187 | // Key derived from the fist byte of the KIC/KID key 188 | enum key_identifier_base slot_id = key_id_to_kmu_slot(key[0]); 189 | psa_key_handle_t key_handle; 190 | psa_status_t status; 191 | 192 | LOG_DBG("AES encrypt to key id: %d", slot_id); 193 | 194 | if (slot_id == KEY_ID_UNKNOWN) { 195 | printk("Unknown key id: %d", key[0]); 196 | return; 197 | } 198 | 199 | status = psa_open_key((psa_key_id_t)slot_id, &key_handle); 200 | 201 | if (status != PSA_SUCCESS) { 202 | LOG_ERR("ss_utils_aes_decrypt: psa_open_key failed! (Error: %d)", status); 203 | return; 204 | } 205 | 206 | psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; 207 | uint8_t iv[AES_BLOCKSIZE] = {0}; // per standards in telco.. 208 | // uint8_t *encrypted_buffer = SS_ALLOC_N(buffer_len); 209 | uint8_t *encrypted_buffer = shared_buffer; 210 | uint32_t out_len = 0; 211 | 212 | status = psa_cipher_encrypt_setup(&operation, key_handle, PSA_ALG_CBC_NO_PADDING); 213 | ASSERT_STATUS(status, PSA_SUCCESS); 214 | 215 | psa_cipher_set_iv(&operation, iv, AES_BLOCKSIZE); 216 | 217 | status = 218 | cipher_operation(&operation, buffer, buffer_len, AES_BLOCKSIZE, encrypted_buffer, SHARED_BUFFER_SIZE, &out_len); 219 | ASSERT_STATUS(status, PSA_SUCCESS); 220 | 221 | memcpy(buffer, encrypted_buffer, out_len); 222 | 223 | exit: 224 | psa_cipher_abort(&operation); 225 | return; 226 | } 227 | 228 | int aes_128_encrypt_block(const uint8_t *key, const uint8_t *in, uint8_t *out) 229 | { 230 | uint8_t buffer_cpy[AES_BLOCKSIZE]; 231 | memcpy(buffer_cpy, in, AES_BLOCKSIZE); 232 | ss_utils_aes_encrypt(buffer_cpy, AES_BLOCKSIZE, key, AES_BLOCKSIZE); 233 | memcpy(out, buffer_cpy, AES_BLOCKSIZE); 234 | return 0; 235 | } 236 | 237 | int ss_utils_setup_key_helper(size_t key_len, uint8_t key[static key_len], int key_id, psa_key_usage_t usage_flags, 238 | psa_algorithm_t alg, psa_key_type_t key_type) 239 | { 240 | psa_status_t status; 241 | psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; 242 | psa_key_handle_t key_handle; 243 | 244 | // Check if it exists already, if it does, destroy it so the new one can be imported 245 | status = psa_open_key(key_id, &key_handle); 246 | if (status == PSA_SUCCESS) { 247 | status = psa_destroy_key(key_handle); 248 | if (status != PSA_SUCCESS) { 249 | LOG_ERR("Failed to destroy a persistent key, ERR: %d", status); 250 | return -1; 251 | } 252 | } 253 | 254 | psa_set_key_usage_flags(&key_attributes, usage_flags); 255 | psa_set_key_algorithm(&key_attributes, alg); 256 | psa_set_key_type(&key_attributes, key_type); 257 | psa_set_key_bits(&key_attributes, 128); 258 | psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_PERSISTENT); 259 | psa_set_key_id(&key_attributes, (psa_key_id_t)key_id); 260 | status = psa_import_key(&key_attributes, key, key_len, &key_handle); 261 | 262 | if (status != PSA_SUCCESS) { 263 | LOG_ERR("Failed to import key, ERR: %d", status); 264 | psa_reset_key_attributes(&key_attributes); 265 | return -1; 266 | } 267 | 268 | psa_reset_key_attributes(&key_attributes); 269 | 270 | return 0; 271 | } 272 | 273 | int ss_utils_setup_key(size_t key_len, uint8_t key[static key_len], enum key_identifier_base key_id) 274 | { 275 | psa_status_t status; 276 | 277 | __ASSERT_NO_MSG(key_len == AES_BLOCKSIZE); 278 | __ASSERT_NO_MSG(key_id >= KEY_ID_KI && key_id <= KEY_ID_KID); 279 | LOG_DBG("Provisioning key %d...", key_id); 280 | 281 | status = psa_crypto_init(); 282 | if (status != PSA_SUCCESS) { 283 | LOG_ERR("Failed to initialize PSA crypto library. %d", status); 284 | return -1; 285 | } 286 | 287 | switch (key_id) { 288 | case KEY_ID_KI: 289 | // KI only used for encryption 290 | ss_utils_setup_key_helper(key_len, key, key_id, PSA_KEY_USAGE_ENCRYPT, PSA_ALG_CBC_NO_PADDING, PSA_KEY_TYPE_AES); 291 | break; 292 | case KEY_ID_KIC: 293 | // KIC only used for encryption/decrypt AES operations 294 | ss_utils_setup_key_helper(key_len, key, key_id, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT, 295 | PSA_ALG_CBC_NO_PADDING, PSA_KEY_TYPE_AES); 296 | 297 | break; 298 | case KEY_ID_KID: 299 | // KID only used for CMAC operations 300 | ss_utils_setup_key_helper(key_len, key, key_id, PSA_KEY_USAGE_SIGN_MESSAGE, PSA_ALG_CMAC, PSA_KEY_TYPE_AES); 301 | 302 | break; 303 | default: 304 | LOG_ERR("Unknown key id: %d", key_id); 305 | return -1; 306 | } 307 | return 0; 308 | } 309 | 310 | int ss_utils_check_key_existence(enum key_identifier_base key_id) 311 | { 312 | psa_status_t status; 313 | status = psa_open_key((psa_key_id_t)key_id, &(psa_key_handle_t){0}); 314 | LOG_DBG("Check key existence - open key returned: %d\n", status); 315 | return status == PSA_SUCCESS; 316 | } 317 | -------------------------------------------------------------------------------- /lib/ss_crypto.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define EINVAL 22 6 | #define AES_BLOCKSIZE 16 7 | 8 | // argument to calc_cc 9 | enum enc_algorithm { 10 | NONE, 11 | TRIPLE_DES_CBC2, 12 | AES_CBC, 13 | AES_CMAC, 14 | }; 15 | 16 | // SLOTS TO KEEP KEYS IN :) 17 | // TODO make configurable 18 | enum key_identifier_base { KEY_ID_KI = 10, KEY_ID_KIC = 11, KEY_ID_KID = 12, KEY_ID_UNKNOWN = 13 }; 19 | 20 | int ss_utils_ota_calc_cc(uint8_t *cc, size_t cc_len, uint8_t *key, size_t key_len, enum enc_algorithm alg, 21 | uint8_t *data1, size_t data1_len, uint8_t *data2, size_t data2_len); 22 | 23 | void ss_utils_3des_decrypt(uint8_t *buffer, size_t buffer_len, const uint8_t *key); 24 | 25 | void ss_utils_3des_encrypt(uint8_t *buffer, size_t buffer_len, const uint8_t *key); 26 | 27 | /*! Perform an in-place AES decryption with the common settings of OTA 28 | * (CBC mode, zero IV). 29 | * \param[inout] buffer user provided memory with plaintext to decrypt. 30 | * \param[in] buffer_len length of the plaintext data to decrypt (multiple of 31 | * 16). \param[in] key AES key. \param[in] key_len length of the AES key. */ 32 | void ss_utils_aes_decrypt(uint8_t *buffer, size_t buffer_len, const uint8_t *key, size_t key_len); 33 | 34 | /*! Perform an in-place AES encryption with the common settings of OTA 35 | * (CBC mode, zero IV). 36 | * \param[inout] buffer user provided memory with plaintext to encrypt. 37 | * \param[in] buffer_len length of the plaintext data to encrypt (multiple of 38 | * 16). \param[in] key 16 byte AES key. \param[in] key_len length of the AES 39 | * key. */ 40 | void ss_utils_aes_encrypt(uint8_t *buffer, size_t buffer_len, const uint8_t *key, size_t key_len); 41 | 42 | /** 43 | * @brief Setup key in KMU to be used for encryption/decryption/CC calculation 44 | * 45 | * @param key pointer to key data 46 | * @param key_len length of key data 47 | * @param key_id key identifier. Resolves into a slot in the KMU eventually. 48 | * This key id is derived during runtime by the content of the now unused 49 | * EF_A001 and EF_a004 file */ 50 | int ss_utils_setup_key(size_t key_len, uint8_t key[static key_len], enum key_identifier_base key_id); 51 | 52 | /** 53 | * @brief check if a key exists in KMU or not. Use this to check for softsim 54 | * provisioning status. 55 | * 56 | * @param key_id key identifier. Resolves into a slot in the KMU eventually. 57 | * @return int 1 if key exists, 0 if not */ 58 | int ss_utils_check_key_existence(enum key_identifier_base key_id); 59 | 60 | int aes_128_encrypt_block(const uint8_t *key, const uint8_t *in, uint8_t *out); 61 | -------------------------------------------------------------------------------- /lib/ss_fs.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ss_cache.h" 9 | #include "ss_provision.h" 10 | #include "ss_profile.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | LOG_MODULE_DECLARE(softsim, CONFIG_SOFTSIM_LOG_LEVEL); 18 | 19 | #define NVS_PARTITION nvs_storage 20 | #define NVS_PARTITION_DEVICE FIXED_PARTITION_DEVICE(NVS_PARTITION) 21 | #define NVS_PARTITION_OFFSET FIXED_PARTITION_OFFSET(NVS_PARTITION) 22 | 23 | #define DIR_ID (1UL) 24 | 25 | #define IMSI_PATH "/3f00/7ff0/6f07" 26 | #define ICCID_PATH "/3f00/2fe2" 27 | #define A001_PATH "/3f00/a001" 28 | #define A004_PATH "/3f00/a004" 29 | 30 | #ifndef SEEK_SET 31 | #define SEEK_SET 0 /* set file offset to offset */ 32 | #endif 33 | #ifndef SEEK_CUR 34 | #define SEEK_CUR 1 /* set file offset to current plus offset */ 35 | #endif 36 | #ifndef SEEK_END 37 | #define SEEK_END 2 /* set file offset to EOF plus offset */ 38 | #endif 39 | 40 | static struct nvs_fs fs; 41 | static struct ss_list fs_cache; 42 | 43 | static uint8_t fs_is_initialized = 0; 44 | 45 | // internal functions 46 | 47 | // that the buffer is set. Either by allocating new memory or by 48 | // stealing from another entry. 49 | static void ss_read_nvs_to_cache(struct cache_entry *entry); 50 | 51 | int ss_init_fs() 52 | { 53 | if (fs_is_initialized) return 0; // already initialized 54 | 55 | ss_list_init(&fs_cache); 56 | uint8_t *data = NULL; 57 | size_t len = 0; 58 | 59 | fs.flash_device = NVS_PARTITION_DEVICE; 60 | fs.sector_size = 0x1000; // where to read this? :DT_PROP(NVS_PARTITION, erase_block_size); //<0x1000> 61 | fs.sector_count = FLASH_AREA_SIZE(nvs_storage) / fs.sector_size; 62 | fs.offset = NVS_PARTITION_OFFSET; 63 | 64 | int rc = nvs_mount(&fs); 65 | if (rc) { 66 | LOG_ERR("failed to mount nvs\n"); 67 | return -1; 68 | } 69 | 70 | rc = nvs_read(&fs, DIR_ID, NULL, 0); 71 | 72 | len = rc; 73 | 74 | /************************* 75 | * Read DIR_ENTRY from NVS 76 | * This is used to construct a linked list that 77 | * serves as a cache and lookup table for the filesystem 78 | */ 79 | if (!data && rc) { 80 | data = SS_ALLOC_N(len * sizeof(uint8_t)); 81 | rc = nvs_read(&fs, DIR_ID, data, len); 82 | __ASSERT_NO_MSG(rc == len); 83 | } 84 | 85 | ss_list_init(&fs_cache); 86 | generate_dir_table_from_blob(&fs_cache, data, len); 87 | 88 | if (ss_list_empty(&fs_cache)) goto out; 89 | 90 | fs_is_initialized++; 91 | 92 | out: 93 | SS_FREE(data); 94 | return ss_list_empty(&fs_cache); 95 | } 96 | 97 | int ss_deinit_fs() 98 | { 99 | // TODO: check if DIR entry is still valid 100 | // if not recreate and write. 101 | // Will NVS only commit if there are changes? If so, we can just recreate 102 | // and let NVS do the compare. 103 | 104 | struct cache_entry *cursor, *pre_cursor; 105 | 106 | // Free all memory allocated by cache and commit changes to NVS 107 | SS_LIST_FOR_EACH_SAVE(&fs_cache, cursor, pre_cursor, struct cache_entry, list) { 108 | if (cursor->_b_dirty) { 109 | LOG_INF("Softsim stop - committing %s to NVS", cursor->name); 110 | nvs_write(&fs, cursor->key, cursor->buf, cursor->_l); 111 | } 112 | 113 | ss_list_remove(&cursor->list); 114 | 115 | if (cursor->buf) { 116 | SS_FREE(cursor->buf); 117 | } 118 | SS_FREE(cursor->name); 119 | SS_FREE(cursor); 120 | } 121 | 122 | fs_is_initialized = 0; 123 | 124 | return 0; 125 | } 126 | 127 | /** 128 | * @brief Implements a version of standard C fopen. 129 | * 130 | * @param path Full path. 131 | * @param mode Currently ignorred. 132 | * @return Pointer to a "file" represented by a struct cache_entry internally. */ 133 | port_FILE port_fopen(char *path, char *mode) 134 | { 135 | struct cache_entry *cursor = NULL; 136 | int rc = 0; 137 | 138 | cursor = f_cache_find_by_name(path, &fs_cache); 139 | if (!cursor) { 140 | return NULL; 141 | } 142 | 143 | /** 144 | * Currently not used. 145 | * Could potentially be used in the future to re-arrange order. 146 | * Initial order is ordered by frequency already so not big optimizations can 147 | * be achieved. */ 148 | if (cursor->_cache_hits < 0xFF) cursor->_cache_hits++; 149 | 150 | // File opened first time. 151 | if (!cursor->_l) { 152 | rc = nvs_read(&fs, cursor->key, NULL, 0); 153 | 154 | if (rc < 0) { // TODO: this can not happen. 155 | return NULL; 156 | } else { 157 | cursor->_l = rc; 158 | } 159 | } 160 | 161 | // Reset internal read/write pointer 162 | cursor->_p = 0; 163 | 164 | // Guarantee buffer contains valid data 165 | ss_read_nvs_to_cache(cursor); 166 | return (void *)cursor; 167 | } 168 | 169 | /** 170 | * @brief Implements a version of standard C fread. Internally it will use a 171 | * cache. 172 | * 173 | * @param ptr destinanion memory 174 | * @param size size of element 175 | * @param nmemb number of elements 176 | * @param fp file pointer 177 | * @return elements read */ 178 | size_t port_fread(void *ptr, size_t size, size_t nmemb, port_FILE fp) 179 | { 180 | if (nmemb == 0 || size == 0) { 181 | return 0; 182 | } 183 | struct cache_entry *entry = (struct cache_entry *)fp; 184 | 185 | size_t max_element_to_return = (entry->_l - entry->_p) / size; 186 | size_t element_to_return = nmemb > max_element_to_return ? max_element_to_return : nmemb; 187 | 188 | memcpy(ptr, entry->buf + entry->_p, element_to_return * size); 189 | 190 | entry->_p += element_to_return * size; // move internal read/write pointer 191 | return element_to_return; 192 | } 193 | 194 | /** 195 | * @brief Makes sure internal buffer points to the actual data by fetching it 196 | * from NVS or protected storage if needed. 197 | * 198 | * @param entry Pointer to a cache entry. 199 | */ 200 | void ss_read_nvs_to_cache(struct cache_entry *entry) 201 | { 202 | struct cache_entry *tmp; 203 | 204 | // If entry has a buffer assigned it contains valid data. Return early. 205 | if (entry->buf) return; 206 | 207 | // best bet for a buffer we can reuse 208 | tmp = f_cache_find_buffer(entry, &fs_cache); 209 | 210 | uint8_t *buffer_to_use = NULL; 211 | size_t buffer_size = 0; 212 | 213 | if (tmp) { 214 | if (tmp->_b_dirty) { 215 | LOG_DBG("Cache entry %s is dirty, writing to NVS\n", tmp->name); 216 | nvs_write(&fs, tmp->key, tmp->buf, tmp->_l); 217 | } 218 | 219 | // should buffer be freed 220 | if (entry->_l > tmp->_b_size) { 221 | SS_FREE(tmp->buf); 222 | } else { // or reused 223 | buffer_size = tmp->_b_size; 224 | buffer_to_use = tmp->buf; 225 | memset(buffer_to_use, 0, buffer_size); 226 | } 227 | 228 | tmp->buf = NULL; 229 | tmp->_b_size = 0; 230 | tmp->_b_dirty = 0; 231 | } 232 | 233 | if (!buffer_to_use) { 234 | buffer_size = entry->_l; 235 | LOG_DBG("Allocating buffer of size %d", buffer_size); 236 | buffer_to_use = SS_ALLOC_N(buffer_size * sizeof(uint8_t)); 237 | } 238 | 239 | if (!buffer_to_use) { 240 | LOG_ERR("Failed to allocate buffer of size %d", buffer_size); 241 | return; 242 | } 243 | 244 | int rc = 0; 245 | 246 | rc = nvs_read(&fs, entry->key, buffer_to_use, entry->_l); 247 | 248 | if (rc < 0) { 249 | LOG_ERR("NVS read failed: %d\n", rc); 250 | SS_FREE(buffer_to_use); 251 | return; 252 | } 253 | 254 | entry->buf = buffer_to_use; 255 | entry->_b_size = buffer_size; 256 | entry->_b_dirty = 0; 257 | } 258 | 259 | char *port_fgets(char *str, int n, port_FILE fp) { 260 | struct cache_entry *entry = (struct cache_entry *)fp; 261 | 262 | if (!entry) { 263 | return NULL; 264 | } 265 | 266 | if (entry->_p >= entry->_l) { // no more data 267 | return NULL; 268 | } 269 | 270 | int idx = 0; // dest idx 271 | 272 | while (entry->_p < entry->_l && idx < n - 1 && entry->buf[entry->_p] != '\0' && entry->buf[entry->_p] != '\n') { 273 | str[idx++] = entry->buf[entry->_p++]; 274 | } 275 | 276 | str[idx] = '\0'; 277 | return str; 278 | } 279 | 280 | int port_fclose(port_FILE fp) 281 | { 282 | struct cache_entry *entry = (struct cache_entry *)fp; 283 | 284 | if (!entry) { 285 | return -1; 286 | } 287 | 288 | if (entry->_flags & FS_READ_ONLY) { 289 | goto out; 290 | } 291 | 292 | if (entry->_flags & FS_COMMIT_ON_CLOSE) { 293 | if (entry->_b_dirty) { 294 | nvs_write(&fs, entry->key, entry->buf, entry->_l); 295 | } 296 | entry->_b_dirty = 0; 297 | } 298 | 299 | out: 300 | entry->_p = 0; // TODO: not needed? 301 | return 0; 302 | } 303 | 304 | int port_fseek(port_FILE fp, long offset, int whence) 305 | { 306 | struct cache_entry *entry = (struct cache_entry *)fp; 307 | 308 | if (!entry) { 309 | return -1; 310 | } 311 | 312 | if (whence == SEEK_SET) { 313 | entry->_p = offset; 314 | } else if (whence == SEEK_CUR) { 315 | entry->_p += offset; 316 | if (entry->_p >= entry->_l) entry->_p = entry->_l - 1; // how is std c behaving here? 317 | } else if (whence == SEEK_END) { 318 | entry->_p = entry->_l - offset; 319 | } 320 | return 0; 321 | } 322 | 323 | long port_ftell(port_FILE fp) 324 | { 325 | struct cache_entry *entry = (struct cache_entry *)fp; 326 | 327 | if (!entry) { 328 | return -1; 329 | } 330 | 331 | return entry->_p; 332 | } 333 | 334 | int port_fputc(int c, port_FILE fp) 335 | { 336 | struct cache_entry *entry = (struct cache_entry *)fp; 337 | 338 | if (!entry) { 339 | return -1; 340 | } 341 | 342 | // writing beyond the end of the buffer 343 | if (entry->_p >= entry->_b_size) { 344 | uint8_t *old_buffer = entry->buf; 345 | size_t old_size = entry->_b_size; 346 | entry->buf = SS_ALLOC_N(entry->_b_size + 20); 347 | 348 | if (!entry->buf) { 349 | entry->buf = old_buffer; 350 | return -1; 351 | } 352 | 353 | memcpy(entry->buf, old_buffer, old_size); 354 | entry->_b_size += 20; 355 | } 356 | 357 | entry->buf[entry->_p++] = (uint8_t)c; 358 | entry->_b_dirty = 1; 359 | entry->_l = entry->_l >= entry->_p ? entry->_l : entry->_p; 360 | 361 | return c; 362 | } 363 | 364 | int port_access(const char *path, int amode) 365 | { 366 | return 0; 367 | } // TODO -> safe to omit for now. Internally SoftSIM will verify that a 368 | // directory exists after creation. Easier to guarentee since it isn't a 369 | // 'thing' 370 | 371 | int port_mkdir(const char *, int) 372 | { 373 | return 0; 374 | } // don't care. We don't really obey directories (creating file 375 | // 'test/a/b/c.def) implicitly creates the directories 376 | 377 | int port_remove(const char *path) 378 | { 379 | struct cache_entry *entry = f_cache_find_by_name(path, &fs_cache); 380 | 381 | if (!entry) { 382 | return -1; 383 | } 384 | 385 | ss_list_remove(&entry->list); // doesn't free data 386 | 387 | // should we be smarter about this? 388 | nvs_delete(&fs, entry->key); 389 | 390 | if (entry->buf) SS_FREE(entry->buf); 391 | 392 | SS_FREE(entry->name); 393 | SS_FREE(entry); 394 | 395 | return 0; 396 | } 397 | 398 | // list for each { is_name_partial_match? {remove port_remove(cursor->name)} } 399 | int port_rmdir(const char *) { return 0; } // todo. Remove all entries with directory match. 400 | 401 | static uint8_t default_imsi[] = {0x08, 0x09, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10}; 402 | 403 | int port_check_provisioned() 404 | { 405 | int ret; 406 | uint8_t buffer[IMSI_LEN] = {0}; 407 | struct cache_entry *entry = (struct cache_entry *)f_cache_find_by_name(IMSI_PATH, &fs_cache); 408 | 409 | ret = nvs_read(&fs, entry->key, buffer, IMSI_LEN); 410 | if (ret < 0) { 411 | return 0; 412 | } 413 | 414 | // IMSI read is still the default one from the template 415 | if (memcmp(buffer, default_imsi, IMSI_LEN) == 0) { 416 | return 0; 417 | } 418 | 419 | return 1; 420 | } 421 | 422 | /** 423 | * @brief Provision the SoftSIM with the given profile 424 | * 425 | * @param profile ptr to the profile 426 | * @param len Len of profile. 332 otherwise invalid. 427 | */ 428 | int port_provision(struct ss_profile *profile) 429 | { 430 | int rc = ss_init_fs(); 431 | if (rc) { 432 | LOG_ERR("Failed to init FS"); 433 | } 434 | 435 | // IMSI 6f07 436 | struct cache_entry *entry = (struct cache_entry *)f_cache_find_by_name(IMSI_PATH, &fs_cache); 437 | 438 | LOG_INF("Provisioning SoftSIM 1/4"); 439 | if (nvs_write(&fs, entry->key, profile->IMSI, IMSI_LEN) < 0) goto out_err; 440 | entry->_flags = 0; 441 | 442 | LOG_INF("Provisioning SoftSIM 2/4"); 443 | entry = (struct cache_entry *)f_cache_find_by_name(ICCID_PATH, &fs_cache); 444 | if (nvs_write(&fs, entry->key, profile->ICCID, ICCID_LEN) < 0) { 445 | goto out_err; 446 | } 447 | entry->_flags = 0; 448 | 449 | LOG_INF("Provisioning SoftSIM 3/4"); 450 | entry = (struct cache_entry *)f_cache_find_by_name(A001_PATH, &fs_cache); 451 | if (nvs_write(&fs, entry->key, profile->A001, sizeof(profile->A001)) < 0) goto out_err; 452 | entry->_flags = 0; 453 | 454 | LOG_INF("Provisioning SoftSIM 4/4"); 455 | entry = (struct cache_entry *)f_cache_find_by_name(A004_PATH, &fs_cache); 456 | if (nvs_write(&fs, entry->key, profile->A004, sizeof(profile->A004)) < 0) goto out_err; 457 | entry->_flags = 0; 458 | 459 | // This is for test now. Removes uicc suspend flag as it isn't supported *yet* in softsim 460 | // Modem seems to 'deactivate' sim more often instead which isn't good :/ 461 | 462 | // Enable the following to remove the uicc suspend flag 463 | 464 | // entry = (struct cache_entry *)f_cache_find_by_name("/3f00/2f08", &fs_cache); 465 | // if (nvs_write(&fs, entry->key, "0a05000000", 10) < 0) goto out_err; 466 | // entry->_flags = 0; 467 | 468 | LOG_INF("SoftSIM provisioned"); 469 | return 0; 470 | 471 | out_err: 472 | LOG_ERR("SoftSIM provisioning failed"); 473 | return -1; 474 | } 475 | 476 | size_t port_fwrite(const void *prt, size_t size, size_t count, port_FILE f) 477 | { 478 | struct cache_entry *entry = (struct cache_entry *)f; 479 | 480 | if (!entry) { 481 | return -1; 482 | } 483 | 484 | const size_t requiredBufferSize = entry->_p + size * count; 485 | 486 | // In reality this rarely occurs. Potentially when OTA updating the SIM 487 | if (requiredBufferSize > entry->_b_size) { 488 | uint8_t *oldBuffer = entry->buf; 489 | const size_t oldSize = entry->_b_size; 490 | 491 | entry->buf = SS_ALLOC_N(requiredBufferSize); 492 | 493 | if (!entry->buf) { 494 | entry->buf = oldBuffer; 495 | return -1; 496 | } else { 497 | entry->_b_size = requiredBufferSize; 498 | } 499 | 500 | memcpy(entry->buf, oldBuffer, oldSize); 501 | SS_FREE(oldBuffer); 502 | } 503 | const size_t buffer_left = entry->_b_size - entry->_p; 504 | const size_t elements_to_copy = buffer_left > size * count ? count : buffer_left / size; 505 | 506 | const uint8_t content_is_different = memcmp(entry->buf + entry->_p, prt, size * elements_to_copy); 507 | 508 | if (content_is_different) { 509 | memcpy(entry->buf + entry->_p, prt, size * elements_to_copy); 510 | entry->_b_dirty = 1; 511 | } 512 | entry->_p += size * elements_to_copy; 513 | 514 | return elements_to_copy; 515 | } 516 | -------------------------------------------------------------------------------- /lib/ss_heap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /** 5 | * @brief custom allocator 6 | */ 7 | void *port_malloc(size_t size) 8 | { 9 | return k_malloc(size); 10 | } 11 | 12 | /** 13 | * @brief custom free 14 | */ 15 | void port_free(void * ptr) 16 | { 17 | k_free(ptr); 18 | } 19 | -------------------------------------------------------------------------------- /lib/ss_profile.c: -------------------------------------------------------------------------------- 1 | #include "ss_profile.h" 2 | #include 3 | #include 4 | 5 | static uint8_t ss_hex_to_uint8(const char *hex); 6 | static void ss_hex_string_to_bytes(const uint8_t *hex, size_t hex_len, uint8_t bytes[static hex_len / 2]); 7 | 8 | /** 9 | * ASSERTions might be a bit agressive but reasoning behind is that this abosoletely 10 | * cannot go wrong in prod. 11 | */ 12 | void decode_profile(size_t len, uint8_t data[static len], struct ss_profile *profile) 13 | { 14 | *profile = (struct ss_profile){0}; 15 | 16 | // for future compatibility we use TLV encoding for the profile 17 | // I.e. TAG | LEN | DATA[LEN] || TAG | LEN | DATA[LEN] || TAG | LEN | DATA[LEN] || ... 18 | 19 | size_t pos = 0; 20 | size_t data_end = 0; 21 | size_t data_start = 0; 22 | 23 | while (pos < len - 2) { 24 | uint8_t tag = ss_hex_to_uint8((char *)&data[pos]); 25 | uint8_t data_len = ss_hex_to_uint8((char *)&data[pos + 2]); 26 | data_start = pos + 4; 27 | 28 | data_end = data_start + data_len; 29 | 30 | // advance to next tag 31 | pos = data_end; 32 | 33 | // bad encoding. 34 | __ASSERT_NO_MSG(data_end <= len); 35 | 36 | switch (tag) { 37 | case ICCID_TAG: 38 | __ASSERT_NO_MSG(data_len == ICCID_LEN * 2); 39 | ss_hex_string_to_bytes(&data[data_start], data_len, profile->ICCID); 40 | break; 41 | case IMSI_TAG: 42 | __ASSERT_NO_MSG(data_len == IMSI_LEN * 2); 43 | ss_hex_string_to_bytes(&data[data_start], data_len, profile->IMSI); 44 | break; 45 | case OPC_TAG: 46 | __ASSERT_NO_MSG(data_len == KEY_SIZE * 2); 47 | ss_hex_string_to_bytes(&data[data_start], data_len, profile->OPC); 48 | break; 49 | // special care here as we need to load into KMU 50 | case KI_TAG: 51 | __ASSERT_NO_MSG(data_len == KEY_SIZE * 2); 52 | ss_hex_string_to_bytes(&data[data_start], data_len, profile->K); 53 | break; 54 | case KIC_TAG: 55 | __ASSERT_NO_MSG(data_len == KEY_SIZE * 2); 56 | ss_hex_string_to_bytes(&data[data_start], data_len, profile->KIC); 57 | break; 58 | case KID_TAG: 59 | __ASSERT_NO_MSG(data_len == KEY_SIZE * 2); 60 | ss_hex_string_to_bytes(&data[data_start], data_len, profile->KID); 61 | break; 62 | case SMSP_TAG: 63 | __ASSERT_NO_MSG(data_len == SMSP_RECORD_SIZE * 2); 64 | ss_hex_string_to_bytes(&data[data_start], data_len, profile->SMSP); 65 | break; 66 | case END_TAG: 67 | // end of profile 68 | pos = len; 69 | break; 70 | default: 71 | // unknown tag, skip 72 | break; 73 | } 74 | } 75 | 76 | // for now OPC must live here 77 | memcpy(&profile->A001[KEY_SIZE], profile->OPC, KEY_SIZE); 78 | // when KMU is invoked we pick key based on the tag passed. 79 | // As far as SoftSIM is concerned 0x010000...00 is the KI key. AES 80 | // implementation uses this pseudokey to invoke the KMU with correct key 81 | // slot. 82 | // KI_TAG -> 0x04 83 | profile->A001[0] = 0x4; 84 | 85 | // stucture (a004) [TAR[3] | MSL | KIC_IND | KID_IND | KIC[32] | KID[32] | 86 | // "ffff..."] 87 | char *a004_header = "b00011060101"; 88 | const size_t header_size = 12 / 2; 89 | const size_t record_size = header_size + KEY_SIZE + KEY_SIZE; 90 | ss_hex_string_to_bytes(a004_header, strlen(a004_header), profile->A004); 91 | 92 | // memcpy(profile->A004, a004_header, header_size); 93 | // memset(&profile->A004[header_size], '0', KEY_SIZE * 2); 94 | 95 | // set rest to ffff... 96 | memset(&profile->A004[record_size * 1], 0xFF, sizeof(profile->A004) - 1 * record_size); 97 | 98 | // set KIC KID tags 99 | profile->A004[header_size] = KIC_TAG; 100 | // profile->A004[header_size + 1] = '5'; 101 | 102 | // SET KID TAG 103 | profile->A004[header_size + KEY_SIZE] = KID_TAG; 104 | // profile->A004[header_size + KEY_SIZE + 1] = '6'; 105 | } 106 | 107 | uint8_t ss_hex_to_uint8(const char *hex) 108 | { 109 | char hex_str[3] = {0}; 110 | hex_str[0] = hex[0]; 111 | hex_str[1] = hex[1]; 112 | return (hex_str[0] % 32 + 9) % 25 * 16 + (hex_str[1] % 32 + 9) % 25; 113 | } 114 | 115 | void ss_hex_string_to_bytes(const uint8_t *hex, size_t hex_len, uint8_t bytes[static hex_len / 2]) 116 | { 117 | for (int i = 0; i < hex_len / 2; i++) { 118 | bytes[i] = ss_hex_to_uint8((char *)&hex[i * 2]); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/ss_profile.h: -------------------------------------------------------------------------------- 1 | #ifndef _SOFTIM_PROFILE_H_ 2 | #define _SOFTIM_PROFILE_H_ 3 | 4 | #include 5 | #include 6 | 7 | #define IMSI_LEN (18 / 2) 8 | #define A001_LEN (66 / 2) 9 | #define A004_LEN (228 / 2) 10 | #define ICCID_LEN (20 / 2) 11 | 12 | #define IMSI_TAG (0x01) 13 | #define ICCID_TAG (0x02) 14 | #define OPC_TAG (0x03) 15 | #define KI_TAG (0x04) 16 | #define KIC_TAG (0x05) 17 | #define KID_TAG (0x06) 18 | #define SMSP_TAG (0x07) // SFI '6F42' -> TODO figure out formatting 19 | #define END_TAG (0xFF) 20 | 21 | #define KEY_SIZE (32 / 2) 22 | #define KMU_KEY_SIZE (16) 23 | #define SMSP_RECORD_SIZE (52 / 2) 24 | 25 | struct ss_profile { 26 | uint8_t ICCID[ICCID_LEN]; // ICCID formatted to be written directly to FS 27 | uint8_t IMSI[IMSI_LEN]; // IMSI formatted to be written directly to FS 28 | uint8_t OPC[KEY_SIZE]; // OPC cannot go to the kmu unfortunately. 29 | uint8_t K[KMU_KEY_SIZE]; // Kc - write to KMU 30 | uint8_t KIC[KMU_KEY_SIZE]; // OTA key - write to KMU 31 | uint8_t KID[KMU_KEY_SIZE]; // OTA key - write to KMU 32 | uint8_t A001[A001_LEN]; // 32 bytes KI, 32 bytes OPC, 2 byte indication 33 | // encoded as HEX 34 | uint8_t A004[A004_LEN]; // TODO determine precise encoding scheme used. 35 | // Contains multiple recodes in theory. 36 | uint8_t SMSP[SMSP_RECORD_SIZE]; // SMS related parameters. 37 | }; 38 | 39 | /** 40 | * @brief 41 | * 42 | * @param len Len of profile 43 | * @param data Raw profile data 44 | * @param profile Memory to hold profile. Caller must allocate memory. 45 | */ 46 | void decode_profile(size_t len, uint8_t data[static len], struct ss_profile *profile); 47 | 48 | #endif // _SOFTIM_PROFILE_H_ 49 | -------------------------------------------------------------------------------- /lib/ss_provision.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "ss_cache.h" 7 | #include "ss_provision.h" 8 | #include 9 | 10 | char storage_path[] = ""; 11 | 12 | /** 13 | * @brief TODO: move this function to a more appropriate place 14 | * It is used to generate the directory structure based on the content in the 15 | * "DIR" file. The DIR file encodes ID (used to locate actual file in flash) and 16 | * name of the file. 17 | * 18 | * @param dirs linked list to populate 19 | * @param blob pointer to blob of data 20 | * @param size size of blob 21 | */ 22 | void generate_dir_table_from_blob(struct ss_list *dirs, uint8_t *blob, size_t size) 23 | { 24 | size_t cursor = 0; 25 | while (cursor < size) { 26 | uint8_t len = blob[cursor++]; 27 | uint16_t id = (blob[cursor] << 8) | blob[cursor + 1]; 28 | 29 | cursor += 2; 30 | 31 | char *name = SS_ALLOC_N(len + 1); 32 | memcpy(name, &blob[cursor], len); 33 | name[len] = '\0'; 34 | cursor += (len); 35 | 36 | struct cache_entry *entry = SS_ALLOC(struct cache_entry); 37 | memset(entry, 0, sizeof(struct cache_entry)); 38 | 39 | entry->key = id; 40 | entry->name = name; 41 | entry->_flags = (id & 0xFF00) >> 8; 42 | entry->buf = NULL; 43 | 44 | ss_list_put(dirs, &entry->list); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/ss_provision.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROVISION_H_ 2 | #define _PROVISION_H_ 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * @brief TODO: move this function to a more appropriate place 9 | * It is used to generate the directory structure based on the content in the 10 | * "DIR" file. The DIR file encodes ID (used to locate actual file in flash) and 11 | * name of the file. 12 | * 13 | * @param dirs linked list to populate 14 | * @param blob pointer to blob of data 15 | * @param size size of blob 16 | */ 17 | void generate_dir_table_from_blob(struct ss_list* dirs, uint8_t* blob, size_t size); 18 | 19 | #endif // _PROVISION_H_ 20 | -------------------------------------------------------------------------------- /overlay-softsim.conf: -------------------------------------------------------------------------------- 1 | # Onomondo SoftSIM Configurations 2 | CONFIG_SOFTSIM=y 3 | CONFIG_SOFTSIM_AUTO_INIT=y 4 | 5 | # NVS Configurations 6 | CONFIG_NVS=y 7 | CONFIG_NVS_LOG_LEVEL_ERR=y 8 | CONFIG_PM_PARTITION_SIZE_NVS_STORAGE=0x8000 9 | 10 | # Flash Configurations 11 | CONFIG_FLASH=y 12 | CONFIG_FLASH_MAP=y 13 | CONFIG_FLASH_PAGE_LAYOUT=y 14 | CONFIG_MPU_ALLOW_FLASH_WRITE=y 15 | 16 | # Stack and Heap Configurations 17 | CONFIG_MAIN_STACK_SIZE=5000 18 | CONFIG_HEAP_MEM_POOL_SIZE=30000 19 | 20 | # TF-M Configurations 21 | CONFIG_BUILD_WITH_TFM=y 22 | CONFIG_PM_PARTITION_SIZE_TFM=0x18000 23 | CONFIG_PM_PARTITION_SIZE_TFM_SRAM=0xC000 24 | CONFIG_TFM_PROFILE_TYPE_NOT_SET=y 25 | CONFIG_TFM_LOG_LEVEL_SILENCE=y 26 | 27 | # PSA Crypto Configurations 28 | CONFIG_PSA_WANT_ALG_CBC_NO_PADDING=y 29 | CONFIG_PSA_WANT_ALG_ECB_NO_PADDING=y 30 | CONFIG_PSA_WANT_ALG_CMAC=y 31 | CONFIG_PSA_WANT_KEY_TYPE_AES=y 32 | 33 | # Libraries 34 | CONFIG_NEWLIB_LIBC=y 35 | -------------------------------------------------------------------------------- /samples/softsim_external_profile/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2020 Nordic Semiconductor 3 | # 4 | # SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic 5 | # 6 | 7 | cmake_minimum_required(VERSION 3.20.0) 8 | 9 | list(APPEND OVERLAY_CONFIG "$ENV{ZEPHYR_BASE}/../modules/lib/onomondo-softsim/overlay-softsim.conf") 10 | 11 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 12 | project(softsim) 13 | 14 | target_sources(app PRIVATE src/main.c) 15 | -------------------------------------------------------------------------------- /samples/softsim_external_profile/pm_static.yml: -------------------------------------------------------------------------------- 1 | tfm_its: 2 | address: 0xf8000 3 | size: 0x2000 4 | tfm_otp_nv_counters: 5 | address: 0xfa000 6 | size: 0x2000 7 | EMPTY_tfm_ps: 8 | address: 0xfc000 9 | size: 0x4000 10 | tfm_storage: 11 | address: 0xf8000 12 | span: 13 | - EMPTY_tfm_ps 14 | - tfm_its 15 | - tfm_otp_nv_counters 16 | size: 0x8000 -------------------------------------------------------------------------------- /samples/softsim_external_profile/prj.conf: -------------------------------------------------------------------------------- 1 | ### Onomondo SoftSIM ### 2 | CONFIG_SOFTSIM_DEBUG=n 3 | CONFIG_SOFTSIM_LOG_LEVEL_ERR=y 4 | ### Onomondo SoftSIM ### 5 | 6 | # Log Configurations 7 | CONFIG_DEBUG=n 8 | CONFIG_LOG=y 9 | CONFIG_NRF_MODEM_LOG=n 10 | 11 | # Configured to allow a reboot following the provisioning of a profile 12 | CONFIG_REBOOT=y 13 | 14 | # General Configurations 15 | CONFIG_LOG_BUFFER_SIZE=5000 16 | CONFIG_LOG_PROCESS_TRIGGER_THRESHOLD=5 17 | CONFIG_LOG_DEFAULT_LEVEL=1 18 | 19 | # Modem Configurations 20 | CONFIG_NRF_MODEM_LIB=y 21 | 22 | # NET Configurations 23 | CONFIG_NETWORKING=y 24 | CONFIG_NET_SOCKETS=y 25 | CONFIG_NET_NATIVE=n 26 | CONFIG_NET_SOCKETS_OFFLOAD=y 27 | CONFIG_NET_SOCKETS_POSIX_NAMES=y 28 | 29 | # LTE Configurations 30 | CONFIG_LTE_LINK_CONTROL=y 31 | CONFIG_LTE_LC_PSM_MODULE=y 32 | CONFIG_LTE_LC_EDRX_MODULE=y 33 | 34 | # AT host library 35 | CONFIG_SERIAL=y 36 | CONFIG_AT_HOST_LIBRARY=y 37 | CONFIG_UART_INTERRUPT_DRIVEN=y 38 | 39 | # Reduce the size of TF-M by disabling the PS 40 | CONFIG_TFM_PARTITION_PROTECTED_STORAGE=n 41 | CONFIG_PSA_CRYPTO_DRIVER_CC3XX=n 42 | 43 | # Reduce the size of the TF-M partition 44 | # when combined with static partitioning 45 | CONFIG_PM_PARTITION_SIZE_TFM=0x10000 46 | 47 | # The bootloader adds a small mcuboot_pad partition, 48 | # whose size must be subtracted from the TF-M partition 49 | #CONFIG_BOOTLOADER_MCUBOOT=y 50 | #CONFIG_PM_PARTITION_SIZE_TFM=0xfe00 51 | 52 | -------------------------------------------------------------------------------- /samples/softsim_external_profile/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | LOG_MODULE_REGISTER(softsim_sample, LOG_LEVEL_INF); 26 | 27 | #define PROFILE_MIN_SIZE 180 // minimum size of a Onomondo SoftSIM profile 28 | #define PROFILE_MAX_SIZE 360 // maximum size of a Onomondo SoftSIM profile 29 | 30 | // semaphores 31 | K_SEM_DEFINE(lte_connected, 0, 1); // semaphore to signal that the LTE connection is established 32 | K_SEM_DEFINE(profile_received, 0, 1); // semaphore to signal that a profile has been received 33 | 34 | struct rx_buf_t { 35 | char *buf; 36 | size_t len; 37 | size_t pos; 38 | }; 39 | 40 | static void lte_handler(const struct lte_lc_evt *const evt); 41 | static int server_connect(void); 42 | 43 | static int client_fd; 44 | static struct sockaddr_storage host_addr; 45 | static struct k_work_delayable server_transmission_work; 46 | static const struct device *const uart_dev = DEVICE_DT_GET(DT_NODELABEL(uart0)); 47 | 48 | 49 | static void server_transmission_work_fn(struct k_work *work) 50 | { 51 | char buffer[] = "{\"message\":\"Hello from Onomondo!\"}"; 52 | 53 | int err = send(client_fd, buffer, sizeof(buffer) - 1, 0); 54 | 55 | if (err < 0) { 56 | LOG_ERR("Failed to transmit UDP packet, %d", errno); 57 | k_work_schedule(&server_transmission_work, K_SECONDS(2)); 58 | return; 59 | } 60 | 61 | k_work_schedule(&server_transmission_work, K_SECONDS(150)); 62 | } 63 | 64 | static void work_init(void) 65 | { 66 | k_work_init_delayable(&server_transmission_work, server_transmission_work_fn); 67 | } 68 | 69 | static void lte_handler(const struct lte_lc_evt *const evt) 70 | { 71 | switch (evt->type) { 72 | case LTE_LC_EVT_NW_REG_STATUS: 73 | if ((evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_HOME) && 74 | (evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING)) { 75 | break; 76 | } 77 | 78 | LOG_INF("Network registration status: %s", 79 | evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME ? "Connected - home network" : "Connected - roaming"); 80 | k_sem_give(<e_connected); 81 | break; 82 | case LTE_LC_EVT_PSM_UPDATE: 83 | LOG_INF("PSM parameter update: TAU: %d, Active time: %d", evt->psm_cfg.tau, evt->psm_cfg.active_time); 84 | break; 85 | case LTE_LC_EVT_EDRX_UPDATE: { 86 | char log_buf[60]; 87 | ssize_t len; 88 | 89 | len = snprintf(log_buf, sizeof(log_buf), "eDRX parameter update: eDRX: %f, PTW: %f", 90 | (double)evt->edrx_cfg.edrx, (double)evt->edrx_cfg.ptw); 91 | if (len > 0) { 92 | LOG_INF("%s\n", log_buf); 93 | } 94 | break; 95 | } 96 | case LTE_LC_EVT_RRC_UPDATE: 97 | LOG_INF("RRC mode: %s\n", evt->rrc_mode == LTE_LC_RRC_MODE_CONNECTED ? "Connected" : "Idle"); 98 | break; 99 | case LTE_LC_EVT_CELL_UPDATE: 100 | LOG_INF("LTE cell changed: Cell ID: %d, Tracking area: %d", evt->cell.id, evt->cell.tac); 101 | break; 102 | default: 103 | break; 104 | } 105 | } 106 | 107 | static void modem_connect(void) 108 | { 109 | int err = lte_lc_connect_async(lte_handler); 110 | if (err) { 111 | LOG_ERR("Connecting to LTE network failed, error: %d", err); 112 | return; 113 | } 114 | } 115 | 116 | static void server_disconnect(void) 117 | { 118 | (void)close(client_fd); 119 | } 120 | 121 | static int server_init(void) 122 | { 123 | struct sockaddr_in *server4 = ((struct sockaddr_in *)&host_addr); 124 | 125 | server4->sin_family = AF_INET; 126 | server4->sin_port = htons(4321); 127 | 128 | inet_pton(AF_INET, "1.2.3.4", &server4->sin_addr); 129 | 130 | return 0; 131 | } 132 | 133 | static int server_connect(void) 134 | { 135 | int err; 136 | 137 | client_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 138 | if (client_fd < 0) { 139 | LOG_ERR("Failed to create UDP socket: %d\n", errno); 140 | err = -errno; 141 | goto error; 142 | } 143 | 144 | err = connect(client_fd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr_in)); 145 | if (err < 0) { 146 | LOG_ERR("Connect failed : %d\n", errno); 147 | goto error; 148 | } 149 | 150 | return 0; 151 | 152 | error: 153 | server_disconnect(); 154 | return err; 155 | } 156 | 157 | void serial_cb(const struct device *dev, void *user_data) 158 | { 159 | int rx_recv = 0; 160 | struct rx_buf_t *rx = (struct rx_buf_t *)user_data; 161 | char *rx_buf = rx->buf; 162 | size_t *rx_buf_pos = &rx->pos; 163 | 164 | if (!uart_irq_update(uart_dev)) { 165 | return; 166 | } 167 | 168 | while (uart_irq_rx_ready(uart_dev)) { 169 | rx_recv = uart_fifo_read(uart_dev, &rx_buf[*rx_buf_pos], 1); 170 | 171 | if ((rx_buf[*rx_buf_pos] == '\n') || // search for those end of line characters 172 | (rx_buf[*rx_buf_pos] == '\r')) { 173 | rx_buf[*rx_buf_pos] = 0; 174 | k_sem_give(&profile_received); 175 | return; 176 | } 177 | 178 | *rx_buf_pos += rx_recv; 179 | } 180 | } 181 | 182 | int main(void) 183 | { 184 | LOG_INF("SoftSIM sample started."); 185 | 186 | if (!nrf_softsim_check_provisioned()) { 187 | if (!device_is_ready(uart_dev)) { 188 | LOG_ERR("UART device not found!"); 189 | return -1; 190 | } 191 | 192 | char *profile_read_from_external_source = k_malloc(PROFILE_MAX_SIZE); 193 | __ASSERT_NO_MSG(profile_read_from_external_source != NULL); 194 | 195 | struct rx_buf_t rx = { 196 | .buf = profile_read_from_external_source, 197 | .len = PROFILE_MAX_SIZE, 198 | .pos = 0, 199 | }; 200 | 201 | uart_irq_callback_user_data_set(uart_dev, serial_cb, &rx); 202 | uart_irq_rx_enable(uart_dev); 203 | 204 | do { 205 | LOG_INF("Transfer SoftSIM profile using serial COM port, terminate by newline character (return key)"); 206 | } while (k_sem_take(&profile_received, K_SECONDS(20))); 207 | 208 | LOG_INF("Profile received: %d characters in total", rx.pos); 209 | 210 | uart_irq_rx_disable(uart_dev); 211 | 212 | // the profile is now in the buffer, provision it to the SoftSIM filesystem 213 | if(nrf_softsim_provision((uint8_t *)profile_read_from_external_source, rx.pos) != 0) { 214 | LOG_ERR("SoftSIM Profile provisioning failed"); 215 | } 216 | 217 | // clean up the decrypted profile buffer, ensuring no sensitive data is left in memory 218 | if (profile_read_from_external_source != NULL) { 219 | k_free(profile_read_from_external_source); 220 | } 221 | 222 | // soft reset to free uart for AT host/monitor 223 | while (log_data_pending()) { 224 | log_process(); 225 | k_yield(); 226 | } 227 | sys_reboot(0); 228 | } 229 | 230 | int32_t err = nrf_modem_lib_init(); 231 | if (err) { 232 | LOG_ERR("Failed to initialize modem library, error: %d\n", err); 233 | } 234 | 235 | work_init(); 236 | 237 | modem_connect(); 238 | 239 | LOG_INF("Waiting for LTE connect event.\n"); 240 | do { 241 | } while (k_sem_take(<e_connected, K_SECONDS(10))); 242 | 243 | LOG_INF("LTE connected!\n"); 244 | err = server_init(); 245 | if (err) { 246 | LOG_ERR("Not able to initialize UDP server connection\n"); 247 | return -1; 248 | } 249 | 250 | err = server_connect(); 251 | if (err) { 252 | LOG_ERR("Not able to connect to UDP server\n"); 253 | return -1; 254 | } 255 | 256 | k_work_schedule(&server_transmission_work, K_NO_WAIT); 257 | } 258 | -------------------------------------------------------------------------------- /samples/softsim_external_profile/sysbuild.conf: -------------------------------------------------------------------------------- 1 | SB_CONFIG_SOFTSIM_BUNDLE_TEMPLATE_HEX=y 2 | -------------------------------------------------------------------------------- /samples/softsim_static_profile/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2020 Nordic Semiconductor 3 | # 4 | # SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic 5 | # 6 | 7 | cmake_minimum_required(VERSION 3.20.0) 8 | 9 | list(APPEND OVERLAY_CONFIG "$ENV{ZEPHYR_BASE}/../modules/lib/onomondo-softsim/overlay-softsim.conf") 10 | 11 | find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 12 | project(softsim) 13 | 14 | target_sources(app PRIVATE src/main.c) 15 | -------------------------------------------------------------------------------- /samples/softsim_static_profile/pm_static.yml: -------------------------------------------------------------------------------- 1 | tfm_its: 2 | address: 0xf8000 3 | size: 0x2000 4 | tfm_otp_nv_counters: 5 | address: 0xfa000 6 | size: 0x2000 7 | EMPTY_tfm_ps: 8 | address: 0xfc000 9 | size: 0x4000 10 | tfm_storage: 11 | address: 0xf8000 12 | span: 13 | - EMPTY_tfm_ps 14 | - tfm_its 15 | - tfm_otp_nv_counters 16 | size: 0x8000 -------------------------------------------------------------------------------- /samples/softsim_static_profile/prj.conf: -------------------------------------------------------------------------------- 1 | ### Onomondo SoftSIM ### 2 | CONFIG_SOFTSIM_DEBUG=n 3 | CONFIG_SOFTSIM_LOG_LEVEL_ERR=y 4 | CONFIG_SOFTSIM_STATIC_PROFILE_ENABLE=y 5 | # GSMA TS.48 USIM profile (standard test profile) replace with actual Onomondo profile 6 | CONFIG_SOFTSIM_STATIC_PROFILE="01120809101010325406360214980010325476981032140320000000000000000000000000000000000420000102030405060708090A0B0C0D0E0F0520000102030405060708090A0B0C0D0E0F0620000102030405060708090A0B0C0D0E0F" 7 | ### Onomondo SoftSIM ### 8 | 9 | # Log Configurations 10 | CONFIG_DEBUG=n 11 | CONFIG_LOG=y 12 | CONFIG_NRF_MODEM_LOG=n 13 | 14 | # General Configurations 15 | CONFIG_LOG_BUFFER_SIZE=5000 16 | CONFIG_LOG_PROCESS_TRIGGER_THRESHOLD=5 17 | CONFIG_LOG_DEFAULT_LEVEL=1 18 | 19 | # Modem Configurations 20 | CONFIG_NRF_MODEM_LIB=y 21 | 22 | # NET Configurations 23 | CONFIG_NETWORKING=y 24 | CONFIG_NET_SOCKETS=y 25 | CONFIG_NET_NATIVE=n 26 | CONFIG_NET_SOCKETS_OFFLOAD=y 27 | CONFIG_NET_SOCKETS_POSIX_NAMES=y 28 | 29 | # LTE Configurations 30 | CONFIG_LTE_LINK_CONTROL=y 31 | CONFIG_LTE_LC_PSM_MODULE=y 32 | CONFIG_LTE_LC_EDRX_MODULE=y 33 | 34 | # AT host library 35 | CONFIG_SERIAL=y 36 | CONFIG_AT_HOST_LIBRARY=y 37 | CONFIG_UART_INTERRUPT_DRIVEN=y 38 | 39 | # Reduce the size of TF-M by disabling the PS 40 | CONFIG_TFM_PARTITION_PROTECTED_STORAGE=n 41 | CONFIG_PSA_CRYPTO_DRIVER_CC3XX=n 42 | 43 | # Reduce the size of the TF-M partition 44 | # when combined with static partitioning 45 | CONFIG_PM_PARTITION_SIZE_TFM=0x10000 46 | 47 | # The bootloader adds a small mcuboot_pad partition, 48 | # whose size must be subtracted from the TF-M partition 49 | #CONFIG_BOOTLOADER_MCUBOOT=y 50 | #CONFIG_PM_PARTITION_SIZE_TFM=0xFE00 51 | 52 | -------------------------------------------------------------------------------- /samples/softsim_static_profile/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | LOG_MODULE_REGISTER(softsim_sample, LOG_LEVEL_INF); 24 | 25 | // semaphores 26 | K_SEM_DEFINE(lte_connected, 0, 1); // semaphore to signal that the LTE connection is established 27 | 28 | static void lte_handler(const struct lte_lc_evt *const evt); 29 | static int server_connect(void); 30 | 31 | static int client_fd; 32 | static struct sockaddr_storage host_addr; 33 | static struct k_work_delayable server_transmission_work; 34 | 35 | 36 | static void server_transmission_work_fn(struct k_work *work) 37 | { 38 | char buffer[] = "{\"message\":\"Hello from Onomondo!\"}"; 39 | 40 | int err = send(client_fd, buffer, sizeof(buffer) - 1, 0); 41 | 42 | if (err < 0) { 43 | LOG_ERR("Failed to transmit UDP packet, %d", errno); 44 | k_work_schedule(&server_transmission_work, K_SECONDS(2)); 45 | return; 46 | } 47 | 48 | k_work_schedule(&server_transmission_work, K_SECONDS(150)); 49 | } 50 | 51 | static void work_init(void) 52 | { 53 | k_work_init_delayable(&server_transmission_work, server_transmission_work_fn); 54 | } 55 | 56 | static void lte_handler(const struct lte_lc_evt *const evt) 57 | { 58 | switch (evt->type) { 59 | case LTE_LC_EVT_NW_REG_STATUS: 60 | if ((evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_HOME) && 61 | (evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING)) { 62 | break; 63 | } 64 | 65 | LOG_INF("Network registration status: %s", 66 | evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME ? "Connected - home network" : "Connected - roaming"); 67 | k_sem_give(<e_connected); 68 | break; 69 | case LTE_LC_EVT_PSM_UPDATE: 70 | LOG_INF("PSM parameter update: TAU: %d, Active time: %d", evt->psm_cfg.tau, evt->psm_cfg.active_time); 71 | break; 72 | case LTE_LC_EVT_EDRX_UPDATE: { 73 | char log_buf[60]; 74 | ssize_t len; 75 | 76 | len = snprintf(log_buf, sizeof(log_buf), "eDRX parameter update: eDRX: %f, PTW: %f", 77 | (double)evt->edrx_cfg.edrx, (double)evt->edrx_cfg.ptw); 78 | if (len > 0) { 79 | LOG_INF("%s\n", log_buf); 80 | } 81 | break; 82 | } 83 | case LTE_LC_EVT_RRC_UPDATE: 84 | LOG_INF("RRC mode: %s\n", evt->rrc_mode == LTE_LC_RRC_MODE_CONNECTED ? "Connected" : "Idle"); 85 | break; 86 | case LTE_LC_EVT_CELL_UPDATE: 87 | LOG_INF("LTE cell changed: Cell ID: %d, Tracking area: %d", evt->cell.id, evt->cell.tac); 88 | break; 89 | default: 90 | break; 91 | } 92 | } 93 | 94 | static void modem_connect(void) 95 | { 96 | int err = lte_lc_connect_async(lte_handler); 97 | if (err) { 98 | LOG_ERR("Connecting to LTE network failed, error: %d", err); 99 | return; 100 | } 101 | } 102 | 103 | static void server_disconnect(void) 104 | { 105 | (void)close(client_fd); 106 | } 107 | 108 | static int server_init(void) 109 | { 110 | struct sockaddr_in *server4 = ((struct sockaddr_in *)&host_addr); 111 | 112 | server4->sin_family = AF_INET; 113 | server4->sin_port = htons(4321); 114 | 115 | inet_pton(AF_INET, "1.2.3.4", &server4->sin_addr); 116 | 117 | return 0; 118 | } 119 | 120 | static int server_connect(void) 121 | { 122 | int err; 123 | 124 | client_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 125 | if (client_fd < 0) { 126 | LOG_ERR("Failed to create UDP socket: %d\n", errno); 127 | err = -errno; 128 | goto error; 129 | } 130 | 131 | err = connect(client_fd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr_in)); 132 | if (err < 0) { 133 | LOG_ERR("Connect failed : %d\n", errno); 134 | goto error; 135 | } 136 | 137 | return 0; 138 | 139 | error: 140 | server_disconnect(); 141 | return err; 142 | } 143 | 144 | int main(void) 145 | { 146 | LOG_INF("SoftSIM sample started."); 147 | 148 | int32_t err = nrf_modem_lib_init(); 149 | if (err) { 150 | LOG_ERR("Failed to initialize modem library, error: %d\n", err); 151 | } 152 | 153 | work_init(); 154 | 155 | modem_connect(); 156 | 157 | LOG_INF("Waiting for LTE connect event.\n"); 158 | do { 159 | } while (k_sem_take(<e_connected, K_SECONDS(10))); 160 | 161 | LOG_INF("LTE connected!\n"); 162 | err = server_init(); 163 | if (err) { 164 | LOG_ERR("Not able to initialize UDP server connection\n"); 165 | return -1; 166 | } 167 | 168 | err = server_connect(); 169 | if (err) { 170 | LOG_ERR("Not able to connect to UDP server\n"); 171 | return -1; 172 | } 173 | 174 | k_work_schedule(&server_transmission_work, K_NO_WAIT); 175 | } 176 | -------------------------------------------------------------------------------- /samples/softsim_static_profile/sysbuild.conf: -------------------------------------------------------------------------------- 1 | SB_CONFIG_SOFTSIM_BUNDLE_TEMPLATE_HEX=y 2 | -------------------------------------------------------------------------------- /sysbuild/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | zephyr_get(ZEPHYR_SDK_INSTALL_DIR) 2 | 3 | set(ONOMONDO_SOFTSIM_BASE_ADDRESS $) 4 | set(ONOMONDO_SOFTSIM_TEMPLATE_OUTPUT ${CMAKE_BINARY_DIR}/onomondo-softsim/template.hex) 5 | 6 | add_custom_command( 7 | OUTPUT template.hex 8 | COMMAND ${ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/bin/arm-zephyr-eabi-objcopy 9 | --input-target=binary 10 | --output-target=ihex 11 | --change-address ${ONOMONDO_SOFTSIM_BASE_ADDRESS} 12 | ${CMAKE_CURRENT_LIST_DIR}/../lib/profile/template.bin 13 | ${ONOMONDO_SOFTSIM_TEMPLATE_OUTPUT} 14 | BYPRODUCTS ${ONOMONDO_SOFTSIM_TEMPLATE_OUTPUT} # This is needed for Ninja 15 | ) 16 | add_custom_target(onomondo_softsim_template DEPENDS template.hex) 17 | 18 | if(SB_CONFIG_SOFTSIM_BUNDLE_TEMPLATE_HEX) 19 | set_property( 20 | GLOBAL PROPERTY 21 | nvs_storage_PM_HEX_FILE 22 | ${ONOMONDO_SOFTSIM_TEMPLATE_OUTPUT} 23 | ) 24 | 25 | set_property( 26 | GLOBAL PROPERTY 27 | nvs_storage_PM_TARGET 28 | onomondo_softsim_template 29 | ) 30 | endif() 31 | 32 | function(${SYSBUILD_CURRENT_MODULE_NAME}_pre_cmake) 33 | # Set the default NCS static partition layout for the Thingy:91 34 | if(SB_CONFIG_THINGY91_STATIC_PARTITIONS_FACTORY) 35 | set(PM_STATIC_YML_FILE ${SYSBUILD_SOFTSIM_MODULE_DIR}/boards/thingy91_pm_static.yml CACHE INTERNAL "") 36 | endif() 37 | 38 | # Set the default NCS static partition layout for the Thingy:91 X 39 | if(SB_CONFIG_BOARD_THINGY91X_NRF9151_NS AND SB_CONFIG_THINGY91X_STATIC_PARTITIONS_FACTORY) 40 | set(PM_STATIC_YML_FILE ${SYSBUILD_SOFTSIM_MODULE_DIR}/boards/thingy91x_nrf9151_pm_static.yml CACHE INTERNAL "") 41 | endif() 42 | endfunction(${SYSBUILD_CURRENT_MODULE_NAME}_pre_cmake) 43 | -------------------------------------------------------------------------------- /sysbuild/Kconfig: -------------------------------------------------------------------------------- 1 | menu "Onomondo SoftSIM Options" 2 | 3 | config SOFTSIM_BUNDLE_TEMPLATE_HEX 4 | bool "Automatically bundle the template profile hex into the application" 5 | default n 6 | 7 | endmenu 8 | -------------------------------------------------------------------------------- /west.yml: -------------------------------------------------------------------------------- 1 | manifest: 2 | remotes: 3 | - name: nrfconnect 4 | url-base: https://github.com/nrfconnect/ 5 | projects: 6 | - name: sdk-nrf 7 | path: nrf 8 | revision: v2.9.1 9 | remote: nrfconnect 10 | import: true 11 | self: 12 | path: modules/lib/onomondo-softsim 13 | -------------------------------------------------------------------------------- /zephyr/module.yml: -------------------------------------------------------------------------------- 1 | name: softsim 2 | 3 | build: 4 | cmake: . 5 | kconfig: ./Kconfig 6 | sysbuild-cmake: sysbuild 7 | sysbuild-kconfig: sysbuild/Kconfig 8 | depends: 9 | - trusted-firmware-m 10 | 11 | samples: 12 | - samples 13 | --------------------------------------------------------------------------------