[
  {
    "path": ".github/workflows/documentation.yml",
    "content": "# matth-x/MicroOcpp\n# Copyright Matthias Akstaller 2019 - 2024\n# MIT License\n\nname: Documentation\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\npermissions:\n  contents: write\n\njobs:\n  build_simulator:\n    name: Build Simulator\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - uses: actions/setup-python@v4\n      with:\n        python-version: 3.x\n    - uses: actions/cache@v4\n      with:\n        key: ${{ github.ref }}\n        path: .cache\n    - name: Get build tools\n      run: |\n        sudo apt update\n        sudo apt install cmake libssl-dev build-essential\n    - name: Checkout Simulator\n      uses: actions/checkout@v3\n      with:\n        repository: matth-x/MicroOcppSimulator\n        path: MicroOcppSimulator\n        ref: 2cb07cdbe53954a694a29336ab31eac2d2b48673\n        submodules: 'recursive'\n    - name: Clean MicroOcpp submodule\n      run: |\n        rm -rf MicroOcppSimulator/lib/MicroOcpp\n    - name: Checkout MicroOcpp submodule\n      uses: actions/checkout@v3\n      with:\n        path: MicroOcppSimulator/lib/MicroOcpp\n    - name: Generate CMake files\n      run: cmake -S ./MicroOcppSimulator -B ./MicroOcppSimulator/build -DCMAKE_CXX_FLAGS=\"-DMO_OVERRIDE_ALLOCATION=1 -DMO_ENABLE_HEAP_PROFILER=1\"\n    - name: Compile\n      run: cmake --build ./MicroOcppSimulator/build -j 32 --target mo_simulator\n    - name: Upload Simulator executable\n      uses: actions/upload-artifact@v4\n      with:\n        name: Simulator executable\n        path: |\n          MicroOcppSimulator/build/mo_simulator\n          MicroOcppSimulator/public/bundle.html.gz\n        if-no-files-found: error\n        retention-days: 1\n\n  measure_heap:\n    needs: build_simulator\n    name: Heap measurements\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - uses: actions/setup-python@v4\n      with:\n        python-version: 3.x\n    - name: Install Python dependencies\n      run: pip install requests paramiko pandas\n    - name: Get Simulator\n      uses: actions/download-artifact@v4\n      with:\n        name: Simulator executable\n        path: MicroOcppSimulator\n    - name: Measure heap and create reports\n      run: |\n        mkdir -p docs/assets/tables\n        python tests/benchmarks/scripts/measure_heap.py\n      env:\n        TEST_DRIVER_URL: ${{ secrets.TEST_DRIVER_URL }}\n        TEST_DRIVER_CONFIG: ${{ secrets.TEST_DRIVER_CONFIG }}\n        TEST_DRIVER_KEY: ${{ secrets.TEST_DRIVER_KEY }}\n        MO_SIM_CONFIG: ${{ secrets.MO_SIM_CONFIG }}\n        MO_SIM_OCPP_SERVER: ${{ secrets.MO_SIM_OCPP_SERVER }}\n        MO_SIM_API_CERT: ${{ secrets.MO_SIM_API_CERT }}\n        MO_SIM_API_KEY: ${{ secrets.MO_SIM_API_KEY }}\n        MO_SIM_API_CONFIG: ${{ secrets.MO_SIM_API_CONFIG }}\n        SSH_LOCAL_PRIV: ${{ secrets.SSH_LOCAL_PRIV }}\n        SSH_HOST_PUB: ${{ secrets.SSH_HOST_PUB }}\n    - name: Upload reports\n      uses: actions/upload-artifact@v4\n      with:\n        name: Memory usage reports CSV\n        path: docs/assets/tables\n        if-no-files-found: error\n\n  build_firmware_size:\n    name: Build firmware\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - name: Cache pip\n      uses: actions/cache@v4\n      with:\n        path: ~/.cache/pip\n        key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}\n        restore-keys: |\n          ${{ runner.os }}-pip-\n    - name: Cache PlatformIO\n      uses: actions/cache@v4\n      with:\n        path: ~/.platformio\n        key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}\n    - name: Set up Python\n      uses: actions/setup-python@v4\n    - name: Install PlatformIO\n      run: |\n        python -m pip install --upgrade pip\n        pip install --upgrade platformio\n    - name: Run PlatformIO\n      run: pio ci --lib=\".\" --build-dir=\"${{ github.workspace }}/../build\" --keep-build-dir --project-conf=\"./tests/benchmarks/firmware_size/platformio.ini\" ./tests/benchmarks/firmware_size/main.cpp\n    - name: Move firmware files # change path to location without parent dir ('..') statement (to make upload-artifact happy)\n      run: |\n        mkdir firmware\n        mv \"${{ github.workspace }}/../build/.pio/build/v16/firmware.elf\"  firmware/firmware_v16.elf\n        mv \"${{ github.workspace }}/../build/.pio/build/v201/firmware.elf\" firmware/firmware_v201.elf\n    - name: Upload firmware linker files\n      uses: actions/upload-artifact@v4\n      with:\n        name: Firmware linker files\n        path: firmware\n        if-no-files-found: error\n        retention-days: 1\n\n  evaluate_firmware:\n    needs: build_firmware_size\n    name: Static firmware analysis\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - uses: actions/setup-python@v4\n      with:\n        python-version: 3.x\n    - uses: actions/cache@v4\n      with:\n        key: ${{ github.ref }}\n        path: .cache\n    - name: Install Python dependencies\n      run: pip install pandas \n    - name: Get build tools\n      run: |\n        sudo apt update\n        sudo apt install build-essential cmake ninja-build\n        sudo apt -y install gcc-9 g++-9\n        g++ --version\n    - name: Check out bloaty\n      uses: actions/checkout@v3\n      with:\n        repository: google/bloaty\n        ref: e1155149d54bb09b81e86f0e4e5cb7fbd2a318eb\n        path: tools/bloaty\n        submodules: recursive\n    - name: Install bloaty\n      run: |\n        cmake -B tools/bloaty/build -G Ninja -S tools/bloaty\n        cmake --build tools/bloaty/build -j 32\n    - name: Get firmware linker files\n      uses: actions/download-artifact@v4\n      with:\n        name: Firmware linker files\n        path: firmware\n    - name: Run bloaty\n      run: |\n        mkdir -p docs/assets/tables\n        tools/bloaty/build/bloaty firmware/firmware_v16.elf  -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v16.csv\n        tools/bloaty/build/bloaty firmware/firmware_v201.elf -d compileunits --csv -n 0 > docs/assets/tables/bloaty_v201.csv\n    - name: Evaluate and create reports\n      run: python tests/benchmarks/scripts/eval_firmware_size.py\n    - name: Upload reports\n      uses: actions/upload-artifact@v4\n      with:\n        name: Firmware size reports CSV\n        path: docs/assets/tables\n        if-no-files-found: error\n\n  deploy:\n    needs: [evaluate_firmware, measure_heap]\n    name: Deploy docs\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/main'\n    steps:\n    - uses: actions/checkout@v3\n    - uses: actions/setup-python@v4\n      with:\n        python-version: 3.x\n    - uses: actions/cache@v4\n      with:\n        key: ${{ github.ref }}\n        path: .cache\n    - name: Install Python dependencies\n      run: pip install pandas mkdocs-material mkdocs-table-reader-plugin\n    - name: Get firmware size reports\n      uses: actions/download-artifact@v4\n      with:\n        name: Firmware size reports CSV\n        path: docs/assets/tables\n    - name: Get memory occupation reports\n      uses: actions/download-artifact@v4\n      with:\n        name: Memory usage reports CSV\n        path: docs/assets/tables\n    - name: Run mkdocs\n      run: mkdocs gh-deploy --force\n"
  },
  {
    "path": ".github/workflows/esp-idf.yml",
    "content": "# matth-x/MicroOcpp\n# Copyright Matthias Akstaller 2019 - 2024\n# MIT License\n\nname: ESP-IDF CI\n\non:\n  push:\n    branches:\n      - main\n\n  pull_request:\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout ESP-IDF example folder structure\n      uses: actions/checkout@v3\n      with:\n        sparse-checkout: examples/ESP-IDF\n    - name: Clean sumodules folders template\n      run: rm -r ./examples/ESP-IDF/components/*\n    - name: Checkout main repo\n      uses: actions/checkout@v3\n      with:\n        path: examples/ESP-IDF/components/MicroOcpp\n    - name: Checkout Mongoose\n      uses: actions/checkout@v3\n      with:\n        repository: cesanta/mongoose-esp-idf\n        path: examples/ESP-IDF/components/mongoose\n        submodules: 'recursive'\n    - name: Checkout Mongoose WS adapter\n      uses: actions/checkout@v3\n      with:\n        repository: matth-x/MicroOcppMongoose\n        ref: v1.2.0\n        path: examples/ESP-IDF/components/MicroOcppMongoose\n    - name: Checkout ArduinoJson\n      uses: actions/checkout@v3\n      with:\n        repository: bblanchon/ArduinoJson\n        ref: 3e1be980d93e47b2a0073efeeb9a9396fd7a83be\n        path: examples/ESP-IDF/components/ArduinoJson\n    - name: esp-idf build\n      uses: espressif/esp-idf-ci-action@v1\n      with:\n        esp_idf_version: v4.4\n        target: esp32\n        path: './examples/ESP-IDF'\n"
  },
  {
    "path": ".github/workflows/pio.yml",
    "content": "# matth-x/MicroOcpp\n# Copyright Matthias Akstaller 2019 - 2024\n# MIT License\n\nname: PlatformIO CI\n\non:\n  push:\n    branches:\n      - main\n\n  pull_request:\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        example: [examples/ESP/main.cpp, examples/ESP-TLS/main.cpp]\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Cache pip\n      uses: actions/cache@v4\n      with:\n        path: ~/.cache/pip\n        key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}\n        restore-keys: |\n          ${{ runner.os }}-pip-\n    - name: Cache PlatformIO\n      uses: actions/cache@v4\n      with:\n        path: ~/.platformio\n        key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}\n    - name: Set up Python\n      uses: actions/setup-python@v4\n    - name: Install PlatformIO\n      run: |\n        python -m pip install --upgrade pip\n        pip install --upgrade platformio\n    - name: Install library dependencies\n      run: pio pkg install\n    - name: Run PlatformIO\n      run: pio ci --lib=\".\" --project-conf=platformio.ini ${{ matrix.dashboard-extra }}\n      env:\n        PLATFORMIO_CI_SRC: ${{ matrix.example }}\n"
  },
  {
    "path": ".github/workflows/platformless.yml",
    "content": "# matth-x/MicroOcpp\n# Copyright Matthias Akstaller 2019 - 2024\n# MIT License\n\nname: Default Compilation\n\non:\n  push:\n    branches:\n      - main\n\n  pull_request:\n\njobs:\n\n  compile-platform:\n    name: Compile (no linking)\n    runs-on: ubuntu-latest\n    steps:\n    - name: Check out repository code\n      uses: actions/checkout@v3\n    - name: Get gcc compiler\n      run: |\n        sudo apt update\n        sudo apt install build-essential\n        sudo apt -y install gcc-9 g++-9\n        g++ --version\n        echo \"g++ version must be 9.4.0\"\n    - name: Get ArduinoJson\n      run: wget -Uri https://github.com/bblanchon/ArduinoJson/releases/download/v6.19.4/ArduinoJson-v6.19.4.h -O ./src/ArduinoJson.h\n    - name: Compile\n      run: g++ -c -std=c++11 -I ./src $(find ./src -type f -iregex \".*\\.cpp\") -DMO_PLATFORM=MO_PLATFORM_NONE -Wall -Wextra -Wno-unused-parameter -Wno-redundant-move -Werror\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "# matth-x/MicroOcpp\n# Copyright Matthias Akstaller 2019 - 2024\n# MIT License\n\nname: Unit tests\n\non:\n  push:\n    branches:\n      - main\n\n  pull_request:\n\njobs:\n\n  compile-and-run:\n    name: Automated Tests\n    runs-on: ubuntu-latest\n    steps:\n    - name: Check out repository code\n      uses: actions/checkout@v3\n    - name: Check out MbedTLS\n      uses: actions/checkout@v3\n      with:\n        repository: Mbed-TLS/mbedtls\n        ref: v2.28.10\n        path: lib/mbedtls\n    - name: Get build tools\n      run: |\n        sudo apt update\n        sudo apt install build-essential cmake lcov valgrind\n        sudo apt -y install gcc-9 g++-9\n        g++ --version\n        echo \"g++ version must be 9.4.0\"\n    - name: Get ArduinoJson\n      run: wget -Uri https://github.com/bblanchon/ArduinoJson/releases/download/v6.21.3/ArduinoJson-v6.21.3.h -O ./src/ArduinoJson.h\n    - name: Generate CMake build files\n      run: cmake -S . -B ./build -DMO_BUILD_UNIT_MBEDTLS=True\n    - name: Compile\n      run: cmake --build ./build -j 32 --target mo_unit_tests\n    - name: Configure FS\n      run: mkdir mo_store\n    - name: Run tests (valgrind)\n      run: valgrind --error-exitcode=1 --leak-check=full ./build/mo_unit_tests --abort\n    - name: Generate CMake build files (AddressSanitizer, UndefinedBehaviorSanitizer)\n      run: |\n        rm -r ./build\n        cmake -S . -B ./build -DCMAKE_CXX_FLAGS=\"-fsanitize=address -fsanitize=undefined\" -DCMAKE_EXE_LINKER_FLAGS=\"-fsanitize=address -fsanitize=undefined\" -DMO_BUILD_UNIT_MBEDTLS=True\n    - name: Compile (ASan, UBSan)\n      run: cmake --build ./build -j 32 --target mo_unit_tests\n    - name: Run tests (ASan, UBSan)\n      run: ./build/mo_unit_tests --abort\n    - name: Create coverage report\n      run: |\n        lcov --directory . --capture --output-file coverage.info --ignore-errors mismatch\n        lcov --remove coverage.info '/usr/*' '*/tests/*' '*/ArduinoJson.h' --output-file coverage.info\n        lcov --list coverage.info\n    - name: Upload coverage reports to Codecov\n      uses: codecov/codecov-action@v3\n      env:\n        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".pio\n.vscode\nbuild\nlib\nmo_store\nsrc/ArduinoJson*\nsrc/main.cpp\ntests/helpers/ArduinoJson*\ncoverage.info\ndocs/assets\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## Unreleased\n\n### Changed\n\n- Change `MicroOcpp::TxNotification` into C-style enum, replace `OCPP_TxNotication` ([#386](https://github.com/matth-x/MicroOcpp/pull/386))\n- Improved UUID generation ([#383](https://github.com/matth-x/MicroOcpp/pull/383))\n- `beginTransaction()` returns bool for better v2.0.1 interop ([#386](https://github.com/matth-x/MicroOcpp/pull/386))\n- Configurations C-API updates ([#400](https://github.com/matth-x/MicroOcpp/pull/400))\n- Platform integrations C-API upates ([#400](https://github.com/matth-x/MicroOcpp/pull/400))\n\n### Added\n\n- `getTransactionV201()` exposes v201 Tx in API ([#386](https://github.com/matth-x/MicroOcpp/pull/386))\n- v201 support in Transaction.h C-API ([#386](https://github.com/matth-x/MicroOcpp/pull/386))\n- Write-only Configurations ([#400](https://github.com/matth-x/MicroOcpp/pull/400))\n\n### Fixed\n\n- Timing issues for OCTT test cases ([#383](https://github.com/matth-x/MicroOcpp/pull/383))\n- Misleading Reset failure dbg msg ([#388](https://github.com/matth-x/MicroOcpp/pull/388))\n- Reject negative ints in ChangeConfig ([#388](https://github.com/matth-x/MicroOcpp/pull/388))\n- Revised SCons integration ([#400](https://github.com/matth-x/MicroOcpp/pull/400))\n\n## [1.2.0] - 2024-11-03\n\n### Changed\n\n- Change `MicroOcpp::ChargePointStatus` into C-style enum ([#309](https://github.com/matth-x/MicroOcpp/pull/309))\n- Connector lock disabled by default per `MO_ENABLE_CONNECTOR_LOCK` ([#312](https://github.com/matth-x/MicroOcpp/pull/312))\n- Relaxed temporal order of non-tx-related operations ([#345](https://github.com/matth-x/MicroOcpp/pull/345))\n- Use pseudo-GUIDs as messageId ([#345](https://github.com/matth-x/MicroOcpp/pull/345))\n- ISO 8601 milliseconds omitted by default ([352](https://github.com/matth-x/MicroOcpp/pull/352))\n- Rename `MO_NUM_EVSE` into `MO_NUM_EVSEID` (v2.0.1) ([#371](https://github.com/matth-x/MicroOcpp/pull/371))\n- Change `MicroOcpp::ReadingContext` into C-style struct ([#371](https://github.com/matth-x/MicroOcpp/pull/371))\n- Refactor RequestStartTransaction (v2.0.1) ([#371](https://github.com/matth-x/MicroOcpp/pull/371))\n\n### Added\n\n- Provide ChargePointStatus in API ([#309](https://github.com/matth-x/MicroOcpp/pull/309))\n- Built-in OTA over FTP ([#313](https://github.com/matth-x/MicroOcpp/pull/313))\n- Built-in Diagnostics over FTP ([#313](https://github.com/matth-x/MicroOcpp/pull/313))\n- Error `severity` mechanism ([#331](https://github.com/matth-x/MicroOcpp/pull/331))\n- Build flag `MO_REPORT_NOERROR` to report error recovery ([#331](https://github.com/matth-x/MicroOcpp/pull/331))\n- Support for `parentIdTag` ([#344](https://github.com/matth-x/MicroOcpp/pull/344))\n- Input validation for unsigned int Configs ([#344](https://github.com/matth-x/MicroOcpp/pull/344))\n- Support for TransactionMessageAttempts/-RetryInterval ([#345](https://github.com/matth-x/MicroOcpp/pull/345), [#380](https://github.com/matth-x/MicroOcpp/pull/380))\n- Heap profiler and custom allocator support ([#350](https://github.com/matth-x/MicroOcpp/pull/350))\n- Migration of persistent storage ([#355](https://github.com/matth-x/MicroOcpp/pull/355))\n- Benchmarks pipeline ([#369](https://github.com/matth-x/MicroOcpp/pull/369), [#376](https://github.com/matth-x/MicroOcpp/pull/376))\n- MeterValues port for OCPP 2.0.1 ([#371](https://github.com/matth-x/MicroOcpp/pull/371))\n- UnlockConnector port for OCPP 2.0.1 ([#371](https://github.com/matth-x/MicroOcpp/pull/371))\n- More APIs ported to OCPP 2.0.1 ([#371](https://github.com/matth-x/MicroOcpp/pull/371))\n- Support for AuthorizeRemoteTxRequests ([#373](https://github.com/matth-x/MicroOcpp/pull/373))\n- Persistent Variable and Tx store for OCPP 2.0.1 ([#379](https://github.com/matth-x/MicroOcpp/pull/379))\n\n### Removed\n\n- ESP32 built-in HTTP OTA ([#313](https://github.com/matth-x/MicroOcpp/pull/313))\n- Operation store (files op-*.jsn and opstore.jsn) ([#345](https://github.com/matth-x/MicroOcpp/pull/345))\n- Explicit tracking of txNr (file txstore.jsn) ([#345](https://github.com/matth-x/MicroOcpp/pull/345))\n- SimpleRequestFactory ([#351](https://github.com/matth-x/MicroOcpp/pull/351))\n\n### Fixed\n\n- Skip Unix files . and .. in ftw_root ([#313](https://github.com/matth-x/MicroOcpp/pull/313))\n- Skip clock-aligned measurements when time not set\n- Hold back error StatusNotifs when time not set ([#311](https://github.com/matth-x/MicroOcpp/issues/311))\n- Don't send Available when tx occupies connector ([#315](https://github.com/matth-x/MicroOcpp/issues/315))\n- Make ChargingScheduleAllowedChargingRateUnit read-only ([#328](https://github.com/matth-x/MicroOcpp/issues/328))\n- ~Don't send StatusNotifs while offline ([#344](https://github.com/matth-x/MicroOcpp/pull/344))~ (see ([#371](https://github.com/matth-x/MicroOcpp/pull/371)))\n- Don't change into Unavailable upon Reset ([#344](https://github.com/matth-x/MicroOcpp/pull/344))\n- Reject DataTransfer by default ([#344](https://github.com/matth-x/MicroOcpp/pull/344))\n- UnlockConnector NotSupported if connectorId invalid ([#344](https://github.com/matth-x/MicroOcpp/pull/344))\n- Fix regression bug of [#345](https://github.com/matth-x/MicroOcpp/pull/345) ([#353](https://github.com/matth-x/MicroOcpp/pull/353), [#356](https://github.com/matth-x/MicroOcpp/pull/356))\n- Correct MeterValue PreBoot timestamp ([#354](https://github.com/matth-x/MicroOcpp/pull/354))\n- Send errorCode in triggered StatusNotif ([#359](https://github.com/matth-x/MicroOcpp/pull/359))\n- Remove int to bool conversion in ChangeConfig ([#362](https://github.com/matth-x/MicroOcpp/pull/362))\n- Multiple fixes of the OCPP 2.0.1 extension ([#371](https://github.com/matth-x/MicroOcpp/pull/371))\n\n## [1.1.0] - 2024-05-21\n\n### Changed\n\n- Replace `PollResult<bool>` with enum `UnlockConnectorResult` ([#271](https://github.com/matth-x/MicroOcpp/pull/271))\n- Rename master branch into main\n- Tx logic directly checks if WebSocket is offline ([#282](https://github.com/matth-x/MicroOcpp/pull/282))\n- `ocppPermitsCharge` ignores Faulted state ([#279](https://github.com/matth-x/MicroOcpp/pull/279))\n- `setEnergyMeterInput` expects `int` input ([#301](https://github.com/matth-x/MicroOcpp/pull/301))\n\n### Added\n\n- File index ([#270](https://github.com/matth-x/MicroOcpp/pull/270))\n- Config `Cst_TxStartOnPowerPathClosed` to put back TxStartPoint ([#271](https://github.com/matth-x/MicroOcpp/pull/271))\n- Build flag `MO_ENABLE_RESERVATION=0` disables Reservation module ([#302](https://github.com/matth-x/MicroOcpp/pull/302))\n- Build flag `MO_ENABLE_LOCAL_AUTH=0` disables LocalAuthList module ([#303](https://github.com/matth-x/MicroOcpp/pull/303))\n- Function `bool isConnected()` in `Connection` interface ([#282](https://github.com/matth-x/MicroOcpp/pull/282))\n- Build flags for customizing memory limits of SmartCharging ([#260](https://github.com/matth-x/MicroOcpp/pull/260))\n- SConscript ([#287](https://github.com/matth-x/MicroOcpp/pull/287))\n- C-API for custom Configs store ([297](https://github.com/matth-x/MicroOcpp/pull/297))\n- Certificate Management, UCs M03 - M05 ([#262](https://github.com/matth-x/MicroOcpp/pull/262), [#274](https://github.com/matth-x/MicroOcpp/pull/274), [#292](https://github.com/matth-x/MicroOcpp/pull/292))\n- FTP Client ([#291](https://github.com/matth-x/MicroOcpp/pull/291))\n- `ProtocolVersion` selects v1.6 or v2.0.1 ([#247](https://github.com/matth-x/MicroOcpp/pull/247))\n- Build flag `MO_ENABLE_V201=1` enables OCPP 2.0.1 features ([#247](https://github.com/matth-x/MicroOcpp/pull/247))\n    - Variables (non-persistent), UCs B05 - B07 ([#247](https://github.com/matth-x/MicroOcpp/pull/247), [#284](https://github.com/matth-x/MicroOcpp/pull/284))\n    - Transactions (preview only), UCs E01 - E12 ([#247](https://github.com/matth-x/MicroOcpp/pull/247))\n    - StatusNotification compatibility ([#247](https://github.com/matth-x/MicroOcpp/pull/247))\n    - ChangeAvailability compatibility ([#285](https://github.com/matth-x/MicroOcpp/pull/285))\n    - Reset compatibility, UCs B11 - B12 ([#286](https://github.com/matth-x/MicroOcpp/pull/286))\n    - RequestStart-/StopTransaction, UCs F01 - F02 ([#289](https://github.com/matth-x/MicroOcpp/pull/289))\n\n### Fixed\n\n- Fix defect idTag check in `endTransaction` ([#275](https://github.com/matth-x/MicroOcpp/pull/275))\n- Make field localAuthorizationList in SendLocalList optional\n- Update charging profiles when flash disabled (relates to [#260](https://github.com/matth-x/MicroOcpp/pull/260))\n- Ignore UnlockConnector when handler not set ([#271](https://github.com/matth-x/MicroOcpp/pull/271))\n- Reject ChargingProfile if unit not supported ([#271](https://github.com/matth-x/MicroOcpp/pull/271))\n- Fix building with debug level warn and error\n- Reduce debug output FW size overhead ([#304](https://github.com/matth-x/MicroOcpp/pull/304))\n- Fix transaction freeze in offline mode ([#279](https://github.com/matth-x/MicroOcpp/pull/279), [#287](https://github.com/matth-x/MicroOcpp/pull/287))\n- Fix compilation error caused by `PRId32` ([#279](https://github.com/matth-x/MicroOcpp/pull/279))\n- Don't load FW-mngt. module when no handlers set ([#271](https://github.com/matth-x/MicroOcpp/pull/271))\n- Change arduinoWebSockets URL param to path ([#278](https://github.com/matth-x/MicroOcpp/issues/278))\n- Avoid creating conf when operation fails ([#290](https://github.com/matth-x/MicroOcpp/pull/290))\n- Fix whitespaces in MeterValues ([#301](https://github.com/matth-x/MicroOcpp/pull/301))\n- Make SmartChargingProfile txId field optional ([#348](https://github.com/matth-x/MicroOcpp/pull/348))\n\n## [1.0.3] - 2024-04-06\n\n### Fixed\n\n- Fix nullptr access in endTransaction ([#275](https://github.com/matth-x/MicroOcpp/pull/275))\n- Backport: Fix building with debug level warn and error\n\n## [1.0.2] - 2024-03-24\n\n### Fixed\n\n- Correct MO version numbers in code (they were still `1.0.0`)\n\n## [1.0.1] - 2024-02-27\n\n### Fixed\n\n- Allow `nullptr` as parameter for `mocpp_set_console_out` ([#224](https://github.com/matth-x/MicroOcpp/issues/224))\n- Fix `mocpp_tick_ms()` on esp-idf roll-over after 12 hours\n- Pin ArduinoJson to v6.21 ([#245](https://github.com/matth-x/MicroOcpp/issues/245))\n- Fix bounds checking in SmartCharging module ([#260](https://github.com/matth-x/MicroOcpp/pull/260))\n\n## [1.0.0] - 2023-10-22\n\n_First release_\n\n### Changed\n\n- `mocpp_initialize` takes OCPP URL without explicit host, port ([#220](https://github.com/matth-x/MicroOcpp/pull/220))\n- `endTransaction` checks authorization of `idTag`\n- Update configurations API ([#195](https://github.com/matth-x/MicroOcpp/pull/195))\n- Update Firmware- and DiagnosticsService API ([#207](https://github.com/matth-x/MicroOcpp/pull/207))\n- Update Connection interface\n- Update Authorization module functions ([#213](https://github.com/matth-x/MicroOcpp/pull/213))\n- Reflect changes in C-API\n- Change build flag prefix from `MOCPP_` to `MO_`\n- Change `mo_set_console_out` to `mocpp_set_console_out`\n- Revise README.md\n- Revise misleading debug messages\n- Update Arduino IDE manifest ([#206](https://github.com/matth-x/MicroOcpp/issues/206))\n\n### Added\n\n- Auto-recovery switch in `mocpp_initialize` params\n- WebAssembly port\n- Configurable `MO_PARTITION_LABEL` for the esp-idf SPIFFS integration ([#218](https://github.com/matth-x/MicroOcpp/pull/218))\n- `MO_TX_CLEAN_ABORTED=0` keeps aborted txs in journal\n- `MO_VERSION` specifier\n- `MO_PLATFORM_NONE` for compilation on custom platforms\n- `endTransaction_authorized` enforces the tx end\n- Add valgrind, ASan, UBSan CI/CD steps ([#189](https://github.com/matth-x/MicroOcpp/pull/189))\n\n### Fixed\n\n- Reservation ([#196](https://github.com/matth-x/MicroOcpp/pull/196))\n- Fix immediate FW-update Download phase abort ([#216](https://github.com/matth-x/MicroOcpp/pull/216))\n- `stat` usage on arduino-esp32 LittleFS\n- SetChargingProfile JSON capacity calculation\n- Set correct idTag when Reset triggers StopTx\n- Execute operations only once despite multiple .conf send attempts ([#207](https://github.com/matth-x/MicroOcpp/pull/207))\n- ConnectionTimeOut only applies when connector is still unplugged\n- Fix valgrind warnings\n\n## [1eff6e5] - 23-08-23\n\n_Previous point with breaking changes on master_\n\nRenaming to MicroOcpp is completed since this commit. See the [migration guide](https://matth-x.github.io/MicroOcpp/migration/) for more details on what's changed. Changelogs and semantic versioning are adopted starting with v1.0.0\n\n## [0.3.0] - 23-08-19\n\n_Last version under the project name ArduinoOcpp_\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "# matth-x/MicroOcpp\n# Copyright Matthias Akstaller 2019 - 2024\n# MIT License\n\ncmake_minimum_required(VERSION 3.15)\n\nset(CMAKE_CXX_STANDARD 11)\n\nset(MO_SRC\n    src/MicroOcpp/Core/Configuration_c.cpp\n    src/MicroOcpp/Core/Configuration.cpp\n    src/MicroOcpp/Core/ConfigurationContainer.cpp\n    src/MicroOcpp/Core/ConfigurationContainerFlash.cpp\n    src/MicroOcpp/Core/ConfigurationKeyValue.cpp\n    src/MicroOcpp/Core/FilesystemAdapter.cpp\n    src/MicroOcpp/Core/FilesystemUtils.cpp\n    src/MicroOcpp/Core/FtpMbedTLS.cpp\n    src/MicroOcpp/Core/Memory.cpp\n    src/MicroOcpp/Core/RequestQueue.cpp\n    src/MicroOcpp/Core/Context.cpp\n    src/MicroOcpp/Core/Operation.cpp\n    src/MicroOcpp/Model/Model.cpp\n    src/MicroOcpp/Core/Request.cpp\n    src/MicroOcpp/Core/Connection.cpp\n    src/MicroOcpp/Core/Time.cpp\n    src/MicroOcpp/Core/UuidUtils.cpp\n    src/MicroOcpp/Operations/Authorize.cpp\n    src/MicroOcpp/Operations/BootNotification.cpp\n    src/MicroOcpp/Operations/CancelReservation.cpp\n    src/MicroOcpp/Operations/ChangeAvailability.cpp\n    src/MicroOcpp/Operations/ChangeConfiguration.cpp\n    src/MicroOcpp/Operations/ClearCache.cpp\n    src/MicroOcpp/Operations/ClearChargingProfile.cpp\n    src/MicroOcpp/Operations/CustomOperation.cpp\n    src/MicroOcpp/Operations/DataTransfer.cpp\n    src/MicroOcpp/Operations/DeleteCertificate.cpp\n    src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp\n    src/MicroOcpp/Operations/FirmwareStatusNotification.cpp\n    src/MicroOcpp/Operations/GetBaseReport.cpp\n    src/MicroOcpp/Operations/GetCompositeSchedule.cpp\n    src/MicroOcpp/Operations/GetConfiguration.cpp\n    src/MicroOcpp/Operations/GetDiagnostics.cpp\n    src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp\n    src/MicroOcpp/Operations/GetLocalListVersion.cpp\n    src/MicroOcpp/Operations/GetVariables.cpp\n    src/MicroOcpp/Operations/Heartbeat.cpp\n    src/MicroOcpp/Operations/MeterValues.cpp\n    src/MicroOcpp/Operations/NotifyReport.cpp\n    src/MicroOcpp/Operations/RemoteStartTransaction.cpp\n    src/MicroOcpp/Operations/RemoteStopTransaction.cpp\n    src/MicroOcpp/Operations/RequestStartTransaction.cpp\n    src/MicroOcpp/Operations/RequestStopTransaction.cpp\n    src/MicroOcpp/Operations/ReserveNow.cpp\n    src/MicroOcpp/Operations/Reset.cpp\n    src/MicroOcpp/Operations/SecurityEventNotification.cpp\n    src/MicroOcpp/Operations/SendLocalList.cpp\n    src/MicroOcpp/Operations/SetChargingProfile.cpp\n    src/MicroOcpp/Operations/SetVariables.cpp\n    src/MicroOcpp/Operations/StartTransaction.cpp\n    src/MicroOcpp/Operations/StatusNotification.cpp\n    src/MicroOcpp/Operations/StopTransaction.cpp\n    src/MicroOcpp/Operations/TransactionEvent.cpp\n    src/MicroOcpp/Operations/TriggerMessage.cpp\n    src/MicroOcpp/Operations/InstallCertificate.cpp\n    src/MicroOcpp/Operations/UnlockConnector.cpp\n    src/MicroOcpp/Operations/UpdateFirmware.cpp\n    src/MicroOcpp/Debug.cpp\n    src/MicroOcpp/Platform.cpp\n    src/MicroOcpp/Core/OperationRegistry.cpp\n    src/MicroOcpp/Model/Availability/AvailabilityService.cpp\n    src/MicroOcpp/Model/Authorization/AuthorizationData.cpp\n    src/MicroOcpp/Model/Authorization/AuthorizationList.cpp\n    src/MicroOcpp/Model/Authorization/AuthorizationService.cpp\n    src/MicroOcpp/Model/Authorization/IdToken.cpp\n    src/MicroOcpp/Model/Boot/BootService.cpp\n    src/MicroOcpp/Model/Certificates/Certificate.cpp\n    src/MicroOcpp/Model/Certificates/Certificate_c.cpp\n    src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp\n    src/MicroOcpp/Model/Certificates/CertificateService.cpp\n    src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.cpp\n    src/MicroOcpp/Model/ConnectorBase/Connector.cpp\n    src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp\n    src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp\n    src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp\n    src/MicroOcpp/Model/Metering/MeteringConnector.cpp\n    src/MicroOcpp/Model/Metering/MeteringService.cpp\n    src/MicroOcpp/Model/Metering/MeterStore.cpp\n    src/MicroOcpp/Model/Metering/MeterValue.cpp\n    src/MicroOcpp/Model/Metering/MeterValuesV201.cpp\n    src/MicroOcpp/Model/Metering/ReadingContext.cpp\n    src/MicroOcpp/Model/Metering/SampledValue.cpp\n    src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp\n    src/MicroOcpp/Model/Reservation/Reservation.cpp\n    src/MicroOcpp/Model/Reservation/ReservationService.cpp\n    src/MicroOcpp/Model/Reset/ResetService.cpp\n    src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp\n    src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp\n    src/MicroOcpp/Model/Transactions/Transaction.cpp\n    src/MicroOcpp/Model/Transactions/TransactionDeserialize.cpp\n    src/MicroOcpp/Model/Transactions/TransactionService.cpp\n    src/MicroOcpp/Model/Transactions/TransactionStore.cpp\n    src/MicroOcpp/Model/Variables/Variable.cpp\n    src/MicroOcpp/Model/Variables/VariableContainer.cpp\n    src/MicroOcpp/Model/Variables/VariableService.cpp\n    src/MicroOcpp.cpp\n    src/MicroOcpp_c.cpp\n)\n\nif(ESP_PLATFORM)\n\n    idf_component_register(SRCS ${MO_SRC}\n        INCLUDE_DIRS \"./src\" \"../ArduinoJson/src\"\n        PRIV_REQUIRES spiffs\n    )\n\n    target_compile_options(${COMPONENT_TARGET} PUBLIC\n        -DMO_PLATFORM=MO_PLATFORM_ESPIDF\n    )\n\n    return()\nendif()\n\nproject(MicroOcpp VERSION 1.2.0)\n\nadd_library(MicroOcpp ${MO_SRC})\n\ntarget_include_directories(MicroOcpp PUBLIC\n    \"./src\"\n    \"../ArduinoJson/src\"\n)\n\ntarget_compile_definitions(MicroOcpp PUBLIC\n    MO_PLATFORM=MO_PLATFORM_UNIX\n)\n\n# Unit tests\n\nset(MO_SRC_UNIT\n    tests/helpers/testHelper.cpp\n    tests/ocppEngineLifecycle.cpp\n    tests/TransactionSafety.cpp\n    tests/ChargingSessions.cpp\n    tests/ConfigurationBehavior.cpp\n    tests/SmartCharging.cpp\n    tests/Api.cpp\n    tests/Metering.cpp\n    tests/Configuration.cpp\n    tests/Reservation.cpp\n    tests/Reset.cpp\n    tests/LocalAuthList.cpp\n    tests/Variables.cpp\n    tests/Transactions.cpp\n    tests/RemoteStartTransaction.cpp\n    tests/Certificates.cpp\n    tests/FirmwareManagement.cpp\n    tests/ChargePointError.cpp\n    tests/Boot.cpp\n    tests/Security.cpp\n)\n\nadd_executable(mo_unit_tests\n    ${MO_SRC}\n    ${MO_SRC_UNIT}\n    ./tests/catch2/catchMain.cpp\n)\n\nif (MO_BUILD_UNIT_MBEDTLS)\n    add_subdirectory(lib/mbedtls)\n    target_link_libraries(mo_unit_tests PUBLIC \n        mbedtls\n        mbedcrypto\n        mbedx509\n    )\n\n    target_compile_definitions(mo_unit_tests PUBLIC\n        MO_ENABLE_MBEDTLS=1\n    )\nendif()\n\ntarget_include_directories(mo_unit_tests PUBLIC\n    \"./tests\"\n    \"./tests/helpers\"\n    \"./src\"\n)\n\ntarget_compile_definitions(mo_unit_tests PUBLIC\n    MO_PLATFORM=MO_PLATFORM_UNIX\n    MO_NUMCONNECTORS=3\n    MO_CUSTOM_TIMER\n    MO_DBG_LEVEL=MO_DL_INFO\n    MO_TRAFFIC_OUT\n    MO_FILENAME_PREFIX=\"./mo_store/\"\n    MO_LocalAuthListMaxLength=8\n    MO_SendLocalListMaxLength=4\n    MO_ENABLE_FILE_INDEX=1\n    MO_ChargeProfileMaxStackLevel=2\n    MO_ChargingScheduleMaxPeriods=4\n    MO_MaxChargingProfilesInstalled=3\n    MO_ENABLE_CERT_MGMT=1\n    MO_ENABLE_CONNECTOR_LOCK=1\n    MO_REPORT_NOERROR=1\n    MO_ENABLE_V201=1\n    MO_OVERRIDE_ALLOCATION=1\n    MO_ENABLE_HEAP_PROFILER=1\n    MO_HEAP_PROFILER_EXTERNAL_CONTROL=1\n    CATCH_CONFIG_EXTERNAL_INTERFACES\n)\n\ntarget_compile_options(mo_unit_tests PUBLIC\n    -Wall\n    -O0\n    -g\n    --coverage\n)\n\ntarget_link_options(mo_unit_tests PUBLIC\n    --coverage\n)\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 - 2024 Matthias Akstaller\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# <img src=\"https://github.com/matth-x/MicroOcpp/assets/63792403/1c49d1ad-7afc-48d3-a54e-9aef2d4886db\" alt=\"Icon\" height=\"24\"> &nbsp; MicroOCPP\n\n[![Build Status]( https://github.com/matth-x/MicroOcpp/workflows/PlatformIO%20CI/badge.svg)](https://github.com/matth-x/MicroOcpp/actions)\n[![Unit tests]( https://github.com/matth-x/MicroOcpp/workflows/Unit%20tests/badge.svg)](https://github.com/matth-x/MicroOcpp/actions)\n[![codecov](https://codecov.io/github/matth-x/ArduinoOcpp/branch/develop/graph/badge.svg?token=UN6LO96HM7)](https://codecov.io/github/matth-x/ArduinoOcpp)\n\nOCPP 1.6 / 2.0.1 client for microcontrollers. Portable C/C++. Compatible with Espressif, Arduino, NXP, STM, Linux and more.\n\n:heavy_check_mark: Works with [15+ commercial Central Systems](https://www.micro-ocpp.com/#h.314525e8447cc93c_81)\n\n:heavy_check_mark: Eligible for public chargers (Eichrecht-compliant)\n\n:heavy_check_mark: Supports all OCPP 1.6 feature profiles and the [basic OCPP 2.0.1 UCs](https://github.com/matth-x/MicroOcpp/tree/feature/prepare-release?tab=readme-ov-file#ocpp-201-and-iso-15118)\n\nReference usage: [OpenEVSE](https://github.com/OpenEVSE/ESP32_WiFi_V4.x/blob/master/src/ocpp.cpp) | Technical introduction: [Docs](https://matth-x.github.io/MicroOcpp/intro-tech) | Website: [www.micro-ocpp.com](https://www.micro-ocpp.com)\n\n## AI-friendly code\n\nAI models perform extremely well with the MicroOCPP codebase. The upcoming new release of MO (v2.0) will further optimize the code for more reliable results with AI models (preview to be found in the `develop/remodel-api` branch). The hope is to allow integrating MO into an existing EV charger project with only a few queries.\n\nCurrently, the `develop/remodel-api` branch is not stable yet, but recommended for new developments. To get started, load `MicroOcpp.h` (now unified for C and C++) into the context window and ask what the AI model needs to know to integrate it into your codebase.\n\nIf your tools have issues with something in MicroOCPP, please open an issue on GitHub. Any feedback on how to further optimize the codebase is also highly appreciated.\n\n## Tester / Demo App\n\n*Main repository: [MicroOcppSimulator](https://github.com/matth-x/MicroOcppSimulator)*\n\nThe Simulator is a demo & development tool for MicroOCPP which allows to quickly assess the compatibility with different OCPP backends. It simulates a full charging station, adds a GUI and a mocked hardware binding to MicroOCPP and runs in the browser (using WebAssembly): [Try it](https://demo.micro-ocpp.com/)\n\n<div align=\"center\"><img src=\"https://github.com/matth-x/MicroOcpp/assets/63792403/27f2819b-41fd-41a7-88a8-9e673b8a88b8\" alt=\"Screenshot\" width=\"800em\" href=\"https://demo.micro-ocpp.com/\"></div>\n\n#### Usage\n\n**OCPP server setup**: Navigate to \"Control Center\". In the WebSocket options, add the OCPP backend URL, charge box ID and authorization key if existent. Press \"Update WebSocket\" to save. The Simulator should connect to the OCPP server. To check the connection status, it could be helpful to open the developer tools of the browser.\n\nIf you don't have an OCPP server at hand, leave the charge box ID blank and enter the following backend address: `wss://echo.websocket.events/` (this server is sponsored by Lob.com)\n\n**RFID authentication**: Go to \"Control Center\" > \"Connectors\" > \"Transaction\" and update the idTag with the desired value.\n\n## Benchmarks\n\n*Full report: [MicroOCPP benchmarks](https://matth-x.github.io/MicroOcpp/benchmarks/)*\n\nThe following measurements were taken on the ESP32 @ 160MHz and represent the optimistic best case scenario for a charger with two physical connectors (i.e. compiled with `-Os`, disabled debug output and logs).\n\n| Description | Value |\n| :--- | ---: |\n| Flash size (minimal) | 121,170 B |\n| Heap occupation (idle) | 12,308 B |\n| Heap occupation (peak) | 21,916 B |\n| Initailization | 21 ms |\n| `loop()` call (idle) | 0.05 ms |\n| Large message sent | 5 ms |\n\nIn practical setups, the execution time is largely determined by IO delays and the heap occupation is significantly influenced by the configuration with reservation, local authorization and charging profile lists.\n\n## Developers guide\n\nPlatformIO package: [MicroOcpp](https://registry.platformio.org/libraries/matth-x/MicroOcpp)\n\nMicroOCPP is an implementation of the OCPP communication behavior. It automatically initiates the corresponding OCPP operations once the hardware status changes or the RFID input is updated with a new value. Conversely it processes new data from the server, stores it locally and updates the hardware controls when applicable.\n\nPlease take `examples/ESP/main.cpp` as the starting point for the first project. It is a minimal example which shows how to establish an OCPP connection and how to start and stop charging sessions. The API documentation can be found in [`MicroOcpp.h`](https://github.com/matth-x/MicroOcpp/blob/main/src/MicroOcpp.h). Also check out the [Docs](https://matth-x.github.io/MicroOcpp).\n\n### Dependencies\n\nMandatory:\n\n- [bblanchon/ArduinoJSON](https://github.com/bblanchon/ArduinoJson) (version `6.21`)\n\nIf compiled with the Arduino integration:\n\n- [Links2004/arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) (version `2.4.1`)\n\nIf using the built-in certificate store (to enable, set build flag `MO_ENABLE_MBEDTLS=1`):\n\n- [Mbed-TLS/mbedtls](https://github.com/Mbed-TLS/mbedtls) (version `2.28.1`)\n\nIn case you use PlatformIO, you can copy all dependencies from `platformio.ini` into your own configuration file. Alternatively, you can install the full library with dependencies by adding `matth-x/MicroOcpp@1.2.0` in the PIO library manager.\n\n## OCPP 2.0.1 and ISO 15118\n\nThe following OCPP 2.0.1 use cases are implemented:\n\n| UC | Description | Note |\n| :--- | :--- | :--- |\n| B01 - B04<br>B11 - B12 | Provisioning | Ported from OCPP 1.6 |\n| B05 - B07 | Variables | |\n| C01 - C06 | Authorization options | |\n| C15 | Offline Authorization | |\n| E01 - E12 | Transactions | |\n| F01 - F03<br>F05 - F06 | RemoteControl | |\n| G01 - G04 | Availability | |\n| J02 | Tx-related MeterValues | persistency not supported yet |\n| M03 - M05 | Certificate management | Enable Mbed-TLS to use the built-in certificate store |\n| P01 - P02 | Data transfer | |\n| - | Protocol negotiation | The charger can select the OCPP version at runtime |\n\nThe OCPP 2.0.1 features are in an alpha development stage. By default, they are disabled and excluded from the build, so they have no impact on the firmware size. To enable, set the build flag `MO_ENABLE_V201=1` and initialize the library with the ProtocolVersion parameter `2.0.1`  (see [this example](https://github.com/matth-x/MicroOcppSimulator/blob/657e606c3b178d3add242935d413c72624130ff3/src/main.cpp#L43-L47) in the Simulator).\n\nAn integration of the library for OCPP 1.6 will also be functional with the 2.0.1 upgrade. It works with the same API in MicroOcpp.h.\n\nISO 15118 defines some use cases which include a message exchange between the charger and server. This library facilitates the integration of ISO 15118 by handling its OCPP-side communication.\n\n## Contact\n\nIf you have any questions, or found a potential bug, feel free to open an issue. This type of interaction is highly appreciated, because it shows problems in the codebase and helps improve the project for clarity. For further questions which shouldn't stand in public, you can reach me via LinkedIn or the following email address:\n\n:envelope: : matthias [A⊤] micro-ocpp [DО⊤] com\n"
  },
  {
    "path": "SConscript.py",
    "content": "# matth-x/MicroOcpp\n# Copyright Matthias Akstaller 2019 - 2024\n# MIT License\n\n# NOTE: This SConscript is still WIP. It has thankfully been contributed from a project using SCons,\n#       not necessarily considering full reusability in other projects though.\n#       Use this file as a starting point for writing your own SCons integration. And as always, any\n#       contributions are highly welcome!\n\nImport(\"env\")\n\nimport os, pathlib\n\ndef getAllDirs(root_dir):\n    dir_list = []\n    for root, subfolders, files in os.walk(root_dir.abspath):\n        dir_list.append(Dir(root))\n    return dir_list\n\nSOURCE_DIR = Dir(\".\").srcnode().Dir(\"src\")\n\nsource_dirs = getAllDirs(SOURCE_DIR)\n\nsource_files = []\n\nfor folder in source_dirs:\n    source_files += folder.glob(\"*.c\")\n    source_files += folder.glob(\"*.cpp\")\n\ncompiled_objects = []\nfor source_file in source_files:\n    obj = env.Object(\n        target = pathlib.Path(source_file.path).stem\n        + \".o\",\n        source=source_file,\n    )\n    compiled_objects.append(obj)\n\nlibmicroocpp = env.StaticLibrary(\n    target='libmicroocpp',\n    source=sorted(compiled_objects)\n)\n\nexports = {\n    'library': libmicroocpp,\n    'CPPPATH': SOURCE_DIR\n}\n\nReturn(\"exports\")\n"
  },
  {
    "path": "docs/benchmarks.md",
    "content": "# Benchmarks\n\nMicrocontrollers have tight hardware constraints which affect how much resources the firmware can demand. It is important to make sure that the available resources are not depleted to allow for robust operation and that there is sufficient flash head room to allow for future software upgrades.\n\nIn general, microcontrollers have three relevant hardware constraints:\n\n- Limited processing speed\n- Limited memory size\n- Limited flash size\n\nFor OCPP, the relevant bottlenecks are especially the memory and flash size. The processing speed is no concern, since OCPP is not computationally complex and does not include any extensive planning algorithms on the charger size. A previous [benchmark on the ESP-IDF](https://github.com/matth-x/MicroOcpp-benchmark) showed that the processing times are in the lower milliseconds range and are probably outweighed by IO times and network round trip times.\n\nHowever, the memory and flash requirements are important figures, because the device model of OCPP has a significant size. The microcontroller needs to keep the model data in the heap memory for the largest part and the firmware which covers the corresponding processing routines needs to have sufficient space on flash.\n\nThis chapter presents benchmarks of the memory and flash requirements. They should help to determine the required microcontroller capabilities, or to give general insights for taking further action on optimizing the firmware.\n\n## Firmware size\n\nWhen compiling a firmware with MicroOCPP, the resulting binary will contain functionality which is not related to OCPP, like hardware drivers, modules which are shared, like MbedTLS and the actual MicroOCPP object files. The size of the latter is the final flash requirement of MicroOCPP.\n\nFor the flash benchmark, the profiler compiles a [dummy OCPP firmware](https://github.com/matth-x/MicroOcpp/tree/main/tests/benchmarks/firmware_size/main.cpp), analyzes the size of the compilation units using [bloaty](https://github.com/google/bloaty) and evaluates the bloaty report using a [Python script](https://github.com/matth-x/MicroOcpp/tree/main/tests/benchmarks/scripts/eval_firmware_size.py). To give realistic results, the firwmare is compiled with `-Os`, no RTTI or exceptions and newlib as the standard C library. The following tables show the results.\n\n### OCPP 1.6\n\nThe following table shows the cumulated size of the objects files per module. The Module category consists of the OCPP 2.0.1 functional blocks, OCPP 1.6 feature profiles and general functionality which is shared accross the library. If a feature of the implementation falls under both an OCPP 2.0.1 functional block and OCPP 1.6 feature profile definition, it is preferrably assigned to the OCPP 2.0.1 category. This allows for better comparability between both OCPP versions.\n\n**Table 1: Firmware size per Module**\n\n{{ read_csv('modules_v16.csv') }}\n\n### OCPP 2.0.1\n\n**Table 2: Firmware size per Module**\n\n{{ read_csv('modules_v201.csv') }}\n\n## Memory usage\n\nMicroOCPP uses the heap memory to process incoming messages, maintain the device model and create outgoing OCPP messages. The total heap usage should remain low enough to not risk a heap depletion which would not only affect the OCPP module, but the whole controller, because heap memory is typically shared on microcontrollers. To assess the heap usage of MicroOCPP, a test suite runs a variety of simulated charger use cases and measures the maximum occupied memory. Then, the maximum observed value is considered as the memory requirement of MicroOCPP.\n\nAnother important figure is the base level which is much closer to the average heap usage. The total heap usage consists of a base level and a dynamic part. Some memory objects are only initialized once during startup or as the device model is populated (e.g. Charging Schedules) and therefore belong to the base which changes only slowly over time. In contrast, objects for the JSON parsing and serialization and the internal execution of the operations are highly dynamic as they are instantiated for one operation and freed again after completion of the action. If the firmware contains multiple components besides MicroOCPP with this usage pattern, then the average total memory occupation of the device RAM is even closer to the base levels of the individual components.\n\nThe following table shows the dynamic heap usage for a variety of test cases, followed by the base level and resulting maximum memory occupation of MicroOCPP. At the time being, the measurements are limited to only OCPP 2.0.1 and a narrow set of test cases. They will be gradually extended over time.\n\n**Table 3: Memory usage per use case and total**\n\n{{ read_csv('heap_v201.csv') }}\n\n## Full data sets\n\nThis section contains the raw data which is the basis for the evaluations above.\n\n**Table 4: All compilation units for OCPP 1.6 firmware**\n\n{{ read_csv('compile_units_v16.csv') }}\n\n**Table 5: All compilation units for OCPP 2.0.1 firmware**\n\n{{ read_csv('compile_units_v201.csv') }}\n"
  },
  {
    "path": "docs/index.md",
    "content": "MicroOCPP is an OCPP client which runs on microcontrollers and enables EVSEs to participate in OCPP charging networks. As a software library, it can be added to the firmware of the EVSE and will become a new part of it. If the EVSE has already an internet controller, then most likely, no extra hardware is required.\n\n[Technical introduction](intro-tech)\n\n[Migrating to v1.0](migration)\n\n[Modules](modules)\n\n[Development tools and basic prerequisites](prerequisites)\n\n[Security whitepaper](security)\n\n<!-- This chapter shows how to kick-start your OCPP project. -->\n\n*Documentation WIP. See the [GitHub Readme](https://github.com/matth-x/MicroOcpp) or the [API description](https://github.com/matth-x/MicroOcpp/blob/main/src/MicroOcpp.h) as reference.*\n"
  },
  {
    "path": "docs/intro-tech.md",
    "content": "# Technical introduction\n\nThis chapter covers the technical concepts of MicroOCPP.\n\n## Scope of MicroOCPP\n\nThe OCPP specification defines a charger data model, operations on the data model and the resulting physical behavior on the charger side. MicroOCPP implements the full scope of OCPP, i.e. a minimalistic data store for the data model, the OCPP operations and an interface to the surrounding firmware.\n\nAnother part of OCPP is its messaging mechanism, the so-called Remote Procedure Calls (RPC) framework. MicroOCPP also implements the specified RPC framework with the required guarantees of message delivery or the corresponding error handling.\n\nAt the lowest layer, OCPP relies on standard WebSockets. MicroOCPP works with any WebSocket library and has a lean interface to integrate them.\n\nThe high-level API in `MicroOcpp.h` bundles all touch points of the EVSE firmware with the OCPP library.\n\n<p style=\"text-align:center\">\n    <img src=\"../img/components_overview.svg\">\n    <br />\n    <em>Overview of the architecture</em>\n</p>\n\n## High-level OCPP support\n\nBeing a full implementation of OCPP, MicroOCPP handles the OCPP communication, i.e. it sends OCPP requests and processes incoming OCPP requests autonomously. The messages are triggered by the internal data model and by input from the high-level API. Incoming OCPP requests are used to update the internal data model and if an action on the charger is required, the library signals that to the main firmware through the high-level API.\n\nIn consequence, the high-level API decouples the main firmware from the OCPP communication and hides the operations. This has the following good reasons:\n\n- The high-level API guarantees correctnes of the OCPP integration. As soon as the charger adopts it properly, it is fully OCPP-compliant\n- The hardware-near design decreases the integration effort into the firmware hugely\n- The API won't change substantially for the OCPP 2.0.1 upgrade. The EVSE will get OCPP 2.0.1 support on the fly by a later firmware update\n\n## Customizability\n\nOne core principle of the architecture of MicroOCPP is the customizability and the selective usage of its components.\n\nSelective usage of components means that the EVSE firmware can use parts of MicroOCPP and work with its own implementation for the rest. In that case only the selected parts of MicroOCPP will be compiled into the firmware. For example, the main firmware can use the RPC framework and build a custom implementation of the OCPP logic on top of it. This could be necessary if the OCPP behavior should be tightly coupled to other modules of the firmware. In a different scenario, the EVSE firmware could already contain an extensive RPC framework and the OCPP client should reuse it. Then, only the business logic and high-level API are of interest.\n\n<p style=\"text-align:center\">\n    <img src=\"../img/components_selective.svg\">\n    <br />\n    <em>Selective usage of MicroOCPP</em>\n</p>\n\nCustomizations of the library allow to integrate use cases for which the high-level API is too restrictive. The high-level API is designed to provide a facade for the expected usage of the library, but since the charging sector is driven by innovation, new use cases for OCPP emerge every day. If a custom use case cannot be integrated on the API level, the main firmware can access the internal data structures of MicroOCPP and complement the required functionality or replace parts of the internal behavior with custom implementations which fits the concrete scenarios better.\n\n## Main-loop paradigm\n\nMicroOCPP works with the common main-loop execution model of microcontrollers. After initialization, the EVSE firmware most likely enters a main-loop and repeats it infinitely. To run MicroOCPP, a call to its loop function must be placed into the main loop of the firmware. Then at each main-loop iteration, MicroOCPP executes its internal routines, i.e. it processes input data, updates its data model, executes operations and creates new output data. The MicroOCPP loop function does not block the main loop but executes immediately. This library does not contain any delay functions. Some activities of the library spread over many loop iterations like the start of a charging session which needs to await the approval of an NFC card and a hardware diagnosis of the high power electronics for example. All activities in MicroOCPP support the distribution over many loop calls, leading to a pseudo-parallel execution behavior.\n\nNo separate RTOS task is needed and MicroOCPP does not have an internal mechanism for multi-task synchronization. However, it is of course possible to create a dedicated OCPP task, as long as extra care is taken of the synchronization.\n\n## How the API works\n\nThe high-level API consists of four parts:\n\n- **Library lifecycle**: The library has initialize functions with a few initialization options. Dynamic system components like the WebSocket adapter need to be set at initialization time. The deinitialize function reverts the library into an unitialized state. That's useful for memory inspection tools like valgrind or to disable the OCPP communication. The loop function also counts as part of the lifecycle management.\n- **Sensor Inputs**: EVSEs are mechanical systems with a variety of sensor information. OCPP is used to send parts of the sensor readings to the server. The other part of the sensor data flows into the local charger model of MicroOCPP where it is further processed. To update MicroOCPP with the input data from the sensors, the firmware needs to bind the sensors to the library. An *Input-binding*, or in short *Input*, is a function which transfers the current sensor value to MicroOCPP. Inputs are callback functions which read a specific sensor value and pass the value in the return statement. The firmware defines those callback functions for each sensor and adds them to MicroOCPP during initialization. After initialization, MicroOCPP uses the callbacks and executes them to fetch the most recent sensor values. <br/>\nThis concept is reused for the data *Outputs* of the library to the firmware, where the callback applies output data from MicroOCPP to the firmware.\n- **Transaction management**: OCPP considers EVSEs as vending machines. To enable payment processing and the billing of the EVSE usage, all charging activity is assigned to transactions. A big portion of OCPP is about transactions, their prerequisites, runtime and their termination scenarios. The MicroOCPP API breaks transactions down into an initiation and termination function and gives a transparent view on the current process status, authorization result and offline behavior strategy. For non-commercial setups, the transaction mechanism is the same but has only informational purposes.\n- **Device management**: MicroOCPP implements the OCPP side of the device management operations. For the actual execution, the firmware needs to provide the charger-side implementations of the operations to MicroOCPP by passing handler functions to the API. For example, the OCPP server can restart the charger. Upon receipt of the request, MicroOCPP terminates the transactions and eventually triggers the system restart using the handler function which the firmware has provided through the high-level API.\n\n## Transaction safety\n\nSoftware in EVSEs needs to withstand hazardous operating conditions. EVSEs are located on the street or in garages where the WiFi or LTE signal strength is often weak, leading to long offline periods or where random power cuts can occur. In addition to that, the lack of process virtualization on microcontrollers means that a malfunction in one part of the firmware leads to the crash of all other parts.\n\nThe transaction process of MicroOCPP is robust against random failures or resets. A minimal transaction log on the flash storage ensures that each operation on a transaction is fully executed. It will always result in a consistent state between the EVSE and the OCPP server, even over resets of the microcontroller. The RPC queue facilitates this by tracking the delivery status of relevant messages. If the microcontroller is reset while the delivery status of a message is unknown, MicroOCPP takes up the message delivery again at the next start up and completes it.\n\nA requirement for the transaction safety feature is the availability of a journaling file system. Examples include LittleFS, SPIFFS and the POSIX file API, but some microcontroller platforms don't support this natively, so an extension would be required.\n\n## Unit testing\n\nMicroOCPP includes a number of unit tests based on the [Catch2](https://github.com/catchorg/Catch2) framework. A [GitHub Action](https://github.com/matth-x/MicroOcpp/actions) runs the unit tests against each new commit in the MicroOCPP repository, which ensures that new features don't break old code.\n\nThe scope of the unit tests is to to ensure a correct implementation of OCPP and to validate the high-level API against its definition. For that, it is not necessary to establish an actual test connection to an OCPP server. In fact, real-world communication would disturb the tests and make them undeterministic. That's why the test suite is fully based on an integrated, tiny OCPP test server which the OCPP client reaches over a loopback connection. The test suite does not access the WebSocket library. When making the unit tests of the main firmware, it is not necessary to check the full OCPP communication, but only to validate correct usage of the high-level API. An example of how the library can be initialized with a loopback connection can be found in its test suite.\n\n## Microcontroller optimization\n\nAs a library for microcontrollers, the design of MicroOCPP considers the strict memory limits and complies with the best practices of embedded software development. Also, a few measures were taken to optimize the memory usage which include the spare inclusion of external libraries, an optimization of the internal data structures and the exclusion of C++ run-time type information (RTTI) and exceptions. Features of C++ which may have a larger footprint are carefully used such as the standard template library (STL) and lambda functions. The STL increases the robustness of the code and lambdas prove to be a powerful tool to deal with the complexity of asynchronous data processing in embedded systems. That's also why the high-level API has many functional parameters.\n\nBecause of the high importance of C in the embedded world, MicroOCPP provides its high-level API in C too. It is typically simple to instruct the compiler to compile and link the C++-based library in a C-based firmware development. In case that the firmware requires custom features which are not part of the C-API, then the firmware can implement it in a new C++ source file, export the new functions to the C namespace and use it normally in the main source.\n\nWhile memory constraints are of concern, the execution time generally is not. OCPP is rather uncomplex on the algorithmic side for clients, since there is no need for elaborate planning algorithms or complex data transformations.\n\nLow resource requirements also allow new usage areas on top of EV charging. For example, MicroOCPP has been ported to ordinary IoT equipment such as Wi-Fi sockets to integrate further electric devices into OCPP networks.\n\nAlthough MicroOCPP is optimized for the usage on microcontrollers, it is also suitable for embedded Linux systems. With more memory available, the upper limits of the internal data structures can be increased, leading to a more versatile support of charging use cases. Also, the separation of the charger firmware into multiple processes can lead to more robustness. MicroOCPP can be extended by an inter-process communication (IPC) interface to run in a separate process.\n"
  },
  {
    "path": "docs/migration.md",
    "content": "# Migrating to v1.1\n\nAs a new minor version, all features should work the same as in v1.0 and existing integrations are mostly backwards compatible. However, some fixes / cleanup steps in MicroOCPP require syntactic changes or special consideration when upgrading from v1.0 to v1.1. The known pitfalls are as follows:\n\n- The default branch has been renamed from `master` into `main`\n- Need to include extra headers: the transitive includes have been cleaned a bit. Probably it's necessary to add more includes next to `#include <MicroOcpp.h>`. E.g.<br/>`#include <MicroOcpp/Model/Diagnostics/DiagnosticsService.h>`<br/>`#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>`\n- `ocppPermitsCharge()` does not consider failures reported by the charger anymore. Before v1.1 it was possible to report failures to MicroOCPP using ErrorCodeInputs and then to rely on `ocppPermitsCharge()` becoming false when a failure occurs. For backwards compatibility, complement any occurence to `ocppPermitsCharge() && !isFaulted()`\n- `setEnergyMeterInput` changed the expected return type of the callback function from `float` to `int` (see [#301](https://github.com/matth-x/MicroOcpp/pull/301))\n- The return type of the UnlockConnector handler also changed from `PollResult<bool>` to enum `UnlockConnectorResult` (see [#271](https://github.com/matth-x/MicroOcpp/pull/271))\n\nIf upgrading MicroOcppMongoose at the same time, then the following changes are very important to consider:\n\n- Certificates are no longer copied into heap memory, but the MO-Mongoose class takes the passed certificate pointer as a zero-copy parameter. The string behind the passed pointer must outlive the MO-Mongoose class (see [#10](https://github.com/matth-x/MicroOcppMongoose/pull/10))\n- WebSocket authorization keys are no longer stored as c-strings, but as `unsigned char` buffers. For backwards compatibility, a null-byte is still appended and the buffer can be accessed as c-string, but this should be tested in existing deployments. Furtermore, MicroOCPP only accepts hex-encoded keys coming via ChangeConfiguration which is mandated by the standard. This also may break existing deployments (see [#4](https://github.com/matth-x/MicroOcppMongoose/pull/4)).\n\nIf accessing the MicroOCPP modules directly (i.e. not over `MicroOcpp.h` or `MicroOcpp_c.h`) then there are likely some more modifications to be done. See the history of pull requests where each change to the code is documented. However, if the existing integration compiles under the new MO version, then there shouldn't be too many unexpected incompatibilities.\n\n## Migrating to v1.0\n\nThe API has been continously improved to best suit the common use cases for MicroOCPP. Moreover, the project has been given a new name to prevent confusion with the relation to the Arduino platform and to reflect the project goals properly. With the new project name, the API has been frozen for the v1.0 release.\n\n### Adopting the new project name in existing projects\n\nFind and replace the keywords in the following.\n\nIf using the C-facade (skip if you don't use anything from *ArduinoOcpp_c.h*):\n\n- `AO_Connection` to `OCPP_Connection`\n- `AO_Transaction` to `OCPP_Transaction`\n- `AO_FilesystemOpt` to `OCPP_FilesystemOpt`\n- `AO_TxNotification` to `OCPP_TxNotification`\n- `ao_set_console_out_c` to `ocpp_set_console_out_c`\n\nChange this in any case:\n\n- `ArduinoOcpp` to `MicroOcpp`\n- `\"AO_` to `\"Cst_` (define build flag `MO_CONFIG_EXT_PREFIX=\"AO_\"` to keep old config keys)\n- `AO_` to `MO_`\n- `ocpp_` to `mocpp_`\n\nChange this if used anywhere:\n\n- `ao_set_console_out` to `mocpp_set_console_out`\n- `ao_tick_ms` to `mocpp_tick_ms`\n\nIf using the C-facade, change this as the final step:\n\n- `ao_` to `ocpp_`\n\n### Further API changes to consider\n\nIn addition to the new project name, the API has also been reworked for more consistency. After renaming the existing project as described above, also take a look at the [changelogs](https://github.com/matth-x/MicroOcpp/blob/1.0.x/CHANGELOG.md) (see Section Changed for v1.0.0).\n\n**If something is missing in this guide, please share the issue here:** [https://github.com/matth-x/MicroOcpp/issues/176](https://github.com/matth-x/MicroOcpp/issues/176)\n"
  },
  {
    "path": "docs/modules.md",
    "content": "# Modules\n\nThis chapter gives an overview of the class structure of MicroOCPP.\n\n## Context\n\nThe *Context* contains all runtime data of MicroOCPP. Every data object which this library creates is stored in the Context instance, except only the Configuration. So it is the basic entry point to the internals of the library. The structure of the context follows the main architecture as described in [this introduction](intro-tech) and consists of the Request queue and message deserializer for the RPC framework and the Model object for the OCPP model and behavior (see below).\n\nWhen the library is initialized, `getOcppContext()` returns the current Context object.\n\n## Model\n\nThe *Model* represents the OCPP device model and behavior. OCPP defines a rough charger model, i.e. the hardware parts of the charger and their basic functionality in relation to the OCPP operations. Furthermore, OCPP specifies a few only software related features like the reservation of the charger. This charger model is implemented as straightforward C++ data structures and corresponding algorithms.\n\nThe implementation of the Model is structured into a top-level Model class and the subordinate Service classes. Each Service class represents a functional block of the OCPP specification and implements the corresponding data structures and functionality. The definition of the functional blocks in MicroOCPP is very similar to the feature profiles in OCPP. Only the Core profile is split into multiple functional blocks to keep a smaller module scope.\n\nThe following list contains the resulting functional blocks:\n\n- **Authorization**: local information of user identifiers and their authorization status\n- **Boot**: implementation of the *preboot* behavior, i.e. sending and processing the BootNotification message\n- **ChargingSession**: management of charging sessions and control of the high power charging hardware\n- **Diagnostics**: GetDiagnostics upload routine\n- **FirmwareManagement**: UpdateFirmware download routine\n- **Heartbeat**: periodic OCPP Heartbeats (not including WebSocket ping-pongs)\n- **Metering**: periodic MeterValue messages and local caching\n- **Reservation**: management of Reservation lists and their effect on the authorization routine\n- **Reset**: execution of OCPP Reset message\n- **Transactions**: transaction journal behind StartTransaction and StopTransaction messages and *Transaction* class for extensions of the transaction mechanism\n\n## Requests\n\nThe *Request* class and all similarly named classes implement the Remote Procdure Call (RPC) framework of OCPP. A request executes an operation on the remote end of an OCPP connection. If a charger sends a request to a server, then the server will update its data base with the payload and vice versa. After receiving a request, each node replies with a confirmation, acknowledging the successful execution of the operation or notifying about an error.\n\nWhen being offline, outgoing requests must be queued before sending which is implemented in *RequestQueue*. Queueing is especially challenging for longer offline periods when the number of cached messages exceeds the memory limit. To address this, messages are swapped to the flash memory when the queue limit is reached as implemented in the *RequestStore* and *RequestQueueStorageStrategy* class. Incoming messages can be processed directly and don't have an extensive queueing mechanism.\n\n## Operations\n\nEvery OCPP operation (e.g. Heartbeat, BootNotification) has a dedicated class for creating outgoing messages, interpreting incoming messages, executing the specified OCPP action and handling responses. Operations work on the data structures of the Model layer.\n\nTo send operations to the OCPP server, they must be wrapped into a Request object. The RPC framework and operations are separated modules. While the RPC framework (including the Request class) deals with the messaging mechanism and transfering data to the other OCPP device, operations define the effect on the OCPP model and data structure and execute the desired action. The operation classes inherit from *Operation* which is the interface visible to the Request class.\n\nIncoming messages are unmarshalled using the *OperationRegistry*. During the initialization phase of the library, the Model classes register all supported operations with their name and an instantiator. The instantiator, when executed, provides the Request interpreter with an instance of the corresponding Operation subclasses. It is possible to extend MicroOCPP by adding new Operation instantiators to the registry, or to modify the behavior by overriding the default Operation implementations. In addition to that, event handlers can be set which the RPC queue will notify with the payload once operations are sent or received.\n\n## Configuration\n\nConfigurations like the HeartbeatInterval are managed by the *Configuration* module which consists of\n\n- *AbstractConfiguration*: a single configuration as a key-value pair without value type\n    - *Configuration*: a concrete configuration with a value type like `bool` or `const char*`. Inherits from AbstractConfiguration\n- *ConfigurationContainer*: a collection of AbstractConfigurations and an optional storage implementation. Multiple containers can be set for a separation of the configurations and different storage strategies. Each container has a unique file name\n    - *ConfigurationContainerVolatile*: no persistency and access to the file system\n    - *ConfigurationContainerFlash*: persistency by storing JSON files on the flash\n\nIf another storage implementation is required (e.g. for syncing with an external configuration manager), then it's possible to add a custom ConfigurationContainer.\n\nIn the initialization phase, MicroOCPP loads the built-in Configurations with hard-coded factory defaults and a default storage structure. To customize the factory defaults or which ConfigurationContainers will be used, the Configuration module must be initialized before loading the library. To do so, call `configuration_init(...)`. Then the factory defaults can be applied by calling `declareConfiguration<T>(...)` with the desired default value. To use a custom ConfigurationContainer, call `addConfigurationContainer(...)` with the custom implementation. When the library is loaded afterwards, it will use the previously provided Configurations / Containers and create only the data structure which hasn't been set already.\n"
  },
  {
    "path": "docs/prerequisites.md",
    "content": "# Development tools and basic prerequisites\n\nThis page explains how to work with this library using the appropriate development tools. Skip it if your IDE is set up and you already have an OCPP test server.\n\n## Development tools prerequisites\n\nThroughout these document pages, it is assumed that you already have set up your development environment and that you are familiar with the corresponding building, flashing and (basic) debugging routines. MicroOCPP runs in many environments (from the Arduino-IDE to proprietary microcontroller IDEs like the Code Composer Studio). If you do not have any preferences yet, it is highly recommended to get started with VSCode + the PlatformIO add-on, since it is the favorite setup of the community and therefore you find the most related information in the Issues pages of the main repository.\n\nThere are many high-quality tutorials for out there for setting up VSCode + PIO. The following site covers everything you need to know:\n[https://randomnerdtutorials.com/vs-code-platformio-ide-esp32-esp8266-arduino/](https://randomnerdtutorials.com/vs-code-platformio-ide-esp32-esp8266-arduino/)\n\nOnce that's done, adding MicroOCPP is no big deal anymore. However, let's discuss another very important tool for your project first.\n\n## OCPP Server prerequisites\n\nMicroOCPP is just a client, but all the magic of OCPP lives in the communication between a client and a server. Although it *is* possible to run MicroOCPP without a real server for testing purposes, the best approach for getting started is to get the hands on a real server. So you can always use the client in a practical setup, see immediate results and simplify development a lot.\n\nPerhaps you were already given access to an OCPP server for your project. Then you can use that, it should work fine. If you don't have a server already, it is highly recommended to get\nSteVe ([https://github.com/steve-community/steve](https://github.com/steve-community/steve)).\nIt allows to control every detail of the OCPP operations and shows detail-rich information about the results. And again, it is the favorite test server of the community, so you will find the most related information on the Web. For the installation instructions, please refer to the\n[SteVe docs](https://github.com/steve-community/steve#configuration-and-installation).\n\nIn case you can't wait to get started, you can make the first connection test with a WebSocket echo server as a fake OCPP service. MicroOCPP supports that: it can send all messages to an echo server which reflects all traffic. MicroOCPP gets back its own messages and replies to itself with mocked responses. Complicated, but it does work and the console will show a valid OCPP communication. An example echo server is given in the following section. For the further development though, you will definitely need a real OCPP server.\n\n## Project structure\n\nMicroOCPP is a library, i.e. it is not a full firmware, but just solves one specific task in your project which is the OCPP connectivity. The project structure should reflect this: typically you download MicroOCPP into a libraries or dependencies subfolder, while the main part of the development takes place in a main source folder. All dependencies of MicroOCPP (i.e. ArduinoJson, see the dependencies sections) should be located in the same libraries or dependencies folder.\n\nWhen the include paths are correctly set up, you should be able `#include <MicroOcpp.h>` at the top of your own source files. This setup keeps the OCPP library source separate from your integration and gives the project a clear structure.\n\n## Dependency managers\n\nCurrently, the PlatformIO dependency manager is supported. In the `platformio.ini` manifest, you can add `matth-x/MicroOcpp` to the `lib_deps` section.\n"
  },
  {
    "path": "docs/security.md",
    "content": "# Security\n\nMicroOCPP is designed to be compatible with IoT devices which leads to special considerations regarding cyber security. This section describes the challenges and security concepts of MicroOCPP.\n\n## Challenges of using microcontrollers in safety-critical environments\n\nThe two challenges are as follows:\n\n1. Lack of process virtualization in RTOS operating systems\n2. Less attention for potential vulnerabilities in the used libraries\n\nIn a general purpose OS like Linux, the internet communication modules of an application typically run in a different process than the data base or the hardware supervision / control function. In contrast, on a typical RTOS, all modules are compiled into the same binary, sharing the same address space and lifecycle when being executed. This means that once the network stack crashes, all software on the chip is reset and a vulnerability on the network stack could be exploited to read or manipulate the data of the full runtime environment.\n\nChallenge 2) is due to the fact that OCPP uses standard web technology (WebSocket over TLS), but microcontrollers are missing out the most widespread networking software like OpenSSL or the networking libraries of Linux. The available networking libraries for microcontrollers are also audited well (e.g. lwIP, mbedTLS), but in general there is more attention on potential vulnerabilites in the Linux world, because a huge share of commercial IT systems is based on Linux.\n\nOn the upside, an advantage of microcontrollers is their single purpose usage and thus, reduced complexity. Many security breaches are caused by misconfigured and often even superflous software components (e.g. due to overlooked open ports) which are not a regular part of a microcontroller firmware.\n\n## Security measures of MicroOCPP\n\nTo address the challenges, the following measures were taken:\n\n- Input sanitazion: MicroOCPP only accepts the JSON format for all input. It is validated by ArduinoJson. Every JSON value is checked against the expected format and for conformity with the OCPP specification before using it. The JSON object is discarded immediately after interpretation\n- Transaction safety: to address crashes and random reboots of the microcontroller during operation, all activities of the OCPP library are programmed so that they will either be resumed or fully reverted after reboots, preventing inconsistent states. See also [Transaction safety](../intro-tech/#transaction-safety)\n- Careful choice of the dependencies: the mandatory dependency, [ArduinoJson](https://github.com/bblanchon/ArduinoJson), has a test coverage of nearly 100% and is fuzzed. The same goes for the recommended WebSocket library, [Mongoose](https://github.com/cesanta/mongoose). Both projects are very relevant in their field with over 6k and 9k stars on GitHub\n\nTwo further measures would be beneficial and could be requested via support request:\n\n- Precautious memory allocation: migrating memory management to the stack and where possible would simplify code analysis and reduce the potential of vulnerabilities\n- OCPP fuzzer: as a stateful application protocol, there are specific challenges of developing a fuzzer. An open source fuzzing framework for OCPP could reveal vulnerabilities and be of use for other OCPP projects as well. MicroOCPP is a good foundation for trying new fuzzing approaches. The exposure of the main-loop function and the clock allow a fine-grained access to the program flow and facilitating random alterations of the environment conditions. Furthermore, all persistent data is stored in the JSON format and it is possible to develop a grammatic which contains both a device status and incoming OCPP messages. The Configuration interface could be reused for further status variables which don't need to be persistent in practice, but would improve fuzzing performance when being accessible by the fuzzer.\n- Memory pool: object orientation is a very helpful programming paradigm for OCPP. The standard contains a lot of polymorphic entities and optional or variable length data fields. MicroOCPP makes use of the heap and allocates new chunks of memory as the device model is populated with data. On the upside this allows to save a lot of memory during normal operation, but it also entails the risk of memory depletion of the whole controller. A fixed memory pool for OCPP would encapsulate the heap usage to a certain address space and set a hard limit for the memory consumption and avoid polluting the shared heap area by heap fragmentation. To realize the memory pool, it would be necessary to make the allocate and deallocate functions configurable by the client code. Then appropriate (de)allocators can be injected limiting the memory use to a restricted address area. As a consequence, a more thorough allocation error handling in the MicroOCPP code is required and test cases which randomly suppress allocations to test if the library always reverts to a consistent state. A less invasive alternative to memory pools is to inject measured (de)allocators which just prevent the allocation of new memory chunks after a certain threshold has been exceeded. This programming technique would also allow to create much more fine-grained benchmarks of the library.\n\n## Measures to be taken by the EVSE vendor\n\nAs a general rule, the communication controller which is exposed to the internet shouldn't be used for safety-critical tasks on the charging hardware. That's because the networking stack is a very complex piece of software which very likely still has open bugs which can crash the controller despite all the effort to improve it. Safety-critical tasks on the charging hardware shouldn't rely on a controller which could crash at any time because of incoming network traffic. To mitigate this, either the OCPP library and internet functionality should be placed onto a separate chip, or the most vital safety functionality should get a dedicated controller.\n\nThe recommended [Mongoose WebSocket adapter for MicroOCPP](https://github.com/matth-x/MicroOcppMongoose) supports the OCPP Security Profile 2 (TLS with Basic Authentication) and needs to be provided with the necessary TLS certificate.\n\nMost IoT-controllers have built-in mechanisms to ensure the authenticity of their firmware. For example, the Espressif32 supports [Secure Boot](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/security/secure-boot-v2.html) which is a signature verification of the installed firmware before that firmware is executed. Many platforms also have a built-in signature verification for incoming OTA firmware updates. To prove the authenticity of the charger to the OCPP server, it is also important to keep the WebSocket key secret by [encrypting the flash memory](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/security/flash-encryption.html). These security mechanisms heavily depend on the host controller which runs MicroOCPP. It is the responsibility of the main firmware to make proper use of them.\n\n## OCPP Security Whitepaper and ISO 15118\n\nWith MicroOCPP, the recommended way of handling certificates on microcontrollers is to compile them into the firmware binary and to rely on the built-in firmware signature checks of the host microcontroller platform. This lean approach results in a smaller attack vector compared to establishing a separate infrastructure for the server- and firmware certificate. It can be assumed that the OTA functionality of the microcontrollers is thoroughly tested and consequently, reaching a comparable level of robustness would require much effort.\n\nIn case the certificate handling mechanism of the Security Whitepaper is preferred, then the EVSE vendor needs to implement it via a custom extension. Unfortunately, this mechanism hasn't been requested yet and is not natively supported by MicroOCPP yet. The new custom operations can be implemented by extending the class `Operation`. A handler for incoming messages can be registered via `OperationRegistry::registerOperation(...)`. To send custom messages to the server, use `Context::initiateRequest(...)`.\n\nA further challenge for microcontrollers is the relatively low processor speed which becomes relevant for a potential ISO 15118 integration. Some incoming message types (`AuthorizationReq` and `MeteringReceiptReq`) include a signature which needs to be verified on the communications controller of the EVSE. Moreover, messages in the ISO 15118 V2G protocol have a maximum round trip time (which is 2 seconds for the message types in question) and so the signature verification is time-contrained. [These benchmarks](https://web.archive.org/web/20230724184529/https://www.oryx-embedded.com/benchmark/espressif/crypto-esp32.html) for the Espressif32 show that for some signature algorithms, the verification time can get close or exceed the timing requirements of ISO 15118 if done on the processor only. As a consequence, hardware acceleration by the crypto-core is mandatory to ensure a robust communication between the EVSE and EV. Before making a communications controller with ISO 15118 support, the performance of the host controller should be benchmarked and checked against the requirements.\n\n*Disclaimer: the outlined risks in this section are not a complete list. Also, every system has unique security challenges which require individual attention. In doubt, please consult an IT-security specialist.*\n"
  },
  {
    "path": "docs/stylesheets/extra.css",
    "content": ":root {\n    --md-primary-fg-color:        #2984C7;\n  }\n"
  },
  {
    "path": "examples/ESP/main.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <Arduino.h>\n#if defined(ESP8266)\n#include <ESP8266WiFi.h>\n#include <ESP8266WiFiMulti.h>\nESP8266WiFiMulti WiFiMulti;\n#elif defined(ESP32)\n#include <WiFi.h>\n#else\n#error only ESP32 or ESP8266 supported at the moment\n#endif\n\n#include <MicroOcpp.h>\n\n#define STASSID \"YOUR_WIFI_SSID\"\n#define STAPSK  \"YOUR_WIFI_PW\"\n\n#define OCPP_BACKEND_URL   \"ws://echo.websocket.events\"\n#define OCPP_CHARGE_BOX_ID \"\"\n\n//\n//  Settings which worked for my SteVe instance:\n//\n//#define OCPP_BACKEND_URL   \"ws://192.168.178.100:8180/steve/websocket/CentralSystemService\"\n//#define OCPP_CHARGE_BOX_ID \"esp-charger\"\n\nvoid setup() {\n\n    /*\n     * Initialize Serial and WiFi\n     */ \n\n    Serial.begin(115200);\n\n    Serial.print(F(\"[main] Wait for WiFi: \"));\n\n#if defined(ESP8266)\n    WiFiMulti.addAP(STASSID, STAPSK);\n    while (WiFiMulti.run() != WL_CONNECTED) {\n        Serial.print('.');\n        delay(1000);\n    }\n#elif defined(ESP32)\n    WiFi.begin(STASSID, STAPSK);\n    while (!WiFi.isConnected()) {\n        Serial.print('.');\n        delay(1000);\n    }\n#else\n#error only ESP32 or ESP8266 supported at the moment\n#endif\n\n    Serial.println(F(\" connected!\"));\n\n    /*\n     * Initialize the OCPP library\n     */\n    mocpp_initialize(OCPP_BACKEND_URL, OCPP_CHARGE_BOX_ID, \"My Charging Station\", \"My company name\");\n\n    /*\n     * Integrate OCPP functionality. You can leave out the following part if your EVSE doesn't need it.\n     */\n    setEnergyMeterInput([]() {\n        //take the energy register of the main electricity meter and return the value in watt-hours\n        return 0.f;\n    });\n\n    setSmartChargingCurrentOutput([](float limit) {\n        //set the SAE J1772 Control Pilot value here\n        Serial.printf(\"[main] Smart Charging allows maximum charge rate: %.0f\\n\", limit);\n    });\n\n    setConnectorPluggedInput([]() {\n        //return true if an EV is plugged to this EVSE\n        return false;\n    });\n\n    //... see MicroOcpp.h for more settings\n}\n\nvoid loop() {\n\n    /*\n     * Do all OCPP stuff (process WebSocket input, send recorded meter values to Central System, etc.)\n     */\n    mocpp_loop();\n\n    /*\n     * Energize EV plug if OCPP transaction is up and running\n     */\n    if (ocppPermitsCharge()) {\n        //OCPP set up and transaction running. Energize the EV plug here\n    } else {\n        //No transaction running at the moment. De-energize EV plug\n    }\n\n    /*\n     * Use NFC reader to start and stop transactions\n     */\n    if (/* RFID chip detected? */ false) {\n        String idTag = \"0123456789ABCD\"; //e.g. idTag = RFID.readIdTag();\n\n        if (!getTransaction()) {\n            //no transaction running or preparing. Begin a new transaction\n            Serial.printf(\"[main] Begin Transaction with idTag %s\\n\", idTag.c_str());\n\n            /*\n             * Begin Transaction. The OCPP lib will prepare transaction by checking the Authorization\n             * and listen to the ConnectorPlugged Input. When the Authorization succeeds and an EV\n             * is plugged, the OCPP lib will send the StartTransaction\n             */\n            auto ret = beginTransaction(idTag.c_str());\n\n            if (ret) {\n                Serial.println(F(\"[main] Transaction initiated. OCPP lib will send a StartTransaction when\" \\\n                                 \"ConnectorPlugged Input becomes true and if the Authorization succeeds\"));\n            } else {\n                Serial.println(F(\"[main] No transaction initiated\"));\n            }\n\n        } else {\n            //Transaction already initiated. Check if to stop current Tx by RFID card\n            if (idTag.equals(getTransactionIdTag())) {\n                //card matches -> user can stop Tx\n                Serial.println(F(\"[main] End transaction by RFID card\"));\n\n                endTransaction(idTag.c_str());\n            } else {\n                Serial.println(F(\"[main] Cannot end transaction by RFID card (different card?)\"));\n            }\n        }\n    }\n\n    //... see MicroOcpp.h for more possibilities\n}\n"
  },
  {
    "path": "examples/ESP-IDF/CMakeLists.txt",
    "content": "# The following five lines of boilerplate have to be in your project's\n# CMakeLists in this exact order for cmake to work correctly\ncmake_minimum_required(VERSION 3.5)\n\ninclude($ENV{IDF_PATH}/tools/cmake/project.cmake)\nproject(mo_example)\n\nidf_build_set_property(COMPILE_OPTIONS -DMO_TRAFFIC_OUT APPEND)\nidf_build_set_property(COMPILE_OPTIONS -DMO_FILENAME_PREFIX=\"/mo_store/\" APPEND)\n"
  },
  {
    "path": "examples/ESP-IDF/Makefile",
    "content": "#\n# This is a project Makefile. It is assumed the directory this Makefile resides in is a\n# project subdirectory.\n#\n\nPROJECT_NAME := mo_example\n\ninclude $(IDF_PATH)/make/project.mk\n"
  },
  {
    "path": "examples/ESP-IDF/README.md",
    "content": "# ESP-IDF integration example\n\nTo run MicroOcpp on the ESP-IDF platform, please take this example as the starting point. It is widely based on the [Wi-Fi Station Example](https://github.com/espressif/esp-idf/tree/release/v4.4/examples/wifi/getting_started/station) of Espressif. This example works with the ESP-IDF version `4.4`. For a general guide how to setup and use the ESP-IDF, please refer to the documentation of Espressif.\n\n## Setup guide\n\n### Dependencies\n\nPlease clone the following repositories into the respective components-directories:\n\n- [MicroOcpp](https://github.com/matth-x/MicroOcpp) into `components/MicroOcpp`\n- [Mongoose (ESP-IDF integration)](https://github.com/cesanta/mongoose-esp-idf) into `components/mongoose`\n- [Mongoose adapter for MicroOcpp](https://github.com/matth-x/MicroOcppMongoose) into `components/MicroOcppMongoose`\n- [ArduinoJson (v6.21)](https://github.com/bblanchon/ArduinoJson) into `components/ArduinoJson`\n\nFor setup, the following commands could come handy (change to the root directory of the ESP-IDF project first):\n\n```\nrm components/mongoose/.gitkeep\nrm components/MicroOcpp/.gitkeep\nrm components/MicroOcppMongoose/.gitkeep\nrm components/ArduinoJson/.gitkeep\ngit clone https://github.com/matth-x/MicroOcpp components/MicroOcpp\ngit clone --recurse-submodules https://github.com/cesanta/mongoose-esp-idf.git components/mongoose\ngit clone https://github.com/matth-x/MicroOcppMongoose components/MicroOcppMongoose\ngit clone https://github.com/bblanchon/ArduinoJson components/ArduinoJson\ncd components/ArduinoJson\ngit checkout 3e1be980d93e47b2a0073efeeb9a9396fd7a83be\n```\n\nThe setup is done if the following include statements work:\n\n```cpp\n#include <MicroOcpp.h>\n#include <mongoose.h>\n#include <MicroOcppMongooseClient.h>\n#include <ArduinoJson.h>\n```\n\n### Configure the project\n\nOpen the project configuration menu (`idf.py menuconfig`). \n\nIn the `Example Configuration` menu:\n\n* Set the Wi-Fi configuration.\n    * Set `WiFi SSID`.\n    * Set `WiFi Password`.\n    * Set `OCPP backend URL`. (e.g. `ws://ocpp.example.com/steve/websocket/CentralSystemService`)\n    * Set `ChargeBoxId`. (e.g. `my-charger` - last part of the WebSocket URL\n    * Set `AuthorizationKey`, or leave empty if not necessary\n\nOptional: If you need, change the other options according to your requirements.\n"
  },
  {
    "path": "examples/ESP-IDF/components/ArduinoJson/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/ESP-IDF/components/ArduinoOcpp/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/ESP-IDF/components/ArduinoOcppMongoose/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/ESP-IDF/components/README.md",
    "content": "## Components folder structure\n\nThe ESP-IDF integration requires at least the following components:\n\n- [MicroOcpp](https://github.com/matth-x/MicroOcpp)\n- [Mongoose (ESP-IDF integration)](https://github.com/cesanta/mongoose-esp-idf)\n- [Mongoose adapter for MicroOcpp](https://github.com/matth-x/MicroOcppMongoose)\n- [ArduinoJson (v6.21)](https://github.com/bblanchon/ArduinoJson)\n\nThis example only provides the folder structure. You need to clone every project into it in order to run the example.\n"
  },
  {
    "path": "examples/ESP-IDF/components/mongoose/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/ESP-IDF/main/CMakeLists.txt",
    "content": "idf_component_register(SRCS \"main.c\"\n                    INCLUDE_DIRS \".\")\n"
  },
  {
    "path": "examples/ESP-IDF/main/Kconfig.projbuild",
    "content": "menu \"Example Configuration\"\n\n    config ESP_WIFI_SSID\n        string \"WiFi SSID\"\n        default \"myssid\"\n        help\n            SSID (network name) for the example to connect to.\n\n    config ESP_WIFI_PASSWORD\n        string \"WiFi Password\"\n        default \"mypassword\"\n        help\n            WiFi password (WPA or WPA2) for the example to use.\n\n    config ESP_MAXIMUM_RETRY\n        int \"Maximum retry\"\n        default 5\n        help\n            Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.\n    \n    config MO_OCPP_BACKEND\n        string \"OCPP backend URL\"\n        default \"ws://echo.websocket.events/\"\n        help\n            URL of the OCPP backend\n    \n    config MO_CHARGEBOXID\n        string \"ChargeBoxId\"\n        default \"\"\n        help\n            ChargeBoxId as it appears in the WebSocket connection URL\n\n    config MO_AUTHORIZATIONKEY\n        string \"Authorization Key\"\n        default \"\"\n        help\n            Passphrase for connecting to the OCPP server\nendmenu\n"
  },
  {
    "path": "examples/ESP-IDF/main/component.mk",
    "content": "#\n# Main component makefile.\n#\n# This Makefile can be left empty. By default, it will take the sources in the\n# src/ directory, compile them and link them into lib(subdirectory_name).a\n# in the build directory. This behaviour is entirely configurable,\n# please read the ESP-IDF documents if you need to do this.\n#\n"
  },
  {
    "path": "examples/ESP-IDF/main/main.c",
    "content": "/* Based on the ESP-IDF WiFi station Example (see https://github.com/espressif/esp-idf/tree/release/v4.4/examples/wifi/getting_started/station/main)\n\n   This example code extends the WiFi example with the necessary calls to establish an\n   OCPP connection on the ESP-IDF. \n*/\n#include <string.h>\n#include \"freertos/FreeRTOS.h\"\n#include \"freertos/task.h\"\n#include \"freertos/event_groups.h\"\n#include \"esp_system.h\"\n#include \"esp_wifi.h\"\n#include \"esp_event.h\"\n#include \"esp_log.h\"\n#include \"nvs_flash.h\"\n\n#include \"lwip/err.h\"\n#include \"lwip/sys.h\"\n\n/* MicroOcpp includes */\n#include <mongoose.h>\n#include <MicroOcpp_c.h> //C-facade of MicroOcpp\n#include <MicroOcppMongooseClient_c.h> //WebSocket integration for ESP-IDF\n\n/* The examples use WiFi configuration that you can set via project configuration menu\n\n   If you'd rather not, just change the below entries to strings with\n   the config you want - ie #define EXAMPLE_WIFI_SSID \"mywifissid\"\n*/\n#define EXAMPLE_ESP_WIFI_SSID      CONFIG_ESP_WIFI_SSID\n#define EXAMPLE_ESP_WIFI_PASS      CONFIG_ESP_WIFI_PASSWORD\n#define EXAMPLE_ESP_MAXIMUM_RETRY  CONFIG_ESP_MAXIMUM_RETRY\n#define EXAMPLE_MO_OCPP_BACKEND    CONFIG_MO_OCPP_BACKEND\n#define EXAMPLE_MO_CHARGEBOXID     CONFIG_MO_CHARGEBOXID\n#define EXAMPLE_MO_AUTHORIZATIONKEY CONFIG_MO_AUTHORIZATIONKEY\n\n/* FreeRTOS event group to signal when we are connected*/\nstatic EventGroupHandle_t s_wifi_event_group;\n\n/* The event group allows multiple bits for each event, but we only care about two events:\n * - we are connected to the AP with an IP\n * - we failed to connect after the maximum amount of retries */\n#define WIFI_CONNECTED_BIT BIT0\n#define WIFI_FAIL_BIT      BIT1\n\nstatic const char *TAG = \"wifi station\";\n\nstatic int s_retry_num = 0;\n\nstatic void event_handler(void* arg, esp_event_base_t event_base,\n                                int32_t event_id, void* event_data)\n{\n    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {\n        esp_wifi_connect();\n    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {\n        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {\n            esp_wifi_connect();\n            s_retry_num++;\n            ESP_LOGI(TAG, \"retry to connect to the AP\");\n        } else {\n            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);\n        }\n        ESP_LOGI(TAG,\"connect to the AP fail\");\n    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {\n        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;\n        ESP_LOGI(TAG, \"got ip:\" IPSTR, IP2STR(&event->ip_info.ip));\n        s_retry_num = 0;\n        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);\n    }\n}\n\nvoid wifi_init_sta(void)\n{\n    s_wifi_event_group = xEventGroupCreate();\n\n    ESP_ERROR_CHECK(esp_netif_init());\n\n    ESP_ERROR_CHECK(esp_event_loop_create_default());\n    esp_netif_create_default_wifi_sta();\n\n    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();\n    ESP_ERROR_CHECK(esp_wifi_init(&cfg));\n\n    esp_event_handler_instance_t instance_any_id;\n    esp_event_handler_instance_t instance_got_ip;\n    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,\n                                                        ESP_EVENT_ANY_ID,\n                                                        &event_handler,\n                                                        NULL,\n                                                        &instance_any_id));\n    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,\n                                                        IP_EVENT_STA_GOT_IP,\n                                                        &event_handler,\n                                                        NULL,\n                                                        &instance_got_ip));\n\n    wifi_config_t wifi_config = {\n        .sta = {\n            .ssid = EXAMPLE_ESP_WIFI_SSID,\n            .password = EXAMPLE_ESP_WIFI_PASS,\n            /* Setting a password implies station will connect to all security modes including WEP/WPA.\n             * However these modes are deprecated and not advisable to be used. Incase your Access point\n             * doesn't support WPA2, these mode can be enabled by commenting below line */\n\t     .threshold.authmode = WIFI_AUTH_WPA2_PSK,\n        },\n    };\n    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );\n    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );\n    ESP_ERROR_CHECK(esp_wifi_start() );\n\n    ESP_LOGI(TAG, \"wifi_init_sta finished.\");\n\n    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum\n     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */\n    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,\n            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,\n            pdFALSE,\n            pdFALSE,\n            portMAX_DELAY);\n\n    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually\n     * happened. */\n    if (bits & WIFI_CONNECTED_BIT) {\n        ESP_LOGI(TAG, \"connected to ap SSID:%s password:%s\",\n                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);\n    } else if (bits & WIFI_FAIL_BIT) {\n        ESP_LOGI(TAG, \"Failed to connect to SSID:%s, password:%s\",\n                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);\n    } else {\n        ESP_LOGE(TAG, \"UNEXPECTED EVENT\");\n    }\n\n    /* The event will not be processed after unregister */\n    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));\n    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));\n    vEventGroupDelete(s_wifi_event_group);\n}\n\nvoid app_main(void)\n{\n    //Initialize NVS\n    esp_err_t ret = nvs_flash_init();\n    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {\n      ESP_ERROR_CHECK(nvs_flash_erase());\n      ret = nvs_flash_init();\n    }\n    ESP_ERROR_CHECK(ret);\n\n    ESP_LOGI(TAG, \"ESP_WIFI_MODE_STA\");\n    wifi_init_sta();\n\n    /* Initialize Mongoose (necessary for MicroOcpp)*/\n    struct mg_mgr mgr;        // Event manager\n    mg_mgr_init(&mgr);        // Initialise event manager\n    mg_log_set(MG_LL_DEBUG);  // Set log level\n\n    /* Initialize MicroOcpp */\n    struct OCPP_FilesystemOpt fsopt = { .use = true, .mount = true, .formatFsOnFail = true};\n\n    OCPP_Connection *osock = ocpp_makeConnection(&mgr,\n            EXAMPLE_MO_OCPP_BACKEND, \n            EXAMPLE_MO_CHARGEBOXID, \n            EXAMPLE_MO_AUTHORIZATIONKEY, \"\", fsopt);\n    ocpp_initialize(osock, \"ESP-IDF charger\", \"Your brand name here\", fsopt, false, false);\n\n    /* Enter infinite loop */\n    while (1) {\n        mg_mgr_poll(&mgr, 10);\n        ocpp_loop();\n    }\n    \n    /* Deallocate ressources */\n    ocpp_deinitialize();\n    ocpp_deinitConnection(osock);\n    mg_mgr_free(&mgr);\n    return;\n}\n"
  },
  {
    "path": "examples/ESP-IDF/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     ,        0x4000,\notadata,  data, ota,     ,        0x2000,\nphy_init, data, phy,     ,        0x1000,\nota_0,    app,  ota_0,   ,        0x190000,\nota_1,    app,  ota_1,   ,        0x190000,\nmo,       data, spiffs,  ,        0xD0000,"
  },
  {
    "path": "examples/ESP-IDF/sdkconfig",
    "content": "#\n# Automatically generated file. DO NOT EDIT.\n# Espressif IoT Development Framework (ESP-IDF) Project Configuration\n#\nCONFIG_IDF_CMAKE=y\nCONFIG_IDF_TARGET_ARCH_XTENSA=y\nCONFIG_IDF_TARGET=\"esp32\"\nCONFIG_IDF_TARGET_ESP32=y\nCONFIG_IDF_FIRMWARE_CHIP_ID=0x0000\n\n#\n# SDK tool configuration\n#\nCONFIG_SDK_TOOLPREFIX=\"xtensa-esp32-elf-\"\n# CONFIG_SDK_TOOLCHAIN_SUPPORTS_TIME_WIDE_64_BITS is not set\n# end of SDK tool configuration\n\n#\n# Build type\n#\nCONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y\n# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set\nCONFIG_APP_BUILD_GENERATE_BINARIES=y\nCONFIG_APP_BUILD_BOOTLOADER=y\nCONFIG_APP_BUILD_USE_FLASH_SECTIONS=y\n# end of Build type\n\n#\n# Application manager\n#\nCONFIG_APP_COMPILE_TIME_DATE=y\n# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set\n# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set\n# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set\nCONFIG_APP_RETRIEVE_LEN_ELF_SHA=16\n# end of Application manager\n\n#\n# Bootloader config\n#\nCONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x1000\nCONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y\n# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set\n# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set\n# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set\n# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set\n# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set\n# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set\nCONFIG_BOOTLOADER_LOG_LEVEL_INFO=y\n# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set\n# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set\nCONFIG_BOOTLOADER_LOG_LEVEL=3\n# CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_8V is not set\nCONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y\n# CONFIG_BOOTLOADER_FACTORY_RESET is not set\n# CONFIG_BOOTLOADER_APP_TEST is not set\nCONFIG_BOOTLOADER_WDT_ENABLE=y\n# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set\nCONFIG_BOOTLOADER_WDT_TIME_MS=9000\n# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set\n# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set\n# CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set\n# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set\nCONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0\n# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set\nCONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y\n# end of Bootloader config\n\n#\n# Security features\n#\n# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set\n# CONFIG_SECURE_BOOT is not set\n# CONFIG_SECURE_FLASH_ENC_ENABLED is not set\n# end of Security features\n\n#\n# Serial flasher config\n#\nCONFIG_ESPTOOLPY_BAUD_OTHER_VAL=115200\n# CONFIG_ESPTOOLPY_NO_STUB is not set\n# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set\n# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set\nCONFIG_ESPTOOLPY_FLASHMODE_DIO=y\n# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set\nCONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y\nCONFIG_ESPTOOLPY_FLASHMODE=\"dio\"\n# CONFIG_ESPTOOLPY_FLASHFREQ_80M is not set\nCONFIG_ESPTOOLPY_FLASHFREQ_40M=y\n# CONFIG_ESPTOOLPY_FLASHFREQ_26M is not set\n# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set\nCONFIG_ESPTOOLPY_FLASHFREQ=\"40m\"\n# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set\n# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set\nCONFIG_ESPTOOLPY_FLASHSIZE_4MB=y\n# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set\n# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set\n# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set\n# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set\n# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set\nCONFIG_ESPTOOLPY_FLASHSIZE=\"4MB\"\nCONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y\nCONFIG_ESPTOOLPY_BEFORE_RESET=y\n# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set\nCONFIG_ESPTOOLPY_BEFORE=\"default_reset\"\nCONFIG_ESPTOOLPY_AFTER_RESET=y\n# CONFIG_ESPTOOLPY_AFTER_NORESET is not set\nCONFIG_ESPTOOLPY_AFTER=\"hard_reset\"\n# CONFIG_ESPTOOLPY_MONITOR_BAUD_CONSOLE is not set\n# CONFIG_ESPTOOLPY_MONITOR_BAUD_9600B is not set\n# CONFIG_ESPTOOLPY_MONITOR_BAUD_57600B is not set\nCONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y\n# CONFIG_ESPTOOLPY_MONITOR_BAUD_230400B is not set\n# CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B is not set\n# CONFIG_ESPTOOLPY_MONITOR_BAUD_2MB is not set\n# CONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER is not set\nCONFIG_ESPTOOLPY_MONITOR_BAUD_OTHER_VAL=115200\nCONFIG_ESPTOOLPY_MONITOR_BAUD=115200\n# end of Serial flasher config\n\n#\n# Partition Table\n#\n# CONFIG_PARTITION_TABLE_SINGLE_APP is not set\n# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set\n# CONFIG_PARTITION_TABLE_TWO_OTA is not set\nCONFIG_PARTITION_TABLE_CUSTOM=y\nCONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions.csv\"\nCONFIG_PARTITION_TABLE_FILENAME=\"partitions.csv\"\nCONFIG_PARTITION_TABLE_OFFSET=0x8000\nCONFIG_PARTITION_TABLE_MD5=y\n# end of Partition Table\n\n#\n# Example Configuration\n#\nCONFIG_ESP_WIFI_SSID=\"myssid\"\nCONFIG_ESP_WIFI_PASSWORD=\"mypassword\"\nCONFIG_ESP_MAXIMUM_RETRY=5\nCONFIG_MO_OCPP_BACKEND=\"ws://echo.websocket.events/\"\nCONFIG_MO_CHARGEBOXID=\"\"\nCONFIG_MO_AUTHORIZATIONKEY=\"\"\n# end of Example Configuration\n\n#\n# Compiler options\n#\nCONFIG_COMPILER_OPTIMIZATION_DEFAULT=y\n# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set\n# CONFIG_COMPILER_OPTIMIZATION_PERF is not set\n# CONFIG_COMPILER_OPTIMIZATION_NONE is not set\nCONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y\n# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set\n# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set\nCONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2\n# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set\nCONFIG_COMPILER_HIDE_PATHS_MACROS=y\n# CONFIG_COMPILER_CXX_EXCEPTIONS is not set\n# CONFIG_COMPILER_CXX_RTTI is not set\nCONFIG_COMPILER_STACK_CHECK_MODE_NONE=y\n# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set\n# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set\n# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set\n# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set\n# CONFIG_COMPILER_DISABLE_GCC8_WARNINGS is not set\n# CONFIG_COMPILER_DUMP_RTL_FILES is not set\n# end of Compiler options\n\n#\n# Component config\n#\n\n#\n# Application Level Tracing\n#\n# CONFIG_APPTRACE_DEST_JTAG is not set\nCONFIG_APPTRACE_DEST_NONE=y\nCONFIG_APPTRACE_LOCK_ENABLE=y\n# end of Application Level Tracing\n\n#\n# ESP-ASIO\n#\n# CONFIG_ASIO_SSL_SUPPORT is not set\n# end of ESP-ASIO\n\n#\n# Bluetooth\n#\n# CONFIG_BT_ENABLED is not set\n# end of Bluetooth\n\n#\n# CoAP Configuration\n#\nCONFIG_COAP_MBEDTLS_PSK=y\n# CONFIG_COAP_MBEDTLS_PKI is not set\n# CONFIG_COAP_MBEDTLS_DEBUG is not set\nCONFIG_COAP_LOG_DEFAULT_LEVEL=0\n# end of CoAP Configuration\n\n#\n# Driver configurations\n#\n\n#\n# ADC configuration\n#\n# CONFIG_ADC_FORCE_XPD_FSM is not set\nCONFIG_ADC_DISABLE_DAC=y\n# end of ADC configuration\n\n#\n# MCPWM configuration\n#\n# CONFIG_MCPWM_ISR_IN_IRAM is not set\n# end of MCPWM configuration\n\n#\n# SPI configuration\n#\n# CONFIG_SPI_MASTER_IN_IRAM is not set\nCONFIG_SPI_MASTER_ISR_IN_IRAM=y\n# CONFIG_SPI_SLAVE_IN_IRAM is not set\nCONFIG_SPI_SLAVE_ISR_IN_IRAM=y\n# end of SPI configuration\n\n#\n# TWAI configuration\n#\n# CONFIG_TWAI_ISR_IN_IRAM is not set\n# CONFIG_TWAI_ERRATA_FIX_BUS_OFF_REC is not set\n# CONFIG_TWAI_ERRATA_FIX_TX_INTR_LOST is not set\n# CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID is not set\n# CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT is not set\n# end of TWAI configuration\n\n#\n# UART configuration\n#\n# CONFIG_UART_ISR_IN_IRAM is not set\n# end of UART configuration\n\n#\n# RTCIO configuration\n#\n# CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC is not set\n# end of RTCIO configuration\n\n#\n# GPIO Configuration\n#\n# CONFIG_GPIO_ESP32_SUPPORT_SWITCH_SLP_PULL is not set\n# end of GPIO Configuration\n\n#\n# GDMA Configuration\n#\n# CONFIG_GDMA_CTRL_FUNC_IN_IRAM is not set\n# CONFIG_GDMA_ISR_IRAM_SAFE is not set\n# end of GDMA Configuration\n# end of Driver configurations\n\n#\n# eFuse Bit Manager\n#\n# CONFIG_EFUSE_CUSTOM_TABLE is not set\n# CONFIG_EFUSE_VIRTUAL is not set\n# CONFIG_EFUSE_CODE_SCHEME_COMPAT_NONE is not set\nCONFIG_EFUSE_CODE_SCHEME_COMPAT_3_4=y\n# CONFIG_EFUSE_CODE_SCHEME_COMPAT_REPEAT is not set\nCONFIG_EFUSE_MAX_BLK_LEN=192\n# end of eFuse Bit Manager\n\n#\n# ESP-TLS\n#\nCONFIG_ESP_TLS_USING_MBEDTLS=y\n# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set\n# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set\n# CONFIG_ESP_TLS_SERVER is not set\n# CONFIG_ESP_TLS_PSK_VERIFICATION is not set\n# CONFIG_ESP_TLS_INSECURE is not set\n# end of ESP-TLS\n\n#\n# ESP32-specific\n#\nCONFIG_ESP32_REV_MIN_0=y\n# CONFIG_ESP32_REV_MIN_1 is not set\n# CONFIG_ESP32_REV_MIN_2 is not set\n# CONFIG_ESP32_REV_MIN_3 is not set\nCONFIG_ESP32_REV_MIN=0\nCONFIG_ESP32_DPORT_WORKAROUND=y\n# CONFIG_ESP32_DEFAULT_CPU_FREQ_80 is not set\nCONFIG_ESP32_DEFAULT_CPU_FREQ_160=y\n# CONFIG_ESP32_DEFAULT_CPU_FREQ_240 is not set\nCONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=160\n# CONFIG_ESP32_SPIRAM_SUPPORT is not set\n# CONFIG_ESP32_TRAX is not set\nCONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0\n# CONFIG_ESP32_ULP_COPROC_ENABLED is not set\nCONFIG_ESP32_ULP_COPROC_RESERVE_MEM=0\nCONFIG_ESP32_DEBUG_OCDAWARE=y\nCONFIG_ESP32_BROWNOUT_DET=y\nCONFIG_ESP32_BROWNOUT_DET_LVL_SEL_0=y\n# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_1 is not set\n# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_2 is not set\n# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_3 is not set\n# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_4 is not set\n# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_5 is not set\n# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_6 is not set\n# CONFIG_ESP32_BROWNOUT_DET_LVL_SEL_7 is not set\nCONFIG_ESP32_BROWNOUT_DET_LVL=0\nCONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y\n# CONFIG_ESP32_TIME_SYSCALL_USE_RTC is not set\n# CONFIG_ESP32_TIME_SYSCALL_USE_FRC1 is not set\n# CONFIG_ESP32_TIME_SYSCALL_USE_NONE is not set\nCONFIG_ESP32_RTC_CLK_SRC_INT_RC=y\n# CONFIG_ESP32_RTC_CLK_SRC_EXT_CRYS is not set\n# CONFIG_ESP32_RTC_CLK_SRC_EXT_OSC is not set\n# CONFIG_ESP32_RTC_CLK_SRC_INT_8MD256 is not set\nCONFIG_ESP32_RTC_CLK_CAL_CYCLES=1024\nCONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY=2000\nCONFIG_ESP32_XTAL_FREQ_40=y\n# CONFIG_ESP32_XTAL_FREQ_26 is not set\n# CONFIG_ESP32_XTAL_FREQ_AUTO is not set\nCONFIG_ESP32_XTAL_FREQ=40\n# CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE is not set\n# CONFIG_ESP32_NO_BLOBS is not set\n# CONFIG_ESP32_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set\n# CONFIG_ESP32_COMPATIBLE_PRE_V3_1_BOOTLOADERS is not set\n# CONFIG_ESP32_USE_FIXED_STATIC_RAM_SIZE is not set\nCONFIG_ESP32_DPORT_DIS_INTERRUPT_LVL=5\n# end of ESP32-specific\n\n#\n# ADC-Calibration\n#\nCONFIG_ADC_CAL_EFUSE_TP_ENABLE=y\nCONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y\nCONFIG_ADC_CAL_LUT_ENABLE=y\n# end of ADC-Calibration\n\n#\n# Common ESP-related\n#\nCONFIG_ESP_ERR_TO_NAME_LOOKUP=y\n# end of Common ESP-related\n\n#\n# Ethernet\n#\nCONFIG_ETH_ENABLED=y\nCONFIG_ETH_USE_ESP32_EMAC=y\nCONFIG_ETH_PHY_INTERFACE_RMII=y\nCONFIG_ETH_RMII_CLK_INPUT=y\n# CONFIG_ETH_RMII_CLK_OUTPUT is not set\nCONFIG_ETH_RMII_CLK_IN_GPIO=0\nCONFIG_ETH_DMA_BUFFER_SIZE=512\nCONFIG_ETH_DMA_RX_BUFFER_NUM=10\nCONFIG_ETH_DMA_TX_BUFFER_NUM=10\nCONFIG_ETH_USE_SPI_ETHERNET=y\n# CONFIG_ETH_SPI_ETHERNET_DM9051 is not set\n# CONFIG_ETH_SPI_ETHERNET_W5500 is not set\n# CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL is not set\n# CONFIG_ETH_USE_OPENETH is not set\n# end of Ethernet\n\n#\n# Event Loop Library\n#\n# CONFIG_ESP_EVENT_LOOP_PROFILING is not set\nCONFIG_ESP_EVENT_POST_FROM_ISR=y\nCONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y\n# end of Event Loop Library\n\n#\n# GDB Stub\n#\n# end of GDB Stub\n\n#\n# ESP HTTP client\n#\nCONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y\n# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set\nCONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y\n# end of ESP HTTP client\n\n#\n# HTTP Server\n#\nCONFIG_HTTPD_MAX_REQ_HDR_LEN=512\nCONFIG_HTTPD_MAX_URI_LEN=512\nCONFIG_HTTPD_ERR_RESP_NO_DELAY=y\nCONFIG_HTTPD_PURGE_BUF_LEN=32\n# CONFIG_HTTPD_LOG_PURGE_DATA is not set\n# CONFIG_HTTPD_WS_SUPPORT is not set\n# end of HTTP Server\n\n#\n# ESP HTTPS OTA\n#\n# CONFIG_OTA_ALLOW_HTTP is not set\n# end of ESP HTTPS OTA\n\n#\n# ESP HTTPS server\n#\n# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set\n# end of ESP HTTPS server\n\n#\n# Hardware Settings\n#\n\n#\n# MAC Config\n#\nCONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y\nCONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y\nCONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y\nCONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y\n# CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO is not set\nCONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y\nCONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4\n# end of MAC Config\n\n#\n# Sleep Config\n#\nCONFIG_ESP_SLEEP_POWER_DOWN_FLASH=y\nCONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y\n# CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND is not set\n# CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND is not set\n# end of Sleep Config\n\n#\n# RTC Clock Config\n#\n# end of RTC Clock Config\n# end of Hardware Settings\n\n#\n# IPC (Inter-Processor Call)\n#\nCONFIG_ESP_IPC_TASK_STACK_SIZE=1536\nCONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y\nCONFIG_ESP_IPC_ISR_ENABLE=y\n# end of IPC (Inter-Processor Call)\n\n#\n# LCD and Touch Panel\n#\n\n#\n# LCD Peripheral Configuration\n#\nCONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE=32\n# end of LCD Peripheral Configuration\n# end of LCD and Touch Panel\n\n#\n# ESP NETIF Adapter\n#\nCONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120\nCONFIG_ESP_NETIF_TCPIP_LWIP=y\n# CONFIG_ESP_NETIF_LOOPBACK is not set\nCONFIG_ESP_NETIF_TCPIP_ADAPTER_COMPATIBLE_LAYER=y\n# end of ESP NETIF Adapter\n\n#\n# PHY\n#\nCONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y\n# CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set\nCONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20\nCONFIG_ESP_PHY_MAX_TX_POWER=20\nCONFIG_ESP_PHY_REDUCE_TX_POWER=y\n# end of PHY\n\n#\n# Power Management\n#\n# CONFIG_PM_ENABLE is not set\n# end of Power Management\n\n#\n# ESP System Settings\n#\n# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set\nCONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y\n# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set\n# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set\n# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set\n\n#\n# Memory protection\n#\n# end of Memory protection\n\nCONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32\nCONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304\nCONFIG_ESP_MAIN_TASK_STACK_SIZE=8192\nCONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y\n# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set\n# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set\nCONFIG_ESP_MAIN_TASK_AFFINITY=0x0\nCONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048\nCONFIG_ESP_CONSOLE_UART_DEFAULT=y\n# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set\n# CONFIG_ESP_CONSOLE_NONE is not set\nCONFIG_ESP_CONSOLE_UART=y\nCONFIG_ESP_CONSOLE_MULTIPLE_UART=y\nCONFIG_ESP_CONSOLE_UART_NUM=0\nCONFIG_ESP_CONSOLE_UART_BAUDRATE=115200\nCONFIG_ESP_INT_WDT=y\nCONFIG_ESP_INT_WDT_TIMEOUT_MS=300\nCONFIG_ESP_INT_WDT_CHECK_CPU1=y\nCONFIG_ESP_TASK_WDT=y\n# CONFIG_ESP_TASK_WDT_PANIC is not set\nCONFIG_ESP_TASK_WDT_TIMEOUT_S=5\nCONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y\nCONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y\n# CONFIG_ESP_PANIC_HANDLER_IRAM is not set\n# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set\n# CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 is not set\nCONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y\n# end of ESP System Settings\n\n#\n# High resolution timer (esp_timer)\n#\n# CONFIG_ESP_TIMER_PROFILING is not set\nCONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y\nCONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y\nCONFIG_ESP_TIMER_TASK_STACK_SIZE=3584\nCONFIG_ESP_TIMER_INTERRUPT_LEVEL=1\n# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set\n# CONFIG_ESP_TIMER_IMPL_FRC2 is not set\nCONFIG_ESP_TIMER_IMPL_TG0_LAC=y\n# end of High resolution timer (esp_timer)\n\n#\n# Wi-Fi\n#\nCONFIG_ESP32_WIFI_ENABLED=y\nCONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10\nCONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32\n# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set\nCONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y\nCONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1\nCONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32\n# CONFIG_ESP32_WIFI_CSI_ENABLED is not set\nCONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y\nCONFIG_ESP32_WIFI_TX_BA_WIN=6\nCONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y\nCONFIG_ESP32_WIFI_RX_BA_WIN=6\nCONFIG_ESP32_WIFI_NVS_ENABLED=y\nCONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y\n# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set\nCONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752\nCONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32\nCONFIG_ESP32_WIFI_IRAM_OPT=y\nCONFIG_ESP32_WIFI_RX_IRAM_OPT=y\nCONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y\n# CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set\n# CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE is not set\n# CONFIG_ESP_WIFI_GMAC_SUPPORT is not set\nCONFIG_ESP_WIFI_SOFTAP_SUPPORT=y\n# end of Wi-Fi\n\n#\n# Core dump\n#\n# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set\n# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set\nCONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y\n# end of Core dump\n\n#\n# FAT Filesystem support\n#\n# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set\nCONFIG_FATFS_CODEPAGE_437=y\n# CONFIG_FATFS_CODEPAGE_720 is not set\n# CONFIG_FATFS_CODEPAGE_737 is not set\n# CONFIG_FATFS_CODEPAGE_771 is not set\n# CONFIG_FATFS_CODEPAGE_775 is not set\n# CONFIG_FATFS_CODEPAGE_850 is not set\n# CONFIG_FATFS_CODEPAGE_852 is not set\n# CONFIG_FATFS_CODEPAGE_855 is not set\n# CONFIG_FATFS_CODEPAGE_857 is not set\n# CONFIG_FATFS_CODEPAGE_860 is not set\n# CONFIG_FATFS_CODEPAGE_861 is not set\n# CONFIG_FATFS_CODEPAGE_862 is not set\n# CONFIG_FATFS_CODEPAGE_863 is not set\n# CONFIG_FATFS_CODEPAGE_864 is not set\n# CONFIG_FATFS_CODEPAGE_865 is not set\n# CONFIG_FATFS_CODEPAGE_866 is not set\n# CONFIG_FATFS_CODEPAGE_869 is not set\n# CONFIG_FATFS_CODEPAGE_932 is not set\n# CONFIG_FATFS_CODEPAGE_936 is not set\n# CONFIG_FATFS_CODEPAGE_949 is not set\n# CONFIG_FATFS_CODEPAGE_950 is not set\nCONFIG_FATFS_CODEPAGE=437\nCONFIG_FATFS_LFN_NONE=y\n# CONFIG_FATFS_LFN_HEAP is not set\n# CONFIG_FATFS_LFN_STACK is not set\nCONFIG_FATFS_FS_LOCK=0\nCONFIG_FATFS_TIMEOUT_MS=10000\nCONFIG_FATFS_PER_FILE_CACHE=y\n# CONFIG_FATFS_USE_FASTSEEK is not set\n# end of FAT Filesystem support\n\n#\n# Modbus configuration\n#\nCONFIG_FMB_COMM_MODE_TCP_EN=y\nCONFIG_FMB_TCP_PORT_DEFAULT=502\nCONFIG_FMB_TCP_PORT_MAX_CONN=5\nCONFIG_FMB_TCP_CONNECTION_TOUT_SEC=20\nCONFIG_FMB_COMM_MODE_RTU_EN=y\nCONFIG_FMB_COMM_MODE_ASCII_EN=y\nCONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=150\nCONFIG_FMB_MASTER_DELAY_MS_CONVERT=200\nCONFIG_FMB_QUEUE_LENGTH=20\nCONFIG_FMB_PORT_TASK_STACK_SIZE=4096\nCONFIG_FMB_SERIAL_BUF_SIZE=256\nCONFIG_FMB_SERIAL_ASCII_BITS_PER_SYMB=8\nCONFIG_FMB_SERIAL_ASCII_TIMEOUT_RESPOND_MS=1000\nCONFIG_FMB_PORT_TASK_PRIO=10\n# CONFIG_FMB_PORT_TASK_AFFINITY_NO_AFFINITY is not set\nCONFIG_FMB_PORT_TASK_AFFINITY_CPU0=y\n# CONFIG_FMB_PORT_TASK_AFFINITY_CPU1 is not set\nCONFIG_FMB_PORT_TASK_AFFINITY=0x0\nCONFIG_FMB_CONTROLLER_SLAVE_ID_SUPPORT=y\nCONFIG_FMB_CONTROLLER_SLAVE_ID=0x00112233\nCONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT=20\nCONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE=20\nCONFIG_FMB_CONTROLLER_STACK_SIZE=4096\nCONFIG_FMB_EVENT_QUEUE_TIMEOUT=20\n# CONFIG_FMB_TIMER_PORT_ENABLED is not set\nCONFIG_FMB_TIMER_GROUP=0\nCONFIG_FMB_TIMER_INDEX=0\nCONFIG_FMB_MASTER_TIMER_GROUP=0\nCONFIG_FMB_MASTER_TIMER_INDEX=0\n# CONFIG_FMB_TIMER_ISR_IN_IRAM is not set\n# end of Modbus configuration\n\n#\n# FreeRTOS\n#\n# CONFIG_FREERTOS_UNICORE is not set\nCONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF\nCONFIG_FREERTOS_TICK_SUPPORT_CORETIMER=y\nCONFIG_FREERTOS_CORETIMER_0=y\n# CONFIG_FREERTOS_CORETIMER_1 is not set\nCONFIG_FREERTOS_SYSTICK_USES_CCOUNT=y\nCONFIG_FREERTOS_HZ=100\nCONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y\n# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set\n# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set\nCONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y\n# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set\nCONFIG_FREERTOS_INTERRUPT_BACKTRACE=y\nCONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1\nCONFIG_FREERTOS_ASSERT_FAIL_ABORT=y\n# CONFIG_FREERTOS_ASSERT_FAIL_PRINT_CONTINUE is not set\n# CONFIG_FREERTOS_ASSERT_DISABLE is not set\nCONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536\nCONFIG_FREERTOS_ISR_STACKSIZE=1536\n# CONFIG_FREERTOS_LEGACY_HOOKS is not set\nCONFIG_FREERTOS_MAX_TASK_NAME_LEN=16\nCONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y\n# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set\nCONFIG_FREERTOS_TIMER_TASK_PRIORITY=1\nCONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048\nCONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10\nCONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0\n# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set\n# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set\nCONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y\nCONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y\n# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set\n# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set\nCONFIG_FREERTOS_DEBUG_OCDAWARE=y\n# CONFIG_FREERTOS_FPU_IN_ISR is not set\nCONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y\n# CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH is not set\n# end of FreeRTOS\n\n#\n# Hardware Abstraction Layer (HAL) and Low Level (LL)\n#\nCONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y\n# CONFIG_HAL_ASSERTION_DISABLE is not set\n# CONFIG_HAL_ASSERTION_SILIENT is not set\n# CONFIG_HAL_ASSERTION_ENABLE is not set\nCONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2\n# end of Hardware Abstraction Layer (HAL) and Low Level (LL)\n\n#\n# Heap memory debugging\n#\nCONFIG_HEAP_POISONING_DISABLED=y\n# CONFIG_HEAP_POISONING_LIGHT is not set\n# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set\nCONFIG_HEAP_TRACING_OFF=y\n# CONFIG_HEAP_TRACING_STANDALONE is not set\n# CONFIG_HEAP_TRACING_TOHOST is not set\n# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set\n# end of Heap memory debugging\n\n#\n# jsmn\n#\n# CONFIG_JSMN_PARENT_LINKS is not set\n# CONFIG_JSMN_STRICT is not set\n# end of jsmn\n\n#\n# libsodium\n#\n# end of libsodium\n\n#\n# Log output\n#\n# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set\n# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set\n# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set\nCONFIG_LOG_DEFAULT_LEVEL_INFO=y\n# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set\n# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set\nCONFIG_LOG_DEFAULT_LEVEL=3\nCONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y\n# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set\n# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set\nCONFIG_LOG_MAXIMUM_LEVEL=3\nCONFIG_LOG_COLORS=y\nCONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y\n# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set\n# end of Log output\n\n#\n# LWIP\n#\nCONFIG_LWIP_LOCAL_HOSTNAME=\"espressif\"\n# CONFIG_LWIP_NETIF_API is not set\n# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set\nCONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y\n# CONFIG_LWIP_L2_TO_L3_COPY is not set\n# CONFIG_LWIP_IRAM_OPTIMIZATION is not set\nCONFIG_LWIP_TIMERS_ONDEMAND=y\nCONFIG_LWIP_MAX_SOCKETS=10\n# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set\n# CONFIG_LWIP_SO_LINGER is not set\nCONFIG_LWIP_SO_REUSE=y\nCONFIG_LWIP_SO_REUSE_RXTOALL=y\n# CONFIG_LWIP_SO_RCVBUF is not set\n# CONFIG_LWIP_NETBUF_RECVINFO is not set\nCONFIG_LWIP_IP4_FRAG=y\nCONFIG_LWIP_IP6_FRAG=y\n# CONFIG_LWIP_IP4_REASSEMBLY is not set\n# CONFIG_LWIP_IP6_REASSEMBLY is not set\n# CONFIG_LWIP_IP_FORWARD is not set\n# CONFIG_LWIP_STATS is not set\n# CONFIG_LWIP_ETHARP_TRUST_IP_MAC is not set\nCONFIG_LWIP_ESP_GRATUITOUS_ARP=y\nCONFIG_LWIP_GARP_TMR_INTERVAL=60\nCONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32\nCONFIG_LWIP_DHCP_DOES_ARP_CHECK=y\n# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set\nCONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y\n# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set\nCONFIG_LWIP_DHCP_OPTIONS_LEN=68\n\n#\n# DHCP server\n#\nCONFIG_LWIP_DHCPS=y\nCONFIG_LWIP_DHCPS_LEASE_UNIT=60\nCONFIG_LWIP_DHCPS_MAX_STATION_NUM=8\n# end of DHCP server\n\n# CONFIG_LWIP_AUTOIP is not set\nCONFIG_LWIP_IPV6=y\n# CONFIG_LWIP_IPV6_AUTOCONFIG is not set\nCONFIG_LWIP_IPV6_NUM_ADDRESSES=3\n# CONFIG_LWIP_IPV6_FORWARD is not set\n# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set\nCONFIG_LWIP_NETIF_LOOPBACK=y\nCONFIG_LWIP_LOOPBACK_MAX_PBUFS=8\n\n#\n# TCP\n#\nCONFIG_LWIP_MAX_ACTIVE_TCP=16\nCONFIG_LWIP_MAX_LISTENING_TCP=16\nCONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y\nCONFIG_LWIP_TCP_MAXRTX=12\nCONFIG_LWIP_TCP_SYNMAXRTX=12\nCONFIG_LWIP_TCP_MSS=1440\nCONFIG_LWIP_TCP_TMR_INTERVAL=250\nCONFIG_LWIP_TCP_MSL=60000\nCONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744\nCONFIG_LWIP_TCP_WND_DEFAULT=5744\nCONFIG_LWIP_TCP_RECVMBOX_SIZE=6\nCONFIG_LWIP_TCP_QUEUE_OOSEQ=y\n# CONFIG_LWIP_TCP_SACK_OUT is not set\n# CONFIG_LWIP_TCP_KEEP_CONNECTION_WHEN_IP_CHANGES is not set\nCONFIG_LWIP_TCP_OVERSIZE_MSS=y\n# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set\n# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set\nCONFIG_LWIP_TCP_RTO_TIME=1500\n# end of TCP\n\n#\n# UDP\n#\nCONFIG_LWIP_MAX_UDP_PCBS=16\nCONFIG_LWIP_UDP_RECVMBOX_SIZE=6\n# end of UDP\n\n#\n# Checksums\n#\n# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set\n# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set\nCONFIG_LWIP_CHECKSUM_CHECK_ICMP=y\n# end of Checksums\n\nCONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072\nCONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y\n# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set\n# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set\nCONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF\n# CONFIG_LWIP_PPP_SUPPORT is not set\nCONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3\nCONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5\n# CONFIG_LWIP_SLIP_SUPPORT is not set\n\n#\n# ICMP\n#\nCONFIG_LWIP_ICMP=y\n# CONFIG_LWIP_MULTICAST_PING is not set\n# CONFIG_LWIP_BROADCAST_PING is not set\n# end of ICMP\n\n#\n# LWIP RAW API\n#\nCONFIG_LWIP_MAX_RAW_PCBS=16\n# end of LWIP RAW API\n\n#\n# SNTP\n#\nCONFIG_LWIP_SNTP_MAX_SERVERS=1\n# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set\nCONFIG_LWIP_SNTP_UPDATE_DELAY=3600000\n# end of SNTP\n\nCONFIG_LWIP_ESP_LWIP_ASSERT=y\n\n#\n# Hooks\n#\n# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set\nCONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y\n# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set\nCONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y\n# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set\n# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set\nCONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y\n# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set\n# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set\nCONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y\n# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set\n# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set\n# end of Hooks\n\n# CONFIG_LWIP_DEBUG is not set\n# end of LWIP\n\n#\n# mbedTLS\n#\nCONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y\n# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set\n# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set\nCONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y\nCONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384\nCONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096\n# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set\n# CONFIG_MBEDTLS_DEBUG is not set\n\n#\n# mbedTLS v2.28.x related\n#\n# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set\n# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set\n# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set\nCONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y\n# end of mbedTLS v2.28.x related\n\n#\n# Certificate Bundle\n#\nCONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y\nCONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y\n# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set\n# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set\n# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set\n# end of Certificate Bundle\n\n# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set\n# CONFIG_MBEDTLS_CMAC_C is not set\nCONFIG_MBEDTLS_HARDWARE_AES=y\nCONFIG_MBEDTLS_HARDWARE_MPI=y\nCONFIG_MBEDTLS_HARDWARE_SHA=y\nCONFIG_MBEDTLS_ROM_MD5=y\n# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set\n# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set\nCONFIG_MBEDTLS_HAVE_TIME=y\n# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set\nCONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y\nCONFIG_MBEDTLS_SHA512_C=y\nCONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y\n# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set\n# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set\n# CONFIG_MBEDTLS_TLS_DISABLED is not set\nCONFIG_MBEDTLS_TLS_SERVER=y\nCONFIG_MBEDTLS_TLS_CLIENT=y\nCONFIG_MBEDTLS_TLS_ENABLED=y\n\n#\n# TLS Key Exchange Methods\n#\n# CONFIG_MBEDTLS_PSK_MODES is not set\nCONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y\nCONFIG_MBEDTLS_KEY_EXCHANGE_DHE_RSA=y\nCONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y\nCONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y\nCONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y\nCONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y\nCONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y\n# end of TLS Key Exchange Methods\n\nCONFIG_MBEDTLS_SSL_RENEGOTIATION=y\n# CONFIG_MBEDTLS_SSL_PROTO_SSL3 is not set\nCONFIG_MBEDTLS_SSL_PROTO_TLS1=y\nCONFIG_MBEDTLS_SSL_PROTO_TLS1_1=y\nCONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y\n# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set\n# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set\nCONFIG_MBEDTLS_SSL_ALPN=y\nCONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y\nCONFIG_MBEDTLS_X509_CHECK_KEY_USAGE=y\nCONFIG_MBEDTLS_X509_CHECK_EXTENDED_KEY_USAGE=y\nCONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y\n\n#\n# Symmetric Ciphers\n#\nCONFIG_MBEDTLS_AES_C=y\n# CONFIG_MBEDTLS_CAMELLIA_C is not set\n# CONFIG_MBEDTLS_DES_C is not set\nCONFIG_MBEDTLS_RC4_DISABLED=y\n# CONFIG_MBEDTLS_RC4_ENABLED_NO_DEFAULT is not set\n# CONFIG_MBEDTLS_RC4_ENABLED is not set\n# CONFIG_MBEDTLS_BLOWFISH_C is not set\n# CONFIG_MBEDTLS_XTEA_C is not set\nCONFIG_MBEDTLS_CCM_C=y\nCONFIG_MBEDTLS_GCM_C=y\n# CONFIG_MBEDTLS_NIST_KW_C is not set\n# end of Symmetric Ciphers\n\n# CONFIG_MBEDTLS_RIPEMD160_C is not set\n\n#\n# Certificates\n#\nCONFIG_MBEDTLS_PEM_PARSE_C=y\nCONFIG_MBEDTLS_PEM_WRITE_C=y\nCONFIG_MBEDTLS_X509_CRL_PARSE_C=y\nCONFIG_MBEDTLS_X509_CSR_PARSE_C=y\n# end of Certificates\n\nCONFIG_MBEDTLS_ECP_C=y\nCONFIG_MBEDTLS_ECDH_C=y\nCONFIG_MBEDTLS_ECDSA_C=y\n# CONFIG_MBEDTLS_ECJPAKE_C is not set\nCONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y\nCONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y\nCONFIG_MBEDTLS_ECP_NIST_OPTIM=y\n# CONFIG_MBEDTLS_POLY1305_C is not set\n# CONFIG_MBEDTLS_CHACHA20_C is not set\n# CONFIG_MBEDTLS_HKDF_C is not set\n# CONFIG_MBEDTLS_THREADING_C is not set\n# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set\n# CONFIG_MBEDTLS_SECURITY_RISKS is not set\n# end of mbedTLS\n\n#\n# mDNS\n#\nCONFIG_MDNS_MAX_SERVICES=10\nCONFIG_MDNS_TASK_PRIORITY=1\nCONFIG_MDNS_TASK_STACK_SIZE=4096\n# CONFIG_MDNS_TASK_AFFINITY_NO_AFFINITY is not set\nCONFIG_MDNS_TASK_AFFINITY_CPU0=y\n# CONFIG_MDNS_TASK_AFFINITY_CPU1 is not set\nCONFIG_MDNS_TASK_AFFINITY=0x0\nCONFIG_MDNS_SERVICE_ADD_TIMEOUT_MS=2000\n# CONFIG_MDNS_STRICT_MODE is not set\nCONFIG_MDNS_TIMER_PERIOD_MS=100\n# CONFIG_MDNS_NETWORKING_SOCKET is not set\nCONFIG_MDNS_MULTIPLE_INSTANCE=y\n# end of mDNS\n\n#\n# ESP-MQTT Configurations\n#\nCONFIG_MQTT_PROTOCOL_311=y\nCONFIG_MQTT_TRANSPORT_SSL=y\nCONFIG_MQTT_TRANSPORT_WEBSOCKET=y\nCONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y\n# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set\n# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set\n# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set\n# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set\n# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set\n# CONFIG_MQTT_CUSTOM_OUTBOX is not set\n# end of ESP-MQTT Configurations\n\n#\n# Newlib\n#\nCONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y\n# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set\n# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set\n# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set\n# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set\nCONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y\n# CONFIG_NEWLIB_NANO_FORMAT is not set\n# end of Newlib\n\n#\n# NVS\n#\n# end of NVS\n\n#\n# OpenSSL\n#\n# CONFIG_OPENSSL_DEBUG is not set\nCONFIG_OPENSSL_ERROR_STACK=y\n# CONFIG_OPENSSL_ASSERT_DO_NOTHING is not set\nCONFIG_OPENSSL_ASSERT_EXIT=y\n# end of OpenSSL\n\n#\n# OpenThread\n#\n# CONFIG_OPENTHREAD_ENABLED is not set\n# end of OpenThread\n\n#\n# PThreads\n#\nCONFIG_PTHREAD_TASK_PRIO_DEFAULT=5\nCONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072\nCONFIG_PTHREAD_STACK_MIN=768\nCONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y\n# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set\n# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set\nCONFIG_PTHREAD_TASK_CORE_DEFAULT=-1\nCONFIG_PTHREAD_TASK_NAME_DEFAULT=\"pthread\"\n# end of PThreads\n\n#\n# SPI Flash driver\n#\n# CONFIG_SPI_FLASH_VERIFY_WRITE is not set\n# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set\nCONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y\nCONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y\n# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set\n# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set\n# CONFIG_SPI_FLASH_USE_LEGACY_IMPL is not set\n# CONFIG_SPI_FLASH_SHARE_SPI1_BUS is not set\n# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set\nCONFIG_SPI_FLASH_YIELD_DURING_ERASE=y\nCONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20\nCONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1\nCONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192\n# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set\n# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set\n# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set\n\n#\n# Auto-detect flash chips\n#\nCONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y\nCONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y\nCONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y\nCONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y\n# CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP is not set\n# CONFIG_SPI_FLASH_SUPPORT_TH_CHIP is not set\n# end of Auto-detect flash chips\n\nCONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y\n# end of SPI Flash driver\n\n#\n# SPIFFS Configuration\n#\nCONFIG_SPIFFS_MAX_PARTITIONS=3\n\n#\n# SPIFFS Cache Configuration\n#\nCONFIG_SPIFFS_CACHE=y\nCONFIG_SPIFFS_CACHE_WR=y\n# CONFIG_SPIFFS_CACHE_STATS is not set\n# end of SPIFFS Cache Configuration\n\nCONFIG_SPIFFS_PAGE_CHECK=y\nCONFIG_SPIFFS_GC_MAX_RUNS=10\n# CONFIG_SPIFFS_GC_STATS is not set\nCONFIG_SPIFFS_PAGE_SIZE=256\nCONFIG_SPIFFS_OBJ_NAME_LEN=32\n# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set\nCONFIG_SPIFFS_USE_MAGIC=y\nCONFIG_SPIFFS_USE_MAGIC_LENGTH=y\nCONFIG_SPIFFS_META_LENGTH=4\nCONFIG_SPIFFS_USE_MTIME=y\n\n#\n# Debug Configuration\n#\n# CONFIG_SPIFFS_DBG is not set\n# CONFIG_SPIFFS_API_DBG is not set\n# CONFIG_SPIFFS_GC_DBG is not set\n# CONFIG_SPIFFS_CACHE_DBG is not set\n# CONFIG_SPIFFS_CHECK_DBG is not set\n# CONFIG_SPIFFS_TEST_VISUALISATION is not set\n# end of Debug Configuration\n# end of SPIFFS Configuration\n\n#\n# TCP Transport\n#\n\n#\n# Websocket\n#\nCONFIG_WS_TRANSPORT=y\nCONFIG_WS_BUFFER_SIZE=1024\n# end of Websocket\n# end of TCP Transport\n\n#\n# Unity unit testing library\n#\nCONFIG_UNITY_ENABLE_FLOAT=y\nCONFIG_UNITY_ENABLE_DOUBLE=y\n# CONFIG_UNITY_ENABLE_64BIT is not set\n# CONFIG_UNITY_ENABLE_COLOR is not set\nCONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y\n# CONFIG_UNITY_ENABLE_FIXTURE is not set\n# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set\n# end of Unity unit testing library\n\n#\n# Virtual file system\n#\nCONFIG_VFS_SUPPORT_IO=y\nCONFIG_VFS_SUPPORT_DIR=y\nCONFIG_VFS_SUPPORT_SELECT=y\nCONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y\nCONFIG_VFS_SUPPORT_TERMIOS=y\n\n#\n# Host File System I/O (Semihosting)\n#\nCONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1\nCONFIG_VFS_SEMIHOSTFS_HOST_PATH_MAX_LEN=128\n# end of Host File System I/O (Semihosting)\n# end of Virtual file system\n\n#\n# Wear Levelling\n#\n# CONFIG_WL_SECTOR_SIZE_512 is not set\nCONFIG_WL_SECTOR_SIZE_4096=y\nCONFIG_WL_SECTOR_SIZE=4096\n# end of Wear Levelling\n\n#\n# Wi-Fi Provisioning Manager\n#\nCONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16\nCONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30\n# end of Wi-Fi Provisioning Manager\n\n#\n# Supplicant\n#\nCONFIG_WPA_MBEDTLS_CRYPTO=y\n# CONFIG_WPA_WAPI_PSK is not set\n# CONFIG_WPA_SUITE_B_192 is not set\n# CONFIG_WPA_DEBUG_PRINT is not set\n# CONFIG_WPA_TESTING_OPTIONS is not set\n# CONFIG_WPA_WPS_STRICT is not set\n# CONFIG_WPA_11KV_SUPPORT is not set\n# CONFIG_WPA_MBO_SUPPORT is not set\n# CONFIG_WPA_DPP_SUPPORT is not set\n# end of Supplicant\n# end of Component config\n\n#\n# Compatibility options\n#\n# CONFIG_LEGACY_INCLUDE_COMMON_HEADERS is not set\n# end of Compatibility options\n\n# Deprecated options for backward compatibility\nCONFIG_TOOLPREFIX=\"xtensa-esp32-elf-\"\n# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set\n# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set\n# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set\nCONFIG_LOG_BOOTLOADER_LEVEL_INFO=y\n# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set\n# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set\nCONFIG_LOG_BOOTLOADER_LEVEL=3\n# CONFIG_APP_ROLLBACK_ENABLE is not set\n# CONFIG_FLASH_ENCRYPTION_ENABLED is not set\n# CONFIG_FLASHMODE_QIO is not set\n# CONFIG_FLASHMODE_QOUT is not set\nCONFIG_FLASHMODE_DIO=y\n# CONFIG_FLASHMODE_DOUT is not set\n# CONFIG_MONITOR_BAUD_9600B is not set\n# CONFIG_MONITOR_BAUD_57600B is not set\nCONFIG_MONITOR_BAUD_115200B=y\n# CONFIG_MONITOR_BAUD_230400B is not set\n# CONFIG_MONITOR_BAUD_921600B is not set\n# CONFIG_MONITOR_BAUD_2MB is not set\n# CONFIG_MONITOR_BAUD_OTHER is not set\nCONFIG_MONITOR_BAUD_OTHER_VAL=115200\nCONFIG_MONITOR_BAUD=115200\nCONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y\n# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set\nCONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y\n# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set\n# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set\nCONFIG_OPTIMIZATION_ASSERTION_LEVEL=2\n# CONFIG_CXX_EXCEPTIONS is not set\nCONFIG_STACK_CHECK_NONE=y\n# CONFIG_STACK_CHECK_NORM is not set\n# CONFIG_STACK_CHECK_STRONG is not set\n# CONFIG_STACK_CHECK_ALL is not set\n# CONFIG_WARN_WRITE_STRINGS is not set\n# CONFIG_DISABLE_GCC8_WARNINGS is not set\n# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set\nCONFIG_ESP32_APPTRACE_DEST_NONE=y\nCONFIG_ESP32_APPTRACE_LOCK_ENABLE=y\nCONFIG_ADC2_DISABLE_DAC=y\n# CONFIG_SPIRAM_SUPPORT is not set\nCONFIG_TRACEMEM_RESERVE_DRAM=0x0\n# CONFIG_ULP_COPROC_ENABLED is not set\nCONFIG_ULP_COPROC_RESERVE_MEM=0\nCONFIG_BROWNOUT_DET=y\nCONFIG_BROWNOUT_DET_LVL_SEL_0=y\n# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set\n# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set\n# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set\n# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set\n# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set\n# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set\n# CONFIG_BROWNOUT_DET_LVL_SEL_7 is not set\nCONFIG_BROWNOUT_DET_LVL=0\nCONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC=y\n# CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL is not set\n# CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_OSC is not set\n# CONFIG_ESP32_RTC_CLOCK_SOURCE_INTERNAL_8MD256 is not set\n# CONFIG_DISABLE_BASIC_ROM_CONSOLE is not set\n# CONFIG_NO_BLOBS is not set\n# CONFIG_COMPATIBLE_PRE_V2_1_BOOTLOADERS is not set\n# CONFIG_EVENT_LOOP_PROFILING is not set\nCONFIG_POST_EVENTS_FROM_ISR=y\nCONFIG_POST_EVENTS_FROM_IRAM_ISR=y\n# CONFIG_TWO_UNIVERSAL_MAC_ADDRESS is not set\nCONFIG_FOUR_UNIVERSAL_MAC_ADDRESS=y\nCONFIG_NUMBER_OF_UNIVERSAL_MAC_ADDRESS=4\nCONFIG_ESP_SYSTEM_PD_FLASH=y\n# CONFIG_ESP32C3_LIGHTSLEEP_GPIO_RESET_WORKAROUND is not set\nCONFIG_IPC_TASK_STACK_SIZE=1536\nCONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y\n# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set\nCONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20\nCONFIG_ESP32_PHY_MAX_TX_POWER=20\nCONFIG_ESP32_REDUCE_PHY_TX_POWER=y\n# CONFIG_ESP32S2_PANIC_PRINT_HALT is not set\nCONFIG_ESP32S2_PANIC_PRINT_REBOOT=y\n# CONFIG_ESP32S2_PANIC_SILENT_REBOOT is not set\n# CONFIG_ESP32S2_PANIC_GDBSTUB is not set\nCONFIG_SYSTEM_EVENT_QUEUE_SIZE=32\nCONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304\nCONFIG_MAIN_TASK_STACK_SIZE=8192\nCONFIG_CONSOLE_UART_DEFAULT=y\n# CONFIG_CONSOLE_UART_CUSTOM is not set\n# CONFIG_ESP_CONSOLE_UART_NONE is not set\nCONFIG_CONSOLE_UART=y\nCONFIG_CONSOLE_UART_NUM=0\nCONFIG_CONSOLE_UART_BAUDRATE=115200\nCONFIG_INT_WDT=y\nCONFIG_INT_WDT_TIMEOUT_MS=300\nCONFIG_INT_WDT_CHECK_CPU1=y\nCONFIG_TASK_WDT=y\n# CONFIG_TASK_WDT_PANIC is not set\nCONFIG_TASK_WDT_TIMEOUT_S=5\nCONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y\nCONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y\n# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set\nCONFIG_TIMER_TASK_STACK_SIZE=3584\n# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set\n# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set\nCONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y\nCONFIG_MB_MASTER_TIMEOUT_MS_RESPOND=150\nCONFIG_MB_MASTER_DELAY_MS_CONVERT=200\nCONFIG_MB_QUEUE_LENGTH=20\nCONFIG_MB_SERIAL_TASK_STACK_SIZE=4096\nCONFIG_MB_SERIAL_BUF_SIZE=256\nCONFIG_MB_SERIAL_TASK_PRIO=10\nCONFIG_MB_CONTROLLER_SLAVE_ID_SUPPORT=y\nCONFIG_MB_CONTROLLER_SLAVE_ID=0x00112233\nCONFIG_MB_CONTROLLER_NOTIFY_TIMEOUT=20\nCONFIG_MB_CONTROLLER_NOTIFY_QUEUE_SIZE=20\nCONFIG_MB_CONTROLLER_STACK_SIZE=4096\nCONFIG_MB_EVENT_QUEUE_TIMEOUT=20\n# CONFIG_MB_TIMER_PORT_ENABLED is not set\nCONFIG_MB_TIMER_GROUP=0\nCONFIG_MB_TIMER_INDEX=0\n# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set\nCONFIG_TIMER_TASK_PRIORITY=1\nCONFIG_TIMER_TASK_STACK_DEPTH=2048\nCONFIG_TIMER_QUEUE_LENGTH=10\n# CONFIG_L2_TO_L3_COPY is not set\n# CONFIG_USE_ONLY_LWIP_SELECT is not set\nCONFIG_ESP_GRATUITOUS_ARP=y\nCONFIG_GARP_TMR_INTERVAL=60\nCONFIG_TCPIP_RECVMBOX_SIZE=32\nCONFIG_TCP_MAXRTX=12\nCONFIG_TCP_SYNMAXRTX=12\nCONFIG_TCP_MSS=1440\nCONFIG_TCP_MSL=60000\nCONFIG_TCP_SND_BUF_DEFAULT=5744\nCONFIG_TCP_WND_DEFAULT=5744\nCONFIG_TCP_RECVMBOX_SIZE=6\nCONFIG_TCP_QUEUE_OOSEQ=y\n# CONFIG_ESP_TCP_KEEP_CONNECTION_WHEN_IP_CHANGES is not set\nCONFIG_TCP_OVERSIZE_MSS=y\n# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set\n# CONFIG_TCP_OVERSIZE_DISABLE is not set\nCONFIG_UDP_RECVMBOX_SIZE=6\nCONFIG_TCPIP_TASK_STACK_SIZE=3072\nCONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y\n# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set\n# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set\nCONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF\n# CONFIG_PPP_SUPPORT is not set\nCONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5\nCONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072\nCONFIG_ESP32_PTHREAD_STACK_MIN=768\nCONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y\n# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set\n# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set\nCONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1\nCONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT=\"pthread\"\nCONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y\n# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set\n# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set\nCONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y\nCONFIG_SUPPORT_TERMIOS=y\nCONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1\nCONFIG_SEMIHOSTFS_HOST_PATH_MAX_LEN=128\n# End of deprecated options\n"
  },
  {
    "path": "examples/ESP-TLS/main.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <Arduino.h>\n#if defined(ESP8266)\n#include <ESP8266WiFi.h>\n#include <ESP8266WiFiMulti.h>\nESP8266WiFiMulti WiFiMulti;\n#elif defined(ESP32)\n#include <WiFi.h>\n#else\n#error only ESP32 or ESP8266 supported at the moment\n#endif\n\n#include <MicroOcpp.h>\n\n#define STASSID \"YOUR_WIFI_SSID\"\n#define STAPSK  \"YOUR_WIFI_PW\"\n\n#define OCPP_BACKEND_URL   \"wss://echo.websocket.events\"\n#define OCPP_CHARGE_BOX_ID \"\"\n#define OCPP_AUTH_KEY      \"SecureAuthKey\" // OCPP Security Profile 2: TLS with Basic Authentication\n\n/*\n * ISRG ROOT X1\n */\nconst char ca_cert[] PROGMEM = R\"EOF(-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\nWhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\nZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\nMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\nh77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\nA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\nT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\nB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\nB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\nKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\nOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\njh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\nqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\nrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\nhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\nubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\nNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\nORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\nTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\njNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\noyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\nmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\nemyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n-----END CERTIFICATE-----\n)EOF\";\n\nvoid setup() {\n\n    /*\n     * Initialize Serial and WiFi\n     */ \n\n    Serial.begin(115200);\n\n    Serial.print(F(\"[main] Wait for WiFi: \"));\n\n#if defined(ESP8266)\n    WiFiMulti.addAP(STASSID, STAPSK);\n    while (WiFiMulti.run() != WL_CONNECTED) {\n        Serial.print('.');\n        delay(1000);\n    }\n#elif defined(ESP32)\n    WiFi.begin(STASSID, STAPSK);\n    while (!WiFi.isConnected()) {\n        Serial.print('.');\n        delay(1000);\n    }\n#else\n#error only ESP32 or ESP8266 supported at the moment\n#endif\n\n    Serial.println(F(\" connected!\"));\n\n    /*\n     * Set system time (required for Certificate validation)\n     */\n    configTime(3 * 3600, 0, \"pool.ntp.org\", \"time.nist.gov\"); //alternatively: settimeofday(&de, &tz);\n    Serial.print(F(\"[main] Wait for NTP time (used for certificate validation) \"));\n    time_t now = time(nullptr);\n    while (now < 8 * 3600 * 2) {\n        delay(1000);\n        Serial.print('.');\n        now = time(nullptr);\n    }\n    Serial.printf(\" finished. Unix timestamp is %lu\\n\", now);\n    \n    /*\n     * Initialize the OCPP library (using OCPP Security Profile 2: TLS with Basic Authentication)\n     */\n    mocpp_initialize(\n            OCPP_BACKEND_URL,\n            OCPP_CHARGE_BOX_ID,\n            \"My Charging Station\",\n            \"My company name\",\n            MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail,\n            OCPP_AUTH_KEY,\n            ca_cert);\n\n    /*\n     * ... see MicroOcpp.h for how to integrate the EVSE hardware.\n     *\n     * This example only showcases the TLS connection. For examples about the HW integration,\n     * see the other examples\n     */\n}\n\nvoid loop() {\n\n    /*\n     * Execute all charge point routines and handle WebSocket\n     */\n    mocpp_loop();\n\n    //... see MicroOcpp.h and the other examples for how to integrate the EVSE hardware.\n}\n"
  },
  {
    "path": "library.json",
    "content": "{\n    \"name\": \"MicroOcpp\",\n    \"version\": \"1.2.0\",\n    \"description\": \"OCPP 1.6 / 2.0.1 Client for microcontrollers\",\n    \"keywords\": \"OCPP, 1.6, OCPP 1.6, OCPP 2.0.1, Smart Energy, Smart Charging, client, ESP8266, ESP32, Arduino, esp-idf, EVSE, Charge Point\",\n    \"repository\":\n    {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/matth-x/MicroOcpp/\"\n    },\n    \"authors\":\n    [\n      {\n        \"name\": \"Matthias Akstaller\",\n        \"url\": \"https://www.micro-ocpp.com\",\n        \"maintainer\": true\n      }\n    ],\n    \"license\": \"MIT\",\n    \"homepage\": \"https://www.micro-ocpp.com\",\n    \"dependencies\": [\n        {\n            \"owner\": \"bblanchon\",\n            \"name\": \"ArduinoJson\",\n            \"version\": \"6.20.1\"\n        },\n        {\n            \"owner\": \"links2004\",\n            \"name\": \"WebSockets\",\n            \"version\": \"2.4.1\"\n        }\n      ],\n    \"frameworks\": \"arduino,espidf\",\n    \"platforms\": \"espressif8266, espressif32\",\n\n    \"export\": {\n        \"include\":\n        [\n            \"docs/*\",\n            \"examples/*\",\n            \"src/*\",\n            \"CHANGELOG.md\",\n            \"CMakeLists.txt\",\n            \"library.json\",\n            \"library.properties\",\n            \"LICENSE\",\n            \"mkdocs.yml\",\n            \"platformio.ini\",\n            \"README.md\"\n        ]\n    },\n\n    \"examples\": [\n      {\n          \"name\": \"Basic OCPP connection\",\n          \"base\": \"examples/ESP\",\n          \"files\": [\n              \"main.cpp\"\n          ]\n      },\n      {\n          \"name\": \"OCPP Security Profile 2\",\n          \"base\": \"examples/ESP-TLS\",\n          \"files\": [\n              \"main.cpp\"\n          ]\n      },\n      {\n        \"name\": \"ESP-IDF integration\",\n        \"base\": \"examples/ESP-IDF\",\n        \"files\": [\n            \"main/main.c\"\n        ]\n      }\n    ]\n}\n"
  },
  {
    "path": "library.properties",
    "content": "name=MicroOcpp\nversion=1.2.0\nauthor=Matthias Akstaller\nmaintainer=Matthias Akstaller\nsentence=OCPP 1.6 Client for microcontrollers\nparagraph=\ncategory=Communication\nurl=https://github.com/matth-x/MicroOcpp/\narchitectures=*\ndepends=ArduinoJson, WebSockets\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: MicroOCPP docs\n\ntheme:\n  name: material\n  features:\n    - announce.dismiss\n    - content.action.edit\n    - content.action.view\n    - content.code.annotate\n    - content.code.copy\n    # - content.tabs.link\n    - content.tooltips\n    # - header.autohide\n    # - navigation.expand\n    - navigation.footer\n    - navigation.indexes\n    # - navigation.instant\n    # - navigation.prune\n    - navigation.sections\n    - navigation.tabs\n    # - navigation.tabs.sticky\n    - navigation.top\n    - navigation.tracking\n    - search.highlight\n    - search.share\n    - search.suggest\n    - toc.follow\n    # - toc.integrate\n  palette:\n    - scheme: default\n      primary: custom\n      accent: custom\n      toggle:\n        icon: material/brightness-7\n        name: Switch to dark mode\n    - scheme: slate\n      primary: custom\n      accent: custom\n      toggle:\n        icon: material/brightness-4\n        name: Switch to light mode\n  font:\n    text: Roboto\n    code: Roboto Mono\n  favicon: img/favicon.ico\n  icon:\n    logo: logo\n\nextra_css:\n  - stylesheets/extra.css\n\nplugins:\n  - search\n  - table-reader:\n      data_path: \"docs/assets/tables\"\n"
  },
  {
    "path": "platformio.ini",
    "content": "; matth-x/MicroOcpp\n; Copyright Matthias Akstaller 2019 - 2024\n; MIT License\n\n[platformio]\ndefault_envs = esp32-development-board\n\n[common]\nframework = arduino\nlib_deps =\n    bblanchon/ArduinoJson@6.20.1\n    links2004/WebSockets@2.4.1\nmonitor_speed = 115200\n\n[env:nodemcuv2]\nplatform = espressif8266@2.6.3\nboard = nodemcuv2\nframework = ${common.framework}\nlib_deps = ${common.lib_deps}\nmonitor_speed = ${common.monitor_speed}\nbuild_flags =\n    -D MO_DBG_LEVEL=MO_DL_INFO ; flood the serial monitor with information about the internal state\n    -DMO_TRAFFIC_OUT   ; print the OCPP communication to the serial monitor\n    -D ARDUINOJSON_ENABLE_STD_STRING=1\n\n[env:esp32-development-board]\nplatform = espressif32@6.0.1\nboard = esp-wrover-kit\nframework = ${common.framework}\nlib_deps = ${common.lib_deps}\nmonitor_speed = ${common.monitor_speed}\nbuild_flags =\n    -D MO_DBG_LEVEL=MO_DL_INFO ; flood the serial monitor with information about the internal state\n    -DMO_TRAFFIC_OUT   ; print the OCPP communication to the serial monitor\nboard_build.partitions = min_spiffs.csv\nupload_speed = 921600\nmonitor_filters =\n    esp32_exception_decoder\n"
  },
  {
    "path": "src/MicroOcpp/Core/Configuration.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/ConfigurationContainerFlash.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Debug.h>\n\n#include <string.h>\n#include <algorithm>\n#include <ArduinoJson.h>\n\nnamespace MicroOcpp {\n\nstruct Validator {\n    const char *key = nullptr;\n    std::function<bool(const char*)> checkValue;\n    Validator(const char *key, std::function<bool(const char*)> checkValue) : key(key), checkValue(checkValue) {\n\n    }\n};\n\nnamespace ConfigurationLocal {\n\nstd::shared_ptr<FilesystemAdapter> filesystem;\nauto configurationContainers = makeVector<std::shared_ptr<ConfigurationContainer>>(\"v16.Configuration.Containers\");\nauto validators = makeVector<Validator>(\"v16.Configuration.Validators\");\n\n}\n\nusing namespace ConfigurationLocal;\n\nstd::unique_ptr<ConfigurationContainer> createConfigurationContainer(const char *filename, bool accessible) {\n    //create non-persistent Configuration store (i.e. lives only in RAM) if\n    //     - Flash FS usage is switched off OR\n    //     - Filename starts with \"/volatile\"\n    if (!filesystem ||\n                 !strncmp(filename, CONFIGURATION_VOLATILE, strlen(CONFIGURATION_VOLATILE))) {\n        return makeConfigurationContainerVolatile(filename, accessible);\n    } else {\n        //create persistent Configuration store. This is the normal case\n        return makeConfigurationContainerFlash(filesystem, filename, accessible);\n    }\n}\n\n\nvoid addConfigurationContainer(std::shared_ptr<ConfigurationContainer> container) {\n    configurationContainers.push_back(container);\n}\n\nstd::shared_ptr<ConfigurationContainer> getContainer(const char *filename) {\n    auto container = std::find_if(configurationContainers.begin(), configurationContainers.end(),\n        [filename](decltype(configurationContainers)::value_type &elem) {\n            return !strcmp(elem->getFilename(), filename);\n        });\n\n    if (container != configurationContainers.end()) {\n        return *container;\n    } else {\n        return nullptr;\n    }\n}\n\nConfigurationContainer *declareContainer(const char *filename, bool accessible) {\n\n    auto container = getContainer(filename);\n    \n    if (!container) {\n        MO_DBG_DEBUG(\"init new configurations container: %s\", filename);\n\n        container = createConfigurationContainer(filename, accessible);\n        if (!container) {\n            MO_DBG_ERR(\"OOM\");\n            return nullptr;\n        }\n        configurationContainers.push_back(container);\n    }\n\n    if (container->isAccessible() != accessible) {\n        MO_DBG_ERR(\"%s: conflicting accessibility declarations (expect %s)\", filename, container->isAccessible() ? \"accessible\" : \"inaccessible\");\n    }\n\n    return container.get();\n}\n\nstd::shared_ptr<Configuration> loadConfiguration(TConfig type, const char *key, bool accessible) {\n    for (auto& container : configurationContainers) {\n        if (auto config = container->getConfiguration(key)) {\n            if (config->getType() != type) {\n                MO_DBG_ERR(\"conflicting type for %s - remove old config\", key);\n                container->remove(config.get());\n                continue;\n            }\n            if (container->isAccessible() != accessible) {\n                MO_DBG_ERR(\"conflicting accessibility for %s\", key);\n            }\n            container->loadStaticKey(*config.get(), key);\n            return config;\n        }\n    }\n    return nullptr;\n}\n\ntemplate<class T>\nbool loadFactoryDefault(Configuration& config, T loadFactoryDefault);\n\ntemplate<>\nbool loadFactoryDefault<int>(Configuration& config, int factoryDef) {\n    config.setInt(factoryDef);\n    return true;\n}\n\ntemplate<>\nbool loadFactoryDefault<bool>(Configuration& config, bool factoryDef) {\n    config.setBool(factoryDef);\n    return true;\n}\n\ntemplate<>\nbool loadFactoryDefault<const char*>(Configuration& config, const char *factoryDef) {\n    return config.setString(factoryDef);\n}\n\nvoid loadPermissions(Configuration& config, bool readonly, bool rebootRequired) {\n    if (readonly) {\n        config.setReadOnly();\n    }\n\n    if (rebootRequired) {\n        config.setRebootRequired();\n    }\n}\n\ntemplate<class T>\nstd::shared_ptr<Configuration> declareConfiguration(const char *key, T factoryDef, const char *filename, bool readonly, bool rebootRequired, bool accessible) {\n\n    std::shared_ptr<Configuration> res = loadConfiguration(convertType<T>(), key, accessible);\n    if (!res) {\n        auto container = declareContainer(filename, accessible);\n        if (!container) {\n            return nullptr;\n        }\n\n        res = container->createConfiguration(convertType<T>(), key);\n        if (!res) {\n            return nullptr;\n        }\n\n        if (!loadFactoryDefault(*res.get(), factoryDef)) {\n            container->remove(res.get());\n            return nullptr;\n        }\n    }\n\n    loadPermissions(*res.get(), readonly, rebootRequired);\n    return res;\n}\n\ntemplate std::shared_ptr<Configuration> declareConfiguration<int>(const char *key, int factoryDef, const char *filename, bool readonly, bool rebootRequired, bool accessible);\ntemplate std::shared_ptr<Configuration> declareConfiguration<bool>(const char *key, bool factoryDef, const char *filename, bool readonly, bool rebootRequired, bool accessible);\ntemplate std::shared_ptr<Configuration> declareConfiguration<const char*>(const char *key, const char *factoryDef, const char *filename, bool readonly, bool rebootRequired, bool accessible);\n\nstd::function<bool(const char*)> *getConfigurationValidator(const char *key) {\n    for (auto& v : validators) {\n        if (!strcmp(v.key, key)) {\n            return &v.checkValue;\n        }\n    }\n    return nullptr;\n}\n\nvoid registerConfigurationValidator(const char *key, std::function<bool(const char*)> validator) {\n    for (auto& v : validators) {\n        if (!strcmp(v.key, key)) {\n            v.checkValue = validator;\n            return;\n        }\n    }\n    validators.push_back(Validator{key, validator});\n}\n\nConfiguration *getConfigurationPublic(const char *key) {\n    for (auto& container : configurationContainers) {\n        if (container->isAccessible()) {\n            if (auto res = container->getConfiguration(key)) {\n                return res.get();\n            }\n        }\n    }\n\n    return nullptr;\n}\n\nVector<ConfigurationContainer*> getConfigurationContainersPublic() {\n    auto res = makeVector<ConfigurationContainer*>(\"v16.Configuration.Containers\");\n\n    for (auto& container : configurationContainers) {\n        if (container->isAccessible()) {\n            res.push_back(container.get());\n        }\n    }\n\n    return res;\n}\n\nbool configuration_init(std::shared_ptr<FilesystemAdapter> _filesystem) {\n    filesystem = _filesystem;\n    return true;\n}\n\nvoid configuration_deinit() {\n    makeVector<decltype(configurationContainers)::value_type>(\"v16.Configuration.Containers\").swap(configurationContainers); //release allocated memory (see https://cplusplus.com/reference/vector/vector/clear/)\n    makeVector<decltype(validators)::value_type>(\"v16.Configuration.Validators\").swap(validators);\n    filesystem.reset();\n}\n\nbool configuration_load(const char *filename) {\n    bool success = true;\n\n    for (auto& container : configurationContainers) {\n        if ((!filename || !strcmp(filename, container->getFilename())) && !container->load()) {\n            success = false;\n        }\n    }\n\n    return success;\n}\n\nbool configuration_save() {\n    bool success = true;\n\n    for (auto& container : configurationContainers) {\n        if (!container->save()) {\n            success = false;\n        }\n    }\n\n    return success;\n}\n\nbool configuration_clean_unused() {\n    for (auto& container : configurationContainers) {\n        container->removeUnused();\n    }\n    return configuration_save();\n}\n\nbool VALIDATE_UNSIGNED_INT(const char *value) {\n    for(size_t i = 0; value[i] != '\\0'; i++) {\n        if (value[i] < '0' || value[i] > '9') {\n            return false;\n        }\n    }\n    return true;\n}\n\n} //end namespace MicroOcpp\n"
  },
  {
    "path": "src/MicroOcpp/Core/Configuration.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CONFIGURATION_H\n#define MO_CONFIGURATION_H\n\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Core/ConfigurationContainer.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#include <memory>\n\n#define CONFIGURATION_FN (MO_FILENAME_PREFIX \"ocpp-config.jsn\")\n#define CONFIGURATION_VOLATILE \"/volatile\"\n#define MO_KEYVALUE_FN (MO_FILENAME_PREFIX \"client-state.jsn\")\n\nnamespace MicroOcpp {\n\ntemplate <class T>\nstd::shared_ptr<Configuration> declareConfiguration(const char *key, T factoryDefault, const char *filename = CONFIGURATION_FN, bool readonly = false, bool rebootRequired = false, bool accessible = true);\n\nstd::function<bool(const char*)> *getConfigurationValidator(const char *key);\nvoid registerConfigurationValidator(const char *key, std::function<bool(const char*)> validator);\n\nvoid addConfigurationContainer(std::shared_ptr<ConfigurationContainer> container);\n\nConfiguration *getConfigurationPublic(const char *key);\nVector<ConfigurationContainer*> getConfigurationContainersPublic();\n\nbool configuration_init(std::shared_ptr<FilesystemAdapter> filesytem);\nvoid configuration_deinit();\n\nbool configuration_load(const char *filename = nullptr);\n\nbool configuration_save();\n\nbool configuration_clean_unused(); //remove configs which haven't been accessed\n\n//default implementation for common validator\nbool VALIDATE_UNSIGNED_INT(const char*);\n\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/ConfigurationContainer.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/ConfigurationContainer.h>\n\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nConfigurationContainer::~ConfigurationContainer() {\n\n}\n\nConfigurationContainerVolatile::ConfigurationContainerVolatile(const char *filename, bool accessible) :\n        ConfigurationContainer(filename, accessible), MemoryManaged(\"v16.Configuration.ContainerVoltaile.\", filename), configurations(makeVector<std::shared_ptr<Configuration>>(getMemoryTag())) {\n\n}\n\nbool ConfigurationContainerVolatile::load() {\n    return true;\n}\n\nbool ConfigurationContainerVolatile::save() {\n    return true;\n}\n\nstd::shared_ptr<Configuration> ConfigurationContainerVolatile::createConfiguration(TConfig type, const char *key) {\n    auto res = std::shared_ptr<Configuration>(makeConfiguration(type, key).release(), std::default_delete<Configuration>(), makeAllocator<Configuration>(\"v16.Configuration.\", key));\n    if (!res) {\n        //allocation failure - OOM\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n    configurations.push_back(res);\n    return res;\n}\n\nvoid ConfigurationContainerVolatile::remove(Configuration *config) {\n    for (auto entry = configurations.begin(); entry != configurations.end();) {\n        if (entry->get() == config) {\n            entry = configurations.erase(entry);\n        } else {\n            entry++;\n        }\n    }\n}\n\nsize_t ConfigurationContainerVolatile::size() {\n    return configurations.size();\n}\n\nConfiguration *ConfigurationContainerVolatile::getConfiguration(size_t i) {\n    return configurations[i].get();\n}\n\nstd::shared_ptr<Configuration> ConfigurationContainerVolatile::getConfiguration(const char *key) {\n    for (auto& entry : configurations) {\n        if (entry->getKey() && !strcmp(entry->getKey(), key)) {\n            return entry;\n        }\n    }\n    return nullptr;\n}\n\nvoid ConfigurationContainerVolatile::add(std::shared_ptr<Configuration> c) {\n    configurations.push_back(std::move(c));\n}\n\nnamespace MicroOcpp {\n\nstd::unique_ptr<ConfigurationContainerVolatile> makeConfigurationContainerVolatile(const char *filename, bool accessible) {\n    return std::unique_ptr<ConfigurationContainerVolatile>(new ConfigurationContainerVolatile(filename, accessible));\n}\n\n} //end namespace MicroOcpp\n"
  },
  {
    "path": "src/MicroOcpp/Core/ConfigurationContainer.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CONFIGURATIONCONTAINER_H\n#define MO_CONFIGURATIONCONTAINER_H\n\n#include <memory>\n\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass ConfigurationContainer {\nprivate:\n    const char *filename;\n    bool accessible;\npublic:\n    ConfigurationContainer(const char *filename, bool accessible) : filename(filename), accessible(accessible) { }\n\n    virtual ~ConfigurationContainer();\n\n    const char *getFilename() {return filename;}\n    bool isAccessible() {return accessible;}\n\n    virtual bool load() = 0; //called at the end of mocpp_intialize, to load the configurations with the stored value\n    virtual bool save() = 0;\n\n    virtual std::shared_ptr<Configuration> createConfiguration(TConfig type, const char *key) = 0;\n    virtual void remove(Configuration *config) = 0;\n\n    virtual size_t size() = 0;\n    virtual Configuration *getConfiguration(size_t i) = 0;\n    virtual std::shared_ptr<Configuration> getConfiguration(const char *key) = 0;\n\n    virtual void loadStaticKey(Configuration& config, const char *key) { } //possible optimization: can replace internal key with passed static key\n\n    virtual void removeUnused() { } //remove configs which haven't been accessed (optional and only if known)\n};\n\nclass ConfigurationContainerVolatile : public ConfigurationContainer, public MemoryManaged {\nprivate:\n    Vector<std::shared_ptr<Configuration>> configurations;\npublic:\n    ConfigurationContainerVolatile(const char *filename, bool accessible);\n\n    //ConfigurationContainer definitions\n    bool load() override;\n    bool save() override;\n    std::shared_ptr<Configuration> createConfiguration(TConfig type, const char *key) override;\n    void remove(Configuration *config) override;\n    size_t size() override;\n    Configuration *getConfiguration(size_t i) override;\n    std::shared_ptr<Configuration> getConfiguration(const char *key) override;\n\n    //add custom Configuration object\n    void add(std::shared_ptr<Configuration> c);\n};\n\nstd::unique_ptr<ConfigurationContainerVolatile> makeConfigurationContainerVolatile(const char *filename, bool accessible);\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/ConfigurationContainerFlash.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/ConfigurationContainerFlash.h>\n\n#include <algorithm>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Debug.h>\n\n#define MAX_CONFIGURATIONS 50\n\nnamespace MicroOcpp {\n\nclass ConfigurationContainerFlash : public ConfigurationContainer, public MemoryManaged {\nprivate:\n    Vector<std::shared_ptr<Configuration>> configurations;\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    uint16_t revisionSum = 0;\n\n    bool loaded = false;\n\n    Vector<char*> keyPool;\n    \n    void clearKeyPool(const char *key) {\n        auto it = keyPool.begin();\n        while (it != keyPool.end()) {\n            if (!strcmp(*it, key)) {\n                MO_DBG_VERBOSE(\"clear key %s\", key);\n                MO_FREE(*it);\n                it = keyPool.erase(it);\n            } else {\n                ++it;\n            }\n        }\n    }\n\n    bool configurationsUpdated() {\n        auto revisionSum_old = revisionSum;\n\n        revisionSum = 0;\n        for (auto& config : configurations) {\n            revisionSum += config->getValueRevision();\n        }\n\n        return revisionSum != revisionSum_old;\n    }\npublic:\n    ConfigurationContainerFlash(std::shared_ptr<FilesystemAdapter> filesystem, const char *filename, bool accessible) :\n            ConfigurationContainer(filename, accessible), MemoryManaged(\"v16.Configuration.ContainerFlash.\", filename), configurations(makeVector<std::shared_ptr<Configuration>>(getMemoryTag())), filesystem(filesystem), keyPool(makeVector<char*>(getMemoryTag())) { }\n\n    ~ConfigurationContainerFlash() {\n        auto it = keyPool.begin();\n        while (it != keyPool.end()) {\n            MO_FREE(*it);\n            it = keyPool.erase(it);\n        }\n    }\n    \n    bool load() override {\n\n        if (loaded) {\n            return true;\n        }\n\n        if (!filesystem) {\n            return false;\n        }\n\n        size_t file_size = 0;\n        if (filesystem->stat(getFilename(), &file_size) != 0 // file does not exist\n                || file_size == 0) {                         // file exists, but empty\n            MO_DBG_DEBUG(\"Populate FS: create configuration file\");\n            return save();\n        }\n\n        auto doc = FilesystemUtils::loadJson(filesystem, getFilename(), getMemoryTag());\n        if (!doc) {\n            MO_DBG_ERR(\"failed to load %s\", getFilename());\n            return false;\n        }\n\n        JsonObject root = doc->as<JsonObject>();\n\n        JsonObject configHeader = root[\"head\"];\n\n        if (strcmp(configHeader[\"content-type\"] | \"Invalid\", \"ocpp_config_file\") &&\n                strcmp(configHeader[\"content-type\"] | \"Invalid\", \"ao_configuration_file\")) { //backwards-compatibility\n            MO_DBG_ERR(\"Unable to initialize: unrecognized configuration file format\");\n            return false;\n        }\n\n        if (strcmp(configHeader[\"version\"] | \"Invalid\", \"2.0\") &&\n                strcmp(configHeader[\"version\"] | \"Invalid\", \"1.1\")) { //backwards-compatibility\n            MO_DBG_ERR(\"Unable to initialize: unsupported version\");\n            return false;\n        }\n        \n        JsonArray configurationsArray = root[\"configurations\"];\n        if (configurationsArray.size() > MAX_CONFIGURATIONS) {\n            MO_DBG_ERR(\"Unable to initialize: configurations_len is too big (=%zu)\", configurationsArray.size());\n            return false;\n        }\n\n        for (JsonObject stored : configurationsArray) {\n            TConfig type;\n            if (!deserializeTConfig(stored[\"type\"] | \"_Undefined\", type)) {\n                MO_DBG_ERR(\"corrupt config\");\n                continue;\n            }\n\n            const char *key = stored[\"key\"] | \"\";\n            if (!*key) {\n                MO_DBG_ERR(\"corrupt config\");\n                continue;\n            }\n\n            if (!stored.containsKey(\"value\")) {\n                MO_DBG_ERR(\"corrupt config\");\n                continue;\n            }\n\n            char *key_pooled = nullptr;\n\n            auto config = getConfiguration(key).get();\n            if (config && config->getType() != type) {\n                MO_DBG_ERR(\"conflicting type for %s - remove old config\", key);\n                remove(config);\n                config = nullptr;\n            }\n            if (!config) {\n                #if MO_ENABLE_HEAP_PROFILER\n                char memoryTag [64];\n                snprintf(memoryTag, sizeof(memoryTag), \"%s%s\", \"v16.Configuration.\", key);\n                #else\n                const char *memoryTag = nullptr;\n                (void)memoryTag;\n                #endif\n                key_pooled = static_cast<char*>(MO_MALLOC(memoryTag, strlen(key) + 1));\n                if (!key_pooled) {\n                    MO_DBG_ERR(\"OOM: %s\", key);\n                    return false;\n                }\n                strcpy(key_pooled, key);\n            }\n\n            switch (type) {\n                case TConfig::Int: {\n                    if (!stored[\"value\"].is<int>()) {\n                        MO_DBG_ERR(\"corrupt config\");\n                        MO_FREE(key_pooled);\n                        continue;\n                    }\n                    int value = stored[\"value\"] | 0;\n                    if (!config) {\n                        //create new config\n                        config = createConfiguration(TConfig::Int, key_pooled).get();\n                    }\n                    if (config) {\n                        config->setInt(value);\n                    }\n                    break;\n                }\n                case TConfig::Bool: {\n                    if (!stored[\"value\"].is<bool>()) {\n                        MO_DBG_ERR(\"corrupt config\");\n                        MO_FREE(key_pooled);\n                        continue;\n                    }\n                    bool value = stored[\"value\"] | false;\n                    if (!config) {\n                        //create new config\n                        config = createConfiguration(TConfig::Bool, key_pooled).get();\n                    }\n                    if (config) {\n                        config->setBool(value);\n                    }\n                    break;\n                }\n                case TConfig::String: {\n                    if (!stored[\"value\"].is<const char*>()) {\n                        MO_DBG_ERR(\"corrupt config\");\n                        MO_FREE(key_pooled);\n                        continue;\n                    }\n                    const char *value = stored[\"value\"] | \"\";\n                    if (!config) {\n                        //create new config\n                        config = createConfiguration(TConfig::String, key_pooled).get();\n                    }\n                    if (config) {\n                        config->setString(value);\n                    }\n                    break;\n                }\n            }\n\n            if (config) {\n                //success\n\n                if (key_pooled) {\n                    //allocated key, need to store\n                    keyPool.push_back(std::move(key_pooled));\n                }\n            } else {\n                MO_DBG_ERR(\"OOM: %s\", key);\n                MO_FREE(key_pooled);\n            }\n        }\n\n        configurationsUpdated();\n\n        MO_DBG_DEBUG(\"Initialization finished\");\n        loaded = true;\n        return true;\n    }\n\n    bool save() override {\n\n        if (!filesystem) {\n            return false;\n        }\n\n        if (!configurationsUpdated()) {\n            return true; //nothing to be done\n        }\n\n        //during mocpp_deinitialize(), key owners are destructed. Don't store if this container is affected\n        for (auto& config : configurations) {\n            if (!config->getKey()) {\n                MO_DBG_DEBUG(\"don't write back container with destructed key(s)\");\n                return false;\n            }\n        }\n\n        size_t jsonCapacity = 2 * JSON_OBJECT_SIZE(2); //head + configurations + head payload\n        jsonCapacity += JSON_ARRAY_SIZE(configurations.size()); //configurations array\n        jsonCapacity += configurations.size() * JSON_OBJECT_SIZE(3); //config entries in array\n\n        if (jsonCapacity > MO_MAX_JSON_CAPACITY) {\n            MO_DBG_ERR(\"configs JSON exceeds maximum capacity (%s, %zu entries). Crop configs file (by FCFS)\", getFilename(), configurations.size());\n            jsonCapacity = MO_MAX_JSON_CAPACITY;\n        }\n\n        auto doc = initJsonDoc(getMemoryTag(), jsonCapacity);\n        JsonObject head = doc.createNestedObject(\"head\");\n        head[\"content-type\"] = \"ocpp_config_file\";\n        head[\"version\"] = \"2.0\";\n\n        JsonArray configurationsArray = doc.createNestedArray(\"configurations\");\n\n        size_t trackCapacity = 0;\n\n        for (size_t i = 0; i < configurations.size(); i++) {\n            auto& config = *configurations[i];\n\n            size_t entryCapacity = JSON_OBJECT_SIZE(3) + (JSON_ARRAY_SIZE(2) - JSON_ARRAY_SIZE(1));\n            if (trackCapacity + entryCapacity > MO_MAX_JSON_CAPACITY) {\n                break;\n            }\n\n            trackCapacity += entryCapacity;\n\n            auto stored = configurationsArray.createNestedObject();\n\n            stored[\"type\"] = serializeTConfig(config.getType());\n            stored[\"key\"] = config.getKey();\n            \n            switch (config.getType()) {\n                case TConfig::Int:\n                    stored[\"value\"] = config.getInt();\n                    break;\n                case TConfig::Bool:\n                    stored[\"value\"] = config.getBool();\n                    break;\n                case TConfig::String:\n                    stored[\"value\"] = config.getString();\n                    break;\n            }\n        }\n\n        bool success = FilesystemUtils::storeJson(filesystem, getFilename(), doc);\n\n        if (success) {\n            MO_DBG_DEBUG(\"Saving configurations finished\");\n        } else {\n            MO_DBG_ERR(\"could not save configs file: %s\", getFilename());\n        }\n\n        return success;\n    }\n\n    std::shared_ptr<Configuration> createConfiguration(TConfig type, const char *key) override {\n        auto res = std::shared_ptr<Configuration>(makeConfiguration(type, key).release(), std::default_delete<Configuration>(), makeAllocator<Configuration>(\"v16.Configuration.\", key));\n        if (!res) {\n            //allocation failure - OOM\n            MO_DBG_ERR(\"OOM\");\n            return nullptr;\n        }\n        configurations.push_back(res);\n        return res;\n    }\n\n    void remove(Configuration *config) override {\n        const char *key = config->getKey();\n        configurations.erase(std::remove_if(configurations.begin(), configurations.end(),\n            [config] (std::shared_ptr<Configuration>& entry) {\n                return entry.get() == config;\n            }), configurations.end());\n        if (key) {\n            clearKeyPool(key);\n        }\n    }\n\n    size_t size() override {\n        return configurations.size();\n    }\n\n    Configuration *getConfiguration(size_t i) override {\n        return configurations[i].get();\n    }\n\n    std::shared_ptr<Configuration> getConfiguration(const char *key) override {\n        for (auto& entry : configurations) {\n            if (entry->getKey() && !strcmp(entry->getKey(), key)) {\n                return entry;\n            }\n        }\n        return nullptr;\n    }\n\n    void loadStaticKey(Configuration& config, const char *key) override {\n        config.setKey(key);\n        clearKeyPool(key);\n    }\n\n    void removeUnused() override {\n        //if a config's key is still in the keyPool, we know it's unused because it has never been declared in FW (originates from an older FW version)\n\n        auto key = keyPool.begin();\n        while (key != keyPool.end()) {\n\n            for (auto config = configurations.begin(); config != configurations.end(); ++config) {\n                if ((*config)->getKey() == *key) {\n                    MO_DBG_DEBUG(\"remove unused config %s\", (*config)->getKey());\n                    configurations.erase(config);\n                    break;\n                }\n            }\n\n            MO_FREE(*key);\n            key = keyPool.erase(key);\n        }\n    }\n};\n\nstd::unique_ptr<ConfigurationContainer> makeConfigurationContainerFlash(std::shared_ptr<FilesystemAdapter> filesystem, const char *filename, bool accessible) {\n    return std::unique_ptr<ConfigurationContainer>(new ConfigurationContainerFlash(filesystem, filename, accessible));\n}\n\n} //end namespace MicroOcpp\n"
  },
  {
    "path": "src/MicroOcpp/Core/ConfigurationContainerFlash.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CONFIGURATIONCONTAINERFLASH_H\n#define MO_CONFIGURATIONCONTAINERFLASH_H\n\n#include <MicroOcpp/Core/ConfigurationContainer.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n\nnamespace MicroOcpp {\n\nstd::unique_ptr<ConfigurationContainer> makeConfigurationContainerFlash(std::shared_ptr<FilesystemAdapter> filesystem, const char *filename, bool accessible);\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/ConfigurationKeyValue.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Debug.h>\n\n#include <string.h>\n#include <ArduinoJson.h>\n\n#define KEY_MAXLEN 60\n#define STRING_VAL_MAXLEN 512\n\nnamespace MicroOcpp {\n\ntemplate<> TConfig convertType<int>() {return TConfig::Int;}\ntemplate<> TConfig convertType<bool>() {return TConfig::Bool;}\ntemplate<> TConfig convertType<const char*>() {return TConfig::String;}\n\nConfiguration::~Configuration() {\n\n}\n\nvoid Configuration::setInt(int) {\n#if MO_CONFIG_TYPECHECK\n    MO_DBG_ERR(\"type err\");\n#endif\n}\n\nvoid Configuration::setBool(bool) {\n#if MO_CONFIG_TYPECHECK\n    MO_DBG_ERR(\"type err\");\n#endif\n}\n\nbool Configuration::setString(const char*) {\n#if MO_CONFIG_TYPECHECK\n    MO_DBG_ERR(\"type err\");\n#endif\n    return false;\n}\n\nint Configuration::getInt() {\n#if MO_CONFIG_TYPECHECK\n    MO_DBG_ERR(\"type err\");\n#endif\n    return 0;\n}\n\nbool Configuration::getBool() {\n#if MO_CONFIG_TYPECHECK\n    MO_DBG_ERR(\"type err\");\n#endif\n    return false;\n}\n\nconst char *Configuration::getString() {\n#if MO_CONFIG_TYPECHECK\n    MO_DBG_ERR(\"type err\");\n#endif\n    return \"\";\n}\n\nrevision_t Configuration::getValueRevision() {\n    return value_revision;\n}\n\nvoid Configuration::setRebootRequired() {\n    rebootRequired = true;\n}\n\nbool Configuration::isRebootRequired() {\n    return rebootRequired;\n}\n\nvoid Configuration::setReadOnly() {\n    if (mutability == Mutability::ReadWrite) {\n        mutability = Mutability::ReadOnly;\n    } else {\n        mutability = Mutability::None;\n    }\n}\n\nbool Configuration::isReadOnly() {\n    return mutability == Mutability::ReadOnly;\n}\n\nbool Configuration::isReadable() {\n    return mutability == Mutability::ReadWrite || mutability == Mutability::ReadOnly;\n}\n\nvoid Configuration::setWriteOnly() {\n    if (mutability == Mutability::ReadWrite) {\n        mutability = Mutability::WriteOnly;\n    } else {\n        mutability = Mutability::None;\n    }\n}\n\n/*\n * Default implementations of the Configuration interface.\n *\n * How to use custom implementations: for each OCPP config, pass a config instance to the OCPP lib\n * before its initialization stage. Then the library won't create new config objects but \n */\n\nclass ConfigInt : public Configuration, public MemoryManaged {\nprivate:\n    const char *key = nullptr;\n    int val = 0;\npublic:\n\n    ~ConfigInt() = default;\n\n    bool setKey(const char *key) override {\n        this->key = key;\n        updateMemoryTag(\"v16.Configuration.\", key);\n        return true;\n    }\n\n    const char *getKey() override {\n        return key;\n    }\n\n    TConfig getType() override {\n        return TConfig::Int;\n    }\n\n    void setInt(int val) override {\n        this->val = val;\n        value_revision++;\n    }\n\n    int getInt() override {\n        return val;\n    }\n};\n\nclass ConfigBool : public Configuration, public MemoryManaged {\nprivate:\n    const char *key = nullptr;\n    bool val = false;\npublic:\n\n    ~ConfigBool() = default;\n\n    bool setKey(const char *key) override {\n        this->key = key;\n        updateMemoryTag(\"v16.Configuration.\", key);\n        return true;\n    }\n\n    const char *getKey() override {\n        return key;\n    }\n\n    TConfig getType() override {\n        return TConfig::Bool;\n    }\n\n    void setBool(bool val) override {\n        this->val = val;\n        value_revision++;\n    }\n\n    bool getBool() override {\n        return val;\n    }\n};\n\nclass ConfigString : public Configuration, public MemoryManaged {\nprivate:\n    const char *key = nullptr;\n    char *val = nullptr;\npublic:\n    ConfigString() = default;\n    ConfigString(const ConfigString&) = delete;\n    ConfigString(ConfigString&&) = delete;\n    ConfigString& operator=(const ConfigString&) = delete;\n\n    ~ConfigString() {\n        MO_FREE(val);\n    }\n\n    bool setKey(const char *key) override {\n        this->key = key;\n        updateMemoryTag(\"v16.Configuration.\", key);\n        if (val) {\n            MO_MEM_SET_TAG(val, getMemoryTag());\n        }\n        return true;\n    }\n\n    const char *getKey() override {\n        return key;\n    }\n\n    TConfig getType() override {\n        return TConfig::String;\n    }\n\n    bool setString(const char *src) override {\n        bool src_empty = !src || !*src;\n\n        if (!val && src_empty) {\n            return true;\n        }\n\n        if (this->val && src && !strcmp(this->val, src)) {\n            return true;\n        }\n\n        size_t size = 0;\n        if (!src_empty) {\n            size = strlen(src) + 1;\n        }\n\n        if (size > MO_CONFIG_MAX_VALSTRSIZE) {\n            return false;\n        }\n\n        value_revision++;\n\n        if (this->val) {\n            MO_FREE(this->val);\n            this->val = nullptr;\n        }\n\n        if (!src_empty) {\n            this->val = (char*) MO_MALLOC(getMemoryTag(), size);\n            if (!this->val) {\n                return false;\n            }\n            strcpy(this->val, src);\n        }\n\n        return true;\n    }\n\n    const char *getString() override {\n        if (!val) {\n            return \"\";\n        }\n        return val;\n    }\n};\n\nstd::unique_ptr<Configuration> makeConfiguration(TConfig type, const char *key) {\n    std::unique_ptr<Configuration> res;\n    switch (type) {\n        case TConfig::Int:\n            res.reset(new ConfigInt());\n            break;\n        case TConfig::Bool:\n            res.reset(new ConfigBool());\n            break;\n        case TConfig::String:\n            res.reset(new ConfigString());\n            break;\n    }\n    if (!res) {\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n    res->setKey(key);\n    return res;\n}\n\nbool deserializeTConfig(const char *serialized, TConfig& out) {\n    if (!strcmp(serialized, \"int\")) {\n        out = TConfig::Int;\n        return true;\n    } else if (!strcmp(serialized, \"bool\")) {\n        out = TConfig::Bool;\n        return true;\n    } else if (!strcmp(serialized, \"string\")) {\n        out = TConfig::String;\n        return true;\n    } else {\n        MO_DBG_WARN(\"config type error\");\n        return false;\n    }\n}\n\nconst char *serializeTConfig(TConfig type) {\n    switch (type) {\n        case TConfig::Int:\n            return \"int\";\n        case TConfig::Bool:\n            return \"bool\";\n        case TConfig::String:\n            return \"string\";\n    }\n    return \"_Undefined\";\n}\n\n} //end namespace MicroOcpp\n"
  },
  {
    "path": "src/MicroOcpp/Core/ConfigurationKeyValue.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef CONFIGURATIONKEYVALUE_H\n#define CONFIGURATIONKEYVALUE_H\n\n#include <ArduinoJson.h>\n#include <memory>\n\n#define MO_CONFIG_MAX_VALSTRSIZE 128\n\n#ifndef MO_CONFIG_EXT_PREFIX\n#define MO_CONFIG_EXT_PREFIX \"Cst_\"\n#endif\n\n#ifndef MO_CONFIG_TYPECHECK\n#define MO_CONFIG_TYPECHECK 1 //enable this for debugging\n#endif\n\nnamespace MicroOcpp {\n\nusing revision_t = uint16_t;\n\nenum class TConfig : uint8_t {\n    Int,\n    Bool,\n    String\n};\n\ntemplate<class T>\nTConfig convertType();\n\nclass Configuration {\nprotected:\n    revision_t value_revision = 0; //write access counter; used to check if this config has been changed\nprivate:\n    bool rebootRequired = false;\n\n    enum class Mutability : uint8_t {\n        ReadWrite,\n        ReadOnly,\n        WriteOnly,\n        None\n    };\n    Mutability mutability = Mutability::ReadWrite;\n\npublic:\n    virtual ~Configuration();\n\n    virtual bool setKey(const char *key) = 0;\n    virtual const char *getKey() = 0;\n\n    virtual void setInt(int);\n    virtual void setBool(bool);\n    virtual bool setString(const char*);\n\n    virtual int getInt();\n    virtual bool getBool();\n    virtual const char *getString(); //always returns c-string (empty if undefined)\n\n    virtual TConfig getType() = 0;\n\n    virtual revision_t getValueRevision();\n\n    void setRebootRequired();\n    bool isRebootRequired();\n\n    void setReadOnly();\n    bool isReadOnly();\n    bool isReadable();\n\n    void setWriteOnly();\n};\n\n/*\n * Default implementations of the Configuration interface.\n *\n * How to use custom implementations: for each OCPP config, pass a config instance to the OCPP lib\n * before its initialization stage. Then the library won't create new config objects but \n */\nstd::unique_ptr<Configuration> makeConfiguration(TConfig type, const char *key);\n\nconst char *serializeTConfig(TConfig type);\nbool deserializeTConfig(const char *serialized, TConfig& out);\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/ConfigurationOptions.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CONFIGURATIONOPTIONS_H\n#define MO_CONFIGURATIONOPTIONS_H\n\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct OCPP_FilesystemOpt {\n    bool use;\n    bool mount;\n    bool formatFsOnFail;\n};\n\n#ifdef __cplusplus\n}\n\nnamespace MicroOcpp {\n\nclass FilesystemOpt{\nprivate:\n    bool use = false;\n    bool mount = false;\n    bool formatFsOnFail = false;\npublic:\n    enum Mode : uint8_t {Deactivate, Use, Use_Mount, Use_Mount_FormatOnFail};\n\n    FilesystemOpt() = default;\n    FilesystemOpt(Mode mode) {\n        switch (mode) {\n            case (FilesystemOpt::Use_Mount_FormatOnFail):\n                formatFsOnFail = true;\n                //fallthrough\n            case (FilesystemOpt::Use_Mount):\n                mount = true;\n                //fallthrough\n            case (FilesystemOpt::Use):\n                use = true;\n                break;\n            default:\n                break;\n        }\n    }\n    FilesystemOpt(struct OCPP_FilesystemOpt fsopt) {\n        this->use = fsopt.use;\n        this->mount = fsopt.mount;\n        this->formatFsOnFail = fsopt.formatFsOnFail;\n    }\n\n    bool accessAllowed() {return use;}\n    bool mustMount() {return mount;}\n    bool formatOnFail() {return formatFsOnFail;}\n};\n\n} //end namespace MicroOcpp\n\n#endif //__cplusplus\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/Configuration_c.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Configuration_c.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nclass ConfigurationC : public Configuration, public MemoryManaged {\nprivate:\n    ocpp_configuration *config;\npublic:\n    ConfigurationC(ocpp_configuration *config) :\n            config(config) {\n        if (config->read_only) {\n            setReadOnly();\n        }\n        if (config->write_only) {\n            setWriteOnly();\n        }\n        if (config->reboot_required) {\n            setRebootRequired();\n        }\n    }\n\n    bool setKey(const char *key) override {\n        updateMemoryTag(\"v16.Configuration.\", key);\n        return config->set_key(config->user_data, key);\n    }\n\n    const char *getKey() override {\n        return config->get_key(config->user_data);\n    }\n\n    void setInt(int val) override {\n        #if MO_CONFIG_TYPECHECK\n        if (config->get_type(config->user_data) != ENUM_CDT_INT) {\n            MO_DBG_ERR(\"type err\");\n            return;\n        }\n        #endif\n        config->set_int(config->user_data, val);\n    }\n\n    void setBool(bool val) override {\n        #if MO_CONFIG_TYPECHECK\n        if (config->get_type(config->user_data) != ENUM_CDT_BOOL) {\n            MO_DBG_ERR(\"type err\");\n            return;\n        }\n        #endif\n        config->set_bool(config->user_data, val);\n    }\n\n    bool setString(const char *val) override {\n        #if MO_CONFIG_TYPECHECK\n        if (config->get_type(config->user_data) != ENUM_CDT_STRING) {\n            MO_DBG_ERR(\"type err\");\n            return false;\n        }\n        #endif\n        return config->set_string(config->user_data, val);\n    }\n\n    int getInt() override {\n        #if MO_CONFIG_TYPECHECK\n        if (config->get_type(config->user_data) != ENUM_CDT_INT) {\n            MO_DBG_ERR(\"type err\");\n            return 0;\n        }\n        #endif\n        return config->get_int(config->user_data);\n    }\n\n    bool getBool() override {\n        #if MO_CONFIG_TYPECHECK\n        if (config->get_type(config->user_data) != ENUM_CDT_BOOL) {\n            MO_DBG_ERR(\"type err\");\n            return false;\n        }\n        #endif\n        return config->get_bool(config->user_data);\n    }\n\n    const char *getString() override {\n        #if MO_CONFIG_TYPECHECK\n        if (config->get_type(config->user_data) != ENUM_CDT_STRING) {\n            MO_DBG_ERR(\"type err\");\n            return \"\";\n        }\n        #endif\n        return config->get_string(config->user_data);\n    }\n\n    TConfig getType() override {\n        TConfig res = TConfig::Int;\n        switch (config->get_type(config->user_data)) {\n            case ENUM_CDT_INT:\n                res = TConfig::Int;\n                break;\n            case ENUM_CDT_BOOL:\n                res = TConfig::Bool;\n                break;\n            case ENUM_CDT_STRING:\n                res = TConfig::String;\n                break;\n            default:\n                MO_DBG_ERR(\"type conversion\");\n                break;\n        }\n\n        return res;\n    }\n\n    uint16_t getValueRevision() override {\n        return config->get_write_count(config->user_data);\n    }\n\n    ocpp_configuration *getConfiguration() {\n        return config;\n    }\n};\n\nnamespace MicroOcpp {\n\nConfigurationC *getConfigurationC(ocpp_configuration *config) {\n    if (!config->mo_data) {\n        return nullptr;\n    }\n    return reinterpret_cast<std::shared_ptr<ConfigurationC>*>(config->mo_data)->get();\n}\n\n}\n\nusing namespace MicroOcpp;\n\n\nvoid ocpp_setRebootRequired(ocpp_configuration *config) {\n    if (auto c = getConfigurationC(config)) {\n        c->setRebootRequired();\n    }\n    config->reboot_required = true;\n}\nbool ocpp_isRebootRequired(ocpp_configuration *config) {\n    if (auto c = getConfigurationC(config)) {\n        return c->isRebootRequired();\n    }\n    return config->reboot_required;\n}\n\nvoid ocpp_setReadOnly(ocpp_configuration *config) {\n    if (auto c = getConfigurationC(config)) {\n        c->setReadOnly();\n    }\n    config->read_only = true;\n}\nbool ocpp_isReadOnly(ocpp_configuration *config) {\n    if (auto c = getConfigurationC(config)) {\n        return c->isReadOnly();\n    }\n    return config->read_only;\n}\nbool ocpp_isReadable(ocpp_configuration *config) {\n    if (auto c = getConfigurationC(config)) {\n        return c->isReadable();\n    }\n    return !config->write_only;\n}\n\nvoid ocpp_setWriteOnly(ocpp_configuration *config) {\n    if (auto c = getConfigurationC(config)) {\n        c->setWriteOnly();\n    }\n    config->write_only = true;\n}\n\nclass ConfigurationContainerC : public ConfigurationContainer, public MemoryManaged {\nprivate:\n    ocpp_configuration_container *container;\npublic:\n    ConfigurationContainerC(ocpp_configuration_container *container, const char *filename, bool accessible) :\n            ConfigurationContainer(filename, accessible), MemoryManaged(\"v16.Configuration.ContainerC.\", filename), container(container) {\n\n    }\n\n    ~ConfigurationContainerC() {\n        for (size_t i = 0; i < container->size(container->user_data); i++) {\n            if (auto config = container->get_configuration(container->user_data, i)) {\n                if (config->mo_data) {\n                    delete reinterpret_cast<std::shared_ptr<ConfigurationC>*>(config->mo_data);\n                    config->mo_data = nullptr;\n                }\n            }\n        }\n    }\n\n    bool load() override {\n        if (container->load) {\n            return container->load(container->user_data);\n        } else {\n            return true;\n        }\n    }\n\n    bool save() override {\n        if (container->save) {\n            return container->save(container->user_data);\n        } else {\n            return true;\n        }\n    }\n\n    std::shared_ptr<Configuration> createConfiguration(TConfig type, const char *key) override {\n\n        auto result = std::shared_ptr<ConfigurationC>(nullptr, std::default_delete<ConfigurationC>(), makeAllocator<ConfigurationC>(getMemoryTag()));\n\n        if (!container->create_configuration) {\n            return result;\n        }\n\n        ocpp_config_datatype dt;\n        switch (type) {\n            case TConfig::Int:\n                dt = ENUM_CDT_INT;\n                break;\n            case TConfig::Bool:\n                dt = ENUM_CDT_BOOL;\n                break;\n            case TConfig::String:\n                dt = ENUM_CDT_STRING;\n                break;\n            default:\n                MO_DBG_ERR(\"internal error\");\n                return result;\n        }\n        ocpp_configuration *config = container->create_configuration(container->user_data, dt, key);\n        if (!config) {\n            return result;\n        }\n        \n        result.reset(new ConfigurationC(config));\n\n        if (result) {\n            auto captureConfigC = new std::shared_ptr<ConfigurationC>(result);\n            config->mo_data = reinterpret_cast<void*>(captureConfigC);\n        } else {\n            MO_DBG_ERR(\"could not create config: %s\", key);\n            if (container->remove) {\n                container->remove(container->user_data, key);\n            }\n        }\n\n        return result;\n    }\n\n    void remove(Configuration *config) override {\n        if (!container->remove) {\n            return;\n        }\n\n        if (auto c = container->get_configuration_by_key(container->user_data, config->getKey())) {\n            delete reinterpret_cast<std::shared_ptr<ConfigurationC>*>(c->mo_data);\n            c->mo_data = nullptr;\n        }\n\n        container->remove(container->user_data, config->getKey());\n    }\n\n    size_t size() override {\n        return container->size(container->user_data);\n    }\n\n    Configuration *getConfiguration(size_t i) override {\n        auto config = container->get_configuration(container->user_data, i);\n        if (config) {\n            if (!config->mo_data) {\n                auto c = new ConfigurationC(config);\n                if (c) {\n                    config->mo_data = reinterpret_cast<void*>(new std::shared_ptr<ConfigurationC>(c, std::default_delete<ConfigurationC>(), makeAllocator<ConfigurationC>(getMemoryTag())));\n                }\n            }\n            return static_cast<Configuration*>(config->mo_data ? reinterpret_cast<std::shared_ptr<ConfigurationC>*>(config->mo_data)->get() : nullptr);\n        } else {\n            return nullptr;\n        }\n    }\n\n    std::shared_ptr<Configuration> getConfiguration(const char *key) override {\n        auto config = container->get_configuration_by_key(container->user_data, key);\n        if (config) {\n            if (!config->mo_data) {\n                auto c = new ConfigurationC(config);\n                if (c) {\n                    config->mo_data = reinterpret_cast<void*>(new std::shared_ptr<ConfigurationC>(c, std::default_delete<ConfigurationC>(), makeAllocator<ConfigurationC>(getMemoryTag())));\n                }\n            }\n            return config->mo_data ? *reinterpret_cast<std::shared_ptr<ConfigurationC>*>(config->mo_data) : nullptr;\n        } else {\n            return nullptr;\n        }\n    }\n\n    void loadStaticKey(Configuration& config, const char *key) override {\n        if (container->load_static_key) {\n            container->load_static_key(container->user_data, key);\n        }\n    }\n};\n\nvoid ocpp_configuration_container_add(ocpp_configuration_container *container, const char *container_path, bool accessible) {\n    addConfigurationContainer(std::allocate_shared<ConfigurationContainerC>(makeAllocator<ConfigurationContainerC>(\"v16.Configuration.ContainerC.\", container_path), container, container_path, accessible));\n}\n"
  },
  {
    "path": "src/MicroOcpp/Core/Configuration_c.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CONFIGURATION_C_H\n#define MO_CONFIGURATION_C_H\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef enum ocpp_config_datatype {\n    ENUM_CDT_INT,\n    ENUM_CDT_BOOL,\n    ENUM_CDT_STRING\n} ocpp_config_datatype;\n\ntypedef struct ocpp_configuration {\n    void *user_data; // Set this at your choice. MO passes it back to the functions below\n\n    bool (*set_key) (void *user_data, const char *key); // Optional. MO may provide a static key value which you can use to replace a possibly malloc'd key buffer\n    const char* (*get_key) (void *user_data); // Return Configuration key\n\n    ocpp_config_datatype (*get_type) (void *user_data); // Return internal data type of config (determines which of the following getX()/setX() pairs are valid)\n\n    // Set value of Config\n    union {\n        void (*set_int) (void *user_data, int val);\n        void (*set_bool) (void *user_data, bool val);\n        bool (*set_string) (void *user_data, const char *val);\n    };\n\n    // Get value of Config\n    union {\n        int (*get_int) (void *user_data);\n        bool (*get_bool) (void *user_data);\n        const char* (*get_string) (void *user_data);\n    };\n\n    uint16_t (*get_write_count) (void *user_data); // Return number of changes of the value. MO uses this to detect if the firmware has updated the config\n\n    bool read_only;\n    bool write_only;\n    bool reboot_required;\n\n    void *mo_data; // Reserved for MO\n} ocpp_configuration;\n\nvoid ocpp_setRebootRequired(ocpp_configuration *config);\nbool ocpp_isRebootRequired(ocpp_configuration *config);\n\nvoid ocpp_setReadOnly(ocpp_configuration *config);\nbool ocpp_isReadOnly(ocpp_configuration *config);\nbool ocpp_isReadable(ocpp_configuration *config);\n\nvoid ocpp_setWriteOnly(ocpp_configuration *config);\n\ntypedef struct ocpp_configuration_container {\n    void *user_data; //set this at your choice. MO passes it back to the functions below\n\n    bool (*load) (void *user_data); // Called after declaring Configurations, to load them with their values\n    bool (*save) (void *user_data); // Commit all Configurations to memory\n\n    ocpp_configuration* (*create_configuration) (void *user_data, ocpp_config_datatype dt, const char *key); // Called to get a reference to a Configuration managed by this container (create new or return existing)\n    void (*remove) (void *user_data, const char *key); // Remove this config from the container. Do not free the config here, the config must outlive the MO lifecycle\n\n    size_t (*size) (void *user_data); // Number of Configurations currently managed by this container\n    ocpp_configuration* (*get_configuration) (void *user_data, size_t i); // Return config at container position i\n    ocpp_configuration* (*get_configuration_by_key) (void *user_data, const char *key); // Return config for given key\n\n    void (*load_static_key) (void *user_data, const char *key); // Optional. MO may provide a static key value which you can use to replace a possibly malloc'd key buffer\n} ocpp_configuration_container;\n\n// Add custom Configuration container. Add one container per container_path before mocpp_initialize(...)\nvoid ocpp_configuration_container_add(ocpp_configuration_container *container, const char *container_path, bool accessible);\n\n#ifdef __cplusplus\n} // extern \"C\"\n#endif\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/Connection.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nLoopbackConnection::LoopbackConnection() : MemoryManaged(\"WebSocketLoopback\") { }\n\nvoid LoopbackConnection::loop() { }\n\nbool LoopbackConnection::sendTXT(const char *msg, size_t length) {\n    if (!connected || !online) {\n        return false;\n    }\n    if (receiveTXT) {\n        lastRecv = mocpp_tick_ms();\n        return receiveTXT(msg, length);\n    } else {\n        return false;\n    }\n}\n\nvoid LoopbackConnection::setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT) {\n    this->receiveTXT = receiveTXT;\n}\n\nunsigned long LoopbackConnection::getLastRecv() {\n    return lastRecv;\n}\n\nunsigned long LoopbackConnection::getLastConnected() {\n    return lastConn;\n}\n\nvoid LoopbackConnection::setOnline(bool online) {\n    if (online) {\n        lastConn = mocpp_tick_ms();\n    }\n    this->online = online;\n}\n\nvoid LoopbackConnection::setConnected(bool connected) {\n    if (connected) {\n        lastConn = mocpp_tick_ms();\n    }\n    this->connected = connected;\n}\n\n#ifndef MO_CUSTOM_WS\n\nusing namespace MicroOcpp::EspWiFi;\n\nWSClient::WSClient(WebSocketsClient *wsock) : MemoryManaged(\"WebSocketsClient\"), wsock(wsock) {\n\n}\n\nvoid WSClient::loop() {\n    wsock->loop();\n}\n\nbool WSClient::sendTXT(const char *msg, size_t length) {\n    return wsock->sendTXT(msg, length);\n}\n\nvoid WSClient::setReceiveTXTcallback(ReceiveTXTcallback &callback) {\n    auto& captureLastRecv = lastRecv;\n    auto& captureLastConnected = lastConnected;\n    wsock->onEvent([callback, &captureLastRecv, &captureLastConnected](WStype_t type, uint8_t * payload, size_t length) {\n        switch (type) {\n            case WStype_DISCONNECTED:\n                MO_DBG_INFO(\"Disconnected\");\n                break;\n            case WStype_CONNECTED:\n                MO_DBG_INFO(\"Connected (path: %s)\", payload);\n                captureLastRecv = mocpp_tick_ms();\n                captureLastConnected = mocpp_tick_ms();\n                break;\n            case WStype_TEXT:\n                if (callback((const char *) payload, length)) { //forward message to RequestQueue\n                    captureLastRecv = mocpp_tick_ms();\n                } else {\n                    MO_DBG_WARN(\"Processing WebSocket input event failed\");\n                }\n                break;\n            case WStype_BIN:\n                MO_DBG_WARN(\"Binary data stream not supported\");\n                break;\n            case WStype_PING:\n                // pong will be send automatically\n                MO_DBG_TRAFFIC_IN(8, \"WS ping\");\n                captureLastRecv = mocpp_tick_ms();\n                break;\n            case WStype_PONG:\n                // answer to a ping we send\n                MO_DBG_TRAFFIC_IN(8, \"WS pong\");\n                captureLastRecv = mocpp_tick_ms();\n                break;\n            case WStype_FRAGMENT_TEXT_START: //fragments are not supported\n            default:\n                MO_DBG_WARN(\"Unsupported WebSocket event type\");\n                break;\n        }\n    });\n}\n\nunsigned long WSClient::getLastRecv() {\n    return lastRecv;\n}\n\nunsigned long WSClient::getLastConnected() {\n    return lastConnected;\n}\n\nbool WSClient::isConnected() {\n    return wsock->isConnected();\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/Connection.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CONNECTION_H\n#define MO_CONNECTION_H\n\n#include <functional>\n#include <memory>\n\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Platform.h>\n\n//On all platforms other than Arduino, the integrated WS lib (links2004/arduinoWebSockets) cannot be\n//used. On Arduino its usage is optional.\n#ifndef MO_CUSTOM_WS\n#if MO_PLATFORM != MO_PLATFORM_ARDUINO\n#define MO_CUSTOM_WS\n#endif\n#endif //ndef MO_CUSTOM_WS\n\nnamespace MicroOcpp {\n\nusing ReceiveTXTcallback = std::function<bool(const char*, size_t)>;\n\nclass Connection {\npublic:\n    Connection() = default;\n    virtual ~Connection() = default;\n\n    /*\n     * The OCPP library will call this function frequently. If you need to execute regular routines, like\n     * calling the loop-function of the WebSocket library, implement them here\n     */\n    virtual void loop() = 0;\n\n    /*\n     * The OCPP library calls this function for sending out OCPP messages to the server\n     */\n    virtual bool sendTXT(const char *msg, size_t length) = 0;\n\n    /*\n     * The OCPP library calls this function once during initialization. It passes a callback function to\n     * the socket. The socket should forward any incoming payload from the OCPP server to the receiveTXT callback\n     */\n    virtual void setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT) = 0;\n\n    /*\n     * Returns the timestamp of the last incoming message. Use mocpp_tick_ms() for creating the correct timestamp\n     *\n     * DEPRECATED: this function is superseded by isConnected(). Will be removed in MO v2.0\n     */\n    virtual unsigned long getLastRecv() {return 0;}\n\n    /*\n     * Returns the timestamp of the last time a connection got successfully established. Use mocpp_tick_ms() for creating the correct timestamp\n     */\n    virtual unsigned long getLastConnected() = 0;\n\n    /*\n     * NEW IN v1.1\n     *\n     * Returns true if the connection is open; false if the charger is known to be offline.\n     * \n     * This function determines if MO is in \"offline mode\". In offline mode, MO doesn't wait for Authorize responses\n     * before performing fully local authorization. If the connection is disrupted but isConnected is still true, then\n     * MO will first wait for a timeout to expire (20 seconds) before going into offline mode.\n     * \n     * Returning true will have no further effects other than  using the timeout-then-offline mechanism. If the\n     * connection status is uncertain, it's best to return true by default.\n     */\n    virtual bool isConnected() {return true;} //MO ignores true. This default implementation keeps backwards-compatibility\n};\n\nclass LoopbackConnection : public Connection, public MemoryManaged {\nprivate:\n    ReceiveTXTcallback receiveTXT;\n\n    //for simulating connection losses\n    bool online = true;\n    bool connected = true;\n    unsigned long lastRecv = 0;\n    unsigned long lastConn = 0;\npublic:\n    LoopbackConnection();\n\n    void loop() override;\n    bool sendTXT(const char *msg, size_t length) override;\n    void setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT) override;\n    unsigned long getLastRecv() override;\n    unsigned long getLastConnected() override;\n\n    void setOnline(bool online); //\"online\": sent messages are going through\n    bool isOnline() {return online;}\n    void setConnected(bool connected); //\"connected\": connection has been established, but messages may not go through (e.g. weak connection)\n    bool isConnected() override {return connected;}\n};\n\n} //end namespace MicroOcpp\n\n#ifndef MO_CUSTOM_WS\n\n#include <WebSocketsClient.h>\n\nnamespace MicroOcpp {\nnamespace EspWiFi {\n\nclass WSClient : public Connection, public MemoryManaged {\nprivate:\n    WebSocketsClient *wsock;\n    unsigned long lastRecv = 0, lastConnected = 0;\npublic:\n    WSClient(WebSocketsClient *wsock);\n\n    void loop();\n\n    bool sendTXT(const char *msg, size_t length);\n\n    void setReceiveTXTcallback(ReceiveTXTcallback &receiveTXT);\n\n    unsigned long getLastRecv() override; //get time of last successful receive in millis\n\n    unsigned long getLastConnected() override; //get last connection creation in millis\n\n    bool isConnected() override;\n};\n\n} //end namespace EspWiFi\n} //end namespace MicroOcpp\n#endif //ndef MO_CUSTOM_WS\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/Context.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Model/Model.h>\n\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nContext::Context(Connection& connection, std::shared_ptr<FilesystemAdapter> filesystem, uint16_t bootNr, ProtocolVersion version)\n        : MemoryManaged(\"Context\"), connection(connection), model{version, bootNr}, reqQueue{connection, operationRegistry} {\n\n}\n\nContext::~Context() {\n\n}\n\nvoid Context::loop() {\n    connection.loop();\n    reqQueue.loop();\n    model.loop();\n}\n\nvoid Context::initiateRequest(std::unique_ptr<Request> op) {\n    if (!op) {\n        MO_DBG_ERR(\"invalid arg\");\n        return;\n    }\n    reqQueue.sendRequest(std::move(op));\n}\n\nModel& Context::getModel() {\n    return model;\n}\n\nOperationRegistry& Context::getOperationRegistry() {\n    return operationRegistry;\n}\n\nconst ProtocolVersion& Context::getVersion() {\n    return model.getVersion();\n}\n\nConnection& Context::getConnection() {\n    return connection;\n}\n\nRequestQueue& Context::getRequestQueue() {\n    return reqQueue;\n}\n\nvoid Context::setFtpClient(std::unique_ptr<FtpClient> ftpClient) {\n    this->ftpClient = std::move(ftpClient);\n}\n\nFtpClient *Context::getFtpClient() {\n    return ftpClient.get();\n}\n"
  },
  {
    "path": "src/MicroOcpp/Core/Context.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CONTEXT_H\n#define MO_CONTEXT_H\n\n#include <memory>\n\n#include <MicroOcpp/Core/OperationRegistry.h>\n#include <MicroOcpp/Core/RequestQueue.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Core/Ftp.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Version.h>\n\nnamespace MicroOcpp {\n\nclass Connection;\nclass FilesystemAdapter;\n\nclass Context : public MemoryManaged {\nprivate:\n    Connection& connection;\n    OperationRegistry operationRegistry;\n    Model model;\n    RequestQueue reqQueue;\n\n    std::unique_ptr<FtpClient> ftpClient;\n\npublic:\n    Context(Connection& connection, std::shared_ptr<FilesystemAdapter> filesystem, uint16_t bootNr, ProtocolVersion version);\n    ~Context();\n\n    void loop();\n\n    void initiateRequest(std::unique_ptr<Request> op);\n\n    Model& getModel();\n\n    OperationRegistry& getOperationRegistry();\n\n    const ProtocolVersion& getVersion();\n\n    Connection& getConnection();\n\n    RequestQueue& getRequestQueue();\n\n    void setFtpClient(std::unique_ptr<FtpClient> ftpClient);\n    FtpClient *getFtpClient();\n};\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/FilesystemAdapter.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/ConfigurationOptions.h> //FilesystemOpt\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Debug.h>\n\n#include <cstring>\n\n/*\n * Platform specific implementations. Currently supported:\n *     - Arduino LittleFs\n *     - Arduino SPIFFS\n *     - ESP-IDF SPIFFS\n *     - POSIX-like API (tested on Ubuntu 20.04)\n * Plus a filesystem index decorator working with any of the above\n * \n * You can add support for other file systems by passing a custom adapter to mocpp_initialize(...)\n */\n\n#if MO_ENABLE_FILE_INDEX\n\n#include <algorithm>\n\nnamespace MicroOcpp {\n\nclass FilesystemAdapterIndex;\n\nclass IndexedFileAdapter : public FileAdapter, public MemoryManaged {\nprivate:\n    FilesystemAdapterIndex& index;\n    char fn [MO_MAX_PATH_SIZE];\n    std::unique_ptr<FileAdapter> file;\n\n    size_t written = 0;\npublic:\n    IndexedFileAdapter(FilesystemAdapterIndex& index, const char *fn, std::unique_ptr<FileAdapter> file)\n            : MemoryManaged(\"FilesystemIndex\"), index(index), file(std::move(file)) {\n        snprintf(this->fn, sizeof(this->fn), \"%s\", fn);\n    }\n\n    ~IndexedFileAdapter(); // destructor updates file index with written size\n\n    size_t read(char *buf, size_t len) override {\n        return file->read(buf, len);\n    }\n\n    size_t write(const char *buf, size_t len) override {\n        auto ret = file->write(buf, len);\n        written += ret;\n        return ret;\n    }\n\n    size_t seek(size_t offset) override {\n        auto ret = file->seek(offset);\n        written = ret;\n        return ret;\n    }\n\n    int read() override {\n        return file->read();\n    }\n};\n\nclass FilesystemAdapterIndex : public FilesystemAdapter, public MemoryManaged {\nprivate:\n    std::shared_ptr<FilesystemAdapter> filesystem;\n\n    struct IndexEntry {\n        String fname;\n        size_t size;\n\n        IndexEntry(const char *fname, size_t size) : fname(makeString(\"FilesystemIndex\", fname)), size(size) { }\n    };\n\n    Vector<IndexEntry> index;\n\n    IndexEntry *getEntryByFname(const char *fn) {\n        auto entry = std::find_if(index.begin(), index.end(),\n            [fn] (const IndexEntry& el) -> bool {\n                return el.fname.compare(fn) == 0;\n            });\n\n        if (entry != index.end()) {\n            return &(*entry);\n        } else {\n            return nullptr;\n        }\n    }\n\n    IndexEntry *getEntryByPath(const char *path) {\n        if (strlen(path) < sizeof(MO_FILENAME_PREFIX) - 1) {\n            MO_DBG_ERR(\"invalid fn\");\n            return nullptr;\n        }\n\n        const char *fn = path + sizeof(MO_FILENAME_PREFIX) - 1;\n        return getEntryByFname(fn);\n    }\n    \n    void (*onDestruct)(void*) = nullptr;\npublic:\n    FilesystemAdapterIndex(std::shared_ptr<FilesystemAdapter> filesystem, void (*onDestruct)(void*) = nullptr) : MemoryManaged(\"FilesystemIndex\"), filesystem(std::move(filesystem)), index(makeVector<IndexEntry>(\"FilesystemIndex\")), onDestruct(onDestruct) { }\n\n    ~FilesystemAdapterIndex() {\n        if (onDestruct) {\n            onDestruct(this);\n        }\n    }\n\n    int stat(const char *path, size_t *size) override {\n        if (auto file = getEntryByPath(path)) {\n            *size = file->size;\n            return 0;\n        } else {\n            return -1;\n        }\n    }\n\n    std::unique_ptr<FileAdapter> open(const char *path, const char *mode) {\n        if (!strcmp(mode, \"r\")) {\n            return filesystem->open(path, \"r\");\n        } else if (!strcmp(mode, \"w\")) {\n\n            if (strlen(path) < sizeof(MO_FILENAME_PREFIX) - 1) {\n                MO_DBG_ERR(\"invalid fn\");\n                return nullptr;\n            }\n\n            const char *fn = path + sizeof(MO_FILENAME_PREFIX) - 1;\n\n            auto file = filesystem->open(path, \"w\");\n            if (!file) {\n                return nullptr;\n            }\n\n            IndexEntry *entry = nullptr;\n            if (!(entry = getEntryByFname(fn))) {\n                index.emplace_back(fn, 0);\n                entry = &index.back();\n            }\n\n            if (!entry) {\n                MO_DBG_ERR(\"internal error\");\n                return nullptr;\n            }\n\n            entry->size = 0; //write always empties the file\n\n            return std::unique_ptr<IndexedFileAdapter>(new IndexedFileAdapter(*this, entry->fname.c_str(), std::move(file)));\n        } else {\n            MO_DBG_ERR(\"only support r or w\");\n            return nullptr;\n        }\n    }\n\n    bool remove(const char *path) override {\n        if (strlen(path) >= sizeof(MO_FILENAME_PREFIX) - 1) {\n            //valid path\n            const char *fn = path + sizeof(MO_FILENAME_PREFIX) - 1;\n            index.erase(std::remove_if(index.begin(), index.end(),\n                [fn] (const IndexEntry& el) -> bool {\n                    return el.fname.compare(fn) == 0;\n                }), index.end());\n        }\n\n        return filesystem->remove(path);\n    }\n\n    int ftw_root(std::function<int(const char *fpath)> fn) {\n        // allow fn to remove elements\n        for (size_t it = 0; it < index.size();) {\n            auto size_before = index.size();\n            auto err = fn(index[it].fname.c_str());\n            if (err) {\n                return err;\n            }\n            if (index.size() + 1 == size_before) {\n                // element removed\n                continue;\n            }\n            // normal execution\n            it++;\n        }\n\n        return 0;\n    }\n\n    bool createIndex() {\n        if (!index.empty()) {\n            return false;\n        }\n        auto ret = filesystem->ftw_root([this] (const char *fn) -> int {\n            int ret;\n            char path [MO_MAX_PATH_SIZE];\n\n            ret = snprintf(path, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"%s\", fn);\n            if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n                MO_DBG_ERR(\"fn error: %i\", ret);\n                return 0; //ignore this entry and continue ftw\n            }\n\n            size_t size;\n            ret = filesystem->stat(path, &size);\n            if (ret == 0) {\n                //add fn and size to index\n                MO_DBG_DEBUG(\"add file to index: %s (%zuB)\", fn, size);\n                index.emplace_back(fn, size);\n                return 0; //successfully added filename to index\n            } else {\n                MO_DBG_ERR(\"unexpected entry: %s\", fn);\n                return 0; //ignore this entry and continue ftw\n            }\n        });\n\n        MO_DBG_DEBUG(\"create fs index: %s, %zu entries\", ret == 0 ? \"success\" : \"failure\", index.size());\n\n        return ret == 0;\n    }\n\n    void updateFilesize(const char *fn, size_t size) {\n        if (auto entry = getEntryByFname(fn)) {\n            entry->size = size;\n            MO_DBG_DEBUG(\"update index: %s (%zuB)\", entry->fname.c_str(), entry->size);\n        }\n    }\n};\n\nIndexedFileAdapter::~IndexedFileAdapter() {\n    index.updateFilesize(fn, written);\n}\n\nstd::shared_ptr<FilesystemAdapter> decorateIndex(std::shared_ptr<FilesystemAdapter> filesystem, void (*onDestruct)(void*) = nullptr) {\n\n    auto fsIndex = std::allocate_shared<FilesystemAdapterIndex>(makeAllocator<FilesystemAdapterIndex>(\"FilesystemIndex\"), std::move(filesystem), onDestruct);\n    if (!fsIndex) {\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n\n    if (!fsIndex->createIndex()) {\n        MO_DBG_ERR(\"createIndex err\");\n        return nullptr;\n    }\n\n    return fsIndex;\n}\n\n} // namespace MicroOcpp\n\n#endif //MO_ENABLE_FILE_INDEX\n\n#if MO_USE_FILEAPI == ARDUINO_LITTLEFS || MO_USE_FILEAPI == ARDUINO_SPIFFS\n\n#if MO_USE_FILEAPI == ARDUINO_LITTLEFS\n#include <LittleFS.h>\n#include <vfs_api.h>\n#define USE_FS LittleFS\n#elif MO_USE_FILEAPI == ARDUINO_SPIFFS\n#include <FS.h>\n#define USE_FS SPIFFS\n#endif\n\nnamespace MicroOcpp {\n\nclass ArduinoFileAdapter : public FileAdapter, public MemoryManaged {\n    File file;\npublic:\n    ArduinoFileAdapter(File&& file) : MemoryManaged(\"Filesystem\"), file(file) {}\n\n    ~ArduinoFileAdapter() {\n        if (file) {\n            file.close();\n        }\n    }\n    \n    int read() override {\n        return file.read();\n    };\n    size_t read(char *buf, size_t len) override {\n        return file.readBytes(buf, len);\n    }\n    size_t write(const char *buf, size_t len) override {\n        return file.printf(\"%.*s\", len, buf);\n    }\n    size_t seek(size_t offset) override {\n        return file.seek(offset);\n    }\n};\n\nclass ArduinoFilesystemAdapter : public FilesystemAdapter, public MemoryManaged {\nprivate:\n    bool valid = false;\n    FilesystemOpt config;\n\n    void (* onDestruct)(void*) = nullptr;\npublic:\n    ArduinoFilesystemAdapter(FilesystemOpt config, void (*onDestruct)(void*) = nullptr) : MemoryManaged(\"Filesystem\"), config(config), onDestruct(onDestruct) {\n        valid = true;\n\n        if (config.mustMount()) { \n#if MO_USE_FILEAPI == ARDUINO_LITTLEFS\n            if(!USE_FS.begin(config.formatOnFail())) {\n                MO_DBG_ERR(\"Error while mounting LITTLEFS\");\n                valid = false;\n            } else {\n                MO_DBG_DEBUG(\"LittleFS mount success\");\n            }\n#elif MO_USE_FILEAPI == ARDUINO_SPIFFS\n            //ESP8266\n            SPIFFSConfig cfg;\n            cfg.setAutoFormat(config.formatOnFail());\n            SPIFFS.setConfig(cfg);\n\n            if (!SPIFFS.begin()) {\n                MO_DBG_ERR(\"Unable to initialize: unable to mount SPIFFS\");\n                valid = false;\n            }\n#else\n#error\n#endif\n        } //end if mustMount()\n\n    }\n\n    ~ArduinoFilesystemAdapter() {\n        if (config.mustMount()) {\n            USE_FS.end();\n        }\n\n        if (onDestruct) {\n            onDestruct(this);\n        }\n    }\n\n    operator bool() {return valid;}\n\n    int stat(const char *path, size_t *size) override {\n#if MO_USE_FILEAPI == ARDUINO_LITTLEFS\n        char partition_path [MO_MAX_PATH_SIZE];\n        auto ret = snprintf(partition_path, MO_MAX_PATH_SIZE, \"/littlefs%s\", path);\n        if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n            MO_DBG_ERR(\"fn error: %i\", ret);\n            return -1;\n        }\n        struct ::stat st;\n        auto status = ::stat(partition_path, &st);\n        if (status == 0) {\n            *size = st.st_size;\n        }\n        return status;\n#elif MO_USE_FILEAPI == ARDUINO_SPIFFS\n        if (!USE_FS.exists(path)) {\n            return -1;\n        }\n        File f = USE_FS.open(path, \"r\");\n        if (!f) {\n            return -1;\n        }\n\n        int status = -1;\n        if (!f.isDirectory()) {\n            *size = f.size();\n            status = 0;\n        } else {\n            //fetch more information for directory when MicroOcpp also uses them\n            //status = 0;\n        }\n\n        f.close();\n        return status;\n#else\n#error\n#endif\n    } //end stat\n\n    std::unique_ptr<FileAdapter> open(const char *fn, const char *mode) override {\n        File file = USE_FS.open(fn, mode);\n        if (file && !file.isDirectory()) {\n            MO_DBG_DEBUG(\"File open successful: %s\", fn);\n            return std::unique_ptr<FileAdapter>(new ArduinoFileAdapter(std::move(file)));\n        } else {\n            return nullptr;\n        }\n    }\n    bool remove(const char *fn) override {\n        return USE_FS.remove(fn);\n    };\n    int ftw_root(std::function<int(const char *fpath)> fn) override {\n#if MO_USE_FILEAPI == ARDUINO_LITTLEFS\n        auto dir = USE_FS.open(MO_FILENAME_PREFIX);\n        if (!dir) {\n            MO_DBG_ERR(\"cannot open root directory: \" MO_FILENAME_PREFIX);\n            return -1;\n        }\n\n        int err = 0;\n        while (auto entry = dir.openNextFile()) {\n\n            char fname [MO_MAX_PATH_SIZE];\n            auto ret = snprintf(fname, MO_MAX_PATH_SIZE, \"%s\", entry.name());\n            entry.close();\n\n            if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n                MO_DBG_ERR(\"fn error: %i\", ret);\n                return -1;\n            }\n\n            err = fn(fname);\n            if (err) {\n                break;\n            }\n        }\n        return err;\n#elif MO_USE_FILEAPI == ARDUINO_SPIFFS\n        auto dir = USE_FS.openDir(MO_FILENAME_PREFIX);\n        int err = 0;\n        while (dir.next()) {\n            auto fname = dir.fileName();\n            if (fname.c_str()) {\n                err = fn(fname.c_str() + strlen(MO_FILENAME_PREFIX));\n            } else {\n                MO_DBG_ERR(\"fs error\");\n                err = -1;\n            }\n            if (err) {\n                break;\n            }\n        }\n        return err;\n#else\n#error\n#endif\n    }\n};\n\nstd::weak_ptr<FilesystemAdapter> filesystemCache;\n\nvoid resetFilesystemCache(void*) {\n    filesystemCache.reset();\n}\n\nstd::shared_ptr<FilesystemAdapter> makeDefaultFilesystemAdapter(FilesystemOpt config) {\n\n    if (auto cached = filesystemCache.lock()) {\n        return cached;\n    }\n\n    if (!config.accessAllowed()) {\n        MO_DBG_DEBUG(\"Access to Arduino FS not allowed by config\");\n        return nullptr;\n    }\n\n    auto fs_concrete = new ArduinoFilesystemAdapter(config, resetFilesystemCache);\n    auto fs = std::shared_ptr<FilesystemAdapter>(fs_concrete, std::default_delete<FilesystemAdapter>(), makeAllocator<FilesystemAdapter>(\"Filesystem\"));\n\n#if MO_ENABLE_FILE_INDEX\n    fs = decorateIndex(fs, resetFilesystemCache);\n#endif // MO_ENABLE_FILE_INDEX\n\n    filesystemCache = fs;\n\n    if (*fs_concrete) {\n        return fs;\n    } else {\n        return nullptr;\n    }\n}\n\n} //end namespace MicroOcpp\n\n#elif MO_USE_FILEAPI == ESPIDF_SPIFFS\n\n#include <sys/stat.h>\n#include <dirent.h>\n#include \"esp_spiffs.h\"\n\n#ifndef MO_PARTITION_LABEL\n#define MO_PARTITION_LABEL \"mo\"\n#endif\n\nnamespace MicroOcpp {\n\nclass EspIdfFileAdapter : public FileAdapter, public MemoryManaged {\n    FILE *file {nullptr};\npublic:\n    EspIdfFileAdapter(FILE *file) : MemoryManaged(\"Filesystem\"), file(file) {}\n\n    ~EspIdfFileAdapter() {\n        fclose(file);\n    }\n\n    size_t read(char *buf, size_t len) override {\n        return fread(buf, 1, len, file);\n    }\n\n    size_t write(const char *buf, size_t len) override {\n        return fwrite(buf, 1, len, file);\n    }\n\n    size_t seek(size_t offset) override {\n        return fseek(file, offset, SEEK_SET);\n    }\n\n    int read() override {\n        return fgetc(file);\n    }\n};\n\nclass EspIdfFilesystemAdapter : public FilesystemAdapter, public MemoryManaged {\npublic:\n    FilesystemOpt config;\n\n    void (* onDestruct)(void*) = nullptr;\npublic:\n    EspIdfFilesystemAdapter(FilesystemOpt config, void (* onDestruct)(void*) = nullptr) : MemoryManaged(\"Filesystem\"), config(config), onDestruct(onDestruct) { }\n\n    ~EspIdfFilesystemAdapter() {\n        if (config.mustMount()) {\n            esp_vfs_spiffs_unregister(MO_PARTITION_LABEL);\n            MO_DBG_DEBUG(\"SPIFFS unmounted\");\n        }\n\n        if (onDestruct) {\n            onDestruct(this);\n        }\n    }\n\n    int stat(const char *path, size_t *size) override {\n        struct ::stat st;\n        auto ret = ::stat(path, &st);\n        if (ret == 0) {\n            *size = st.st_size;\n        }\n        return ret;\n    }\n\n    std::unique_ptr<FileAdapter> open(const char *fn, const char *mode) override {\n        auto file = fopen(fn, mode);\n        if (file) {\n            return std::unique_ptr<FileAdapter>(new EspIdfFileAdapter(std::move(file)));\n        } else {\n            MO_DBG_DEBUG(\"Failed to open file path %s\", fn);\n            return nullptr;\n        }\n    }\n\n    bool remove(const char *fn) override {\n        return unlink(fn) == 0;\n    }\n\n    int ftw_root(std::function<int(const char *fpath)> fn) override {\n        //open MO root directory\n        char dname [MO_MAX_PATH_SIZE];\n        auto dlen = snprintf(dname, MO_MAX_PATH_SIZE, \"%s\", MO_FILENAME_PREFIX);\n        if (dlen < 0 || dlen >= MO_MAX_PATH_SIZE) {\n            MO_DBG_ERR(\"fn error: %i\", dlen);\n            return -1;\n        }\n\n        // trim trailing '/' if not root directory\n        if (dlen >= 2 && dname[dlen - 1] == '/') {\n            dname[dlen - 1] = '\\0';\n        }\n\n        auto dir = opendir(dname);\n        if (!dir) {\n            MO_DBG_ERR(\"cannot open root directory: %s\", dname);\n            return -1;\n        }\n\n        int err = 0;\n        while (auto entry = readdir(dir)) {\n            err = fn(entry->d_name);\n            if (err) {\n                break;\n            }\n        }\n\n        closedir(dir);\n        return err;\n    }\n};\n\nstd::weak_ptr<FilesystemAdapter> filesystemCache;\n\nvoid resetFilesystemCache(void*) {\n    filesystemCache.reset();\n}\n\nstd::shared_ptr<FilesystemAdapter> makeDefaultFilesystemAdapter(FilesystemOpt config) {\n\n    if (auto cached = filesystemCache.lock()) {\n        return cached;\n    }\n\n    if (!config.accessAllowed()) {\n        MO_DBG_DEBUG(\"Access to ESP-IDF SPIFFS not allowed by config\");\n        return nullptr;\n    }\n\n    bool mounted = true;\n\n    if (config.mustMount()) {\n        mounted = false;\n\n        char fnpref [MO_MAX_PATH_SIZE];\n        auto fnpref_len = snprintf(fnpref, MO_MAX_PATH_SIZE, \"%s\", MO_FILENAME_PREFIX);\n        if (fnpref_len < 0 || fnpref_len >= MO_MAX_PATH_SIZE) {\n            MO_DBG_ERR(\"MO_FILENAME_PREFIX error %i\", fnpref_len);\n            return nullptr;\n        } else if (fnpref_len <= 2) { //shortest possible prefix: \"/p/\", i.e. length = 3\n            MO_DBG_ERR(\"MO_FILENAME_PREFIX cannot be root on ESP-IDF (working example: \\\"/mo_store/\\\")\");\n            return nullptr;\n        }\n\n        // trim trailing '/'\n        if (fnpref[fnpref_len - 1] == '/') {\n            fnpref[fnpref_len - 1] = '\\0';\n        }\n\n        esp_vfs_spiffs_conf_t conf = {\n            .base_path = fnpref,\n            .partition_label = MO_PARTITION_LABEL,\n            .max_files = 5,\n            .format_if_mount_failed = config.formatOnFail()\n        };\n\n        esp_err_t ret = esp_vfs_spiffs_register(&conf);\n\n        if (ret == ESP_OK) {\n            mounted = true;\n            MO_DBG_DEBUG(\"SPIFFS mounted\");\n        } else {\n            if (ret == ESP_FAIL) {\n                MO_DBG_ERR(\"Failed to mount or format filesystem\");\n            } else if (ret == ESP_ERR_NOT_FOUND) {\n                MO_DBG_ERR(\"Failed to find SPIFFS partition\");\n            } else {\n                MO_DBG_ERR(\"Failed to initialize SPIFFS (%s)\", esp_err_to_name(ret));\n            }\n        }\n    }\n\n    if (mounted) {\n        auto fs = std::shared_ptr<FilesystemAdapter>(new EspIdfFilesystemAdapter(config, resetFilesystemCache), std::default_delete<FilesystemAdapter>(), makeAllocator<FilesystemAdapter>(\"Filesystem\"));\n\n#if MO_ENABLE_FILE_INDEX\n        fs = decorateIndex(fs, resetFilesystemCache);\n#endif // MO_ENABLE_FILE_INDEX\n\n        filesystemCache = fs;\n        return fs;\n    } else {\n        return nullptr;\n    }\n}\n\n} //end namespace MicroOcpp\n\n#elif MO_USE_FILEAPI == POSIX_FILEAPI\n\n#include <cstdio>\n#include <sys/stat.h>\n#include <dirent.h>\n\nnamespace MicroOcpp {\n\nclass PosixFileAdapter : public FileAdapter, public MemoryManaged {\n    FILE *file {nullptr};\npublic:\n    PosixFileAdapter(FILE *file) : MemoryManaged(\"Filesystem\"), file(file) {}\n\n    ~PosixFileAdapter() {\n        fclose(file);\n    }\n\n    size_t read(char *buf, size_t len) override {\n        return fread(buf, 1, len, file);\n    }\n\n    size_t write(const char *buf, size_t len) override {\n        return fwrite(buf, 1, len, file);\n    }\n\n    size_t seek(size_t offset) override {\n        return fseek(file, offset, SEEK_SET);\n    }\n\n    int read() override {\n        return fgetc(file);\n    }\n};\n\nclass PosixFilesystemAdapter : public FilesystemAdapter, public MemoryManaged {\npublic:\n    FilesystemOpt config;\n\n    void (* onDestruct)(void*) = nullptr;\npublic:\n    PosixFilesystemAdapter(FilesystemOpt config, void (* onDestruct)(void*) = nullptr) : MemoryManaged(\"Filesystem\"), config(config), onDestruct(onDestruct) { }\n\n    ~PosixFilesystemAdapter() {\n        if (onDestruct) {\n            onDestruct(this);\n        }\n    }\n\n    int stat(const char *path, size_t *size) override {\n        struct ::stat st;\n        auto ret = ::stat(path, &st);\n        if (ret == 0) {\n            *size = st.st_size;\n        }\n        return ret;\n    }\n\n    std::unique_ptr<FileAdapter> open(const char *fn, const char *mode) override {\n        auto file = fopen(fn, mode);\n        if (file) {\n            return std::unique_ptr<FileAdapter>(new PosixFileAdapter(std::move(file)));\n        } else {\n            MO_DBG_DEBUG(\"Failed to open file path %s\", fn);\n            return nullptr;\n        }\n    }\n\n    bool remove(const char *fn) override {\n        return ::remove(fn) == 0;\n    }\n\n    int ftw_root(std::function<int(const char *fpath)> fn) override {\n        auto dir = opendir(MO_FILENAME_PREFIX); // use c_str() to convert the path string to a C-style string\n        if (!dir) {\n            MO_DBG_ERR(\"cannot open root directory: \" MO_FILENAME_PREFIX);\n            return -1;\n        }\n\n        int err = 0;\n        while (auto entry = readdir(dir)) {\n            if (!strcmp(entry->d_name, \".\") || !strcmp(entry->d_name, \"..\")) {\n                continue; //files . and .. are specific to desktop systems and rarely appear on microcontroller filesystems. Filter them\n            }\n            err = fn(entry->d_name);\n            if (err) {\n                break;\n            }\n        }\n\n        closedir(dir);\n        return err;\n    }\n};\n\nstd::weak_ptr<FilesystemAdapter> filesystemCache;\n\nvoid resetFilesystemCache(void*) {\n    filesystemCache.reset();\n}\n\nstd::shared_ptr<FilesystemAdapter> makeDefaultFilesystemAdapter(FilesystemOpt config) {\n\n    if (auto cached = filesystemCache.lock()) {\n        return cached;\n    }\n\n    if (!config.accessAllowed()) {\n        MO_DBG_DEBUG(\"Access to FS not allowed by config\");\n        return nullptr;\n    }\n\n    if (config.mustMount()) {\n        MO_DBG_DEBUG(\"Skip mounting on UNIX host\");\n    }\n\n    auto fs = std::shared_ptr<FilesystemAdapter>(new PosixFilesystemAdapter(config, resetFilesystemCache), std::default_delete<FilesystemAdapter>(), makeAllocator<FilesystemAdapter>(\"Filesystem\"));\n\n#if MO_ENABLE_FILE_INDEX\n    fs = decorateIndex(fs, resetFilesystemCache);\n#endif // MO_ENABLE_FILE_INDEX\n\n    filesystemCache = fs;\n    return fs;\n}\n\n} //end namespace MicroOcpp\n\n#else //filesystem disabled\n\nnamespace MicroOcpp {\n\nstd::shared_ptr<FilesystemAdapter> makeDefaultFilesystemAdapter(FilesystemOpt config) {\n    return nullptr;\n}\n\n} //end namespace MicroOcpp\n\n#endif //switch-case MO_USE_FILEAPI\n"
  },
  {
    "path": "src/MicroOcpp/Core/FilesystemAdapter.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_FILESYSTEMADAPTER_H\n#define MO_FILESYSTEMADAPTER_H\n\n#include <memory>\n#include <functional>\n\n#include <MicroOcpp/Platform.h>\n#include <MicroOcpp/Core/ConfigurationOptions.h>\n\n#define DISABLE_FS       0\n#define ARDUINO_LITTLEFS 1\n#define ARDUINO_SPIFFS   2\n#define ESPIDF_SPIFFS    3\n#define POSIX_FILEAPI    4\n\n// choose FileAPI if not given by build flag; assume usage with Arduino if no build flags are present\n#ifndef MO_USE_FILEAPI\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO\n#if defined(ESP32)\n#define MO_USE_FILEAPI ARDUINO_LITTLEFS\n#else\n#define MO_USE_FILEAPI ARDUINO_SPIFFS\n#endif\n#elif MO_PLATFORM == MO_PLATFORM_ESPIDF\n#define MO_USE_FILEAPI ESPIDF_SPIFFS\n#elif MO_PLATFORM == MO_PLATFORM_UNIX\n#define MO_USE_FILEAPI POSIX_FILEAPI\n#else\n#define MO_USE_FILEAPI DISABLE_FS\n#endif //switch-case MO_PLATFORM\n#endif //ndef MO_USE_FILEAPI\n\n#ifndef MO_FILENAME_PREFIX\n#if MO_USE_FILEAPI == ESPIDF_SPIFFS\n#define MO_FILENAME_PREFIX \"/mo_store/\"\n#else\n#define MO_FILENAME_PREFIX \"/\"\n#endif\n#endif\n\n// set default max path size parameters\n#ifndef MO_MAX_PATH_SIZE\n#if MO_USE_FILEAPI == POSIX_FILEAPI\n#define MO_MAX_PATH_SIZE 128\n#else\n#define MO_MAX_PATH_SIZE 30\n#endif\n#endif\n\n#ifndef MO_ENABLE_FILE_INDEX\n#define MO_ENABLE_FILE_INDEX 0\n#endif\n\nnamespace MicroOcpp {\n\nclass FileAdapter {\npublic:\n    virtual ~FileAdapter() = default;\n    virtual size_t read(char *buf, size_t len) = 0;\n    virtual size_t write(const char *buf, size_t len) = 0;\n    virtual size_t seek(size_t offset) = 0;\n\n    virtual int read() = 0;\n};\n\nclass FilesystemAdapter {\npublic:\n    virtual ~FilesystemAdapter() = default;\n    virtual int stat(const char *path, size_t *size) = 0;\n    virtual std::unique_ptr<FileAdapter> open(const char *fn, const char *mode) = 0;\n    virtual bool remove(const char *fn) = 0;\n    virtual int ftw_root(std::function<int(const char *fpath)> fn) = 0; //enumerate the files in the mo_store root folder\n};\n\n/*\n * Platform specific implementation. Currently supported:\n *     - Arduino LittleFs\n *     - Arduino SPIFFS\n *     - ESP-IDF SPIFFS\n *     - POSIX-like API (tested on Ubuntu 20.04)\n * \n * You can add support for other file systems by passing a custom adapter to mocpp_initialize(...)\n * \n * Returns null if platform is not supported or Filesystem is disabled\n */\nstd::shared_ptr<FilesystemAdapter> makeDefaultFilesystemAdapter(FilesystemOpt config);\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/FilesystemUtils.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/ConfigurationOptions.h> //FilesystemOpt\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nstd::unique_ptr<JsonDoc> FilesystemUtils::loadJson(std::shared_ptr<FilesystemAdapter> filesystem, const char *fn, const char *memoryTag) {\n    if (!filesystem || !fn || *fn == '\\0') {\n        MO_DBG_ERR(\"Format error\");\n        return nullptr;\n    }\n\n    if (strnlen(fn, MO_MAX_PATH_SIZE) >= MO_MAX_PATH_SIZE) {\n        MO_DBG_ERR(\"Fn too long: %.*s\", MO_MAX_PATH_SIZE, fn);\n        return nullptr;\n    }\n    \n    size_t fsize = 0;\n    if (filesystem->stat(fn, &fsize) != 0) {\n        MO_DBG_DEBUG(\"File does not exist: %s\", fn);\n        return nullptr;\n    }\n\n    if (fsize < 2) {\n        MO_DBG_ERR(\"File too small for JSON, collect %s\", fn);\n        filesystem->remove(fn);\n        return nullptr;\n    }\n\n    auto file = filesystem->open(fn, \"r\");\n    if (!file) {\n        MO_DBG_ERR(\"Could not open file %s\", fn);\n        return nullptr;\n    }\n\n    size_t capacity_init = (3 * fsize) / 2;\n\n    //capacity = ceil capacity_init to the next power of two; should be at least 128\n\n    size_t capacity = 128;\n    while (capacity < capacity_init && capacity < MO_MAX_JSON_CAPACITY) {\n        capacity *= 2;\n    }\n    if (capacity > MO_MAX_JSON_CAPACITY) {\n        capacity = MO_MAX_JSON_CAPACITY;\n    }\n    \n    std::unique_ptr<JsonDoc> doc;\n    DeserializationError err = DeserializationError::NoMemory;\n    ArduinoJsonFileAdapter fileReader {file.get()};\n\n    while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) {\n\n        doc = makeJsonDoc(memoryTag, capacity);\n        err = deserializeJson(*doc, fileReader);\n\n        capacity *= 2;\n\n        file->seek(0); //rewind file to beginning\n    }\n\n    if (err) {\n        MO_DBG_ERR(\"Error deserializing file %s: %s\", fn, err.c_str());\n        //skip this file\n        return nullptr;\n    }\n\n    MO_DBG_DEBUG(\"Loaded JSON file: %s\", fn);\n\n    return doc;\n}\n\nbool FilesystemUtils::storeJson(std::shared_ptr<FilesystemAdapter> filesystem, const char *fn, const JsonDoc& doc) {\n    if (!filesystem || !fn || *fn == '\\0') {\n        MO_DBG_ERR(\"Format error\");\n        return false;\n    }\n\n    if (strnlen(fn, MO_MAX_PATH_SIZE) >= MO_MAX_PATH_SIZE) {\n        MO_DBG_ERR(\"Fn too long: %.*s\", MO_MAX_PATH_SIZE, fn);\n        return false;\n    }\n\n    if (doc.isNull() || doc.overflowed()) {\n        MO_DBG_ERR(\"Invalid JSON %s\", fn);\n        return false;\n    }\n\n    auto file = filesystem->open(fn, \"w\");\n    if (!file) {\n        MO_DBG_ERR(\"Could not open file %s\", fn);\n        return false;\n    }\n\n    ArduinoJsonFileAdapter fileWriter {file.get()};\n\n    size_t written = serializeJson(doc, fileWriter);\n\n    if (written < 2) {\n        MO_DBG_ERR(\"Error writing file %s\", fn);\n        size_t file_size = 0;\n        if (filesystem->stat(fn, &file_size) == 0) {\n            MO_DBG_DEBUG(\"Collect invalid file %s\", fn);\n            filesystem->remove(fn);\n        }\n        return false;\n    }\n\n    MO_DBG_DEBUG(\"Wrote JSON file: %s\", fn);\n    return true;\n}\n\nbool FilesystemUtils::remove_if(std::shared_ptr<FilesystemAdapter> filesystem, std::function<bool(const char*)> pred) {\n    auto ret = filesystem->ftw_root([filesystem, pred] (const char *fpath) {\n        if (pred(fpath)) {\n\n            char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n            auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"%s\", fpath);\n            if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n                MO_DBG_ERR(\"fn error: %i\", ret);\n                return -1;\n            }\n\n            filesystem->remove(fn);\n            //no error handling - just skip failed file\n        }\n        return 0;\n    });\n\n    if (ret != 0) {\n        MO_DBG_ERR(\"ftw_root: %i\", ret);\n    }\n\n    return ret == 0;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Core/FilesystemUtils.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_FILESYSTEMUTILS_H\n#define MO_FILESYSTEMUTILS_H\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <ArduinoJson.h>\n#include <memory>\n\nnamespace MicroOcpp {\n\nclass ArduinoJsonFileAdapter {\nprivate:\n    FileAdapter *file;\npublic:\n    ArduinoJsonFileAdapter(FileAdapter *file) : file(file) { }\n\n    size_t readBytes(char *buf, size_t len) {\n        return file->read(buf, len);\n    }\n\n    int read() {\n        return file->read();\n    }\n\n    size_t write(const uint8_t *buf, size_t len) {\n        return file->write((const char*) buf, len);\n    }\n\n    size_t write(uint8_t c) {\n        return file->write((const char*) &c, 1);\n    }\n};\n\nnamespace FilesystemUtils {\n\nstd::unique_ptr<JsonDoc> loadJson(std::shared_ptr<FilesystemAdapter> filesystem, const char *fn, const char *memoryTag = nullptr);\nbool storeJson(std::shared_ptr<FilesystemAdapter> filesystem, const char *fn, const JsonDoc& doc);\n\nbool remove_if(std::shared_ptr<FilesystemAdapter> filesystem, std::function<bool(const char*)> pred);\n\n}\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/Ftp.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_FTP_H\n#define MO_FTP_H\n\n#include <stddef.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef enum {\n    MO_FtpCloseReason_Undefined,\n    MO_FtpCloseReason_Success,\n    MO_FtpCloseReason_Failure\n}   MO_FtpCloseReason;\n\ntypedef struct ocpp_ftp_download {\n    void *user_data; //set this at your choice. MO passes it back to the functions below\n\n    void (*loop)(void *user_data);\n    void (*is_active)(void *user_data);\n} ocpp_ftp_download;\n\ntypedef struct ocpp_ftp_upload {\n    void *user_data; //set this at your choice. MO passes it back to the functions below\n\n    void (*loop)(void *user_data);\n    void (*is_active)(void *user_data);\n} ocpp_ftp_upload;\n\ntypedef struct ocpp_ftp_client {\n    void *user_data; //set this at your choice. MO passes it back to the functions below\n\n    void (*loop)(void *user_data);\n\n    ocpp_ftp_download* (*get_file)(void *user_data,\n        const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename\n        size_t (*file_writer)(void *mo_data, unsigned char *data, size_t len),\n        void (*on_close)(void *mo_data, MO_FtpCloseReason reason),\n        void *mo_data,\n        const char *ca_cert); // nullptr to disable cert check; will be ignored for non-TLS connections\n\n    void (*get_file_free)(void *user_data, ocpp_ftp_download*);\n\n    ocpp_ftp_upload* (*post_file)(void *user_data,\n        const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename\n        size_t (*file_reader)(void *mo_data, unsigned char *buf, size_t bufsize),\n        void (*on_close)(void *mo_data, MO_FtpCloseReason reason),\n        void *mo_data,\n        const char *ca_cert); // nullptr to disable cert check; will be ignored for non-TLS connections\n\n    void (*post_file_free)(void *user_data, ocpp_ftp_upload*);\n} ocpp_ftp_client;\n\n#ifdef __cplusplus\n} //extern \"C\"\n\n#include <memory>\n#include <functional>\n\nnamespace MicroOcpp {\n\nclass FtpDownload {\npublic:\n    virtual ~FtpDownload() = default;\n    virtual void loop() = 0;\n    virtual bool isActive() = 0;\n};\n\nclass FtpUpload {\npublic:\n    virtual ~FtpUpload() = default;\n    virtual void loop() = 0;\n    virtual bool isActive() = 0;\n};\n\nclass FtpClient {\npublic:\n    virtual ~FtpClient() = default;\n\n    virtual std::unique_ptr<FtpDownload> getFile(\n                const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename\n                std::function<size_t(unsigned char *data, size_t len)> fileWriter,\n                std::function<void(MO_FtpCloseReason reason)> onClose,\n                const char *ca_cert = nullptr) = 0; // nullptr to disable cert check; will be ignored for non-TLS connections\n\n    virtual std::unique_ptr<FtpUpload> postFile(\n                const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename\n                std::function<size_t(unsigned char *out, size_t buffsize)> fileReader, //write at most buffsize bytes into out-buffer. Return number of bytes written\n                std::function<void(MO_FtpCloseReason reason)> onClose,\n                const char *ca_cert = nullptr) = 0; // nullptr to disable cert check; will be ignored for non-TLS connections\n};\n\n} // namespace MicroOcpp\n#endif //def __cplusplus\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/FtpMbedTLS.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/FtpMbedTLS.h>\n\n#if MO_ENABLE_MBEDTLS\n\n#include <string.h>\n#include <functional>\n\n#include \"mbedtls/net_sockets.h\"\n#include \"mbedtls/ssl.h\"\n#include \"mbedtls/entropy.h\"\n#include \"mbedtls/ctr_drbg.h\"\n#include \"mbedtls/x509.h\"\n#include \"mbedtls/error.h\"\n\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Debug.h>\n\nnamespace MicroOcpp {\n\nclass FtpTransferMbedTLS : public FtpUpload, public FtpDownload, public MemoryManaged {\nprivate:\n    //MbedTLS common\n    mbedtls_entropy_context entropy;\n    mbedtls_ctr_drbg_context ctr_drbg;\n    mbedtls_ssl_config conf;\n    mbedtls_x509_crt cacert;\n    mbedtls_x509_crt clicert;\n    mbedtls_pk_context pkey;\n    const char *ca_cert = nullptr;\n    const char *client_cert = nullptr;\n    const char *client_key = nullptr;\n    bool isSecure = false; //tls policy\n\n    //control connection specific\n    mbedtls_net_context ctrl_fd;\n    mbedtls_ssl_context ctrl_ssl;\n    bool ctrl_opened = false;\n    bool ctrl_ssl_established = false;\n\n    //data connection specific\n    mbedtls_net_context data_fd;\n    mbedtls_ssl_context data_ssl;\n    bool data_opened = false;\n    bool data_ssl_established = false;\n    bool data_conn_accepted = false; //Server sent okay to upload / download data\n\n    //FTP URL\n    String user;\n    String pass;\n    String ctrl_host;\n    String ctrl_port;\n    String dir;\n    String fname;\n\n    String data_host;\n    String data_port;\n\n    bool read_url_ctrl(const char *ftp_url);\n    bool read_url_data(const char *data_url);\n    \n    std::function<size_t(unsigned char *data, size_t len)> fileWriter;\n    std::function<size_t(unsigned char *out, size_t bufsize)> fileReader;\n    std::function<void(MO_FtpCloseReason)> onClose;\n\n    enum class Method {\n        Retrieve,  //download file\n        Store,     //upload file\n        UNDEFINED\n    };\n    Method method = Method::UNDEFINED;\n\n    int setup_tls();\n    int connect(mbedtls_net_context& fd, mbedtls_ssl_context& ssl, const char *server_name, const char *server_port);\n    int connect_ctrl();\n    int connect_data();\n    void close_ctrl();\n    void close_data(MO_FtpCloseReason reason);\n\n    int handshake_tls();\n\n    void send_cmd(const char *cmd, const char *arg = nullptr, bool disable_tls_policy = false);\n\n    void process_ctrl();\n    void process_data();\n\n    unsigned char *data_buf = nullptr;\n    size_t data_buf_size = 4096;\n    size_t data_buf_avail = 0;\n    size_t data_buf_offs = 0;\n\npublic:\n    FtpTransferMbedTLS(bool tls_only = false, const char *client_cert = nullptr, const char *client_key = nullptr);\n    ~FtpTransferMbedTLS();\n\n    void loop() override;\n    \n    bool isActive() override;\n\n    bool getFile(const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename\n            std::function<size_t(unsigned char *data, size_t len)> fileWriter,\n            std::function<void(MO_FtpCloseReason)> onClose,\n            const char *ca_cert = nullptr); // nullptr to disable cert check; will be ignored for non-TLS connections\n\n    bool postFile(const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename\n            std::function<size_t(unsigned char *out, size_t buffsize)> fileReader, //write at most buffsize bytes into out-buffer. Return number of bytes written\n            std::function<void(MO_FtpCloseReason)> onClose,\n            const char *ca_cert = nullptr); // nullptr to disable cert check; will be ignored for non-TLS connections\n};\n\nclass FtpClientMbedTLS : public FtpClient, public MemoryManaged {\nprivate:\n    const char *client_cert = nullptr;\n    const char *client_key = nullptr;\n    bool tls_only = false; //tls policy\npublic:\n\n    FtpClientMbedTLS(bool tls_only = false, const char *client_cert = nullptr, const char *client_key = nullptr);\n\n    std::unique_ptr<FtpDownload> getFile(const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename\n            std::function<size_t(unsigned char *data, size_t len)> fileWriter,\n            std::function<void(MO_FtpCloseReason)> onClose,\n            const char *ca_cert = nullptr) override; // nullptr to disable cert check; will be ignored for non-TLS connections\n\n    std::unique_ptr<FtpUpload> postFile(const char *ftp_url, // ftp[s]://[user[:pass]@]host[:port][/directory]/filename\n            std::function<size_t(unsigned char *out, size_t buffsize)> fileReader, //write at most buffsize bytes into out-buffer. Return number of bytes written\n            std::function<void(MO_FtpCloseReason)> onClose,\n            const char *ca_cert = nullptr) override; // nullptr to disable cert check; will be ignored for non-TLS connections\n};\n\nstd::unique_ptr<FtpClient> makeFtpClientMbedTLS(bool tls_only, const char *client_cert, const char *client_key) {\n    return std::unique_ptr<FtpClient>(new FtpClientMbedTLS(tls_only, client_cert, client_key));\n}\n\nvoid mo_mbedtls_log(void *user, int level, const char *file, int line, const char *str) {\n\n    /*\n     * MbedTLS debug level documented in mbedtls/debug.h:\n     *     - 0 No debug\n     *     - 1 Error\n     *     - 2 State change\n     *     - 3 Informational\n     *     - 4 Verbose\n     * \n     * To change the debug level, use the build flag MO_DBG_LEVEL_MBEDTLS accordingly\n     */\n    const char *lstr = \"\";\n    if (level <= 1) {\n        lstr = \"ERROR\";\n    } else if (level <= 3) {\n        lstr = \"debug\";\n    } else {\n        lstr = \"verbose\";\n    }\n\n    MO_CONSOLE_PRINTF(\"[MO] %s (%s:%i): %s\\n\", lstr, file, line, str);\n}\n\n/*\n * FTP implementation\n */\n\nFtpTransferMbedTLS::FtpTransferMbedTLS(bool tls_only, const char *client_cert, const char *client_key) : \n        MemoryManaged(\"FTP.TransferMbedTLS\"),\n        client_cert(client_cert),\n        client_key(client_key),\n        isSecure(tls_only),\n        user(makeString(getMemoryTag())),\n        pass(makeString(getMemoryTag())),\n        ctrl_host(makeString(getMemoryTag())),\n        ctrl_port(makeString(getMemoryTag())),\n        dir(makeString(getMemoryTag())),\n        fname(makeString(getMemoryTag())),\n        data_host(makeString(getMemoryTag())),\n        data_port(makeString(getMemoryTag())) {\n\n    mbedtls_net_init(&ctrl_fd);\n    mbedtls_ssl_init(&ctrl_ssl);\n    mbedtls_net_init(&data_fd);\n    mbedtls_ssl_init(&data_ssl);\n    mbedtls_ssl_config_init(&conf);\n    mbedtls_x509_crt_init(&cacert);\n    mbedtls_x509_crt_init(&clicert);\n    mbedtls_pk_init(&pkey);\n    mbedtls_ctr_drbg_init(&ctr_drbg);\n    mbedtls_entropy_init(&entropy);\n}\n\nFtpTransferMbedTLS::~FtpTransferMbedTLS() {\n    if (onClose) {\n        onClose(MO_FtpCloseReason_Failure); //data connection not closed properly\n        onClose = nullptr;\n    }\n    MO_FREE(data_buf);\n    mbedtls_x509_crt_free(&clicert);\n    mbedtls_x509_crt_free(&cacert);\n    mbedtls_pk_free(&pkey);\n    mbedtls_ssl_config_free(&conf);\n    mbedtls_ctr_drbg_free(&ctr_drbg);\n    mbedtls_entropy_free(&entropy);\n    mbedtls_net_free(&ctrl_fd);\n    mbedtls_ssl_free(&ctrl_ssl);\n    mbedtls_net_free(&data_fd);\n    mbedtls_ssl_free(&data_ssl);\n}\n\nint FtpTransferMbedTLS::setup_tls() {\n\n    if (auto ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,\n                                     (const unsigned char*) __FILE__,\n                                     strlen(__FILE__)) != 0) {\n        MO_DBG_ERR(\"mbedtls_ctr_drbg_seed: %i\", ret);\n        return ret;\n    }\n\n    if (ca_cert) {\n        if (auto ret = mbedtls_x509_crt_parse(&cacert, (const unsigned char *) ca_cert,\n                                    strlen(ca_cert)) < 0) {\n            MO_DBG_ERR(\"mbedtls_x509_crt_parse(ca_cert): %i\", ret);\n            return ret;\n        }\n    }\n\n    if (client_cert) {\n        if (auto ret = mbedtls_x509_crt_parse(&clicert, (const unsigned char *) client_cert,\n                                    strlen(client_cert))) {\n            MO_DBG_ERR(\"mbedtls_x509_crt_parse(client_cert): %i\", ret);\n            return ret;\n        }\n    }\n\n    if (client_key) {\n        if (auto ret = mbedtls_pk_parse_key(&pkey,\n                                    (const unsigned char *) client_key,\n                                    strlen(client_key),\n                                    NULL,\n                                    0)) {\n            MO_DBG_ERR(\"mbedtls_pk_parse_key: %i\", ret);\n            return ret;\n        }\n    }\n\n    if (auto ret = mbedtls_ssl_config_defaults(&conf,\n                                           MBEDTLS_SSL_IS_CLIENT,\n                                           MBEDTLS_SSL_TRANSPORT_STREAM,\n                                           MBEDTLS_SSL_PRESET_DEFAULT) != 0) {\n        MO_DBG_ERR(\"mbedtls_ssl_config_defaults: %i\", ret);\n        return ret;\n    }\n\n    mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL); //certificate check result manually handled for now\n\n    mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);\n    mbedtls_ssl_conf_dbg(&conf, mo_mbedtls_log, NULL);\n\n    if (ca_cert) {\n        mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);\n    }\n\n    if (client_cert || client_key) {\n        if (auto ret = mbedtls_ssl_conf_own_cert(&conf, &clicert, &pkey) != 0) {\n            MO_DBG_ERR(\"mbedtls_ssl_conf_own_cert: %i\", ret);\n            return ret;\n        }\n    }\n\n    return 0; //success\n}\n\nint FtpTransferMbedTLS::connect(mbedtls_net_context& fd, mbedtls_ssl_context& ssl, const char *server_name, const char *server_port) {\n\n    if (auto ret = mbedtls_net_connect(&fd, server_name, server_port, MBEDTLS_NET_PROTO_TCP) != 0) {\n        MO_DBG_ERR(\"mbedtls_net_connect: %i\", ret);\n        return ret;\n    }\n\n    if (auto ret = mbedtls_net_set_nonblock(&fd)) {\n        MO_DBG_ERR(\"mbedtls_net_set_nonblock: %i\", ret);\n        return ret;\n    }\n\n    if (auto ret = mbedtls_ssl_setup(&ssl, &conf) != 0) {\n        MO_DBG_ERR(\"mbedtls_ssl_setup: %i\", ret);\n        return ret;\n    }\n\n    if (auto ret = mbedtls_ssl_set_hostname(&ssl, server_name) != 0) {\n        MO_DBG_ERR(\"mbedtls_ssl_set_hostname: %i\", ret);\n        return ret;\n    }\n\n    mbedtls_ssl_set_bio(&ssl, &fd, mbedtls_net_send, mbedtls_net_recv, NULL);\n\n    return 0; //success\n}\n\nint FtpTransferMbedTLS::connect_ctrl() {\n    if (auto ret = connect(ctrl_fd, ctrl_ssl, ctrl_host.c_str(), ctrl_port.c_str())) {\n        MO_DBG_ERR(\"connect: %i\", ret);\n        return ret;\n    }\n\n    ctrl_opened = true;\n\n    //handshake will be done later during STARTTLS procedure\n\n    return 0; //success\n}\n\nint FtpTransferMbedTLS::connect_data() {\n    if (auto ret = connect(data_fd, data_ssl, data_host.c_str(), data_port.c_str())) {\n        MO_DBG_ERR(\"connect: %i\", ret);\n        return ret;\n    }\n\n    data_opened = true;\n\n    if (isSecure) {\n        //reuse SSL session of ctrl conn\n\n        if (auto ret = mbedtls_ssl_set_session(&data_ssl, \n                    mbedtls_ssl_get_session_pointer(&ctrl_ssl))) {\n            MO_DBG_ERR(\"session reuse failure: %i\", ret);\n            return ret;\n        }\n\n        data_ssl_established = true;\n    }\n\n    if (!data_buf) {\n        data_buf = static_cast<unsigned char*>(MO_MALLOC(getMemoryTag(), data_buf_size));\n        if (!data_buf) {\n            MO_DBG_ERR(\"OOM\");\n            return -1;\n        }\n        memset(data_buf, 0, data_buf_size);\n    }\n\n    return 0; //success\n}\n\nvoid FtpTransferMbedTLS::close_ctrl() {\n    if (!ctrl_opened) {\n        return;\n    }\n\n    if (ctrl_ssl_established) {\n        mbedtls_ssl_close_notify(&ctrl_ssl);\n        ctrl_ssl_established = false;\n    }\n    mbedtls_net_free(&ctrl_fd);\n    ctrl_opened = false;\n\n    if (onClose && !data_opened) {\n        onClose(MO_FtpCloseReason_Failure); //data connection has never been opened --> failure\n        onClose = nullptr;\n    }\n}\n\nvoid FtpTransferMbedTLS::close_data(MO_FtpCloseReason reason) {\n    if (!data_opened) {\n        return;\n    }\n\n    MO_DBG_DEBUG(\"closing data conn\");\n\n    if (data_ssl_established) {\n        MO_DBG_DEBUG(\"TLS shutdown\");\n        mbedtls_ssl_close_notify(&data_ssl);\n        data_ssl_established = false;\n    }\n    mbedtls_net_free(&data_fd);\n    data_opened = false;\n    data_conn_accepted = false;\n\n    if (onClose) {\n        onClose(reason);\n        onClose = nullptr;\n    }\n}\n\nint FtpTransferMbedTLS::handshake_tls() {\n\n    int ret;\n    while ((ret = mbedtls_ssl_handshake(&ctrl_ssl)) != 0) {\n        if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != 1) {\n            char buf [1024];\n            mbedtls_strerror(ret, (char *) buf, 1024);\n            MO_DBG_ERR(\"mbedtls_ssl_handshake: %i, %s\", ret, buf);\n            return ret;\n        }\n    }\n\n    if (ca_cert) {\n        //certificate validation enabled\n\n        if ((ret = mbedtls_ssl_get_verify_result(&ctrl_ssl)) != 0) {\n            char vrfy_buf[512];\n            mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), \"   > \", ret);\n            MO_DBG_ERR(\"mbedtls_ssl_get_verify_result: %i, %s\", ret, vrfy_buf);\n            return ret;\n        }\n    }\n\n    ctrl_ssl_established = true;\n\n    return 0; //success\n}\n\nvoid FtpTransferMbedTLS::send_cmd(const char *cmd, const char *arg, bool disable_tls_policy) {\n\n    const size_t MSG_SIZE = 128;\n    unsigned char msg [MSG_SIZE];\n\n    auto len = snprintf((char*) msg, MSG_SIZE, \"%s%s%s\\r\\n\", \n            cmd,               //cmd mandatory (e.g. \"USER\")\n            arg ? \" \" : \"\",    //line spacing if arg is provided\n            arg ? arg : \"\");   //arg optional (e.g. \"anonymous\")\n    if (len < 0 || (size_t)len >= MSG_SIZE) {\n        MO_DBG_ERR(\"could not write cmd, send QUIT instead\");\n        len = sprintf((char*) msg, \"QUIT\\r\\n\");\n    } else {\n        //show outgoing traffic for debug, but shadow PASS\n        MO_DBG_DEBUG(\"SEND: %s %s\", \n                cmd,\n                !strncmp((char*) cmd, \"PASS\", strlen(\"PASS\")) ? \"***\" : arg ? (char*) arg : \"\");\n    }\n\n    int ret = -1;\n\n    if (ctrl_ssl_established) {\n        ret = mbedtls_ssl_write(&ctrl_ssl, (unsigned char*) msg, len);\n    } else if (!isSecure || disable_tls_policy) {\n        ret = mbedtls_net_send(&ctrl_fd, (unsigned char*) msg, len);\n    } else {\n        MO_DBG_ERR(\"TLS policy failure\");\n        len = strlen(\"QUIT\\r\\n\");\n        ret = mbedtls_net_send(&ctrl_fd, (unsigned char*) \"QUIT\\r\\n\", len);\n    }\n\n    if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE ||\n            ret <= 0 ||\n            ret < (int) len) {\n        char buf [1024];\n        mbedtls_strerror(ret, (char *) buf, 1024);\n        MO_DBG_ERR(\"fatal - message on ctrl channel lost: %i, %s\", ret, buf);\n        close_ctrl();\n        return;\n    }\n}\n\nbool FtpTransferMbedTLS::getFile(const char *ftp_url_raw, std::function<size_t(unsigned char *data, size_t len)> fileWriter, std::function<void(MO_FtpCloseReason)> onClose, const char *ca_cert) {\n\n    if (method != Method::UNDEFINED) {\n        MO_DBG_ERR(\"FTP Client reuse not supported\");\n        return false;\n    }\n    \n    if (!ftp_url_raw || !fileWriter) {\n        MO_DBG_ERR(\"invalid args\");\n        return false;\n    }\n\n    this->ca_cert = ca_cert;\n    this->method = Method::Retrieve;\n    this->fileWriter = fileWriter;\n    this->onClose = onClose;\n\n    if (!read_url_ctrl(ftp_url_raw)) {\n        MO_DBG_ERR(\"could not parse URL\");\n        return false;\n    }\n\n    MO_DBG_DEBUG(\"init download from %s: %s\", ctrl_host.c_str(), fname.c_str());\n\n    if (auto ret = setup_tls()) {\n        MO_DBG_ERR(\"could not setup MbedTLS: %i\", ret);\n        return false;\n    }\n\n    if (auto ret = connect_ctrl()) {\n        MO_DBG_ERR(\"could not establish connection to FTP server: %i\", ret);\n        return false;\n    }\n\n    return true;\n}\n\nbool FtpTransferMbedTLS::postFile(const char *ftp_url_raw, std::function<size_t(unsigned char *out, size_t buffsize)> fileReader, std::function<void(MO_FtpCloseReason)> onClose, const char *ca_cert) {\n    \n    if (method != Method::UNDEFINED) {\n        MO_DBG_ERR(\"FTP Client reuse not supported\");\n        return false;\n    }\n\n    if (!ftp_url_raw || !fileReader) {\n        MO_DBG_ERR(\"invalid args\");\n        return false;\n    }\n\n    MO_DBG_DEBUG(\"init upload %s\", ftp_url_raw);\n\n    this->ca_cert = ca_cert;\n    this->method = Method::Store;\n    this->fileReader = fileReader;\n    this->onClose = onClose;\n\n    if (!read_url_ctrl(ftp_url_raw)) {\n        MO_DBG_ERR(\"could not parse URL\");\n        return false;\n    }\n\n    if (auto ret = setup_tls()) {\n        MO_DBG_ERR(\"could not setup MbedTLS: %i\", ret);\n        return false;\n    }\n\n    if (auto ret = connect_ctrl()) {\n        MO_DBG_ERR(\"could not establish connection to FTP server: %i\", ret);\n        return false;\n    }\n\n    return true;\n}\n\nvoid FtpTransferMbedTLS::process_ctrl() {\n    // read input (if available)\n\n    const size_t INBUF_SIZE = 128;\n    unsigned char inbuf [INBUF_SIZE];\n    memset(inbuf, 0, INBUF_SIZE);\n\n    int ret = -1;\n\n    if (ctrl_ssl_established) {\n        ret = mbedtls_ssl_read(&ctrl_ssl, inbuf, INBUF_SIZE - 1);\n    } else {\n        ret = mbedtls_net_recv(&ctrl_fd, inbuf, INBUF_SIZE - 1);\n    }\n\n    if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {\n        //no new input data to be processed\n        return;\n    } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY || ret == 0) {\n        MO_DBG_ERR(\"FTP transfer aborted\");\n        close_ctrl();\n        return;\n    } else if (ret < 0) {\n        MO_DBG_ERR(\"mbedtls_net_recv: %i\", ret);\n        send_cmd(\"QUIT\");\n        close_ctrl();\n        return;\n    }\n\n    size_t inbuf_len = ret;\n\n    // read multi-line command\n    char *line_next = (char*) inbuf;\n    while (line_next < (char*) inbuf + inbuf_len) {\n\n        // take current line\n        char *line = line_next;\n\n        // null-terminate current line and find begin of next line\n        while (line_next + 1 < (char*) inbuf + inbuf_len && *line_next != '\\n') {\n            line_next++;\n        }\n        *line_next = '\\0';\n        line_next++;\n\n        MO_DBG_DEBUG(\"RECV: %s\", line);\n\n        if (isSecure && !ctrl_ssl_established) { //tls not established yet, set up according to RFC 4217\n            if (!strncmp(\"220\", line, 3)) {\n                MO_DBG_DEBUG(\"start TLS negotiation\");\n                send_cmd(\"AUTH TLS\", nullptr, true);\n                return;\n            } else if (!strncmp(\"234\", line, 3)) { // Proceed with TLS negotiation\n                MO_DBG_DEBUG(\"upgrade to TLS\");\n\n                if (auto ret = handshake_tls()) {\n                    MO_DBG_ERR(\"handshake: %i\", ret);\n                    send_cmd(\"QUIT\", nullptr, true);\n                    return;\n                }\n            } else {\n                MO_DBG_ERR(\"cannot proceed without TLS\");\n                send_cmd(\"QUIT\", nullptr, true);\n                return;\n            }\n        }\n\n        if (isSecure && !ctrl_ssl_established) {\n            //failure to establish security policy\n            MO_DBG_ERR(\"internal error\");\n            send_cmd(\"QUIT\", nullptr, true);\n            return;\n        }\n\n        //security policy met\n                \n        if (!strncmp(\"530\", line, 3)            // Not logged in\n                || !strncmp(\"220\", line, 3)     // Service ready for new user\n                || !strncmp(\"234\", line, 3)) {  // Just completed AUTH TLS handshake\n            MO_DBG_DEBUG(\"select user %s\", user.empty() ? \"anonymous\" : user.c_str());\n            send_cmd(\"USER\", user.empty() ? \"anonymous\" : user.c_str());\n        } else if (!strncmp(\"331\", line, 3)) { // User name okay, need password\n            MO_DBG_DEBUG(\"enter pass %.2s***\", pass.empty() ? \"-\" : pass.c_str());\n            send_cmd(\"PASS\", pass.c_str());\n        } else if (!strncmp(\"230\", line, 3)) { // User logged in, proceed\n            MO_DBG_DEBUG(\"select directory %s\", dir.empty() ? \"/\" : dir.c_str());\n            send_cmd(\"CWD\", dir.empty() ? \"/\" : dir.c_str());\n        } else if (!strncmp(\"250\", line, 3)) { // Requested file action okay, completed\n            MO_DBG_DEBUG(\"enter passive mode\");\n            if (isSecure) {\n                send_cmd(\"PBSZ 0\\r\\n\"\n                         \"PROT P\\r\\n\" //RFC 4217: set FTP session Private\n                         \"PASV\");\n            } else {\n                send_cmd(\"PASV\");\n            }\n        } else if (!strncmp(\"227\", line, 3)) { // Entering Passive Mode (h1,h2,h3,h4,p1,p2)\n\n            if (!read_url_data(line + 3)) { //trim leading response code\n                MO_DBG_ERR(\"could not process data url. Expect format: (h1,h2,h3,h4,p1,p2)\");\n                send_cmd(\"QUIT\");\n                return;\n            }\n\n            if (auto ret = connect_data()) {\n                MO_DBG_ERR(\"data connection failure: %i\", ret);\n                send_cmd(\"QUIT\");\n                return;\n            }\n\n            if (method == Method::Retrieve) {\n                MO_DBG_DEBUG(\"request download for %s\", fname.c_str());\n                send_cmd(\"RETR\", fname.c_str());\n            } else if (method == Method::Store) {\n                MO_DBG_DEBUG(\"request upload for %s\", fname.c_str());\n                send_cmd(\"STOR\", fname.c_str());\n            } else {\n                MO_DBG_ERR(\"internal error\");\n                send_cmd(\"QUIT\");\n                return;\n            }\n\n        } else if (!strncmp(\"150\", line, 3)    // File status okay; about to open data connection\n                || !strncmp(\"125\", line, 3)) { // Data connection already open\n            MO_DBG_DEBUG(\"data connection accepted\");\n            data_conn_accepted = true;\n        } else if (!strncmp(\"226\", line, 3)) { // Closing data connection. Requested file action successful (for example, file transfer or file abort)\n            MO_DBG_INFO(\"FTP success: %s\", line);\n            send_cmd(\"QUIT\");\n            return;\n        } else if (!strncmp(\"55\", line, 2)) { // Requested action not taken / aborted\n            MO_DBG_WARN(\"FTP failure: %s\", line);\n            send_cmd(\"QUIT\");\n            return;\n        } else if (!strncmp(\"200\", line, 3)) { //PBSZ -> 0 and PROT -> P accepted\n            MO_DBG_INFO(\"PBSZ/PROT success: %s\", line);\n        } else if (!strncmp(\"221\", line, 3)) { // Server Goodbye\n            MO_DBG_DEBUG(\"closing ctrl connection\");\n            close_ctrl();\n            return;\n        } else {\n            MO_DBG_WARN(\"unkown commad (close connection): %s\", line);\n            send_cmd(\"QUIT\");\n            return;\n        }\n    }\n}\n\nvoid FtpTransferMbedTLS::process_data() {\n    if (!data_conn_accepted) {\n        return;\n    }\n\n    if (isSecure && !data_ssl_established) {\n        //failure to establish security policy\n        MO_DBG_ERR(\"internal error\");\n        close_data(MO_FtpCloseReason_Failure);\n        send_cmd(\"QUIT\", nullptr, true);\n        return;\n    }\n\n    if (method == Method::Retrieve) {\n\n        if (data_buf_avail == 0) {\n            //load new data from socket\n\n            data_buf_offs = 0;\n\n            int ret = -1;\n            if (data_ssl_established) {\n                ret = mbedtls_ssl_read(&data_ssl, data_buf, data_buf_size - 1);\n            } else {\n                ret = mbedtls_net_recv(&data_fd, data_buf, data_buf_size - 1);\n            }\n\n            if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {\n                //no new input data to be processed\n                return;\n            } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY || ret == 0) {\n                //download finished\n                close_data(MO_FtpCloseReason_Success);\n                return;\n            } else if (ret < 0) {\n                MO_DBG_ERR(\"mbedtls_net_recv: %i\", ret);\n                close_data(MO_FtpCloseReason_Failure);\n                send_cmd(\"QUIT\");\n                return;\n            }\n\n            data_buf_avail = ret;\n        }\n\n        auto ret = fileWriter(data_buf + data_buf_offs, data_buf_avail);\n\n        if (ret == 0) {\n            MO_DBG_ERR(\"fileWriter aborted download\");\n            close_data(MO_FtpCloseReason_Failure);\n            send_cmd(\"QUIT\");\n            return;\n        } else if (ret <= data_buf_avail) {\n            data_buf_avail -= ret;\n            data_buf_offs += ret;\n        } else {\n            MO_DBG_ERR(\"write error\");\n            close_data(MO_FtpCloseReason_Failure);\n            send_cmd(\"QUIT\");\n            return;\n        }\n\n        //success\n    } else if (method == Method::Store) {\n\n        if (data_buf_avail == 0) {\n            //load new data from file to write on socket\n\n            data_buf_offs = 0;\n\n            data_buf_avail = fileReader(data_buf, data_buf_size);\n        }\n\n        if (data_buf_avail > 0) {\n\n            int ret = -1;\n            if (data_ssl_established) {\n                ret = mbedtls_ssl_write(&data_ssl, data_buf + data_buf_offs, data_buf_avail);\n            } else {\n                ret = mbedtls_net_send(&data_fd, data_buf + data_buf_offs, data_buf_avail);\n            }\n\n            if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {\n                //no data sent, wait\n                return;\n            } else if (ret <= 0) {\n                MO_DBG_ERR(\"mbedtls_ssl_write: %i\", ret);\n                close_data(MO_FtpCloseReason_Failure);\n                send_cmd(\"QUIT\");\n                return;\n            }\n\n            //successful write\n            data_buf_avail -= ret;\n            data_buf_offs += ret;\n        } else {\n            //no data in fileReader anymore\n            MO_DBG_DEBUG(\"finished file reading\");\n            close_data(MO_FtpCloseReason_Success);\n        }\n    }\n}\n\nvoid FtpTransferMbedTLS::loop() {\n\n    if (ctrl_opened) {\n        process_ctrl();\n    }\n\n    if (data_opened) {\n        process_data();\n    }\n}\n\nbool FtpTransferMbedTLS::isActive() {\n    return ctrl_opened || data_opened;\n}\n\nbool FtpTransferMbedTLS::read_url_ctrl(const char *ftp_url_raw) {\n    String ftp_url = makeString(getMemoryTag(), ftp_url_raw); //copy input ftp_url\n\n    //tolower protocol specifier\n    for (auto c = ftp_url.begin(); *c != ':' && c != ftp_url.end(); c++) {\n        *c = tolower(*c);\n    }\n\n    //parse FTP URL: protocol specifier\n    String proto = makeString(getMemoryTag());\n    if (!strncmp(ftp_url.c_str(), \"ftps://\", strlen(\"ftps://\"))) {\n        //FTP over TLS (RFC 4217)\n        proto = \"ftps://\";\n        isSecure = true; //TLS policy\n    } else if (!strncmp(ftp_url.c_str(), \"ftp://\", strlen(\"ftp://\"))) {\n        //FTP without security policies (RFC 959)\n        proto = \"ftp://\";\n    } else {\n        MO_DBG_ERR(\"protocol not supported. Please use ftps:// or ftp://\");\n        return false;\n    }\n\n    //parse FTP URL: dir and fname\n    auto dir_pos = ftp_url.find_first_of('/', proto.length());\n    if (dir_pos != String::npos) {\n        auto fname_pos = ftp_url.find_last_of('/');\n        dir = ftp_url.substr(dir_pos, fname_pos - dir_pos);\n        fname = ftp_url.substr(fname_pos + 1);\n    }\n\n    if (fname.empty()) {\n        MO_DBG_ERR(\"missing filename\");\n        return false;\n    }\n\n    MO_DBG_DEBUG(\"parsed dir: %s; fname: %s\", dir.c_str(), fname.c_str());\n\n    //parse FTP URL: user, pass, host, port\n\n    String user_pass_host_port = ftp_url.substr(proto.length(), dir_pos - proto.length());\n    String user_pass = makeString(getMemoryTag());\n    String host_port = makeString(getMemoryTag());\n    auto user_pass_delim = user_pass_host_port.find_first_of('@');\n    if (user_pass_delim != String::npos) {\n        host_port = user_pass_host_port.substr(user_pass_delim + 1);\n        user_pass = user_pass_host_port.substr(0, user_pass_delim);\n    } else {\n        host_port = user_pass_host_port;\n    }\n\n    if (!user_pass.empty()) {\n        auto user_delim = user_pass.find_first_of(':');\n        if (user_delim != String::npos) {\n            user = user_pass.substr(0, user_delim);\n            pass = user_pass.substr(user_delim + 1);\n        } else {\n            user = user_pass;\n        }\n    }\n\n    MO_DBG_DEBUG(\"parsed user: %s; pass: %.2s***\", user.c_str(), pass.empty() ? \"-\" : pass.c_str());\n\n    if (host_port.empty()) {\n        MO_DBG_ERR(\"missing hostname\");\n        return false;\n    }\n\n    auto host_port_delim = host_port.find(':');\n    if (host_port_delim != String::npos) {\n        ctrl_host = host_port.substr(0, host_port_delim);\n        ctrl_port = host_port.substr(host_port_delim + 1);\n    } else {\n        //use default port number\n        ctrl_host = host_port;\n        ctrl_port = \"21\";\n    }\n\n    MO_DBG_DEBUG(\"parsed host: %s; port: %s\", ctrl_host.c_str(), ctrl_port.c_str());\n\n    return true;\n}\n\nbool FtpTransferMbedTLS::read_url_data(const char *data_url_raw) {\n\n    String data_url = makeString(getMemoryTag(), data_url_raw); //format like \" Entering Passive Mode (h1,h2,h3,h4,p1,p2)\"\n\n    // parse address field. Replace all non-digits by delimiter character ' '\n    for (char& c : data_url) {\n        if (c < '0' || c > '9') {\n            c = (unsigned char) ' ';\n        }\n    }\n\n    unsigned int h1 = 0, h2 = 0, h3 = 0, h4 = 0, p1 = 0, p2 = 0;\n\n    auto ntokens = sscanf(data_url.c_str(), \"%u %u %u %u %u %u\", &h1, &h2, &h3, &h4, &p1, &p2);\n    if (ntokens != 6) {\n        MO_DBG_ERR(\"could not process data url. Expect format: (h1,h2,h3,h4,p1,p2)\");\n        return false;\n    }\n\n    unsigned int port = 256U * p1 + p2;\n\n    char buf [64] = {'\\0'};\n    auto ret = snprintf(buf, 64, \"%u.%u.%u.%u\", h1, h2, h3, h4);\n    if (ret < 0 || ret >= 64) {\n        MO_DBG_ERR(\"data url format failure\");\n        return false;\n    }\n    data_host = buf;\n\n    ret = snprintf(buf, 64, \"%u\", port);\n    if (ret < 0 || ret >= 64) {\n        MO_DBG_ERR(\"data url format failure\");\n        return false;\n    }\n    data_port = buf;\n\n    return true;\n}\n\nFtpClientMbedTLS::FtpClientMbedTLS(bool tls_only, const char *client_cert, const char *client_key)\n        : MemoryManaged(\"FTP.ClientMbedTLS\"), client_cert(client_cert), client_key(client_key), tls_only(tls_only) {\n\n}\n\nstd::unique_ptr<FtpDownload> FtpClientMbedTLS::getFile(const char *ftp_url_raw, std::function<size_t(unsigned char *data, size_t len)> fileWriter, std::function<void(MO_FtpCloseReason)> onClose, const char *ca_cert) {\n\n    auto ftp_handle = std::unique_ptr<FtpTransferMbedTLS>(new FtpTransferMbedTLS(tls_only, client_cert, client_key));\n    if (!ftp_handle) {\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n\n    bool success = ftp_handle->getFile(ftp_url_raw, fileWriter, onClose, ca_cert);\n\n    if (success) {\n        return ftp_handle;\n    } else {\n        return nullptr;\n    }\n}\n\nstd::unique_ptr<FtpUpload> FtpClientMbedTLS::postFile(const char *ftp_url_raw, std::function<size_t(unsigned char *out, size_t buffsize)> fileReader, std::function<void(MO_FtpCloseReason)> onClose, const char *ca_cert) {\n    \n    auto ftp_handle = std::unique_ptr<FtpTransferMbedTLS>(new FtpTransferMbedTLS(tls_only, client_cert, client_key));\n    if (!ftp_handle) {\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n\n    bool success = ftp_handle->postFile(ftp_url_raw, fileReader, onClose, ca_cert);\n\n    if (success) {\n        return ftp_handle;\n    } else {\n        return nullptr;\n    }\n}\n\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_MBEDTLS\n"
  },
  {
    "path": "src/MicroOcpp/Core/FtpMbedTLS.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_FTP_MBEDTLS_H\n#define MO_FTP_MBEDTLS_H\n\n/*\n * Built-in FTP client (depends on MbedTLS)\n *\n * Moved from https://github.com/matth-x/MicroFtp\n * \n * Currently, the compatibility with the following FTP servers has been tested:\n * \n * | Server                                                                | FTP | FTPS |\n * | --------------------------------------------------------------------- | --- | ---- |\n * | [vsftp](https://security.appspot.com/vsftpd.html)                     |     |  x   |\n * | [Rebex](https://www.rebex.net/)                                       |  x  |  x   |\n * | [Windows Server 2022](https://www.microsoft.com/en-us/windows-server) |  x  |  x   |\n * | [SFTPGo](https://github.com/drakkan/sftpgo)                           |  x  |      |\n * \n */\n\n#include <MicroOcpp/Platform.h>\n\n#if MO_ENABLE_MBEDTLS\n\n#include <memory>\n\n#include <MicroOcpp/Core/Ftp.h>\n\nnamespace MicroOcpp {\n\nstd::unique_ptr<FtpClient> makeFtpClientMbedTLS(bool tls_only = false, const char *client_cert = nullptr, const char *client_key = nullptr);\n\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_MBEDTLS\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/Memory.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Debug.h>\n\n#if MO_OVERRIDE_ALLOCATION && MO_ENABLE_HEAP_PROFILER\n\n#include <map>\n\nnamespace MicroOcpp {\nnamespace Memory {\n\nstruct MemBlockInfo {\n    void* tagger_ptr = nullptr;\n    std::string tag;\n    size_t size = 0;\n\n    MemBlockInfo(void* ptr, const char *tag, size_t size) : size{size} {\n        updateTag(ptr, tag);\n    }\n\n    void updateTag(void* ptr, const char *tag);\n};\n\nstd::map<void*,MemBlockInfo> memBlocks; //key: memory address of malloc'd block\n\nstruct MemTagInfo {\n    size_t current_size = 0;\n    size_t max_size = 0;\n\n    MemTagInfo(size_t size) {\n        operator+=(size);\n    }\n\n    void operator+=(size_t size) {\n        current_size += size;\n        max_size = std::max(max_size, current_size);\n    }\n\n    void operator-=(size_t size) {\n        if (size > current_size) {\n            MO_DBG_ERR(\"captured size does not fit\");\n            //return; let it happen for now\n        }\n        current_size -= size;\n    }\n\n    void reset() {\n        max_size = current_size;\n    }\n};\n\nstd::map<std::string,MemTagInfo> memTags;\n\nsize_t memTotal, memTotalMax;\n\nvoid MemBlockInfo::updateTag(void* ptr, const char *tag) {\n    if (!tag) {\n        return;\n    }\n    if (tagger_ptr == nullptr || ptr < tagger_ptr) {\n        MO_DBG_VERBOSE(\"update tag from %s to %s, ptr from %p to %p\", this->tag.c_str(), tag, tagger_ptr, ptr);\n\n        auto tagInfo = memTags.find(this->tag);\n        if (tagInfo != memTags.end()) {\n            tagInfo->second -= size;\n        }\n\n        tagInfo = memTags.find(tag);\n        if (tagInfo != memTags.end()) {\n            tagInfo->second += size;\n        } else {\n            memTags.emplace(tag, size);\n        }\n\n        tagger_ptr = ptr;\n        this->tag = tag;\n    }\n}\n\n} //namespace Memory\n} //namespace MicroOcpp\n\n#endif //MO_OVERRIDE_ALLOCATION && MO_ENABLE_HEAP_PROFILER\n\n#if MO_OVERRIDE_ALLOCATION\n\nnamespace MicroOcpp {\nnamespace Memory {\n\nvoid* (*malloc_override)(size_t);\nvoid (*free_override)(void*);\n\n}\n}\n\nusing namespace MicroOcpp::Memory;\n\nvoid mo_mem_set_malloc_free(void* (*malloc_override)(size_t), void (*free_override)(void*)) {\n    MicroOcpp::Memory::malloc_override = malloc_override;\n    MicroOcpp::Memory::free_override = free_override;\n}\n\nvoid *mo_mem_malloc(const char *tag, size_t size) {\n    MO_DBG_VERBOSE(\"malloc %zu B (%s)\", size, tag ? tag : \"unspecified\");\n\n    void *ptr;\n    if (malloc_override) {\n        ptr = malloc_override(size);\n    } else {\n        ptr = malloc(size);\n    }\n\n    #if MO_ENABLE_HEAP_PROFILER\n    if (ptr) {\n        memBlocks.emplace(ptr, MemBlockInfo(ptr, tag, size));\n\n        memTotal += size;\n        memTotalMax = std::max(memTotalMax, memTotal);\n    }\n    #endif\n    return ptr;\n}\n\nvoid mo_mem_free(void* ptr) {\n    MO_DBG_VERBOSE(\"free\");\n\n    #if MO_ENABLE_HEAP_PROFILER\n    if (ptr) {\n\n        auto blockInfo = memBlocks.find(ptr);\n        if (blockInfo != memBlocks.end()) {\n            auto tagInfo = memTags.find(blockInfo->second.tag);\n            if (tagInfo != memTags.end()) {\n                tagInfo->second -= blockInfo->second.size;\n            }\n            memTotal -= blockInfo->second.size;\n        }\n\n        if (blockInfo != memBlocks.end()) {\n            memBlocks.erase(blockInfo);\n        }\n    }\n    #endif\n\n    if (free_override) {\n        free_override(ptr);\n    } else {\n        free(ptr);\n    }\n}\n\n#endif //MO_OVERRIDE_ALLOCATION\n\n#if MO_OVERRIDE_ALLOCATION && MO_ENABLE_HEAP_PROFILER\n\nvoid mo_mem_deinit() {\n    memBlocks.clear();\n    memTags.clear();\n}\n\nvoid mo_mem_reset() {\n    MO_DBG_DEBUG(\"Reset all maximum values to current values\");\n\n    for (auto tagInfo = (memTags).begin(); tagInfo != memTags.end(); ++tagInfo) {\n        tagInfo->second.reset();\n    }\n\n    memTotalMax = memTotal;\n}\n\nvoid mo_mem_set_tag(void *ptr, const char *tag) {\n    MO_DBG_VERBOSE(\"set tag (%s)\", tag ? tag : \"unspecified\");\n\n    if (!tag) {\n        return;\n    }\n\n    bool hasTagged = false;\n\n    if (tag) {\n        auto foundBlock = memBlocks.upper_bound(ptr);\n        if (foundBlock != memBlocks.begin()) {\n            --foundBlock;\n        }\n        if (foundBlock != memBlocks.end() &&\n                (unsigned char*)ptr - (unsigned char*)foundBlock->first < (std::ptrdiff_t)foundBlock->second.size) {\n            foundBlock->second.updateTag(ptr, tag);\n            hasTagged = true;\n        }\n    }\n\n    if (!hasTagged) {\n        MO_DBG_VERBOSE(\"memory area doesn't apply\");\n    }\n}\n\nvoid mo_mem_print_stats() {\n\n    MO_CONSOLE_PRINTF(\"\\n *** Heap usage statistics ***\\n\");\n\n    size_t size = 0;\n\n    size_t untagged = 0, untagged_size = 0;\n\n    for (const auto& heapEntry : memBlocks) {\n        size += heapEntry.second.size;\n        #if MO_DBG_LEVEL >= MO_DL_VERBOSE\n        {\n            MO_CONSOLE_PRINTF(\"@%p - %zu B (%s)\\n\", heapEntry.first, heapEntry.second.size, heapEntry.second.tag.c_str());\n        }\n        #endif\n\n        if (heapEntry.second.tag.empty()) {\n            untagged ++;\n            untagged_size += heapEntry.second.size;\n        }\n    }\n\n    std::map<std::string, size_t> tags;\n    for (const auto& heapEntry : memBlocks) {\n        auto foundTag = tags.find(heapEntry.second.tag);\n        if (foundTag != tags.end()) {\n            foundTag->second += heapEntry.second.size;\n        } else {\n            tags.emplace(heapEntry.second.tag, heapEntry.second.size);\n        }\n    }\n\n    size_t size_control = 0;\n\n    for (const auto& tag : tags) {\n        size_control += tag.second;\n        #if MO_DBG_LEVEL >= MO_DL_VERBOSE\n        {\n            MO_CONSOLE_PRINTF(\"%s - %zu B\\n\", tag.first.c_str(), tag.second);\n        }\n        #endif\n    }\n\n    size_t size_control2 = 0;\n    for (const auto& tag : memTags) {\n        size_control2 += tag.second.current_size;\n        MO_CONSOLE_PRINTF(\"%s - %zu B (max. %zu B)\\n\", tag.first.c_str(), tag.second.current_size, tag.second.max_size);\n    }\n\n    MO_CONSOLE_PRINTF(\" *** Summary ***\\nBlocks: %zu\\nTags: %zu\\nCurrent usage: %zu B\\nMaximum usage: %zu B\\n\", memBlocks.size(), memTags.size(), memTotal, memTotalMax);\n    #if MO_DBG_LEVEL >= MO_DL_DEBUG\n    {\n        MO_CONSOLE_PRINTF(\" *** Debug information ***\\nTotal blocks (control value 1): %zu B\\nTags (control value): %zu\\nTotal tagged (control value 2): %zu B\\nTotal tagged (control value 3): %zu B\\nUntagged: %zu\\nTotal untagged: %zu B\\n\", size, tags.size(), size_control, size_control2, untagged, untagged_size);\n    }\n    #endif\n}\n\nint mo_mem_write_stats_json(char *buf, size_t size) {\n    DynamicJsonDocument doc {size * 2};\n\n    doc[\"total_current\"] = memTotal;\n    doc[\"total_max\"] = memTotalMax;\n    doc[\"total_blocks\"] = memBlocks.size();\n\n    JsonArray by_tag = doc.createNestedArray(\"by_tag\");\n    for (const auto& tag : memTags) {\n        JsonObject entry = by_tag.createNestedObject();\n        entry[\"tag\"] = tag.first.c_str();\n        entry[\"current\"] = tag.second.current_size;\n        entry[\"max\"] = tag.second.max_size;\n    }\n\n    size_t untagged = 0, untagged_size = 0;\n\n    for (const auto& heapEntry : memBlocks) {\n        if (heapEntry.second.tag.empty()) {\n            untagged ++;\n            untagged_size += heapEntry.second.size;\n        }\n    }\n\n    doc[\"untagged_blocks\"] = untagged;\n    doc[\"untagged_size\"] = untagged_size;\n\n    if (doc.overflowed()) {\n        MO_DBG_ERR(\"exceeded JSON capacity\");\n        return -1;\n    }\n\n    return (int)serializeJson(doc, buf, size);\n}\n\n#endif //MO_OVERRIDE_ALLOCATION && MO_ENABLE_HEAP_PROFILER\n\nnamespace MicroOcpp {\n\nString makeString(const char *tag, const char *val) {\n#if MO_OVERRIDE_ALLOCATION\n    if (val) {\n        return String(val, Allocator<char>(tag));\n    } else {\n        return String(Allocator<char>(tag));\n    }\n#else\n    if (val) {\n        return String(val);\n    } else {\n        return String();\n    }\n#endif\n}\n\nJsonDoc initJsonDoc(const char *tag, size_t capacity) {\n#if MO_OVERRIDE_ALLOCATION\n    return JsonDoc(capacity, ArduinoJsonAllocator(tag));\n#else\n    return JsonDoc(capacity);\n#endif\n}\n\nstd::unique_ptr<JsonDoc> makeJsonDoc(const char *tag, size_t capacity) {\n#if MO_OVERRIDE_ALLOCATION\n    return std::unique_ptr<JsonDoc>(new JsonDoc(capacity, ArduinoJsonAllocator(tag)));\n#else\n    return std::unique_ptr<JsonDoc>(new JsonDoc(capacity));\n#endif\n}\n\n}\n"
  },
  {
    "path": "src/MicroOcpp/Core/Memory.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_MEMORY_H\n#define MO_MEMORY_H\n\n#include <stddef.h>\n\n#ifndef MO_OVERRIDE_ALLOCATION\n#define MO_OVERRIDE_ALLOCATION 0\n#endif\n\n#ifndef MO_ENABLE_EXTERNAL_RAM\n#define MO_ENABLE_EXTERNAL_RAM 0\n#endif\n\n#ifndef MO_ENABLE_HEAP_PROFILER\n#define MO_ENABLE_HEAP_PROFILER 0\n#endif\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if MO_OVERRIDE_ALLOCATION\n\nvoid mo_mem_set_malloc_free(void* (*malloc_override)(size_t), void (*free_override)(void*)); //pass custom malloc and free function to be used with the OCPP lib. If not set or NULL, defaults to standard malloc\n\nvoid *mo_mem_malloc(const char *tag, size_t size);\n\nvoid mo_mem_free(void* ptr);\n\n#define MO_MALLOC mo_mem_malloc\n#define MO_FREE mo_mem_free\n\n#else\n#define MO_MALLOC(TAG, SIZE) malloc(SIZE) //default malloc provided by host system\n#define MO_FREE(PTR) free(PTR)       //default free provided by host system\n#endif //MO_OVERRIDE_ALLOCATION\n\n\n#if MO_OVERRIDE_ALLOCATION && MO_ENABLE_HEAP_PROFILER\n\nvoid mo_mem_deinit(); //release allocated memory and deinit\nvoid mo_mem_reset(); //reset maximum heap occuption\n\nvoid mo_mem_set_tag(void *ptr, const char *tag);\n\nvoid mo_mem_get_current_heap(const char *tag);\nvoid mo_mem_get_maximum_heap(const char *tag);\nvoid mo_mem_get_current_heap_by_tag(const char *tag);\nvoid mo_mem_get_maximum_heap_by_tag(const char *tag);\n\nint mo_mem_write_stats_json(char *buf, size_t size);\n\nvoid mo_mem_print_stats();\n\n#define MO_MEM_DEINIT mo_mem_deinit\n#define MO_MEM_RESET mo_mem_reset\n#define MO_MEM_SET_TAG mo_mem_set_tag\n#define MO_MEM_PRINT_STATS mo_mem_print_stats\n\n#else\n#define MO_MEM_DEINIT(...) (void)0\n#define MO_MEM_RESET(...) (void)0\n#define MO_MEM_SET_TAG(...) (void)0\n#define MO_MEM_PRINT_STATS(...) (void)0\n#endif //MO_OVERRIDE_ALLOCATION && MO_ENABLE_HEAP_PROFILER\n\n\n#if MO_ENABLE_EXTERNAL_RAM\n\nvoid mo_mem_set_malloc_free_ext(void* (*malloc_override)(size_t), void (*free_override)(void*)); //pass malloc and free function to external RAM to be used with the OCPP lib. If not set or NULL, defaults to standard malloc\n\nvoid *mo_mem_malloc_ext(const char *tag, size_t size);\n\nvoid mo_mem_free_ext(void* ptr);\n\n#define MO_MALLOC_EXT(TAG, SIZE) mo_mem_malloc_ext(TAG, SIZE)\n#define MO_FREE_EXT(PTR) mo_mem_free_ext(PTR)\n\n#else\n#define MO_MALLOC_EXT MO_MALLOC\n#define MO_FREE_EXT MO_FREE\n#endif //MO_ENABLE_EXTERNAL_RAM\n\n\n#ifdef __cplusplus\n}\n\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include <ArduinoJson.h>\n\n#if MO_OVERRIDE_ALLOCATION\n\n#include <string.h>\n\nnamespace MicroOcpp {\n\nclass MemoryManaged {\nprivate:\n    #if MO_ENABLE_HEAP_PROFILER\n    char *tag = nullptr;\n    #endif\nprotected:\n    void updateMemoryTag(const char *src1, const char *src2 = nullptr) {\n        #if MO_ENABLE_HEAP_PROFILER\n        if (!src1 && !src2) {\n            //empty source does not update tag\n            return;\n        }\n        char src [64];\n        snprintf(src, sizeof(src), \"%s%s\", src1 ? src1 : \"\", src2 ? src2 : \"\");\n        if (tag) {\n            if (!strcmp(src, tag)) {\n                //nothing to do\n                return;\n            }\n            MO_FREE(tag);\n            tag = nullptr;\n        }\n        size_t size = strlen(src) + 1;\n        tag = static_cast<char*>(malloc(size)); //heap profiler bypasses custom malloc to not count into the statistics\n        memset(tag, 0, size);\n        snprintf(tag, size, \"%s\", src);\n        mo_mem_set_tag(this, tag);\n        #else\n        (void)src1;\n        (void)src2;\n        #endif\n    }\n    const char *getMemoryTag() const {\n        #if MO_ENABLE_HEAP_PROFILER\n        return tag;\n        #else\n        return nullptr;\n        #endif\n    }\npublic:\n    void *operator new(size_t size) {\n        return MO_MALLOC(nullptr, size);\n    }\n    void operator delete(void * ptr) {\n        MO_FREE(ptr);\n    }\n\n    MemoryManaged(const char *tag = nullptr, const char *tag_suffix = nullptr) {\n        #if MO_ENABLE_HEAP_PROFILER\n        updateMemoryTag(tag, tag_suffix);\n        #endif\n    }\n\n    MemoryManaged(MemoryManaged&& other) {\n        #if MO_ENABLE_HEAP_PROFILER\n        tag = other.tag;\n        other.tag = nullptr;\n        #endif\n    }\n\n    ~MemoryManaged() {\n        #if MO_ENABLE_HEAP_PROFILER\n        MO_FREE(tag);\n        tag = nullptr;\n        #endif\n    }\n\n    void operator=(const MemoryManaged& other) {\n        #if MO_ENABLE_HEAP_PROFILER\n        updateMemoryTag(other.tag);\n        #endif\n    }\n};\n\ntemplate<class T>\nstruct Allocator {\n\n    Allocator(const char *tag = nullptr, const char *tag_suffix = nullptr) {\n        #if MO_ENABLE_HEAP_PROFILER\n        updateMemoryTag(tag, tag_suffix);\n        #endif\n    }\n\n    template<class U>\n    Allocator(const Allocator<U>& other) {\n        #if MO_ENABLE_HEAP_PROFILER\n        updateMemoryTag(other.tag);\n        #endif\n    }\n\n    Allocator(const Allocator& other) {\n        #if MO_ENABLE_HEAP_PROFILER\n        updateMemoryTag(other.tag);\n        #endif\n    }\n\n    //template<class U>\n    //Allocator(Allocator<U>&& other) {\n    Allocator(Allocator&& other) {\n        #if MO_ENABLE_HEAP_PROFILER\n        updateMemoryTag(other.tag); //ignore move semantics for allocators as it simplifies moving std::vector<T, Allocator<T>>. This is okay because the Allocator's state is only the memory tag which is not exclusively owned\n        #endif\n    }\n\n    ~Allocator() {\n        #if MO_ENABLE_HEAP_PROFILER\n        if (tag) {\n            //MO_FREE(tag);\n            free(tag);\n            tag = nullptr;\n        }\n        #endif\n    }\n\n    T *allocate(size_t count) {\n        #if MO_ENABLE_HEAP_PROFILER\n            return static_cast<T*>(MO_MALLOC(tag, sizeof(T) * count));\n        #else\n            return static_cast<T*>(MO_MALLOC(nullptr, sizeof(T) * count));\n        #endif\n    }\n\n    void deallocate(T *ptr, size_t count) {\n        MO_FREE(ptr);\n    }\n\n    bool operator==(const Allocator<T>& other) {\n        #if MO_ENABLE_HEAP_PROFILER\n        if (!tag && !other.tag) {\n            return true;\n        } else if (tag && other.tag) {\n            return !strcmp(tag, other.tag);\n        } else {\n            return false;\n        }\n        #else\n        return true;\n        #endif\n    }\n\n    bool operator!=(const Allocator<T>& other) {\n        return !operator==(other);\n    }\n\n    typedef T value_type;\n\n    #if MO_ENABLE_HEAP_PROFILER\n    char *tag = nullptr;\n\n    void updateMemoryTag(const char *src1, const char *src2 = nullptr) {\n        if (!src1 && !src2) {\n            //empty source does not update tag\n            return;\n        }\n        char src [64];\n        snprintf(src, sizeof(src), \"%s%s\", src1 ? src1 : \"\", src2 ? src2 : \"\");\n        if (tag) {\n            if (!strcmp(src, tag)) {\n                //nothing to do\n                return;\n            }\n            //MO_FREE(tag);\n            free(tag);\n            tag = nullptr;\n        }\n        size_t size = strlen(src) + 1;\n        tag = static_cast<char*>(malloc(size));\n        memset(tag, 0, size);\n        snprintf(tag, size, \"%s\", src);\n    }\n    #endif\n};\n\ntemplate<class T>\nAllocator<T> makeAllocator(const char *tag, const char *tag_suffix = nullptr) {\n    return Allocator<T>(tag, tag_suffix);\n}\n\nusing String = std::basic_string<char, std::char_traits<char>, MicroOcpp::Allocator<char>>;\n\ntemplate<class T>\nusing Vector = std::vector<T, Allocator<T>>;\n\ntemplate<class T>\nVector<T> makeVector(const char *tag) {\n    return Vector<T>(Allocator<T>(tag));\n}\n\nclass ArduinoJsonAllocator {\nprivate:\n    #if MO_ENABLE_HEAP_PROFILER\n    char *tag = nullptr;\n\n    void updateMemoryTag(const char *src1, const char *src2 = nullptr) {\n        if (!src1 && !src2) {\n            //empty source does not update tag\n            return;\n        }\n        char src [64];\n        snprintf(src, sizeof(src), \"%s%s\", src1 ? src1 : \"\", src2 ? src2 : \"\");\n        if (tag) {\n            if (!strcmp(src, tag)) {\n                //nothing to do\n                return;\n            }\n            MO_FREE(tag);\n            tag = nullptr;\n        }\n        size_t size = strlen(src) + 1;\n        //tag = static_cast<char*>(MO_MALLOC(\"HeapProfilerInternal\", size));\n        tag = static_cast<char*>(malloc(size));\n        memset(tag, 0, size);\n        snprintf(tag, size, \"%s\", src);\n    }\n    #endif\npublic:\n\n    ArduinoJsonAllocator(const char *tag = nullptr, const char *tag_suffix = nullptr) {\n        #if MO_ENABLE_HEAP_PROFILER\n        updateMemoryTag(tag, tag_suffix);\n        #endif\n    }\n\n    ArduinoJsonAllocator(const ArduinoJsonAllocator& other) {\n        #if MO_ENABLE_HEAP_PROFILER\n        updateMemoryTag(other.tag);\n        #endif\n    }\n\n    ArduinoJsonAllocator(ArduinoJsonAllocator&& other) {\n        #if MO_ENABLE_HEAP_PROFILER\n        tag = other.tag;\n        other.tag = nullptr;\n        #endif\n    }\n\n    ~ArduinoJsonAllocator() {\n        #if MO_ENABLE_HEAP_PROFILER\n        if (tag) {\n            MO_FREE(tag);\n            tag = nullptr;\n        }\n        #endif\n    }\n\n    void *allocate(size_t size) {\n        #if MO_ENABLE_HEAP_PROFILER\n            return MO_MALLOC(tag, size);\n        #else\n            return MO_MALLOC(nullptr, size);\n        #endif\n    }\n    void deallocate(void *ptr) {\n        MO_FREE(ptr);\n    }\n};\n\nusing JsonDoc = BasicJsonDocument<ArduinoJsonAllocator>;\n\ntemplate<class T, typename ...Args>\nT *mo_mem_new(const char *tag, Args&& ...args)  {\n    if (auto ptr = MO_MALLOC(tag, sizeof(T))) {\n        return new(ptr) T(std::forward<Args>(args)...);\n    }\n    return nullptr; //OOM\n}\n\ntemplate<class T>\nvoid mo_mem_delete(T *ptr)  {\n    if (ptr) {\n        ptr->~T();\n        MO_FREE(ptr);\n    }\n}\n\n} //namespace MicroOcpp\n\n#else\n\n#include <memory>\n\nnamespace MicroOcpp {\n\nclass MemoryManaged {\nprotected:\n    const char *getMemoryTag() const {return nullptr;}\n    void updateMemoryTag(const char*,const char*) { } \npublic:\n    MemoryManaged() { }\n    MemoryManaged(const char*) { }\n    MemoryManaged(const char*,const char*) { }\n};\n\ntemplate<class T>\nusing Allocator = ::std::allocator<T>;\n\ntemplate<class T>\nAllocator<T> makeAllocator(const char *, const char *unused = nullptr) {\n    (void)unused;\n    return Allocator<T>();\n}\n\nusing String = std::string;\n\ntemplate<class T>\nusing Vector = std::vector<T>;\n\ntemplate<class T>\nVector<T> makeVector(const char *tag) {\n    return Vector<T>();\n}\n\nusing JsonDoc = DynamicJsonDocument;\n\ntemplate <class T, typename ...Args>\nT *mo_mem_new(Args&& ...args)  {\n    return new T(std::forward<Args>(args)...);\n}\n\ntemplate<class T>\nvoid mo_mem_delete(T *ptr)  {\n    delete ptr;\n}\n\n} //namespace MicroOcpp\n\n#endif //MO_OVERRIDE_ALLOCATION\n\nnamespace MicroOcpp {\n\nString makeString(const char *tag, const char *val = nullptr);\n\nJsonDoc initJsonDoc(const char *tag, size_t capacity = 0);\nstd::unique_ptr<JsonDoc> makeJsonDoc(const char *tag, size_t capacity = 0);\n\n}\n\n#endif //__cplusplus\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/OcppError.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_OCPPERROR_H\n#define MO_OCPPERROR_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass NotImplemented : public Operation, public MemoryManaged {\npublic:\n    NotImplemented() : MemoryManaged(\"v16.CallError.\", \"NotImplemented\") { }\n\n    const char *getErrorCode() override {\n        return \"NotImplemented\";\n    }\n};\n\nclass MsgBufferExceeded : public Operation, public MemoryManaged {\nprivate:\n    size_t maxCapacity;\n    size_t msgLen;\npublic:\n    MsgBufferExceeded(size_t maxCapacity, size_t msgLen) : MemoryManaged(\"v16.CallError.\", \"GenericError\"), maxCapacity(maxCapacity), msgLen(msgLen) { }\n    const char *getErrorCode() override {\n        return \"GenericError\";\n    }\n    const char *getErrorDescription() override {\n        return \"JSON too long or too many fields. Cannot deserialize\";\n    }\n    std::unique_ptr<JsonDoc> getErrorDetails() override {\n        auto errDoc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2));\n        JsonObject err = errDoc->to<JsonObject>();\n        err[\"max_capacity\"] = maxCapacity;\n        err[\"msg_length\"] = msgLen;\n        return errDoc;\n    }\n};\n\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/Operation.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Operation.h>\n\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nOperation::Operation() {}\n\nOperation::~Operation() {}\n  \nconst char* Operation::getOperationType(){\n    MO_DBG_ERR(\"Unsupported operation: getOperationType() is not implemented\");\n    return \"CustomOperation\";\n}\n\nstd::unique_ptr<JsonDoc> Operation::createReq() {\n    MO_DBG_ERR(\"Unsupported operation: createReq() is not implemented\");\n    return createEmptyDocument();\n}\n\nvoid Operation::processConf(JsonObject payload) {\n    MO_DBG_ERR(\"Unsupported operation: processConf() is not implemented\");\n}\n\nvoid Operation::processReq(JsonObject payload) {\n    MO_DBG_ERR(\"Unsupported operation: processReq() is not implemented\");\n}\n\nstd::unique_ptr<JsonDoc> Operation::createConf() {\n    MO_DBG_ERR(\"Unsupported operation: createConf() is not implemented\");\n    return createEmptyDocument();\n}\n\nstd::unique_ptr<JsonDoc> MicroOcpp::createEmptyDocument() {\n    auto emptyDoc = makeJsonDoc(\"EmptyJsonDoc\", 0);\n    emptyDoc->to<JsonObject>();\n    return emptyDoc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Core/Operation.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/**\n * This framework considers OCPP operations to be a combination of two things: first, ensure that the message reaches its\n * destination properly (the \"Remote procedure call\" header, e.g. message Id). Second, transmit the application data\n * as specified in the OCPP 1.6 document.\n * \n * The remote procedure call (RPC) part is implemented by the class Request. The application data part is implemented by\n * the respective Operation subclasses, e.g. BootNotification, StartTransaction, ect.\n * \n * The resulting structure is that the RPC header (=instance of Request) holds a reference to the payload\n * message creator (=instance of BootNotification, StartTransaction, ...). Both objects working together give the complete\n * OCPP operation.\n */\n\n #ifndef MO_OPERATION_H\n #define MO_OPERATION_H\n\n#include <memory>\n#include <ArduinoJson.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nstd::unique_ptr<JsonDoc> createEmptyDocument();\n\nclass Operation {\npublic:\n    Operation();\n\n    virtual ~Operation();\n    \n    virtual const char* getOperationType();\n\n    /**\n     * Create the payload for the respective OCPP message\n     * \n     * For instance operation Authorize: creates Authorize.req(idTag)\n     * \n     * This function is usually called multiple times by the Arduino loop(). On first call, the request is initially sent. In the\n     * succeeding calls, the implementers decide to either recreate the request, or do nothing as the operation is still pending.\n     */\n    virtual std::unique_ptr<JsonDoc> createReq();\n\n\n    virtual void processConf(JsonObject payload);\n    \n    /*\n     * returns if the operation must be aborted\n     */\n    virtual bool processErr(const char *code, const char *description, JsonObject details) { return true;}\n\n    /**\n     * Processes the request in the JSON document. \n     */\n    virtual void processReq(JsonObject payload);\n\n    /**\n     * After successfully processing a request sent by the communication counterpart, this function creates the payload for a confirmation\n     * message.\n     */\n    virtual std::unique_ptr<JsonDoc> createConf();\n\n    virtual const char *getErrorCode() {return nullptr;} //nullptr means no error\n    virtual const char *getErrorDescription() {return \"\";}\n    virtual std::unique_ptr<JsonDoc> getErrorDetails() {return createEmptyDocument();}\n};\n\n} //end namespace MicroOcpp\n #endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/OperationRegistry.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/OperationRegistry.h>\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/OcppError.h>\n#include <MicroOcpp/Debug.h>\n#include <algorithm>\n\nusing namespace MicroOcpp;\n\nOperationRegistry::OperationRegistry() : registry(makeVector<OperationCreator>(\"OperationRegistry\")) {\n\n}\n\nOperationCreator *OperationRegistry::findCreator(const char *operationType) {\n    for (auto it = registry.begin(); it != registry.end(); ++it) {\n        if (!strcmp(it->operationType, operationType)) {\n            return &*it;\n        }\n    }\n    return nullptr;\n}\n\nvoid OperationRegistry::registerOperation(const char *operationType, std::function<Operation*()> creator) {\n    registry.erase(std::remove_if(registry.begin(), registry.end(),\n                [operationType] (const OperationCreator& el) {\n                    return !strcmp(operationType, el.operationType);\n                }),\n            registry.end());\n    \n    OperationCreator entry;\n    entry.operationType = operationType;\n    entry.creator = creator;\n\n    registry.push_back(entry);\n\n    MO_DBG_DEBUG(\"registered operation %s\", operationType);\n}\n\nvoid OperationRegistry::setOnRequest(const char *operationType, OnReceiveReqListener onRequest) {\n    if (auto entry = findCreator(operationType)) {\n        entry->onRequest = onRequest;\n    } else {\n        MO_DBG_ERR(\"%s not registered\", operationType);\n    }\n}\n\nvoid OperationRegistry::setOnResponse(const char *operationType, OnSendConfListener onResponse) {\n    if (auto entry = findCreator(operationType)) {\n        entry->onResponse = onResponse;\n    } else {\n        MO_DBG_ERR(\"%s not registered\", operationType);\n    }\n}\n\nstd::unique_ptr<Request> OperationRegistry::deserializeOperation(const char *operationType) {\n    \n    if (auto entry = findCreator(operationType)) {\n        auto payload = entry->creator();\n        if (payload) {\n            auto result = std::unique_ptr<Request>(new Request(\n                                std::unique_ptr<Operation>(payload)));\n            result->setOnReceiveReqListener(entry->onRequest);\n            result->setOnSendConfListener(entry->onResponse);\n            return result;\n        }\n    }\n\n    return std::unique_ptr<Request>(new Request(\n                std::unique_ptr<Operation>(new NotImplemented())));\n}\n\nvoid OperationRegistry::debugPrint() {\n    for (auto& creator : registry) {\n        MO_CONSOLE_PRINTF(\"[OCPP]     > %s\\n\", creator.operationType);\n    }\n}\n"
  },
  {
    "path": "src/MicroOcpp/Core/OperationRegistry.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_OPERATIONREGISTRY_H\n#define MO_OPERATIONREGISTRY_H\n\n#include <functional>\n#include <memory>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Core/RequestCallbacks.h>\n\nnamespace MicroOcpp {\n\nclass Operation;\nclass Request;\n\nstruct OperationCreator {\n    const char *operationType {nullptr};\n    std::function<Operation*()> creator {nullptr};\n    OnReceiveReqListener onRequest {nullptr};\n    OnSendConfListener onResponse {nullptr};\n};\n\nclass OperationRegistry {\nprivate:\n    Vector<OperationCreator> registry;\n    OperationCreator *findCreator(const char *operationType);\n\npublic:\n    OperationRegistry();\n\n    void registerOperation(const char *operationType, std::function<Operation*()> creator);\n    void setOnRequest(const char *operationType, OnReceiveReqListener onRequest);\n    void setOnResponse(const char *operationType, OnSendConfListener onResponse);\n    \n    std::unique_ptr<Request> deserializeOperation(const char *operationType);\n\n    void debugPrint();\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/Request.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Core/UuidUtils.h>\n\n#include <MicroOcpp/Operations/StartTransaction.h>\n#include <MicroOcpp/Operations/StopTransaction.h>\n\n#include <MicroOcpp/Platform.h>\n#include <MicroOcpp/Debug.h>\n\nnamespace MicroOcpp {\n    unsigned int g_randSeed = 1394827383;\n\n    void writeRandomNonsecure(unsigned char *buf, size_t len) {\n        g_randSeed += mocpp_tick_ms();\n        const unsigned int a = 16807;\n        const unsigned int m = 2147483647;\n        for (size_t i = 0; i < len; i++) {\n            g_randSeed = (a * g_randSeed) % m;\n            buf[i] = g_randSeed;\n        }\n    }\n}\n\nusing namespace MicroOcpp;\n\nRequest::Request(std::unique_ptr<Operation> msg) : MemoryManaged(\"Request.\", msg->getOperationType()), messageID(makeString(getMemoryTag())), operation(std::move(msg)) {\n    timeout_start = mocpp_tick_ms();\n    debugRequest_start = mocpp_tick_ms();\n}\n\nRequest::~Request(){\n\n}\n\nOperation *Request::getOperation(){\n    return operation.get();\n}\n\nvoid Request::setTimeout(unsigned long timeout) {\n    this->timeout_period = timeout;\n}\n\nbool Request::isTimeoutExceeded() {\n    return timed_out || (timeout_period && mocpp_tick_ms() - timeout_start >= timeout_period);\n}\n\nvoid Request::executeTimeout() {\n    if (!timed_out) {\n        onTimeoutListener();\n        onAbortListener();\n    }\n    timed_out = true;\n}\n\nvoid Request::setMessageID(const char *id){\n    if (!messageID.empty()){\n        MO_DBG_ERR(\"messageID already defined\");\n    }\n    messageID = id;\n}\n\nRequest::CreateRequestResult Request::createRequest(JsonDoc& requestJson) {\n\n    if (messageID.empty()) {\n        char uuid [37] = {'\\0'};\n        generateUUID(uuid, 37);\n        messageID = uuid;\n    }\n\n    /*\n     * Create the OCPP message\n     */\n    auto requestPayload = operation->createReq();\n    if (!requestPayload) {\n        return CreateRequestResult::Failure;\n    }\n\n    /*\n     * Create OCPP-J Remote Procedure Call header\n     */\n    size_t json_buffsize = JSON_ARRAY_SIZE(4) + (messageID.length() + 1) + requestPayload->capacity();\n    requestJson = initJsonDoc(getMemoryTag(), json_buffsize);\n\n    requestJson.add(MESSAGE_TYPE_CALL);                    //MessageType\n    requestJson.add(messageID);                      //Unique message ID\n    requestJson.add(operation->getOperationType());  //Action\n    requestJson.add(*requestPayload);                      //Payload\n\n    if (MO_DBG_LEVEL >= MO_DL_DEBUG && mocpp_tick_ms() - debugRequest_start >= 10000) { //print contents on the console\n        debugRequest_start = mocpp_tick_ms();\n\n        char *buf = new char[1024];\n        size_t len = 0;\n        if (buf) {\n            len = serializeJson(requestJson, buf, 1024);\n        }\n\n        if (!buf || len < 1) {\n            MO_DBG_DEBUG(\"Try to send request: %s\", operation->getOperationType());\n        } else {\n            MO_DBG_DEBUG(\"Try to send request: %.*s (...)\", 128, buf);\n        }\n\n        delete[] buf;\n    }\n\n    return CreateRequestResult::Success;\n}\n\nbool Request::receiveResponse(JsonArray response){\n    /*\n     * check if messageIDs match. If yes, continue with this function. If not, return false for message not consumed\n     */\n    if (messageID.compare(response[1].as<const char*>())){\n        return false;\n    }\n\n    int messageTypeId = response[0] | -1;\n\n    if (messageTypeId == MESSAGE_TYPE_CALLRESULT) {\n\n        /*\n        * Hand the payload over to the Operation object\n        */\n        JsonObject payload = response[2];\n        operation->processConf(payload);\n\n        /*\n        * Hand the payload over to the onReceiveConf Callback\n        */\n        onReceiveConfListener(payload);\n\n        /*\n        * return true as this message has been consumed\n        */\n        return true;\n    } else if (messageTypeId == MESSAGE_TYPE_CALLERROR) {\n\n        /*\n        * Hand the error over to the Operation object\n        */\n        const char *errorCode = response[2];\n        const char *errorDescription = response[3];\n        JsonObject errorDetails = response[4];\n        bool abortOperation = operation->processErr(errorCode, errorDescription, errorDetails);\n\n        if (abortOperation) {\n            onReceiveErrorListener(errorCode, errorDescription, errorDetails);\n            onAbortListener();\n        }\n\n        return abortOperation;\n    } else {\n        MO_DBG_WARN(\"invalid response\");\n        return false; //don't discard this message but retry sending it\n    }\n\n}\n\nbool Request::receiveRequest(JsonArray request) {\n\n    if (!request[1].is<const char*>()) {\n        MO_DBG_ERR(\"malformatted msgId\");\n        return false;\n    }\n  \n    setMessageID(request[1].as<const char*>());\n    \n    /*\n     * Hand the payload over to the Request object\n     */\n    JsonObject payload = request[3];\n    operation->processReq(payload);\n    \n    /*\n     * Hand the payload over to the first Callback. It is a callback that notifies the client that request has been processed in the OCPP-library\n     */\n    onReceiveReqListener(payload);\n\n    return true; //success\n}\n\nRequest::CreateResponseResult Request::createResponse(JsonDoc& response) {\n\n    bool operationFailure = operation->getErrorCode() != nullptr;\n\n    if (!operationFailure) {\n\n        std::unique_ptr<JsonDoc> payload = operation->createConf();\n\n        if (!payload) {\n            return CreateResponseResult::Pending; //confirmation message still pending\n        }\n\n        /*\n         * Create OCPP-J Remote Procedure Call header\n         */\n        size_t json_buffsize = JSON_ARRAY_SIZE(3) + payload->capacity();\n        response = initJsonDoc(getMemoryTag(), json_buffsize);\n\n        response.add(MESSAGE_TYPE_CALLRESULT);   //MessageType\n        response.add(messageID.c_str());            //Unique message ID\n        response.add(*payload);              //Payload\n\n        if (onSendConfListener) {\n            onSendConfListener(payload->as<JsonObject>());\n        }\n    } else {\n        //operation failure. Send error message instead\n\n        const char *errorCode = operation->getErrorCode();\n        const char *errorDescription = operation->getErrorDescription();\n        std::unique_ptr<JsonDoc> errorDetails = operation->getErrorDetails();\n\n        /*\n         * Create OCPP-J Remote Procedure Call header\n         */\n        size_t json_buffsize = JSON_ARRAY_SIZE(5)\n                    + errorDetails->capacity();\n        response = initJsonDoc(getMemoryTag(), json_buffsize);\n\n        response.add(MESSAGE_TYPE_CALLERROR);   //MessageType\n        response.add(messageID.c_str());            //Unique message ID\n        response.add(errorCode);\n        response.add(errorDescription);\n        response.add(*errorDetails);              //Error description\n    }\n\n    return CreateResponseResult::Success;\n}\n\nvoid Request::setOnReceiveConfListener(OnReceiveConfListener onReceiveConf){\n    if (onReceiveConf)\n        onReceiveConfListener = onReceiveConf;\n}\n\n/**\n * Sets a Listener that is called after this machine processed a request by the communication counterpart\n */\nvoid Request::setOnReceiveReqListener(OnReceiveReqListener onReceiveReq){\n    if (onReceiveReq)\n        onReceiveReqListener = onReceiveReq;\n}\n\nvoid Request::setOnSendConfListener(OnSendConfListener onSendConf){\n    if (onSendConf)\n        onSendConfListener = onSendConf;\n}\n\nvoid Request::setOnTimeoutListener(OnTimeoutListener onTimeout) {\n    if (onTimeout)\n        onTimeoutListener = onTimeout;\n}\n\nvoid Request::setOnReceiveErrorListener(OnReceiveErrorListener onReceiveError) {\n    if (onReceiveError)\n        onReceiveErrorListener = onReceiveError;\n}\n\nvoid Request::setOnAbortListener(OnAbortListener onAbort) {\n    if (onAbort)\n        onAbortListener = onAbort;\n}\n\nconst char *Request::getOperationType() {\n    return operation ? operation->getOperationType() : \"UNDEFINED\";\n}\n\nvoid Request::setRequestSent() {\n    requestSent = true;\n}\n\nbool Request::isRequestSent() {\n    return requestSent;\n}\n\nnamespace MicroOcpp {\n\nstd::unique_ptr<Request> makeRequest(std::unique_ptr<Operation> operation){\n    if (operation == nullptr) {\n        return nullptr;\n    }\n    return std::unique_ptr<Request>(new Request(std::move(operation)));\n}\n\nstd::unique_ptr<Request> makeRequest(Operation *operation) {\n    return makeRequest(std::unique_ptr<Operation>(operation));\n}\n\n} //end namespace MicroOcpp\n"
  },
  {
    "path": "src/MicroOcpp/Core/Request.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_REQUEST_H\n#define MO_REQUEST_H\n\n#define MESSAGE_TYPE_CALL 2\n#define MESSAGE_TYPE_CALLRESULT 3\n#define MESSAGE_TYPE_CALLERROR 4\n\n#include <memory>\n\n#include <MicroOcpp/Core/RequestCallbacks.h>\n\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Operation;\nclass Model;\n\nclass Request : public MemoryManaged {\nprivate:\n    String messageID;\n    std::unique_ptr<Operation> operation;\n    void setMessageID(const char *id);\n    OnReceiveConfListener onReceiveConfListener = [] (JsonObject payload) {};\n    OnReceiveReqListener onReceiveReqListener = [] (JsonObject payload) {};\n    OnSendConfListener onSendConfListener = [] (JsonObject payload) {};\n    OnTimeoutListener onTimeoutListener = [] () {};\n    OnReceiveErrorListener onReceiveErrorListener = [] (const char *code, const char *description, JsonObject details) {};\n    OnAbortListener onAbortListener = [] () {};\n\n    unsigned long timeout_start = 0;\n    unsigned long timeout_period = 40000;\n    bool timed_out = false;\n    \n    unsigned long debugRequest_start = 0;\n\n    bool requestSent = false;\npublic:\n\n    Request(std::unique_ptr<Operation> msg);\n\n    ~Request();\n\n    Operation *getOperation();\n\n    void setTimeout(unsigned long timeout); //0 = disable timeout\n    bool isTimeoutExceeded();\n    void executeTimeout(); //call Timeout Listener\n    void setOnTimeoutListener(OnTimeoutListener onTimeout);\n\n    /**\n     * Sends the message(s) that belong to the OCPP Operation. This function puts a JSON message on the lower protocol layer.\n     * \n     * For instance operation Authorize: sends Authorize.req(idTag)\n     * \n     * This function is usually called multiple times by the Arduino loop(). On first call, the request is initially sent. In the\n     * succeeding calls, the implementers decide to either resend the request, or do nothing as the operation is still pending.\n     */\n    enum class CreateRequestResult {\n        Success,\n        Failure\n    };\n    CreateRequestResult createRequest(JsonDoc& out);\n\n   /**\n    * Decides if message belongs to this operation instance and if yes, proccesses it. Receives both Confirmations and Errors\n    * \n    * Returns true if JSON object has been consumed, false otherwise.\n    */\n    bool receiveResponse(JsonArray json);\n\n    /**\n     * Processes the request in the JSON document. Returns true on success, false on error.\n     * \n     * Returns false if the request doesn't belong to the corresponding operation instance\n     */\n    bool receiveRequest(JsonArray json);\n\n    /**\n     * After processing a request sent by the communication counterpart, this function sends a confirmation\n     * message. Returns true on success, false otherwise. Returns also true if a CallError has successfully\n     * been sent\n     */\n    enum class CreateResponseResult {\n        Success,\n        Pending,\n        Failure\n    };\n\n    CreateResponseResult createResponse(JsonDoc& out);\n\n    void setOnReceiveConfListener(OnReceiveConfListener onReceiveConf); //listener executed when we received the .conf() to a .req() we sent\n    void setOnReceiveReqListener(OnReceiveReqListener onReceiveReq); //listener executed when we receive a .req()\n    void setOnSendConfListener(OnSendConfListener onSendConf); //listener executed when we send a .conf() to a .req() we received\n\n    void setOnReceiveErrorListener(OnReceiveErrorListener onReceiveError);\n\n    /**\n     * The listener onAbort will be called whenever the engine stops trying to execute an operation normally which were initiated\n     * on this device. This includes timeouts or if the ocpp counterpart sends an error (then it will be called in addition to\n     * onTimeout or onReceiveError, respectively). Causes for onAbort:\n     * \n     *    - Cannot create OCPP payload\n     *    - Timeout\n     *    - Receives error msg instead of confirmation msg\n     * \n     * The engine uses this listener in both modes: EVSE mode and Central system mode\n     */\n    void setOnAbortListener(OnAbortListener onAbort);\n\n    const char *getOperationType();\n\n    void setRequestSent();\n    bool isRequestSent();\n};\n\n/*\n * Simple factory functions\n */\nstd::unique_ptr<Request> makeRequest(std::unique_ptr<Operation> op);\nstd::unique_ptr<Request> makeRequest(Operation *op); //takes ownership of op\n\n} //end namespace MicroOcpp\n\n #endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/RequestCallbacks.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_REQUESTCALLBACKS_H\n#define MO_REQUESTCALLBACKS_H\n\n#include <ArduinoJson.h>\n#include <functional>\n\nnamespace MicroOcpp {\n\nusing OnReceiveConfListener = std::function<void(JsonObject payload)>;\nusing OnReceiveReqListener = std::function<void(JsonObject payload)>;\nusing OnSendConfListener = std::function<void(JsonObject payload)>;\nusing OnTimeoutListener = std::function<void()>;\nusing OnReceiveErrorListener = std::function<void(const char *code, const char *description, JsonObject details)>; //will be called if OCPP communication partner returns error code\nusing OnAbortListener = std::function<void()>; //will be called whenever the engine will stop trying to execute the operation normallythere is a timeout or error (onAbort = onTimeout || onReceiveError)\n\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/RequestQueue.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <limits>\n\n#include <MicroOcpp/Core/RequestQueue.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/OcppError.h>\n#include <MicroOcpp/Core/OperationRegistry.h>\n#include <MicroOcpp/Operations/StatusNotification.h>\n\n#include <MicroOcpp/Debug.h>\n\nsize_t removePayload(const char *src, size_t src_size, char *dst, size_t dst_size);\n\nusing namespace MicroOcpp;\n\nVolatileRequestQueue::VolatileRequestQueue() : MemoryManaged(\"VolatileRequestQueue\") {\n\n}\n\nVolatileRequestQueue::~VolatileRequestQueue() = default;\n\nvoid VolatileRequestQueue::loop() {\n\n    /*\n     * Drop timed out operations\n     */\n    size_t i = 0;\n    while (i < len) {\n        size_t index = (front + i) % MO_REQUEST_CACHE_MAXSIZE;\n        auto& request = requests[index];\n\n        if (request->isTimeoutExceeded()) {\n            MO_DBG_INFO(\"operation timeout: %s\", request->getOperationType());\n            request->executeTimeout();\n\n            if (index == front) {\n                requests[front].reset();\n                front = (front + 1) % MO_REQUEST_CACHE_MAXSIZE;\n                len--;\n            } else {\n                requests[index].reset();\n                for (size_t i = (index + MO_REQUEST_CACHE_MAXSIZE - front) % MO_REQUEST_CACHE_MAXSIZE; i < len - 1; i++) {\n                    requests[(front + i) % MO_REQUEST_CACHE_MAXSIZE] = std::move(requests[(front + i + 1) % MO_REQUEST_CACHE_MAXSIZE]);\n                }\n                len--;\n            }\n        } else {\n            i++;\n        }\n    }\n}\n\nunsigned int VolatileRequestQueue::getFrontRequestOpNr() {\n    if (len == 0) {\n        return NoOperation;\n    }\n\n    return 1; //return OpNr 1 to grant PreBoot queue higher priority (=0), but send messages before tx-msg queue (starting with 10)\n}\n\nstd::unique_ptr<Request> VolatileRequestQueue::fetchFrontRequest() {\n    if (len == 0) {\n        return nullptr;\n    }\n\n    std::unique_ptr<Request> result = std::move(requests[front]);\n    front = (front + 1) % MO_REQUEST_CACHE_MAXSIZE;\n    len--;\n\n    MO_DBG_VERBOSE(\"front %zu len %zu\", front, len);\n\n    return result;\n}\n\nbool VolatileRequestQueue::pushRequestBack(std::unique_ptr<Request> request) {\n\n    // Don't queue up multiple StatusNotification messages for the same connectorId\n    #if 0 // Leads to ASAN failure when executed by Unit test suite (CustomOperation is casted to StatusNotification)\n    if (strcmp(request->getOperationType(), \"StatusNotification\") == 0)\n    {\n        size_t i = 0;\n        while (i < len) {\n            size_t index = (front + i) % MO_REQUEST_CACHE_MAXSIZE;\n\n            if (strcmp(requests[index]->getOperationType(), \"StatusNotification\")!= 0)\n            {\n                i++;\n                continue;\n            }\n            auto new_status_notification = static_cast<Ocpp16::StatusNotification*>(request->getOperation());\n            auto old_status_notification = static_cast<Ocpp16::StatusNotification*>(requests[index]->getOperation());\n            if (old_status_notification->getConnectorId() == new_status_notification->getConnectorId()) {\n                requests[index].reset();\n                for (size_t i = (index + MO_REQUEST_CACHE_MAXSIZE - front) % MO_REQUEST_CACHE_MAXSIZE; i < len - 1; i++) {\n                    requests[(front + i) % MO_REQUEST_CACHE_MAXSIZE] = std::move(requests[(front + i + 1) % MO_REQUEST_CACHE_MAXSIZE]);\n                }\n                len--;\n            } else {\n                i++;\n            }\n        }\n    }\n    #endif\n\n    if (len >= MO_REQUEST_CACHE_MAXSIZE) {\n        MO_DBG_INFO(\"Drop cached operation (cache full): %s\", requests[front]->getOperationType());\n        requests[front]->executeTimeout();\n        requests[front].reset();\n        front = (front + 1) % MO_REQUEST_CACHE_MAXSIZE;\n        len--;\n    }\n\n    requests[(front + len) % MO_REQUEST_CACHE_MAXSIZE] = std::move(request);\n    len++;\n    return true;\n}\n\nRequestQueue::RequestQueue(Connection& connection, OperationRegistry& operationRegistry)\n            : MemoryManaged(\"RequestQueue\"), connection(connection), operationRegistry(operationRegistry) {\n\n    ReceiveTXTcallback callback = [this] (const char *payload, size_t length) {\n        return this->receiveMessage(payload, length);\n    };\n    \n    connection.setReceiveTXTcallback(callback);\n\n    memset(sendQueues, 0, sizeof(sendQueues));\n    addSendQueue(&defaultSendQueue);\n}\n\nvoid RequestQueue::loop() {\n\n    /*\n     * Check if front request timed out\n     */\n    if (sendReqFront && sendReqFront->isTimeoutExceeded()) {\n        MO_DBG_INFO(\"operation timeout: %s\", sendReqFront->getOperationType());\n        sendReqFront->executeTimeout();\n        sendReqFront.reset();\n    }\n\n    if (recvReqFront && recvReqFront->isTimeoutExceeded()) {\n        MO_DBG_INFO(\"operation timeout: %s\", recvReqFront->getOperationType());\n        recvReqFront->executeTimeout();\n        recvReqFront.reset();\n    }\n\n    defaultSendQueue.loop();\n\n    if (!connection.isConnected()) {\n        return;\n    }\n\n    /**\n     * Send and dequeue a pending confirmation message, if existing\n     * \n     * If a message has been sent, terminate this loop() function.\n     */\n\n    if (!recvReqFront) {\n        recvReqFront = recvQueue.fetchFrontRequest();\n    }\n\n    if (recvReqFront) {\n\n        auto response = initJsonDoc(getMemoryTag());\n        auto ret = recvReqFront->createResponse(response);\n\n        if (ret == Request::CreateResponseResult::Success) {\n            auto out = makeString(getMemoryTag());\n            serializeJson(response, out);\n    \n            bool success = connection.sendTXT(out.c_str(), out.length());\n\n            if (success) {\n                MO_DBG_TRAFFIC_OUT(out.c_str());\n                recvReqFront.reset();\n            }\n\n            return;\n        } //else: There will be another attempt to send this conf message in a future loop call\n    }\n\n    /**\n     * Send pending req message\n     */\n\n    if (!sendReqFront) {\n\n        unsigned int minOpNr = RequestEmitter::NoOperation;\n        size_t index = MO_NUM_REQUEST_QUEUES;\n        for (size_t i = 0; i < MO_NUM_REQUEST_QUEUES && sendQueues[i]; i++) {\n            auto opNr = sendQueues[i]->getFrontRequestOpNr();\n            if (opNr < minOpNr) {\n                minOpNr = opNr;\n                index = i;\n            }\n        }\n\n        if (index < MO_NUM_REQUEST_QUEUES) {\n            sendReqFront = sendQueues[index]->fetchFrontRequest();\n        }\n    }\n\n    if (sendReqFront && !sendReqFront->isRequestSent()) {\n\n        auto request = initJsonDoc(getMemoryTag());\n        auto ret = sendReqFront->createRequest(request);\n\n        if (ret == Request::CreateRequestResult::Success) {\n\n            //send request\n            auto out = makeString(getMemoryTag());\n            serializeJson(request, out);\n\n            bool success = connection.sendTXT(out.c_str(), out.length());\n\n            if (success) {\n                MO_DBG_TRAFFIC_OUT(out.c_str());\n                sendReqFront->setRequestSent(); //mask as sent and wait for response / timeout\n            }\n\n            return;\n        }\n    }\n}\n\nvoid RequestQueue::sendRequest(std::unique_ptr<Request> op){\n    defaultSendQueue.pushRequestBack(std::move(op));\n}\n\nvoid RequestQueue::sendRequestPreBoot(std::unique_ptr<Request> op){\n    if (!preBootSendQueue) {\n        MO_DBG_ERR(\"did not set PreBoot queue\");\n        return;\n    }\n    preBootSendQueue->pushRequestBack(std::move(op));\n}\n\nvoid RequestQueue::addSendQueue(RequestEmitter* sendQueue) {\n    for (size_t i = 0; i < MO_NUM_REQUEST_QUEUES; i++) {\n        if (!sendQueues[i]) {\n            sendQueues[i] = sendQueue;\n            return;\n        }\n    }\n    MO_DBG_ERR(\"exceeded sendQueue capacity\");\n}\n\nvoid RequestQueue::setPreBootSendQueue(VolatileRequestQueue *preBootQueue) {\n    this->preBootSendQueue = preBootQueue;\n    addSendQueue(preBootQueue);\n}\n\nunsigned int RequestQueue::getNextOpNr() {\n    return nextOpNr++;\n}\n\nbool RequestQueue::receiveMessage(const char* payload, size_t length) {\n\n    MO_DBG_TRAFFIC_IN((int) length, payload);\n\n    size_t capacity_init = (3 * length) / 2;\n\n    //capacity = ceil capacity_init to the next power of two; should be at least 128\n\n    size_t capacity = 128;\n    while (capacity < capacity_init && capacity < MO_MAX_JSON_CAPACITY) {\n        capacity *= 2;\n    }\n    if (capacity > MO_MAX_JSON_CAPACITY) {\n        capacity = MO_MAX_JSON_CAPACITY;\n    }\n    \n    auto doc = initJsonDoc(getMemoryTag());\n    DeserializationError err = DeserializationError::NoMemory;\n\n    while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) {\n\n        doc = initJsonDoc(getMemoryTag(), capacity);\n        err = deserializeJson(doc, payload, length);\n\n        capacity *= 2;\n    }\n\n    bool success = false;\n\n    switch (err.code()) {\n        case DeserializationError::Ok: {\n            int messageTypeId = doc[0] | -1;\n\n            if (messageTypeId == MESSAGE_TYPE_CALL) {\n                receiveRequest(doc.as<JsonArray>());      \n                success = true;\n            } else if (messageTypeId == MESSAGE_TYPE_CALLRESULT ||\n                    messageTypeId == MESSAGE_TYPE_CALLERROR) {\n                receiveResponse(doc.as<JsonArray>());\n                success = true;\n            } else {\n                MO_DBG_WARN(\"Invalid OCPP message! (though JSON has successfully been deserialized)\");\n            }\n            break; \n        }\n        case DeserializationError::InvalidInput:\n            MO_DBG_WARN(\"Invalid input! Not a JSON\");\n            break;\n        case DeserializationError::NoMemory: {\n            MO_DBG_WARN(\"incoming operation exceeds buffer capacity. Input length = %zu, max capacity = %d\", length, MO_MAX_JSON_CAPACITY);\n\n            /*\n                * If websocket input is of message type MESSAGE_TYPE_CALL, send back a message of type MESSAGE_TYPE_CALLERROR.\n                * Then the communication counterpart knows that this operation failed.\n                * If the input type is MESSAGE_TYPE_CALLRESULT, then abort the operation to avoid getting stalled.\n                */\n\n            doc = initJsonDoc(getMemoryTag(), 200);\n            char onlyRpcHeader[200];\n            size_t onlyRpcHeader_len = removePayload(payload, length, onlyRpcHeader, sizeof(onlyRpcHeader));\n            DeserializationError err2 = deserializeJson(doc, onlyRpcHeader, onlyRpcHeader_len);\n            if (err2.code() == DeserializationError::Ok) {\n                int messageTypeId = doc[0] | -1;\n                if (messageTypeId == MESSAGE_TYPE_CALL) {\n                    success = true;\n                    auto op = makeRequest(new MsgBufferExceeded(MO_MAX_JSON_CAPACITY, length));\n                    receiveRequest(doc.as<JsonArray>(), std::move(op));\n                } else if (messageTypeId == MESSAGE_TYPE_CALLRESULT ||\n                            messageTypeId == MESSAGE_TYPE_CALLERROR) {\n                    success = true;\n                    MO_DBG_WARN(\"crop incoming response\");\n                    receiveResponse(doc.as<JsonArray>());\n                }\n            }\n            break;\n        }\n        default:\n            MO_DBG_WARN(\"Deserialization failed: %s\", err.c_str());\n            break;\n    }\n\n    return success;\n}\n\n/**\n * call conf() on each element of the queue. Start with first element. On successful message delivery,\n * delete the element from the list. Try all the pending OCPP Operations until the right one is found.\n * \n * This function could result in improper behavior in Charging Stations, because messages are not\n * guaranteed to be received and therefore processed in the right order.\n */\nvoid RequestQueue::receiveResponse(JsonArray json) {\n\n    if (!sendReqFront || !sendReqFront->receiveResponse(json)) {\n        MO_DBG_WARN(\"Received response doesn't match pending operation\");\n    }\n\n    sendReqFront.reset();\n}\n\nvoid RequestQueue::receiveRequest(JsonArray json) {\n    auto op = operationRegistry.deserializeOperation(json[2] | \"UNDEFINED\");\n    if (op == nullptr) {\n        MO_DBG_WARN(\"OOM\");\n        return;\n    }\n    receiveRequest(json, std::move(op));\n}\n\nvoid RequestQueue::receiveRequest(JsonArray json, std::unique_ptr<Request> op) {\n    op->receiveRequest(json); //execute the operation\n    recvQueue.pushRequestBack(std::move(op)); //enqueue so loop() plans conf sending\n}\n\n/*\n * Tries to recover the Ocpp-Operation header from a broken message.\n * \n * Example input: \n * [2, \"75705e50-682d-404e-b400-1bca33d41e19\", \"ChangeConfiguration\", {\"key\":\"now the message breaks...\n * \n * The Json library returns an error code when trying to deserialize that broken message. This\n * function searches for the first occurence of the character '{' and writes \"}]\" after it.\n * \n * Example output:\n * [2, \"75705e50-682d-404e-b400-1bca33d41e19\", \"ChangeConfiguration\", {}]\n *\n */\nsize_t removePayload(const char *src, size_t src_size, char *dst, size_t dst_size) {\n    size_t res_len = 0;\n    for (size_t i = 0; i < src_size && i < dst_size-3; i++) {\n        if (src[i] == '\\0'){\n            //no payload found within specified range. Cancel execution\n            break;\n        }\n        dst[i] = src[i];\n        if (src[i] == '{') {\n            dst[i+1] = '}';\n            dst[i+2] = ']';\n            res_len = i+3;\n            break;\n        }\n    }\n    dst[res_len] = '\\0';\n    res_len++;\n    return res_len;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Core/RequestQueue.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_REQUESTQUEUE_H\n#define MO_REQUESTQUEUE_H\n\n#include <limits>\n\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#include <memory>\n#include <ArduinoJson.h>\n\n#ifndef MO_REQUEST_CACHE_MAXSIZE\n#define MO_REQUEST_CACHE_MAXSIZE 10\n#endif\n\n#ifndef MO_NUM_REQUEST_QUEUES\n#define MO_NUM_REQUEST_QUEUES 10\n#endif\n\nnamespace MicroOcpp {\n\nclass Connection;\nclass OperationRegistry;\nclass Request;\n\nclass RequestEmitter {\npublic:\n    static const unsigned int NoOperation = std::numeric_limits<unsigned int>::max();\n\n    virtual unsigned int getFrontRequestOpNr() = 0; //return OpNr of front request or NoOperation if queue is empty\n    virtual std::unique_ptr<Request> fetchFrontRequest() = 0;\n};\n\nclass VolatileRequestQueue : public RequestEmitter, public MemoryManaged {\nprivate:\n    std::unique_ptr<Request> requests [MO_REQUEST_CACHE_MAXSIZE];\n    size_t front = 0, len = 0;\npublic:\n    VolatileRequestQueue();\n    ~VolatileRequestQueue();\n    void loop();\n\n    unsigned int getFrontRequestOpNr() override;\n    std::unique_ptr<Request> fetchFrontRequest() override;\n\n    bool pushRequestBack(std::unique_ptr<Request> request);\n};\n\nclass RequestQueue : public MemoryManaged {\nprivate:\n    Connection& connection;\n    OperationRegistry& operationRegistry;\n\n    RequestEmitter* sendQueues [MO_NUM_REQUEST_QUEUES];\n    VolatileRequestQueue defaultSendQueue;\n    VolatileRequestQueue *preBootSendQueue = nullptr;\n    std::unique_ptr<Request> sendReqFront;\n\n    VolatileRequestQueue recvQueue;\n    std::unique_ptr<Request> recvReqFront;\n\n    bool receiveMessage(const char* payload, size_t length); //receive from  server: either a request or response\n    void receiveRequest(JsonArray json);\n    void receiveRequest(JsonArray json, std::unique_ptr<Request> op);\n    void receiveResponse(JsonArray json);\n\n    unsigned long sockTrackLastConnected = 0;\n\n    unsigned int nextOpNr = 10; //Nr 0 - 9 reservered for internal purposes\npublic:\n    RequestQueue() = delete;\n    RequestQueue(const RequestQueue&) = delete;\n    RequestQueue(const RequestQueue&&) = delete;\n\n    RequestQueue(Connection& connection, OperationRegistry& operationRegistry);\n\n    void loop(); //polls all reqQueues and decides which request to send (if any)\n\n    void sendRequest(std::unique_ptr<Request> request); //send an OCPP operation request to the server; adds request to default queue\n    void sendRequestPreBoot(std::unique_ptr<Request> request); //send an OCPP operation request to the server; adds request to preBootQueue\n\n    void addSendQueue(RequestEmitter* sendQueue);\n    void setPreBootSendQueue(VolatileRequestQueue *preBootQueue);\n\n    unsigned int getNextOpNr();\n};\n\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/Time.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Time.h>\n#include <string.h>\n#include <ctype.h>\t\n\nnamespace MicroOcpp {\n\nconst Timestamp MIN_TIME = Timestamp(2010, 0, 0, 0, 0, 0);\nconst Timestamp MAX_TIME = Timestamp(2037, 0, 0, 0, 0, 0);\n\nTimestamp::Timestamp() : MemoryManaged(\"Timestamp\") {\n    \n}\n\nTimestamp::Timestamp(const Timestamp& other) : MemoryManaged(\"Timestamp\") {\n    *this = other;\n}\n\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    Timestamp::Timestamp(int16_t year, int16_t month, int16_t day, int32_t hour, int32_t minute, int32_t second, int32_t ms) :\n                MemoryManaged(\"Timestamp\"), year(year), month(month), day(day), hour(hour), minute(minute), second(second), ms(ms) { }\n#else \n    Timestamp::Timestamp(int16_t year, int16_t month, int16_t day, int32_t hour, int32_t minute, int32_t second) :\n                MemoryManaged(\"Timestamp\"), year(year), month(month), day(day), hour(hour), minute(minute), second(second) { }\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\nint noDays(int month, int year) {\n    return (month == 0 || month == 2 || month == 4 || month == 6 || month == 7 || month == 9 || month == 11) ? 31 :\n            ((month == 3 || month == 5 || month == 8 || month == 10) ? 30 :\n            ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 29 : 28));\n}\n\nbool Timestamp::setTime(const char *jsonDateString) {\n\n    const int JSONDATE_MINLENGTH = 19;\n\n    if (strlen(jsonDateString) < JSONDATE_MINLENGTH){\n        return false;\n    }\n\n    if (!isdigit(jsonDateString[0]) ||  //2\n        !isdigit(jsonDateString[1]) ||    //0\n        !isdigit(jsonDateString[2]) ||    //1\n        !isdigit(jsonDateString[3]) ||    //3\n        jsonDateString[4] != '-' ||       //-\n        !isdigit(jsonDateString[5]) ||    //0\n        !isdigit(jsonDateString[6]) ||    //2\n        jsonDateString[7] != '-' ||       //-\n        !isdigit(jsonDateString[8]) ||    //0\n        !isdigit(jsonDateString[9]) ||    //1\n        jsonDateString[10] != 'T' ||      //T\n        !isdigit(jsonDateString[11]) ||   //2\n        !isdigit(jsonDateString[12]) ||   //0\n        jsonDateString[13] != ':' ||      //:\n        !isdigit(jsonDateString[14]) ||   //5\n        !isdigit(jsonDateString[15]) ||   //3\n        jsonDateString[16] != ':' ||      //:\n        !isdigit(jsonDateString[17]) ||   //3\n        !isdigit(jsonDateString[18])) {   //2\n                                        //ignore subsequent characters\n        return false;\n    }\n    \n    int year  =  (jsonDateString[0] - '0') * 1000 +\n                (jsonDateString[1] - '0') * 100 +\n                (jsonDateString[2] - '0') * 10 +\n                (jsonDateString[3] - '0');\n    int month =  (jsonDateString[5] - '0') * 10 +\n                (jsonDateString[6] - '0') - 1;\n    int day   =  (jsonDateString[8] - '0') * 10 +\n                (jsonDateString[9] - '0') - 1;\n    int hour  =  (jsonDateString[11] - '0') * 10 +\n                (jsonDateString[12] - '0');\n    int minute = (jsonDateString[14] - '0') * 10 +\n                (jsonDateString[15] - '0');\n    int second = (jsonDateString[17] - '0') * 10 +\n                (jsonDateString[18] - '0');\n\n    //optional fractals\n    int ms = 0;\n    if (jsonDateString[19] == '.') {\n        if (isdigit(jsonDateString[20]) ||   //1\n            isdigit(jsonDateString[21]) ||   //2\n            isdigit(jsonDateString[22])) {\n            \n            ms  =  (jsonDateString[20] - '0') * 100 +\n                    (jsonDateString[21] - '0') * 10 +\n                    (jsonDateString[22] - '0');\n        } else {\n            return false;\n        }\n    }\n\n    if (year < 1970 || year >= 2038 ||\n        month < 0 || month >= 12 ||\n        day < 0 || day >= noDays(month, year) ||\n        hour < 0 || hour >= 24 ||\n        minute < 0 || minute >= 60 ||\n        second < 0 || second > 60 || //tolerate leap seconds -- (23:59:60) can be a valid time\n        ms < 0 || ms >= 1000) {\n        return false;\n    }\n\n    this->year = year;\n    this->month = month;\n    this->day = day;\n    this->hour = hour;\n    this->minute = minute;\n    this->second = second;\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    this->ms = ms;\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n    \n    return true;\n}\n\nbool Timestamp::toJsonString(char *jsonDateString, size_t buffsize) const {\n    if (buffsize < JSONDATE_LENGTH + 1) return false;\n\n    jsonDateString[0] = ((char) ((year / 1000) % 10)) + '0';\n    jsonDateString[1] = ((char) ((year / 100) % 10)) + '0';\n    jsonDateString[2] = ((char) ((year / 10) % 10))  + '0';\n    jsonDateString[3] = ((char) ((year / 1) % 10))  + '0';\n    jsonDateString[4] = '-';\n    jsonDateString[5] = ((char) (((month + 1) / 10) % 10))  + '0';\n    jsonDateString[6] = ((char) (((month + 1) / 1) % 10))  + '0';\n    jsonDateString[7] = '-';\n    jsonDateString[8] = ((char) (((day + 1) / 10) % 10))  + '0';\n    jsonDateString[9] = ((char) (((day + 1) / 1) % 10))  + '0';\n    jsonDateString[10] = 'T';\n    jsonDateString[11] = ((char) ((hour / 10) % 10))  + '0';\n    jsonDateString[12] = ((char) ((hour / 1) % 10))  + '0';\n    jsonDateString[13] = ':';\n    jsonDateString[14] = ((char) ((minute / 10) % 10))  + '0';\n    jsonDateString[15] = ((char) ((minute / 1) % 10))  + '0';\n    jsonDateString[16] = ':';\n    jsonDateString[17] = ((char) ((second / 10) % 10))  + '0';\n    jsonDateString[18] = ((char) ((second / 1) % 10))  + '0';\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    jsonDateString[19] = '.';\n    jsonDateString[20] = ((char) ((ms / 100) % 10))  + '0';\n    jsonDateString[21] = ((char) ((ms / 10) % 10))  + '0';\n    jsonDateString[22] = ((char) ((ms / 1) % 10))  + '0';\n    jsonDateString[23] = 'Z';\n    jsonDateString[24] = '\\0';\n#else\n    jsonDateString[19] = 'Z';\n    jsonDateString[20] = '\\0';\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\n    return true;\n}\n\nTimestamp &Timestamp::operator+=(int secs) {\n\n    second += secs;\n\n    if (second >= 0 && second < 60) return *this;\n\n    minute += second / 60;\n    second %= 60;\n    if (second < 0) {\n        minute--;\n        second += 60;\n    }\n\n    if (minute >= 0 && minute < 60) return *this;\n\n    hour += minute / 60;\n    minute %= 60;\n    if (minute < 0) {\n        hour--;\n        minute += 60;\n    }\n\n    if (hour >= 0 && hour < 24) return *this;\n\n    day += hour / 24;\n    hour %= 24;\n    if (hour < 0) {\n        day--;\n        hour += 24;\n    }\n\n    while (day >= noDays(month, year)) {\n        day -= noDays(month, year);\n        month++;\n        \n        if (month >= 12) {\n            month -= 12;\n            year++;\n        }\n    }\n\n    while (day < 0) {\n        month--;\n        if (month < 0) {\n            month += 12;\n            year--;\n        }\n        day += noDays(month, year);\n    }\n\n    return *this;\n}\n\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\nTimestamp &Timestamp::addMilliseconds(int val) {\n\n    ms += val;\n\n    if (ms >= 0 && ms < 1000) return *this;\n    \n    auto dsecond = ms / 1000;\n    ms %= 1000;\n    if (ms < 0) {\n        dsecond--;\n        ms += 1000;\n    }\n    return this->operator+=(dsecond);\n}\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\nTimestamp &Timestamp::operator-=(int secs) {\n    return operator+=(-secs);\n}\n\nint Timestamp::operator-(const Timestamp &rhs) const {\n    //dt = rhs - mocpp_base\n    \n    int16_t year_base, year_end;\n    if (year <= rhs.year) {\n        year_base = year;\n        year_end = rhs.year;\n    } else {\n        year_base = rhs.year;\n        year_end = year;\n    }\n\n    int16_t lhsDays = day;\n    int16_t rhsDays = rhs.day;\n\n    for (int16_t iy = year_base; iy <= year_end; iy++) {\n        for (int16_t im = 0; im < 12; im++) {\n            if (year > iy || (year == iy && month > im)) {\n                lhsDays += noDays(im, iy);\n            }\n            if (rhs.year > iy || (rhs.year == iy && rhs.month > im)) {\n                rhsDays += noDays(im, iy);\n            }\n        }\n    }\n\n    int dt = (lhsDays - rhsDays) * (24 * 3600) + (hour - rhs.hour) * 3600 + (minute - rhs.minute) * 60 + second - rhs.second;\n\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    // Make it so that we round the difference to the nearest second, instead of being up to almost a whole second off\n    if ((ms - rhs.ms) > 500) dt++;\n    if ((ms - rhs.ms) < -500) dt--;\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\n    return dt;\n}\n\nTimestamp &Timestamp::operator=(const Timestamp &rhs) {\n    year = rhs.year;\n    month = rhs.month;\n    day = rhs.day;\n    hour = rhs.hour;\n    minute = rhs.minute;\n    second = rhs.second;\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    ms = rhs.ms;\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\n    return *this;\n}\n\nTimestamp operator+(const Timestamp &lhs, int secs) {\n    Timestamp res = lhs;\n    res += secs;\n    return res;\n}\n\nTimestamp operator-(const Timestamp &lhs, int secs) {\n    return operator+(lhs, -secs);\n}\n\nbool operator==(const Timestamp &lhs, const Timestamp &rhs) {\n    return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day && lhs.hour == rhs.hour && lhs.minute == rhs.minute && lhs.second == rhs.second\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    && lhs.ms == rhs.ms\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n    ;\n}\n\nbool operator!=(const Timestamp &lhs, const Timestamp &rhs) {\n    return !(lhs == rhs);\n}\n\nbool operator<(const Timestamp &lhs, const Timestamp &rhs) {\n    if (lhs.year != rhs.year)\n        return lhs.year < rhs.year;\n    if (lhs.month != rhs.month)\n        return lhs.month < rhs.month;\n    if (lhs.day != rhs.day)\n        return lhs.day < rhs.day;\n    if (lhs.hour != rhs.hour)\n        return lhs.hour < rhs.hour;\n    if (lhs.minute != rhs.minute)\n        return lhs.minute < rhs.minute;\n    if (lhs.second != rhs.second)\n        return lhs.second < rhs.second;\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    if (lhs.ms != rhs.ms)\n        return lhs.ms < rhs.ms;\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n    return false;  \n}\n\nbool operator<=(const Timestamp &lhs, const Timestamp &rhs) {\n    return lhs < rhs || lhs == rhs;\n}\n\nbool operator>(const Timestamp &lhs, const Timestamp &rhs) {\n    return rhs < lhs;\n}\n\nbool operator>=(const Timestamp &lhs, const Timestamp &rhs) {\n    return rhs <= lhs;\n}\n\n\nClock::Clock() {\n\n}\n\nbool Clock::setTime(const char* jsonDateString) {\n\n    Timestamp timestamp = Timestamp();\n    \n    if (!timestamp.setTime(jsonDateString)) {\n        return false;\n    }\n\n    system_basetime = mocpp_tick_ms();\n    mocpp_basetime = timestamp;\n\n    currentTime = mocpp_basetime;\n    lastUpdate = system_basetime;\n\n    return true;\n}\n\nconst Timestamp &Clock::now() {\n    auto tReading = mocpp_tick_ms();\n    auto delta = tReading - lastUpdate;\n\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    currentTime.addMilliseconds(delta);\n    lastUpdate = tReading;\n#else\n    auto deltaSecs = delta / 1000;\n    currentTime += deltaSecs;\n    lastUpdate += deltaSecs * 1000;\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\n    return currentTime;\n}\n\nTimestamp Clock::adjustPrebootTimestamp(const Timestamp& t) {\n    auto systemtime_in = t - Timestamp();\n    if (systemtime_in > (int) system_basetime / 1000) {\n        return mocpp_basetime;\n    }\n    return mocpp_basetime - ((int) (system_basetime / 1000) - systemtime_in);\n}\n\n}\n"
  },
  {
    "path": "src/MicroOcpp/Core/Time.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_TIME_H\n#define MO_TIME_H\n\n#include <functional>\n#include <stdint.h>\n#include <stddef.h>\n\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Platform.h>\n\n#ifndef MO_ENABLE_TIMESTAMP_MILLISECONDS\n#define MO_ENABLE_TIMESTAMP_MILLISECONDS 0\n#endif\n\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n#define JSONDATE_LENGTH 24   //max. ISO 8601 date length, excluding the terminating zero\n#else\n#define JSONDATE_LENGTH 20   //ISO 8601 date length, excluding the terminating zero\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\nnamespace MicroOcpp {\n\nclass Timestamp : public MemoryManaged {\nprivate:\n    /*\n     * Internal representation of the current time. The initial values correspond to UNIX-time 0. January\n     * corresponds to month 0 and the first day in the month is day 0.\n     */\n    int16_t year = 1970;\n    int16_t month = 0;\n    int16_t day = 0;\n    int32_t hour = 0;\n    int32_t minute = 0;\n    int32_t second = 0;\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    int32_t ms = 0;\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\npublic:\n\n    Timestamp();\n\n    Timestamp(const Timestamp& other);\n\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    Timestamp(int16_t year, int16_t month, int16_t day, int32_t hour, int32_t minute, int32_t second, int32_t ms = 0);\n#else \n    Timestamp(int16_t year, int16_t month, int16_t day, int32_t hour, int32_t minute, int32_t second);\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\n    /**\n     * Expects a date string like\n     * 2020-10-01T20:53:32.486Z\n     * \n     * as generated in JavaScript by calling toJSON() on a Date object\n     * \n     * Only processes the first 19 characters. The subsequent are ignored until terminating 0.\n     * \n     * Has a semi-sophisticated type check included. Will return true on successful time set and false if\n     * the given string is not a JSON Date string.\n     * \n     * jsonDateString: 0-terminated string\n     */\n    bool setTime(const char* jsonDateString);\n\n    bool toJsonString(char *out, size_t buffsize) const;\n\n    Timestamp &operator=(const Timestamp &rhs);\n\n#if MO_ENABLE_TIMESTAMP_MILLISECONDS\n    Timestamp &addMilliseconds(int ms);\n#endif //MO_ENABLE_TIMESTAMP_MILLISECONDS\n\n    /*\n     * Time periods are given in seconds for all of the following arithmetic operations\n     */\n    Timestamp &operator+=(int secs);\n    Timestamp &operator-=(int secs);\n\n    int operator-(const Timestamp &rhs) const;\n\n    friend Timestamp operator+(const Timestamp &lhs, int secs);\n    friend Timestamp operator-(const Timestamp &lhs, int secs);\n\n    friend bool operator==(const Timestamp &lhs, const Timestamp &rhs);\n    friend bool operator!=(const Timestamp &lhs, const Timestamp &rhs);\n    friend bool operator<(const Timestamp &lhs, const Timestamp &rhs);\n    friend bool operator<=(const Timestamp &lhs, const Timestamp &rhs);\n    friend bool operator>(const Timestamp &lhs, const Timestamp &rhs);\n    friend bool operator>=(const Timestamp &lhs, const Timestamp &rhs);\n};\n\nextern const Timestamp MIN_TIME;\nextern const Timestamp MAX_TIME;\n\nclass Clock {\nprivate:\n\n    Timestamp mocpp_basetime = Timestamp();\n    decltype(mocpp_tick_ms()) system_basetime = 0; //the value of mocpp_tick_ms() when OCPP server's time was taken\n    decltype(mocpp_tick_ms()) lastUpdate = 0;\n\n    Timestamp currentTime = Timestamp();\n\npublic:\n\n    Clock();\n    Clock(const Clock&) = delete;\n    Clock(const Clock&&) = delete;\n    Clock& operator=(const Clock&) = delete;\n\n    const Timestamp &now();\n\n    /**\n     * Expects a date string like\n     * 2020-10-01T20:53:32.486Z\n     * \n     * as generated in JavaScript by calling toJSON() on a Date object\n     * \n     * Only processes the first 23 characters. The subsequent are ignored\n     * \n     * Has a semi-sophisticated type check included. Will return true on successful time set and false if\n     * the given string is not a JSON Date string.\n     * \n     * jsonDateString: 0-terminated string\n     */\n    bool setTime(const char* jsonDateString);\n\n    /*\n     * Timestamps which were taken before the Clock was initially set can be adjusted retrospectively. Two\n     * conditions must be true: the Clock was set in the meantime and the Timestamp was taken at the same\n     * run of this library. The caller must check this\n     */\n    Timestamp adjustPrebootTimestamp(const Timestamp& t);\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Core/UuidUtils.cpp",
    "content": "#include <MicroOcpp/Core/UuidUtils.h>\n#include <MicroOcpp/Platform.h>\n\n#include <stdint.h>\n\nnamespace MicroOcpp {\n\n#define UUID_STR_LEN 36\n\nbool generateUUID(char *uuidBuffer, size_t len) {\n  if (len < UUID_STR_LEN + 1)\n  {\n    return false;\n  }\n\n  uint32_t ar[4];\n  for (uint8_t i = 0; i < 4; i++) {\n    ar[i] = mocpp_rng();\n  }\n\n  // Conforming to RFC 4122 Specification\n  // - byte 7: four most significant bits ==> 0100  --> always 4\n  // - byte 9: two  most significant bits ==> 10    --> always {8, 9, A, B}.\n  //\n  // patch bits for version 1 and variant 4 here\n  ar[1] &= 0xFFF0FFFF;   //  remove 4 bits.\n  ar[1] |= 0x00040000;   //  variant 4\n  ar[2] &= 0xFFFFFFF3;   //  remove 2 bits\n  ar[2] |= 0x00000008;   //  version 1\n\n  // loop through the random 16 byte array\n  for (uint8_t i = 0, j = 0; i < 16; i++) {\n    // multiples of 4 between 8 and 20 get a -.\n    // note we are processing 2 digits in one loop.\n    if ((i & 0x1) == 0) {\n      if ((4 <= i) && (i <= 10)) {\n        uuidBuffer[j++] = '-';\n      }\n    }\n\n    // encode the byte as two hex characters\n    uint8_t nr   = i / 4;\n    uint8_t xx   = ar[nr];\n    uint8_t ch   = xx & 0x0F;\n    uuidBuffer[j++] = (ch < 10)? '0' + ch : ('a' - 10) + ch;\n\n    ch = (xx >> 4) & 0x0F;\n    ar[nr] >>= 8;\n    uuidBuffer[j++] = (ch < 10)? '0' + ch : ('a' - 10) + ch;\n  }\n\n  uuidBuffer[UUID_STR_LEN] = 0;\n  return true;\n}\n\n}\n"
  },
  {
    "path": "src/MicroOcpp/Core/UuidUtils.h",
    "content": "#ifndef MO_UUIDUTILS_H\n#define MO_UUIDUTILS_H\n\n#include <stddef.h>\nnamespace MicroOcpp {\n\n// Generates a UUID (Universally Unique Identifier) and writes it into a given buffer\n// Returns false if the generation failed\n// The buffer must be at least 37 bytes long (36 characters + zero termination)\nbool generateUUID(char *uuidBuffer, size_t len);\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Debug.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <string.h>\n\n#include <MicroOcpp/Debug.h>\n\nconst char *level_label [] = {\n    \"\",         //MO_DL_NONE 0x00\n    \"ERROR\",    //MO_DL_ERROR 0x01\n    \"warning\",  //MO_DL_WARN 0x02\n    \"info\",     //MO_DL_INFO 0x03\n    \"debug\",    //MO_DL_DEBUG 0x04\n    \"verbose\"   //MO_DL_VERBOSE 0x05\n};\n\n#if MO_DBG_FORMAT == MO_DF_MINIMAL\nvoid mo_dbg_print_prefix(int level, const char *fn, int line) {\n    (void)0;\n}\n\n#elif MO_DBG_FORMAT == MO_DF_COMPACT\nvoid mo_dbg_print_prefix(int level, const char *fn, int line) {\n        size_t l = strlen(fn);\n        size_t r = l;\n        while (l > 0 && fn[l-1] != '/' && fn[l-1] != '\\\\') {\n            l--;\n            if (fn[l] == '.') r = l;\n        }\n        MO_CONSOLE_PRINTF(\"%.*s:%i \", (int) (r - l), fn + l, line);\n}\n\n#elif MO_DBG_FORMAT == MO_DF_FILE_LINE\nvoid mo_dbg_print_prefix(int level, const char *fn, int line) {\n    size_t l = strlen(fn);\n    while (l > 0 && fn[l-1] != '/' && fn[l-1] != '\\\\') {\n        l--;\n    }\n    MO_CONSOLE_PRINTF(\"[MO] %s (%s:%i): \", level_label[level], fn + l, line);\n}\n\n#elif MO_DBG_FORMAT == MO_DF_FULL\nvoid mo_dbg_print_prefix(int level, const char *fn, int line) {\n    MO_CONSOLE_PRINTF(\"[MO] %s (%s:%i): \", level_label[level], fn, line);\n}\n\n#else\n#error invalid MO_DBG_FORMAT definition\n#endif\n\nvoid mo_dbg_print_suffix() {\n    MO_CONSOLE_PRINTF(\"\\n\");\n}\n"
  },
  {
    "path": "src/MicroOcpp/Debug.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_DEBUG_H\n#define MO_DEBUG_H\n\n#include <MicroOcpp/Platform.h>\n\n#define MO_DL_NONE 0x00     //suppress all output to the console\n#define MO_DL_ERROR 0x01    //report failures\n#define MO_DL_WARN 0x02     //report observed or assumed inconsistent state\n#define MO_DL_INFO 0x03     //inform about internal state changes\n#define MO_DL_DEBUG 0x04    //relevant info for debugging\n#define MO_DL_VERBOSE 0x05  //all output\n\n#ifndef MO_DBG_LEVEL\n#define MO_DBG_LEVEL MO_DL_INFO  //default\n#endif\n\n//MbedTLS debug level documented in mbedtls/debug.h:\n#ifndef MO_DBG_LEVEL_MBEDTLS\n#define MO_DBG_LEVEL_MBEDTLS 1\n#endif\n\n#define MO_DF_MINIMAL 0x00   //don't reveal origin of a debug message\n#define MO_DF_COMPACT 0x01   //print module by file name and line number\n#define MO_DF_FILE_LINE 0x02 //print file and line number\n#define MO_DF_FULL 0x03      //print path and file and line numbr\n\n#ifndef MO_DBG_FORMAT\n#define MO_DBG_FORMAT MO_DF_FILE_LINE //default\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid mo_dbg_print_prefix(int level, const char *fn, int line);\nvoid mo_dbg_print_suffix();\n\n#ifdef __cplusplus\n}\n#endif\n\n#define MO_DBG(level, X) \\\n    do { \\\n        mo_dbg_print_prefix(level, __FILE__, __LINE__); \\\n        MO_CONSOLE_PRINTF X; \\\n        mo_dbg_print_suffix(); \\\n    } while (0)\n\n#if MO_DBG_LEVEL >= MO_DL_ERROR\n#define MO_DBG_ERR(...) MO_DBG(MO_DL_ERROR,(__VA_ARGS__))\n#else\n#define MO_DBG_ERR(...) ((void)0)\n#endif\n\n#if MO_DBG_LEVEL >= MO_DL_WARN\n#define MO_DBG_WARN(...) MO_DBG(MO_DL_WARN,(__VA_ARGS__))\n#else\n#define MO_DBG_WARN(...) ((void)0)\n#endif\n\n#if MO_DBG_LEVEL >= MO_DL_INFO\n#define MO_DBG_INFO(...) MO_DBG(MO_DL_INFO,(__VA_ARGS__))\n#else\n#define MO_DBG_INFO(...) ((void)0)\n#endif\n\n#if MO_DBG_LEVEL >= MO_DL_DEBUG\n#define MO_DBG_DEBUG(...) MO_DBG(MO_DL_DEBUG,(__VA_ARGS__))\n#else\n#define MO_DBG_DEBUG(...) ((void)0)\n#endif\n\n#if MO_DBG_LEVEL >= MO_DL_VERBOSE\n#define MO_DBG_VERBOSE(...) MO_DBG(MO_DL_VERBOSE,(__VA_ARGS__))\n#else\n#define MO_DBG_VERBOSE(...) ((void)0)\n#endif\n\n#ifdef MO_TRAFFIC_OUT\n\n#define MO_DBG_TRAFFIC_OUT(...)   \\\n    do {                        \\\n        MO_CONSOLE_PRINTF(\"[MO] Send: %s\",__VA_ARGS__);           \\\n        MO_CONSOLE_PRINTF(\"\\n\");         \\\n    } while (0)\n\n#define MO_DBG_TRAFFIC_IN(...)   \\\n    do {                        \\\n        MO_CONSOLE_PRINTF(\"[MO] Recv: %.*s\",__VA_ARGS__);           \\\n        MO_CONSOLE_PRINTF(\"\\n\");         \\\n    } while (0)\n\n#else\n#define MO_DBG_TRAFFIC_OUT(...) ((void)0)\n#define MO_DBG_TRAFFIC_IN(...)  ((void)0)\n#endif\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Authorization/AuthorizationData.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Model/Authorization/AuthorizationData.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nAuthorizationData::AuthorizationData() : MemoryManaged(\"v16.Authorization.AuthorizationData\") {\n\n}\n\nAuthorizationData::AuthorizationData(AuthorizationData&& other) : MemoryManaged(\"v16.Authorization.AuthorizationData\") {\n    operator=(std::move(other));\n}\n\nAuthorizationData::~AuthorizationData() {\n    MO_FREE(parentIdTag);\n    parentIdTag = nullptr;\n}\n\nAuthorizationData& AuthorizationData::operator=(AuthorizationData&& other) {\n    parentIdTag = other.parentIdTag;\n    other.parentIdTag = nullptr;\n    expiryDate = std::move(other.expiryDate);\n    strncpy(idTag, other.idTag, IDTAG_LEN_MAX + 1);\n    idTag[IDTAG_LEN_MAX] = '\\0';\n    status = other.status;\n    return *this;\n}\n\nvoid AuthorizationData::readJson(JsonObject entry, bool compact) {\n    if (entry.containsKey(AUTHDATA_KEY_IDTAG(compact))) {\n        strncpy(idTag, entry[AUTHDATA_KEY_IDTAG(compact)], IDTAG_LEN_MAX + 1);\n        idTag[IDTAG_LEN_MAX] = '\\0';\n    } else {\n        idTag[0] = '\\0';\n    }\n\n    JsonObject idTagInfo;\n    if (compact){\n        idTagInfo = entry;\n    } else {\n        idTagInfo = entry[AUTHDATA_KEY_IDTAGINFO];\n    }\n\n    if (idTagInfo.containsKey(AUTHDATA_KEY_EXPIRYDATE(compact))) {\n        expiryDate = std::unique_ptr<Timestamp>(new Timestamp());\n        if (!expiryDate->setTime(idTagInfo[AUTHDATA_KEY_EXPIRYDATE(compact)])) {\n            expiryDate.reset();\n        }\n    } else {\n        expiryDate.reset();\n    }\n\n    if (idTagInfo.containsKey(AUTHDATA_KEY_PARENTIDTAG(compact))) {\n        MO_FREE(parentIdTag);\n        parentIdTag = nullptr;\n        parentIdTag = static_cast<char*>(MO_MALLOC(getMemoryTag(), IDTAG_LEN_MAX + 1));\n        if (parentIdTag) {\n            strncpy(parentIdTag, idTagInfo[AUTHDATA_KEY_PARENTIDTAG(compact)], IDTAG_LEN_MAX + 1);\n            parentIdTag[IDTAG_LEN_MAX] = '\\0';\n        } else {\n            MO_DBG_ERR(\"OOM\");\n        }\n    } else {\n        MO_FREE(parentIdTag);\n        parentIdTag = nullptr;\n    }\n\n    if (idTagInfo.containsKey(AUTHDATA_KEY_STATUS(compact))) {\n        status = deserializeAuthorizationStatus(idTagInfo[AUTHDATA_KEY_STATUS(compact)]);\n    } else {\n        if (compact) {\n            status = AuthorizationStatus::Accepted;\n        } else {\n            status = AuthorizationStatus::UNDEFINED;\n        }\n    }\n}\n\nsize_t AuthorizationData::getJsonCapacity() const {\n    return JSON_OBJECT_SIZE(2) +\n            (idTag[0] != '\\0' ? \n                JSON_OBJECT_SIZE(1) : 0) +\n            (expiryDate ?\n                JSON_OBJECT_SIZE(1) + JSONDATE_LENGTH + 1 : 0) +\n            (parentIdTag ?\n                JSON_OBJECT_SIZE(1) : 0) +\n            (status != AuthorizationStatus::UNDEFINED ?\n                JSON_OBJECT_SIZE(1) : 0);\n}\n\nvoid AuthorizationData::writeJson(JsonObject& entry, bool compact) {\n    if (idTag[0] != '\\0') {\n        entry[AUTHDATA_KEY_IDTAG(compact)] = (const char*) idTag;\n    }\n\n    JsonObject idTagInfo;\n    if (compact) {\n        idTagInfo = entry;\n    } else {\n        idTagInfo = entry.createNestedObject(AUTHDATA_KEY_IDTAGINFO);\n    }\n\n    if (expiryDate) {\n        char buf [JSONDATE_LENGTH + 1];\n        if (expiryDate->toJsonString(buf, JSONDATE_LENGTH + 1)) {\n            idTagInfo[AUTHDATA_KEY_EXPIRYDATE(compact)] = buf;\n        }\n    }\n\n    if (parentIdTag) {\n        idTagInfo[AUTHDATA_KEY_PARENTIDTAG(compact)] = (const char *) parentIdTag;\n    }\n\n    if (status != AuthorizationStatus::Accepted) {\n        idTagInfo[AUTHDATA_KEY_STATUS(compact)] = serializeAuthorizationStatus(status);\n    } else if (!compact) {\n        idTagInfo[AUTHDATA_KEY_STATUS(compact)] = serializeAuthorizationStatus(AuthorizationStatus::Invalid);\n    }\n}\n\nconst char *AuthorizationData::getIdTag() const {\n    return idTag;\n}\nTimestamp *AuthorizationData::getExpiryDate() const {\n    return expiryDate.get();\n}\nconst char *AuthorizationData::getParentIdTag() const {\n    return parentIdTag;\n}\nAuthorizationStatus AuthorizationData::getAuthorizationStatus() const {\n    return status;\n}\n\nvoid AuthorizationData::reset() {\n    idTag[0] = '\\0';\n}\n\nconst char *MicroOcpp::serializeAuthorizationStatus(AuthorizationStatus status) {\n    switch (status) {\n        case (AuthorizationStatus::Accepted):\n            return \"Accepted\";\n        case (AuthorizationStatus::Blocked):\n            return \"Blocked\";\n        case (AuthorizationStatus::Expired):\n            return \"Expired\";\n        case (AuthorizationStatus::Invalid):\n            return \"Invalid\";\n        case (AuthorizationStatus::ConcurrentTx):\n            return \"ConcurrentTx\";\n        default:\n            return \"UNDEFINED\";\n    }\n}\n\nMicroOcpp::AuthorizationStatus MicroOcpp::deserializeAuthorizationStatus(const char *cstr) {\n    if (!cstr) {\n        return AuthorizationStatus::UNDEFINED;\n    }\n\n    if (!strcmp(cstr, \"Accepted\")) {\n        return AuthorizationStatus::Accepted;\n    } else if (!strcmp(cstr, \"Blocked\")) {\n        return AuthorizationStatus::Blocked;\n    } else if (!strcmp(cstr, \"Expired\")) {\n        return AuthorizationStatus::Expired;\n    } else if (!strcmp(cstr, \"Invalid\")) {\n        return AuthorizationStatus::Invalid;\n    } else if (!strcmp(cstr, \"ConcurrentTx\")) {\n        return AuthorizationStatus::ConcurrentTx;\n    } else {\n        return AuthorizationStatus::UNDEFINED;\n    }\n}\n\n#endif //MO_ENABLE_LOCAL_AUTH\n"
  },
  {
    "path": "src/MicroOcpp/Model/Authorization/AuthorizationData.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_AUTHORIZATIONDATA_H\n#define MO_AUTHORIZATIONDATA_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Operations/CiStrings.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <ArduinoJson.h>\n#include <memory>\n\nnamespace MicroOcpp {\n\nenum class AuthorizationStatus : uint8_t {\n    Accepted,\n    Blocked,\n    Expired,\n    Invalid,\n    ConcurrentTx,\n    UNDEFINED //not part of OCPP 1.6\n};\n\n#define AUTHDATA_KEY_IDTAG(COMPACT)       (COMPACT ? \"it\" : \"idTag\")\n#define AUTHDATA_KEY_IDTAGINFO            \"idTagInfo\"\n#define AUTHDATA_KEY_EXPIRYDATE(COMPACT)  (COMPACT ? \"ed\" : \"expiryDate\")\n#define AUTHDATA_KEY_PARENTIDTAG(COMPACT) (COMPACT ? \"pi\" : \"parentIdTag\")\n#define AUTHDATA_KEY_STATUS(COMPACT)      (COMPACT ? \"st\" : \"status\")\n\n#define AUTHORIZATIONSTATUS_LEN_MAX (sizeof(\"ConcurrentTx\") - 1) //max length of serialized AuthStatus\n\nconst char *serializeAuthorizationStatus(AuthorizationStatus status);\nAuthorizationStatus deserializeAuthorizationStatus(const char *cstr);\n\nclass AuthorizationData : public MemoryManaged {\nprivate:\n    //data structure optimized for memory consumption\n\n    char *parentIdTag = nullptr;\n    std::unique_ptr<Timestamp> expiryDate;\n\n    char idTag [IDTAG_LEN_MAX + 1] = {'\\0'};\n\n    AuthorizationStatus status = AuthorizationStatus::UNDEFINED;\npublic:\n    AuthorizationData();\n    AuthorizationData(AuthorizationData&& other);\n    ~AuthorizationData();\n\n    AuthorizationData& operator=(AuthorizationData&& other);\n\n    void readJson(JsonObject entry, bool compact = false); //compact: compressed representation for flash storage\n\n    size_t getJsonCapacity() const;\n    void writeJson(JsonObject& entry, bool compact = false); //compact: compressed representation for flash storage\n\n    const char *getIdTag() const;\n    Timestamp *getExpiryDate() const;\n    const char *getParentIdTag() const;\n    AuthorizationStatus getAuthorizationStatus() const;\n\n    void reset();\n};\n\n}\n\n#endif //MO_ENABLE_LOCAL_AUTH\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Authorization/AuthorizationList.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Model/Authorization/AuthorizationList.h>\n#include <MicroOcpp/Debug.h>\n\n#include <algorithm>\n#include <numeric>\n\nusing namespace MicroOcpp;\n\nAuthorizationList::AuthorizationList() : MemoryManaged(\"v16.Authorization.AuthorizationList\"), localAuthorizationList(makeVector<AuthorizationData>(getMemoryTag())) {\n\n}\n\nAuthorizationList::~AuthorizationList() {\n    \n}\n\nMicroOcpp::AuthorizationData *AuthorizationList::get(const char *idTag) {\n    //binary search\n\n    if (!idTag) {\n        return nullptr;\n    }\n\n    int l = 0;\n    int r = ((int) localAuthorizationList.size()) - 1;\n    while (l <= r) {\n        auto m = (r + l) / 2;\n        auto diff = strcmp(localAuthorizationList[m].getIdTag(), idTag);\n        if (diff < 0) {\n            l = m + 1;\n        } else if (diff > 0) {\n            r = m - 1;\n        } else {\n            return &localAuthorizationList[m];\n        }\n    }\n    return nullptr;\n}\n\nbool AuthorizationList::readJson(JsonArray authlistJson, int listVersion, bool differential, bool compact) {\n\n    if (compact) {\n        //compact representations don't contain remove commands\n        differential = false;\n    }\n\n    for (size_t i = 0; i < authlistJson.size(); i++) {\n\n        //check if JSON object is valid\n        if (!authlistJson[i].as<JsonObject>().containsKey(AUTHDATA_KEY_IDTAG(compact))) {\n            return false;\n        }\n    }\n\n    auto authlist_index = makeVector<int>(getMemoryTag());\n    auto remove_list = makeVector<int>(getMemoryTag());\n\n    unsigned int resultingListLength = 0;\n\n    if (!differential) {\n        //every entry will insert an idTag\n        resultingListLength = authlistJson.size();\n    } else {\n        //update type is differential; only unkown entries will insert an idTag\n\n        resultingListLength = localAuthorizationList.size();\n\n        //also, build index here\n        authlist_index.resize(authlistJson.size(), -1);\n\n        for (size_t i = 0; i < authlistJson.size(); i++) {\n\n            //check if locally stored auth info is present; if yes, apply it to the index\n            AuthorizationData *found = get(authlistJson[i][AUTHDATA_KEY_IDTAG(compact)]);\n\n            if (found) {\n\n                authlist_index[i] = (int) (found - localAuthorizationList.data());\n\n                //remove or update?\n                if (!authlistJson[i].as<JsonObject>().containsKey(AUTHDATA_KEY_IDTAGINFO)) {\n                    //this entry should be removed\n                    found->reset(); //mark for deletion\n                    remove_list.push_back((int) (found - localAuthorizationList.data()));\n                    resultingListLength--;\n                } //else: this entry should be updated\n            } else {\n                //insert or ignore?\n                if (authlistJson[i].as<JsonObject>().containsKey(AUTHDATA_KEY_IDTAGINFO)) {\n                    //add\n                    resultingListLength++;\n                } //else: ignore\n            }\n        }\n    }\n\n    if (resultingListLength > MO_LocalAuthListMaxLength) {\n        MO_DBG_WARN(\"localAuthList capacity exceeded\");\n        return false;\n    }\n\n    //apply new list\n\n    if (compact) {\n        localAuthorizationList.clear();\n\n        for (size_t i = 0; i < authlistJson.size(); i++) {\n            localAuthorizationList.emplace_back();\n            localAuthorizationList.back().readJson(authlistJson[i], compact);\n        }\n    } else if (differential) {\n\n        for (size_t i = 0; i < authlistJson.size(); i++) {\n\n            //is entry a remove command?\n            if (!authlistJson[i].as<JsonObject>().containsKey(AUTHDATA_KEY_IDTAGINFO)) {\n                continue; //yes, remove command, will be deleted afterwards\n            }\n\n            //update, or insert\n\n            if (authlist_index[i] < 0) {\n                //auth list does not contain idTag yet -> insert new entry\n\n                //reuse removed AuthData object?\n                if (!remove_list.empty()) {\n                    //yes, reuse\n                    authlist_index[i] = remove_list.back();\n                    remove_list.pop_back();\n                } else {\n                    //no, create new\n                    authlist_index[i] = localAuthorizationList.size();\n                    localAuthorizationList.emplace_back();\n                }\n            }\n\n            localAuthorizationList[authlist_index[i]].readJson(authlistJson[i], compact);\n        }\n\n    } else {\n        localAuthorizationList.clear();\n\n        for (size_t i = 0; i < authlistJson.size(); i++) {\n            if (authlistJson[i].as<JsonObject>().containsKey(AUTHDATA_KEY_IDTAGINFO)) {\n                localAuthorizationList.emplace_back();\n                localAuthorizationList.back().readJson(authlistJson[i], compact);\n            }\n        }\n    }\n\n    localAuthorizationList.erase(std::remove_if(localAuthorizationList.begin(), localAuthorizationList.end(),\n            [] (const AuthorizationData& elem) {\n                return elem.getIdTag()[0] == '\\0'; //\"\" means no idTag --> marked for removal\n            }), localAuthorizationList.end());\n\n    std::sort(localAuthorizationList.begin(), localAuthorizationList.end(),\n            [] (const AuthorizationData& lhs, const AuthorizationData& rhs) {\n                return strcmp(lhs.getIdTag(), rhs.getIdTag()) < 0;\n            });\n    \n    this->listVersion = listVersion;\n\n    if (localAuthorizationList.empty()) {\n        this->listVersion = 0;\n    }\n\n    return true;\n}\n\nvoid AuthorizationList::clear() {\n    localAuthorizationList.clear();\n    listVersion = 0;\n}\n\nsize_t AuthorizationList::getJsonCapacity() {\n    size_t res = JSON_ARRAY_SIZE(localAuthorizationList.size());\n    for (auto& entry : localAuthorizationList) {\n        res += entry.getJsonCapacity();\n    }\n    return res;\n}\n\nvoid AuthorizationList::writeJson(JsonArray authListOut, bool compact) {\n    for (auto& entry : localAuthorizationList) {\n        JsonObject entryJson = authListOut.createNestedObject();\n        entry.writeJson(entryJson, compact);\n    }\n}\n\nsize_t AuthorizationList::size() {\n    return localAuthorizationList.size();\n}\n\n#endif //MO_ENABLE_LOCAL_AUTH\n"
  },
  {
    "path": "src/MicroOcpp/Model/Authorization/AuthorizationList.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_AUTHORIZATIONLIST_H\n#define MO_AUTHORIZATIONLIST_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Model/Authorization/AuthorizationData.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#ifndef MO_LocalAuthListMaxLength\n#define MO_LocalAuthListMaxLength 48\n#endif\n\n#ifndef MO_SendLocalListMaxLength\n#define MO_SendLocalListMaxLength MO_LocalAuthListMaxLength\n#endif\n\nnamespace MicroOcpp {\n\nclass AuthorizationList : public MemoryManaged {\nprivate:\n    int listVersion = 0;\n    Vector<AuthorizationData> localAuthorizationList; //sorted list\npublic:\n    AuthorizationList();\n    ~AuthorizationList();\n\n    AuthorizationData *get(const char *idTag);\n\n    bool readJson(JsonArray localAuthorizationList, int listVersion, bool differential = false, bool compact = false); //compact: if true, then use compact non-ocpp representation\n    void clear();\n\n    size_t getJsonCapacity();\n    void writeJson(JsonArray authListOut, bool compact = false);\n\n    int getListVersion() {return listVersion;}\n    size_t size(); //used in unit tests\n\n};\n\n}\n\n#endif //MO_ENABLE_LOCAL_AUTH\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Authorization/AuthorizationService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/OperationRegistry.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Operations/GetLocalListVersion.h>\n#include <MicroOcpp/Operations/SendLocalList.h>\n#include <MicroOcpp/Operations/StatusNotification.h>\n#include <MicroOcpp/Debug.h>\n\n#define MO_LOCALAUTHORIZATIONLIST_FN (MO_FILENAME_PREFIX \"localauth.jsn\")\n\nusing namespace MicroOcpp;\n\nAuthorizationService::AuthorizationService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem) : MemoryManaged(\"v16.Authorization.AuthorizationService\"), context(context), filesystem(filesystem) {\n\n    localAuthListEnabledBool = declareConfiguration<bool>(\"LocalAuthListEnabled\", true, CONFIGURATION_FN, false, true);\n    declareConfiguration<int>(\"LocalAuthListMaxLength\", MO_LocalAuthListMaxLength, CONFIGURATION_VOLATILE, true);\n    declareConfiguration<int>(\"SendLocalListMaxLength\", MO_SendLocalListMaxLength, CONFIGURATION_VOLATILE, true);\n\n    if (!localAuthListEnabledBool) {\n        MO_DBG_ERR(\"initialization error\");\n    }\n    \n    context.getOperationRegistry().registerOperation(\"GetLocalListVersion\", [&context] () {\n        return new Ocpp16::GetLocalListVersion(context.getModel());});\n    context.getOperationRegistry().registerOperation(\"SendLocalList\", [this] () {\n        return new Ocpp16::SendLocalList(*this);});\n\n    loadLists();\n}\n\nAuthorizationService::~AuthorizationService() {\n    \n}\n\nbool AuthorizationService::loadLists() {\n    if (!filesystem) {\n        MO_DBG_WARN(\"no fs access\");\n        return true;\n    }\n\n    size_t msize = 0;\n    if (filesystem->stat(MO_LOCALAUTHORIZATIONLIST_FN, &msize) != 0) {\n        MO_DBG_DEBUG(\"no local authorization list stored already\");\n        return true;\n    }\n    \n    auto doc = FilesystemUtils::loadJson(filesystem, MO_LOCALAUTHORIZATIONLIST_FN, getMemoryTag());\n    if (!doc) {\n        MO_DBG_ERR(\"failed to load %s\", MO_LOCALAUTHORIZATIONLIST_FN);\n        return false;\n    }\n\n    JsonObject root = doc->as<JsonObject>();\n\n    int listVersion = root[\"listVersion\"] | 0;\n    \n    if (!localAuthorizationList.readJson(root[\"localAuthorizationList\"].as<JsonArray>(), listVersion, false, true)) {\n        MO_DBG_ERR(\"list read failure\");\n        return false;\n    }\n\n    return true;\n}\n\nAuthorizationData *AuthorizationService::getLocalAuthorization(const char *idTag) {\n    if (!localAuthListEnabled()) {\n        return nullptr; //auth cache will follow\n    }\n\n    auto authData = localAuthorizationList.get(idTag);\n    if (!authData) {\n        return nullptr;\n    }\n\n    //check status\n    if (authData->getAuthorizationStatus() != AuthorizationStatus::Accepted) {\n        MO_DBG_DEBUG(\"idTag %s local auth status %s\", idTag, serializeAuthorizationStatus(authData->getAuthorizationStatus()));\n        return authData;\n    }\n\n    return authData;\n}\n\nint AuthorizationService::getLocalListVersion() {\n    return localAuthorizationList.getListVersion();\n}\n\nsize_t AuthorizationService::getLocalListSize() {\n    return localAuthorizationList.size();\n}\n\nbool AuthorizationService::localAuthListEnabled() const {\n    return localAuthListEnabledBool && localAuthListEnabledBool->getBool();\n}\n\nbool AuthorizationService::updateLocalList(JsonArray localAuthorizationListJson, int listVersion, bool differential) {\n    //TC_043_3_CS-Send Local Authorization List - Failed\n    //return false;\n\n    bool success = localAuthorizationList.readJson(localAuthorizationListJson, listVersion, differential, false);\n\n    if (success) {\n        \n        auto doc = initJsonDoc(getMemoryTag(),\n                JSON_OBJECT_SIZE(3) +\n                localAuthorizationList.getJsonCapacity());\n\n        JsonObject root = doc.to<JsonObject>();\n        root[\"listVersion\"] = listVersion;\n        JsonArray authListCompact = root.createNestedArray(\"localAuthorizationList\");\n        localAuthorizationList.writeJson(authListCompact, true);\n        success = FilesystemUtils::storeJson(filesystem, MO_LOCALAUTHORIZATIONLIST_FN, doc);\n\n        if (!success) {\n            loadLists();\n        }\n    }\n\n    return success;\n}\n\nvoid AuthorizationService::notifyAuthorization(const char *idTag, JsonObject idTagInfo) {\n    //check local list conflicts. In future: also update authorization cache\n\n    if (!localAuthListEnabled()) {\n        return; //auth cache will follow\n    }\n\n    if (!idTagInfo.containsKey(\"status\")) {\n        return; //empty idTagInfo\n    }\n\n    auto localInfo = localAuthorizationList.get(idTag);\n    if (!localInfo) {\n        return;\n    }\n\n    //check for conflicts\n\n    auto incomingStatus = deserializeAuthorizationStatus(idTagInfo[\"status\"]);\n    auto localStatus = localInfo->getAuthorizationStatus();\n\n    if (incomingStatus == AuthorizationStatus::UNDEFINED) { //ignore invalid messages (handled elsewhere)\n        return;\n    }\n\n    if (incomingStatus == AuthorizationStatus::ConcurrentTx) { //incoming status ConcurrentTx is equivalent to local Accepted\n        incomingStatus = AuthorizationStatus::Accepted;\n    }\n\n    if (localStatus == AuthorizationStatus::Accepted && localInfo->getExpiryDate()) { //check for expiry\n        auto& t_now = context.getModel().getClock().now();\n        if (t_now > *localInfo->getExpiryDate()) {\n            MO_DBG_DEBUG(\"local auth expired\");\n            localStatus = AuthorizationStatus::Expired;\n        }\n    }\n\n    bool equivalent = true;\n\n    if (incomingStatus != localStatus) {\n        MO_DBG_WARN(\"local auth list status conflict\");\n        equivalent = false;\n    }\n\n    //check if parentIdTag definitions mismatch\n    if (equivalent &&\n            strcmp(localInfo->getParentIdTag() ? localInfo->getParentIdTag() : \"\", idTagInfo[\"parentIdTag\"] | \"\")) {\n        MO_DBG_WARN(\"local auth list parentIdTag conflict\");\n        equivalent = false;\n    }\n\n    MO_DBG_DEBUG(\"idTag %s fully evaluated: %s conflict\", idTag, equivalent ? \"no\" : \"contains\");\n\n    if (!equivalent) {\n        //send error code \"LocalListConflict\" to server\n\n        ChargePointStatus cpStatus = ChargePointStatus_UNDEFINED;\n        if (context.getModel().getNumConnectors() > 0) {\n            cpStatus = context.getModel().getConnector(0)->getStatus();\n        }\n\n        auto statusNotification = makeRequest(new Ocpp16::StatusNotification(\n                    0,\n                    cpStatus, //will be determined in StatusNotification::initiate\n                    context.getModel().getClock().now(),\n                    \"LocalListConflict\"));\n\n        statusNotification->setTimeout(60000);\n\n        context.initiateRequest(std::move(statusNotification));\n    }\n}\n\n#endif //MO_ENABLE_LOCAL_AUTH\n"
  },
  {
    "path": "src/MicroOcpp/Model/Authorization/AuthorizationService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_AUTHORIZATIONSERVICE_H\n#define MO_AUTHORIZATIONSERVICE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Model/Authorization/AuthorizationList.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Context;\n\nclass AuthorizationService : public MemoryManaged {\nprivate:\n    Context& context;\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    AuthorizationList localAuthorizationList;\n\n    std::shared_ptr<Configuration> localAuthListEnabledBool;\n\npublic:\n    AuthorizationService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem);\n    ~AuthorizationService();\n\n    bool loadLists();\n\n    AuthorizationData *getLocalAuthorization(const char *idTag);\n\n    int getLocalListVersion();\n    bool localAuthListEnabled() const;\n    size_t getLocalListSize(); //number of entries in current localAuthList; used in unit tests\n\n    bool updateLocalList(JsonArray localAuthorizationListJson, int listVersion, bool differential);\n\n    void notifyAuthorization(const char *idTag, JsonObject idTagInfo);\n};\n\n}\n\n#endif //MO_ENABLE_LOCAL_AUTH\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Authorization/IdToken.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Authorization/IdToken.h>\n\n#include <string.h>\n#include <stdio.h>\n\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nIdToken::IdToken(const char *token, Type type, const char *memoryTag) : MemoryManaged(memoryTag ? memoryTag : \"v201.Authorization.IdToken\"), type(type) {\n    if (token) {\n        auto ret = snprintf(idToken, MO_IDTOKEN_LEN_MAX + 1, \"%s\", token);\n        if (ret < 0 || ret >= MO_IDTOKEN_LEN_MAX + 1) {\n            MO_DBG_ERR(\"invalid token\");\n            *idToken = '\\0';\n        }\n    } else {\n        *idToken = '\\0';\n    }\n}\n\nIdToken::IdToken(const IdToken& other, const char *memoryTag) : IdToken(other.idToken, other.type, memoryTag ? memoryTag : other.getMemoryTag()) { \n\n}\n\nbool IdToken::parseCstr(const char *token, const char *typeCstr) {\n    if (!token || !typeCstr) {\n        return false;\n    }\n\n    if (!strcmp(typeCstr, \"Central\")) {\n        type = Type::Central;\n    } else if (!strcmp(typeCstr, \"eMAID\")) {\n        type = Type::eMAID;\n    } else if (!strcmp(typeCstr, \"ISO14443\")) {\n        type = Type::ISO14443;\n    } else if (!strcmp(typeCstr, \"ISO15693\")) {\n        type = Type::ISO15693;\n    } else if (!strcmp(typeCstr, \"KeyCode\")) {\n        type = Type::KeyCode;\n    } else if (!strcmp(typeCstr, \"Local\")) {\n        type = Type::Local;\n    } else if (!strcmp(typeCstr, \"MacAddress\")) {\n        type = Type::MacAddress;\n    } else if (!strcmp(typeCstr, \"NoAuthorization\")) {\n        type = Type::NoAuthorization;\n    } else {\n        return false;\n    }\n\n    auto ret = snprintf(idToken, sizeof(idToken), \"%s\", token);\n    if (ret < 0 || (size_t)ret >= sizeof(idToken)) {\n        return false;\n    }\n\n    return true;\n}\n\nconst char *IdToken::get() const {\n    return idToken;\n}\n\nconst char *IdToken::getTypeCstr() const {\n    const char *res = \"\";\n    switch (type) {\n        case Type::UNDEFINED:\n            MO_DBG_ERR(\"internal error\");\n            break;\n        case Type::Central:\n            res = \"Central\";\n            break;\n        case Type::eMAID:\n            res = \"eMAID\";\n            break;\n        case Type::ISO14443:\n            res = \"ISO14443\";\n            break;\n        case Type::ISO15693:\n            res = \"ISO15693\";\n            break;\n        case Type::KeyCode:\n            res = \"KeyCode\";\n            break;\n        case Type::Local:\n            res = \"Local\";\n            break;\n        case Type::MacAddress:\n            res = \"MacAddress\";\n            break;\n        case Type::NoAuthorization:\n            res = \"NoAuthorization\";\n            break;\n    }\n\n    return res;\n}\n\nbool IdToken::equals(const IdToken& other) {\n    return type == other.type && !strcmp(idToken, other.idToken);\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Model/Authorization/IdToken.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_IDTOKEN_H\n#define MO_IDTOKEN_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <stdint.h>\n\n#include <MicroOcpp/Core/Memory.h>\n\n#define MO_IDTOKEN_LEN_MAX 36\n\nnamespace MicroOcpp {\n\n// IdTokenType (2.28)\nclass IdToken : public MemoryManaged {\npublic:\n\n    // IdTokenEnumType (3.43)\n    enum class Type : uint8_t {\n        Central,\n        eMAID,\n        ISO14443,\n        ISO15693,\n        KeyCode,\n        Local,\n        MacAddress,\n        NoAuthorization,\n        UNDEFINED\n    };\n\nprivate:\n    char idToken [MO_IDTOKEN_LEN_MAX + 1];\n    Type type = Type::UNDEFINED;\npublic:\n    IdToken(const char *token = nullptr, Type type = Type::ISO14443, const char *memoryTag = nullptr);\n\n    IdToken(const IdToken& other, const char *memoryTag = nullptr);\n\n    bool parseCstr(const char *token, const char *typeCstr);\n\n    const char *get() const;\n    const char *getTypeCstr() const;\n\n    bool equals(const IdToken& other);\n};\n\n} // namespace MicroOcpp\n\n#endif // MO_ENABLE_V201\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Availability/AvailabilityService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Availability/AvailabilityService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Debug.h>\n#include <MicroOcpp/Model/Reservation/ReservationService.h>\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n#include <MicroOcpp/Operations/ChangeAvailability.h>\n#include <MicroOcpp/Operations/StatusNotification.h>\n\nusing namespace MicroOcpp;\n\nAvailabilityServiceEvse::AvailabilityServiceEvse(Context& context, AvailabilityService& availabilityService, unsigned int evseId) : MemoryManaged(\"v201.Availability.AvailabilityServiceEvse\"), context(context), availabilityService(availabilityService), evseId(evseId) {\n\n}\n\nvoid AvailabilityServiceEvse::loop() {\n\n    if (evseId >= 1) {\n        auto status = getStatus();\n\n        if (status != reportedStatus &&\n                context.getModel().getClock().now() >= MIN_TIME) {\n\n            auto statusNotification = makeRequest(new Ocpp201::StatusNotification(evseId, status, context.getModel().getClock().now()));\n            statusNotification->setTimeout(0);\n            context.initiateRequest(std::move(statusNotification));\n            reportedStatus = status;\n            return;\n        }\n    }\n}\n\nvoid AvailabilityServiceEvse::setConnectorPluggedInput(std::function<bool()> connectorPluggedInput) {\n    this->connectorPluggedInput = connectorPluggedInput;\n}\n\nvoid AvailabilityServiceEvse::setOccupiedInput(std::function<bool()> occupiedInput) {\n    this->occupiedInput = occupiedInput;\n}\n\nChargePointStatus AvailabilityServiceEvse::getStatus() {\n    ChargePointStatus res = ChargePointStatus_UNDEFINED;\n\n    if (isFaulted()) {\n        res = ChargePointStatus_Faulted;\n    } else if (!isAvailable()) {\n        res = ChargePointStatus_Unavailable;\n    }\n    #if MO_ENABLE_RESERVATION\n    else if (context.getModel().getReservationService() && context.getModel().getReservationService()->getReservation(evseId)) {\n        res = ChargePointStatus_Reserved;\n    }\n    #endif \n    else if ((!connectorPluggedInput || !connectorPluggedInput()) &&   //no vehicle plugged\n               (!occupiedInput || !occupiedInput())) {                       //occupied override clear\n        res = ChargePointStatus_Available;\n    } else {\n        res = ChargePointStatus_Occupied;\n    }\n\n    return res;\n}\n\nvoid AvailabilityServiceEvse::setUnavailable(void *requesterId) {\n    for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) {\n        if (!unavailableRequesters[i]) {\n            unavailableRequesters[i] = requesterId;\n            return;\n        }\n    }\n    MO_DBG_ERR(\"exceeded max. unavailable requesters\");\n}\n\nvoid AvailabilityServiceEvse::setAvailable(void *requesterId) {\n    for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) {\n        if (unavailableRequesters[i] == requesterId) {\n            unavailableRequesters[i] = nullptr;\n            return;\n        }\n    }\n    MO_DBG_ERR(\"could not find unavailable requester\");\n}\n\nChangeAvailabilityStatus AvailabilityServiceEvse::changeAvailability(bool operative) {\n    if (operative) {\n        setAvailable(this);\n    } else {\n        setUnavailable(this);\n    }\n\n    if (!operative) {\n        if (isAvailable()) {\n            return ChangeAvailabilityStatus::Scheduled;\n        }\n\n        if (evseId == 0) {\n            for (unsigned int id = 1; id < MO_NUM_EVSEID; id++) {\n                if (availabilityService.getEvse(id) && availabilityService.getEvse(id)->isAvailable()) {\n                    return ChangeAvailabilityStatus::Scheduled;\n                }\n            }\n        }\n    }\n\n    return ChangeAvailabilityStatus::Accepted;\n}\n\nvoid AvailabilityServiceEvse::setFaulted(void *requesterId) {\n    for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) {\n        if (!faultedRequesters[i]) {\n            faultedRequesters[i] = requesterId;\n            return;\n        }\n    }\n    MO_DBG_ERR(\"exceeded max. faulted requesters\");\n}\n\nvoid AvailabilityServiceEvse::resetFaulted(void *requesterId) {\n    for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) {\n        if (faultedRequesters[i] == requesterId) {\n            faultedRequesters[i] = nullptr;\n            return;\n        }\n    }\n    MO_DBG_ERR(\"could not find faulted requester\");\n}\n\nbool AvailabilityServiceEvse::isAvailable() {\n\n    auto txService = context.getModel().getTransactionService();\n    auto txEvse = txService ? txService->getEvse(evseId) : nullptr;\n    if (txEvse) {\n        if (txEvse->getTransaction() &&\n                txEvse->getTransaction()->started &&\n                !txEvse->getTransaction()->stopped) {\n            return true;\n        }\n    }\n\n    if (evseId > 0) {\n        if (availabilityService.getEvse(0) && !availabilityService.getEvse(0)->isAvailable()) {\n            return false;\n        }\n    }\n\n    for (size_t i = 0; i < MO_INOPERATIVE_REQUESTERS_MAX; i++) {\n        if (unavailableRequesters[i]) {\n            return false;\n        }\n    }\n    return true;\n}\n\nbool AvailabilityServiceEvse::isFaulted() {\n    for (size_t i = 0; i < MO_FAULTED_REQUESTERS_MAX; i++) {\n        if (faultedRequesters[i]) {\n            return true;\n        }\n    }\n    return false;\n}\n\nAvailabilityService::AvailabilityService(Context& context, size_t numEvses) : MemoryManaged(\"v201.Availability.AvailabilityService\"), context(context) {\n\n    for (size_t i = 0; i < numEvses && i < MO_NUM_EVSEID; i++) {\n        evses[i] = new AvailabilityServiceEvse(context, *this, (unsigned int)i);\n    }\n\n    context.getOperationRegistry().registerOperation(\"StatusNotification\", [&context] () {\n        return new Ocpp16::StatusNotification(-1, ChargePointStatus_UNDEFINED, Timestamp());});\n    context.getOperationRegistry().registerOperation(\"ChangeAvailability\", [this] () {\n        return new Ocpp201::ChangeAvailability(*this);});\n}\n\nAvailabilityService::~AvailabilityService() {\n    for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) {\n        delete evses[i];\n    }\n}\n\nvoid AvailabilityService::loop() {\n    for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) {\n        evses[i]->loop();\n    }\n}\n\nAvailabilityServiceEvse *AvailabilityService::getEvse(unsigned int evseId) {\n    if (evseId >= MO_NUM_EVSEID) {\n        MO_DBG_ERR(\"invalid arg\");\n        return nullptr;\n    }\n    return evses[evseId];\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Model/Availability/AvailabilityService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Implementation of the UCs G01, G03, G04.\n *\n * G02 (Heartbeat) is implemented in the HeartbeatService\n */\n\n#ifndef MO_AVAILABILITYSERVICE_H\n#define MO_AVAILABILITYSERVICE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <functional>\n\n#include <MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h>\n#include <MicroOcpp/Model/ConnectorBase/EvseId.h>\n#include <MicroOcpp/Model/ConnectorBase/ChargePointStatus.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#ifndef MO_INOPERATIVE_REQUESTERS_MAX\n#define MO_INOPERATIVE_REQUESTERS_MAX 3\n#endif\n\n#ifndef MO_FAULTED_REQUESTERS_MAX\n#define MO_FAULTED_REQUESTERS_MAX 3\n#endif\n\nnamespace MicroOcpp {\n\nclass Context;\nclass AvailabilityService;\n\nclass AvailabilityServiceEvse : public MemoryManaged {\nprivate:\n    Context& context;\n    AvailabilityService& availabilityService;\n    const unsigned int evseId;\n\n    std::function<bool()> connectorPluggedInput;\n    std::function<bool()> occupiedInput; //instead of Available, go into Occupied\n\n    void *unavailableRequesters [MO_INOPERATIVE_REQUESTERS_MAX] = {nullptr};\n    void *faultedRequesters [MO_FAULTED_REQUESTERS_MAX] = {nullptr};\n\n    ChargePointStatus reportedStatus = ChargePointStatus_UNDEFINED;\npublic:\n    AvailabilityServiceEvse(Context& context, AvailabilityService& availabilityService, unsigned int evseId);\n\n    void loop();\n\n    void setConnectorPluggedInput(std::function<bool()> connectorPluggedInput);\n    void setOccupiedInput(std::function<bool()> occupiedInput);\n\n    ChargePointStatus getStatus();\n\n    void setUnavailable(void *requesterId);\n    void setAvailable(void *requesterId);\n\n    ChangeAvailabilityStatus changeAvailability(bool operative);\n\n    void setFaulted(void *requesterId);\n    void resetFaulted(void *requesterId);\n\n    bool isAvailable();\n    bool isFaulted();\n};\n\nclass AvailabilityService : public MemoryManaged {\nprivate:\n    Context& context;\n\n    AvailabilityServiceEvse* evses [MO_NUM_EVSEID] = {nullptr};\n\npublic:\n    AvailabilityService(Context& context, size_t numEvses);\n    ~AvailabilityService();\n\n    void loop();\n\n    AvailabilityServiceEvse *getEvse(unsigned int evseId);\n};\n\n} // namespace MicroOcpp\n\n#endif // MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CHANGEAVAILABILITYSTATUS_H\n#define MO_CHANGEAVAILABILITYSTATUS_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <stdint.h>\n\nnamespace MicroOcpp {\n\nenum class ChangeAvailabilityStatus : uint8_t {\n    Accepted,\n    Rejected,\n    Scheduled\n};\n\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Boot/BootService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <limits>\n\n#include <MicroOcpp/Model/Boot/BootService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Operations/BootNotification.h>\n#include <MicroOcpp/Platform.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nunsigned int PreBootQueue::getFrontRequestOpNr() {\n    if (!activatedPostBootCommunication) {\n        return 0;\n    }\n\n    return VolatileRequestQueue::getFrontRequestOpNr();\n}\n\nvoid PreBootQueue::activatePostBootCommunication() {\n    activatedPostBootCommunication = true;\n}\n\nRegistrationStatus MicroOcpp::deserializeRegistrationStatus(const char *serialized) {\n    if (!strcmp(serialized, \"Accepted\")) {\n        return RegistrationStatus::Accepted;\n    } else if (!strcmp(serialized, \"Pending\")) {\n        return RegistrationStatus::Pending;\n    } else if (!strcmp(serialized, \"Rejected\")) {\n        return RegistrationStatus::Rejected;\n    } else {\n        MO_DBG_ERR(\"deserialization error\");\n        return RegistrationStatus::UNDEFINED;\n    }\n}\n\nBootService::BootService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem) : MemoryManaged(\"v16.Boot.BootService\"), context(context), filesystem(filesystem), cpCredentials{makeString(getMemoryTag())} {\n    \n    context.getRequestQueue().setPreBootSendQueue(&preBootQueue); //register PreBootQueue in RequestQueue module\n    \n    //if transactions can start before the BootNotification succeeds\n    preBootTransactionsBool = declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", false);\n    \n    if (!preBootTransactionsBool) {\n        MO_DBG_ERR(\"initialization error\");\n    }\n\n    //Register message handler for TriggerMessage operation\n    context.getOperationRegistry().registerOperation(\"BootNotification\", [this] () {\n        return new Ocpp16::BootNotification(this->context.getModel(), getChargePointCredentials());});\n}\n\nvoid BootService::loop() {\n\n    if (!executedFirstTime) {\n        executedFirstTime = true;\n        firstExecutionTimestamp = mocpp_tick_ms();\n    }\n\n    if (!executedLongTime && mocpp_tick_ms() - firstExecutionTimestamp >= MO_BOOTSTATS_LONGTIME_MS) {\n        executedLongTime = true;\n        MO_DBG_DEBUG(\"boot success timer reached\");\n\n        configuration_clean_unused();\n\n        BootStats bootstats;\n        loadBootStats(filesystem, bootstats);\n        bootstats.lastBootSuccess = bootstats.bootNr;\n        storeBootStats(filesystem, bootstats);\n    }\n\n    preBootQueue.loop();\n\n    if (!activatedPostBootCommunication && status == RegistrationStatus::Accepted) {\n        preBootQueue.activatePostBootCommunication();\n        activatedPostBootCommunication = true;\n    }\n\n    if (!activatedModel && (status == RegistrationStatus::Accepted || preBootTransactionsBool->getBool())) {\n        context.getModel().activateTasks();\n        activatedModel = true;\n    }\n\n    if (status == RegistrationStatus::Accepted) {\n        return;\n    }\n    \n    if (mocpp_tick_ms() - lastBootNotification < (interval_s * 1000UL)) {\n        return;\n    }\n\n    /*\n     * Create BootNotification. The BootNotifaction object will fetch its paremeters from\n     * this class and notify this class about the response\n     */\n    auto bootNotification = makeRequest(new Ocpp16::BootNotification(context.getModel(), getChargePointCredentials()));\n    bootNotification->setTimeout(interval_s * 1000UL);\n    context.getRequestQueue().sendRequestPreBoot(std::move(bootNotification));\n\n    lastBootNotification = mocpp_tick_ms();\n}\n\nvoid BootService::setChargePointCredentials(JsonObject credentials) {\n    auto written = serializeJson(credentials, cpCredentials);\n    if (written < 2) {\n        MO_DBG_ERR(\"serialization error\");\n        cpCredentials = \"{}\";\n    }\n}\n\nvoid BootService::setChargePointCredentials(const char *credentials) {\n    cpCredentials = credentials;\n    if (cpCredentials.size() < 2) {\n        cpCredentials = \"{}\";\n    }\n}\n\nstd::unique_ptr<JsonDoc> BootService::getChargePointCredentials() {\n    if (cpCredentials.size() <= 2) {\n        return createEmptyDocument();\n    }\n\n    std::unique_ptr<JsonDoc> doc;\n    size_t capacity = JSON_OBJECT_SIZE(9) + cpCredentials.size();\n    DeserializationError err = DeserializationError::NoMemory;\n    while (err == DeserializationError::NoMemory && capacity <= MO_MAX_JSON_CAPACITY) {\n        doc = makeJsonDoc(getMemoryTag(), capacity);\n        err = deserializeJson(*doc, cpCredentials);\n\n        capacity *= 2;\n    }\n\n    if (!err) {\n        return doc;\n    } else {\n        MO_DBG_ERR(\"could not parse stored credentials: %s\", err.c_str());\n        return nullptr;\n    }\n}\n\nvoid BootService::notifyRegistrationStatus(RegistrationStatus status) {\n    this->status = status;\n    lastBootNotification = mocpp_tick_ms();\n}\n\nvoid BootService::setRetryInterval(unsigned long interval_s) {\n    if (interval_s == 0) {\n        this->interval_s = MO_BOOT_INTERVAL_DEFAULT;\n    } else {\n        this->interval_s = interval_s;\n    }\n    lastBootNotification = mocpp_tick_ms();\n}\n\nbool BootService::loadBootStats(std::shared_ptr<FilesystemAdapter> filesystem, BootStats& bstats) {\n    if (!filesystem) {\n        return false;\n    }\n\n    size_t msize = 0;\n    if (filesystem->stat(MO_FILENAME_PREFIX \"bootstats.jsn\", &msize) == 0) {\n        \n        bool success = true;\n\n        auto json = FilesystemUtils::loadJson(filesystem, MO_FILENAME_PREFIX \"bootstats.jsn\", \"v16.Boot.BootService\");\n        if (json) {\n            int bootNrIn = (*json)[\"bootNr\"] | -1;\n            if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits<uint16_t>::max()) {\n                bstats.bootNr = (uint16_t) bootNrIn;\n            } else {\n                success = false;\n            }\n\n            int lastSuccessIn = (*json)[\"lastSuccess\"] | -1;\n            if (lastSuccessIn >= 0 && lastSuccessIn <= std::numeric_limits<uint16_t>::max()) {\n                bstats.lastBootSuccess = (uint16_t) lastSuccessIn;\n            } else {\n                success = false;\n            }\n\n            const char *microOcppVersionIn = (*json)[\"MicroOcppVersion\"] | (const char*)nullptr;\n            if (microOcppVersionIn) {\n                auto ret = snprintf(bstats.microOcppVersion, sizeof(bstats.microOcppVersion), \"%s\", microOcppVersionIn);\n                if (ret < 0 || (size_t)ret >= sizeof(bstats.microOcppVersion)) {\n                    success = false;\n                }\n            } //else: version specifier can be missing after upgrade from pre 1.2.0 version\n        } else {\n            success = false;\n        }\n\n        if (!success) {\n            MO_DBG_ERR(\"bootstats corrupted\");\n            filesystem->remove(MO_FILENAME_PREFIX \"bootstats.jsn\");\n            bstats = BootStats();\n        }\n\n        return success;\n    } else {\n        return false;\n    }\n}\n\nbool BootService::storeBootStats(std::shared_ptr<FilesystemAdapter> filesystem, BootStats& bstats) {\n    if (!filesystem) {\n        return false;\n    }\n\n    auto json = initJsonDoc(\"v16.Boot.BootService\", JSON_OBJECT_SIZE(3));\n\n    json[\"bootNr\"] = bstats.bootNr;\n    json[\"lastSuccess\"] = bstats.lastBootSuccess;\n    json[\"MicroOcppVersion\"] = (const char*)bstats.microOcppVersion;\n\n    return FilesystemUtils::storeJson(filesystem, MO_FILENAME_PREFIX \"bootstats.jsn\", json);\n}\n\nbool BootService::recover(std::shared_ptr<FilesystemAdapter> filesystem, BootStats& bstats) {\n    if (!filesystem) {\n        return false;\n    }\n\n    bool success = FilesystemUtils::remove_if(filesystem, [] (const char *fname) -> bool {\n        return !strncmp(fname, \"sd\", strlen(\"sd\")) ||\n                !strncmp(fname, \"tx\", strlen(\"tx\")) ||\n                !strncmp(fname, \"sc-\", strlen(\"sc-\")) ||\n                !strncmp(fname, \"reservation\", strlen(\"reservation\")) ||\n                !strncmp(fname, \"client-state\", strlen(\"client-state\"));\n    });\n    MO_DBG_ERR(\"clear local state files (recovery): %s\", success ? \"success\" : \"not completed\");\n\n    return success;\n}\n\nbool BootService::migrate(std::shared_ptr<FilesystemAdapter> filesystem, BootStats& bstats) {\n    if (!filesystem) {\n        return false;\n    }\n\n    bool success = true;\n\n    if (strcmp(bstats.microOcppVersion, MO_VERSION)) {\n        MO_DBG_INFO(\"migrate persistent storage to MO v\" MO_VERSION);\n        success = FilesystemUtils::remove_if(filesystem, [] (const char *fname) -> bool {\n            return !strncmp(fname, \"sd\", strlen(\"sd\")) ||\n                    !strncmp(fname, \"tx\", strlen(\"tx\")) ||\n                    !strncmp(fname, \"op\", strlen(\"op\")) ||\n                    !strncmp(fname, \"sc-\", strlen(\"sc-\")) ||\n                    !strcmp(fname, \"client-state.cnf\") ||\n                    !strcmp(fname, \"arduino-ocpp.cnf\") ||\n                    !strcmp(fname, \"ocpp-creds.jsn\");\n        });\n\n        snprintf(bstats.microOcppVersion, sizeof(bstats.microOcppVersion), \"%s\", MO_VERSION);\n        MO_DBG_DEBUG(\"clear local state files (migration): %s\", success ? \"success\" : \"not completed\");\n    }\n    return success;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Boot/BootService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_BOOTSERVICE_H\n#define MO_BOOTSERVICE_H\n\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/RequestQueue.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <memory>\n\n#define MO_BOOT_INTERVAL_DEFAULT 60\n\n#ifndef MO_BOOTSTATS_LONGTIME_MS\n#define MO_BOOTSTATS_LONGTIME_MS 180 * 1000\n#endif\n\nnamespace MicroOcpp {\n\n#define MO_BOOTSTATS_VERSION_SIZE 10\n\nstruct BootStats {\n    uint16_t bootNr = 0;\n    uint16_t lastBootSuccess = 0;\n\n    uint16_t getBootFailureCount() {\n        return bootNr - lastBootSuccess;\n    }\n\n    char microOcppVersion [MO_BOOTSTATS_VERSION_SIZE] = {'\\0'};\n};\n\nenum class RegistrationStatus {\n    Accepted,\n    Pending,\n    Rejected,\n    UNDEFINED\n};\n\nRegistrationStatus deserializeRegistrationStatus(const char *serialized);\n\nclass PreBootQueue : public VolatileRequestQueue {\nprivate:\n    bool activatedPostBootCommunication = false;\npublic:\n    unsigned int getFrontRequestOpNr() override; //override FrontRequestOpNr behavior: in PreBoot mode, always return 0 to avoid other RequestEmitters from sending msgs\n    \n    void activatePostBootCommunication(); //end PreBoot mode, now send Requests normally\n};\n\nclass Context;\n\nclass BootService : public MemoryManaged {\nprivate:\n    Context& context;\n    std::shared_ptr<FilesystemAdapter> filesystem;\n\n    PreBootQueue preBootQueue;\n\n    unsigned long interval_s = MO_BOOT_INTERVAL_DEFAULT;\n    unsigned long lastBootNotification = -1UL / 2;\n\n    RegistrationStatus status = RegistrationStatus::Pending;\n    \n    String cpCredentials;\n\n    std::shared_ptr<Configuration> preBootTransactionsBool;\n\n    bool activatedModel = false;\n    bool activatedPostBootCommunication = false;\n\n    unsigned long firstExecutionTimestamp = 0;\n    bool executedFirstTime = false;\n    bool executedLongTime = false;\n\npublic:\n    BootService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem);\n\n    void loop();\n\n    void setChargePointCredentials(JsonObject credentials);\n    void setChargePointCredentials(const char *credentials); //credentials: serialized BootNotification payload\n    std::unique_ptr<JsonDoc> getChargePointCredentials();\n\n    void notifyRegistrationStatus(RegistrationStatus status);\n    void setRetryInterval(unsigned long interval);\n\n    static bool loadBootStats(std::shared_ptr<FilesystemAdapter> filesystem, BootStats& bstats);\n    static bool storeBootStats(std::shared_ptr<FilesystemAdapter> filesystem, BootStats& bstats);\n\n    static bool recover(std::shared_ptr<FilesystemAdapter> filesystem, BootStats& bstats); //delete all persistent files which could lead to a crash\n\n    static bool migrate(std::shared_ptr<FilesystemAdapter> filesystem, BootStats& bstats); //migrate persistent storage if running on a new MO version\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Certificates/Certificate.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Certificates/Certificate.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <string.h>\n#include <stdio.h>\n\nbool ocpp_cert_equals(const ocpp_cert_hash *h1, const ocpp_cert_hash *h2) {\n    return h1->hashAlgorithm == h2->hashAlgorithm &&\n            h1->serialNumberLen == h2->serialNumberLen && !memcmp(h1->serialNumber, h2->serialNumber, h1->serialNumberLen) &&\n            !memcmp(h1->issuerNameHash, h2->issuerNameHash, HashAlgorithmSize(h1->hashAlgorithm)) &&\n            !memcmp(h1->issuerKeyHash, h2->issuerKeyHash, HashAlgorithmSize(h1->hashAlgorithm));\n}\n\nint ocpp_cert_bytes_to_hex(char *dst, size_t dst_size, const unsigned char *src, size_t src_len) {\n    if (!dst || !dst_size || !src) {\n        return -1;\n    }\n\n    dst[0] = '\\0';\n\n    size_t hexLen = 2 * src_len; // hex-encoding needs two characters per byte\n\n    if (dst_size < hexLen + 1) { // buf will hold hex-encoding + terminating null\n        return -1;\n    }\n\n    for (size_t i = 0; i < src_len; i++) {\n        snprintf(dst, 3, \"%02X\", src[i]);\n        dst += 2;\n    }\n\n    return (int)hexLen;\n}\n\nint ocpp_cert_print_issuerNameHash(const ocpp_cert_hash *src, char *buf, size_t size) {\n    return ocpp_cert_bytes_to_hex(buf, size, src->issuerNameHash, HashAlgorithmSize(src->hashAlgorithm));\n}\n\nint ocpp_cert_print_issuerKeyHash(const ocpp_cert_hash *src, char *buf, size_t size) {\n    return ocpp_cert_bytes_to_hex(buf, size, src->issuerKeyHash, HashAlgorithmSize(src->hashAlgorithm));\n}\n\nint ocpp_cert_print_serialNumber(const ocpp_cert_hash *src, char *buf, size_t size) {\n\n    if (!buf || !size) {\n        return -1;\n    }\n\n    buf[0] = '\\0';\n\n    if (!src->serialNumberLen) {\n        return 0;\n    }\n\n    int hexLen = snprintf(buf, size, \"%X\", src->serialNumber[0]);\n    if (hexLen < 0 || (size_t)hexLen >= size) {\n        return -1;\n    }\n\n    if (src->serialNumberLen > 1) {\n        auto ret = ocpp_cert_bytes_to_hex(buf + (size_t)hexLen, size - (size_t)hexLen, src->serialNumber + 1, src->serialNumberLen - 1);\n        if (ret < 0) {\n            return -1;\n        }\n        hexLen += ret;\n    }\n\n    return hexLen;\n}\n\nint ocpp_cert_hex_to_bytes(unsigned char *dst, size_t dst_size, const char *hex_src) {\n    if (!dst || !dst_size || !hex_src) {\n        return -1;\n    }\n\n    dst[0] = '\\0';\n\n    size_t hex_len = strlen(hex_src);\n\n    size_t write_len = (hex_len + 1) / 2;\n\n    if (dst_size < write_len) {\n        return -1;\n    }\n\n    for (size_t i = 0; i < write_len; i++) {\n        char octet [2];\n\n        if (i == 0 && hex_len % 2) {\n            octet[0] = '0';\n            octet[1] = hex_src[2*i];\n        } else {\n            octet[0] = hex_src[2*i];\n            octet[1] = hex_src[2*i + 1];\n        }\n\n        unsigned char val = 0;\n\n        for (size_t j = 0; j < 2; j++) {\n            char c = octet[j];\n            if (c >= '0' && c <= '9') {\n                val += c - '0';\n            } else if (c >= 'A' && c <= 'F') {\n                val += (c - 'A') + 0xA;\n            } else if (c >= 'a' && c <= 'f') {\n                val += (c - 'a') + 0xA;\n            } else {\n                return -1;\n            }\n\n            if (j == 0) {\n                val *= 0x10;\n            }\n        }\n\n        dst[i] = val;\n    }\n\n    return (int)write_len;\n}\n\nint ocpp_cert_set_issuerNameHash(ocpp_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm) {\n    auto ret = ocpp_cert_hex_to_bytes(dst->issuerNameHash, sizeof(dst->issuerNameHash), hex_src);\n\n    if (ret < 0) {\n        return ret;\n    }\n\n    if (ret != HashAlgorithmSize(hash_algorithm)) {\n        return -1;\n    }\n\n    return ret;\n}\n\nint ocpp_cert_set_issuerKeyHash(ocpp_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm) {\n    auto ret = ocpp_cert_hex_to_bytes(dst->issuerKeyHash, sizeof(dst->issuerNameHash), hex_src);\n\n    if (ret < 0) {\n        return ret;\n    }\n\n    if (ret != HashAlgorithmSize(hash_algorithm)) {\n        return -1;\n    }\n\n    return ret;\n}\n\nint ocpp_cert_set_serialNumber(ocpp_cert_hash *dst, const char *hex_src) {\n    auto ret = ocpp_cert_hex_to_bytes(dst->serialNumber, sizeof(dst->serialNumber), hex_src);\n\n    if (ret < 0) {\n        return ret;\n    }\n\n    dst->serialNumberLen = (size_t)ret;\n\n    return ret;\n}\n\n#endif //MO_ENABLE_CERT_MGMT\n"
  },
  {
    "path": "src/MicroOcpp/Model/Certificates/Certificate.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CERTIFICATE_H\n#define MO_CERTIFICATE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <stddef.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define MO_MAX_CERT_SIZE 5500 //limit of field `certificate` in InstallCertificateRequest, not counting terminating '\\0'. See OCPP 2.0.1 part 2 Data Type 1.30.1\n\n/*\n * See OCPP 2.0.1 part 2 Data Type 3.36\n */\ntypedef enum GetCertificateIdType {\n    GetCertificateIdType_V2GRootCertificate,\n    GetCertificateIdType_MORootCertificate,\n    GetCertificateIdType_CSMSRootCertificate,\n    GetCertificateIdType_V2GCertificateChain,\n    GetCertificateIdType_ManufacturerRootCertificate\n}   GetCertificateIdType;\n\n/*\n * See OCPP 2.0.1 part 2 Data Type 3.40\n */\ntypedef enum GetInstalledCertificateStatus {\n    GetInstalledCertificateStatus_Accepted,\n    GetInstalledCertificateStatus_NotFound\n}   GetInstalledCertificateStatus;\n\n/*\n * See OCPP 2.0.1 part 2 Data Type 3.45\n */\ntypedef enum InstallCertificateType {\n    InstallCertificateType_V2GRootCertificate,\n    InstallCertificateType_MORootCertificate,\n    InstallCertificateType_CSMSRootCertificate,\n    InstallCertificateType_ManufacturerRootCertificate\n}   InstallCertificateType;\n\n/*\n * See OCPP 2.0.1 part 2 Data Type 3.28\n */\ntypedef enum InstallCertificateStatus {\n    InstallCertificateStatus_Accepted,\n    InstallCertificateStatus_Rejected,\n    InstallCertificateStatus_Failed\n}   InstallCertificateStatus;\n\n/*\n * See OCPP 2.0.1 part 2 Data Type 3.28\n */\ntypedef enum DeleteCertificateStatus {\n    DeleteCertificateStatus_Accepted,\n    DeleteCertificateStatus_Failed,\n    DeleteCertificateStatus_NotFound\n}   DeleteCertificateStatus;\n\n/*\n * See OCPP 2.0.1 part 2 Data Type 3.42\n */\ntypedef enum HashAlgorithmType {\n    HashAlgorithmType_SHA256,\n    HashAlgorithmType_SHA384,\n    HashAlgorithmType_SHA512\n}   HashAlgorithmType;\n\n// Convert HashAlgorithmType into string\n#define HashAlgorithmLabel(alg) (alg == HashAlgorithmType_SHA256 ? \"SHA256\" : \\\n                                 alg == HashAlgorithmType_SHA384 ? \"SHA384\" : \\\n                                 alg == HashAlgorithmType_SHA512 ? \"SHA512\" : \"_Undefined\")\n\n// Convert HashAlgorithmType into hash size in bytes (e.g. SHA256 -> 32)\n#define HashAlgorithmSize(alg) (alg == HashAlgorithmType_SHA256 ? 32 : \\\n                                alg == HashAlgorithmType_SHA384 ? 48 : \\\n                                alg == HashAlgorithmType_SHA512 ? 64 : 0)\n\ntypedef struct ocpp_cert_hash {\n    enum HashAlgorithmType hashAlgorithm;\n\n    unsigned char issuerNameHash [64]; // hash buf can hold 64 bytes (SHA512). Actual hash size is determined by hash algorithm\n    unsigned char issuerKeyHash [64];\n    unsigned char serialNumber [20];\n    size_t serialNumberLen; // length of serial number in bytes\n} ocpp_cert_hash;\n\nbool ocpp_cert_equals(const ocpp_cert_hash *h1, const ocpp_cert_hash *h2);\n\n// Max size of hex-encoded cert hash components\n#define MO_CERT_HASH_ISSUER_NAME_KEY_SIZE (128 + 1) // hex-encoding needs two characters per byte + terminating null-byte\n#define MO_CERT_HASH_SERIAL_NUMBER_SIZE (40 + 1)\n\n/*\n * Print the issuerNameHash of ocpp_cert_hash as hex-encoded string (e.g. \"0123AB\") into buf. Bufsize MO_CERT_HASH_ISSUER_NAME_KEY_SIZE is always enough\n *\n * Returns the length not counting the terminating 0 on success, -1 on failure\n */\nint ocpp_cert_print_issuerNameHash(const ocpp_cert_hash *src, char *buf, size_t size);\n\n/*\n * Print the issuerKeyHash of ocpp_cert_hash as hex-encoded string (e.g. \"0123AB\") into buf. Bufsize MO_CERT_HASH_ISSUER_NAME_KEY_SIZE is always enough\n *\n * Returns the length not counting the terminating 0 on success, -1 on failure\n */\nint ocpp_cert_print_issuerKeyHash(const ocpp_cert_hash *src, char *buf, size_t size);\n\n/*\n * Print the serialNumber of ocpp_cert_hash as hex-encoded string without leading 0s (e.g. \"123AB\") into buf. Bufsize MO_CERT_HASH_SERIAL_NUMBER_SIZE is always enough\n *\n * Returns the length not counting the terminating 0 on success, -1 on failure\n */\nint ocpp_cert_print_serialNumber(const ocpp_cert_hash *src, char *buf, size_t size);\n\nint ocpp_cert_set_issuerNameHash(ocpp_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm);\n\nint ocpp_cert_set_issuerKeyHash(ocpp_cert_hash *dst, const char *hex_src, HashAlgorithmType hash_algorithm);\n\nint ocpp_cert_set_serialNumber(ocpp_cert_hash *dst, const char *hex_src);\n\n#ifdef __cplusplus\n} //extern \"C\"\n\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nusing CertificateHash = ocpp_cert_hash;\n\n/*\n * See OCPP 2.0.1 part 2 Data Type 2.5\n */\nstruct CertificateChainHash : public MemoryManaged {\n    GetCertificateIdType certificateType;\n    CertificateHash certificateHashData;\n    Vector<CertificateHash> childCertificateHashData;\n\n    CertificateChainHash() : MemoryManaged(\"v2.0.1.Certificates.CertificateChainHash\"), childCertificateHashData(makeVector<CertificateHash>(getMemoryTag())) { }\n};\n\n/*\n * Interface which allows MicroOcpp to interact with the certificates managed by the local TLS library\n */\nclass CertificateStore {\npublic:\n    virtual ~CertificateStore() = default;\n\n    virtual GetInstalledCertificateStatus getCertificateIds(const Vector<GetCertificateIdType>& certificateType, Vector<CertificateChainHash>& out) = 0;\n    virtual DeleteCertificateStatus deleteCertificate(const CertificateHash& hash) = 0;\n    virtual InstallCertificateStatus installCertificate(InstallCertificateType certificateType, const char *certificate) = 0;\n};\n\n} //namespace MicroOcpp\n\n#endif //__cplusplus\n#endif //MO_ENABLE_CERT_MGMT\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Certificates/CertificateMbedTLS.h>\n\n#if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS\n\n#include <string.h>\n\n#include <mbedtls/version.h>\n#include <mbedtls/x509_crt.h>\n#include <mbedtls/md.h>\n#include <mbedtls/error.h>\n\n#include <MicroOcpp/Debug.h>\n\nbool ocpp_get_cert_hash(mbedtls_x509_crt& cacert, HashAlgorithmType hashAlg, ocpp_cert_hash *out) {\n\n    if (cacert.next) {\n        MO_DBG_ERR(\"only sole root certs supported\");\n        return false;\n    }\n\n    out->hashAlgorithm = hashAlg;\n\n    mbedtls_md_type_t hash_alg_mbed;\n\n    switch (hashAlg) {\n        case HashAlgorithmType_SHA256:\n            hash_alg_mbed = MBEDTLS_MD_SHA256;\n            break;\n        case HashAlgorithmType_SHA384:\n            hash_alg_mbed = MBEDTLS_MD_SHA384;\n            break;\n        case HashAlgorithmType_SHA512:\n            hash_alg_mbed = MBEDTLS_MD_SHA512;\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            return false;\n    }\n\n    const mbedtls_md_info_t *md_info;\n\n    md_info = mbedtls_md_info_from_type(hash_alg_mbed);\n    if (!md_info) {\n        MO_DBG_ERR(\"hash algorithmus not supported\");\n        return false;\n    }\n\n    size_t hash_size = mbedtls_md_get_size(md_info);\n    if (hash_size > sizeof(out->issuerNameHash)) {\n        MO_DBG_ERR(\"internal error\");\n        return false;\n    }\n\n    if (!cacert.issuer_raw.p) {\n        MO_DBG_ERR(\"missing issuer name\");\n        return false;\n    }\n\n    int ret;\n\n    if ((ret = mbedtls_md(md_info, cacert.issuer_raw.p, cacert.issuer_raw.len, out->issuerNameHash))) {\n        MO_DBG_ERR(\"mbedtls_md: %i\", ret);\n        return false;\n    }\n\n    // copy public key into pk_buf to create issuerKeyHash\n    size_t pk_size = cacert.pk_raw.len;\n    unsigned char *pk_buf = static_cast<unsigned char*>(MO_MALLOC(\"v201.Certificates.CertificateStoreMbedTLS\", pk_size));\n    if (!pk_buf) {\n        MO_DBG_ERR(\"OOM (alloc size %zu)\", pk_size);\n        return false;\n    }\n    int pk_len = 0;\n    unsigned char *pk_p = pk_buf + pk_size;\n\n    bool pk_err = false;\n\n    if ((pk_len = mbedtls_pk_write_pubkey(&pk_p, pk_buf, &cacert.pk)) <= 0) {\n        pk_err = true;\n        char err [100];\n        mbedtls_strerror(ret, err, 100);\n        MO_DBG_ERR(\"mbedtls_pk_write_pubkey_pem: %i -- %s\", pk_len, err);\n        // return after pk_buf has been freed\n    }\n\n    if (!pk_err) {\n        if ((ret = mbedtls_md(md_info, pk_p, pk_len, out->issuerKeyHash))) {\n            pk_err = true;\n            MO_DBG_ERR(\"mbedtls_md: %i\", ret);\n        }\n    }\n\n    MO_FREE(pk_buf);\n    if (pk_err) {\n        return false;\n    }\n\n    size_t serial_begin = 0; //trunicate leftmost 0x00 bytes\n    for (; serial_begin < cacert.serial.len - 1; serial_begin++) { //keep at least 1 byte, even if 0x00\n        if (cacert.serial.p[serial_begin] != 0) {\n            break;\n        }\n    }\n\n    out->serialNumberLen = std::min(cacert.serial.len - serial_begin, sizeof(out->serialNumber));\n    memcpy(out->serialNumber, cacert.serial.p + serial_begin, out->serialNumberLen);\n\n    return true;\n}\n\nbool ocpp_get_cert_hash(const unsigned char *buf, size_t len, HashAlgorithmType hashAlg, ocpp_cert_hash *out) {\n\n    mbedtls_x509_crt cacert;\n    mbedtls_x509_crt_init(&cacert);\n\n    bool success = false;\n    int ret;\n\n    if((ret = mbedtls_x509_crt_parse(&cacert, buf, len + 1)) >= 0) {\n        success = ocpp_get_cert_hash(cacert, hashAlg, out);\n    } else {\n        char err [100];\n        mbedtls_strerror(ret, err, 100);\n        MO_DBG_ERR(\"mbedtls_x509_crt_parse: %i -- %s\", ret, err);\n    }\n\n    mbedtls_x509_crt_free(&cacert);\n    return success;\n}\n\nnamespace MicroOcpp {\n\nclass CertificateStoreMbedTLS : public CertificateStore, public MemoryManaged {\nprivate:\n    std::shared_ptr<FilesystemAdapter> filesystem;\n\n    bool getCertHash(const char *fn, HashAlgorithmType hashAlg, CertificateHash& out) {\n        size_t fsize;\n        if (filesystem->stat(fn, &fsize) != 0) {\n            MO_DBG_ERR(\"certificate does not exist: %s\", fn);\n            return false;\n        }\n\n        if (fsize >= MO_MAX_CERT_SIZE) {\n            MO_DBG_ERR(\"cert file exceeds limit: %s,  %zuB\", fn, fsize);\n            return false;\n        }\n\n        auto file = filesystem->open(fn, \"r\");\n        if (!file) {\n            MO_DBG_ERR(\"could not open file: %s\", fn);\n            return false;\n        }\n\n        unsigned char *buf = static_cast<unsigned char*>(MO_MALLOC(getMemoryTag(), fsize + 1));\n        if (!buf) {\n            MO_DBG_ERR(\"OOM\");\n            return false;\n        }\n\n        bool success = true;\n\n        size_t ret;\n        if ((ret = file->read((char*) buf, fsize)) != fsize) {\n            MO_DBG_ERR(\"read error: %zu (expect %zu)\", ret, fsize);\n            success = false;\n        }\n\n        buf[fsize] = '\\0';\n\n        if (success) {\n            success &= ocpp_get_cert_hash(buf, fsize, hashAlg, &out);\n        }\n\n        if (!success) {\n            MO_DBG_ERR(\"could not read cert: %s\", fn);\n        }\n\n        MO_FREE(buf);\n        return success;\n    }\npublic:\n    CertificateStoreMbedTLS(std::shared_ptr<FilesystemAdapter> filesystem)\n            : MemoryManaged(\"v201.Certificates.CertificateStoreMbedTLS\"), filesystem(filesystem) {\n\n    }\n\n    GetInstalledCertificateStatus getCertificateIds(const Vector<GetCertificateIdType>& certificateType, Vector<CertificateChainHash>& out) override {\n        out.clear();\n\n        for (auto certType : certificateType) {\n            const char *certTypeFnStr = nullptr;\n            switch (certType) {\n                case GetCertificateIdType_CSMSRootCertificate:\n                    certTypeFnStr = MO_CERT_FN_CSMS_ROOT;\n                    break;\n                case GetCertificateIdType_ManufacturerRootCertificate:\n                    certTypeFnStr = MO_CERT_FN_MANUFACTURER_ROOT;\n                    break;\n                default:\n                    MO_DBG_ERR(\"only CSMS / Manufacturer root supported\");\n                    break;\n            }\n\n            if (!certTypeFnStr) {\n                continue;\n            }\n\n            for (size_t i = 0; i < MO_CERT_STORE_SIZE; i++) {\n                char fn [MO_MAX_PATH_SIZE];\n                if (!printCertFn(certTypeFnStr, i, fn, MO_MAX_PATH_SIZE)) {\n                    MO_DBG_ERR(\"internal error\");\n                    out.clear();\n                    break;\n                }\n\n                size_t msize;\n                if (filesystem->stat(fn, &msize) != 0) {\n                    continue; //no cert installed at this slot\n                }\n\n                out.emplace_back();\n                CertificateChainHash& rootCert = out.back();\n\n                rootCert.certificateType = certType;\n\n                if (!getCertHash(fn, HashAlgorithmType_SHA256, rootCert.certificateHashData)) {\n                    MO_DBG_ERR(\"could not create hash: %s\", fn);\n                    out.pop_back();\n                    continue;\n                }\n            }\n        }\n\n        return out.empty() ?\n                GetInstalledCertificateStatus_NotFound :\n                GetInstalledCertificateStatus_Accepted;\n    }\n\n    DeleteCertificateStatus deleteCertificate(const CertificateHash& hash) override {\n        bool err = false;\n\n        //enumerate all certs possibly installed by this CertStore implementation\n        for (const char *certTypeFnStr : {MO_CERT_FN_CSMS_ROOT, MO_CERT_FN_MANUFACTURER_ROOT}) {\n            for (size_t i = 0; i < MO_CERT_STORE_SIZE; i++) {\n\n                char fn [MO_MAX_PATH_SIZE] = {'\\0'}; //cert fn on flash storage\n\n                if (!printCertFn(certTypeFnStr, i, fn, MO_MAX_PATH_SIZE)) {\n                    MO_DBG_ERR(\"internal error\");\n                    return DeleteCertificateStatus_Failed;\n                }\n\n                size_t msize;\n                if (filesystem->stat(fn, &msize) != 0) {\n                    continue; //no cert installed at this slot\n                }\n\n                CertificateHash probe;\n                if (!getCertHash(fn, hash.hashAlgorithm, probe)) {\n                    MO_DBG_ERR(\"could not create hash: %s\", fn);\n                    err = true;\n                    continue;\n                }\n\n                if (ocpp_cert_equals(&probe, &hash)) {\n                    //found, delete\n\n                    bool success = filesystem->remove(fn);\n                    return success ?\n                        DeleteCertificateStatus_Accepted :\n                        DeleteCertificateStatus_Failed;\n                }\n            }\n        }\n\n        return err ?\n            DeleteCertificateStatus_Failed :\n            DeleteCertificateStatus_NotFound;\n    }\n\n    InstallCertificateStatus installCertificate(InstallCertificateType certificateType, const char *certificate) override {\n        const char *certTypeFnStr;\n        GetCertificateIdType certTypeGetType;\n        switch (certificateType) {\n            case InstallCertificateType_CSMSRootCertificate:\n                certTypeFnStr = MO_CERT_FN_CSMS_ROOT;\n                certTypeGetType = GetCertificateIdType_CSMSRootCertificate;\n                break;\n            case InstallCertificateType_ManufacturerRootCertificate:\n                certTypeFnStr = MO_CERT_FN_MANUFACTURER_ROOT;\n                certTypeGetType = GetCertificateIdType_ManufacturerRootCertificate;\n                break;\n            default:\n                MO_DBG_ERR(\"only CSMS / Manufacturer root supported\");\n                return InstallCertificateStatus_Failed;\n        }\n\n        //check if this implementation is able to parse incoming cert\n        CertificateHash certId;\n        if (!ocpp_get_cert_hash((const unsigned char*)certificate, strlen(certificate), HashAlgorithmType_SHA256, &certId)) {\n            MO_DBG_ERR(\"unable to parse cert\");\n            return InstallCertificateStatus_Rejected;\n        }\n\n#if MO_DBG_LEVEL >= MO_DL_DEBUG\n        {\n            MO_DBG_DEBUG(\"Cert ID:\");\n            MO_DBG_DEBUG(\"hashAlgorithm: %s\", HashAlgorithmLabel(certId.hashAlgorithm));\n            char buf [MO_CERT_HASH_ISSUER_NAME_KEY_SIZE];\n\n            ocpp_cert_print_issuerNameHash(&certId, buf, sizeof(buf));\n            MO_DBG_DEBUG(\"issuerNameHash: %s\", buf);\n\n            ocpp_cert_print_issuerKeyHash(&certId, buf, sizeof(buf));\n            MO_DBG_DEBUG(\"issuerKeyHash: %s\", buf);\n\n            ocpp_cert_print_serialNumber(&certId, buf, sizeof(buf));\n            MO_DBG_DEBUG(\"serialNumber: %s\", buf);\n        }\n#endif // MO_DBG_LEVEL >= MO_DL_DEBUG\n\n        //check if cert is already stored on flash\n        auto installedCerts = makeVector<CertificateChainHash>(getMemoryTag());\n        auto ret = getCertificateIds({certTypeGetType}, installedCerts);\n        if (ret == GetInstalledCertificateStatus_Accepted) {\n            for (auto &installedCert : installedCerts) {\n                if (ocpp_cert_equals(&installedCert.certificateHashData, &certId)) {\n                    MO_DBG_INFO(\"certificate already installed\");\n                    return InstallCertificateStatus_Accepted;\n                }\n                for (auto& installedChild : installedCert.childCertificateHashData) {\n                    if (ocpp_cert_equals(&installedChild, &certId)) {\n                        MO_DBG_INFO(\"certificate already installed\");\n                        return InstallCertificateStatus_Accepted;\n                    }\n                }\n            }\n        }\n\n        char fn [MO_MAX_PATH_SIZE] = {'\\0'}; //cert fn on flash storage\n\n        //check for free cert slot\n        for (size_t i = 0; i < MO_CERT_STORE_SIZE; i++) {\n            if (!printCertFn(certTypeFnStr, i, fn, MO_MAX_PATH_SIZE)) {\n                MO_DBG_ERR(\"invalid cert fn\");\n                return InstallCertificateStatus_Failed;\n            }\n\n            size_t msize;\n            if (filesystem->stat(fn, &msize) != 0) {\n                //found free slot; fn contains result\n                break;\n            } else {\n                //this slot is already occupied; invalidate fn and try next\n                fn[0] = '\\0';\n            }\n        }\n\n        if (fn[0] == '\\0') {\n            MO_DBG_ERR(\"exceed maximum number of certs; must delete before\");\n            return InstallCertificateStatus_Rejected;\n        }\n\n        auto file = filesystem->open(fn, \"w\");\n        if (!file) {\n            MO_DBG_ERR(\"could not open file\");\n            return InstallCertificateStatus_Failed;\n        }\n\n        size_t cert_len = strlen(certificate);\n        auto written = file->write(certificate, cert_len);\n        if (written < cert_len) {\n            MO_DBG_ERR(\"file write error\");\n            file.reset();\n            filesystem->remove(fn);\n            return InstallCertificateStatus_Failed;\n        }\n\n        MO_DBG_INFO(\"installed certificate: %s\", fn);\n        return InstallCertificateStatus_Accepted;\n    }\n};\n\nstd::unique_ptr<CertificateStore> makeCertificateStoreMbedTLS(std::shared_ptr<FilesystemAdapter> filesystem) {\n    if (!filesystem) {\n        MO_DBG_WARN(\"default Certificate Store requires FS\");\n        return nullptr;\n    }\n    return std::unique_ptr<CertificateStore>(new CertificateStoreMbedTLS(filesystem));\n}\n\nbool printCertFn(const char *certType, size_t index, char *buf, size_t bufsize) {\n    if (!certType || !*certType || index >= MO_CERT_STORE_SIZE || !buf) {\n        MO_DBG_ERR(\"invalid args\");\n        return false;\n    }\n\n    auto ret = snprintf(buf, bufsize, MO_FILENAME_PREFIX MO_CERT_FN_PREFIX \"%s\" \"-%zu\" MO_CERT_FN_SUFFIX,\n            certType, index);\n    if (ret < 0 || ret >= (int)bufsize) {\n        MO_DBG_ERR(\"fn error: %i\", ret);\n        return false;\n    }\n    return true;\n}\n\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS\n"
  },
  {
    "path": "src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CERTIFICATE_MBEDTLS_H\n#define MO_CERTIFICATE_MBEDTLS_H\n\n/*\n * Built-in implementation of the Certificate interface for MbedTLS\n */\n\n#include <MicroOcpp/Version.h>\n#include <MicroOcpp/Platform.h>\n\n#ifndef MO_ENABLE_CERT_STORE_MBEDTLS\n#define MO_ENABLE_CERT_STORE_MBEDTLS MO_ENABLE_MBEDTLS\n#endif\n\n#if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS\n\n/*\n * Provide certificate interpreter to facilitate cert store in C. A full implementation is only available for C++\n */\n#include <MicroOcpp/Model/Certificates/Certificate.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nbool ocpp_get_cert_hash(const unsigned char *cert, size_t len, enum HashAlgorithmType hashAlg, ocpp_cert_hash *out);\n\n#ifdef __cplusplus\n} //extern \"C\"\n\n#include <memory>\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n\n#ifndef MO_CERT_FN_PREFIX\n#define MO_CERT_FN_PREFIX \"cert-\"\n#endif\n\n#ifndef MO_CERT_FN_SUFFIX\n#define MO_CERT_FN_SUFFIX \".pem\"\n#endif\n\n#ifndef MO_CERT_FN_CSMS_ROOT\n#define MO_CERT_FN_CSMS_ROOT \"csms\"\n#endif\n\n#ifndef MO_CERT_FN_MANUFACTURER_ROOT\n#define MO_CERT_FN_MANUFACTURER_ROOT \"mfact\"\n#endif\n\n#ifndef MO_CERT_STORE_SIZE\n#define MO_CERT_STORE_SIZE 3 //max number of certs per certificate type (e.g. CSMS root CA, Manufacturer root CA)\n#endif\n\nnamespace MicroOcpp {\n\nstd::unique_ptr<CertificateStore> makeCertificateStoreMbedTLS(std::shared_ptr<FilesystemAdapter> filesystem);\n\nbool printCertFn(const char *certType, size_t index, char *buf, size_t bufsize);\n\n} //namespace MicroOcpp\n\n#endif //def __cplusplus\n#endif //MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Certificates/CertificateService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Certificates/CertificateService.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Operations/DeleteCertificate.h>\n#include <MicroOcpp/Operations/GetInstalledCertificateIds.h>\n#include <MicroOcpp/Operations/InstallCertificate.h>\n\nusing namespace MicroOcpp;\n\nCertificateService::CertificateService(Context& context)\n        : MemoryManaged(\"v201.Certificates.CertificateService\"), context(context) {\n\n    context.getOperationRegistry().registerOperation(\"DeleteCertificate\", [this] () {\n        return new Ocpp201::DeleteCertificate(*this);});\n    context.getOperationRegistry().registerOperation(\"GetInstalledCertificateIds\", [this] () {\n        return new Ocpp201::GetInstalledCertificateIds(*this);});\n    context.getOperationRegistry().registerOperation(\"InstallCertificate\", [this] () {\n        return new Ocpp201::InstallCertificate(*this);});\n}\n\nvoid CertificateService::setCertificateStore(std::unique_ptr<CertificateStore> certStore) {\n    this->certStore = std::move(certStore);\n}\n\nCertificateStore *CertificateService::getCertificateStore() {\n    return certStore.get();\n}\n\n#endif //MO_ENABLE_CERT_MGMT\n"
  },
  {
    "path": "src/MicroOcpp/Model/Certificates/CertificateService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Functional Block M: ISO 15118 Certificate Management\n *\n * Implementation of UC:\n *     - M03\n *     - M04\n *     - M05\n */\n\n#ifndef MO_CERTIFICATESERVICE_H\n#define MO_CERTIFICATESERVICE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <functional>\n#include <memory>\n\n#include <MicroOcpp/Model/Certificates/Certificate.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Context;\n\nclass CertificateService : public MemoryManaged {\nprivate:\n    Context& context;\n    std::unique_ptr<CertificateStore> certStore;\npublic:\n    CertificateService(Context& context);\n\n    void setCertificateStore(std::unique_ptr<CertificateStore> certStore);\n    CertificateStore *getCertificateStore();\n};\n\n}\n\n#endif //MO_ENABLE_CERT_MGMT\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Certificates/Certificate_c.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Certificates/Certificate_c.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Debug.h>\n\n#include <string.h>\n\nnamespace MicroOcpp {\n\n/*\n * C++ wrapper for the C-style certificate interface\n */\nclass CertificateStoreC : public CertificateStore, public MemoryManaged {\nprivate:\n    ocpp_cert_store *certstore = nullptr;\npublic:\n    CertificateStoreC(ocpp_cert_store *certstore) : MemoryManaged(\"v201.Certificates.CertificateStoreC\"), certstore(certstore) {\n\n    }\n\n    ~CertificateStoreC() = default;\n\n    GetInstalledCertificateStatus getCertificateIds(const Vector<GetCertificateIdType>& certificateType, Vector<CertificateChainHash>& out) override {\n        out.clear();\n\n        ocpp_cert_chain_hash *cch;\n\n        auto ret = certstore->getCertificateIds(certstore->user_data, &certificateType[0], certificateType.size(), &cch);\n        if (ret == GetInstalledCertificateStatus_NotFound || !cch) {\n            return GetInstalledCertificateStatus_NotFound;\n        }\n\n        bool err = false;\n        \n        for (ocpp_cert_chain_hash *it = cch; it && !err; it = it->next) {\n            out.emplace_back();\n            auto &chd_el = out.back();\n            chd_el.certificateType = it->certType;\n            memcpy(&chd_el.certificateHashData, &it->certHashData, sizeof(ocpp_cert_hash));\n        }\n\n        while (cch) {\n            ocpp_cert_chain_hash *el = cch;\n            cch = cch->next;\n            el->invalidate(el);\n        }\n\n        if (err) {\n            out.clear();\n        }\n\n        return out.empty() ?\n                GetInstalledCertificateStatus_NotFound :\n                GetInstalledCertificateStatus_Accepted;\n    }\n\n    DeleteCertificateStatus deleteCertificate(const CertificateHash& hash) override {\n        return certstore->deleteCertificate(certstore->user_data, &hash);\n    }\n\n    InstallCertificateStatus installCertificate(InstallCertificateType certificateType, const char *certificate) override {\n        return certstore->installCertificate(certstore->user_data, certificateType, certificate);\n    }\n};\n\nstd::unique_ptr<CertificateStore> makeCertificateStoreCwrapper(ocpp_cert_store *certstore) {\n    return std::unique_ptr<CertificateStore>(new CertificateStoreC(certstore));\n}\n\n} //namespace MicroOcpp\n#endif //MO_ENABLE_CERT_MGMT\n"
  },
  {
    "path": "src/MicroOcpp/Model/Certificates/Certificate_c.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CERTIFICATE_C_H\n#define MO_CERTIFICATE_C_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <stddef.h>\n\n#include <MicroOcpp/Model/Certificates/Certificate.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct ocpp_cert_chain_hash {\n    void *user_data; //set this at your choice. MO passes it back to the functions below\n\n    enum GetCertificateIdType certType;\n    ocpp_cert_hash certHashData;\n    //ocpp_cert_hash *childCertificateHashData; \n\n    struct ocpp_cert_chain_hash *next; //link to next list element if result of getCertificateIds\n\n    void (*invalidate)(void *user_data); //free resources here. Guaranteed to be called\n} ocpp_cert_chain_hash;\n\ntypedef struct ocpp_cert_store {\n    void *user_data; //set this at your choice. MO passes it back to the functions below\n\n    enum GetInstalledCertificateStatus (*getCertificateIds)(void *user_data, const enum GetCertificateIdType certType [], size_t certTypeLen, ocpp_cert_chain_hash **out);\n    enum DeleteCertificateStatus (*deleteCertificate)(void *user_data, const ocpp_cert_hash *hash);\n    enum InstallCertificateStatus (*installCertificate)(void *user_data, enum InstallCertificateType certType, const char *cert);\n} ocpp_cert_store;\n\n#ifdef __cplusplus\n} //extern \"C\"\n\n#include <memory>\n\nnamespace MicroOcpp {\n\nstd::unique_ptr<CertificateStore> makeCertificateStoreCwrapper(ocpp_cert_store *certstore);\n\n} //namespace MicroOcpp\n\n#endif //__cplusplus\n\n#endif //MO_ENABLE_CERT_MGMT\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/ConnectorBase/ChargePointErrorData.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CHARGEPOINTERRORCODE_H\n#define MO_CHARGEPOINTERRORCODE_H\n\n#include <stdint.h>\n\nnamespace MicroOcpp {\n\nstruct ErrorData {\n    bool isError = false; //if any error information is set\n    bool isFaulted = false; //if this is a severe error and the EVSE should go into the faulted state\n    uint8_t severity = 1; //severity: don't send less severe errors during highly severe error condition\n    const char *errorCode = nullptr; //see ChargePointErrorCode (p. 76/77) for possible values\n    const char *info = nullptr; //Additional free format information related to the error\n    const char *vendorId = nullptr; //vendor-specific implementation identifier\n    const char *vendorErrorCode = nullptr; //vendor-specific error code\n\n    ErrorData() = default;\n\n    ErrorData(const char *errorCode = nullptr) : errorCode(errorCode) {\n        if (errorCode) {\n            isError = true;\n            isFaulted = true;\n        }\n    }\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/ConnectorBase/ChargePointStatus.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CHARGEPOINTSTATUS_H\n#define MO_CHARGEPOINTSTATUS_H\n\n#include <MicroOcpp/Version.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef enum ChargePointStatus {\n    ChargePointStatus_UNDEFINED, //internal use only - no OCPP standard value \n    ChargePointStatus_Available,\n    ChargePointStatus_Preparing,\n    ChargePointStatus_Charging,\n    ChargePointStatus_SuspendedEVSE,\n    ChargePointStatus_SuspendedEV,\n    ChargePointStatus_Finishing,\n    ChargePointStatus_Reserved,\n    ChargePointStatus_Unavailable,\n    ChargePointStatus_Faulted\n\n#if MO_ENABLE_V201\n    ,ChargePointStatus_Occupied\n#endif\n\n}   ChargePointStatus;\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/ConnectorBase/Connector.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Core/Configuration.h>\n\n#include <MicroOcpp/Operations/Authorize.h>\n#include <MicroOcpp/Operations/StatusNotification.h>\n#include <MicroOcpp/Operations/StartTransaction.h>\n#include <MicroOcpp/Operations/StopTransaction.h>\n#include <MicroOcpp/Operations/CiStrings.h>\n\n#include <MicroOcpp/Debug.h>\n\n#include <MicroOcpp/Model/Metering/MeteringService.h>\n#include <MicroOcpp/Model/Reservation/ReservationService.h>\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n#include <MicroOcpp/Model/ConnectorBase/EvseId.h>\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/Connection.h>\n\n#ifndef MO_TX_CLEAN_ABORTED\n#define MO_TX_CLEAN_ABORTED 1\n#endif\n\nusing namespace MicroOcpp;\n\nConnector::Connector(Context& context, std::shared_ptr<FilesystemAdapter> filesystem, unsigned int connectorId)\n        : MemoryManaged(\"v16.ConnectorBase.Connector\"), context(context), model(context.getModel()), filesystem(filesystem), connectorId(connectorId),\n          errorDataInputs(makeVector<std::function<ErrorData ()>>(getMemoryTag())), trackErrorDataInputs(makeVector<bool>(getMemoryTag())) {\n\n    context.getRequestQueue().addSendQueue(this); //register at RequestQueue as Request emitter\n\n    snprintf(availabilityBoolKey, sizeof(availabilityBoolKey), MO_CONFIG_EXT_PREFIX \"AVAIL_CONN_%d\", connectorId);\n    availabilityBool = declareConfiguration<bool>(availabilityBoolKey, true, MO_KEYVALUE_FN, false, false, false);\n\n#if MO_ENABLE_CONNECTOR_LOCK\n    declareConfiguration<bool>(\"UnlockConnectorOnEVSideDisconnect\", true); //read-write\n#else\n    declareConfiguration<bool>(\"UnlockConnectorOnEVSideDisconnect\", false, CONFIGURATION_VOLATILE, true); //read-only because there is no connector lock\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n    connectionTimeOutInt = declareConfiguration<int>(\"ConnectionTimeOut\", 30);\n    registerConfigurationValidator(\"ConnectionTimeOut\", VALIDATE_UNSIGNED_INT);\n    minimumStatusDurationInt = declareConfiguration<int>(\"MinimumStatusDuration\", 0);\n    registerConfigurationValidator(\"MinimumStatusDuration\", VALIDATE_UNSIGNED_INT);\n    stopTransactionOnInvalidIdBool = declareConfiguration<bool>(\"StopTransactionOnInvalidId\", true);\n    stopTransactionOnEVSideDisconnectBool = declareConfiguration<bool>(\"StopTransactionOnEVSideDisconnect\", true);\n    localPreAuthorizeBool = declareConfiguration<bool>(\"LocalPreAuthorize\", false);\n    localAuthorizeOfflineBool = declareConfiguration<bool>(\"LocalAuthorizeOffline\", true);\n    allowOfflineTxForUnknownIdBool = MicroOcpp::declareConfiguration<bool>(\"AllowOfflineTxForUnknownId\", false);\n\n    //if the EVSE goes offline, can it continue to charge without sending StartTx / StopTx to the server when going online again?\n    silentOfflineTransactionsBool = declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"SilentOfflineTransactions\", false);\n\n    //how long the EVSE tries the Authorize request before it enters offline mode\n    authorizationTimeoutInt = MicroOcpp::declareConfiguration<int>(MO_CONFIG_EXT_PREFIX \"AuthorizationTimeout\", 20);\n    registerConfigurationValidator(MO_CONFIG_EXT_PREFIX \"AuthorizationTimeout\", VALIDATE_UNSIGNED_INT);\n\n    //FreeVend mode\n    freeVendActiveBool = declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"FreeVendActive\", false);\n    freeVendIdTagString = declareConfiguration<const char*>(MO_CONFIG_EXT_PREFIX \"FreeVendIdTag\", \"\");\n\n    txStartOnPowerPathClosedBool = declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"TxStartOnPowerPathClosed\", false);\n\n    transactionMessageAttemptsInt = declareConfiguration<int>(\"TransactionMessageAttempts\", 3);\n    registerConfigurationValidator(\"TransactionMessageAttempts\", VALIDATE_UNSIGNED_INT);\n    transactionMessageRetryIntervalInt = declareConfiguration<int>(\"TransactionMessageRetryInterval\", 60);\n    registerConfigurationValidator(\"TransactionMessageRetryInterval\", VALIDATE_UNSIGNED_INT);\n\n    if (!availabilityBool) {\n        MO_DBG_ERR(\"Cannot declare availabilityBool\");\n    }\n\n    char txFnamePrefix [30];\n    snprintf(txFnamePrefix, sizeof(txFnamePrefix), \"tx-%u-\", connectorId);\n    size_t txFnamePrefixLen = strlen(txFnamePrefix);\n\n    unsigned int txNrPivot = std::numeric_limits<unsigned int>::max();\n\n    if (filesystem) {\n        filesystem->ftw_root([this, txFnamePrefix, txFnamePrefixLen, &txNrPivot] (const char *fname) {\n            if (!strncmp(fname, txFnamePrefix, txFnamePrefixLen)) {\n                unsigned int parsedTxNr = 0;\n                for (size_t i = txFnamePrefixLen; fname[i] >= '0' && fname[i] <= '9'; i++) {\n                    parsedTxNr *= 10;\n                    parsedTxNr += fname[i] - '0';\n                }\n\n                if (txNrPivot == std::numeric_limits<unsigned int>::max()) {\n                    txNrPivot = parsedTxNr;\n                    txNrBegin = parsedTxNr;\n                    txNrEnd = (parsedTxNr + 1) % MAX_TX_CNT;\n                    return 0;\n                }\n\n                if ((parsedTxNr + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT < MAX_TX_CNT / 2) {\n                    //parsedTxNr is after pivot point\n                    if ((parsedTxNr + 1 + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT > (txNrEnd + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT) {\n                        txNrEnd = (parsedTxNr + 1) % MAX_TX_CNT;\n                    }\n                } else if ((txNrPivot + MAX_TX_CNT - parsedTxNr) % MAX_TX_CNT < MAX_TX_CNT / 2) {\n                    //parsedTxNr is before pivot point\n                    if ((txNrPivot + MAX_TX_CNT - parsedTxNr) % MAX_TX_CNT > (txNrPivot + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT) {\n                        txNrBegin = parsedTxNr;\n                    }\n                }\n\n                MO_DBG_DEBUG(\"found %s%u.jsn - Internal range from %u to %u (exclusive)\", txFnamePrefix, parsedTxNr, txNrBegin, txNrEnd);\n            }\n            return 0;\n        });\n    }\n\n    MO_DBG_DEBUG(\"found %u transactions for connector %u. Internal range from %u to %u (exclusive)\", (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT, connectorId, txNrBegin, txNrEnd);\n    txNrFront = txNrBegin;\n\n    if (model.getTransactionStore()) {\n        unsigned int txNrLatest = (txNrEnd + MAX_TX_CNT - 1) % MAX_TX_CNT; //txNr of the most recent tx on flash\n        transaction = model.getTransactionStore()->getTransaction(connectorId, txNrLatest); //returns nullptr if txNrLatest does not exist on flash\n    } else {\n        MO_DBG_ERR(\"must initialize TxStore before Connector\");\n    }\n}\n\nConnector::~Connector() {\n    if (availabilityBool->getKey() == availabilityBoolKey) {\n        availabilityBool->setKey(nullptr);\n    }\n}\n\nChargePointStatus Connector::getStatus() {\n\n    ChargePointStatus res = ChargePointStatus_UNDEFINED;\n\n    /*\n    * Handle special case: This is the Connector for the whole CP (i.e. connectorId=0) --> only states Available, Unavailable, Faulted are possible\n    */\n    if (connectorId == 0) {\n        if (isFaulted()) {\n            res = ChargePointStatus_Faulted;\n        } else if (!isOperative()) {\n            res = ChargePointStatus_Unavailable;\n        } else {\n            res = ChargePointStatus_Available;\n        }\n        return res;\n    }\n\n    if (isFaulted()) {\n        res = ChargePointStatus_Faulted;\n    } else if (!isOperative()) {\n        res = ChargePointStatus_Unavailable;\n    } else if (transaction && transaction->isRunning()) {\n        //Transaction is currently running\n        if (connectorPluggedInput && !connectorPluggedInput()) { //special case when StopTransactionOnEVSideDisconnect is false\n            res = ChargePointStatus_SuspendedEV;\n        } else if (!ocppPermitsCharge() ||\n                (evseReadyInput && !evseReadyInput())) { \n            res = ChargePointStatus_SuspendedEVSE;\n        } else if (evReadyInput && !evReadyInput()) {\n            res = ChargePointStatus_SuspendedEV;\n        } else {\n            res = ChargePointStatus_Charging;\n        }\n    }\n    #if MO_ENABLE_RESERVATION\n    else if (model.getReservationService() && model.getReservationService()->getReservation(connectorId)) {\n        res = ChargePointStatus_Reserved;\n    }\n    #endif \n    else if ((!transaction) &&                                           //no transaction process occupying the connector\n               (!connectorPluggedInput || !connectorPluggedInput()) &&   //no vehicle plugged\n               (!occupiedInput || !occupiedInput())) {                       //occupied override clear\n        res = ChargePointStatus_Available;\n    } else {\n        /*\n         * Either in Preparing or Finishing state. Only way to know is from previous state\n         */\n        const auto previous = currentStatus;\n        if (previous == ChargePointStatus_Finishing ||\n                previous == ChargePointStatus_Charging ||\n                previous == ChargePointStatus_SuspendedEV ||\n                previous == ChargePointStatus_SuspendedEVSE ||\n                (transaction && transaction->getStartSync().isRequested())) { //transaction process still occupying the connector\n            res = ChargePointStatus_Finishing;\n        } else {\n            res = ChargePointStatus_Preparing;\n        }\n    }\n\n#if MO_ENABLE_V201\n    if (model.getVersion().major == 2) {\n        //OCPP 2.0.1: map v1.6 status onto v2.0.1\n        if (res == ChargePointStatus_Preparing ||\n                res == ChargePointStatus_Charging ||\n                res == ChargePointStatus_SuspendedEV ||\n                res == ChargePointStatus_SuspendedEVSE ||\n                res == ChargePointStatus_Finishing) {\n            res = ChargePointStatus_Occupied;\n        }\n    }\n#endif\n\n    if (res == ChargePointStatus_UNDEFINED) {\n        MO_DBG_DEBUG(\"status undefined\");\n        return ChargePointStatus_Faulted; //internal error\n    }\n\n    return res;\n}\n\nbool Connector::ocppPermitsCharge() {\n    if (connectorId == 0) {\n        MO_DBG_WARN(\"not supported for connectorId == 0\");\n        return false;\n    }\n\n    bool suspendDeAuthorizedIdTag = transaction && transaction->isIdTagDeauthorized(); //if idTag status is \"DeAuthorized\" and if charging should stop\n    \n    //check special case for DeAuthorized idTags: FreeVend mode\n    if (suspendDeAuthorizedIdTag && freeVendActiveBool && freeVendActiveBool->getBool()) {\n        suspendDeAuthorizedIdTag = false;\n    }\n\n    // check charge permission depending on TxStartPoint\n    if (txStartOnPowerPathClosedBool && txStartOnPowerPathClosedBool->getBool()) {\n        // tx starts when the power path is closed. Advertise charging before transaction\n        return transaction &&\n                transaction->isActive() &&\n                transaction->isAuthorized() &&\n                !suspendDeAuthorizedIdTag;\n    } else {\n        // tx must be started before the power path can be closed\n        return transaction &&\n               transaction->isRunning() &&\n               transaction->isActive() &&\n               !suspendDeAuthorizedIdTag;\n    }\n}\n\nvoid Connector::loop() {\n\n    if (!trackLoopExecute) {\n        trackLoopExecute = true;\n        if (connectorPluggedInput) {\n            freeVendTrackPlugged = connectorPluggedInput();\n        }\n    }\n\n    if (transaction && ((transaction->isAborted() && MO_TX_CLEAN_ABORTED) || (transaction->isSilent() && transaction->getStopSync().isRequested()))) {\n        //If the transaction is aborted (invalidated before started) or is silent and has stopped. Delete all artifacts from flash\n        //This is an optimization. The memory management will attempt to remove those files again later\n        bool removed = true;\n        if (auto mService = model.getMeteringService()) {\n            mService->abortTxMeterData(connectorId);\n            removed &= mService->removeTxMeterData(connectorId, transaction->getTxNr());\n        }\n\n        if (removed) {\n            removed &= model.getTransactionStore()->remove(connectorId, transaction->getTxNr());\n        }\n\n        if (removed) {\n            if (txNrFront == txNrEnd) {\n                txNrFront = transaction->getTxNr();\n            }\n            txNrEnd = transaction->getTxNr(); //roll back creation of last tx entry\n        }\n\n        MO_DBG_DEBUG(\"collect aborted or silent transaction %u-%u %s\", connectorId, transaction->getTxNr(), removed ? \"\" : \"failure\");\n        MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n        transaction = nullptr;\n    }\n\n    if (transaction && transaction->isAborted()) {\n        MO_DBG_DEBUG(\"collect aborted transaction %u-%u\", connectorId, transaction->getTxNr());\n        MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n        transaction = nullptr;\n    }\n\n    if (transaction && transaction->getStopSync().isRequested()) {\n        MO_DBG_DEBUG(\"collect obsolete transaction %u-%u\", connectorId, transaction->getTxNr());\n        MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n        transaction = nullptr;\n    }\n\n    if (transaction) { //begin exclusively transaction-related operations\n            \n        if (connectorPluggedInput) {\n            if (transaction->isRunning() && transaction->isActive() && !connectorPluggedInput()) {\n                if (!stopTransactionOnEVSideDisconnectBool || stopTransactionOnEVSideDisconnectBool->getBool()) {\n                    MO_DBG_DEBUG(\"Stop Tx due to EV disconnect\");\n                    transaction->setStopReason(\"EVDisconnected\");\n                    transaction->setInactive();\n                    transaction->commit();\n                }\n            }\n\n            if (transaction->isActive() &&\n                    !transaction->getStartSync().isRequested() &&\n                    transaction->getBeginTimestamp() > MIN_TIME &&\n                    connectionTimeOutInt && connectionTimeOutInt->getInt() > 0 &&\n                    !connectorPluggedInput() &&\n                    model.getClock().now() - transaction->getBeginTimestamp() > connectionTimeOutInt->getInt()) {\n\n                MO_DBG_INFO(\"Session mngt: timeout\");\n                transaction->setInactive();\n                transaction->commit();\n\n                updateTxNotification(TxNotification_ConnectionTimeout);\n            }\n        }\n\n        if (transaction->isActive() &&\n                transaction->isIdTagDeauthorized() && ( //transaction has been deAuthorized\n                    !transaction->isRunning() ||        //if transaction hasn't started yet, always end\n                    !stopTransactionOnInvalidIdBool || stopTransactionOnInvalidIdBool->getBool())) { //if transaction is running, behavior depends on StopTransactionOnInvalidId\n            \n            MO_DBG_DEBUG(\"DeAuthorize session\");\n            transaction->setStopReason(\"DeAuthorized\");\n            transaction->setInactive();\n            transaction->commit();\n        }\n\n        /*\n        * Check conditions for start or stop transaction\n        */\n\n        if (!transaction->isRunning()) {\n            //start tx?\n\n            if (transaction->isActive() && transaction->isAuthorized() &&  //tx must be authorized\n                    (!connectorPluggedInput || connectorPluggedInput()) && //if applicable, connector must be plugged\n                    isOperative() && //only start tx if charger is free of error conditions\n                    (!txStartOnPowerPathClosedBool || !txStartOnPowerPathClosedBool->getBool() || !evReadyInput || evReadyInput()) && //if applicable, postpone tx start point to PowerPathClosed\n                    (!startTxReadyInput || startTxReadyInput())) { //if defined, user Input for allowing StartTx must be true\n                //start Transaction\n\n                MO_DBG_INFO(\"Session mngt: trigger StartTransaction\");\n\n                auto meteringService = model.getMeteringService();\n                if (transaction->getMeterStart() < 0 && meteringService) {\n                    auto meterStart = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionBegin);\n                    if (meterStart && *meterStart) {\n                        transaction->setMeterStart(meterStart->toInteger());\n                    } else {\n                        MO_DBG_ERR(\"MeterStart undefined\");\n                    }\n                }\n\n                if (transaction->getStartTimestamp() <= MIN_TIME) {\n                    transaction->setStartTimestamp(model.getClock().now());\n                    transaction->setStartBootNr(model.getBootNr());\n                }\n\n                transaction->getStartSync().setRequested();\n                transaction->getStartSync().setOpNr(context.getRequestQueue().getNextOpNr());\n\n                if (transaction->isSilent()) {\n                    MO_DBG_INFO(\"silent Transaction: omit StartTx\");\n                    transaction->getStartSync().confirm();\n                } else {\n                    //normal transaction, record txMeterData\n                    if (model.getMeteringService()) {\n                        model.getMeteringService()->beginTxMeterData(transaction.get());\n                    }\n                }\n\n                transaction->commit();\n\n                updateTxNotification(TxNotification_StartTx);\n\n                //fetchFrontRequest will create the StartTransaction and pass it to the message sender\n                return;\n            }\n        } else {\n            //stop tx?\n\n            if (!transaction->isActive() &&\n                    (!stopTxReadyInput || stopTxReadyInput())) {\n                //stop transaction\n\n                MO_DBG_INFO(\"Session mngt: trigger StopTransaction\");\n                \n                auto meteringService = model.getMeteringService();\n                if (transaction->getMeterStop() < 0 && meteringService) {\n                    auto meterStop = meteringService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionEnd);\n                    if (meterStop && *meterStop) {\n                        transaction->setMeterStop(meterStop->toInteger());\n                    } else {\n                        MO_DBG_ERR(\"MeterStop undefined\");\n                    }\n                }\n\n                if (transaction->getStopTimestamp() <= MIN_TIME) {\n                    transaction->setStopTimestamp(model.getClock().now());\n                    transaction->setStopBootNr(model.getBootNr());\n                }\n\n                transaction->getStopSync().setRequested();\n                transaction->getStopSync().setOpNr(context.getRequestQueue().getNextOpNr());\n\n                if (transaction->isSilent()) {\n                    MO_DBG_INFO(\"silent Transaction: omit StopTx\");\n                    transaction->getStopSync().confirm();\n                } else {\n                    //normal transaction, record txMeterData\n                    if (model.getMeteringService()) {\n                        model.getMeteringService()->endTxMeterData(transaction.get());\n                    }\n                }\n\n                transaction->commit();\n\n                updateTxNotification(TxNotification_StopTx);\n\n                //fetchFrontRequest will create the StopTransaction and pass it to the message sender\n                return;\n            }\n        }\n    } //end transaction-related operations\n\n    //handle FreeVend mode\n    if (freeVendActiveBool && freeVendActiveBool->getBool() && connectorPluggedInput) {\n        if (!freeVendTrackPlugged && connectorPluggedInput() && !transaction) {\n            const char *idTag = freeVendIdTagString ? freeVendIdTagString->getString() : \"\";\n            if (!idTag || *idTag == '\\0') {\n                idTag = \"A0000000\";\n            }\n            MO_DBG_INFO(\"begin FreeVend Tx using idTag %s\", idTag);\n            beginTransaction_authorized(idTag);\n            \n            if (!transaction) {\n                MO_DBG_ERR(\"could not begin FreeVend Tx\");\n            }\n        }\n\n        freeVendTrackPlugged = connectorPluggedInput();\n    }\n\n    ErrorData errorData {nullptr};\n    errorData.severity = 0;\n    int errorDataIndex = -1;\n\n    if (model.getVersion().major == 1 && model.getClock().now() >= MIN_TIME) {\n        //OCPP 1.6: use StatusNotification to send error codes\n\n        if (reportedErrorIndex >= 0) {\n            auto error = errorDataInputs[reportedErrorIndex].operator()();\n            if (error.isError) {\n                errorData = error;\n                errorDataIndex = reportedErrorIndex;\n            }\n        }\n\n        for (auto i = std::min(errorDataInputs.size(), trackErrorDataInputs.size()); i >= 1; i--) {\n            auto index = i - 1;\n            ErrorData error {nullptr};\n            if ((int)index != errorDataIndex) {\n                error = errorDataInputs[index].operator()();\n            } else {\n                error = errorData;\n            }\n            if (error.isError && !trackErrorDataInputs[index] && error.severity >= errorData.severity) {\n                //new error\n                errorData = error;\n                errorDataIndex = index;\n            } else if (error.isError && error.severity > errorData.severity) {\n                errorData = error;\n                errorDataIndex = index;\n            } else if (!error.isError && trackErrorDataInputs[index]) {\n                //reset error\n                trackErrorDataInputs[index] = false;\n            }\n        }\n\n        if (errorDataIndex != reportedErrorIndex) {\n            if (errorDataIndex >= 0 || MO_REPORT_NOERROR) {\n                reportedStatus = ChargePointStatus_UNDEFINED; //trigger sending currentStatus again with code NoError\n            } else {\n                reportedErrorIndex = -1;\n            }\n        }\n    } //if (model.getVersion().major == 1)\n\n    auto status = getStatus();\n\n    if (status != currentStatus) {\n        MO_DBG_DEBUG(\"Status changed %s -> %s %s\",\n                currentStatus == ChargePointStatus_UNDEFINED ? \"\" : cstrFromOcppEveState(currentStatus),\n                cstrFromOcppEveState(status),\n                minimumStatusDurationInt->getInt() ? \" (will report delayed)\" : \"\");\n        currentStatus = status;\n        t_statusTransition = mocpp_tick_ms();\n    }\n\n    if (reportedStatus != currentStatus &&\n            model.getClock().now() >= MIN_TIME &&\n            (minimumStatusDurationInt->getInt() <= 0 || //MinimumStatusDuration disabled\n            mocpp_tick_ms() - t_statusTransition >= ((unsigned long) minimumStatusDurationInt->getInt()) * 1000UL)) {\n        reportedStatus = currentStatus;\n        reportedErrorIndex = errorDataIndex;\n        if (errorDataIndex >= 0) {\n            trackErrorDataInputs[errorDataIndex] = true;\n        }\n        Timestamp reportedTimestamp = model.getClock().now();\n        reportedTimestamp -= (mocpp_tick_ms() - t_statusTransition) / 1000UL;\n\n        auto statusNotification =\n            #if MO_ENABLE_V201\n            model.getVersion().major == 2 ?\n                makeRequest(\n                    new Ocpp201::StatusNotification(connectorId, reportedStatus, reportedTimestamp)) :\n            #endif //MO_ENABLE_V201\n                makeRequest(\n                    new Ocpp16::StatusNotification(connectorId, reportedStatus, reportedTimestamp, errorData));\n\n        statusNotification->setTimeout(0);\n        context.initiateRequest(std::move(statusNotification));\n        return;\n    }\n\n    return;\n}\n\nbool Connector::isFaulted() {\n    //for (auto i = errorDataInputs.begin(); i != errorDataInputs.end(); ++i) {\n    for (size_t i = 0; i < errorDataInputs.size(); i++) {\n        if (errorDataInputs[i].operator()().isFaulted) {\n            return true;\n        }\n    }\n    return false;\n}\n\nconst char *Connector::getErrorCode() {\n    if (reportedErrorIndex >= 0) {\n        auto error = errorDataInputs[reportedErrorIndex].operator()();\n        if (error.isError && error.errorCode) {\n            return error.errorCode;\n        }\n    }\n    return nullptr;\n}\n\nstd::shared_ptr<Transaction> Connector::allocateTransaction() {\n\n    std::shared_ptr<Transaction> tx;\n\n    //clean possible aborted tx\n    unsigned int txr = txNrEnd;\n    unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT;\n    for (unsigned int i = 0; i < txSize; i++) {\n        txr = (txr + MAX_TX_CNT - 1) % MAX_TX_CNT; //decrement by 1\n\n        auto tx = model.getTransactionStore()->getTransaction(connectorId, txr);\n        //check if dangling silent tx, aborted tx, or corrupted entry (tx == null)\n        if (!tx || tx->isSilent() || (tx->isAborted() && MO_TX_CLEAN_ABORTED)) {\n            //yes, remove\n            bool removed = true;\n            if (auto mService = model.getMeteringService()) {\n                removed &= mService->removeTxMeterData(connectorId, txr);\n            }\n            if (removed) {\n                removed &= model.getTransactionStore()->remove(connectorId, txr);\n            }\n            if (removed) {\n                if (txNrFront == txNrEnd) {\n                    txNrFront = txr;\n                }\n                txNrEnd = txr;\n                MO_DBG_WARN(\"deleted dangling silent or aborted tx for new transaction\");\n            } else {\n                MO_DBG_ERR(\"memory corruption\");\n                break;\n            }\n        } else {\n            //no, tx record trimmed, end\n            break;\n        }\n    }\n\n    txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; //refresh after cleaning txs\n\n    //try to create new transaction\n    if (txSize < MO_TXRECORD_SIZE) {\n        tx = model.getTransactionStore()->createTransaction(connectorId, txNrEnd);\n    }\n\n    if (!tx) {\n        //could not create transaction - now, try to replace tx history entry\n\n        unsigned int txl = txNrBegin;\n        txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT;\n\n        for (unsigned int i = 0; i < txSize; i++) {\n\n            if (tx) {\n                //success, finished here\n                break;\n            }\n\n            //no transaction allocated, delete history entry to make space\n\n            auto txhist = model.getTransactionStore()->getTransaction(connectorId, txl);\n            //oldest entry, now check if it's history and can be removed or corrupted entry\n            if (!txhist || txhist->isCompleted() || txhist->isAborted() || (txhist->isSilent() && txhist->getStopSync().isRequested())) {\n                //yes, remove\n                bool removed = true;\n                if (auto mService = model.getMeteringService()) {\n                    removed &= mService->removeTxMeterData(connectorId, txl);\n                }\n                if (removed) {\n                    removed &= model.getTransactionStore()->remove(connectorId, txl);\n                }\n                if (removed) {\n                    txNrBegin = (txl + 1) % MAX_TX_CNT;\n                    if (txNrFront == txl) {\n                        txNrFront = txNrBegin;\n                    }\n                    MO_DBG_DEBUG(\"deleted tx history entry for new transaction\");\n                    MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n\n                    tx = model.getTransactionStore()->createTransaction(connectorId, txNrEnd);\n                } else {\n                    MO_DBG_ERR(\"memory corruption\");\n                    break;\n                }\n            } else {\n                //no, end of history reached, don't delete further tx\n                MO_DBG_DEBUG(\"cannot delete more tx\");\n                break;\n            }\n\n            txl++;\n            txl %= MAX_TX_CNT;\n        }\n    }\n\n    if (!tx) {\n        //couldn't create normal transaction -> check if to start charging without real transaction\n        if (silentOfflineTransactionsBool && silentOfflineTransactionsBool->getBool()) {\n            //try to handle charging session without sending StartTx or StopTx to the server\n            tx = model.getTransactionStore()->createTransaction(connectorId, txNrEnd, true);\n\n            if (tx) {\n                MO_DBG_DEBUG(\"created silent transaction\");\n            }\n        }\n    }\n\n    if (tx) {\n        //clean meter data which could still be here from a rolled-back transaction\n        if (auto mService = model.getMeteringService()) {\n            if (!mService->removeTxMeterData(connectorId, tx->getTxNr())) {\n                MO_DBG_ERR(\"memory corruption\");\n            }\n        }\n    }\n\n    if (tx) {\n        txNrEnd = (txNrEnd + 1) % MAX_TX_CNT;\n        MO_DBG_DEBUG(\"advance txNrEnd %u-%u\", connectorId, txNrEnd);\n        MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n    }\n\n    return tx;\n}\n\nstd::shared_ptr<Transaction> Connector::beginTransaction(const char *idTag) {\n\n    if (transaction) {\n        MO_DBG_WARN(\"tx process still running. Please call endTransaction(...) before\");\n        return nullptr;\n    }\n\n    MO_DBG_DEBUG(\"Begin transaction process (%s), prepare\", idTag != nullptr ? idTag : \"\");\n    \n    bool localAuthFound = false;\n    const char *parentIdTag = nullptr; //locally stored parentIdTag\n    bool offlineBlockedAuth = false; //if offline authorization will be blocked by local auth list entry\n\n    //check local OCPP whitelist\n    #if MO_ENABLE_LOCAL_AUTH\n    if (auto authService = model.getAuthorizationService()) {\n        auto localAuth = authService->getLocalAuthorization(idTag);\n\n        //check authorization status\n        if (localAuth && localAuth->getAuthorizationStatus() != AuthorizationStatus::Accepted) {\n            MO_DBG_DEBUG(\"local auth denied (%s)\", idTag);\n            offlineBlockedAuth = true;\n            localAuth = nullptr;\n        }\n\n        //check expiry\n        if (localAuth && localAuth->getExpiryDate() && *localAuth->getExpiryDate() < model.getClock().now()) {\n            MO_DBG_DEBUG(\"idTag %s local auth entry expired\", idTag);\n            offlineBlockedAuth = true;\n            localAuth = nullptr;\n        }\n\n        if (localAuth) {\n            localAuthFound = true;\n            parentIdTag = localAuth->getParentIdTag();\n        }\n    }\n    #endif //MO_ENABLE_LOCAL_AUTH\n\n    int reservationId = -1;\n    bool offlineBlockedResv = false; //if offline authorization will be blocked by reservation\n\n    //check if blocked by reservation\n    #if MO_ENABLE_RESERVATION\n    if (model.getReservationService()) {\n\n        auto reservation = model.getReservationService()->getReservation(\n                connectorId,\n                idTag,\n                parentIdTag);\n\n        if (reservation) {\n            reservationId = reservation->getReservationId();\n        }\n\n        if (reservation && !reservation->matches(\n                    idTag,\n                    parentIdTag)) {\n            //reservation blocks connector\n            offlineBlockedResv = true; //when offline, tx is always blocked\n\n            //if parentIdTag is known, abort this tx immediately, otherwise wait for Authorize.conf to decide\n            if (parentIdTag) {\n                //parentIdTag known\n                MO_DBG_INFO(\"connector %u reserved - abort transaction\", connectorId);\n                updateTxNotification(TxNotification_ReservationConflict);\n                return nullptr;\n            } else {\n                //parentIdTag unkown but local authorization failed in any case\n                MO_DBG_INFO(\"connector %u reserved - no local auth\", connectorId);\n                localAuthFound = false;\n            }\n        }\n    }\n    #endif //MO_ENABLE_RESERVATION\n\n    transaction = allocateTransaction();\n\n    if (!transaction) {\n        MO_DBG_ERR(\"could not allocate Tx\");\n        return nullptr;\n    }\n\n    if (!idTag || *idTag == '\\0') {\n        //input string is empty\n        transaction->setIdTag(\"\");\n    } else {\n        transaction->setIdTag(idTag);\n    }\n\n    if (parentIdTag) {\n        transaction->setParentIdTag(parentIdTag);\n    }\n\n    transaction->setBeginTimestamp(model.getClock().now());\n\n    //check for local preauthorization\n    if (localAuthFound && localPreAuthorizeBool && localPreAuthorizeBool->getBool()) {\n        MO_DBG_DEBUG(\"Begin transaction process (%s), preauthorized locally\", idTag != nullptr ? idTag : \"\");\n\n        if (reservationId >= 0) {\n            transaction->setReservationId(reservationId);\n        }\n        transaction->setAuthorized();\n\n        updateTxNotification(TxNotification_Authorized);\n    }\n\n    transaction->commit();\n\n    auto authorize = makeRequest(new Ocpp16::Authorize(context.getModel(), idTag));\n    authorize->setTimeout(authorizationTimeoutInt && authorizationTimeoutInt->getInt() > 0 ? authorizationTimeoutInt->getInt() * 1000UL : 20UL * 1000UL);\n\n    if (!context.getConnection().isConnected()) {\n        //WebSockt unconnected. Enter offline mode immediately\n        authorize->setTimeout(1);\n    }\n\n    auto tx = transaction;\n    authorize->setOnReceiveConfListener([this, tx] (JsonObject response) {\n        JsonObject idTagInfo = response[\"idTagInfo\"];\n\n        if (strcmp(\"Accepted\", idTagInfo[\"status\"] | \"UNDEFINED\")) {\n            //Authorization rejected, abort transaction\n            MO_DBG_DEBUG(\"Authorize rejected (%s), abort tx process\", tx->getIdTag());\n            tx->setIdTagDeauthorized();\n            tx->commit();\n            updateTxNotification(TxNotification_AuthorizationRejected);\n            return;\n        }\n\n        #if MO_ENABLE_RESERVATION\n        if (model.getReservationService()) {\n            auto reservation = model.getReservationService()->getReservation(\n                        connectorId,\n                        tx->getIdTag(),\n                        idTagInfo[\"parentIdTag\"] | (const char*) nullptr);\n            if (reservation) {\n                //reservation found for connector\n                if (reservation->matches(\n                            tx->getIdTag(),\n                            idTagInfo[\"parentIdTag\"] | (const char*) nullptr)) {\n                    MO_DBG_INFO(\"connector %u matches reservationId %i\", connectorId, reservation->getReservationId());\n                    tx->setReservationId(reservation->getReservationId());\n                } else {\n                    //reservation found for connector but does not match idTag or parentIdTag\n                    MO_DBG_INFO(\"connector %u reserved - abort transaction\", connectorId);\n                    tx->setInactive();\n                    tx->commit();\n                    updateTxNotification(TxNotification_ReservationConflict);\n                    return;\n                }\n            }\n        }\n        #endif //MO_ENABLE_RESERVATION\n\n        if (idTagInfo.containsKey(\"parentIdTag\")) {\n            tx->setParentIdTag(idTagInfo[\"parentIdTag\"] | \"\");\n        }\n\n        MO_DBG_DEBUG(\"Authorized transaction process (%s)\", tx->getIdTag());\n        tx->setAuthorized();\n        tx->commit();\n\n        updateTxNotification(TxNotification_Authorized);\n    });\n\n    //capture local auth and reservation check in for timeout handler\n    authorize->setOnTimeoutListener([this, tx,\n                offlineBlockedAuth, \n                offlineBlockedResv, \n                localAuthFound,\n                reservationId] () {\n\n        if (offlineBlockedAuth) {\n            //local auth entry exists, but is expired -> avoid offline tx\n            MO_DBG_DEBUG(\"Abort transaction process (%s), timeout, expired local auth\", tx->getIdTag());\n            tx->setInactive();\n            tx->commit();\n            updateTxNotification(TxNotification_AuthorizationTimeout);\n            return;\n        }\n\n        if (offlineBlockedResv) {\n            //reservation found for connector but does not match idTag or parentIdTag\n            MO_DBG_INFO(\"connector %u reserved (offline) - abort transaction\", connectorId);\n            tx->setInactive();\n            tx->commit();\n            updateTxNotification(TxNotification_ReservationConflict);\n            return;\n        }\n\n        if (localAuthFound && localAuthorizeOfflineBool && localAuthorizeOfflineBool->getBool()) {\n            MO_DBG_DEBUG(\"Offline transaction process (%s), locally authorized\", tx->getIdTag());\n            if (reservationId >= 0) {\n                tx->setReservationId(reservationId);\n            }\n            tx->setAuthorized();\n            tx->commit();\n\n            updateTxNotification(TxNotification_Authorized);\n            return;\n        }\n\n        if (allowOfflineTxForUnknownIdBool && allowOfflineTxForUnknownIdBool->getBool()) {\n            MO_DBG_DEBUG(\"Offline transaction process (%s), allow unknown ID\", tx->getIdTag());\n            if (reservationId >= 0) {\n                tx->setReservationId(reservationId);\n            }\n            tx->setAuthorized();\n            tx->commit();\n            updateTxNotification(TxNotification_Authorized);\n            return;\n        }\n\n        MO_DBG_DEBUG(\"Abort transaction process (%s): timeout\", tx->getIdTag());\n        tx->setInactive();\n        tx->commit();\n        updateTxNotification(TxNotification_AuthorizationTimeout);\n        return; //offline tx disabled\n    });\n    context.initiateRequest(std::move(authorize));\n\n    return transaction;\n}\n\nstd::shared_ptr<Transaction> Connector::beginTransaction_authorized(const char *idTag, const char *parentIdTag) {\n    \n    if (transaction) {\n        MO_DBG_WARN(\"tx process still running. Please call endTransaction(...) before\");\n        return nullptr;\n    }\n\n    transaction = allocateTransaction();\n\n    if (!transaction) {\n        MO_DBG_ERR(\"could not allocate Tx\");\n        return nullptr;\n    }\n\n    if (!idTag || *idTag == '\\0') {\n        //input string is empty\n        transaction->setIdTag(\"\");\n    } else {\n        transaction->setIdTag(idTag);\n    }\n\n    if (parentIdTag) {\n        transaction->setParentIdTag(parentIdTag);\n    }\n\n    transaction->setBeginTimestamp(model.getClock().now());\n    \n    MO_DBG_DEBUG(\"Begin transaction process (%s), already authorized\", idTag != nullptr ? idTag : \"\");\n\n    transaction->setAuthorized();\n\n    #if MO_ENABLE_RESERVATION\n    if (model.getReservationService()) {\n        if (auto reservation = model.getReservationService()->getReservation(connectorId, idTag, parentIdTag)) {\n            if (reservation->matches(idTag, parentIdTag)) {\n                transaction->setReservationId(reservation->getReservationId());\n            }\n        }\n    }\n    #endif //MO_ENABLE_RESERVATION\n\n    transaction->commit();\n\n    return transaction;\n}\n\nvoid Connector::endTransaction(const char *idTag, const char *reason) {\n\n    if (!transaction || !transaction->isActive()) {\n        //transaction already ended / not active anymore\n        return;\n    }\n\n    MO_DBG_DEBUG(\"End session started by idTag %s\",\n                            transaction->getIdTag());\n    \n    if (idTag && *idTag != '\\0') {\n        transaction->setStopIdTag(idTag);\n    }\n    \n    if (reason) {\n        transaction->setStopReason(reason);\n    }\n    transaction->setInactive();\n    transaction->commit();\n}\n\nstd::shared_ptr<Transaction>& Connector::getTransaction() {\n    return transaction;\n}\n\nbool Connector::isOperative() {\n    if (isFaulted()) {\n        return false;\n    }\n\n    if (!trackLoopExecute) {\n        return false;\n    }\n\n    //check for running transaction(s) - if yes then the connector is always operative\n    if (connectorId == 0) {\n        for (unsigned int cId = 1; cId < model.getNumConnectors(); cId++) {\n            if (model.getConnector(cId)->getTransaction() && model.getConnector(cId)->getTransaction()->isRunning()) {\n                return true;\n            }\n        }\n    } else {\n        if (transaction && transaction->isRunning()) {\n            return true;\n        }\n    }\n\n    #if MO_ENABLE_V201\n    if (model.getVersion().major == 2 && model.getTransactionService()) {\n        auto txService = model.getTransactionService();\n\n        if (connectorId == 0) {\n            for (unsigned int cId = 1; cId < model.getNumConnectors(); cId++) {\n                if (txService->getEvse(cId)->getTransaction() &&\n                        txService->getEvse(cId)->getTransaction()->started &&\n                        !txService->getEvse(cId)->getTransaction()->stopped) {\n                    return true;\n                }\n            }\n        } else {\n            if (txService->getEvse(connectorId)->getTransaction() &&\n                    txService->getEvse(connectorId)->getTransaction()->started &&\n                    !txService->getEvse(connectorId)->getTransaction()->stopped) {\n                return true;\n            }\n        }\n    }\n    #endif //MO_ENABLE_V201\n\n    return availabilityVolatile && availabilityBool->getBool();\n}\n\nvoid Connector::setAvailability(bool available) {\n    availabilityBool->setBool(available);\n    configuration_save();\n}\n\nvoid Connector::setAvailabilityVolatile(bool available) {\n    availabilityVolatile = available;\n}\n\nvoid Connector::setConnectorPluggedInput(std::function<bool()> connectorPlugged) {\n    this->connectorPluggedInput = connectorPlugged;\n}\n\nvoid Connector::setEvReadyInput(std::function<bool()> evRequestsEnergy) {\n    this->evReadyInput = evRequestsEnergy;\n}\n\nvoid Connector::setEvseReadyInput(std::function<bool()> connectorEnergized) {\n    this->evseReadyInput = connectorEnergized;\n}\n\nvoid Connector::addErrorCodeInput(std::function<const char*()> connectorErrorCode) {\n    addErrorDataInput([connectorErrorCode] () -> ErrorData {\n        return ErrorData(connectorErrorCode());\n    });\n}\n\nvoid Connector::addErrorDataInput(std::function<ErrorData ()> errorDataInput) {\n    this->errorDataInputs.push_back(errorDataInput);\n    this->trackErrorDataInputs.push_back(false);\n}\n\n#if MO_ENABLE_CONNECTOR_LOCK\nvoid Connector::setOnUnlockConnector(std::function<UnlockConnectorResult()> unlockConnector) {\n    this->onUnlockConnector = unlockConnector;\n}\n\nstd::function<UnlockConnectorResult()> Connector::getOnUnlockConnector() {\n    return this->onUnlockConnector;\n}\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\nvoid Connector::setStartTxReadyInput(std::function<bool()> startTxReady) {\n    this->startTxReadyInput = startTxReady;\n}\n\nvoid Connector::setStopTxReadyInput(std::function<bool()> stopTxReady) {\n    this->stopTxReadyInput = stopTxReady;\n}\n\nvoid Connector::setOccupiedInput(std::function<bool()> occupied) {\n    this->occupiedInput = occupied;\n}\n\nvoid Connector::setTxNotificationOutput(std::function<void(Transaction*, TxNotification)> txNotificationOutput) {\n    this->txNotificationOutput = txNotificationOutput;\n}\n\nvoid Connector::updateTxNotification(TxNotification event) {\n    if (txNotificationOutput) {\n        txNotificationOutput(transaction.get(), event);\n    }\n}\n\nunsigned int Connector::getFrontRequestOpNr() {\n\n    /*\n     * Advance front transaction?\n     */\n\n    unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrFront) % MAX_TX_CNT;\n\n    if (transactionFront && txSize == 0) {\n        //catch edge case where txBack has been rolled back and txFront was equal to txBack\n        MO_DBG_DEBUG(\"collect front transaction %u-%u after tx rollback\", connectorId, transactionFront->getTxNr());\n        MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n        transactionFront = nullptr;\n    }\n\n    for (unsigned int i = 0; i < txSize; i++) {\n\n        if (!transactionFront) {\n            transactionFront = model.getTransactionStore()->getTransaction(connectorId, txNrFront);\n\n            #if MO_DBG_LEVEL >= MO_DL_VERBOSE\n            if (transactionFront)\n            {\n                MO_DBG_VERBOSE(\"load front transaction %u-%u\", connectorId, transactionFront->getTxNr());\n            }\n            #endif\n        }\n\n        if (transactionFront && (transactionFront->isAborted() || transactionFront->isCompleted() || transactionFront->isSilent())) {\n            //advance front\n            MO_DBG_DEBUG(\"collect front transaction %u-%u\", connectorId, transactionFront->getTxNr());\n            transactionFront = nullptr;\n            txNrFront = (txNrFront + 1) % MAX_TX_CNT;\n            MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n        } else {\n            //front is accurate. Done here\n            break;\n        }\n    }\n\n    if (transactionFront) {\n        if (transactionFront->getStartSync().isRequested() && !transactionFront->getStartSync().isConfirmed()) {\n            return transactionFront->getStartSync().getOpNr();\n        }\n\n        if (transactionFront->getStopSync().isRequested() && !transactionFront->getStopSync().isConfirmed()) {\n            return transactionFront->getStopSync().getOpNr();\n        }\n    }\n\n    return NoOperation;\n}\n\nstd::unique_ptr<Request> Connector::fetchFrontRequest() {\n\n    if (transactionFront && !transactionFront->isSilent()) {\n        if (transactionFront->getStartSync().isRequested() && !transactionFront->getStartSync().isConfirmed()) {\n            //send StartTx?\n\n            bool cancelStartTx = false;\n\n            if (transactionFront->getStartTimestamp() < MIN_TIME &&\n                    transactionFront->getStartBootNr() != model.getBootNr()) {\n                //time not set, cannot be restored anymore -> invalid tx\n                MO_DBG_ERR(\"cannot recover tx from previus run\");\n\n                cancelStartTx = true;\n            }\n\n            if ((int)transactionFront->getStartSync().getAttemptNr() >= transactionMessageAttemptsInt->getInt()) {\n                MO_DBG_WARN(\"exceeded TransactionMessageAttempts. Discard transaction\");\n\n                cancelStartTx = true;\n            }\n\n            if (cancelStartTx) {\n                transactionFront->setSilent();\n                transactionFront->setInactive();\n                transactionFront->commit();\n\n                //clean up possible tx records\n                if (auto mSerivce = model.getMeteringService()) {\n                    mSerivce->removeTxMeterData(connectorId, transactionFront->getTxNr());\n                }\n                //next getFrontRequestOpNr() call will collect transactionFront\n                return nullptr;\n            }\n\n            Timestamp nextAttempt = transactionFront->getStartSync().getAttemptTime() +\n                                    transactionFront->getStartSync().getAttemptNr() * std::max(0, transactionMessageRetryIntervalInt->getInt());\n\n            if (nextAttempt > model.getClock().now()) {\n                return nullptr;\n            }\n\n            transactionFront->getStartSync().advanceAttemptNr();\n            transactionFront->getStartSync().setAttemptTime(model.getClock().now());\n            transactionFront->commit();\n\n            auto startTx = makeRequest(new Ocpp16::StartTransaction(model, transactionFront));\n            startTx->setOnReceiveConfListener([this] (JsonObject response) {\n                //fetch authorization status from StartTransaction.conf() for user notification\n\n                const char* idTagInfoStatus = response[\"idTagInfo\"][\"status\"] | \"_Undefined\";\n                if (strcmp(idTagInfoStatus, \"Accepted\")) {\n                    updateTxNotification(TxNotification_DeAuthorized);\n                }\n            });\n            auto transactionFront_capture = transactionFront;\n            startTx->setOnAbortListener([this, transactionFront_capture] () {\n                //shortcut to the attemptNr check above. Relevant if other operations block the queue while this StartTx is timing out\n                if (transactionFront_capture && (int)transactionFront_capture->getStartSync().getAttemptNr() >= transactionMessageAttemptsInt->getInt()) {\n                    MO_DBG_WARN(\"exceeded TransactionMessageAttempts. Discard transaction\");\n\n                    transactionFront_capture->setSilent();\n                    transactionFront_capture->setInactive();\n                    transactionFront_capture->commit();\n\n                    //clean up possible tx records\n                    if (auto mSerivce = model.getMeteringService()) {\n                        mSerivce->removeTxMeterData(connectorId, transactionFront_capture->getTxNr());\n                    }\n                    //next getFrontRequestOpNr() call will collect transactionFront\n                }\n            });\n\n            return startTx;\n        }\n\n        if (transactionFront->getStopSync().isRequested() && !transactionFront->getStopSync().isConfirmed()) {\n            //send StopTx?\n\n            if ((int)transactionFront->getStopSync().getAttemptNr() >= transactionMessageAttemptsInt->getInt()) {\n                MO_DBG_WARN(\"exceeded TransactionMessageAttempts. Discard transaction\");\n\n                transactionFront->setSilent();\n\n                //clean up possible tx records\n                if (auto mSerivce = model.getMeteringService()) {\n                    mSerivce->removeTxMeterData(connectorId, transactionFront->getTxNr());\n                }\n                //next getFrontRequestOpNr() call will collect transactionFront\n                return nullptr;\n            }\n\n            Timestamp nextAttempt = transactionFront->getStopSync().getAttemptTime() +\n                                    transactionFront->getStopSync().getAttemptNr() * std::max(0, transactionMessageRetryIntervalInt->getInt());\n\n            if (nextAttempt > model.getClock().now()) {\n                return nullptr;\n            }\n\n            transactionFront->getStopSync().advanceAttemptNr();\n            transactionFront->getStopSync().setAttemptTime(model.getClock().now());\n            transactionFront->commit();\n\n            std::shared_ptr<TransactionMeterData> stopTxData;\n\n            if (auto meteringService = model.getMeteringService()) {\n                stopTxData = meteringService->getStopTxMeterData(transactionFront.get());\n            }\n\n            std::unique_ptr<Request> stopTx;\n\n            if (stopTxData) {\n                stopTx = makeRequest(new Ocpp16::StopTransaction(model, transactionFront, stopTxData->retrieveStopTxData()));\n            } else {\n                stopTx = makeRequest(new Ocpp16::StopTransaction(model, transactionFront));\n            }\n            auto transactionFront_capture = transactionFront;\n            stopTx->setOnAbortListener([this, transactionFront_capture] () {\n                //shortcut to the attemptNr check above. Relevant if other operations block the queue while this StopTx is timing out\n                if ((int)transactionFront_capture->getStopSync().getAttemptNr() >= transactionMessageAttemptsInt->getInt()) {\n                    MO_DBG_WARN(\"exceeded TransactionMessageAttempts. Discard transaction\");\n\n                    transactionFront_capture->setSilent();\n                    transactionFront_capture->setInactive();\n                    transactionFront_capture->commit();\n\n                    //clean up possible tx records\n                    if (auto mSerivce = model.getMeteringService()) {\n                        mSerivce->removeTxMeterData(connectorId, transactionFront_capture->getTxNr());\n                    }\n                    //next getFrontRequestOpNr() call will collect transactionFront\n                }\n            });\n\n            return stopTx;\n        }\n    }\n\n    return nullptr;\n}\n\nbool Connector::triggerStatusNotification() {\n\n    ErrorData errorData {nullptr};\n    errorData.severity = 0;\n\n    if (reportedErrorIndex >= 0) {\n        errorData = errorDataInputs[reportedErrorIndex].operator()();\n    } else {\n        //find errorData with maximum severity\n        for (auto i = errorDataInputs.size(); i >= 1; i--) {\n            auto index = i - 1;\n            ErrorData error = errorDataInputs[index].operator()();\n            if (error.isError && error.severity >= errorData.severity) {\n                errorData = error;\n            }\n        }\n    }\n\n    auto statusNotification = makeRequest(new Ocpp16::StatusNotification(\n                connectorId,\n                getStatus(),\n                context.getModel().getClock().now(),\n                errorData));\n\n    statusNotification->setTimeout(60000);\n\n    context.getRequestQueue().sendRequestPreBoot(std::move(statusNotification));\n\n    return true;\n}\n\nunsigned int Connector::getTxNrBeginHistory() {\n    return txNrBegin;\n}\n\nunsigned int Connector::getTxNrFront() {\n    return txNrFront;\n}\n\nunsigned int Connector::getTxNrEnd() {\n    return txNrEnd;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/ConnectorBase/Connector.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CONNECTOR_H\n#define MO_CONNECTOR_H\n\n#include <MicroOcpp/Model/ConnectorBase/ChargePointStatus.h>\n#include <MicroOcpp/Model/ConnectorBase/ChargePointErrorData.h>\n#include <MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Core/RequestQueue.h>\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Operations/CiStrings.h>\n\n#include <functional>\n#include <memory>\n\n#ifndef MO_TXRECORD_SIZE\n#define MO_TXRECORD_SIZE 4 //no. of tx to hold on flash storage\n#endif\n\n#ifndef MO_REPORT_NOERROR\n#define MO_REPORT_NOERROR 0\n#endif\n\nnamespace MicroOcpp {\n\nclass Context;\nclass Model;\nclass Operation;\n\nclass Connector : public RequestEmitter, public MemoryManaged {\nprivate:\n    Context& context;\n    Model& model;\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    \n    const unsigned int connectorId;\n\n    std::shared_ptr<Transaction> transaction;\n\n    std::shared_ptr<Configuration> availabilityBool;\n    char availabilityBoolKey [sizeof(MO_CONFIG_EXT_PREFIX \"AVAIL_CONN_xxxx\") + 1];\n    bool availabilityVolatile = true;\n\n    std::function<bool()> connectorPluggedInput;\n    std::function<bool()> evReadyInput;\n    std::function<bool()> evseReadyInput;\n    Vector<std::function<ErrorData ()>> errorDataInputs;\n    Vector<bool> trackErrorDataInputs;\n    int reportedErrorIndex = -1; //last reported error\n    bool isFaulted();\n    const char *getErrorCode();\n\n    ChargePointStatus currentStatus = ChargePointStatus_UNDEFINED;\n    std::shared_ptr<Configuration> minimumStatusDurationInt; //in seconds\n    ChargePointStatus reportedStatus = ChargePointStatus_UNDEFINED;\n    unsigned long t_statusTransition = 0;\n\n#if MO_ENABLE_CONNECTOR_LOCK\n    std::function<UnlockConnectorResult()> onUnlockConnector;\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n    std::function<bool()> startTxReadyInput; //the StartTx request will be delayed while this Input is false\n    std::function<bool()> stopTxReadyInput; //the StopTx request will be delayed while this Input is false\n    std::function<bool()> occupiedInput; //instead of Available, go into Preparing / Finishing state\n\n    std::function<void(Transaction*,TxNotification)> txNotificationOutput;\n\n    std::shared_ptr<Configuration> connectionTimeOutInt; //in seconds\n    std::shared_ptr<Configuration> stopTransactionOnInvalidIdBool;\n    std::shared_ptr<Configuration> stopTransactionOnEVSideDisconnectBool;\n    std::shared_ptr<Configuration> localPreAuthorizeBool;\n    std::shared_ptr<Configuration> localAuthorizeOfflineBool;\n    std::shared_ptr<Configuration> allowOfflineTxForUnknownIdBool;\n\n    std::shared_ptr<Configuration> silentOfflineTransactionsBool;\n    std::shared_ptr<Configuration> authorizationTimeoutInt; //in seconds\n    std::shared_ptr<Configuration> freeVendActiveBool;\n    std::shared_ptr<Configuration> freeVendIdTagString;\n    bool freeVendTrackPlugged = false;\n\n    std::shared_ptr<Configuration> txStartOnPowerPathClosedBool; // this postpones the tx start point to when evReadyInput becomes true\n\n    std::shared_ptr<Configuration> transactionMessageAttemptsInt;\n    std::shared_ptr<Configuration> transactionMessageRetryIntervalInt;\n\n    bool trackLoopExecute = false; //if loop has been executed once\n\n    unsigned int txNrBegin = 0; //oldest (historical) transaction on flash. Has no function, but is useful for error diagnosis\n    unsigned int txNrFront = 0; //oldest transaction which is still queued to be sent to the server\n    unsigned int txNrEnd = 0; //one position behind newest transaction\n\n    std::shared_ptr<Transaction> transactionFront;\npublic:\n    Connector(Context& context, std::shared_ptr<FilesystemAdapter> filesystem, unsigned int connectorId);\n    Connector(const Connector&) = delete;\n    Connector(Connector&&) = delete;\n    Connector& operator=(const Connector&) = delete;\n\n    ~Connector();\n\n    /*\n     * beginTransaction begins the transaction process which eventually leads to a StartTransaction\n     * request in the normal case.\n     * \n     * If the transaction process begins successfully, a Transaction object is returned\n     * If no transaction process begins due to this call, nullptr is returned (e.g. memory allocation failed)\n     */\n    std::shared_ptr<Transaction> beginTransaction(const char *idTag);\n    std::shared_ptr<Transaction> beginTransaction_authorized(const char *idTag, const char *parentIdTag = nullptr);\n\n    /*\n     * End the current transaction process, if existing and not ended yet. This eventually leads to\n     * a StopTransaction request, if the transaction process has actually ended due to this call. It\n     * is safe to call this function at any time even if no transaction is running\n     */\n    void endTransaction(const char *idTag = nullptr, const char *reason = nullptr);\n    \n    std::shared_ptr<Transaction>& getTransaction();\n\n    //create detached transaction - won't have any side-effects with the transaction handling of this lib\n    std::shared_ptr<Transaction> allocateTransaction(); \n\n    bool isOperative();\n    void setAvailability(bool available);\n    void setAvailabilityVolatile(bool available); //set inoperative state but keep only until reboot at most\n    void setConnectorPluggedInput(std::function<bool()> connectorPlugged);\n    void setEvReadyInput(std::function<bool()> evRequestsEnergy);\n    void setEvseReadyInput(std::function<bool()> connectorEnergized);\n    void addErrorCodeInput(std::function<const char*()> connectorErrorCode);\n    void addErrorDataInput(std::function<ErrorData ()> errorCodeInput);\n\n    void loop();\n\n    ChargePointStatus getStatus();\n\n    bool ocppPermitsCharge();\n\n#if MO_ENABLE_CONNECTOR_LOCK\n    void setOnUnlockConnector(std::function<UnlockConnectorResult()> unlockConnector);\n    std::function<UnlockConnectorResult()> getOnUnlockConnector();\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n    void setStartTxReadyInput(std::function<bool()> startTxReady);\n    void setStopTxReadyInput(std::function<bool()> stopTxReady);\n    void setOccupiedInput(std::function<bool()> occupied);\n\n    void setTxNotificationOutput(std::function<void(Transaction*,TxNotification)> txNotificationOutput);\n    void updateTxNotification(TxNotification event);\n\n    unsigned int getFrontRequestOpNr() override;\n    std::unique_ptr<Request> fetchFrontRequest() override;\n\n    bool triggerStatusNotification();\n\n    unsigned int getTxNrBeginHistory(); //if getTxNrBeginHistory() != getTxNrFront(), then return value is the txNr of the oldest tx history entry. If equal to getTxNrFront(), then the history is empty\n    unsigned int getTxNrFront(); //if getTxNrEnd() != getTxNrFront(), then return value is the txNr of the oldest transaction queued to be sent to the server. If equal to getTxNrEnd(), then there is no tx to be sent to the server\n    unsigned int getTxNrEnd(); //upper limit for the range of valid txNrs\n};\n\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/ConnectorBase/ConnectorsCommon.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Operations/ChangeAvailability.h>\n#include <MicroOcpp/Operations/ChangeConfiguration.h>\n#include <MicroOcpp/Operations/ClearCache.h>\n#include <MicroOcpp/Operations/DataTransfer.h>\n#include <MicroOcpp/Operations/GetConfiguration.h>\n#include <MicroOcpp/Operations/RemoteStartTransaction.h>\n#include <MicroOcpp/Operations/RemoteStopTransaction.h>\n#include <MicroOcpp/Operations/Reset.h>\n#include <MicroOcpp/Operations/TriggerMessage.h>\n#include <MicroOcpp/Operations/UnlockConnector.h>\n\n#include <MicroOcpp/Operations/Authorize.h>\n#include <MicroOcpp/Operations/StartTransaction.h>\n#include <MicroOcpp/Operations/StatusNotification.h>\n#include <MicroOcpp/Operations/StopTransaction.h>\n#include <MicroOcpp/Operations/TransactionEvent.h>\n\n#include <MicroOcpp/Debug.h>\n#include <MicroOcpp/Version.h>\n\nusing namespace MicroOcpp;\n\nConnectorsCommon::ConnectorsCommon(Context& context, unsigned int numConn, std::shared_ptr<FilesystemAdapter> filesystem) :\n        MemoryManaged(\"v16.ConnectorBase.ConnectorsCommon\"), context(context) {\n    \n    declareConfiguration<int>(\"NumberOfConnectors\", numConn >= 1 ? numConn - 1 : 0, CONFIGURATION_VOLATILE, true);\n    \n    /*\n     * Further configuration keys which correspond to the Core profile\n     */\n    declareConfiguration<bool>(\"AuthorizeRemoteTxRequests\", false);\n    declareConfiguration<int>(\"GetConfigurationMaxKeys\", 30, CONFIGURATION_VOLATILE, true);\n    \n    context.getOperationRegistry().registerOperation(\"ChangeAvailability\", [&context] () {\n        return new Ocpp16::ChangeAvailability(context.getModel());});\n    context.getOperationRegistry().registerOperation(\"ChangeConfiguration\", [] () {\n        return new Ocpp16::ChangeConfiguration();});\n    context.getOperationRegistry().registerOperation(\"ClearCache\", [filesystem] () {\n        return new Ocpp16::ClearCache(filesystem);});\n    context.getOperationRegistry().registerOperation(\"DataTransfer\", [] () {\n        return new Ocpp16::DataTransfer();});\n    context.getOperationRegistry().registerOperation(\"GetConfiguration\", [] () {\n        return new Ocpp16::GetConfiguration();});\n    context.getOperationRegistry().registerOperation(\"RemoteStartTransaction\", [&context] () {\n        return new Ocpp16::RemoteStartTransaction(context.getModel());});\n    context.getOperationRegistry().registerOperation(\"RemoteStopTransaction\", [&context] () {\n        return new Ocpp16::RemoteStopTransaction(context.getModel());});\n    context.getOperationRegistry().registerOperation(\"Reset\", [&context] () {\n        return new Ocpp16::Reset(context.getModel());});\n    context.getOperationRegistry().registerOperation(\"TriggerMessage\", [&context] () {\n        return new Ocpp16::TriggerMessage(context);});\n    context.getOperationRegistry().registerOperation(\"UnlockConnector\", [&context] () {\n        return new Ocpp16::UnlockConnector(context.getModel());});\n\n    /*\n     * Register further message handlers to support echo mode: when this library\n     * is connected with a WebSocket echo server, let it reply to its own requests.\n     * Mocking an OCPP Server on the same device makes running (unit) tests easier.\n     */\n#if MO_ENABLE_V201\n    if (context.getVersion().major == 2) {\n        // OCPP 2.0.1 compliant echo messages\n        context.getOperationRegistry().registerOperation(\"Authorize\", [&context] () {\n            return new Ocpp201::Authorize(context.getModel(), \"\");});\n        context.getOperationRegistry().registerOperation(\"TransactionEvent\", [&context] () {\n            return new Ocpp201::TransactionEvent(context.getModel(), nullptr);});\n    } else\n#endif //MO_ENABLE_V201\n    {\n        // OCPP 1.6 compliant echo messages\n        context.getOperationRegistry().registerOperation(\"Authorize\", [&context] () {\n            return new Ocpp16::Authorize(context.getModel(), \"\");});\n        context.getOperationRegistry().registerOperation(\"StartTransaction\", [&context] () {\n            return new Ocpp16::StartTransaction(context.getModel(), nullptr);});\n        context.getOperationRegistry().registerOperation(\"StopTransaction\", [&context] () {\n            return new Ocpp16::StopTransaction(context.getModel(), nullptr);});\n    }\n    // OCPP 1.6 + 2.0.1 compliant echo messages\n    context.getOperationRegistry().registerOperation(\"StatusNotification\", [&context] () {\n        return new Ocpp16::StatusNotification(-1, ChargePointStatus_UNDEFINED, Timestamp());});\n}\n\nvoid ConnectorsCommon::loop() {\n    //do nothing\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CHARGECONTROLCOMMON_H\n#define MO_CHARGECONTROLCOMMON_H\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Context;\n\nclass ConnectorsCommon : public MemoryManaged {\nprivate:\n    Context& context;\npublic:\n    ConnectorsCommon(Context& context, unsigned int numConnectors, std::shared_ptr<FilesystemAdapter> filesystem);\n\n    void loop();\n};\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/ConnectorBase/EvseId.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_EVSEID_H\n#define MO_EVSEID_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n// number of EVSE IDs (including 0). Defaults to MO_NUMCONNECTORS if defined, otherwise to 2\n#ifndef MO_NUM_EVSEID\n#if defined(MO_NUMCONNECTORS)\n#define MO_NUM_EVSEID MO_NUMCONNECTORS\n#else\n#define MO_NUM_EVSEID 2\n#endif\n#endif // MO_NUM_EVSEID\n\nnamespace MicroOcpp {\n\n// EVSEType (2.23)\nstruct EvseId {\n    int id;\n    int connectorId = -1; //optional\n\n    EvseId(int id) : id(id) { }\n    EvseId(int id, int connectorId) : id(id), connectorId(connectorId) { }\n};\n\n}\n\n#endif // MO_ENABLE_V201\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_UNLOCKCONNECTORRESULT_H\n#define MO_UNLOCKCONNECTORRESULT_H\n\n#include <stdint.h>\n\n// Connector-lock related behavior (i.e. if UnlockConnectorOnEVSideDisconnect is RW; enable HW binding for UnlockConnector)\n#ifndef MO_ENABLE_CONNECTOR_LOCK\n#define MO_ENABLE_CONNECTOR_LOCK 0\n#endif\n\n#if MO_ENABLE_CONNECTOR_LOCK\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif // __cplusplus\n\n#ifndef MO_UNLOCK_TIMEOUT\n#define MO_UNLOCK_TIMEOUT 10000 // if Result is Pending, wait at most this period (in ms) until sending UnlockFailed\n#endif\n\ntypedef enum {\n    UnlockConnectorResult_UnlockFailed,\n    UnlockConnectorResult_Unlocked,\n    UnlockConnectorResult_Pending // unlock action not finished yet, result still unknown (MO will check again later)\n} UnlockConnectorResult;\n\n#ifdef __cplusplus\n}\n#endif // __cplusplus\n\n#endif // MO_ENABLE_CONNECTOR_LOCK\n#endif // MO_UNLOCKCONNECTORRESULT_H\n"
  },
  {
    "path": "src/MicroOcpp/Model/Diagnostics/DiagnosticsService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Diagnostics/DiagnosticsService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Debug.h>\n\n#include <MicroOcpp/Operations/GetDiagnostics.h>\n#include <MicroOcpp/Operations/DiagnosticsStatusNotification.h>\n\n//Fetch relevant data from other modules for diagnostics\n#include <MicroOcpp/Model/Boot/BootService.h>\n#include <MicroOcpp/Operations/StatusNotification.h> //for serializing ChargePointStatus\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Version.h> //for MO_ENABLE_V201\n#include <MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h> //for MO_ENABLE_CONNECTOR_LOCK\n\nusing MicroOcpp::DiagnosticsService;\nusing MicroOcpp::Ocpp16::DiagnosticsStatus;\nusing MicroOcpp::Request;\n\nDiagnosticsService::DiagnosticsService(Context& context) : MemoryManaged(\"v16.Diagnostics.DiagnosticsService\"), context(context), location(makeString(getMemoryTag())), diagFileList(makeVector<String>(getMemoryTag())) {\n\n    context.getOperationRegistry().registerOperation(\"GetDiagnostics\", [this] () {\n        return new Ocpp16::GetDiagnostics(*this);});\n\n    //Register message handler for TriggerMessage operation\n    context.getOperationRegistry().registerOperation(\"DiagnosticsStatusNotification\", [this] () {\n        return new Ocpp16::DiagnosticsStatusNotification(getDiagnosticsStatus());});\n}\n\nDiagnosticsService::~DiagnosticsService() {\n    MO_FREE(diagPreamble);\n    MO_FREE(diagPostamble);\n}\n\nvoid DiagnosticsService::loop() {\n\n    if (ftpUpload && ftpUpload->isActive()) {\n        ftpUpload->loop();\n    }\n\n    if (ftpUpload) {\n        if (ftpUpload->isActive()) {\n            ftpUpload->loop();\n        } else {\n            MO_DBG_DEBUG(\"Deinit FTP upload\");\n            ftpUpload.reset();\n        }\n    }\n\n    auto notification = getDiagnosticsStatusNotification();\n    if (notification) {\n        context.initiateRequest(std::move(notification));\n    }\n\n    const auto& timestampNow = context.getModel().getClock().now();\n    if (retries > 0 && timestampNow >= nextTry) {\n\n        if (!uploadIssued) {\n            if (onUpload != nullptr) {\n                MO_DBG_DEBUG(\"Call onUpload\");\n                onUpload(location.c_str(), startTime, stopTime);\n                uploadIssued = true;\n                uploadFailure = false;\n            } else {\n                MO_DBG_ERR(\"onUpload must be set! (see setOnUpload). Will abort\");\n                retries = 0;\n                uploadIssued = false;\n                uploadFailure = true;\n            }\n        }\n\n        if (uploadIssued) {\n            if (uploadStatusInput != nullptr && uploadStatusInput() == UploadStatus::Uploaded) {\n                //success!\n                MO_DBG_DEBUG(\"end upload routine (by status)\");\n                uploadIssued = false;\n                retries = 0;\n            }\n\n            //check if maximum time elapsed or failed\n            const int UPLOAD_TIMEOUT = 60;\n            if (timestampNow - nextTry >= UPLOAD_TIMEOUT\n                    || (uploadStatusInput != nullptr && uploadStatusInput() == UploadStatus::UploadFailed)) {\n                //maximum upload time elapsed or failed\n\n                if (uploadStatusInput == nullptr) {\n                    //No way to find out if failed. But maximum time has elapsed. Assume success\n                    MO_DBG_DEBUG(\"end upload routine (by timer)\");\n                    uploadIssued = false;\n                    retries = 0;\n                } else {\n                    //either we have UploadFailed status or (NotUploaded + timeout) here\n                    MO_DBG_WARN(\"Upload timeout or failed\");\n                    ftpUpload.reset();\n\n                    const int TRANSITION_DELAY = 10;\n                    if (retryInterval <= UPLOAD_TIMEOUT + TRANSITION_DELAY) {\n                        nextTry = timestampNow;\n                        nextTry += TRANSITION_DELAY; //wait for another 10 seconds\n                    } else {\n                        nextTry += retryInterval;\n                    }\n                    retries--;\n\n                    if (retries == 0) {\n                        MO_DBG_DEBUG(\"end upload routine (no more retry)\");\n                        uploadFailure = true;\n                    }\n                }\n            }\n        } //end if (uploadIssued)\n    } //end try upload\n}\n\n//timestamps before year 2021 will be treated as \"undefined\"\nMicroOcpp::String DiagnosticsService::requestDiagnosticsUpload(const char *location, unsigned int retries, unsigned int retryInterval, Timestamp startTime, Timestamp stopTime) {\n    if (onUpload == nullptr) {\n        return makeString(getMemoryTag());\n    }\n\n    String fileName;\n    if (refreshFilename) {\n        fileName = refreshFilename().c_str();\n    } else {\n        fileName = \"diagnostics.log\";\n    }\n\n    this->location.reserve(strlen(location) + 1 + fileName.size());\n\n    this->location = location;\n\n    if (!this->location.empty() && this->location.back() != '/') {\n        this->location.append(\"/\");\n    }\n    this->location.append(fileName.c_str());\n\n    this->retries = retries;\n    this->retryInterval = retryInterval;\n    this->startTime = startTime;\n    \n    Timestamp stopMin = Timestamp(2021,0,0,0,0,0);\n    if (stopTime >= stopMin) {\n        this->stopTime = stopTime;\n    } else {\n        auto newStop = context.getModel().getClock().now();\n        newStop += 3600 * 24 * 365; //set new stop time one year in future\n        this->stopTime = newStop;\n    }\n    \n#if MO_DBG_LEVEL >= MO_DL_INFO\n    {\n        char dbuf [JSONDATE_LENGTH + 1] = {'\\0'};\n        char dbuf2 [JSONDATE_LENGTH + 1] = {'\\0'};\n        this->startTime.toJsonString(dbuf, JSONDATE_LENGTH + 1);\n        this->stopTime.toJsonString(dbuf2, JSONDATE_LENGTH + 1);\n\n        MO_DBG_INFO(\"Scheduled Diagnostics upload!\\n\" \\\n                        \"                  location = %s\\n\" \\\n                        \"                  retries = %i\" \\\n                        \", retryInterval = %u\" \\\n                        \"                  startTime = %s\\n\" \\\n                        \"                  stopTime = %s\",\n                this->location.c_str(),\n                this->retries,\n                this->retryInterval,\n                dbuf,\n                dbuf2);\n    }\n#endif\n\n    nextTry = context.getModel().getClock().now();\n    nextTry += 5; //wait for 5s before upload\n    uploadIssued = false;\n\n#if MO_DBG_LEVEL >= MO_DL_DEBUG\n    {\n        char dbuf [JSONDATE_LENGTH + 1] = {'\\0'};\n        nextTry.toJsonString(dbuf, JSONDATE_LENGTH + 1);\n        MO_DBG_DEBUG(\"Initial try at %s\", dbuf);\n    }\n#endif\n\n    return fileName;\n}\n\nDiagnosticsStatus DiagnosticsService::getDiagnosticsStatus() {\n    if (uploadFailure) {\n        return DiagnosticsStatus::UploadFailed;\n    }\n\n    if (uploadIssued) {\n        if (uploadStatusInput != nullptr) {\n            switch (uploadStatusInput()) {\n                case UploadStatus::NotUploaded:\n                    return DiagnosticsStatus::Uploading;\n                case UploadStatus::Uploaded:\n                    return DiagnosticsStatus::Uploaded;\n                case UploadStatus::UploadFailed:\n                    return DiagnosticsStatus::UploadFailed;\n            }\n        }\n        return DiagnosticsStatus::Uploading;\n    }\n    return DiagnosticsStatus::Idle;\n}\n\nstd::unique_ptr<Request> DiagnosticsService::getDiagnosticsStatusNotification() {\n\n    if (getDiagnosticsStatus() != lastReportedStatus) {\n        lastReportedStatus = getDiagnosticsStatus();\n        if (lastReportedStatus != DiagnosticsStatus::Idle) {\n            Operation *diagNotificationMsg = new Ocpp16::DiagnosticsStatusNotification(lastReportedStatus);\n            auto diagNotification = makeRequest(diagNotificationMsg);\n            return diagNotification;\n        }\n    }\n\n    return nullptr;\n}\n\nvoid DiagnosticsService::setRefreshFilename(std::function<std::string()> refreshFn) {\n    this->refreshFilename = refreshFn;\n}\n\nvoid DiagnosticsService::setOnUpload(std::function<bool(const char *location, Timestamp &startTime, Timestamp &stopTime)> onUpload) {\n    this->onUpload = onUpload;\n}\n\nvoid DiagnosticsService::setOnUploadStatusInput(std::function<UploadStatus()> uploadStatusInput) {\n    this->uploadStatusInput = uploadStatusInput;\n}\n\nvoid DiagnosticsService::setDiagnosticsReader(std::function<size_t(char *buf, size_t size)> diagnosticsReader, std::function<void()> onClose, std::shared_ptr<FilesystemAdapter> filesystem) {\n\n    this->onUpload = [this, diagnosticsReader, onClose, filesystem] (const char *location, Timestamp &startTime, Timestamp &stopTime) -> bool {\n\n        auto ftpClient = context.getFtpClient();\n        if (!ftpClient) {\n            MO_DBG_ERR(\"FTP client not set\");\n            this->ftpUploadStatus = UploadStatus::UploadFailed;\n            return false;\n        }\n\n        const size_t diagPreambleSize = 128;\n        diagPreamble = static_cast<char*>(MO_MALLOC(getMemoryTag(), diagPreambleSize));\n        if (!diagPreamble) {\n            MO_DBG_ERR(\"OOM\");\n            this->ftpUploadStatus = UploadStatus::UploadFailed;\n            return false;\n        }\n        diagPreambleLen = 0;\n        diagPreambleTransferred = 0;\n\n        diagReaderHasData = diagnosticsReader ? true : false;\n\n        const size_t diagPostambleSize = 1024;\n        diagPostamble = static_cast<char*>(MO_MALLOC(getMemoryTag(), diagPostambleSize));\n        if (!diagPostamble) {\n            MO_DBG_ERR(\"OOM\");\n            this->ftpUploadStatus = UploadStatus::UploadFailed;\n            MO_FREE(diagPreamble);\n            return false;\n        }\n        diagPostambleLen = 0;\n        diagPostambleTransferred = 0;\n        diagFilesBackTransferred = 0;\n\n        auto& model = context.getModel();\n\n        auto cpVendor = makeString(getMemoryTag());\n        auto cpModel = makeString(getMemoryTag());\n        auto fwVersion = makeString(getMemoryTag());\n\n        if (auto bootService = model.getBootService()) {\n            if (auto cpCreds = bootService->getChargePointCredentials()) {\n                cpVendor = (*cpCreds)[\"chargePointVendor\"] | \"Vendor\";\n                cpModel = (*cpCreds)[\"chargePointModel\"] | \"Charger\";\n                fwVersion = (*cpCreds)[\"firmwareVersion\"] | \"\";\n            }\n        }\n\n        char jsonDate [JSONDATE_LENGTH + 1];\n        model.getClock().now().toJsonString(jsonDate, sizeof(jsonDate));\n\n        int ret;\n\n        ret = snprintf(diagPreamble, diagPreambleSize,\n                \"### %s %s - Hardware Diagnostics%s%s\\n%s\\n\",\n                cpVendor.c_str(),\n                cpModel.c_str(),\n                fwVersion.empty() ? \"\" : \" - v. \", fwVersion.c_str(),\n                jsonDate);\n\n        if (ret < 0 || (size_t)ret >= diagPreambleSize) {\n            MO_DBG_ERR(\"snprintf: %i\", ret);\n            this->ftpUploadStatus = UploadStatus::UploadFailed;\n            MO_FREE(diagPreamble);\n            MO_FREE(diagPostamble);\n            return false;\n        }\n\n        diagPreambleLen += (size_t)ret;\n\n        Connector *connector0 = model.getConnector(0);\n        Connector *connector1 = model.getConnector(1);\n        Transaction *connector1Tx = connector1 ? connector1->getTransaction().get() : nullptr;\n        Connector *connector2 = model.getNumConnectors() > 2 ? model.getConnector(2) : nullptr;\n        Transaction *connector2Tx = connector2 ? connector2->getTransaction().get() : nullptr;\n\n        ret = 0;\n\n        if (ret >= 0 && (size_t)ret + diagPostambleLen < diagPostambleSize) {\n            diagPostambleLen += (size_t)ret;\n            ret = snprintf(diagPostamble + diagPostambleLen, diagPostambleSize - diagPostambleLen,\n                \"\\n# OCPP\"\n                \"\\nclient_version=%s\"\n                \"\\nuptime=%lus\"\n                \"%s%s\"\n                \"%s%s\"\n                \"%s%s\"\n                \"\\nws_status=%s\"\n                \"\\nws_last_conn=%lus\"\n                \"\\nws_last_recv=%lus\"\n                \"%s%s\"\n                \"%s%s\"\n                \"%s%s\"\n                \"%s%s\"\n                \"%s%s\"\n                \"%s%s\"\n                \"%s%s\"\n                \"%s%s\"\n                \"\\nENABLE_CONNECTOR_LOCK=%i\"\n                \"\\nENABLE_FILE_INDEX=%i\"\n                \"\\nENABLE_V201=%i\"\n                \"\\n\",\n                MO_VERSION,\n                mocpp_tick_ms() / 1000UL,\n                connector0 ? \"\\nocpp_status_cId0=\" : \"\", connector0 ? cstrFromOcppEveState(connector0->getStatus()) : \"\",\n                connector1 ? \"\\nocpp_status_cId1=\" : \"\", connector1 ? cstrFromOcppEveState(connector1->getStatus()) : \"\",\n                connector2 ? \"\\nocpp_status_cId2=\" : \"\", connector2 ? cstrFromOcppEveState(connector2->getStatus()) : \"\",\n                context.getConnection().isConnected() ? \"connected\" : \"unconnected\",\n                context.getConnection().getLastConnected() / 1000UL,\n                context.getConnection().getLastRecv() / 1000UL,\n                connector1 ? \"\\ncId1_hasTx=\" : \"\", connector1 ? (connector1Tx ? \"1\" : \"0\") : \"\",\n                connector1Tx ? \"\\ncId1_txActive=\" : \"\", connector1Tx ? (connector1Tx->isActive() ? \"1\" : \"0\") : \"\",\n                connector1Tx ? \"\\ncId1_txHasStarted=\" : \"\", connector1Tx ? (connector1Tx->getStartSync().isRequested() ? \"1\" : \"0\") : \"\",\n                connector1Tx ? \"\\ncId1_txHasStopped=\" : \"\", connector1Tx ? (connector1Tx->getStopSync().isRequested() ? \"1\" : \"0\") : \"\",\n                connector2 ? \"\\ncId2_hasTx=\" : \"\", connector2 ? (connector2Tx ? \"1\" : \"0\") : \"\",\n                connector2Tx ? \"\\ncId2_txActive=\" : \"\", connector2Tx ? (connector2Tx->isActive() ? \"1\" : \"0\") : \"\",\n                connector2Tx ? \"\\ncId2_txHasStarted=\" : \"\", connector2Tx ? (connector2Tx->getStartSync().isRequested() ? \"1\" : \"0\") : \"\",\n                connector2Tx ? \"\\ncId2_txHasStopped=\" : \"\", connector2Tx ? (connector2Tx->getStopSync().isRequested() ? \"1\" : \"0\") : \"\",\n                MO_ENABLE_CONNECTOR_LOCK,\n                MO_ENABLE_FILE_INDEX,\n                MO_ENABLE_V201\n                );\n        }\n\n        if (filesystem) {\n\n            if (ret >= 0 && (size_t)ret + diagPostambleLen < diagPostambleSize) {\n                diagPostambleLen += (size_t)ret;\n                ret = snprintf(diagPostamble + diagPostambleLen, diagPostambleSize - diagPostambleLen, \"\\n# Filesystem\\n\");\n            }\n\n            filesystem->ftw_root([this, &ret] (const char *fname) -> int {\n                if (ret >= 0 && (size_t)ret + diagPostambleLen < diagPostambleSize) {\n                    diagPostambleLen += (size_t)ret;\n                    ret = snprintf(diagPostamble + diagPostambleLen, diagPostambleSize - diagPostambleLen, \"%s\\n\", fname);\n                }\n                diagFileList.emplace_back(fname);\n                return 0;\n            });\n\n            MO_DBG_DEBUG(\"discovered %zu files\", diagFileList.size());\n        }\n\n        if (ret >= 0 && (size_t)ret + diagPostambleLen < diagPostambleSize) {\n            diagPostambleLen += (size_t)ret;\n        } else {\n            char errMsg [64];\n            auto errLen = snprintf(errMsg, sizeof(errMsg), \"\\n[Diagnostics cut]\\n\");\n            size_t ellipseStart = std::min(diagPostambleSize - (size_t)errLen - 1, diagPostambleLen);\n            auto ret2 = snprintf(diagPostamble + ellipseStart, diagPostambleSize - ellipseStart, \"%s\", errMsg);\n            diagPostambleLen += (size_t)ret2;\n        }\n\n        this->ftpUpload = ftpClient->postFile(location,\n            [this, diagnosticsReader, filesystem] (unsigned char *buf, size_t size) -> size_t {\n                size_t written = 0;\n                if (written < size && diagPreambleTransferred < diagPreambleLen) {\n                    size_t writeLen = std::min(size - written, diagPreambleLen - diagPreambleTransferred);\n                    memcpy(buf + written, diagPreamble + diagPreambleTransferred, writeLen);\n                    diagPreambleTransferred += writeLen;\n                    written += writeLen;\n                }\n\n                while (written < size && diagReaderHasData && diagnosticsReader) {\n                    size_t writeLen = diagnosticsReader((char*)buf + written, size - written);\n                    if (writeLen == 0) {\n                        diagReaderHasData = false;\n                    }\n                    written += writeLen;\n                }\n\n                if (written < size && diagPostambleTransferred < diagPostambleLen) {\n                    size_t writeLen = std::min(size - written, diagPostambleLen - diagPostambleTransferred);\n                    memcpy(buf + written, diagPostamble + diagPostambleTransferred, writeLen);\n                    diagPostambleTransferred += writeLen;\n                    written += writeLen;\n                }\n\n                while (written < size && !diagFileList.empty() && filesystem) {\n\n                    char fpath [MO_MAX_PATH_SIZE];\n                    auto ret = snprintf(fpath, sizeof(fpath), \"%s%s\", MO_FILENAME_PREFIX, diagFileList.back().c_str());\n                    if (ret < 0 || (size_t)ret >= sizeof(fpath)) {\n                        MO_DBG_ERR(\"fn error: %i\", ret);\n                        diagFileList.pop_back();\n                        // next file starts from offset 0\n                        diagFilesBackTransferred = 0;\n                        continue;\n                    }\n\n                    if (auto file = filesystem->open(fpath, \"r\")) {\n\n                        if (diagFilesBackTransferred == 0) {\n                            char fileHeading [30 + MO_MAX_PATH_SIZE];\n                            auto writeLen = snprintf(fileHeading, sizeof(fileHeading), \"\\n\\n# File %s:\\n\", diagFileList.back().c_str());\n                            if (writeLen < 0 || (size_t)writeLen >= sizeof(fileHeading)) {\n                                MO_DBG_ERR(\"fn error: %i\", ret);\n                                diagFileList.pop_back();\n                                diagFilesBackTransferred = 0;\n                                continue;\n                            }\n                            if (writeLen + written > size || //heading doesn't fit anymore, return with a bit unused buffer space and print heading the next time\n                                    writeLen + written == size) { //filling the buffer up exactly would mean that no file payload is written and this head gets printed again\n                                \n                                MO_DBG_DEBUG(\"upload diag chunk (%zuB)\", written);\n                                return written;\n                            }\n\n                            memcpy(buf + written, fileHeading, (size_t)writeLen);\n                            written += (size_t)writeLen;\n                        }\n\n                        file->seek(diagFilesBackTransferred);\n                        size_t writeLen = file->read((char*)buf + written, size - written);\n                        // advance per-file offset\n                        diagFilesBackTransferred += writeLen;\n                        if (writeLen < size - written) {\n                            // EOF for this file; move to next and reset offset\n                            MO_DBG_DEBUG(\"upload diag chunk %zu (done)\", diagFilesBackTransferred);\n                            diagFileList.pop_back();\n                            diagFilesBackTransferred = 0;\n                        }\n                        written += writeLen;\n                    } else {\n                        MO_DBG_ERR(\"could not open file: %s\", fpath);\n                        diagFileList.pop_back();\n                        diagFilesBackTransferred = 0;\n                    }\n                }\n\n                MO_DBG_DEBUG(\"upload diag chunk (%zuB)\", written);\n                return written;\n            },\n            [this, onClose] (MO_FtpCloseReason reason) -> void {\n                if (reason == MO_FtpCloseReason_Success) {\n                    MO_DBG_INFO(\"FTP upload success\");\n                    this->ftpUploadStatus = UploadStatus::Uploaded;\n                } else {\n                    MO_DBG_INFO(\"FTP upload failure (%i)\", reason);\n                    this->ftpUploadStatus = UploadStatus::UploadFailed;\n                }\n\n                MO_FREE(diagPreamble);\n                MO_FREE(diagPostamble);\n                diagFileList.clear();\n                diagFilesBackTransferred = 0; //reset offset for future uploads\n\n                if (onClose) {\n                    onClose();\n                }\n            });\n\n        if (this->ftpUpload) {\n            this->ftpUploadStatus = UploadStatus::NotUploaded;\n            return true;\n        } else {\n            this->ftpUploadStatus = UploadStatus::UploadFailed;\n            return false;\n        }\n    };\n\n    this->uploadStatusInput = [this] () {\n        return this->ftpUploadStatus;\n    };\n}\n\nvoid DiagnosticsService::setFtpServerCert(const char *cert) {\n    this->ftpServerCert = cert;\n}\n\n#if !defined(MO_CUSTOM_DIAGNOSTICS)\n\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS\n\n#include \"esp_heap_caps.h\"\n#include <LittleFS.h>\n\nbool g_diagsSent = false;\n\nstd::unique_ptr<DiagnosticsService> MicroOcpp::makeDefaultDiagnosticsService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem) {\n    std::unique_ptr<DiagnosticsService> diagService = std::unique_ptr<DiagnosticsService>(new DiagnosticsService(context));\n\n    diagService->setDiagnosticsReader(\n        [] (char *buf, size_t size) -> size_t {\n            if (!g_diagsSent) {\n                g_diagsSent = true;\n                int ret = snprintf(buf, size,\n                    \"\\n# Memory\\n\"\n                    \"freeHeap=%zu\\n\"\n                    \"minHeap=%zu\\n\"\n                    \"maxAllocHeap=%zu\\n\"\n                    \"LittleFS_used=%zu\\n\"\n                    \"LittleFS_total=%zu\\n\",\n                    heap_caps_get_free_size(MALLOC_CAP_DEFAULT),\n                    heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT),\n                    heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT),\n                    LittleFS.usedBytes(),\n                    LittleFS.totalBytes()\n                );\n                if (ret < 0 || (size_t)ret >= size) {\n                    MO_DBG_ERR(\"snprintf: %i\", ret);\n                    return 0;\n                }\n                return (size_t)ret;\n            }\n            return 0;\n        }, [] () {\n            g_diagsSent = false;\n        },\n        filesystem);\n\n    return diagService;\n}\n\n#elif MO_ENABLE_MBEDTLS\n\nstd::unique_ptr<DiagnosticsService> MicroOcpp::makeDefaultDiagnosticsService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem) {\n    std::unique_ptr<DiagnosticsService> diagService = std::unique_ptr<DiagnosticsService>(new DiagnosticsService(context));\n\n    diagService->setDiagnosticsReader(nullptr, nullptr, filesystem); //report the built-in MO defaults\n\n    return diagService;\n}\n\n#endif //MO_PLATFORM\n#endif //!defined(MO_CUSTOM_DIAGNOSTICS)\n"
  },
  {
    "path": "src/MicroOcpp/Model/Diagnostics/DiagnosticsService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef DIAGNOSTICSSERVICE_H\n#define DIAGNOSTICSSERVICE_H\n\n#include <functional>\n#include <memory>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/Ftp.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Model/Diagnostics/DiagnosticsStatus.h>\n\nnamespace MicroOcpp {\n\nenum class UploadStatus {\n    NotUploaded,\n    Uploaded,\n    UploadFailed\n};\n\nclass Context;\nclass Request;\nclass FilesystemAdapter;\n\nclass DiagnosticsService : public MemoryManaged {\nprivate:\n    Context& context;\n    \n    String location;\n    unsigned int retries = 0;\n    unsigned int retryInterval = 0;\n    Timestamp startTime;\n    Timestamp stopTime;\n\n    Timestamp nextTry;\n\n    std::function<std::string()> refreshFilename;\n    std::function<bool(const char *location, Timestamp &startTime, Timestamp &stopTime)> onUpload;\n    std::function<UploadStatus()> uploadStatusInput;\n    bool uploadIssued = false;\n    bool uploadFailure = false;\n\n    std::unique_ptr<FtpUpload> ftpUpload;\n    UploadStatus ftpUploadStatus = UploadStatus::NotUploaded;\n    const char *ftpServerCert = nullptr;\n    char *diagPreamble = nullptr;\n    size_t diagPreambleLen = 0;\n    size_t diagPreambleTransferred = 0;\n    bool diagReaderHasData = false;\n    char *diagPostamble = nullptr;\n    size_t diagPostambleLen = 0;\n    size_t diagPostambleTransferred = 0;\n    Vector<String> diagFileList;\n    size_t diagFilesBackTransferred = 0;\n\n    std::unique_ptr<Request> getDiagnosticsStatusNotification();\n\n    Ocpp16::DiagnosticsStatus lastReportedStatus = Ocpp16::DiagnosticsStatus::Idle;\n\npublic:\n    DiagnosticsService(Context& context);\n    ~DiagnosticsService();\n\n    void loop();\n\n    //timestamps before year 2021 will be treated as \"undefined\"\n    //returns empty std::string if onUpload is missing or upload cannot be scheduled for another reason\n    //returns fileName of diagnostics file to be uploaded if upload has been scheduled\n    String requestDiagnosticsUpload(const char *location, unsigned int retries = 1, unsigned int retryInterval = 0, Timestamp startTime = Timestamp(), Timestamp stopTime = Timestamp());\n\n    Ocpp16::DiagnosticsStatus getDiagnosticsStatus();\n\n    void setRefreshFilename(std::function<std::string()> refreshFn); //refresh a new filename which will be used for the subsequent upload tries\n\n    /*\n     * Sets the diagnostics data reader. When the server sends a GetDiagnostics operation, then MO will open an FTP\n     * connection to the FTP server and upload a diagnostics file. MO automatically creates a small report about\n     * the OCPP-related status data + it uploads the contents of the OCPP directory. In addition to the automatic\n     * report, MO also sends all data provided by the custom diagnosticsReader. Use the diagnosticsReader to add\n     * all data which could be helpful for troubleshooting, i.e.\n     *     - internal status variables, or state machine states\n     *     - error trip counters\n     *     - current sensor readings and all GPIO values\n     *     - Heap statistics, flash memory statistics\n     *     - and more. The more the better\n     *\n     * MO calls the diagnosticsReader output buffer `buf` and the bufsize `size`. Write at most `size` bytes and\n     * return the number of bytes actually written (without terminating zero-byte). It's not necessary to append\n     * a terminating zero, MO will ignore any data after the string. To end the reading process, return 0.\n     *\n     * Note that this function only works if MO_ENABLE_MBEDTLS=1, or MO has been configured with a custom FTP client\n     */\n    void setDiagnosticsReader(std::function<size_t(char *buf, size_t size)> diagnosticsReader, std::function<void()> onClose, std::shared_ptr<FilesystemAdapter> filesystem);\n\n    void setFtpServerCert(const char *cert); //zero-copy mode, i.e. cert must outlive MO\n\n    void setOnUpload(std::function<bool(const char *location, Timestamp &startTime, Timestamp &stopTime)> onUpload);\n\n    void setOnUploadStatusInput(std::function<UploadStatus()> uploadStatusInput);\n};\n\n} //end namespace MicroOcpp\n\n#if !defined(MO_CUSTOM_DIAGNOSTICS)\n\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS\n\nnamespace MicroOcpp {\nstd::unique_ptr<DiagnosticsService> makeDefaultDiagnosticsService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem);\n}\n\n#elif MO_ENABLE_MBEDTLS\n\nnamespace MicroOcpp {\nstd::unique_ptr<DiagnosticsService> makeDefaultDiagnosticsService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem);\n}\n\n#endif //MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS\n#endif //!defined(MO_CUSTOM_DIAGNOSTICS)\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Diagnostics/DiagnosticsStatus.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_DIAGNOSTICS_STATUS\n#define MO_DIAGNOSTICS_STATUS\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nenum class DiagnosticsStatus {\n    Idle,\n    Uploaded,\n    UploadFailed,\n    Uploading\n};\n\n}\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/FirmwareManagement/FirmwareService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/OperationRegistry.h>\n#include <MicroOcpp/Core/Request.h>\n\n#include <MicroOcpp/Operations/UpdateFirmware.h>\n#include <MicroOcpp/Operations/FirmwareStatusNotification.h>\n\n#include <MicroOcpp/Platform.h>\n#include <MicroOcpp/Debug.h>\n\n//debug option: update immediately and don't wait for the retreive date\n#ifndef MO_IGNORE_FW_RETR_DATE\n#define MO_IGNORE_FW_RETR_DATE 0\n#endif\n\nusing MicroOcpp::FirmwareService;\nusing MicroOcpp::Ocpp16::FirmwareStatus;\nusing MicroOcpp::Request;\n\nFirmwareService::FirmwareService(Context& context) : MemoryManaged(\"v16.Firmware.FirmwareService\"), context(context), buildNumber(makeString(getMemoryTag())), location(makeString(getMemoryTag())) {\n    \n    context.getOperationRegistry().registerOperation(\"UpdateFirmware\", [this] () {\n        return new Ocpp16::UpdateFirmware(*this);});\n\n    //Register message handler for TriggerMessage operation\n    context.getOperationRegistry().registerOperation(\"FirmwareStatusNotification\", [this] () {\n        return new Ocpp16::FirmwareStatusNotification(getFirmwareStatus());});\n}\n\nvoid FirmwareService::setBuildNumber(const char *buildNumber) {\n    if (buildNumber == nullptr)\n        return;\n    this->buildNumber = buildNumber;\n    previousBuildNumberString = declareConfiguration<const char*>(\"BUILD_NUMBER\", this->buildNumber.c_str(), MO_KEYVALUE_FN, false, false, false);\n    checkedSuccessfulFwUpdate = false; //--> CS will be notified\n}\n\nvoid FirmwareService::loop() {\n\n    if (ftpDownload && ftpDownload->isActive()) {\n        ftpDownload->loop();\n    }\n\n    if (ftpDownload) {\n        if (ftpDownload->isActive()) {\n            ftpDownload->loop();\n        } else {\n            MO_DBG_DEBUG(\"Deinit FTP download\");\n            ftpDownload.reset();\n        }\n    }\n\n    auto notification = getFirmwareStatusNotification();\n    if (notification) {\n        context.initiateRequest(std::move(notification));\n    }\n\n    if (mocpp_tick_ms() - timestampTransition < delayTransition) {\n        return;\n    }\n\n    auto& timestampNow = context.getModel().getClock().now();\n    if (retries > 0 && timestampNow >= retreiveDate) {\n\n        if (stage == UpdateStage::Idle) {\n            MO_DBG_INFO(\"Start update\");\n\n            if (context.getModel().getNumConnectors() > 0) {\n                auto cp = context.getModel().getConnector(0);\n                cp->setAvailabilityVolatile(false);\n            }\n            if (onDownload == nullptr) {\n                stage = UpdateStage::AfterDownload;\n            } else {\n                downloadIssued = true;\n                stage = UpdateStage::AwaitDownload;\n                timestampTransition = mocpp_tick_ms();\n                delayTransition = 2000; //delay between state \"Downloading\" and actually starting the download\n                return;\n            }\n        }\n\n        if (stage == UpdateStage::AwaitDownload) {\n            MO_DBG_INFO(\"Start download\");\n            stage = UpdateStage::Downloading;\n            if (onDownload != nullptr) {\n                onDownload(location.c_str());\n                timestampTransition = mocpp_tick_ms();\n                delayTransition = downloadStatusInput ? 1000 : 30000; //give the download at least 30s\n                return;\n            }\n        }\n\n        if (stage == UpdateStage::Downloading) {\n\n            if (downloadStatusInput) {\n                //check if client reports download to be finished\n\n                if (downloadStatusInput() == DownloadStatus::Downloaded) {\n                    //passed download stage\n                    stage = UpdateStage::AfterDownload;\n                } else if (downloadStatusInput() == DownloadStatus::DownloadFailed) {\n                    MO_DBG_INFO(\"Download timeout or failed\");\n                    retreiveDate = timestampNow;\n                    retreiveDate += retryInterval;\n                    retries--;\n                    resetStage();\n\n                    timestampTransition = mocpp_tick_ms();\n                    delayTransition = 10000;\n                }\n                return;\n            } else {\n                //if client doesn't report download state, assume download to be finished (at least 30s download time have passed until here)\n                stage = UpdateStage::AfterDownload;\n            }\n        }\n\n        if (stage == UpdateStage::AfterDownload) {\n            bool ongoingTx = false;\n            for (unsigned int cId = 0; cId < context.getModel().getNumConnectors(); cId++) {\n                auto connector = context.getModel().getConnector(cId);\n                if (connector && connector->getTransaction() && connector->getTransaction()->isRunning()) {\n                    ongoingTx = true;\n                    break;\n                }\n            }\n\n            if (!ongoingTx) {\n                if (onInstall == nullptr) {\n                    stage = UpdateStage::Installing;\n                } else {\n                    stage = UpdateStage::AwaitInstallation;\n                }\n                timestampTransition = mocpp_tick_ms();\n                delayTransition = 2000;\n                installationIssued = true;\n            }\n\n            return;\n        }\n\n        if (stage == UpdateStage::AwaitInstallation) {\n            MO_DBG_INFO(\"Installing\");\n            stage = UpdateStage::Installing;\n\n            if (onInstall) {\n                onInstall(location.c_str()); //may restart the device on success\n\n                timestampTransition = mocpp_tick_ms();\n                delayTransition = installationStatusInput ? 1000 : 120 * 1000;\n            }\n            return;\n        }\n\n        if (stage == UpdateStage::Installing) {\n\n            if (installationStatusInput) {\n                if (installationStatusInput() == InstallationStatus::Installed) {\n                    MO_DBG_INFO(\"FW update finished\");\n                    //Charger may reboot during onInstall. If it doesn't, server will send Reset request\n                    resetStage();\n                    retries = 0; //End of update routine\n                    stage = UpdateStage::Installed;\n                    location.clear();\n                } else if (installationStatusInput() == InstallationStatus::InstallationFailed) {\n                    MO_DBG_INFO(\"Installation timeout or failed! Retry\");\n                    retreiveDate = timestampNow;\n                    retreiveDate += retryInterval;\n                    retries--;\n                    resetStage();\n\n                    timestampTransition = mocpp_tick_ms();\n                    delayTransition = 10000;\n                }\n                return;\n            } else {\n                MO_DBG_INFO(\"FW update finished\");\n                //Charger may reboot during onInstall. If it doesn't, server will send Reset request\n                resetStage();\n                stage = UpdateStage::Installed;\n                retries = 0; //End of update routine\n                location.clear();\n                return;\n            }\n        }\n\n        //should never reach this code\n        MO_DBG_ERR(\"Firmware update failed\");\n        retries = 0;\n        resetStage();\n        stage = UpdateStage::InternalError;\n        location.clear();\n    }\n}\n\nvoid FirmwareService::scheduleFirmwareUpdate(const char *location, Timestamp retreiveDate, unsigned int retries, unsigned int retryInterval) {\n\n    if (!onDownload && !onInstall) {\n        MO_DBG_ERR(\"FW service not configured\");\n        stage = UpdateStage::InternalError; //will send \"InstallationFailed\" and not proceed with update\n        return;\n    }\n\n    this->location = location;\n    this->retreiveDate = retreiveDate;\n    this->retries = retries;\n    this->retryInterval = retryInterval;\n\n    if (MO_IGNORE_FW_RETR_DATE) {\n        MO_DBG_DEBUG(\"ignore FW update retreive date\");\n        this->retreiveDate = context.getModel().getClock().now();\n    }\n\n    char dbuf [JSONDATE_LENGTH + 1] = {'\\0'};\n    this->retreiveDate.toJsonString(dbuf, JSONDATE_LENGTH + 1);\n\n    MO_DBG_INFO(\"Scheduled FW update!\\n\" \\\n                    \"                  location = %s\\n\" \\\n                    \"                  retrieveDate = %s\\n\" \\\n                    \"                  retries = %u\" \\\n                    \", retryInterval = %u\",\n            this->location.c_str(),\n            dbuf,\n            this->retries,\n            this->retryInterval);\n\n    timestampTransition = mocpp_tick_ms();\n    delayTransition = 1000;\n\n    resetStage();\n}\n\nFirmwareStatus FirmwareService::getFirmwareStatus() {\n\n    if (stage == UpdateStage::Installed) {\n        return FirmwareStatus::Installed;\n    } else if (stage == UpdateStage::InternalError) {\n        return FirmwareStatus::InstallationFailed; \n    }\n\n    if (installationIssued) {\n        if (installationStatusInput != nullptr) {\n            if (installationStatusInput() == InstallationStatus::Installed) {\n                return FirmwareStatus::Installed;\n            } else if (installationStatusInput() == InstallationStatus::InstallationFailed) {\n                return FirmwareStatus::InstallationFailed;\n            }\n        }\n        if (onInstall != nullptr)\n            return FirmwareStatus::Installing;\n    }\n    \n    if (downloadIssued) {\n        if (downloadStatusInput != nullptr) {\n            if (downloadStatusInput() == DownloadStatus::Downloaded) {\n                return FirmwareStatus::Downloaded;\n            } else if (downloadStatusInput() == DownloadStatus::DownloadFailed) {\n                return FirmwareStatus::DownloadFailed;\n            }\n        }\n        if (onDownload != nullptr)\n            return FirmwareStatus::Downloading;\n    }\n\n    return FirmwareStatus::Idle;\n}\n\nstd::unique_ptr<Request> FirmwareService::getFirmwareStatusNotification() {\n    /*\n     * Check if FW has been updated previously, but only once\n     */\n    if (!checkedSuccessfulFwUpdate && !buildNumber.empty() && previousBuildNumberString != nullptr) {\n        checkedSuccessfulFwUpdate = true;\n\n        MO_DBG_DEBUG(\"Previous build number: %s, new build number: %s\", previousBuildNumberString->getString(), buildNumber.c_str());\n        \n        if (buildNumber.compare(previousBuildNumberString->getString())) {\n            //new FW\n            previousBuildNumberString->setString(buildNumber.c_str());\n            configuration_save();\n\n            buildNumber.clear();\n\n            lastReportedStatus = FirmwareStatus::Installed;\n            auto fwNotificationMsg = new Ocpp16::FirmwareStatusNotification(lastReportedStatus);\n            auto fwNotification = makeRequest(fwNotificationMsg);\n            return fwNotification;\n        }\n    }\n\n    if (getFirmwareStatus() != lastReportedStatus) {\n        lastReportedStatus = getFirmwareStatus();\n        if (lastReportedStatus != FirmwareStatus::Idle) {\n            auto fwNotificationMsg = new Ocpp16::FirmwareStatusNotification(lastReportedStatus);\n            auto fwNotification = makeRequest(fwNotificationMsg);\n            return fwNotification;\n        }\n    }\n\n    return nullptr;\n}\n\nvoid FirmwareService::setOnDownload(std::function<bool(const char *location)> onDownload) {\n    this->onDownload = onDownload;\n}\n\nvoid FirmwareService::setDownloadStatusInput(std::function<DownloadStatus()> downloadStatusInput) {\n    this->downloadStatusInput = downloadStatusInput;\n}\n\nvoid FirmwareService::setOnInstall(std::function<bool(const char *location)> onInstall) {\n    this->onInstall = onInstall;\n}\n\nvoid FirmwareService::setInstallationStatusInput(std::function<InstallationStatus()> installationStatusInput) {\n    this->installationStatusInput = installationStatusInput;\n}\n\nvoid FirmwareService::resetStage() {\n    stage = UpdateStage::Idle;\n    downloadIssued = false;\n    installationIssued = false;\n}\n\nvoid FirmwareService::setDownloadFileWriter(std::function<size_t(const unsigned char *buf, size_t size)> firmwareWriter, std::function<void(MO_FtpCloseReason)> onClose) {\n\n    this->onDownload = [this, firmwareWriter, onClose] (const char *location) -> bool {\n\n        auto ftpClient = context.getFtpClient();\n        if (!ftpClient) {\n            MO_DBG_ERR(\"FTP client not set\");\n            this->ftpDownloadStatus = DownloadStatus::DownloadFailed;\n            return false;\n        }\n\n        this->ftpDownload = ftpClient->getFile(location, firmwareWriter,\n            [this, onClose] (MO_FtpCloseReason reason) -> void {\n                if (reason == MO_FtpCloseReason_Success) {\n                    MO_DBG_INFO(\"FTP download success\");\n                    this->ftpDownloadStatus = DownloadStatus::Downloaded;\n                } else {\n                    MO_DBG_INFO(\"FTP download failure (%i)\", reason);\n                    this->ftpDownloadStatus = DownloadStatus::DownloadFailed;\n                }\n\n                onClose(reason);\n            });\n\n        if (this->ftpDownload) {\n            this->ftpDownloadStatus = DownloadStatus::NotDownloaded;\n            return true;\n        } else {\n            this->ftpDownloadStatus = DownloadStatus::DownloadFailed;\n            return false;\n        }\n    };\n\n    this->downloadStatusInput = [this] () {\n        return this->ftpDownloadStatus;\n    };\n}\n\nvoid FirmwareService::setFtpServerCert(const char *cert) {\n    this->ftpServerCert = cert;\n}\n\n#if !defined(MO_CUSTOM_UPDATER)\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS\n\n#include <Update.h>\n\nstd::unique_ptr<FirmwareService> MicroOcpp::makeDefaultFirmwareService(Context& context) {\n    std::unique_ptr<FirmwareService> fwService = std::unique_ptr<FirmwareService>(new FirmwareService(context));\n    auto ftServicePtr = fwService.get();\n\n    fwService->setDownloadFileWriter(\n        [ftServicePtr] (const unsigned char *data, size_t size) -> size_t {\n            if (!Update.isRunning()) {\n                MO_DBG_DEBUG(\"start writing FW\");\n                MO_DBG_WARN(\"Built-in updater for ESP32 is only intended for demonstration purposes\");\n                ftServicePtr->setInstallationStatusInput([](){return InstallationStatus::NotInstalled;});\n\n                auto ret = Update.begin();\n                if (!ret) {\n                    MO_DBG_ERR(\"cannot start update: %i\", ret);\n                    return 0;\n                }\n            }\n\n            size_t written = Update.write((uint8_t*) data, size);\n\n            #if MO_DBG_LEVEL >= MO_DL_INFO\n            {\n                size_t progress = Update.progress();\n\n                bool printProgress = false;\n\n                if (progress <= 10000) {\n                    size_t p1k = progress / 1000;\n                    printProgress = progress < p1k * 1000 + written && progress >= p1k * 1000;\n                } else if (progress <= 100000) {\n                    size_t p10k = progress / 10000;\n                    printProgress = progress < p10k * 10000 + written && progress >= p10k * 10000;\n                } else {\n                    size_t p100k = progress / 100000;\n                    printProgress = progress < p100k * 100000 + written && progress >= p100k * 100000;\n                }\n\n                if (printProgress) {\n                    MO_DBG_INFO(\"update progress: %zu kB\", progress / 1000);\n                }\n            }\n            #endif //MO_DBG_LEVEL >= MO_DL_DEBUG\n\n            return written;\n        }, [] (MO_FtpCloseReason reason) {\n            if (reason != MO_FtpCloseReason_Success) {\n                Update.abort();\n            }\n        });\n\n    fwService->setOnInstall([ftServicePtr] (const char *location) {\n\n        if (Update.isRunning() && Update.end(true)) {\n            MO_DBG_DEBUG(\"update success\");\n            ftServicePtr->setInstallationStatusInput([](){return InstallationStatus::Installed;});\n\n            ESP.restart();\n        } else {\n            MO_DBG_ERR(\"update failed\");\n            ftServicePtr->setInstallationStatusInput([](){return InstallationStatus::InstallationFailed;});\n        }\n\n        return true;\n    });\n\n    fwService->setInstallationStatusInput([] () {\n        return InstallationStatus::NotInstalled;\n    });\n\n    return fwService;\n}\n\n#elif MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP8266)\n\n#include <ESP8266httpUpdate.h>\n\nstd::unique_ptr<FirmwareService> MicroOcpp::makeDefaultFirmwareService(Context& context) {\n    std::unique_ptr<FirmwareService> fwService = std::unique_ptr<FirmwareService>(new FirmwareService(context));\n    auto fwServicePtr = fwService.get();\n\n    fwService->setOnInstall([fwServicePtr] (const char *location) {\n        \n        MO_DBG_WARN(\"Built-in updater for ESP8266 is only intended for demonstration purposes. HTTP support only\");\n\n        WiFiClient client;\n        //WiFiClientSecure client;\n        //client.setCACert(rootCACertificate);\n        client.setTimeout(60); //in seconds\n\n        //ESPhttpUpdate.setLedPin(downloadStatusLedPin);\n\n        HTTPUpdateResult ret = ESPhttpUpdate.update(client, location);\n\n        switch (ret) {\n            case HTTP_UPDATE_FAILED:\n                fwServicePtr->setInstallationStatusInput([](){return InstallationStatus::InstallationFailed;});\n                MO_DBG_WARN(\"HTTP_UPDATE_FAILED Error (%d): %s\\n\", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());\n                break;\n            case HTTP_UPDATE_NO_UPDATES:\n                fwServicePtr->setInstallationStatusInput([](){return InstallationStatus::InstallationFailed;});\n                MO_DBG_WARN(\"HTTP_UPDATE_NO_UPDATES\");\n                break;\n            case HTTP_UPDATE_OK:\n                fwServicePtr->setInstallationStatusInput([](){return InstallationStatus::Installed;});\n                MO_DBG_INFO(\"HTTP_UPDATE_OK\");\n                ESP.restart();\n                break;\n        }\n\n        return true;\n    });\n\n    fwService->setInstallationStatusInput([] () {\n        return InstallationStatus::NotInstalled;\n    });\n\n    return fwService;\n}\n\n#endif //MO_PLATFORM\n#endif //!defined(MO_CUSTOM_UPDATER)\n"
  },
  {
    "path": "src/MicroOcpp/Model/FirmwareManagement/FirmwareService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef FIRMWARESERVICE_H\n#define FIRMWARESERVICE_H\n\n#include <functional>\n#include <memory>\n\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/Ftp.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nenum class DownloadStatus {\n    NotDownloaded, // == before download or during download\n    Downloaded,\n    DownloadFailed\n};\n\nenum class InstallationStatus {\n    NotInstalled, // == before installation or during installation\n    Installed,\n    InstallationFailed\n};\n\nclass Context;\nclass Request;\n\nclass FirmwareService : public MemoryManaged {\nprivate:\n    Context& context;\n    \n    std::shared_ptr<Configuration> previousBuildNumberString;\n    String buildNumber;\n\n    std::function<DownloadStatus()> downloadStatusInput;\n    bool downloadIssued = false;\n\n    std::unique_ptr<FtpDownload> ftpDownload;\n    DownloadStatus ftpDownloadStatus = DownloadStatus::NotDownloaded;\n    const char *ftpServerCert = nullptr;\n\n    std::function<InstallationStatus()> installationStatusInput;\n    bool installationIssued = false;\n\n    Ocpp16::FirmwareStatus lastReportedStatus = Ocpp16::FirmwareStatus::Idle;\n    bool checkedSuccessfulFwUpdate = false;\n\n    String location;\n    Timestamp retreiveDate;\n    unsigned int retries = 0;\n    unsigned int retryInterval = 0;\n\n    std::function<bool(const char *location)> onDownload;\n    std::function<bool(const char *location)> onInstall;\n\n    unsigned long delayTransition = 0;\n    unsigned long timestampTransition = 0;\n\n    enum class UpdateStage {\n        Idle,\n        AwaitDownload,\n        Downloading,\n        AfterDownload,\n        AwaitInstallation,\n        Installing,\n        Installed,\n        InternalError\n    } stage = UpdateStage::Idle;\n\n    void resetStage();\n\n    std::unique_ptr<Request> getFirmwareStatusNotification();\n\npublic:\n    FirmwareService(Context& context);\n\n    void setBuildNumber(const char *buildNumber);\n\n    void loop();\n\n    void scheduleFirmwareUpdate(const char *location, Timestamp retreiveDate, unsigned int retries = 1, unsigned int retryInterval = 0);\n\n    Ocpp16::FirmwareStatus getFirmwareStatus();\n\n    /*\n     * Sets the firmware writer. During the UpdateFirmware process, MO will use an FTP client to download the firmware and forward\n     * the binary data to `firmwareWriter`. The binary data comes in chunks. MO executes `firmwareWriter` with `buf` containing the\n     * next chunk of FW data and `size` being the chunk size. `firmwareWriter` must return the number of bytes written, whereas\n     * the result can be between 1 and `size`, and 0 aborts the download. MO executes `onClose` with the reason why the connection\n     * has been closed. If the download hasn't been successful, MO will abort the update routine in any case.\n     * \n     * Note that this function only works if MO_ENABLE_MBEDTLS=1, or MO has been configured with a custom FTP client\n     */\n    void setDownloadFileWriter(std::function<size_t(const unsigned char *buf, size_t size)> firmwareWriter, std::function<void(MO_FtpCloseReason)> onClose);\n\n    void setFtpServerCert(const char *cert); //zero-copy mode, i.e. cert must outlive MO\n\n    /*\n     * Manual alternative for FTP download handler `setDownloadFileWriter`\n     */\n    void setOnDownload(std::function<bool(const char *location)> onDownload);\n\n    void setDownloadStatusInput(std::function<DownloadStatus()> downloadStatusInput);\n\n    void setOnInstall(std::function<bool(const char *location)> onInstall);\n\n    void setInstallationStatusInput(std::function<InstallationStatus()> installationStatusInput);\n};\n\n} //endif namespace MicroOcpp\n\n#if !defined(MO_CUSTOM_UPDATER)\n\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS\n\nnamespace MicroOcpp {\nstd::unique_ptr<FirmwareService> makeDefaultFirmwareService(Context& context);\n}\n\n#elif MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP8266)\n\nnamespace MicroOcpp {\nstd::unique_ptr<FirmwareService> makeDefaultFirmwareService(Context& context);\n}\n\n#endif //MO_PLATFORM\n#endif //!defined(MO_CUSTOM_UPDATER)\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_FIRMWARE_STATUS\n#define MO_FIRMWARE_STATUS\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nenum class FirmwareStatus {\n    Downloaded,\n    DownloadFailed,\n    Downloading,\n    Idle,\n    InstallationFailed,\n    Installing,\n    Installed\n};\n\n}\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Heartbeat/HeartbeatService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Heartbeat/HeartbeatService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Operations/Heartbeat.h>\n#include <MicroOcpp/Platform.h>\n\nusing namespace MicroOcpp;\n\nHeartbeatService::HeartbeatService(Context& context) : MemoryManaged(\"v16.Heartbeat.HeartbeatService\"), context(context) {\n    heartbeatIntervalInt = declareConfiguration<int>(\"HeartbeatInterval\", 86400);\n    registerConfigurationValidator(\"HeartbeatInterval\", VALIDATE_UNSIGNED_INT);\n    lastHeartbeat = mocpp_tick_ms();\n\n    //Register message handler for TriggerMessage operation\n    context.getOperationRegistry().registerOperation(\"Heartbeat\", [&context] () {\n        return new Ocpp16::Heartbeat(context.getModel());});\n}\n\nvoid HeartbeatService::loop() {\n    unsigned long hbInterval = heartbeatIntervalInt->getInt();\n    hbInterval *= 1000UL; //conversion s -> ms\n    unsigned long now = mocpp_tick_ms();\n\n    if (now - lastHeartbeat >= hbInterval) {\n        lastHeartbeat = now;\n\n        auto heartbeat = makeRequest(new Ocpp16::Heartbeat(context.getModel()));\n        // Heartbeats can not deviate more than 4s from the configured interval\n        heartbeat->setTimeout(std::min(4000UL, hbInterval));\n        context.initiateRequest(std::move(heartbeat));\n    }\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Heartbeat/HeartbeatService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_HEARTBEATSERVICE_H\n#define MO_HEARTBEATSERVICE_H\n\n#include <memory>\n\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Context;\n\nclass HeartbeatService : public MemoryManaged {\nprivate:\n    Context& context;\n\n    unsigned long lastHeartbeat;\n    std::shared_ptr<Configuration> heartbeatIntervalInt;\n\npublic:\n    HeartbeatService(Context& context);\n\n    void loop();\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeterStore.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Metering/MeterStore.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n\n#include <MicroOcpp/Debug.h>\n\n#include <algorithm>\n\n#ifndef MO_MAX_STOPTXDATA_LEN\n#define MO_MAX_STOPTXDATA_LEN 4\n#endif\n\nusing namespace MicroOcpp;\n\nTransactionMeterData::TransactionMeterData(unsigned int connectorId, unsigned int txNr, std::shared_ptr<FilesystemAdapter> filesystem)\n        : MemoryManaged(\"v16.Metering.TransactionMeterData\"), connectorId(connectorId), txNr(txNr), filesystem{filesystem}, txData{makeVector<std::unique_ptr<MeterValue>>(getMemoryTag())} {\n    \n    if (!filesystem) {\n        MO_DBG_DEBUG(\"volatile mode\");\n    }\n}\n\nbool TransactionMeterData::addTxData(std::unique_ptr<MeterValue> mv) {\n    if (isFinalized()) {\n        MO_DBG_ERR(\"immutable\");\n        return false;\n    }\n\n    if (!mv) {\n        MO_DBG_ERR(\"null\");\n        return false;\n    }\n\n    if (MO_MAX_STOPTXDATA_LEN <= 0) {\n        //txData off\n        return true;\n    }\n\n    bool replaceLast = mvCount >= MO_MAX_STOPTXDATA_LEN; //txData size exceeded? overwrite last entry instead of appending\n\n    if (filesystem) {\n\n        unsigned int mvIndex = 0;\n        if (replaceLast) {\n            mvIndex = mvCount - 1;\n        } else {\n            mvIndex = mvCount ;\n        }\n\n        char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n        auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"sd\" \"-%u-%u-%u.jsn\", connectorId, txNr, mvIndex);\n        if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n            MO_DBG_ERR(\"fn error: %i\", ret);\n            return false;\n        }\n\n        auto mvDoc = mv->toJson();\n        if (!mvDoc) {\n            MO_DBG_ERR(\"MV not ready yet\");\n            return false;\n        }\n\n        if (!FilesystemUtils::storeJson(filesystem, fn, *mvDoc)) {\n            MO_DBG_ERR(\"FS error\");\n            return false;\n        }\n\n        if (!replaceLast) {\n            mvCount++;\n        }\n    }\n\n    if (replaceLast) {\n        txData.back() = std::move(mv);\n        MO_DBG_DEBUG(\"updated latest sd\");\n    } else {\n        txData.push_back(std::move(mv));\n        MO_DBG_DEBUG(\"added sd\");\n    }\n    return true;\n}\n\nVector<std::unique_ptr<MeterValue>> TransactionMeterData::retrieveStopTxData() {\n    if (isFinalized()) {\n        MO_DBG_ERR(\"Can only retrieve once\");\n        return makeVector<std::unique_ptr<MeterValue>>(getMemoryTag());\n    }\n    finalize();\n    MO_DBG_DEBUG(\"creating sd\");\n    return std::move(txData);\n}\n\nbool TransactionMeterData::restore(MeterValueBuilder& mvBuilder) {\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"No FS - nothing to restore\");\n        return true;\n    }\n\n    const unsigned int MISSES_LIMIT = 3;\n    unsigned int i = 0;\n    unsigned int misses = 0;\n\n    while (misses < MISSES_LIMIT) { //search until region without mvs found\n        \n        char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n        auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"sd\" \"-%u-%u-%u.jsn\", connectorId, txNr, i);\n        if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n            MO_DBG_ERR(\"fn error: %i\", ret);\n            return false; //all files have same length\n        }\n\n        auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag());\n\n        if (!doc) {\n            misses++;\n            i++;\n            continue;\n        }\n\n        JsonObject mvJson = doc->as<JsonObject>();\n        std::unique_ptr<MeterValue> mv = mvBuilder.deserializeSample(mvJson);\n\n        if (!mv) {\n            MO_DBG_ERR(\"Deserialization error\");\n            misses++;\n            i++;\n            continue;\n        }\n\n        if (txData.size() >= MO_MAX_STOPTXDATA_LEN) {\n            MO_DBG_ERR(\"corrupted memory\");\n            return false;\n        }\n\n        txData.push_back(std::move(mv));\n\n        i++;\n        mvCount = i;\n        misses = 0;\n    }\n\n    MO_DBG_DEBUG(\"Restored %zu meter values from sd-%u-%u-0 to %u (exclusive)\", txData.size(), connectorId, txNr, mvCount);\n    return true;\n}\n\nMeterStore::MeterStore(std::shared_ptr<FilesystemAdapter> filesystem) : MemoryManaged(\"v16.Metering.MeterStore\"), filesystem {filesystem}, txMeterData{makeVector<std::weak_ptr<TransactionMeterData>>(getMemoryTag())} {\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"volatile mode\");\n    }\n}\n\nstd::shared_ptr<TransactionMeterData> MeterStore::getTxMeterData(MeterValueBuilder& mvBuilder, Transaction *transaction) {\n    if (!transaction || transaction->isSilent()) {\n        //no tx assignment -> don't store txData\n        //tx is silent -> no StopTx will be sent and don't store txData\n        return nullptr;\n    }\n    auto connectorId = transaction->getConnectorId();\n    auto txNr = transaction->getTxNr();\n    \n    auto cached = std::find_if(txMeterData.begin(), txMeterData.end(),\n            [connectorId, txNr] (std::weak_ptr<TransactionMeterData>& txm) {\n                if (auto txml = txm.lock()) {\n                    return txml->getConnectorId() == connectorId && txml->getTxNr() == txNr;\n                } else {\n                    return false;\n                }\n            });\n    \n    if (cached != txMeterData.end()) {\n        if (auto cachedl = cached->lock()) {\n            return cachedl;\n        }\n    }\n\n    //clean outdated pointers before creating new object\n\n    txMeterData.erase(std::remove_if(txMeterData.begin(), txMeterData.end(),\n            [] (std::weak_ptr<TransactionMeterData>& txm) {\n                return txm.expired();\n            }),\n            txMeterData.end());\n\n    //create new object and cache weak pointer\n\n    auto tx = std::allocate_shared<TransactionMeterData>(makeAllocator<TransactionMeterData>(getMemoryTag()), connectorId, txNr, filesystem);\n    \n    if (filesystem) {\n        char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n        auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"sd\" \"-%u-%u-%u.jsn\", connectorId, txNr, 0);\n        if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n            MO_DBG_ERR(\"fn error: %i\", ret);\n            return nullptr; //cannot store\n        }\n\n        size_t size = 0;\n        bool exists = filesystem->stat(fn, &size) == 0;\n\n        if (exists) {\n            if (!tx->restore(mvBuilder)) {\n                remove(connectorId, txNr);\n                MO_DBG_ERR(\"removed corrupted tx entries\");\n            }\n        }\n    }\n\n    txMeterData.push_back(tx);\n\n    MO_DBG_DEBUG(\"Added txNr %u, now holding %zu sds\", txNr, txMeterData.size());\n\n    return tx;\n}\n\nbool MeterStore::remove(unsigned int connectorId, unsigned int txNr) {\n\n    unsigned int mvCount = 0;\n\n    auto cached = std::find_if(txMeterData.begin(), txMeterData.end(),\n            [connectorId, txNr] (std::weak_ptr<TransactionMeterData>& txm) {\n                if (auto txml = txm.lock()) {\n                    return txml->getConnectorId() == connectorId && txml->getTxNr() == txNr;\n                } else {\n                    return false;\n                }\n            });\n    \n    if (cached != txMeterData.end()) {\n        if (auto cachedl = cached->lock()) {\n            mvCount = cachedl->getPathsCount();\n            cachedl->finalize();\n        }\n    }\n\n    bool success = true;\n\n    if (filesystem) {\n        if (mvCount == 0) {\n            const unsigned int MISSES_LIMIT = 3;\n            unsigned int misses = 0;\n            unsigned int i = 0;\n\n            while (misses < MISSES_LIMIT) { //search until region without mvs found\n                \n                char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n                auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"sd\" \"-%u-%u-%u.jsn\", connectorId, txNr, i);\n                if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n                    MO_DBG_ERR(\"fn error: %i\", ret);\n                    return false; //all files have same length\n                }\n\n                size_t nsize = 0;\n                if (filesystem->stat(fn, &nsize) != 0) {\n                    misses++;\n                    i++;\n                    continue;\n                }\n\n                i++;\n                mvCount = i;\n                misses = 0;\n            }\n        }\n\n        MO_DBG_DEBUG(\"remove %u mvs for txNr %u\", mvCount, txNr);\n\n        for (unsigned int i = 0; i < mvCount; i++) {\n            unsigned int sd = mvCount - 1U - i;\n        \n            char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n            auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"sd\" \"-%u-%u-%u.jsn\", connectorId, txNr, sd);\n            if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n                MO_DBG_ERR(\"fn error: %i\", ret);\n                return false;\n            }\n\n            success &= filesystem->remove(fn);\n        }\n    }\n\n    //clean outdated pointers\n\n    txMeterData.erase(std::remove_if(txMeterData.begin(), txMeterData.end(),\n            [] (std::weak_ptr<TransactionMeterData>& txm) {\n                return txm.expired();\n            }),\n            txMeterData.end());\n\n    if (success) {\n        MO_DBG_DEBUG(\"Removed meter values for cId %u, txNr %u\", connectorId, txNr);\n    } else {\n        MO_DBG_DEBUG(\"corrupted fs\");\n    }\n\n    return success;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeterStore.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_METERSTORE_H\n#define MO_METERSTORE_H\n\n#include <MicroOcpp/Model/Metering/MeterValue.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass TransactionMeterData : public MemoryManaged {\nprivate:\n    const unsigned int connectorId; //assignment to Transaction object\n    const unsigned int txNr; //assignment to Transaction object\n\n    unsigned int mvCount = 0; //nr of saved meter values, including gaps\n    bool finalized = false; //if true, this is read-only\n\n    std::shared_ptr<FilesystemAdapter> filesystem;\n\n    Vector<std::unique_ptr<MeterValue>> txData;\n\npublic:\n    TransactionMeterData(unsigned int connectorId, unsigned int txNr, std::shared_ptr<FilesystemAdapter> filesystem);\n\n    bool addTxData(std::unique_ptr<MeterValue> mv);\n\n    Vector<std::unique_ptr<MeterValue>> retrieveStopTxData(); //will invalidate internal cache\n\n    bool restore(MeterValueBuilder& mvBuilder); //load record from memory; true if record found, false if nothing loaded\n\n    unsigned int getConnectorId() {return connectorId;}\n    unsigned int getTxNr() {return txNr;}\n    unsigned int getPathsCount() {return mvCount;} //size of spanned path indexes\n    void finalize() {finalized = true;}\n    bool isFinalized() {return finalized;}\n};\n\nclass MeterStore : public MemoryManaged {\nprivate:\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    \n    Vector<std::weak_ptr<TransactionMeterData>> txMeterData;\n\npublic:\n    MeterStore() = delete;\n    MeterStore(MeterStore&) = delete;\n    MeterStore(std::shared_ptr<FilesystemAdapter> filesystem);\n\n    std::shared_ptr<TransactionMeterData> getTxMeterData(MeterValueBuilder& mvBuilder, Transaction *transaction);\n\n    bool remove(unsigned int connectorId, unsigned int txNr);\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeterValue.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <limits>\n\n#include <MicroOcpp/Model/Metering/MeterValue.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nMeterValue::MeterValue(const Timestamp& timestamp) :\n        MemoryManaged(\"v16.Metering.MeterValue\"), \n        timestamp(timestamp), \n        sampledValue(makeVector<std::unique_ptr<SampledValue>>(getMemoryTag())) {\n\n}\n\nvoid MeterValue::addSampledValue(std::unique_ptr<SampledValue> sample) {\n    sampledValue.push_back(std::move(sample));\n}\n\nstd::unique_ptr<JsonDoc> MeterValue::toJson() {\n    size_t capacity = 0;\n    auto entries = makeVector<std::unique_ptr<JsonDoc>>(getMemoryTag());\n    for (auto sample = sampledValue.begin(); sample != sampledValue.end(); sample++) {\n        auto json = (*sample)->toJson();\n        if (!json) {\n            return nullptr;\n        }\n        capacity += json->capacity();\n        entries.push_back(std::move(json));\n    }\n\n    capacity += JSON_ARRAY_SIZE(entries.size());\n    capacity += JSONDATE_LENGTH + 1;\n    capacity += JSON_OBJECT_SIZE(2);\n    \n    auto result = makeJsonDoc(getMemoryTag(), capacity);\n    auto jsonPayload = result->to<JsonObject>();\n\n    char timestampStr [JSONDATE_LENGTH + 1] = {'\\0'};\n    if (timestamp.toJsonString(timestampStr, JSONDATE_LENGTH + 1)) {\n        jsonPayload[\"timestamp\"] = timestampStr;\n    }\n    auto jsonMeterValue = jsonPayload.createNestedArray(\"sampledValue\");\n    for (auto entry = entries.begin(); entry != entries.end(); entry++) {\n        jsonMeterValue.add(**entry);\n    }\n    return result;\n}\n\nconst Timestamp& MeterValue::getTimestamp() {\n    return timestamp;\n}\n\nvoid MeterValue::setTimestamp(Timestamp timestamp) {\n    this->timestamp = timestamp;\n}\n\nReadingContext MeterValue::getReadingContext() {\n    //all sampledValues have the same ReadingContext. Just get the first result\n    for (auto sample = sampledValue.begin(); sample != sampledValue.end(); sample++) {\n        if ((*sample)->getReadingContext() != ReadingContext_UNDEFINED) {\n            return (*sample)->getReadingContext();\n        }\n    }\n    return ReadingContext_UNDEFINED;\n}\n\nvoid MeterValue::setTxNr(unsigned int txNr) {\n    if (txNr > (unsigned int)std::numeric_limits<int>::max()) {\n        MO_DBG_ERR(\"invalid arg\");\n        return;\n    }\n    this->txNr = (int)txNr;\n}\n\nint MeterValue::getTxNr() {\n    return txNr;\n}\n\nvoid MeterValue::setOpNr(unsigned int opNr) {\n    this->opNr = opNr;\n}\n\nunsigned int MeterValue::getOpNr() {\n    return opNr;\n}\n\nvoid MeterValue::advanceAttemptNr() {\n    attemptNr++;\n}\n\nunsigned int MeterValue::getAttemptNr() {\n    return attemptNr;\n}\n\nunsigned long MeterValue::getAttemptTime() {\n    return attemptTime;\n}\n\nvoid MeterValue::setAttemptTime(unsigned long timestamp) {\n    this->attemptTime = timestamp;\n}\n\nMeterValueBuilder::MeterValueBuilder(const Vector<std::unique_ptr<SampledValueSampler>> &samplers,\n            std::shared_ptr<Configuration> samplersSelectStr) :\n            MemoryManaged(\"v16.Metering.MeterValueBuilder\"),\n            samplers(samplers),\n            selectString(samplersSelectStr),\n            select_mask(makeVector<bool>(getMemoryTag())) {\n\n    updateObservedSamplers();\n    select_observe = selectString->getValueRevision();\n}\n\nvoid MeterValueBuilder::updateObservedSamplers() {\n\n    if (select_mask.size() != samplers.size()) {\n        select_mask.resize(samplers.size(), false);\n        select_n = 0;\n    }\n\n    for (size_t i = 0; i < select_mask.size(); i++) {\n        select_mask[i] = false;\n    }\n    \n    auto sstring = selectString->getString();\n    auto ssize = strlen(sstring) + 1;\n    size_t sl = 0, sr = 0;\n    while (sstring && sl < ssize) {\n        while (sr < ssize) {\n            if (sstring[sr] == ',') {\n                break;\n            }\n            sr++;\n        }\n\n        if (sr != sl + 1) {\n            for (size_t i = 0; i < samplers.size(); i++) {\n                if (!strncmp(samplers[i]->getProperties().getMeasurand(), sstring + sl, sr - sl)) {\n                    select_mask[i] = true;\n                    select_n++;\n                }\n            }\n        }\n\n        sr++;\n        sl = sr;\n    }\n}\n\nstd::unique_ptr<MeterValue> MeterValueBuilder::takeSample(const Timestamp& timestamp, const ReadingContext& context) {\n    if (select_observe != selectString->getValueRevision() || //OCPP server has changed configuration about which measurands to take\n            samplers.size() != select_mask.size()) {    //Client has added another Measurand; synchronize lists\n        MO_DBG_DEBUG(\"Updating observed samplers due to config change or samplers added\");\n        updateObservedSamplers();\n        select_observe = selectString->getValueRevision();\n    }\n\n    if (select_n == 0) {\n        return nullptr;\n    }\n\n    auto sample = std::unique_ptr<MeterValue>(new MeterValue(timestamp));\n\n    for (size_t i = 0; i < select_mask.size(); i++) {\n        if (select_mask[i]) {\n            sample->addSampledValue(samplers[i]->takeValue(context));\n        }\n    }\n\n    return sample;\n}\n\nstd::unique_ptr<MeterValue> MeterValueBuilder::deserializeSample(const JsonObject mvJson) {\n\n    Timestamp timestamp;\n    bool ret = timestamp.setTime(mvJson[\"timestamp\"] | \"Invalid\");\n    if (!ret) {\n        MO_DBG_ERR(\"invalid timestamp\");\n        return nullptr;\n    }\n\n    auto sample = std::unique_ptr<MeterValue>(new MeterValue(timestamp));\n\n    JsonArray sampledValue = mvJson[\"sampledValue\"];\n    for (JsonObject svJson : sampledValue) {  //for each sampled value, search sampler with matching measurand type\n        for (auto& sampler : samplers) {\n            auto& properties = sampler->getProperties();\n            if (!strcmp(properties.getMeasurand(), svJson[\"measurand\"] | \"\") &&\n                    !strcmp(properties.getFormat(), svJson[\"format\"] | \"\") &&\n                    !strcmp(properties.getPhase(), svJson[\"phase\"] | \"\") &&\n                    !strcmp(properties.getLocation(), svJson[\"location\"] | \"\") &&\n                    !strcmp(properties.getUnit(), svJson[\"unit\"] | \"\")) {\n                //found correct sampler\n                auto dVal = sampler->deserializeValue(svJson);\n                if (dVal) {\n                    sample->addSampledValue(std::move(dVal));\n                } else {\n                    MO_DBG_ERR(\"deserialization error\");\n                }\n                break;\n            }\n        }\n    }\n\n    MO_DBG_VERBOSE(\"deserialized MV\");\n    return sample;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeterValue.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_METERVALUE_H\n#define MO_METERVALUE_H\n\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Model/Metering/SampledValue.h>\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <ArduinoJson.h>\n#include <memory>\n\nnamespace MicroOcpp {\n\nclass MeterValue : public MemoryManaged {\nprivate:\n    Timestamp timestamp;\n    Vector<std::unique_ptr<SampledValue>> sampledValue;\n\n    int txNr = -1;\n    unsigned int opNr = 1;\n    unsigned int attemptNr = 0;\n    unsigned long attemptTime = 0;\npublic:\n    MeterValue(const Timestamp& timestamp);\n    MeterValue(const MeterValue& other) = delete;\n\n    void addSampledValue(std::unique_ptr<SampledValue> sample);\n\n    std::unique_ptr<JsonDoc> toJson();\n\n    const Timestamp& getTimestamp();\n    void setTimestamp(Timestamp timestamp);\n\n    ReadingContext getReadingContext();\n\n    void setTxNr(unsigned int txNr);\n    int getTxNr();\n\n    void setOpNr(unsigned int opNr);\n    unsigned int getOpNr();\n\n    void advanceAttemptNr();\n    unsigned int getAttemptNr();\n\n    unsigned long getAttemptTime();\n    void setAttemptTime(unsigned long timestamp);\n};\n\nclass MeterValueBuilder : public MemoryManaged {\nprivate:\n    const Vector<std::unique_ptr<SampledValueSampler>> &samplers;\n    std::shared_ptr<Configuration> selectString;\n    Vector<bool> select_mask;\n    unsigned int select_n {0};\n    decltype(selectString->getValueRevision()) select_observe;\n\n    void updateObservedSamplers();\npublic:\n    MeterValueBuilder(const Vector<std::unique_ptr<SampledValueSampler>> &samplers,\n            std::shared_ptr<Configuration> samplersSelectStr);\n    \n    std::unique_ptr<MeterValue> takeSample(const Timestamp& timestamp, const ReadingContext& context);\n\n    std::unique_ptr<MeterValue> deserializeSample(const JsonObject mvJson);\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeterValuesV201.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Metering/MeterValuesV201.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Variables/Variable.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Debug.h>\n\n//helper function\nnamespace MicroOcpp {\n\nbool csvContains(const char *csv, const char *elem) {\n\n    if (!csv || !elem) {\n        return false;\n    }\n\n    size_t elemLen = strlen(elem);\n\n    size_t sl = 0, sr = 0;\n    while (csv[sr]) {\n        while (csv[sr]) {\n            if (csv[sr] == ',') {\n                break;\n            }\n            sr++;\n        }\n        //csv[sr] is either ',' or '\\0'\n\n        if (sr - sl == elemLen && !strncmp(csv + sl, elem, sr - sl)) {\n            return true;\n        }\n\n        if (csv[sr]) {\n            sr++;\n        }\n        sl = sr;\n    }\n    return false;\n}\n\n} //namespace MicroOcpp\n\nusing namespace MicroOcpp::Ocpp201;\n\nSampledValueProperties::SampledValueProperties() : MemoryManaged(\"v201.MeterValues.SampledValueProperties\") { }\nSampledValueProperties::SampledValueProperties(const SampledValueProperties& other) :\n        MemoryManaged(other.getMemoryTag()), \n        format(other.format), \n        measurand(other.measurand), \n        phase(other.phase), \n        location(other.location), \n        unitOfMeasureUnit(other.unitOfMeasureUnit),\n        unitOfMeasureMultiplier(other.unitOfMeasureMultiplier) {\n\n}\n\nvoid SampledValueProperties::setFormat(const char *format) {this->format = format;}\nconst char *SampledValueProperties::getFormat() const {return format;}\nvoid SampledValueProperties::setMeasurand(const char *measurand) {this->measurand = measurand;}\nconst char *SampledValueProperties::getMeasurand() const {return measurand;}\nvoid SampledValueProperties::setPhase(const char *phase) {this->phase = phase;}\nconst char *SampledValueProperties::getPhase() const {return phase;}\nvoid SampledValueProperties::setLocation(const char *location) {this->location = location;}\nconst char *SampledValueProperties::getLocation() const {return location;}\nvoid SampledValueProperties::setUnitOfMeasureUnit(const char *unitOfMeasureUnit) {this->unitOfMeasureUnit = unitOfMeasureUnit;}\nconst char *SampledValueProperties::getUnitOfMeasureUnit() const {return unitOfMeasureUnit;}\nvoid SampledValueProperties::setUnitOfMeasureMultiplier(int unitOfMeasureMultiplier) {this->unitOfMeasureMultiplier = unitOfMeasureMultiplier;}\nint SampledValueProperties::getUnitOfMeasureMultiplier() const {return unitOfMeasureMultiplier;}\n\nSampledValue::SampledValue(double value, ReadingContext readingContext, SampledValueProperties& properties)\n        : MemoryManaged(\"v201.MeterValues.SampledValue\"), value(value), readingContext(readingContext), properties(properties) {\n\n}\n\nbool SampledValue::toJson(JsonDoc& out) {\n\n    size_t unitOfMeasureElements = \n            (properties.getUnitOfMeasureUnit() ? 1 : 0) +\n            (properties.getUnitOfMeasureMultiplier() ? 1 : 0);\n\n    out = initJsonDoc(getMemoryTag(),\n        JSON_OBJECT_SIZE(\n            1 + //value\n            (readingContext != ReadingContext_SamplePeriodic ? 1 : 0) +\n            (properties.getMeasurand() ? 1 : 0) +\n            (properties.getPhase() ? 1 : 0) +\n            (properties.getLocation() ? 1 : 0) +\n            (unitOfMeasureElements ? 1 : 0)\n        ) +\n        (unitOfMeasureElements ? JSON_OBJECT_SIZE(unitOfMeasureElements) : 0)\n    );\n\n    out[\"value\"] = value;\n    if (readingContext != ReadingContext_SamplePeriodic)\n        out[\"context\"] = serializeReadingContext(readingContext);\n    if (properties.getMeasurand())\n        out[\"measurand\"] = properties.getMeasurand();\n    if (properties.getPhase())\n        out[\"phase\"] = properties.getPhase();\n    if (properties.getLocation())\n        out[\"location\"] = properties.getLocation();\n    if (properties.getUnitOfMeasureUnit())\n        out[\"unitOfMeasure\"][\"unit\"] = properties.getUnitOfMeasureUnit();\n    if (properties.getUnitOfMeasureMultiplier())\n        out[\"unitOfMeasure\"][\"multiplier\"] = properties.getUnitOfMeasureMultiplier();\n\n    return true;\n}\n\nSampledValueInput::SampledValueInput(std::function<double(ReadingContext)> valueInput, const SampledValueProperties& properties)\n        : MemoryManaged(\"v201.MeterValues.SampledValueInput\"), valueInput(valueInput), properties(properties) {\n\n}\n\nSampledValue *SampledValueInput::takeSampledValue(ReadingContext readingContext) {\n    return new SampledValue(valueInput(readingContext), readingContext, properties);\n}\n\nconst SampledValueProperties& SampledValueInput::getProperties() {\n    return properties;\n}\n\nuint8_t& SampledValueInput::getMeasurandTypeFlags() {\n    return measurandTypeFlags;\n}\n\nMeterValue::MeterValue(const Timestamp& timestamp, SampledValue **sampledValue, size_t sampledValueSize) :\n        MemoryManaged(\"v201.MeterValues.MeterValue\"), timestamp(timestamp), sampledValue(sampledValue), sampledValueSize(sampledValueSize) {\n    \n}\n\nMeterValue::~MeterValue() {\n    for (size_t i = 0; i < sampledValueSize; i++) {\n        delete sampledValue[i];\n    }\n    MO_FREE(sampledValue);\n}\n\nbool MeterValue::toJson(JsonDoc& out) {\n\n    size_t capacity = 0;\n\n    for (size_t i = 0; i < sampledValueSize; i++) {\n        //just measure, discard sampledValueJson afterwards\n        JsonDoc sampledValueJson = initJsonDoc(getMemoryTag());\n        sampledValue[i]->toJson(sampledValueJson);\n        capacity += sampledValueJson.capacity();\n    }\n\n    capacity += JSON_OBJECT_SIZE(2) +\n                JSONDATE_LENGTH + 1 +\n                JSON_ARRAY_SIZE(sampledValueSize);\n                \n\n    out = initJsonDoc(\"v201.MeterValues.MeterValue\", capacity);\n\n    char timestampStr [JSONDATE_LENGTH + 1];\n    timestamp.toJsonString(timestampStr, sizeof(timestampStr));\n    \n    out[\"timestamp\"] = timestampStr;\n    JsonArray sampledValueArray = out.createNestedArray(\"sampledValue\");\n    \n    for (size_t i = 0; i < sampledValueSize; i++) {\n        JsonDoc sampledValueJson = initJsonDoc(getMemoryTag());\n        sampledValue[i]->toJson(sampledValueJson);\n        sampledValueArray.add(sampledValueJson);\n    }\n\n    return true;\n}\n\nconst MicroOcpp::Timestamp& MeterValue::getTimestamp() {\n    return timestamp;\n}\n\nMeteringServiceEvse::MeteringServiceEvse(Model& model, unsigned int evseId)\n        : MemoryManaged(\"v201.MeterValues.MeteringServiceEvse\"), model(model), evseId(evseId), sampledValueInputs(makeVector<SampledValueInput>(getMemoryTag())) {\n\n    auto varService = model.getVariableService();\n\n    sampledDataTxStartedMeasurands = varService->declareVariable<const char*>(\"SampledDataCtrlr\", \"TxStartedMeasurands\", \"\");\n    sampledDataTxUpdatedMeasurands = varService->declareVariable<const char*>(\"SampledDataCtrlr\", \"TxUpdatedMeasurands\", \"\");\n    sampledDataTxEndedMeasurands = varService->declareVariable<const char*>(\"SampledDataCtrlr\", \"TxEndedMeasurands\", \"\");\n    alignedDataMeasurands = varService->declareVariable<const char*>(\"AlignedDataCtrlr\", \"AlignedDataMeasurands\", \"\");\n}\n\nvoid MeteringServiceEvse::addMeterValueInput(std::function<double(ReadingContext)> valueInput, const SampledValueProperties& properties) {\n    sampledValueInputs.emplace_back(valueInput, properties);\n}\n\nstd::unique_ptr<MeterValue> MeteringServiceEvse::takeMeterValue(Variable *measurands, uint16_t& trackMeasurandsWriteCount, size_t& trackInputsSize, uint8_t measurandsMask, ReadingContext readingContext) {\n\n    if (measurands->getWriteCount() != trackMeasurandsWriteCount ||\n            sampledValueInputs.size() != trackInputsSize) {\n        MO_DBG_DEBUG(\"Updating observed samplers due to config change or samplers added\");\n        for (size_t i = 0; i < sampledValueInputs.size(); i++) {\n            if (csvContains(measurands->getString(), sampledValueInputs[i].getProperties().getMeasurand())) {\n                sampledValueInputs[i].getMeasurandTypeFlags() |= measurandsMask;\n            } else {\n                sampledValueInputs[i].getMeasurandTypeFlags() &= ~measurandsMask;\n            }\n        }\n\n        trackMeasurandsWriteCount = measurands->getWriteCount();\n        trackInputsSize = sampledValueInputs.size();\n    }\n\n    size_t samplesSize = 0;\n\n    for (size_t i = 0; i < sampledValueInputs.size(); i++) {\n        if (sampledValueInputs[i].getMeasurandTypeFlags() & measurandsMask) {\n            samplesSize++;\n        }\n    }\n\n    if (samplesSize == 0) {\n        return nullptr;\n    }\n\n    SampledValue **sampledValue = static_cast<SampledValue**>(MO_MALLOC(getMemoryTag(), samplesSize * sizeof(SampledValue*)));\n    if (!sampledValue) {\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n    size_t samplesWritten = 0;\n\n    bool memoryErr = false;\n\n    for (size_t i = 0; i < sampledValueInputs.size(); i++) {\n        if (sampledValueInputs[i].getMeasurandTypeFlags() & measurandsMask) {\n            auto sample = sampledValueInputs[i].takeSampledValue(readingContext);\n            if (!sample) {\n                MO_DBG_ERR(\"OOM\");\n                memoryErr = true;\n                break;\n            }\n            sampledValue[samplesWritten++] = sample;\n        }\n    }\n\n    std::unique_ptr<MeterValue> meterValue = std::unique_ptr<MeterValue>(new MeterValue(model.getClock().now(), sampledValue, samplesWritten));\n    if (!meterValue) {\n        MO_DBG_ERR(\"OOM\");\n        memoryErr = true;\n    }\n\n    if (memoryErr) {\n        if (!meterValue) {\n            //meterValue did not take ownership, so clean resources manually\n            for (size_t i = 0; i < samplesWritten; i++) {\n                delete sampledValue[i];\n            }\n            delete sampledValue;\n        }\n        return nullptr;\n    }\n\n    return meterValue;\n}\n\nstd::unique_ptr<MeterValue> MeteringServiceEvse::takeTxStartedMeterValue(ReadingContext readingContext) {\n    return takeMeterValue(sampledDataTxStartedMeasurands, trackSampledDataTxStartedMeasurandsWriteCount, trackSampledValueInputsSizeTxStarted, MO_MEASURAND_TYPE_TXSTARTED, readingContext);\n}\nstd::unique_ptr<MeterValue> MeteringServiceEvse::takeTxUpdatedMeterValue(ReadingContext readingContext) {\n    return takeMeterValue(sampledDataTxUpdatedMeasurands, trackSampledDataTxUpdatedMeasurandsWriteCount, trackSampledValueInputsSizeTxUpdated, MO_MEASURAND_TYPE_TXUPDATED, readingContext);\n}\nstd::unique_ptr<MeterValue> MeteringServiceEvse::takeTxEndedMeterValue(ReadingContext readingContext) {\n    return takeMeterValue(sampledDataTxEndedMeasurands, trackSampledDataTxEndedMeasurandsWriteCount, trackSampledValueInputsSizeTxEnded, MO_MEASURAND_TYPE_TXENDED, readingContext);\n}\nstd::unique_ptr<MeterValue> MeteringServiceEvse::takeTriggeredMeterValues() {\n    return takeMeterValue(alignedDataMeasurands, trackAlignedDataMeasurandsWriteCount, trackSampledValueInputsSizeAligned, MO_MEASURAND_TYPE_ALIGNED, ReadingContext_Trigger);\n}\n\nbool MeteringServiceEvse::existsMeasurand(const char *measurand, size_t len) {\n    for (size_t i = 0; i < sampledValueInputs.size(); i++) {\n        const char *sviMeasurand = sampledValueInputs[i].getProperties().getMeasurand();\n        if (sviMeasurand && len == strlen(sviMeasurand) && !strncmp(sviMeasurand, measurand, len)) {\n            return true;\n        }\n    }\n    return false;\n}\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nbool validateSelectString(const char *csl, void *userPtr) {\n    auto mService = static_cast<MeteringService*>(userPtr);\n\n    bool isValid = true;\n    const char *l = csl; //the beginning of an entry of the comma-separated list\n    const char *r = l; //one place after the last character of the entry beginning with l\n    while (*l) {\n        if (*l == ',') {\n            l++;\n            continue;\n        }\n        r = l + 1;\n        while (*r != '\\0' && *r != ',') {\n            r++;\n        }\n        bool found = false;\n        for (size_t evseId = 0; evseId < MO_NUM_EVSEID && mService->getEvse(evseId); evseId++) {\n            if (mService->getEvse(evseId)->existsMeasurand(l, (size_t) (r - l))) {\n                found = true;\n                break;\n            }\n        }\n        if (!found) {\n            isValid = false;\n            MO_DBG_WARN(\"could not find metering device for %.*s\", (int) (r - l), l);\n            break;\n        }\n        l = r;\n    }\n    return isValid;\n}\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\nusing namespace MicroOcpp::Ocpp201;\n\nMeteringService::MeteringService(Model& model, size_t numEvses) {\n\n    auto varService = model.getVariableService();\n\n    //define factory defaults\n    varService->declareVariable<const char*>(\"SampledDataCtrlr\", \"TxStartedMeasurands\", \"\");\n    varService->declareVariable<const char*>(\"SampledDataCtrlr\", \"TxUpdatedMeasurands\", \"\");\n    varService->declareVariable<const char*>(\"SampledDataCtrlr\", \"TxEndedMeasurands\", \"\");\n    varService->declareVariable<const char*>(\"AlignedDataCtrlr\", \"AlignedDataMeasurands\", \"\");\n\n    varService->registerValidator<const char*>(\"SampledDataCtrlr\", \"TxStartedMeasurands\", validateSelectString, this);\n    varService->registerValidator<const char*>(\"SampledDataCtrlr\", \"TxUpdatedMeasurands\", validateSelectString, this);\n    varService->registerValidator<const char*>(\"SampledDataCtrlr\", \"TxEndedMeasurands\", validateSelectString, this);\n    varService->registerValidator<const char*>(\"AlignedDataCtrlr\", \"AlignedDataMeasurands\", validateSelectString, this);\n\n    for (size_t evseId = 0; evseId < std::min(numEvses, (size_t)MO_NUM_EVSEID); evseId++) {\n        evses[evseId] = new MeteringServiceEvse(model, evseId);\n    }\n}\n\nMeteringService::~MeteringService() {\n    for (size_t evseId = 0; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) {\n        delete evses[evseId];\n    }\n}\n\nMeteringServiceEvse *MeteringService::getEvse(unsigned int evseId) {\n    return evses[evseId];\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeterValuesV201.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Implementation of the UCs E01 - E12\n */\n\n#ifndef MO_METERVALUESV201_H\n#define MO_METERVALUESV201_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <functional>\n\n#include <MicroOcpp/Model/Metering/ReadingContext.h>\n#include <MicroOcpp/Model/ConnectorBase/EvseId.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Model;\nclass Variable;\n\nnamespace Ocpp201 {\n\nclass SampledValueProperties : public MemoryManaged {\nprivate:\n    const char *format = nullptr;\n    const char *measurand = nullptr;\n    const char *phase = nullptr;\n    const char *location = nullptr;\n    const char *unitOfMeasureUnit = nullptr;\n    int unitOfMeasureMultiplier = 0;\n\npublic:\n    SampledValueProperties();\n    SampledValueProperties(const SampledValueProperties&);\n\n    void setFormat(const char *format); //zero-copy\n    const char *getFormat() const;\n    void setMeasurand(const char *measurand); //zero-copy\n    const char *getMeasurand() const;\n    void setPhase(const char *phase); //zero-copy\n    const char *getPhase() const;\n    void setLocation(const char *location); //zero-copy\n    const char *getLocation() const;\n    void setUnitOfMeasureUnit(const char *unitOfMeasureUnit); //zero-copy\n    const char *getUnitOfMeasureUnit() const;\n    void setUnitOfMeasureMultiplier(int unitOfMeasureMultiplier);\n    int getUnitOfMeasureMultiplier() const;\n};\n\nclass SampledValue : public MemoryManaged {\nprivate:\n    double value = 0.;\n    ReadingContext readingContext;\n    SampledValueProperties& properties;\n    //std::unique_ptr<SignedMeterValue> ... this could be an abstract type\npublic:\n    SampledValue(double value, ReadingContext readingContext, SampledValueProperties& properties);\n\n    bool toJson(JsonDoc& out);\n};\n\n#define MO_MEASURAND_TYPE_TXSTARTED (1 << 0)\n#define MO_MEASURAND_TYPE_TXUPDATED (1 << 1)\n#define MO_MEASURAND_TYPE_TXENDED   (1 << 2)\n#define MO_MEASURAND_TYPE_ALIGNED   (1 << 3)\n\nclass SampledValueInput : public MemoryManaged {\nprivate:\n    std::function<double(ReadingContext)> valueInput;\n    SampledValueProperties properties;\n\n    uint8_t measurandTypeFlags = 0;\npublic:\n    SampledValueInput(std::function<double(ReadingContext)> valueInput, const SampledValueProperties& properties);\n    SampledValue *takeSampledValue(ReadingContext readingContext);\n\n    const SampledValueProperties& getProperties();\n\n    uint8_t& getMeasurandTypeFlags();\n};\n\nclass MeterValue : public MemoryManaged {\nprivate:\n    Timestamp timestamp;\n    SampledValue **sampledValue = nullptr;\n    size_t sampledValueSize = 0;\npublic:\n    MeterValue(const Timestamp& timestamp, SampledValue **sampledValue, size_t sampledValueSize);\n    ~MeterValue();\n\n    bool toJson(JsonDoc& out);\n\n    const Timestamp& getTimestamp();\n};\n\nclass MeteringServiceEvse : public MemoryManaged {\nprivate:\n    Model& model;\n    const unsigned int evseId;\n \n    Vector<SampledValueInput> sampledValueInputs;\n\n    Variable *sampledDataTxStartedMeasurands = nullptr;\n    Variable *sampledDataTxUpdatedMeasurands = nullptr;\n    Variable *sampledDataTxEndedMeasurands = nullptr;\n    Variable *alignedDataMeasurands = nullptr;\n\n    size_t trackSampledValueInputsSizeTxStarted = 0;\n    size_t trackSampledValueInputsSizeTxUpdated = 0;\n    size_t trackSampledValueInputsSizeTxEnded = 0;\n    size_t trackSampledValueInputsSizeAligned = 0;\n    uint16_t trackSampledDataTxStartedMeasurandsWriteCount = -1;\n    uint16_t trackSampledDataTxUpdatedMeasurandsWriteCount = -1;\n    uint16_t trackSampledDataTxEndedMeasurandsWriteCount = -1;\n    uint16_t trackAlignedDataMeasurandsWriteCount = -1;\n\n    std::unique_ptr<MeterValue> takeMeterValue(Variable *measurands, uint16_t& trackMeasurandsWriteCount, size_t& trackInputsSize, uint8_t measurandsMask, ReadingContext context);\npublic:\n    MeteringServiceEvse(Model& model, unsigned int evseId);\n\n    void addMeterValueInput(std::function<double(ReadingContext)> valueInput, const SampledValueProperties& properties);\n\n    std::unique_ptr<MeterValue> takeTxStartedMeterValue(ReadingContext context = ReadingContext_TransactionBegin);\n    std::unique_ptr<MeterValue> takeTxUpdatedMeterValue(ReadingContext context = ReadingContext_SamplePeriodic);\n    std::unique_ptr<MeterValue> takeTxEndedMeterValue(ReadingContext context);\n    std::unique_ptr<MeterValue> takeTriggeredMeterValues();\n\n    bool existsMeasurand(const char *measurand, size_t len);\n};\n\nclass MeteringService : public MemoryManaged {\nprivate:\n    MeteringServiceEvse* evses [MO_NUM_EVSEID] = {nullptr};\npublic:\n    MeteringService(Model& model, size_t numEvses);\n    ~MeteringService();\n\n    MeteringServiceEvse *getEvse(unsigned int evseId);\n};\n\n}\n}\n\n#endif\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeteringConnector.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Metering/MeteringConnector.h>\n#include <MicroOcpp/Model/Metering/MeterStore.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Operations/MeterValues.h>\n#include <MicroOcpp/Platform.h>\n#include <MicroOcpp/Debug.h>\n\n#include <cstddef>\n#include <cinttypes>\n\nusing namespace MicroOcpp;\nusing namespace MicroOcpp::Ocpp16;\n\nMeteringConnector::MeteringConnector(Context& context, int connectorId, MeterStore& meterStore)\n        : MemoryManaged(\"v16.Metering.MeteringConnector\"), context(context), model(context.getModel()), connectorId{connectorId}, meterStore(meterStore), meterData(makeVector<std::unique_ptr<MeterValue>>(getMemoryTag())), samplers(makeVector<std::unique_ptr<SampledValueSampler>>(getMemoryTag())) {\n\n    context.getRequestQueue().addSendQueue(this);\n\n    auto meterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\", \"\");\n    declareConfiguration<int>(\"MeterValuesSampledDataMaxLength\", 8, CONFIGURATION_VOLATILE, true);\n    meterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\", 60);\n    registerConfigurationValidator(\"MeterValueSampleInterval\", VALIDATE_UNSIGNED_INT);\n\n    auto stopTxnSampledDataString = declareConfiguration<const char*>(\"StopTxnSampledData\", \"\");\n    declareConfiguration<int>(\"StopTxnSampledDataMaxLength\", 8, CONFIGURATION_VOLATILE, true);\n\n    auto meterValuesAlignedDataString = declareConfiguration<const char*>(\"MeterValuesAlignedData\", \"\");\n    declareConfiguration<int>(\"MeterValuesAlignedDataMaxLength\", 8, CONFIGURATION_VOLATILE, true);\n    clockAlignedDataIntervalInt  = declareConfiguration<int>(\"ClockAlignedDataInterval\", 0);\n    registerConfigurationValidator(\"ClockAlignedDataInterval\", VALIDATE_UNSIGNED_INT);\n\n    auto stopTxnAlignedDataString = declareConfiguration<const char*>(\"StopTxnAlignedData\", \"\");\n\n    meterValuesInTxOnlyBool = declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"MeterValuesInTxOnly\", true);\n    stopTxnDataCapturePeriodicBool = declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"StopTxnDataCapturePeriodic\", false);\n\n    transactionMessageAttemptsInt = declareConfiguration<int>(\"TransactionMessageAttempts\", 3);\n    transactionMessageRetryIntervalInt = declareConfiguration<int>(\"TransactionMessageRetryInterval\", 60);\n\n    sampledDataBuilder = std::unique_ptr<MeterValueBuilder>(new MeterValueBuilder(samplers, meterValuesSampledDataString));\n    alignedDataBuilder = std::unique_ptr<MeterValueBuilder>(new MeterValueBuilder(samplers, meterValuesAlignedDataString));\n    stopTxnSampledDataBuilder = std::unique_ptr<MeterValueBuilder>(new MeterValueBuilder(samplers, stopTxnSampledDataString));\n    stopTxnAlignedDataBuilder = std::unique_ptr<MeterValueBuilder>(new MeterValueBuilder(samplers, stopTxnAlignedDataString));\n}\n\nvoid MeteringConnector::loop() {\n\n    bool txBreak = false;\n    if (model.getConnector(connectorId)) {\n        auto &curTx = model.getConnector(connectorId)->getTransaction();\n        txBreak = (curTx && curTx->isRunning()) != trackTxRunning;\n        trackTxRunning = (curTx && curTx->isRunning());\n    }\n\n    if (txBreak) {\n        lastSampleTime = mocpp_tick_ms();\n    }\n\n    if (model.getConnector(connectorId)) {\n        if (transaction != model.getConnector(connectorId)->getTransaction()) {\n            transaction = model.getConnector(connectorId)->getTransaction();\n        }\n\n        if (transaction && transaction->isRunning() && !transaction->isSilent()) {\n            //check during transaction\n\n            if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) {\n                MO_DBG_WARN(\"reload stopTxnData, %s, for tx-%u-%u\", stopTxnData ? \"replace\" : \"first time\", connectorId, transaction->getTxNr());\n                //reload (e.g. after power cut during transaction)\n                stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction.get());\n            }\n        } else {\n            //check outside of transaction\n\n            if (connectorId != 0 && meterValuesInTxOnlyBool->getBool()) {\n                //don't take any MeterValues outside of transactions on connectorIds other than 0\n                return;\n            }\n        }\n    }\n\n    if (clockAlignedDataIntervalInt->getInt() >= 1 && model.getClock().now() >= MIN_TIME) {\n\n        auto& timestampNow = model.getClock().now();\n        auto dt = nextAlignedTime - timestampNow;\n        if (dt < 0 ||                              //normal case: interval elapsed\n                dt > clockAlignedDataIntervalInt->getInt()) {   //special case: clock has been adjusted or first run\n\n            MO_DBG_DEBUG(\"Clock aligned measurement %ds: %s\", dt,\n                abs(dt) <= 60 ?\n                \"in time (tolerance <= 60s)\" : \"off, e.g. because of first run. Ignore\");\n            if (abs(dt) <= 60) { //is measurement still \"clock-aligned\"?\n\n                if (auto alignedMeterValue = alignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock)) {\n                    if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) {\n                        MO_DBG_INFO(\"MeterValue cache full. Drop old MV\");\n                        meterData.erase(meterData.begin());\n                    }\n                    alignedMeterValue->setOpNr(context.getRequestQueue().getNextOpNr());\n                    if (transaction) {\n                        alignedMeterValue->setTxNr(transaction->getTxNr());\n                    }\n                    meterData.push_back(std::move(alignedMeterValue));\n                }\n\n                if (stopTxnData) {\n                    auto alignedStopTx = stopTxnAlignedDataBuilder->takeSample(model.getClock().now(), ReadingContext_SampleClock);\n                    if (alignedStopTx) {\n                        stopTxnData->addTxData(std::move(alignedStopTx));\n                    }\n                }\n            }\n\n            Timestamp midnightBase = Timestamp(2010,0,0,0,0,0);\n            auto intervall = timestampNow - midnightBase;\n            intervall %= 3600 * 24;\n            Timestamp midnight = timestampNow - intervall;\n            intervall += clockAlignedDataIntervalInt->getInt();\n            if (intervall >= 3600 * 24) {\n                //next measurement is tomorrow; set to precisely 00:00 \n                nextAlignedTime = midnight;\n                nextAlignedTime += 3600 * 24;\n            } else {\n                intervall /= clockAlignedDataIntervalInt->getInt();\n                nextAlignedTime = midnight + (intervall * clockAlignedDataIntervalInt->getInt());\n            }\n        }\n    }\n\n    if (meterValueSampleIntervalInt->getInt() >= 1) {\n        //record periodic tx data\n\n        if (mocpp_tick_ms() - lastSampleTime >= (unsigned long) (meterValueSampleIntervalInt->getInt() * 1000)) {\n            if (auto sampledMeterValue = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic)) {\n                if (meterData.size() >= MO_METERVALUES_CACHE_MAXSIZE) {\n                    MO_DBG_INFO(\"MeterValue cache full. Drop old MV\");\n                    meterData.erase(meterData.begin());\n                }\n                sampledMeterValue->setOpNr(context.getRequestQueue().getNextOpNr());\n                if (transaction) {\n                    sampledMeterValue->setTxNr(transaction->getTxNr());\n                }\n                meterData.push_back(std::move(sampledMeterValue));\n            }\n\n            if (stopTxnData && stopTxnDataCapturePeriodicBool->getBool()) {\n                auto sampleStopTx = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_SamplePeriodic);\n                if (sampleStopTx) {\n                    stopTxnData->addTxData(std::move(sampleStopTx));\n                }\n            }\n            lastSampleTime = mocpp_tick_ms();\n        }\n    }\n}\n\nstd::unique_ptr<Operation> MeteringConnector::takeTriggeredMeterValues() {\n\n    auto sample = sampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_Trigger);\n\n    if (!sample) {\n        return nullptr;\n    }\n\n    std::shared_ptr<Transaction> transaction = nullptr;\n    if (model.getConnector(connectorId)) {\n        transaction = model.getConnector(connectorId)->getTransaction();\n    }\n\n    return std::unique_ptr<MeterValues>(new MeterValues(model, std::move(sample), connectorId, transaction));\n}\n\nvoid MeteringConnector::addMeterValueSampler(std::unique_ptr<SampledValueSampler> meterValueSampler) {\n    if (!strcmp(meterValueSampler->getProperties().getMeasurand(), \"Energy.Active.Import.Register\")) {\n        energySamplerIndex = samplers.size();\n    }\n    samplers.push_back(std::move(meterValueSampler));\n}\n\nstd::unique_ptr<SampledValue> MeteringConnector::readTxEnergyMeter(ReadingContext model) {\n    if (energySamplerIndex >= 0 && (size_t) energySamplerIndex < samplers.size()) {\n        return samplers[energySamplerIndex]->takeValue(model);\n    } else {\n        MO_DBG_DEBUG(\"Called readTxEnergyMeter(), but no energySampler or handling strategy set\");\n        return nullptr;\n    }\n}\n\nvoid MeteringConnector::beginTxMeterData(Transaction *transaction) {\n    if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) {\n        stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction);\n    }\n\n    if (stopTxnData) {\n        auto sampleTxBegin = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionBegin);\n        if (sampleTxBegin) {\n            stopTxnData->addTxData(std::move(sampleTxBegin));\n        }\n    }\n}\n\nstd::shared_ptr<TransactionMeterData> MeteringConnector::endTxMeterData(Transaction *transaction) {\n    if (!stopTxnData || stopTxnData->getTxNr() != transaction->getTxNr()) {\n        stopTxnData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction);\n    }\n\n    if (stopTxnData) {\n        auto sampleTxEnd = stopTxnSampledDataBuilder->takeSample(model.getClock().now(), ReadingContext_TransactionEnd);\n        if (sampleTxEnd) {\n            stopTxnData->addTxData(std::move(sampleTxEnd));\n        }\n    }\n\n    return std::move(stopTxnData);\n}\n\nvoid MeteringConnector::abortTxMeterData() {\n    stopTxnData.reset();\n}\n\nstd::shared_ptr<TransactionMeterData> MeteringConnector::getStopTxMeterData(Transaction *transaction) {\n    auto txData = meterStore.getTxMeterData(*stopTxnSampledDataBuilder, transaction);\n\n    if (!txData) {\n        MO_DBG_ERR(\"could not create TxData\");\n        return nullptr;\n    }\n\n    return txData;\n}\n\nbool MeteringConnector::existsSampler(const char *measurand, size_t len) {\n    for (size_t i = 0; i < samplers.size(); i++) {\n        if (strlen(samplers[i]->getProperties().getMeasurand()) == len &&\n                !strncmp(measurand, samplers[i]->getProperties().getMeasurand(), len)) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nunsigned int MeteringConnector::getFrontRequestOpNr() {\n    if (!meterDataFront && !meterData.empty()) {\n        MO_DBG_DEBUG(\"advance MV front\");\n        meterDataFront = std::move(meterData.front());\n        meterData.erase(meterData.begin());\n    }\n    if (meterDataFront) {\n        return meterDataFront->getOpNr();\n    }\n    return NoOperation;\n}\n\nstd::unique_ptr<Request> MeteringConnector::fetchFrontRequest() {\n\n    if (!meterDataFront) {\n        return nullptr;\n    }\n\n    if ((int)meterDataFront->getAttemptNr() >= transactionMessageAttemptsInt->getInt()) {\n        MO_DBG_WARN(\"exceeded TransactionMessageAttempts. Discard MeterValue\");\n        meterDataFront.reset();\n        return nullptr;\n    }\n\n    if (mocpp_tick_ms() - meterDataFront->getAttemptTime() < meterDataFront->getAttemptNr() * (unsigned long)(std::max(0, transactionMessageRetryIntervalInt->getInt())) * 1000UL) {\n        return nullptr;\n    }\n\n    meterDataFront->advanceAttemptNr();\n    meterDataFront->setAttemptTime(mocpp_tick_ms());\n\n    //fetch tx for meterValue\n    std::shared_ptr<Transaction> tx;\n    if (meterDataFront->getTxNr() >= 0) {\n        tx = model.getTransactionStore()->getTransaction(connectorId, meterDataFront->getTxNr());\n    }\n\n    //discard MV if it belongs to silent tx\n    if (tx && tx->isSilent()) {\n        MO_DBG_DEBUG(\"Drop MeterValue belonging to silent tx\");\n        meterDataFront.reset();\n        return nullptr;\n    }\n\n    auto meterValues = makeRequest(new MeterValues(model, meterDataFront.get(), connectorId, tx));\n    meterValues->setOnReceiveConfListener([this] (JsonObject) {\n        //operation success\n        MO_DBG_DEBUG(\"drop MV front\");\n        meterDataFront.reset();\n    });\n\n    return meterValues;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeteringConnector.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_METERING_CONNECTOR_H\n#define MO_METERING_CONNECTOR_H\n\n#include <functional>\n#include <memory>\n\n#include <MicroOcpp/Model/Metering/MeterValue.h>\n#include <MicroOcpp/Model/Metering/MeterStore.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Core/RequestQueue.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#ifndef MO_METERVALUES_CACHE_MAXSIZE\n#define MO_METERVALUES_CACHE_MAXSIZE MO_REQUEST_CACHE_MAXSIZE\n#endif\n\nnamespace MicroOcpp {\n\nclass Context;\nclass Model;\nclass Operation;\nclass MeterStore;\n\nclass MeteringConnector : public MemoryManaged, public RequestEmitter {\nprivate:\n    Context& context;\n    Model& model;\n    const int connectorId;\n    MeterStore& meterStore;\n    \n    Vector<std::unique_ptr<MeterValue>> meterData;\n    std::unique_ptr<MeterValue> meterDataFront;\n    std::shared_ptr<TransactionMeterData> stopTxnData;\n\n    std::unique_ptr<MeterValueBuilder> sampledDataBuilder;\n    std::unique_ptr<MeterValueBuilder> alignedDataBuilder;\n    std::unique_ptr<MeterValueBuilder> stopTxnSampledDataBuilder;\n    std::unique_ptr<MeterValueBuilder> stopTxnAlignedDataBuilder;\n\n    std::shared_ptr<Configuration> sampledDataSelectString;\n    std::shared_ptr<Configuration> alignedDataSelectString;\n    std::shared_ptr<Configuration> stopTxnSampledDataSelectString;\n    std::shared_ptr<Configuration> stopTxnAlignedDataSelectString;\n\n    unsigned long lastSampleTime = 0; //0 means not charging right now\n    Timestamp nextAlignedTime;\n    std::shared_ptr<Transaction> transaction;\n    bool trackTxRunning = false;\n \n    Vector<std::unique_ptr<SampledValueSampler>> samplers;\n    int energySamplerIndex {-1};\n\n    std::shared_ptr<Configuration> meterValueSampleIntervalInt;\n\n    std::shared_ptr<Configuration> clockAlignedDataIntervalInt;\n\n    std::shared_ptr<Configuration> meterValuesInTxOnlyBool;\n    std::shared_ptr<Configuration> stopTxnDataCapturePeriodicBool;\n\n    std::shared_ptr<Configuration> transactionMessageAttemptsInt;\n    std::shared_ptr<Configuration> transactionMessageRetryIntervalInt;\npublic:\n    MeteringConnector(Context& context, int connectorId, MeterStore& meterStore);\n\n    void loop();\n\n    void addMeterValueSampler(std::unique_ptr<SampledValueSampler> meterValueSampler);\n\n    std::unique_ptr<SampledValue> readTxEnergyMeter(ReadingContext model);\n\n    std::unique_ptr<Operation> takeTriggeredMeterValues();\n\n    void beginTxMeterData(Transaction *transaction);\n\n    std::shared_ptr<TransactionMeterData> endTxMeterData(Transaction *transaction);\n\n    void abortTxMeterData();\n\n    std::shared_ptr<TransactionMeterData> getStopTxMeterData(Transaction *transaction);\n\n    bool existsSampler(const char *measurand, size_t len);\n\n    //RequestEmitter implementation\n    unsigned int getFrontRequestOpNr() override;\n    std::unique_ptr<Request> fetchFrontRequest() override;\n\n};\n\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeteringService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Metering/MeteringService.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Operations/MeterValues.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nMeteringService::MeteringService(Context& context, int numConn, std::shared_ptr<FilesystemAdapter> filesystem)\n      : MemoryManaged(\"v16.Metering.MeteringService\"), context(context), meterStore(filesystem), connectors(makeVector<std::unique_ptr<MeteringConnector>>(getMemoryTag())) {\n\n    //set factory defaults for Metering-related config keys\n    declareConfiguration<const char*>(\"MeterValuesSampledData\", \"Energy.Active.Import.Register,Power.Active.Import\");\n    declareConfiguration<const char*>(\"StopTxnSampledData\", \"\");\n    declareConfiguration<const char*>(\"MeterValuesAlignedData\", \"Energy.Active.Import.Register,Power.Active.Import\");\n    declareConfiguration<const char*>(\"StopTxnAlignedData\", \"\");\n    \n    connectors.reserve(numConn);\n    for (int i = 0; i < numConn; i++) {\n        connectors.emplace_back(new MeteringConnector(context, i, meterStore));\n    }\n\n    std::function<bool(const char*)> validateSelectString = [this] (const char *csl) {\n        bool isValid = true;\n        const char *l = csl; //the beginning of an entry of the comma-separated list\n        const char *r = l; //one place after the last character of the entry beginning with l\n        while (*l) {\n            if (*l == ',') {\n                l++;\n                continue;\n            }\n            r = l + 1;\n            while (*r != '\\0' && *r != ',') {\n                r++;\n            }\n            bool found = false;\n            for (size_t cId = 0; cId < connectors.size(); cId++) {\n                if (connectors[cId]->existsSampler(l, (size_t) (r - l))) {\n                    found = true;\n                    break;\n                }\n            }\n            if (!found) {\n                isValid = false;\n                MO_DBG_WARN(\"could not find metering device for %.*s\", (int) (r - l), l);\n                break;\n            }\n            l = r;\n        }\n        return isValid;\n    };\n\n    registerConfigurationValidator(\"MeterValuesSampledData\", validateSelectString);\n    registerConfigurationValidator(\"StopTxnSampledData\", validateSelectString);\n    registerConfigurationValidator(\"MeterValuesAlignedData\", validateSelectString);\n    registerConfigurationValidator(\"StopTxnAlignedData\", validateSelectString);\n    registerConfigurationValidator(\"MeterValueSampleInterval\", VALIDATE_UNSIGNED_INT);\n    registerConfigurationValidator(\"ClockAlignedDataInterval\", VALIDATE_UNSIGNED_INT);\n\n    /*\n     * Register further message handlers to support echo mode: when this library\n     * is connected with a WebSocket echo server, let it reply to its own requests.\n     * Mocking an OCPP Server on the same device makes running (unit) tests easier.\n     */\n    context.getOperationRegistry().registerOperation(\"MeterValues\", [this] () {\n        return new Ocpp16::MeterValues(this->context.getModel());});\n}\n\nvoid MeteringService::loop(){\n    for (unsigned int i = 0; i < connectors.size(); i++){\n        connectors[i]->loop();\n    }\n}\n\nvoid MeteringService::addMeterValueSampler(int connectorId, std::unique_ptr<SampledValueSampler> meterValueSampler) {\n    if (connectorId < 0 || connectorId >= (int) connectors.size()) {\n        MO_DBG_ERR(\"connectorId is out of bounds\");\n        return;\n    }\n    connectors[connectorId]->addMeterValueSampler(std::move(meterValueSampler));\n}\n\nstd::unique_ptr<SampledValue> MeteringService::readTxEnergyMeter(int connectorId, ReadingContext context) {\n    if (connectorId < 0 || (size_t) connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"connectorId is out of bounds\");\n        return nullptr;\n    }\n    return connectors[connectorId]->readTxEnergyMeter(context);\n}\n\nstd::unique_ptr<Request> MeteringService::takeTriggeredMeterValues(int connectorId) {\n    if (connectorId < 0 || connectorId >= (int) connectors.size()) {\n        MO_DBG_ERR(\"connectorId out of bounds. Ignore\");\n        return nullptr;\n    }\n\n    auto msg = connectors[connectorId]->takeTriggeredMeterValues();\n    if (msg) {\n        auto meterValues = makeRequest(std::move(msg));\n        meterValues->setTimeout(120000);\n        return meterValues;\n    }\n    MO_DBG_DEBUG(\"Did not take any samples for connectorId %d\", connectorId);\n    return nullptr;\n}\n\nvoid MeteringService::beginTxMeterData(Transaction *transaction) {\n    if (!transaction) {\n        MO_DBG_ERR(\"invalid argument\");\n        return;\n    }\n    auto connectorId = transaction->getConnectorId();\n    if (connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"connectorId is out of bounds\");\n        return;\n    }\n    connectors[connectorId]->beginTxMeterData(transaction);\n}\n\nstd::shared_ptr<TransactionMeterData> MeteringService::endTxMeterData(Transaction *transaction) {\n    if (!transaction) {\n        MO_DBG_ERR(\"invalid argument\");\n        return nullptr;\n    }\n    auto connectorId = transaction->getConnectorId();\n    if (connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"connectorId is out of bounds\");\n        return nullptr;\n    }\n    return connectors[connectorId]->endTxMeterData(transaction);\n}\n\nvoid MeteringService::abortTxMeterData(unsigned int connectorId) {\n    if (connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"connectorId is out of bounds\");\n        return;\n    }\n    connectors[connectorId]->abortTxMeterData();\n}\n\nstd::shared_ptr<TransactionMeterData> MeteringService::getStopTxMeterData(Transaction *transaction) {\n    if (!transaction) {\n        MO_DBG_ERR(\"invalid argument\");\n        return nullptr;\n    }\n    auto connectorId = transaction->getConnectorId();\n    if (connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"connectorId is out of bounds\");\n        return nullptr;\n    }\n    return connectors[connectorId]->getStopTxMeterData(transaction);\n}\n\nbool MeteringService::removeTxMeterData(unsigned int connectorId, unsigned int txNr) {\n    return meterStore.remove(connectorId, txNr);\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/MeteringService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_METERINGSERVICE_H\n#define MO_METERINGSERVICE_H\n\n#include <functional>\n#include <memory>\n\n#include <MicroOcpp/Model/Metering/MeteringConnector.h>\n#include <MicroOcpp/Model/Metering/SampledValue.h>\n#include <MicroOcpp/Model/Metering/MeterStore.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Context;\nclass Request;\nclass FilesystemAdapter;\n\nclass MeteringService : public MemoryManaged {\nprivate:\n    Context& context;\n    MeterStore meterStore;\n\n    Vector<std::unique_ptr<MeteringConnector>> connectors;\npublic:\n    MeteringService(Context& context, int numConnectors, std::shared_ptr<FilesystemAdapter> filesystem);\n\n    void loop();\n\n    void addMeterValueSampler(int connectorId, std::unique_ptr<SampledValueSampler> meterValueSampler);\n\n    std::unique_ptr<SampledValue> readTxEnergyMeter(int connectorId, ReadingContext reason);\n\n    std::unique_ptr<Request> takeTriggeredMeterValues(int connectorId); //snapshot of all meters now\n\n    void beginTxMeterData(Transaction *transaction);\n\n    std::shared_ptr<TransactionMeterData> endTxMeterData(Transaction *transaction); //use return value to keep data in cache\n\n    void abortTxMeterData(unsigned int connectorId); //call this to free resources if txMeterData record is not ended normally. Does not remove files\n\n    std::shared_ptr<TransactionMeterData> getStopTxMeterData(Transaction *transaction); //prefer endTxMeterData when possible\n\n    bool removeTxMeterData(unsigned int connectorId, unsigned int txNr);\n\n    int getNumConnectors() {return connectors.size();}\n};\n\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/ReadingContext.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <string.h>\n\n#include <MicroOcpp/Model/Metering/ReadingContext.h>\n#include <MicroOcpp/Debug.h>\n\nnamespace MicroOcpp {\n\nconst char *serializeReadingContext(ReadingContext context) {\n    switch (context) {\n        case (ReadingContext_InterruptionBegin):\n            return \"Interruption.Begin\";\n        case (ReadingContext_InterruptionEnd):\n            return \"Interruption.End\";\n        case (ReadingContext_Other):\n            return \"Other\";\n        case (ReadingContext_SampleClock):\n            return \"Sample.Clock\";\n        case (ReadingContext_SamplePeriodic):\n            return \"Sample.Periodic\";\n        case (ReadingContext_TransactionBegin):\n            return \"Transaction.Begin\";\n        case (ReadingContext_TransactionEnd):\n            return \"Transaction.End\";\n        case (ReadingContext_Trigger):\n            return \"Trigger\";\n        default:\n            MO_DBG_ERR(\"ReadingContext not specified\");\n            /* fall through */\n        case (ReadingContext_UNDEFINED):\n            return \"\";\n    }\n}\nReadingContext deserializeReadingContext(const char *context) {\n    if (!context) {\n        MO_DBG_ERR(\"Invalid argument\");\n        return ReadingContext_UNDEFINED;\n    }\n\n    if (!strcmp(context, \"Sample.Periodic\")) {\n        return ReadingContext_SamplePeriodic;\n    } else if (!strcmp(context, \"Sample.Clock\")) {\n        return ReadingContext_SampleClock;\n    } else if (!strcmp(context, \"Transaction.Begin\")) {\n        return ReadingContext_TransactionBegin;\n    } else if (!strcmp(context, \"Transaction.End\")) {\n        return ReadingContext_TransactionEnd;\n    } else if (!strcmp(context, \"Other\")) {\n        return ReadingContext_Other;\n    } else if (!strcmp(context, \"Interruption.Begin\")) {\n        return ReadingContext_InterruptionBegin;\n    } else if (!strcmp(context, \"Interruption.End\")) {\n        return ReadingContext_InterruptionEnd;\n    } else if (!strcmp(context, \"Trigger\")) {\n        return ReadingContext_Trigger;\n    }\n\n    MO_DBG_ERR(\"ReadingContext not specified %.10s\", context);\n    return ReadingContext_UNDEFINED;\n}\n\n} //namespace MicroOcpp\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/ReadingContext.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_READINGCONTEXT_H\n#define MO_READINGCONTEXT_H\n\ntypedef enum {\n    ReadingContext_UNDEFINED,\n    ReadingContext_InterruptionBegin,\n    ReadingContext_InterruptionEnd,\n    ReadingContext_Other,\n    ReadingContext_SampleClock,\n    ReadingContext_SamplePeriodic,\n    ReadingContext_TransactionBegin,\n    ReadingContext_TransactionEnd,\n    ReadingContext_Trigger\n}   ReadingContext;\n\n#ifdef __cplusplus\n\nnamespace MicroOcpp {\nconst char *serializeReadingContext(ReadingContext context);\nReadingContext deserializeReadingContext(const char *serialized);\n}\n\n#endif\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/SampledValue.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Metering/SampledValue.h>\n#include <MicroOcpp/Debug.h>\n#include <cinttypes>\n\n#ifndef MO_SAMPLEDVALUE_FLOAT_FORMAT\n#define MO_SAMPLEDVALUE_FLOAT_FORMAT \"%.2f\"\n#endif\n\nusing namespace MicroOcpp;\n\nint32_t SampledValueDeSerializer<int32_t>::deserialize(const char *str) {\n    return strtol(str, nullptr, 10);\n}\n\nMicroOcpp::String SampledValueDeSerializer<int32_t>::serialize(int32_t& val) {\n    char str [12] = {'\\0'};\n    snprintf(str, 12, \"%\" PRId32, val);\n    return makeString(\"v16.Metering.SampledValueDeSerializer<int32_t>\", str);\n}\n\nMicroOcpp::String SampledValueDeSerializer<float>::serialize(float& val) {\n    char str [20];\n    str[0] = '\\0';\n    snprintf(str, 20, MO_SAMPLEDVALUE_FLOAT_FORMAT, val);\n    return makeString(\"v16.Metering.SampledValueDeSerializer<float>\", str);\n}\n\nstd::unique_ptr<JsonDoc> SampledValue::toJson() {\n    auto value = serializeValue();\n    if (value.empty()) {\n        return nullptr;\n    }\n    size_t capacity = 0;\n    capacity += JSON_OBJECT_SIZE(8);\n    capacity += value.length() + 1;\n    auto result = makeJsonDoc(\"v16.Metering.SampledValue\", capacity);\n    auto payload = result->to<JsonObject>();\n    payload[\"value\"] = value;\n    auto context_cstr = serializeReadingContext(context);\n    if (context_cstr)\n        payload[\"context\"] = context_cstr;\n    if (*properties.getFormat())\n        payload[\"format\"] = properties.getFormat();\n    if (*properties.getMeasurand())\n        payload[\"measurand\"] = properties.getMeasurand();\n    if (*properties.getPhase())\n        payload[\"phase\"] = properties.getPhase();\n    if (*properties.getLocation())\n        payload[\"location\"] = properties.getLocation();\n    if (*properties.getUnit())\n        payload[\"unit\"] = properties.getUnit();\n    return result;\n}\n\nReadingContext SampledValue::getReadingContext() {\n    return context;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Metering/SampledValue.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef SAMPLEDVALUE_H\n#define SAMPLEDVALUE_H\n\n#include <ArduinoJson.h>\n#include <memory>\n#include <functional>\n\n#include <MicroOcpp/Model/Metering/ReadingContext.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Platform.h>\n\nnamespace MicroOcpp {\n\ntemplate <class T>\nclass SampledValueDeSerializer {\npublic:\n    static T deserialize(const char *str);\n    static bool ready(T& val);\n    static String serialize(T& val);\n    static int32_t toInteger(T& val);\n};\n\ntemplate <>\nclass SampledValueDeSerializer<int32_t> { // example class\npublic:\n    static int32_t deserialize(const char *str);\n    static bool ready(int32_t& val) {return true;} //int32_t is always valid\n    static String serialize(int32_t& val);\n    static int32_t toInteger(int32_t& val) {return val;} //no conversion required\n};\n\ntemplate <>\nclass SampledValueDeSerializer<float> { // Used in meterValues\npublic:\n    static float deserialize(const char *str) {return atof(str);}\n    static bool ready(float& val) {return true;} //float is always valid\n    static String serialize(float& val);\n    static int32_t toInteger(float& val) {return (int32_t) val;}\n};\n\nclass SampledValueProperties {\nprivate:\n    String format;\n    String measurand;\n    String phase;\n    String location;\n    String unit;\n\npublic:\n    SampledValueProperties() :\n            format(makeString(\"v16.Metering.SampledValueProperties\")),\n            measurand(makeString(\"v16.Metering.SampledValueProperties\")),\n            phase(makeString(\"v16.Metering.SampledValueProperties\")),\n            location(makeString(\"v16.Metering.SampledValueProperties\")),\n            unit(makeString(\"v16.Metering.SampledValueProperties\")) { }\n    SampledValueProperties(const SampledValueProperties& other) :\n            format(other.format),\n            measurand(other.measurand),\n            phase(other.phase),\n            location(other.location),\n            unit(other.unit) { }\n    ~SampledValueProperties() = default;\n\n    void setFormat(const char *format) {this->format = format;}\n    const char *getFormat() const {return format.c_str();}\n    void setMeasurand(const char *measurand) {this->measurand = measurand;}\n    const char *getMeasurand() const {return measurand.c_str();}\n    void setPhase(const char *phase) {this->phase = phase;}\n    const char *getPhase() const {return phase.c_str();}\n    void setLocation(const char *location) {this->location = location;}\n    const char *getLocation() const {return location.c_str();}\n    void setUnit(const char *unit) {this->unit = unit;}\n    const char *getUnit() const {return unit.c_str();}\n};\n\nclass SampledValue {\nprotected:\n    const SampledValueProperties& properties;\n    const ReadingContext context;\n    virtual String serializeValue() = 0;\npublic:\n    SampledValue(const SampledValueProperties& properties, ReadingContext context) : properties(properties), context(context) { }\n    SampledValue(const SampledValue& other) : properties(other.properties), context(other.context) { }\n    virtual ~SampledValue() = default;\n\n    std::unique_ptr<JsonDoc> toJson();\n\n    virtual operator bool() = 0;\n    virtual int32_t toInteger() = 0;\n\n    ReadingContext getReadingContext();\n};\n\ntemplate <class T, class DeSerializer>\nclass SampledValueConcrete : public SampledValue, public MemoryManaged {\nprivate:\n    T value;\npublic:\n    SampledValueConcrete(const SampledValueProperties& properties, ReadingContext context, const T&& value) : SampledValue(properties, context), MemoryManaged(\"v16.Metering.SampledValueConcrete\"), value(value) { }\n    SampledValueConcrete(const SampledValueConcrete& other) : SampledValue(other), MemoryManaged(other), value(other.value) { }\n    ~SampledValueConcrete() = default;\n\n    operator bool() override {return DeSerializer::ready(value);}\n\n    String serializeValue() override {return DeSerializer::serialize(value);}\n\n    int32_t toInteger() override { return DeSerializer::toInteger(value);}\n};\n\nclass SampledValueSampler {\nprotected:\n    SampledValueProperties properties;\npublic:\n    SampledValueSampler(SampledValueProperties properties) : properties(properties) { }\n    virtual ~SampledValueSampler() = default;\n    virtual std::unique_ptr<SampledValue> takeValue(ReadingContext context) = 0;\n    virtual std::unique_ptr<SampledValue> deserializeValue(JsonObject svJson) = 0;\n    const SampledValueProperties& getProperties() {return properties;};\n};\n\ntemplate <class T, class DeSerializer>\nclass SampledValueSamplerConcrete : public SampledValueSampler, public MemoryManaged {\nprivate:\n    std::function<T(ReadingContext context)> sampler;\npublic:\n    SampledValueSamplerConcrete(SampledValueProperties properties, std::function<T(ReadingContext)> sampler) : SampledValueSampler(properties), MemoryManaged(\"v16.Metering.SampledValueSamplerConcrete\"), sampler(sampler) { }\n    std::unique_ptr<SampledValue> takeValue(ReadingContext context) override {\n        return std::unique_ptr<SampledValueConcrete<T, DeSerializer>>(new SampledValueConcrete<T, DeSerializer>(\n            properties,\n            context,\n            sampler(context)));\n    }\n    std::unique_ptr<SampledValue> deserializeValue(JsonObject svJson) override {\n        return std::unique_ptr<SampledValueConcrete<T, DeSerializer>>(new SampledValueConcrete<T, DeSerializer>(\n            properties,\n            deserializeReadingContext(svJson[\"context\"] | \"NOT_SET\"),\n            DeSerializer::deserialize(svJson[\"value\"] | \"\")));\n    }\n};\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Model.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Model.h>\n\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Model/SmartCharging/SmartChargingService.h>\n#include <MicroOcpp/Model/ConnectorBase/ConnectorsCommon.h>\n#include <MicroOcpp/Model/Metering/MeteringService.h>\n#include <MicroOcpp/Model/Metering/MeterValuesV201.h>\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>\n#include <MicroOcpp/Model/Diagnostics/DiagnosticsService.h>\n#include <MicroOcpp/Model/Heartbeat/HeartbeatService.h>\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n#include <MicroOcpp/Model/Reservation/ReservationService.h>\n#include <MicroOcpp/Model/Boot/BootService.h>\n#include <MicroOcpp/Model/Reset/ResetService.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n#include <MicroOcpp/Model/Certificates/CertificateService.h>\n#include <MicroOcpp/Model/Availability/AvailabilityService.h>\n#include <MicroOcpp/Model/RemoteControl/RemoteControlService.h>\n\n#include <MicroOcpp/Core/Configuration.h>\n\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nModel::Model(ProtocolVersion version, uint16_t bootNr) : MemoryManaged(\"Model\"), connectors(makeVector<std::unique_ptr<Connector>>(getMemoryTag())), version(version), bootNr(bootNr) {\n\n}\n\nModel::~Model() = default;\n\nvoid Model::loop() {\n\n    if (bootService) {\n        bootService->loop();\n    }\n\n    if (capabilitiesUpdated) {\n        updateSupportedStandardProfiles();\n        capabilitiesUpdated = false;\n    }\n\n    if (!runTasks) {\n        return;\n    }\n\n    for (auto& connector : connectors) {\n        connector->loop();\n    }\n\n    if (chargeControlCommon)\n        chargeControlCommon->loop();\n\n    if (smartChargingService)\n        smartChargingService->loop();\n\n    if (heartbeatService)\n        heartbeatService->loop();\n\n    if (meteringService)\n        meteringService->loop();\n\n    if (diagnosticsService)\n        diagnosticsService->loop();\n\n    if (firmwareService)\n        firmwareService->loop();\n\n#if MO_ENABLE_RESERVATION\n    if (reservationService)\n        reservationService->loop();\n#endif //MO_ENABLE_RESERVATION\n\n    if (resetService)\n        resetService->loop();\n\n#if MO_ENABLE_V201\n    if (availabilityService)\n        availabilityService->loop();\n\n    if (transactionService)\n        transactionService->loop();\n    \n    if (resetServiceV201)\n        resetServiceV201->loop();\n#endif\n}\n\nvoid Model::setTransactionStore(std::unique_ptr<TransactionStore> ts) {\n    transactionStore = std::move(ts);\n    capabilitiesUpdated = true;\n}\n\nTransactionStore *Model::getTransactionStore() {\n    return transactionStore.get();\n}\n\nvoid Model::setSmartChargingService(std::unique_ptr<SmartChargingService> scs) {\n    smartChargingService = std::move(scs);\n    capabilitiesUpdated = true;\n}\n\nSmartChargingService* Model::getSmartChargingService() const {\n    return smartChargingService.get();\n}\n\nvoid Model::setConnectorsCommon(std::unique_ptr<ConnectorsCommon> ccs) {\n    chargeControlCommon = std::move(ccs);\n    capabilitiesUpdated = true;\n}\n\nConnectorsCommon *Model::getConnectorsCommon() {\n    return chargeControlCommon.get();\n}\n\nvoid Model::setConnectors(Vector<std::unique_ptr<Connector>>&& connectors) {\n    this->connectors = std::move(connectors);\n    capabilitiesUpdated = true;\n}\n\nunsigned int Model::getNumConnectors() const {\n    return connectors.size();\n}\n\nConnector *Model::getConnector(unsigned int connectorId) {\n    if (connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"connector with connectorId %u does not exist\", connectorId);\n        return nullptr;\n    }\n\n    return connectors[connectorId].get();\n}\n\nvoid Model::setMeteringSerivce(std::unique_ptr<MeteringService> ms) {\n    meteringService = std::move(ms);\n    capabilitiesUpdated = true;\n}\n\nMeteringService* Model::getMeteringService() const {\n    return meteringService.get();\n}\n\nvoid Model::setFirmwareService(std::unique_ptr<FirmwareService> fws) {\n    firmwareService = std::move(fws);\n    capabilitiesUpdated = true;\n}\n\nFirmwareService *Model::getFirmwareService() const {\n    return firmwareService.get();\n}\n\nvoid Model::setDiagnosticsService(std::unique_ptr<DiagnosticsService> ds) {\n    diagnosticsService = std::move(ds);\n    capabilitiesUpdated = true;\n}\n\nDiagnosticsService *Model::getDiagnosticsService() const {\n    return diagnosticsService.get();\n}\n\nvoid Model::setHeartbeatService(std::unique_ptr<HeartbeatService> hs) {\n    heartbeatService = std::move(hs);\n    capabilitiesUpdated = true;\n}\n\n#if MO_ENABLE_LOCAL_AUTH\nvoid Model::setAuthorizationService(std::unique_ptr<AuthorizationService> as) {\n    authorizationService = std::move(as);\n    capabilitiesUpdated = true;\n}\n\nAuthorizationService *Model::getAuthorizationService() {\n    return authorizationService.get();\n}\n#endif //MO_ENABLE_LOCAL_AUTH\n\n#if MO_ENABLE_RESERVATION\nvoid Model::setReservationService(std::unique_ptr<ReservationService> rs) {\n    reservationService = std::move(rs);\n    capabilitiesUpdated = true;\n}\n\nReservationService *Model::getReservationService() {\n    return reservationService.get();\n}\n#endif //MO_ENABLE_RESERVATION\n\nvoid Model::setBootService(std::unique_ptr<BootService> bs){\n    bootService = std::move(bs);\n    capabilitiesUpdated = true;\n}\n\nBootService *Model::getBootService() const {\n    return bootService.get();\n}\n\nvoid Model::setResetService(std::unique_ptr<ResetService> rs) {\n    this->resetService = std::move(rs);\n    capabilitiesUpdated = true;\n}\n\nResetService *Model::getResetService() const {\n    return resetService.get();\n}\n\n#if MO_ENABLE_CERT_MGMT\nvoid Model::setCertificateService(std::unique_ptr<CertificateService> cs) {\n    this->certService = std::move(cs);\n    capabilitiesUpdated = true;\n}\n\nCertificateService *Model::getCertificateService() const {\n    return certService.get();\n}\n#endif //MO_ENABLE_CERT_MGMT\n\n#if MO_ENABLE_V201\nvoid Model::setAvailabilityService(std::unique_ptr<AvailabilityService> as) {\n    this->availabilityService = std::move(as);\n    capabilitiesUpdated = true;\n}\n\nAvailabilityService *Model::getAvailabilityService() const {\n    return availabilityService.get();\n}\n\nvoid Model::setVariableService(std::unique_ptr<VariableService> vs) {\n    this->variableService = std::move(vs);\n    capabilitiesUpdated = true;\n}\n\nVariableService *Model::getVariableService() const {\n    return variableService.get();\n}\n\nvoid Model::setTransactionService(std::unique_ptr<TransactionService> ts) {\n    this->transactionService = std::move(ts);\n    capabilitiesUpdated = true;\n}\n\nTransactionService *Model::getTransactionService() const {\n    return transactionService.get();\n}\n\nvoid Model::setResetServiceV201(std::unique_ptr<Ocpp201::ResetService> rs) {\n    this->resetServiceV201 = std::move(rs);\n    capabilitiesUpdated = true;\n}\n\nOcpp201::ResetService *Model::getResetServiceV201() const {\n    return resetServiceV201.get();\n}\n\nvoid Model::setMeteringServiceV201(std::unique_ptr<Ocpp201::MeteringService> rs) {\n    this->meteringServiceV201 = std::move(rs);\n    capabilitiesUpdated = true;\n}\n\nOcpp201::MeteringService *Model::getMeteringServiceV201() const {\n    return meteringServiceV201.get();\n}\n\nvoid Model::setRemoteControlService(std::unique_ptr<RemoteControlService> rs) {\n    remoteControlService = std::move(rs);\n    capabilitiesUpdated = true;\n}\n\nRemoteControlService *Model::getRemoteControlService() const {\n    return remoteControlService.get();\n}\n#endif\n\nClock& Model::getClock() {\n    return clock;\n}\n\nconst ProtocolVersion& Model::getVersion() const {\n    return version;\n}\n\nuint16_t Model::getBootNr() {\n    return bootNr;\n}\n\nvoid Model::updateSupportedStandardProfiles() {\n\n    auto supportedFeatureProfilesString =\n        declareConfiguration<const char*>(\"SupportedFeatureProfiles\", \"\", CONFIGURATION_VOLATILE, true);\n    \n    if (!supportedFeatureProfilesString) {\n        MO_DBG_ERR(\"OOM\");\n        return;\n    }\n\n    auto buf = makeString(getMemoryTag(), supportedFeatureProfilesString->getString());\n\n    if (chargeControlCommon &&\n            heartbeatService &&\n            bootService) {\n        if (!strstr(supportedFeatureProfilesString->getString(), \"Core\")) {\n            if (!buf.empty()) buf += ',';\n            buf += \"Core\";\n        }\n    }\n\n    if (firmwareService ||\n            diagnosticsService) {\n        if (!strstr(supportedFeatureProfilesString->getString(), \"FirmwareManagement\")) {\n            if (!buf.empty()) buf += ',';\n            buf += \"FirmwareManagement\";\n        }\n    }\n\n#if MO_ENABLE_LOCAL_AUTH\n    if (authorizationService && authorizationService->localAuthListEnabled()) {\n        if (!strstr(supportedFeatureProfilesString->getString(), \"LocalAuthListManagement\")) {\n            if (!buf.empty()) buf += ',';\n            buf += \"LocalAuthListManagement\";\n        }\n    }\n#endif //MO_ENABLE_LOCAL_AUTH\n\n#if MO_ENABLE_RESERVATION\n    if (reservationService) {\n        if (!strstr(supportedFeatureProfilesString->getString(), \"Reservation\")) {\n            if (!buf.empty()) buf += ',';\n            buf += \"Reservation\";\n        }\n    }\n#endif //MO_ENABLE_RESERVATION\n\n    if (smartChargingService) {\n        if (!strstr(supportedFeatureProfilesString->getString(), \"SmartCharging\")) {\n            if (!buf.empty()) buf += ',';\n            buf += \"SmartCharging\";\n        }\n    }\n\n    if (!strstr(supportedFeatureProfilesString->getString(), \"RemoteTrigger\")) {\n        if (!buf.empty()) buf += ',';\n        buf += \"RemoteTrigger\";\n    }\n\n    supportedFeatureProfilesString->setString(buf.c_str());\n\n    MO_DBG_DEBUG(\"supported feature profiles: %s\", buf.c_str());\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Model.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_MODEL_H\n#define MO_MODEL_H\n\n#include <memory>\n\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Version.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n\nnamespace MicroOcpp {\n\nclass TransactionStore;\nclass SmartChargingService;\nclass ConnectorsCommon;\nclass MeteringService;\nclass FirmwareService;\nclass DiagnosticsService;\nclass HeartbeatService;\nclass BootService;\nclass ResetService;\n\n#if MO_ENABLE_LOCAL_AUTH\nclass AuthorizationService;\n#endif //MO_ENABLE_LOCAL_AUTH\n\n#if MO_ENABLE_RESERVATION\nclass ReservationService;\n#endif //MO_ENABLE_RESERVATION\n\n#if MO_ENABLE_CERT_MGMT\nclass CertificateService;\n#endif //MO_ENABLE_CERT_MGMT\n\n#if MO_ENABLE_V201\nclass AvailabilityService;\nclass VariableService;\nclass TransactionService;\nclass RemoteControlService;\n\nnamespace Ocpp201 {\nclass ResetService;\nclass MeteringService;\n}\n#endif //MO_ENABLE_V201\n\nclass Model : public MemoryManaged {\nprivate:\n    Vector<std::unique_ptr<Connector>> connectors;\n    std::unique_ptr<TransactionStore> transactionStore;\n    std::unique_ptr<SmartChargingService> smartChargingService;\n    std::unique_ptr<ConnectorsCommon> chargeControlCommon;\n    std::unique_ptr<MeteringService> meteringService;\n    std::unique_ptr<FirmwareService> firmwareService;\n    std::unique_ptr<DiagnosticsService> diagnosticsService;\n    std::unique_ptr<HeartbeatService> heartbeatService;\n    std::unique_ptr<BootService> bootService;\n    std::unique_ptr<ResetService> resetService;\n\n#if MO_ENABLE_LOCAL_AUTH\n    std::unique_ptr<AuthorizationService> authorizationService;\n#endif //MO_ENABLE_LOCAL_AUTH\n\n#if MO_ENABLE_RESERVATION\n    std::unique_ptr<ReservationService> reservationService;\n#endif //MO_ENABLE_RESERVATION\n\n#if MO_ENABLE_CERT_MGMT\n    std::unique_ptr<CertificateService> certService;\n#endif //MO_ENABLE_CERT_MGMT\n\n#if MO_ENABLE_V201\n    std::unique_ptr<AvailabilityService> availabilityService;\n    std::unique_ptr<VariableService> variableService;\n    std::unique_ptr<TransactionService> transactionService;\n    std::unique_ptr<Ocpp201::ResetService> resetServiceV201;\n    std::unique_ptr<Ocpp201::MeteringService> meteringServiceV201;\n    std::unique_ptr<RemoteControlService> remoteControlService;\n#endif\n\n    Clock clock;\n\n    ProtocolVersion version;\n\n    bool capabilitiesUpdated = true;\n    void updateSupportedStandardProfiles();\n\n    bool runTasks = false;\n\n    const uint16_t bootNr = 0; //each boot of this lib has a unique number\n\npublic:\n    Model(ProtocolVersion version = ProtocolVersion(1,6), uint16_t bootNr = 0);\n    Model(const Model& rhs) = delete;\n    ~Model();\n\n    void loop();\n\n    void activateTasks() {runTasks = true;}\n\n    void setTransactionStore(std::unique_ptr<TransactionStore> transactionStore);\n    TransactionStore *getTransactionStore();\n\n    void setSmartChargingService(std::unique_ptr<SmartChargingService> scs);\n    SmartChargingService* getSmartChargingService() const;\n\n    void setConnectorsCommon(std::unique_ptr<ConnectorsCommon> ccs);\n    ConnectorsCommon *getConnectorsCommon();\n\n    void setConnectors(Vector<std::unique_ptr<Connector>>&& connectors);\n    unsigned int getNumConnectors() const;\n    Connector *getConnector(unsigned int connectorId);\n\n    void setMeteringSerivce(std::unique_ptr<MeteringService> meteringService);\n    MeteringService* getMeteringService() const;\n\n    void setFirmwareService(std::unique_ptr<FirmwareService> firmwareService);\n    FirmwareService *getFirmwareService() const;\n\n    void setDiagnosticsService(std::unique_ptr<DiagnosticsService> diagnosticsService);\n    DiagnosticsService *getDiagnosticsService() const;\n\n    void setHeartbeatService(std::unique_ptr<HeartbeatService> heartbeatService);\n\n#if MO_ENABLE_LOCAL_AUTH\n    void setAuthorizationService(std::unique_ptr<AuthorizationService> authorizationService);\n    AuthorizationService *getAuthorizationService();\n#endif //MO_ENABLE_LOCAL_AUTH\n\n#if MO_ENABLE_RESERVATION\n    void setReservationService(std::unique_ptr<ReservationService> reservationService);\n    ReservationService *getReservationService();\n#endif //MO_ENABLE_RESERVATION\n\n    void setBootService(std::unique_ptr<BootService> bs);\n    BootService *getBootService() const;\n\n    void setResetService(std::unique_ptr<ResetService> rs);\n    ResetService *getResetService() const;\n\n#if MO_ENABLE_CERT_MGMT\n    void setCertificateService(std::unique_ptr<CertificateService> cs);\n    CertificateService *getCertificateService() const;\n#endif //MO_ENABLE_CERT_MGMT\n\n#if MO_ENABLE_V201\n    void setAvailabilityService(std::unique_ptr<AvailabilityService> as);\n    AvailabilityService *getAvailabilityService() const;\n\n    void setVariableService(std::unique_ptr<VariableService> vs);\n    VariableService *getVariableService() const;\n\n    void setTransactionService(std::unique_ptr<TransactionService> ts);\n    TransactionService *getTransactionService() const;\n\n    void setResetServiceV201(std::unique_ptr<Ocpp201::ResetService> rs);\n    Ocpp201::ResetService *getResetServiceV201() const;\n\n    void setMeteringServiceV201(std::unique_ptr<Ocpp201::MeteringService> ms);\n    Ocpp201::MeteringService *getMeteringServiceV201() const;\n\n    void setRemoteControlService(std::unique_ptr<RemoteControlService> rs);\n    RemoteControlService *getRemoteControlService() const;\n#endif\n\n    Clock &getClock();\n\n    const ProtocolVersion& getVersion() const;\n\n    uint16_t getBootNr();\n};\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/RemoteControl/RemoteControlDefs.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_UNLOCKCONNECTOR_H\n#define MO_UNLOCKCONNECTOR_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <stdint.h>\n\n#include <MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h>\n\ntypedef enum {\n    RequestStartStopStatus_Accepted,\n    RequestStartStopStatus_Rejected\n}   RequestStartStopStatus;\n\n#if MO_ENABLE_CONNECTOR_LOCK\n\ntypedef enum {\n    UnlockStatus_Unlocked,\n    UnlockStatus_UnlockFailed,\n    UnlockStatus_OngoingAuthorizedTransaction,\n    UnlockStatus_UnknownConnector,\n    UnlockStatus_PENDING // unlock action not finished yet, result still unknown (MO will check again later)\n}   UnlockStatus;\n\n#endif // MO_ENABLE_CONNECTOR_LOCK\n\n#endif // MO_ENABLE_V201\n#endif // MO_UNLOCKCONNECTOR_H\n"
  },
  {
    "path": "src/MicroOcpp/Model/RemoteControl/RemoteControlService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/RemoteControl/RemoteControlService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Debug.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n#include <MicroOcpp/Operations/RequestStartTransaction.h>\n#include <MicroOcpp/Operations/RequestStopTransaction.h>\n#include <MicroOcpp/Operations/TriggerMessage.h>\n#include <MicroOcpp/Operations/UnlockConnector.h>\n\nusing namespace MicroOcpp;\n\nRemoteControlServiceEvse::RemoteControlServiceEvse(Context& context, unsigned int evseId) : MemoryManaged(\"v201.RemoteControl.RemoteControlServiceEvse\"), context(context), evseId(evseId) {\n\n}\n\n#if MO_ENABLE_CONNECTOR_LOCK\nvoid RemoteControlServiceEvse::setOnUnlockConnector(UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *userData), void *userData) {\n    this->onUnlockConnector = onUnlockConnector;\n    this->onUnlockConnectorUserData = userData;\n}\n\nUnlockStatus RemoteControlServiceEvse::unlockConnector() {\n\n    if (!onUnlockConnector) {\n        return UnlockStatus_UnlockFailed;\n    }\n\n    if (auto txService = context.getModel().getTransactionService()) {\n        if (auto evse = txService->getEvse(evseId)) {\n            if (auto tx = evse->getTransaction()) {\n                if (tx->started && !tx->stopped && tx->isAuthorized) {\n                    return UnlockStatus_OngoingAuthorizedTransaction;\n                } else {\n                    evse->abortTransaction(Ocpp201::Transaction::StoppedReason::Other,Ocpp201::TransactionEventTriggerReason::UnlockCommand);\n                }\n            }\n        }\n    }\n\n    auto status = onUnlockConnector(evseId, onUnlockConnectorUserData);\n    switch (status) {\n        case UnlockConnectorResult_Pending:\n            return UnlockStatus_PENDING;\n        case UnlockConnectorResult_Unlocked:\n            return UnlockStatus_Unlocked;\n        case UnlockConnectorResult_UnlockFailed:\n            return UnlockStatus_UnlockFailed;\n    }\n\n    MO_DBG_ERR(\"invalid onUnlockConnector result code\");\n    return UnlockStatus_UnlockFailed;\n}\n#endif\n\nRemoteControlService::RemoteControlService(Context& context, size_t numEvses) : MemoryManaged(\"v201.RemoteControl.RemoteControlService\"), context(context) {\n\n    for (size_t i = 0; i < numEvses && i < MO_NUM_EVSEID; i++) {\n        evses[i] = new RemoteControlServiceEvse(context, (unsigned int)i);\n    }\n\n    auto varService = context.getModel().getVariableService();\n    authorizeRemoteStart = varService->declareVariable<bool>(\"AuthCtrlr\", \"AuthorizeRemoteStart\", false);\n\n    context.getOperationRegistry().registerOperation(\"RequestStartTransaction\", [this] () -> Operation* {\n        if (!this->context.getModel().getTransactionService()) {\n            return nullptr; //-> NotSupported\n        }\n        return new Ocpp201::RequestStartTransaction(*this);});\n    context.getOperationRegistry().registerOperation(\"RequestStopTransaction\", [this] () -> Operation* {\n        if (!this->context.getModel().getTransactionService()) {\n            return nullptr; //-> NotSupported\n        }\n        return new Ocpp201::RequestStopTransaction(*this);});\n#if MO_ENABLE_CONNECTOR_LOCK\n    context.getOperationRegistry().registerOperation(\"UnlockConnector\", [this] () {\n        return new Ocpp201::UnlockConnector(*this);});\n#endif\n    context.getOperationRegistry().registerOperation(\"TriggerMessage\", [&context] () {\n        return new Ocpp16::TriggerMessage(context);});\n}\n\nRemoteControlService::~RemoteControlService() {\n    for (size_t i = 0; i < MO_NUM_EVSEID && evses[i]; i++) {\n        delete evses[i];\n    }\n}\n\nRemoteControlServiceEvse *RemoteControlService::getEvse(unsigned int evseId) {\n    if (evseId >= MO_NUM_EVSEID) {\n        MO_DBG_ERR(\"invalid arg\");\n        return nullptr;\n    }\n    return evses[evseId];\n}\n\nRequestStartStopStatus RemoteControlService::requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, IdToken idToken, char *transactionIdOut, size_t transactionIdBufSize) {\n    \n    TransactionService *txService = context.getModel().getTransactionService();\n    if (!txService) {\n        MO_DBG_ERR(\"TxService uninitialized\");\n        return RequestStartStopStatus_Rejected;\n    }\n    \n    auto evse = txService->getEvse(evseId);\n    if (!evse) {\n        MO_DBG_ERR(\"EVSE not found\");\n        return RequestStartStopStatus_Rejected;\n    }\n\n    if (!evse->beginAuthorization(idToken, authorizeRemoteStart->getBool())) {\n        MO_DBG_INFO(\"EVSE still occupied with pending tx\");\n        if (auto tx = evse->getTransaction()) {\n            auto ret = snprintf(transactionIdOut, transactionIdBufSize, \"%s\", tx->transactionId);\n            if (ret < 0 || (size_t)ret >= transactionIdBufSize) {\n                MO_DBG_ERR(\"internal error\");\n                return RequestStartStopStatus_Rejected;\n            }\n        }\n        return RequestStartStopStatus_Rejected;\n    }\n\n    auto tx = evse->getTransaction();\n    if (!tx) {\n        MO_DBG_ERR(\"internal error\");\n        return RequestStartStopStatus_Rejected;\n    }\n\n    auto ret = snprintf(transactionIdOut, transactionIdBufSize, \"%s\", tx->transactionId);\n    if (ret < 0 || (size_t)ret >= transactionIdBufSize) {\n        MO_DBG_ERR(\"internal error\");\n        return RequestStartStopStatus_Rejected;\n    }\n\n    tx->remoteStartId = remoteStartId;\n    tx->notifyRemoteStartId = true;\n\n    return RequestStartStopStatus_Accepted;\n}\n\nRequestStartStopStatus RemoteControlService::requestStopTransaction(const char *transactionId) {\n\n    TransactionService *txService = context.getModel().getTransactionService();\n    if (!txService) {\n        MO_DBG_ERR(\"TxService uninitialized\");\n        return RequestStartStopStatus_Rejected;\n    }\n\n    bool success = false;\n\n    for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID; evseId++) {\n        if (auto evse = txService->getEvse(evseId)) {\n            if (evse->getTransaction() && !strcmp(evse->getTransaction()->transactionId, transactionId)) {\n                success = evse->abortTransaction(Ocpp201::Transaction::StoppedReason::Remote, Ocpp201::TransactionEventTriggerReason::RemoteStop);\n                break;\n            }\n        }\n    }\n\n    return success ?\n            RequestStartStopStatus_Accepted :\n            RequestStartStopStatus_Rejected;\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Model/RemoteControl/RemoteControlService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_REMOTECONTROLSERVICE_H\n#define MO_REMOTECONTROLSERVICE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/RemoteControl/RemoteControlDefs.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Model/ConnectorBase/EvseId.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Context;\nclass Variable;\n\nclass RemoteControlServiceEvse : public MemoryManaged {\nprivate:\n    Context& context;\n    const unsigned int evseId;\n\n#if MO_ENABLE_CONNECTOR_LOCK\n    UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *user) = nullptr;\n    void *onUnlockConnectorUserData = nullptr;\n#endif\n\npublic:\n    RemoteControlServiceEvse(Context& context, unsigned int evseId);\n\n#if MO_ENABLE_CONNECTOR_LOCK\n    void setOnUnlockConnector(UnlockConnectorResult (*onUnlockConnector)(unsigned int evseId, void *userData), void *userData);\n\n    UnlockStatus unlockConnector();\n#endif\n\n};\n\nclass RemoteControlService : public MemoryManaged {\nprivate:\n    Context& context;\n    RemoteControlServiceEvse* evses [MO_NUM_EVSEID] = {nullptr};\n\n    Variable *authorizeRemoteStart = nullptr;\n\npublic:\n    RemoteControlService(Context& context, size_t numEvses);\n    ~RemoteControlService();\n\n    RemoteControlServiceEvse *getEvse(unsigned int evseId);\n\n    RequestStartStopStatus requestStartTransaction(unsigned int evseId, unsigned int remoteStartId, IdToken idToken, char *transactionIdOut, size_t transactionIdBufSize); //ChargingProfile, GroupIdToken not supported yet\n\n    RequestStartStopStatus requestStopTransaction(const char *transactionId);\n};\n\n} // namespace MicroOcpp\n\n#endif // MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Reservation/Reservation.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_RESERVATION\n\n#include <MicroOcpp/Model/Reservation/Reservation.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nReservation::Reservation(Model& model, unsigned int slot) : MemoryManaged(\"v16.Reservation.Reservation\"), model(model), slot(slot) {\n    \n    snprintf(connectorIdKey, sizeof(connectorIdKey), MO_RESERVATION_CID_KEY \"%u\", slot);\n    connectorIdInt = declareConfiguration<int>(connectorIdKey, -1, RESERVATION_FN, false, false, false);\n\n    snprintf(expiryDateRawKey, sizeof(expiryDateRawKey), MO_RESERVATION_EXPDATE_KEY \"%u\", slot);\n    expiryDateRawString = declareConfiguration<const char*>(expiryDateRawKey, \"\", RESERVATION_FN, false, false, false);\n    \n    snprintf(idTagKey, sizeof(idTagKey), MO_RESERVATION_IDTAG_KEY \"%u\", slot);\n    idTagString = declareConfiguration<const char*>(idTagKey, \"\", RESERVATION_FN, false, false, false);\n\n    snprintf(reservationIdKey, sizeof(reservationIdKey), MO_RESERVATION_RESID_KEY \"%u\", slot);\n    reservationIdInt = declareConfiguration<int>(reservationIdKey, -1, RESERVATION_FN, false, false, false);\n\n    snprintf(parentIdTagKey, sizeof(parentIdTagKey), MO_RESERVATION_PARENTID_KEY \"%u\", slot);\n    parentIdTagString = declareConfiguration<const char*>(parentIdTagKey, \"\", RESERVATION_FN, false, false, false);\n\n    if (!connectorIdInt || !expiryDateRawString || !idTagString || !reservationIdInt || !parentIdTagString) {\n        MO_DBG_ERR(\"initialization failure\");\n    }\n}\n\nReservation::~Reservation() {\n    if (connectorIdInt->getKey() == connectorIdKey) {\n        connectorIdInt->setKey(nullptr);\n    }\n    if (expiryDateRawString->getKey() == expiryDateRawKey) {\n        expiryDateRawString->setKey(nullptr);\n    }\n    if (idTagString->getKey() == idTagKey) {\n        idTagString->setKey(nullptr);\n    }\n    if (reservationIdInt->getKey() == reservationIdKey) {\n        reservationIdInt->setKey(nullptr);\n    }\n    if (parentIdTagString->getKey() == parentIdTagKey) {\n        parentIdTagString->setKey(nullptr);\n    }\n}\n\nbool Reservation::isActive() {\n    if (connectorIdInt->getInt() < 0) {\n        //reservation invalidated\n        return false;\n    }\n\n    if (model.getClock().now() > getExpiryDate()) {\n        //reservation expired\n        return false;\n    }\n\n    return true;\n}\n\nbool Reservation::matches(unsigned int connectorId) {\n    return (int) connectorId == connectorIdInt->getInt();\n}\n\nbool Reservation::matches(const char *idTag, const char *parentIdTag) {\n    if (idTag == nullptr && parentIdTag == nullptr) {\n        return true;\n    }\n\n    if (idTag && !strcmp(idTag, idTagString->getString())) {\n        return true;\n    }\n\n    if (parentIdTag && !strcmp(parentIdTag, parentIdTagString->getString())) {\n        return true;\n    }\n\n    return false;\n}\n\nint Reservation::getConnectorId() {\n    return connectorIdInt->getInt();\n}\n\nTimestamp& Reservation::getExpiryDate() {\n    if (expiryDate == MIN_TIME && *expiryDateRawString->getString()) {\n        expiryDate.setTime(expiryDateRawString->getString());\n    }\n    return expiryDate;\n}\n\nconst char *Reservation::getIdTag() {\n    return idTagString->getString();\n}\n\nint Reservation::getReservationId() {\n    return reservationIdInt->getInt();\n}\n\nconst char *Reservation::getParentIdTag() {\n    return parentIdTagString->getString();\n}\n\nvoid Reservation::update(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag) {\n    reservationIdInt->setInt(reservationId);\n    connectorIdInt->setInt((int) connectorId);\n    this->expiryDate = expiryDate;\n    char expiryDate_cstr [JSONDATE_LENGTH + 1];\n    if (this->expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1)) {\n        expiryDateRawString->setString(expiryDate_cstr);\n    }\n    idTagString->setString(idTag);\n    parentIdTagString->setString(parentIdTag);\n\n    configuration_save();\n}\n\nvoid Reservation::clear() {\n    connectorIdInt->setInt(-1);\n    expiryDate = MIN_TIME;\n    expiryDateRawString->setString(\"\");\n    idTagString->setString(\"\");\n    reservationIdInt->setInt(-1);\n    parentIdTagString->setString(\"\");\n\n    configuration_save();\n}\n\n#endif //MO_ENABLE_RESERVATION\n"
  },
  {
    "path": "src/MicroOcpp/Model/Reservation/Reservation.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_RESERVATION_H\n#define MO_RESERVATION_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_RESERVATION\n\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#ifndef RESERVATION_FN\n#define RESERVATION_FN (MO_FILENAME_PREFIX \"reservations.jsn\")\n#endif\n\n#define MO_RESERVATION_CID_KEY \"cid_\"\n#define MO_RESERVATION_EXPDATE_KEY \"expdt_\"\n#define MO_RESERVATION_IDTAG_KEY \"idt_\"\n#define MO_RESERVATION_RESID_KEY \"rsvid_\"\n#define MO_RESERVATION_PARENTID_KEY \"pidt_\"\n\nnamespace MicroOcpp {\n\nclass Model;\n\nclass Reservation : public MemoryManaged {\nprivate:\n    Model& model;\n    const unsigned int slot;\n\n    std::shared_ptr<Configuration> connectorIdInt;\n    char connectorIdKey [sizeof(MO_RESERVATION_CID_KEY \"xxx\") + 1]; //\"xxx\" = placeholder for digits\n    std::shared_ptr<Configuration> expiryDateRawString;\n    char expiryDateRawKey [sizeof(MO_RESERVATION_EXPDATE_KEY \"xxx\") + 1];\n\n    Timestamp expiryDate = MIN_TIME;\n    std::shared_ptr<Configuration> idTagString;\n    char idTagKey [sizeof(MO_RESERVATION_IDTAG_KEY \"xxx\") + 1];\n    std::shared_ptr<Configuration> reservationIdInt;\n    char reservationIdKey [sizeof(MO_RESERVATION_RESID_KEY \"xxx\") + 1];\n    std::shared_ptr<Configuration> parentIdTagString;\n    char parentIdTagKey [sizeof(MO_RESERVATION_PARENTID_KEY \"xxx\") + 1];\n\npublic:\n    Reservation(Model& model, unsigned int slot);\n    Reservation(const Reservation&) = delete;\n    Reservation(Reservation&&) = delete;\n    Reservation& operator=(const Reservation&) = delete;\n\n    ~Reservation();\n\n    bool isActive(); //if this object contains a valid, unexpired reservation\n\n    bool matches(unsigned int connectorId);\n    bool matches(const char *idTag, const char *parentIdTag = nullptr); //idTag == parentIdTag == nullptr -> return True\n\n    int getConnectorId();\n    Timestamp& getExpiryDate();\n    const char *getIdTag();\n    int getReservationId();\n    const char *getParentIdTag();\n\n    void update(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag = nullptr);\n    void clear();\n};\n\n}\n\n#endif //MO_ENABLE_RESERVATION\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Reservation/ReservationService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_RESERVATION\n\n#include <MicroOcpp/Model/Reservation/ReservationService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Operations/CancelReservation.h>\n#include <MicroOcpp/Operations/ReserveNow.h>\n\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nReservationService::ReservationService(Context& context, unsigned int numConnectors) : MemoryManaged(\"v16.Reservation.ReservationService\"), context(context), maxReservations((int) numConnectors - 1), reservations(makeVector<std::unique_ptr<Reservation>>(getMemoryTag())) {\n    if (maxReservations > 0) {\n        reservations.reserve((size_t) maxReservations);\n        for (int i = 0; i < maxReservations; i++) {\n            reservations.emplace_back(new Reservation(context.getModel(), i));\n        }\n    }\n\n    reserveConnectorZeroSupportedBool = declareConfiguration<bool>(\"ReserveConnectorZeroSupported\", true, CONFIGURATION_VOLATILE, true);\n    \n    context.getOperationRegistry().registerOperation(\"CancelReservation\", [this] () {\n        return new Ocpp16::CancelReservation(*this);});\n    context.getOperationRegistry().registerOperation(\"ReserveNow\", [&context] () {\n        return new Ocpp16::ReserveNow(context.getModel());});\n}\n\nvoid ReservationService::loop() {\n    //check if to end reservations\n\n    for (auto& reservation : reservations) {\n        if (!reservation->isActive()) {\n            continue;\n        }\n\n        if (auto connector = context.getModel().getConnector(reservation->getConnectorId())) {\n\n            //check if connector went inoperative\n            auto cStatus = connector->getStatus();\n            if (cStatus == ChargePointStatus_Faulted || cStatus == ChargePointStatus_Unavailable) {\n                reservation->clear();\n                continue;\n            }\n\n            //check if other tx started at this connector (e.g. due to RemoteStartTransaction)\n            if (connector->getTransaction() && connector->getTransaction()->isAuthorized()) {\n                reservation->clear();\n                continue;\n            }\n        }\n\n        //check if tx with same idTag or reservationId has started\n        for (unsigned int cId = 1; cId < context.getModel().getNumConnectors(); cId++) {\n            auto& transaction = context.getModel().getConnector(cId)->getTransaction();\n            if (transaction && transaction->isAuthorized()) {\n                const char *cIdTag = transaction->getIdTag();\n                if (transaction->getReservationId() == reservation->getReservationId() || \n                        (cIdTag && !strcmp(cIdTag, reservation->getIdTag()))) {\n\n                    reservation->clear();\n                    break;\n                }\n            }\n        }\n    }\n}\n\nReservation *ReservationService::getReservation(unsigned int connectorId) {\n    if (connectorId == 0) {\n        MO_DBG_DEBUG(\"tried to fetch connectorId 0\");\n        return nullptr; //cannot fetch for connectorId 0 because multiple reservations are possible at a time\n    }\n\n    for (auto& reservation : reservations) {\n        if (reservation->isActive() && reservation->matches(connectorId)) {\n            return reservation.get();\n        }\n    }\n    \n    return nullptr;\n}\n\nReservation *ReservationService::getReservation(const char *idTag, const char *parentIdTag) {\n    if (idTag == nullptr) {\n        MO_DBG_ERR(\"invalid input\");\n        return nullptr;\n    }\n\n    Reservation *connectorReservation = nullptr;\n\n    for (auto& reservation : reservations) {\n        if (!reservation->isActive()) {\n            continue;\n        }\n\n        //TODO check for parentIdTag\n\n        if (reservation->matches(idTag, parentIdTag)) {\n            if (reservation->getConnectorId() == 0) {\n                return reservation.get(); //reservation at connectorId 0 has higher priority\n            } else {\n                connectorReservation = reservation.get();\n            }\n        }\n    }\n\n    return connectorReservation;\n}\n\nReservation *ReservationService::getReservation(unsigned int connectorId, const char *idTag, const char *parentIdTag) {\n\n    //is connector blocked by a reservation?\n    if (auto reservation = getReservation(connectorId)) {\n        //connector has reservation -> will always be the prevailing reservation\n        return reservation;\n    }\n\n    //is there any reservation at this charge point for idTag?\n    if (idTag) {\n        if (auto reservation = getReservation(idTag, parentIdTag)) {\n            //yes, can use reservation with different connectorId\n            return reservation;\n        }\n    }\n\n    if (reserveConnectorZeroSupportedBool && !reserveConnectorZeroSupportedBool->getBool()) {\n        //no connectorZero check - all done\n        MO_DBG_DEBUG(\"no reservation\");\n        return nullptr;\n    }\n\n    //connectorZero check\n    Reservation *blockingReservation = nullptr; //any reservation which blocks this connector now\n\n    //Check if there are enough free connectors to satisfy all reservations at connectorId 0\n    unsigned int unspecifiedReservations = 0;\n    for (auto& reservation : reservations) {\n        if (reservation->isActive() && reservation->getConnectorId() == 0) {\n            unspecifiedReservations++;\n            blockingReservation = reservation.get();\n        }\n    }\n\n    unsigned int availableCount = 0;\n    for (unsigned int cId = 1; cId < context.getModel().getNumConnectors(); cId++) {\n        if (cId == connectorId) {\n            //don't count this connector\n            continue;\n        }\n        if (auto connector = context.getModel().getConnector(cId)) {\n            if (connector->getStatus() == ChargePointStatus_Available) {\n                availableCount++;\n            }\n        }\n    }\n\n    if (availableCount >= unspecifiedReservations) {\n        //enough other connectors available to satisfy all reservations\n        return nullptr;\n    } else {\n        //not sufficient connectors for all reservations after this action\n        return blockingReservation;\n    }\n}\n\nReservation *ReservationService::getReservationById(int reservationId) {\n    for (auto& reservation : reservations) {\n        if (reservation->isActive() && reservation->getReservationId() == reservationId) {\n            return reservation.get();\n        }\n    }\n\n    return nullptr;\n}\n\nbool ReservationService::updateReservation(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag) {\n    if (auto reservation = getReservationById(reservationId)) {\n        if (getReservation(connectorId) && getReservation(connectorId) != reservation && getReservation(connectorId)->isActive()) {\n            MO_DBG_DEBUG(\"found blocking reservation at connectorId %u\", connectorId);\n            return false; //cannot transfer reservation to other connector with existing reservation\n        }\n        reservation->update(reservationId, connectorId, expiryDate, idTag, parentIdTag);\n        return true;\n    }\n\n// Alternative condition: avoids that one idTag can make two reservations at a time. The specification doesn't\n// mention that double-reservations should be possible but it seems to mean it. \n    if (auto reservation = getReservation(connectorId, nullptr, nullptr)) {\n//                payload[\"idTag\"],\n//                payload.containsKey(\"parentIdTag\") ? payload[\"parentIdTag\"] : nullptr)) {\n//    if (auto reservation = getReservation(payload[\"connectorId\"].as<int>())) {\n        MO_DBG_DEBUG(\"found blocking reservation at connectorId %u\", reservation->getConnectorId());\n        (void)reservation;\n        return false;\n    }\n\n    //update free reservation slot\n    for (auto& reservation : reservations) {\n        if (!reservation->isActive()) {\n            reservation->update(reservationId, connectorId, expiryDate, idTag, parentIdTag);\n            return true;\n        }\n    }\n\n    MO_DBG_ERR(\"error finding blocking reservation\");\n    return false;\n}\n\n#endif //MO_ENABLE_RESERVATION\n"
  },
  {
    "path": "src/MicroOcpp/Model/Reservation/ReservationService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_RESERVATIONSERVICE_H\n#define MO_RESERVATIONSERVICE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_RESERVATION\n\n#include <MicroOcpp/Model/Reservation/Reservation.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#include <memory>\n\nnamespace MicroOcpp {\n\nclass Context;\n\nclass ReservationService : public MemoryManaged {\nprivate:\n    Context& context;\n\n    const int maxReservations; // = number of physical connectors\n    Vector<std::unique_ptr<Reservation>> reservations;\n\n    std::shared_ptr<Configuration> reserveConnectorZeroSupportedBool;\n\npublic:\n    ReservationService(Context& context, unsigned int numConnectors);\n\n    void loop();\n\n    Reservation *getReservation(unsigned int connectorId); //by connectorId\n    Reservation *getReservation(const char *idTag, const char *parentIdTag = nullptr); //by idTag\n\n    /*\n     * Get prevailing reservation for a charging session authorized by idTag/parentIdTag at connectorId.\n     * returns nullptr if there is no reservation in question\n     * returns a reservation if applicable. Caller must check if idTag/parentIdTag match before starting a transaction\n     */\n    Reservation *getReservation(unsigned int connectorId, const char *idTag, const char *parentIdtag = nullptr);\n\n    Reservation *getReservationById(int reservationId);\n\n    bool updateReservation(int reservationId, unsigned int connectorId, Timestamp expiryDate, const char *idTag, const char *parentIdTag = nullptr);\n};\n\n}\n\n#endif //MO_ENABLE_RESERVATION\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Reset/ResetDefs.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_RESETDEFS_H\n#define MO_RESETDEFS_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\ntypedef enum ResetType {\n    ResetType_Immediate,\n    ResetType_OnIdle\n}   ResetType;\n\ntypedef enum ResetStatus {\n    ResetStatus_Accepted,\n    ResetStatus_Rejected,\n    ResetStatus_Scheduled\n}   ResetStatus;\n\n#endif //MO_ENABLE_V201\n#endif \n"
  },
  {
    "path": "src/MicroOcpp/Model/Reset/ResetService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Reset/ResetService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Operations/Reset.h>\n\n#include <MicroOcpp/Operations/Authorize.h>\n#include <MicroOcpp/Operations/StartTransaction.h>\n#include <MicroOcpp/Operations/StatusNotification.h>\n#include <MicroOcpp/Operations/StopTransaction.h>\n\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n\n#include <MicroOcpp/Debug.h>\n\n#ifndef MO_RESET_DELAY\n#define MO_RESET_DELAY 10000\n#endif\n\nusing namespace MicroOcpp;\n\nResetService::ResetService(Context& context)\n      : MemoryManaged(\"v16.Reset.ResetService\"), context(context) {\n\n    resetRetriesInt = declareConfiguration<int>(\"ResetRetries\", 2);\n    registerConfigurationValidator(\"ResetRetries\", VALIDATE_UNSIGNED_INT);\n\n    context.getOperationRegistry().registerOperation(\"Reset\", [&context] () {\n        return new Ocpp16::Reset(context.getModel());});\n}\n\nResetService::~ResetService() {\n\n}\n\nvoid ResetService::loop() {\n\n    if (outstandingResetRetries > 0 && mocpp_tick_ms() - t_resetRetry >= MO_RESET_DELAY) {\n        t_resetRetry = mocpp_tick_ms();\n        outstandingResetRetries--;\n        if (executeReset) {\n            MO_DBG_INFO(\"Reset device\");\n            executeReset(isHardReset);\n        } else {\n            MO_DBG_ERR(\"No Reset function set! Abort\");\n            outstandingResetRetries = 0;\n        }\n\n        if (outstandingResetRetries <= 0) {\n\n            MO_DBG_ERR(\"Reset device failure. Abort\");\n\n            ChargePointStatus cpStatus = ChargePointStatus_UNDEFINED;\n            if (context.getModel().getNumConnectors() > 0) {\n                cpStatus = context.getModel().getConnector(0)->getStatus();\n            }\n\n            auto statusNotification = makeRequest(new Ocpp16::StatusNotification(\n                        0,\n                        cpStatus, //will be determined in StatusNotification::initiate\n                        context.getModel().getClock().now(),\n                        \"ResetFailure\"));\n            statusNotification->setTimeout(60000);\n            context.initiateRequest(std::move(statusNotification));\n        }\n    }\n}\n\nvoid ResetService::setPreReset(std::function<bool(bool)> preReset) {\n    this->preReset = preReset;\n}\n\nstd::function<bool(bool)> ResetService::getPreReset() {\n    return this->preReset;\n}\n\nvoid ResetService::setExecuteReset(std::function<void(bool)> executeReset) {\n    this->executeReset = executeReset;\n}\n\nstd::function<void(bool)> ResetService::getExecuteReset() {\n    return this->executeReset;\n}\n\nvoid ResetService::initiateReset(bool isHard) {\n    isHardReset = isHard;\n    outstandingResetRetries = 1 + resetRetriesInt->getInt(); //one initial try + no. of retries\n    if (outstandingResetRetries > 5) {\n        MO_DBG_ERR(\"no. of reset trials exceeds 5\");\n        outstandingResetRetries = 5;\n    }\n    t_resetRetry = mocpp_tick_ms();\n}\n\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266))\nstd::function<void(bool isHard)> MicroOcpp::makeDefaultResetFn() {\n    return [] (bool isHard) {\n        MO_DBG_DEBUG(\"Perform ESP reset\");\n        ESP.restart();\n    };\n}\n#endif //MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266))\n\n#if MO_ENABLE_V201\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nResetService::ResetService(Context& context)\n      : MemoryManaged(\"v201.Reset.ResetService\"), context(context), evses(makeVector<Evse>(getMemoryTag())) {\n\n    auto varService = context.getModel().getVariableService();\n    resetRetriesInt = varService->declareVariable<int>(\"OCPPCommCtrlr\", \"ResetRetries\", 0);\n\n    context.getOperationRegistry().registerOperation(\"Reset\", [this] () {\n        return new Ocpp201::Reset(*this);});\n}\n\nResetService::~ResetService() {\n\n}\n\nResetService::Evse::Evse(Context& context, ResetService& resetService, unsigned int evseId) : context(context), resetService(resetService), evseId(evseId) {\n    auto varService = context.getModel().getVariableService();\n    varService->declareVariable<bool>(ComponentId(\"EVSE\", evseId >= 1 ? evseId : -1), \"AllowReset\", true, Variable::Mutability::ReadOnly, false);\n}\n\nvoid ResetService::Evse::loop() {\n\n    if (outstandingResetRetries && awaitTxStop) {\n\n        for (unsigned int eId = std::max(1U, evseId); eId < (evseId == 0 ? MO_NUM_EVSEID : evseId + 1); eId++) {\n            //If evseId > 0, execute this block one time for evseId. If evseId == 0, then iterate over all evseIds > 0\n\n            auto txService = context.getModel().getTransactionService();\n            if (txService && txService->getEvse(eId) && txService->getEvse(eId)->getTransaction()) {\n                auto tx = txService->getEvse(eId)->getTransaction();\n\n                if (!tx->stopped) {\n                    // wait until tx stopped\n                    return;\n                }\n            }\n        }\n\n        awaitTxStop = false;\n\n        MO_DBG_INFO(\"Reset - tx stopped\");\n        t_resetRetry = mocpp_tick_ms(); // wait for some more time until final reset\n    }\n\n    if (outstandingResetRetries && mocpp_tick_ms() - t_resetRetry >= MO_RESET_DELAY) {\n        t_resetRetry = mocpp_tick_ms();\n        outstandingResetRetries--;\n\n        MO_DBG_INFO(\"Reset device\");\n\n        bool success = executeReset();\n\n        if (success) {\n            outstandingResetRetries = 0;\n\n            if (evseId != 0) {\n                //Set this EVSE Available again\n                if (auto connector = context.getModel().getConnector(evseId)) {\n                    connector->setAvailabilityVolatile(true);\n                }\n            }\n        } else if (!outstandingResetRetries) {\n            MO_DBG_ERR(\"Reset device failure\");\n\n            if (evseId == 0) {\n                //Set all EVSEs Available again\n                for (unsigned int cId = 0; cId < context.getModel().getNumConnectors(); cId++) {\n                    auto connector = context.getModel().getConnector(cId);\n                    connector->setAvailabilityVolatile(true);\n                }\n            } else {\n                //Set only this EVSE Available\n                if (auto connector = context.getModel().getConnector(evseId)) {\n                    connector->setAvailabilityVolatile(true);\n                }\n            }\n        }\n    }\n}\n\nResetService::Evse *ResetService::getEvse(unsigned int evseId) {\n    for (size_t i = 0; i < evses.size(); i++) {\n        if (evses[i].evseId == evseId) {\n            return &evses[i];\n        }\n    }\n    return nullptr;\n}\n\nResetService::Evse *ResetService::getOrCreateEvse(unsigned int evseId) {\n    if (auto evse = getEvse(evseId)) {\n        return evse;\n    }\n\n    if (evseId >= MO_NUM_EVSEID) {\n        MO_DBG_ERR(\"evseId out of bound\");\n        return nullptr;\n    }\n\n    evses.emplace_back(context, *this, evseId);\n    return &evses.back();\n}\n\nvoid ResetService::loop() {\n    for (Evse& evse : evses) {\n        evse.loop();\n    }\n}\n\nvoid ResetService::setNotifyReset(std::function<bool(ResetType)> notifyReset, unsigned int evseId) {\n    Evse *evse = getOrCreateEvse(evseId);\n    if (!evse) {\n        MO_DBG_ERR(\"evseId not found\");\n        return;\n    }\n    evse->notifyReset = notifyReset;\n}\n\nstd::function<bool(ResetType)> ResetService::getNotifyReset(unsigned int evseId) {\n    Evse *evse = getOrCreateEvse(evseId);\n    if (!evse) {\n        MO_DBG_ERR(\"evseId not found\");\n        return nullptr;\n    }\n    return evse->notifyReset;\n}\n\nvoid ResetService::setExecuteReset(std::function<bool()> executeReset, unsigned int evseId) {\n    Evse *evse = getOrCreateEvse(evseId);\n    if (!evse) {\n        MO_DBG_ERR(\"evseId not found\");\n        return;\n    }\n    evse->executeReset = executeReset;\n}\n\nstd::function<bool()> ResetService::getExecuteReset(unsigned int evseId) {\n    Evse *evse = getOrCreateEvse(evseId);\n    if (!evse) {\n        MO_DBG_ERR(\"evseId not found\");\n        return nullptr;\n    }\n    return evse->executeReset;\n}\n\nResetStatus ResetService::initiateReset(ResetType type, unsigned int evseId) {\n    auto evse = getEvse(evseId);\n    if (!evse) {\n        MO_DBG_ERR(\"evseId not found\");\n        return ResetStatus_Rejected;\n    }\n\n    if (!evse->executeReset) {\n        MO_DBG_INFO(\"EVSE %u does not support Reset\", evseId);\n        return ResetStatus_Rejected;\n    }\n\n    //Check if EVSEs are ready for Reset\n    for (unsigned int eId = evseId; eId < (evseId == 0 ? MO_NUM_EVSEID : evseId + 1); eId++) {\n        //If evseId > 0, execute this block one time for evseId. If evseId == 0, then iterate over all evseIds\n\n        if (auto it = getEvse(eId)) {\n            if (it->notifyReset && !it->notifyReset(type)) {\n                MO_DBG_INFO(\"EVSE %u not able to Reset\", evseId);\n                return ResetStatus_Rejected;\n            }\n        }\n    }\n\n    //Set EVSEs Unavailable\n    if (evseId == 0) {\n        //Set all EVSEs Unavailable\n        for (unsigned int cId = 0; cId < context.getModel().getNumConnectors(); cId++) {\n            auto connector = context.getModel().getConnector(cId);\n            connector->setAvailabilityVolatile(false);\n        }\n    } else {\n        //Set this EVSE Unavailable\n        if (auto connector = context.getModel().getConnector(evseId)) {\n            connector->setAvailabilityVolatile(false);\n        }\n    }\n\n    bool scheduled = false;\n\n    //Tx-related behavior: if immediate Reset, stop txs; otherwise schedule Reset\n    for (unsigned int eId = std::max(1U, evseId); eId < (evseId == 0 ? MO_NUM_EVSEID : evseId + 1); eId++) {\n        //If evseId > 0, execute this block one time for evseId. If evseId == 0, then iterate over all evseIds > 0\n\n        auto txService = context.getModel().getTransactionService();\n        if (txService && txService->getEvse(eId) && txService->getEvse(eId)->getTransaction()) {\n            auto tx = txService->getEvse(eId)->getTransaction();\n            if (tx->active) {\n                //Tx in progress. Check behavior\n                if (type == ResetType_Immediate) {\n                    txService->getEvse(eId)->abortTransaction(Transaction::StoppedReason::ImmediateReset, TransactionEventTriggerReason::ResetCommand);\n                } else {\n                    scheduled = true;\n                    break;\n                }\n            }\n        }\n    }\n\n    //Actually engage Reset\n\n    if (resetRetriesInt->getInt() >= 5) {\n        MO_DBG_ERR(\"no. of reset trials exceeds 5\");\n        evse->outstandingResetRetries = 5;\n    } else {\n        evse->outstandingResetRetries = 1 + resetRetriesInt->getInt(); //one initial try + no. of retries\n    }\n    evse->t_resetRetry = mocpp_tick_ms();\n    evse->awaitTxStop = scheduled;\n\n    return scheduled ? ResetStatus_Scheduled : ResetStatus_Accepted;\n}\n\n} //namespace MicroOcpp\n} //namespace Ocpp201\n#endif //MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Model/Reset/ResetService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_RESETSERVICE_H\n#define MO_RESETSERVICE_H\n\n#include <functional>\n\n#include <MicroOcpp/Version.h>\n#include <MicroOcpp/Core/ConfigurationKeyValue.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Model/Reset/ResetDefs.h>\n\nnamespace MicroOcpp {\n\nclass Context;\n\nclass ResetService : public MemoryManaged {\nprivate:\n    Context& context;\n\n    std::function<bool(bool isHard)> preReset; //true: reset is possible; false: reject reset; Await: need more time to determine\n    std::function<void(bool isHard)> executeReset; //please disconnect WebSocket (MO remains initialized), shut down device and restart with normal initialization routine; on failure reconnect WebSocket\n    unsigned int outstandingResetRetries = 0; //0 = do not reset device\n    bool isHardReset = false;\n    unsigned long t_resetRetry;\n\n    std::shared_ptr<Configuration> resetRetriesInt;\n\npublic:\n    ResetService(Context& context);\n\n    ~ResetService();\n    \n    void loop();\n\n    void setPreReset(std::function<bool(bool isHard)> preReset);\n    std::function<bool(bool isHard)> getPreReset();\n\n    void setExecuteReset(std::function<void(bool isHard)> executeReset);\n    std::function<void(bool isHard)> getExecuteReset();\n\n    void initiateReset(bool isHard);\n};\n\n} //end namespace MicroOcpp\n\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266))\n\nnamespace MicroOcpp {\n\nstd::function<void(bool isHard)> makeDefaultResetFn();\n\n}\n\n#endif //MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266))\n\n#if MO_ENABLE_V201\n\nnamespace MicroOcpp {\n\nclass Variable;\n\nnamespace Ocpp201 {\n\nclass ResetService : public MemoryManaged {\nprivate:\n    Context& context;\n\n    struct Evse {\n        Context& context;\n        ResetService& resetService;\n        const unsigned int evseId;\n\n        std::function<bool(ResetType type)> notifyReset; //notify firmware about a Reset command. Return true if Reset is okay; false if Reset cannot be executed\n        std::function<bool()> executeReset; //execute Reset of connector. Return true if Reset will be executed; false if there is a failure to Reset\n\n        unsigned int outstandingResetRetries = 0; //0 = do not reset device\n        unsigned long t_resetRetry;\n\n        bool awaitTxStop = false;\n\n        Evse(Context& context, ResetService& resetService, unsigned int evseId);\n\n        void loop();\n    };\n\n    Vector<Evse> evses;\n    Evse *getEvse(unsigned int connectorId);\n    Evse *getOrCreateEvse(unsigned int connectorId);\n\n    Variable *resetRetriesInt = nullptr;\n\npublic:\n    ResetService(Context& context);\n    ~ResetService();\n    \n    void loop();\n\n    void setNotifyReset(std::function<bool(ResetType)> notifyReset, unsigned int evseId = 0);\n    std::function<bool(ResetType)> getNotifyReset(unsigned int evseId = 0);\n\n    void setExecuteReset(std::function<bool()> executeReset, unsigned int evseId = 0);\n    std::function<bool()> getExecuteReset(unsigned int evseId = 0);\n\n    ResetStatus initiateReset(ResetType type, unsigned int evseId = 0);\n};\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/SmartCharging/SmartChargingModel.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/SmartCharging/SmartChargingModel.h>\n#include <MicroOcpp/Debug.h>\n\n#include <string.h>\n#include <algorithm>\n\nusing namespace MicroOcpp;\n\nChargeRate MicroOcpp::chargeRate_min(const ChargeRate& a, const ChargeRate& b) {\n    ChargeRate res;\n    res.power = std::min(a.power, b.power);\n    res.current = std::min(a.current, b.current);\n    res.nphases = std::min(a.nphases, b.nphases);\n    return res;\n}\n\nChargingSchedule::ChargingSchedule() : MemoryManaged(\"v16.SmartCharging.SmartChargingModel\"), chargingSchedulePeriod{makeVector<ChargingSchedulePeriod>(getMemoryTag())} {\n\n}\n\nbool ChargingSchedule::calculateLimit(const Timestamp &t, const Timestamp &startOfCharging, ChargeRate& limit, Timestamp& nextChange) {\n    Timestamp basis = Timestamp(); //point in time to which schedule-related times are relative\n    switch (chargingProfileKind) {\n        case (ChargingProfileKindType::Absolute):\n            //check if schedule is not valid yet but begins in future\n            if (startSchedule > t) {\n                //not valid YET\n                nextChange = std::min(nextChange, startSchedule);\n                return false;\n            }\n            //If charging profile is absolute, prefer startSchedule as basis. If absent, use chargingStart instead. If absent, no\n            //behaviour is defined\n            if (startSchedule > MIN_TIME) {\n                basis = startSchedule;\n            } else if (startOfCharging > MIN_TIME && startOfCharging < t) {\n                basis = startOfCharging;\n            } else {\n                MO_DBG_ERR(\"Absolute profile, but neither startSchedule, nor start of charging are set. Undefined behavior, abort\");\n                return false;\n            }\n            break;\n        case (ChargingProfileKindType::Recurring):\n            if (recurrencyKind == RecurrencyKindType::Daily) {\n                basis = t - ((t - startSchedule) % (24 * 3600));\n                nextChange = std::min(nextChange, basis + (24 * 3600)); //constrain nextChange to basis + one day\n            } else if (recurrencyKind == RecurrencyKindType::Weekly) {\n                basis = t - ((t - startSchedule) % (7 * 24 * 3600));\n                nextChange = std::min(nextChange, basis + (7 * 24 * 3600));\n            } else {\n                MO_DBG_ERR(\"Recurring ChargingProfile but no RecurrencyKindType set. Undefined behavior, assume 'Daily'\");\n                basis = t - ((t - startSchedule) % (24 * 3600));\n                nextChange = std::min(nextChange, basis + (24 * 3600));\n            }\n            break;\n        case (ChargingProfileKindType::Relative):\n            //assumed, that it is relative to start of charging\n            //start of charging must be before t or equal to t\n            if (startOfCharging > t) {\n                //Relative charging profiles only work with a currently active charging session which is not the case here\n                return false;\n            }\n            basis = startOfCharging;\n            break;\n    }\n\n    if (t < basis) { //check for error\n        MO_DBG_ERR(\"time basis is smaller than t, but t must be >= basis\");\n        return false;\n    }\n    \n    int t_toBasis = t - basis;\n\n    if (duration > 0){\n        //duration is set\n\n        //check if duration is exceeded and if yes, abort limit algorithm\n        //if no, the duration is an upper limit for the validity of the schedule\n        if (t_toBasis >= duration) { //\"duration\" is given relative to basis\n            return false;\n        } else {\n            nextChange = std::min(nextChange, basis + duration);\n        }\n    }\n\n    /*\n    * Work through the ChargingProfilePeriods here. If the right period was found, assign the limit parameter from it\n    * and make nextChange equal the beginning of the following period. If the right period is the last one, nextChange\n    * will remain the time determined before.\n    */\n    float limit_res = -1.0f; //If limit_res is still -1 after the loop, the limit algorithm failed\n    int nphases_res = -1;\n    for (auto period = chargingSchedulePeriod.begin(); period != chargingSchedulePeriod.end(); period++) {\n        if (period->startPeriod > t_toBasis) {\n            // found the first period that comes after t_toBasis.\n            nextChange = basis + period->startPeriod;\n            nextChange = std::min(nextChange, basis + period->startPeriod);\n            break; //The currently valid limit was set the iteration before\n        }\n        limit_res = period->limit;\n        nphases_res = period->numberPhases;\n    }\n    \n    if (limit_res >= 0.0f) {\n        limit_res = std::max(limit_res, minChargingRate);\n\n        if (chargingRateUnit == ChargingRateUnitType::Amp) {\n            limit.current = limit_res;\n        } else {\n            limit.power = limit_res;\n        }\n\n        limit.nphases = nphases_res;\n        return true;\n    } else {\n        return false; //No limit was found. Either there is no ChargingProfilePeriod, or each period begins after t_toBasis\n    }\n}\n\nbool ChargingSchedule::toJson(JsonDoc& doc) {\n    size_t capacity = 0;\n    capacity += JSON_OBJECT_SIZE(5); //no of fields of ChargingSchedule\n    capacity += JSONDATE_LENGTH + 1; //startSchedule\n    capacity += JSON_ARRAY_SIZE(chargingSchedulePeriod.size()) + chargingSchedulePeriod.size() * JSON_OBJECT_SIZE(3);\n\n    doc = initJsonDoc(\"v16.SmartCharging.ChargingSchedule\", capacity);\n    if (duration >= 0) {\n        doc[\"duration\"] = duration;\n    }\n    char startScheduleJson [JSONDATE_LENGTH + 1] = {'\\0'};\n    startSchedule.toJsonString(startScheduleJson, JSONDATE_LENGTH + 1);\n    doc[\"startSchedule\"] = startScheduleJson;\n    doc[\"chargingRateUnit\"] = chargingRateUnit == (ChargingRateUnitType::Amp) ? \"A\" : \"W\";\n    JsonArray periodArray = doc.createNestedArray(\"chargingSchedulePeriod\");\n    for (auto period = chargingSchedulePeriod.begin(); period != chargingSchedulePeriod.end(); period++) {\n        JsonObject entry = periodArray.createNestedObject();\n        entry[\"startPeriod\"] = period->startPeriod;\n        entry[\"limit\"] = period->limit;\n        if (period->numberPhases != 3) {\n            entry[\"numberPhases\"] = period->numberPhases;\n        }\n    }\n    if (minChargingRate >= 0) {\n        doc[\"minChargeRate\"] = minChargingRate;\n    }\n\n    return true;\n}\n\nvoid ChargingSchedule::printSchedule(){\n\n    char tmp[JSONDATE_LENGTH + 1] = {'\\0'};\n    startSchedule.toJsonString(tmp, JSONDATE_LENGTH + 1);\n\n    MO_CONSOLE_PRINTF(\"   > CHARGING SCHEDULE:\\n\" \\\n                \"       > duration: %i\\n\" \\\n                \"       > startSchedule: %s\\n\" \\\n                \"       > chargingRateUnit: %s\\n\" \\\n                \"       > minChargingRate: %f\\n\",\n                duration,\n                tmp,\n                chargingRateUnit == (ChargingRateUnitType::Amp) ? \"A\" :\n                    chargingRateUnit == (ChargingRateUnitType::Watt) ? \"W\" : \"Error\",\n                minChargingRate);\n\n    for (auto period = chargingSchedulePeriod.begin(); period != chargingSchedulePeriod.end(); period++) {\n        MO_CONSOLE_PRINTF(\"       > CHARGING SCHEDULE PERIOD:\\n\" \\\n                \"           > startPeriod: %i\\n\" \\\n                \"           > limit: %f\\n\" \\\n                \"           > numberPhases: %i\\n\",\n                period->startPeriod,\n                period->limit,\n                period->numberPhases);\n    }\n}\n\nChargingProfile::ChargingProfile() : MemoryManaged(\"v16.SmartCharging.ChargingProfile\") {\n\n}\n\nbool ChargingProfile::calculateLimit(const Timestamp &t, const Timestamp &startOfCharging, ChargeRate& limit, Timestamp& nextChange){\n    if (t > validTo && validTo > MIN_TIME) {\n        return false; //no limit defined\n    }\n    if (t < validFrom) {\n        nextChange = std::min(nextChange, validFrom);\n        return false; //no limit defined\n    }\n\n    return chargingSchedule.calculateLimit(t, startOfCharging, limit, nextChange);\n}\n\nbool ChargingProfile::calculateLimit(const Timestamp &t, ChargeRate& limit, Timestamp& nextChange){\n    return calculateLimit(t, MIN_TIME, limit, nextChange);\n}\n\nint ChargingProfile::getChargingProfileId() {\n    return chargingProfileId;\n}\n\nint ChargingProfile::getTransactionId() {\n    return transactionId;\n}\n\nint ChargingProfile::getStackLevel(){\n    return stackLevel;\n}\n  \nChargingProfilePurposeType ChargingProfile::getChargingProfilePurpose(){\n    return chargingProfilePurpose;\n}\n\nbool ChargingProfile::toJson(JsonDoc& doc) {\n    \n    auto chargingScheduleDoc = initJsonDoc(\"v16.SmartCharging.ChargingSchedule\");\n    if (!chargingSchedule.toJson(chargingScheduleDoc)) {\n        return false;\n    }\n\n    doc = initJsonDoc(\"v16.SmartCharging.ChargingProfile\",\n            JSON_OBJECT_SIZE(9) + //no. of fields in ChargingProfile\n            2 * (JSONDATE_LENGTH + 1) + //validFrom and validTo\n            chargingScheduleDoc.memoryUsage()); //nested JSON object\n\n    doc[\"chargingProfileId\"] = chargingProfileId;\n    if (transactionId >= 0) {\n        doc[\"transactionId\"] = transactionId;\n    }\n    doc[\"stackLevel\"] = stackLevel;\n\n    switch (chargingProfilePurpose) {\n        case (ChargingProfilePurposeType::ChargePointMaxProfile):\n            doc[\"chargingProfilePurpose\"] = \"ChargePointMaxProfile\";\n            break;\n        case (ChargingProfilePurposeType::TxDefaultProfile):\n            doc[\"chargingProfilePurpose\"] = \"TxDefaultProfile\";\n            break;\n        case (ChargingProfilePurposeType::TxProfile):\n            doc[\"chargingProfilePurpose\"] = \"TxProfile\";\n            break;\n    }\n\n    switch (chargingProfileKind) {\n        case (ChargingProfileKindType::Absolute):\n            doc[\"chargingProfileKind\"] = \"Absolute\";\n            break;\n        case (ChargingProfileKindType::Recurring):\n            doc[\"chargingProfileKind\"] = \"Recurring\";\n            break;\n        case (ChargingProfileKindType::Relative):\n            doc[\"chargingProfileKind\"] = \"Relative\";\n            break;\n    }\n\n    switch (recurrencyKind) {\n        case (RecurrencyKindType::Daily):\n            doc[\"recurrencyKind\"] = \"Daily\";\n            break;\n        case (RecurrencyKindType::Weekly):\n            doc[\"recurrencyKind\"] = \"Weekly\";\n            break;\n        default:\n            break;\n    }\n\n    char timeStr [JSONDATE_LENGTH + 1] = {'\\0'};\n\n    if (validFrom > MIN_TIME) {\n        if (!validFrom.toJsonString(timeStr, JSONDATE_LENGTH + 1)) {\n            MO_DBG_ERR(\"serialization error\");\n            return false;\n        }\n        doc[\"validFrom\"] = timeStr;\n    }\n\n    if (validTo > MIN_TIME) {\n        if (!validTo.toJsonString(timeStr, JSONDATE_LENGTH + 1)) {\n            MO_DBG_ERR(\"serialization error\");\n            return false;\n        }\n        doc[\"validTo\"] = timeStr;\n    }\n\n    doc[\"chargingSchedule\"] = chargingScheduleDoc;\n\n    return true;\n}\n\nvoid ChargingProfile::printProfile(){\n\n    char tmp[JSONDATE_LENGTH + 1] = {'\\0'};\n    validFrom.toJsonString(tmp, JSONDATE_LENGTH + 1);\n    char tmp2[JSONDATE_LENGTH + 1] = {'\\0'};\n    validTo.toJsonString(tmp2, JSONDATE_LENGTH + 1);\n\n    MO_CONSOLE_PRINTF(\"   > CHARGING PROFILE:\\n\" \\\n                \"   > chargingProfileId: %i\\n\" \\\n                \"   > transactionId: %i\\n\" \\\n                \"   > stackLevel: %i\\n\" \\\n                \"   > chargingProfilePurpose: %s\\n\",\n                chargingProfileId,\n                transactionId,\n                stackLevel,\n                chargingProfilePurpose == (ChargingProfilePurposeType::ChargePointMaxProfile) ? \"ChargePointMaxProfile\" :\n                    chargingProfilePurpose == (ChargingProfilePurposeType::TxDefaultProfile) ? \"TxDefaultProfile\" :\n                    chargingProfilePurpose == (ChargingProfilePurposeType::TxProfile) ? \"TxProfile\" : \"Error\"\n                );\n    MO_CONSOLE_PRINTF(\n                \"   > chargingProfileKind: %s\\n\" \\\n                \"   > recurrencyKind: %s\\n\" \\\n                \"   > validFrom: %s\\n\" \\\n                \"   > validTo: %s\\n\",\n                chargingProfileKind == (ChargingProfileKindType::Absolute) ? \"Absolute\" :\n                    chargingProfileKind == (ChargingProfileKindType::Recurring) ? \"Recurring\" :\n                    chargingProfileKind == (ChargingProfileKindType::Relative) ? \"Relative\" : \"Error\",\n                recurrencyKind == (RecurrencyKindType::Daily) ? \"Daily\" :\n                    recurrencyKind == (RecurrencyKindType::Weekly) ? \"Weekly\" :\n                    recurrencyKind == (RecurrencyKindType::NOT_SET) ? \"NOT_SET\" : \"Error\",\n                tmp,\n                tmp2\n                );\n\n    chargingSchedule.printSchedule();\n}\n\nnamespace MicroOcpp {\n\nbool loadChargingSchedulePeriod(JsonObject& json, ChargingSchedulePeriod& out) {\n    int startPeriod = json[\"startPeriod\"] | -1;\n    if (startPeriod >= 0) {\n        out.startPeriod = startPeriod;\n    } else {\n        MO_DBG_WARN(\"format violation\");\n        return false;\n    }\n\n    float limit = json[\"limit\"] | -1.f;\n    if (limit >= 0.f) {\n        out.limit = limit;\n    } else {\n        MO_DBG_WARN(\"format violation\");\n        return false;\n    }\n\n    if (json.containsKey(\"numberPhases\")) {\n        int numberPhases = json[\"numberPhases\"];\n        if (numberPhases >= 0 && numberPhases <= 3) {\n            out.numberPhases = numberPhases;\n        } else {\n            MO_DBG_WARN(\"format violation\");\n            return false;\n        }\n    }\n\n    return true;\n}\n\n} //end namespace MicroOcpp\n\nstd::unique_ptr<ChargingProfile> MicroOcpp::loadChargingProfile(JsonObject& json) {\n    auto res = std::unique_ptr<ChargingProfile>(new ChargingProfile());\n\n    int chargingProfileId = json[\"chargingProfileId\"] | -1;\n    if (chargingProfileId >= 0) {\n        res->chargingProfileId = chargingProfileId;\n    } else {\n        MO_DBG_WARN(\"format violation\");\n        return nullptr;\n    }\n\n    int transactionId = json[\"transactionId\"] | -1;\n    if (transactionId >= 0) {\n        res->transactionId = transactionId;\n    }\n\n    int stackLevel = json[\"stackLevel\"] | -1;\n    if (stackLevel >= 0 && stackLevel <= MO_ChargeProfileMaxStackLevel) {\n        res->stackLevel = stackLevel;\n    } else {\n        MO_DBG_WARN(\"format violation\");\n        return nullptr;\n    }\n\n    const char *chargingProfilePurposeStr = json[\"chargingProfilePurpose\"] | \"Invalid\";\n    if (!strcmp(chargingProfilePurposeStr, \"ChargePointMaxProfile\")) {\n        res->chargingProfilePurpose = ChargingProfilePurposeType::ChargePointMaxProfile;\n    } else if (!strcmp(chargingProfilePurposeStr, \"TxDefaultProfile\")) {\n        res->chargingProfilePurpose = ChargingProfilePurposeType::TxDefaultProfile;\n    } else if (!strcmp(chargingProfilePurposeStr, \"TxProfile\")) {\n        res->chargingProfilePurpose = ChargingProfilePurposeType::TxProfile;\n    } else {\n        MO_DBG_WARN(\"format violation\");\n        return nullptr;\n    }\n\n    const char *chargingProfileKindStr = json[\"chargingProfileKind\"] | \"Invalid\";\n    if (!strcmp(chargingProfileKindStr, \"Absolute\")) {\n        res->chargingProfileKind = ChargingProfileKindType::Absolute;\n    } else if (!strcmp(chargingProfileKindStr, \"Recurring\")) {\n        res->chargingProfileKind = ChargingProfileKindType::Recurring;\n    } else if (!strcmp(chargingProfileKindStr, \"Relative\")) {\n        res->chargingProfileKind = ChargingProfileKindType::Relative;\n    } else {\n        MO_DBG_WARN(\"format violation\");\n        return nullptr;\n    }\n\n    const char *recurrencyKindStr = json[\"recurrencyKind\"] | \"Invalid\";\n    if (!strcmp(recurrencyKindStr, \"Daily\")) {\n        res->recurrencyKind = RecurrencyKindType::Daily;\n    } else if (!strcmp(recurrencyKindStr, \"Weekly\")) {\n        res->recurrencyKind = RecurrencyKindType::Weekly;\n    }\n\n    MO_DBG_DEBUG(\"Deserialize JSON: chargingProfileId=%i, chargingProfilePurpose=%s, recurrencyKind=%s\", chargingProfileId, chargingProfilePurposeStr, recurrencyKindStr);\n\n    if (json.containsKey(\"validFrom\")) {\n        if (!res->validFrom.setTime(json[\"validFrom\"] | \"Invalid\")) {\n            //non-success\n            MO_DBG_WARN(\"datetime format violation, expect format like 2022-02-01T20:53:32.486Z\");\n            return nullptr;\n        }\n    } else {\n        res->validFrom = MIN_TIME;\n    }\n\n    if (json.containsKey(\"validTo\")) {\n        if (!res->validTo.setTime(json[\"validTo\"] | \"Invalid\")) {\n            //non-success\n            MO_DBG_WARN(\"datetime format violation, expect format like 2022-02-01T20:53:32.486Z\");\n            return nullptr;\n        }\n    } else {\n        res->validTo = MIN_TIME;\n    }\n\n    JsonObject scheduleJson = json[\"chargingSchedule\"];\n    ChargingSchedule& schedule = res->chargingSchedule;\n    auto success = loadChargingSchedule(scheduleJson, schedule);\n    if (!success) {\n        return nullptr;\n    }\n\n    //duplicate some fields to chargingSchedule to simplify the max charge rate calculation\n    schedule.chargingProfileKind = res->chargingProfileKind;\n    schedule.recurrencyKind = res->recurrencyKind;\n\n    return res;\n}\n\nbool MicroOcpp::loadChargingSchedule(JsonObject& json, ChargingSchedule& out) {\n    if (json.containsKey(\"duration\")) {\n        int duration = json[\"duration\"] | -1;\n        if (duration >= 0) {\n            out.duration = duration;\n        } else {\n            MO_DBG_WARN(\"format violation\");\n            return false;\n        }\n    }\n\n    if (json.containsKey(\"startSchedule\")) {\n        if (!out.startSchedule.setTime(json[\"startSchedule\"] | \"Invalid\")) {\n            //non-success\n            MO_DBG_WARN(\"datetime format violation, expect format like 2022-02-01T20:53:32.486Z\");\n            return false;\n        }\n    } else {\n        out.startSchedule = MIN_TIME;\n    }\n\n    const char *unit = json[\"chargingRateUnit\"] | \"_Undefined\";\n    if (unit[0] == 'a' || unit[0] == 'A') {\n        out.chargingRateUnit = ChargingRateUnitType::Amp;\n    } else if (unit[0] == 'w' || unit[0] == 'W') {\n        out.chargingRateUnit = ChargingRateUnitType::Watt;\n    } else {\n        MO_DBG_WARN(\"format violation\");\n        return false;\n    }\n    \n    JsonArray periodJsonArray = json[\"chargingSchedulePeriod\"];\n    if (periodJsonArray.size() < 1) {\n        MO_DBG_WARN(\"format violation\");\n        return false;\n    }\n\n    if (periodJsonArray.size() > MO_ChargingScheduleMaxPeriods) {\n        MO_DBG_WARN(\"exceed ChargingScheduleMaxPeriods\");\n        return false;\n    }\n\n    for (JsonObject periodJson : periodJsonArray) {\n        out.chargingSchedulePeriod.emplace_back();\n        if (!loadChargingSchedulePeriod(periodJson, out.chargingSchedulePeriod.back())) {\n            return false;\n        }\n    }\n    \n    if (json.containsKey(\"minChargingRate\")) {\n        float minChargingRate = json[\"minChargingRate\"];\n        if (minChargingRate >= 0.f) {\n            out.minChargingRate = minChargingRate;\n        } else {\n            MO_DBG_WARN(\"format violation\");\n            return false;\n        }\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/SmartCharging/SmartChargingModel.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef SMARTCHARGINGMODEL_H\n#define SMARTCHARGINGMODEL_H\n\n#ifndef MO_ChargeProfileMaxStackLevel\n#define MO_ChargeProfileMaxStackLevel 8\n#endif\n\n#ifndef MO_ChargingScheduleMaxPeriods\n#define MO_ChargingScheduleMaxPeriods 24\n#endif\n\n#ifndef MO_MaxChargingProfilesInstalled\n#define MO_MaxChargingProfilesInstalled 10\n#endif\n\n#include <memory>\n#include <limits>\n\n#include <ArduinoJson.h>\n\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nenum class ChargingProfilePurposeType {\n    ChargePointMaxProfile,\n    TxDefaultProfile,\n    TxProfile\n};\n\nenum class ChargingProfileKindType {\n    Absolute,\n    Recurring,\n    Relative\n};\n\nenum class RecurrencyKindType {\n    NOT_SET, //not part of OCPP 1.6\n    Daily,\n    Weekly\n};\n\nenum class ChargingRateUnitType {\n    Watt,\n    Amp\n};\n\nstruct ChargeRate {\n    float power = std::numeric_limits<float>::max();\n    float current = std::numeric_limits<float>::max();\n    int nphases = std::numeric_limits<int>::max();\n\n    bool operator==(const ChargeRate& rhs) {\n        return power == rhs.power &&\n               current == rhs.current &&\n               nphases == rhs.nphases;\n    }\n    bool operator!=(const ChargeRate& rhs) {\n        return !(*this == rhs);\n    }\n};\n\n//returns a new vector with the minimum of each component\nChargeRate chargeRate_min(const ChargeRate& a, const ChargeRate& b);\n\nclass ChargingSchedulePeriod {\npublic:\n    int startPeriod;\n    float limit;\n    int numberPhases = 3;\n};\n\nclass ChargingSchedule : public MemoryManaged {\npublic:\n    int duration = -1;\n    Timestamp startSchedule;\n    ChargingRateUnitType chargingRateUnit;\n    Vector<ChargingSchedulePeriod> chargingSchedulePeriod;\n    float minChargingRate = -1.0f;\n\n    ChargingProfileKindType chargingProfileKind; //copied from ChargingProfile to increase cohesion of limit algorithms\n    RecurrencyKindType recurrencyKind = RecurrencyKindType::NOT_SET; //copied from ChargingProfile to increase cohesion of limit algorithms\n\n    ChargingSchedule();\n\n    /**\n     * limit: output parameter\n     * nextChange: output parameter\n     * \n     * returns if charging profile defines a limit at time t\n     *       if true, limit and nextChange will be set according to this Schedule\n     *       if false, only nextChange will be set\n     */\n    bool calculateLimit(const Timestamp &t, const Timestamp &startOfCharging, ChargeRate& limit, Timestamp& nextChange);\n\n    bool toJson(JsonDoc& out);\n\n    /*\n    * print on console\n    */\n    void printSchedule();\n};\n\nclass ChargingProfile : public MemoryManaged {\npublic:\n    int chargingProfileId = -1;\n    int transactionId = -1;\n    int stackLevel = 0;\n    ChargingProfilePurposeType chargingProfilePurpose {ChargingProfilePurposeType::TxProfile};\n    ChargingProfileKindType chargingProfileKind {ChargingProfileKindType::Relative}; //copied to ChargingSchedule to increase cohesion of limit algorithms\n    RecurrencyKindType recurrencyKind {RecurrencyKindType::NOT_SET}; // copied to ChargingSchedule to increase cohesion\n    Timestamp validFrom;\n    Timestamp validTo;\n    ChargingSchedule chargingSchedule;\n\n    ChargingProfile();\n\n    /**\n     * limit: output parameter\n     * nextChange: output parameter\n     * \n     * returns if charging profile defines a limit at time t\n     *       if true, limit and nextChange will be set according to this Schedule\n     *       if false, only nextChange will be set\n     */\n    bool calculateLimit(const Timestamp &t, const Timestamp &startOfCharging, ChargeRate& limit, Timestamp& nextChange);\n\n    /*\n    * Simpler function if startOfCharging is not available. Caution: This likely will differ from function with startOfCharging\n    */\n    bool calculateLimit(const Timestamp &t, ChargeRate& limit, Timestamp& nextChange);\n\n    int getChargingProfileId();\n    int getTransactionId();\n    int getStackLevel();\n    \n    ChargingProfilePurposeType getChargingProfilePurpose();\n\n    bool toJson(JsonDoc& out);\n\n    /*\n    * print on console\n    */\n    void printProfile();\n};\n\nstd::unique_ptr<ChargingProfile> loadChargingProfile(JsonObject& json);\n\nbool loadChargingSchedule(JsonObject& json, ChargingSchedule& out);\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/SmartCharging/SmartChargingService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/SmartCharging/SmartChargingService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Operations/ClearChargingProfile.h>\n#include <MicroOcpp/Operations/GetCompositeSchedule.h>\n#include <MicroOcpp/Operations/SetChargingProfile.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace::MicroOcpp;\n\nSmartChargingConnector::SmartChargingConnector(Model& model, std::shared_ptr<FilesystemAdapter> filesystem, unsigned int connectorId, ProfileStack& ChargePointMaxProfile, ProfileStack& ChargePointTxDefaultProfile) :\n        MemoryManaged(\"v16.SmartCharging.SmartChargingConnector\"), model(model), filesystem{filesystem}, connectorId{connectorId}, ChargePointMaxProfile(ChargePointMaxProfile), ChargePointTxDefaultProfile(ChargePointTxDefaultProfile) {\n    \n}\n\nSmartChargingConnector::~SmartChargingConnector() {\n\n}\n\n/*\n * limitOut: the calculated maximum charge rate at the moment, or the default value if no limit is defined\n * validToOut: The begin of the next SmartCharging restriction after time t\n */\nvoid SmartChargingConnector::calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut) {\n\n    //initialize output parameters with the default values\n    limitOut = ChargeRate();\n    validToOut = MAX_TIME;\n\n    bool txLimitDefined = false;\n    ChargeRate txLimit;\n\n    //first, check if TxProfile is defined and limits charging\n    for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) {\n        if (TxProfile[i] && ((trackTxRmtProfileId >= 0 && trackTxRmtProfileId == TxProfile[i]->getChargingProfileId()) ||\n                                TxProfile[i]->getTransactionId() < 0 ||\n                                trackTxId == TxProfile[i]->getTransactionId())) {\n            ChargeRate crOut;\n            bool defined = TxProfile[i]->calculateLimit(t, trackTxStart, crOut, validToOut);\n            if (defined) {\n                txLimitDefined = true;\n                txLimit = crOut;\n                break;\n            }\n        }\n    }\n\n    //if no TxProfile limits charging, check the TxDefaultProfiles for this connector\n    if (!txLimitDefined && trackTxStart < MAX_TIME) {\n        for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) {\n            if (TxDefaultProfile[i]) {\n                ChargeRate crOut;\n                bool defined = TxDefaultProfile[i]->calculateLimit(t, trackTxStart, crOut, validToOut);\n                if (defined) {\n                    txLimitDefined = true;\n                    txLimit = crOut;\n                    break;\n                }\n            }\n        }\n    }\n\n    //if no appropriate TxDefaultProfile is set for this connector, search in the general TxDefaultProfiles\n    if (!txLimitDefined && trackTxStart < MAX_TIME) {\n        for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) {\n            if (ChargePointTxDefaultProfile[i]) {\n                ChargeRate crOut;\n                bool defined = ChargePointTxDefaultProfile[i]->calculateLimit(t, trackTxStart, crOut, validToOut);\n                if (defined) {\n                    txLimitDefined = true;\n                    txLimit = crOut;\n                    break;\n                }\n            }\n        }\n    }\n\n    ChargeRate cpLimit;\n\n    //the calculated maximum charge rate is also limited by the ChargePointMaxProfiles\n    for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) {\n        if (ChargePointMaxProfile[i]) {\n            ChargeRate crOut;\n            bool defined = ChargePointMaxProfile[i]->calculateLimit(t, trackTxStart, crOut, validToOut);\n            if (defined) {\n                cpLimit = crOut;\n                break;\n            }\n        }\n    }\n\n    //apply ChargePointMaxProfile value to calculated limit\n    limitOut = chargeRate_min(txLimit, cpLimit);\n}\n\nvoid SmartChargingConnector::trackTransaction() {\n\n    Transaction *tx = nullptr;\n    if (model.getConnector(connectorId)) {\n        tx = model.getConnector(connectorId)->getTransaction().get();\n    }\n\n    bool update = false;\n\n    if (tx) {\n        if (tx->getTxProfileId() != trackTxRmtProfileId) {\n            update = true;\n            trackTxRmtProfileId = tx->getTxProfileId();\n        }\n        if (tx->getStartSync().isRequested() && tx->getStartTimestamp() != trackTxStart) {\n            update = true;\n            trackTxStart = tx->getStartTimestamp();\n        }\n        if (tx->getStartSync().isConfirmed() && tx->getTransactionId() != trackTxId) {\n            update = true;\n            trackTxId = tx->getTransactionId();\n        }\n    } else {\n        //check if transaction has just been completed\n        if (trackTxRmtProfileId >= 0 || trackTxStart < MAX_TIME || trackTxId >= 0) {\n            //yes, clear data\n            update = true;\n            trackTxRmtProfileId = -1;\n            trackTxStart = MAX_TIME;\n            trackTxId = -1;\n\n            clearChargingProfile([this] (int, int connectorId, ChargingProfilePurposeType purpose, int) {\n                return purpose == ChargingProfilePurposeType::TxProfile &&\n                       (int) this->connectorId == connectorId;\n            });\n        }\n    }\n\n    if (update) {\n        nextChange = model.getClock().now(); //will refresh limit calculation\n    }\n}\n\nvoid SmartChargingConnector::loop(){\n\n    trackTransaction();\n\n    /**\n     * check if to call onLimitChange\n     */\n    auto& tnow = model.getClock().now();\n\n    if (tnow >= nextChange){\n\n        ChargeRate limit;\n        nextChange = MAX_TIME; //reset nextChange to default value and refresh it\n\n        calculateLimit(tnow, limit, nextChange);\n\n#if MO_DBG_LEVEL >= MO_DL_INFO\n        {\n            char timestamp1[JSONDATE_LENGTH + 1] = {'\\0'};\n            tnow.toJsonString(timestamp1, JSONDATE_LENGTH + 1);\n            char timestamp2[JSONDATE_LENGTH + 1] = {'\\0'};\n            nextChange.toJsonString(timestamp2, JSONDATE_LENGTH + 1);\n            MO_DBG_INFO(\"New limit for connector %u, scheduled at = %s, nextChange = %s, limit = {%.1f, %.1f, %i}\",\n                                connectorId,\n                                timestamp1, timestamp2,\n                                limit.power != std::numeric_limits<float>::max() ? limit.power : -1.f,\n                                limit.current != std::numeric_limits<float>::max() ? limit.current : -1.f,\n                                limit.nphases != std::numeric_limits<int>::max() ? limit.nphases : -1);\n        }\n#endif\n\n        if (trackLimitOutput != limit) {\n            if (limitOutput) {\n\n                limitOutput(\n                    limit.power != std::numeric_limits<float>::max() ? limit.power : -1.f,\n                    limit.current != std::numeric_limits<float>::max() ? limit.current : -1.f,\n                    limit.nphases != std::numeric_limits<int>::max() ? limit.nphases : -1);\n                trackLimitOutput = limit;\n            }\n        }\n    }\n}\n\nvoid SmartChargingConnector::setSmartChargingOutput(std::function<void(float,float,int)> limitOutput) {\n    if (this->limitOutput) {\n        MO_DBG_WARN(\"replacing existing SmartChargingOutput\");\n    }\n    this->limitOutput = limitOutput;\n}\n\nChargingProfile *SmartChargingConnector::updateProfiles(std::unique_ptr<ChargingProfile> chargingProfile) {\n    \n    int stackLevel = chargingProfile->getStackLevel(); //already validated\n    \n    switch (chargingProfile->getChargingProfilePurpose()) {\n        case (ChargingProfilePurposeType::ChargePointMaxProfile):\n            break;\n        case (ChargingProfilePurposeType::TxDefaultProfile):\n            TxDefaultProfile[stackLevel] = std::move(chargingProfile);\n            return TxDefaultProfile[stackLevel].get();\n        case (ChargingProfilePurposeType::TxProfile):\n            TxProfile[stackLevel] = std::move(chargingProfile);\n            return TxProfile[stackLevel].get();\n    }\n\n    MO_DBG_ERR(\"invalid args\");\n    return nullptr;\n}\n\nvoid SmartChargingConnector::notifyProfilesUpdated() {\n    nextChange = model.getClock().now();\n}\n\nbool SmartChargingConnector::clearChargingProfile(const std::function<bool(int, int, ChargingProfilePurposeType, int)> filter) {\n    bool found = false;\n\n    ProfileStack *profileStacks [] = {&TxProfile, &TxDefaultProfile};\n\n    for (auto stack : profileStacks) {\n        for (size_t iLevel = 0; iLevel < stack->size(); iLevel++) {\n            if (auto& profile = stack->at(iLevel)) {\n                if (profile && filter(profile->getChargingProfileId(), connectorId, profile->getChargingProfilePurpose(), iLevel)) {\n                    found = true;\n                    SmartChargingServiceUtils::removeProfile(filesystem, connectorId, profile->getChargingProfilePurpose(), iLevel);\n                    profile.reset();\n                }\n            }\n        }\n    }\n\n    return found;\n}\n\nstd::unique_ptr<ChargingSchedule> SmartChargingConnector::getCompositeSchedule(int duration, ChargingRateUnitType_Optional unit) {\n    \n    auto& startSchedule = model.getClock().now();\n\n    auto schedule = std::unique_ptr<ChargingSchedule>(new ChargingSchedule());\n    schedule->duration = duration;\n    schedule->startSchedule = startSchedule;\n    schedule->chargingProfileKind = ChargingProfileKindType::Absolute;\n    schedule->recurrencyKind = RecurrencyKindType::NOT_SET;\n\n    auto& periods = schedule->chargingSchedulePeriod;\n\n    Timestamp periodBegin = Timestamp(startSchedule);\n    Timestamp periodStop = Timestamp(startSchedule);\n\n    while (periodBegin - startSchedule < duration && periods.size() < MO_ChargingScheduleMaxPeriods) {\n\n        //calculate limit\n        ChargeRate limit;\n        calculateLimit(periodBegin, limit, periodStop);\n\n        //if the unit is still unspecified, guess by taking the unit of the first limit\n        if (unit == ChargingRateUnitType_Optional::None) {\n            if (limit.power < limit.current) {\n                unit = ChargingRateUnitType_Optional::Watt;\n            } else {\n                unit = ChargingRateUnitType_Optional::Amp;\n            }\n        }\n\n        periods.emplace_back();\n        float limit_opt = unit == ChargingRateUnitType_Optional::Watt ? limit.power : limit.current;\n        periods.back().limit = limit_opt != std::numeric_limits<float>::max() ? limit_opt : -1.f,\n        periods.back().numberPhases = limit.nphases != std::numeric_limits<int>::max() ? limit.nphases : -1;\n        periods.back().startPeriod = periodBegin - startSchedule;\n        \n        periodBegin = periodStop;\n    }\n\n    if (unit == ChargingRateUnitType_Optional::Watt) {\n        schedule->chargingRateUnit = ChargingRateUnitType::Watt;\n    } else {\n        schedule->chargingRateUnit = ChargingRateUnitType::Amp;\n    }\n\n    return schedule;\n}\n\nsize_t SmartChargingConnector::getChargingProfilesCount() {\n    size_t chargingProfilesCount = 0;\n    for (size_t i = 0; i < MO_ChargeProfileMaxStackLevel + 1; i++) {\n        if (TxDefaultProfile[i]) {\n            chargingProfilesCount++;\n        }\n        if (TxProfile[i]) {\n            chargingProfilesCount++;\n        }\n    }\n    return chargingProfilesCount;\n}\n\nSmartChargingConnector *SmartChargingService::getScConnectorById(unsigned int connectorId) {\n    if (connectorId == 0) {\n        return nullptr;\n    }\n\n    if (connectorId - 1 >= connectors.size()) {\n        return nullptr;\n    }\n\n    return &connectors[connectorId-1];\n}\n\nSmartChargingService::SmartChargingService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem, unsigned int numConnectors)\n      : MemoryManaged(\"v16.SmartCharging.SmartChargingService\"), context(context), filesystem{filesystem}, connectors{makeVector<SmartChargingConnector>(getMemoryTag())}, numConnectors(numConnectors) {\n    \n    for (unsigned int cId = 1; cId < numConnectors; cId++) {\n        connectors.emplace_back(context.getModel(), filesystem, cId, ChargePointMaxProfile, ChargePointTxDefaultProfile);\n    }\n\n    declareConfiguration<int>(\"ChargeProfileMaxStackLevel\", MO_ChargeProfileMaxStackLevel, CONFIGURATION_VOLATILE, true);\n    declareConfiguration<const char*>(\"ChargingScheduleAllowedChargingRateUnit\", \"\", CONFIGURATION_VOLATILE, true);\n    declareConfiguration<int>(\"ChargingScheduleMaxPeriods\", MO_ChargingScheduleMaxPeriods, CONFIGURATION_VOLATILE, true);\n    declareConfiguration<int>(\"MaxChargingProfilesInstalled\", MO_MaxChargingProfilesInstalled, CONFIGURATION_VOLATILE, true);\n\n    context.getOperationRegistry().registerOperation(\"ClearChargingProfile\", [this] () {\n        return new Ocpp16::ClearChargingProfile(*this);});\n    context.getOperationRegistry().registerOperation(\"GetCompositeSchedule\", [&context, this] () {\n        return new Ocpp16::GetCompositeSchedule(context.getModel(), *this);});\n    context.getOperationRegistry().registerOperation(\"SetChargingProfile\", [&context, this] () {\n        return new Ocpp16::SetChargingProfile(context.getModel(), *this);});\n\n    loadProfiles();\n}\n\nSmartChargingService::~SmartChargingService() {\n\n}\n\nChargingProfile *SmartChargingService::updateProfiles(unsigned int connectorId, std::unique_ptr<ChargingProfile> chargingProfile){\n\n    if ((connectorId > 0 && !getScConnectorById(connectorId)) || !chargingProfile) {\n        MO_DBG_ERR(\"invalid args\");\n        return nullptr;\n    }\n\n    if (MO_DBG_LEVEL >= MO_DL_VERBOSE) {\n        MO_DBG_VERBOSE(\"Charging Profile internal model:\");\n        chargingProfile->printProfile();\n    }\n\n    int stackLevel = chargingProfile->getStackLevel();\n    if (stackLevel< 0 || stackLevel >= MO_ChargeProfileMaxStackLevel + 1) {\n        MO_DBG_ERR(\"input validation failed\");\n        return nullptr;\n    }\n\n    size_t chargingProfilesCount = 0;\n    for (size_t i = 0; i < MO_ChargeProfileMaxStackLevel + 1; i++) {\n        if (ChargePointTxDefaultProfile[i]) {\n            chargingProfilesCount++;\n        }\n        if (ChargePointMaxProfile[i]) {\n            chargingProfilesCount++;\n        }\n    }\n    for (size_t i = 0; i < connectors.size(); i++) {\n        chargingProfilesCount += connectors[i].getChargingProfilesCount();\n    }\n\n    if (chargingProfilesCount >= MO_MaxChargingProfilesInstalled) {\n        MO_DBG_WARN(\"number of maximum charging profiles exceeded\");\n        return nullptr;\n    }\n\n    ChargingProfile *res = nullptr;\n\n    switch (chargingProfile->getChargingProfilePurpose()) {\n        case (ChargingProfilePurposeType::ChargePointMaxProfile):\n            if (connectorId != 0) {\n                MO_DBG_WARN(\"invalid charging profile\");\n                return nullptr;\n            }\n            ChargePointMaxProfile[stackLevel] = std::move(chargingProfile);\n            res = ChargePointMaxProfile[stackLevel].get();\n            break;\n        case (ChargingProfilePurposeType::TxDefaultProfile):\n            if (connectorId == 0) {\n                ChargePointTxDefaultProfile[stackLevel] = std::move(chargingProfile);\n                res = ChargePointTxDefaultProfile[stackLevel].get();\n            } else {\n                res = getScConnectorById(connectorId)->updateProfiles(std::move(chargingProfile));\n            }\n            break;\n        case (ChargingProfilePurposeType::TxProfile):\n            if (connectorId == 0) {\n                MO_DBG_WARN(\"invalid charging profile\");\n                return nullptr;\n            } else {\n                res = getScConnectorById(connectorId)->updateProfiles(std::move(chargingProfile));\n            }\n            break;\n    }\n\n    /**\n     * Invalidate the last limit by setting the nextChange to now. By the next loop()-call, the limit\n     * and nextChange will be recalculated and onLimitChanged will be called.\n     */\n    if (res) {\n        nextChange = context.getModel().getClock().now();\n        for (size_t i = 0; i < connectors.size(); i++) {\n            connectors[i].notifyProfilesUpdated();\n        }\n    }\n\n    return res;\n}\n\nbool SmartChargingService::loadProfiles() {\n\n    bool success = true;\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no filesystem\");\n        return true; //not an error\n    }\n\n    ChargingProfilePurposeType purposes[] = {ChargingProfilePurposeType::ChargePointMaxProfile, ChargingProfilePurposeType::TxDefaultProfile, ChargingProfilePurposeType::TxProfile};\n\n    char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n\n    for (auto purpose : purposes) {\n        for (unsigned int cId = 0; cId < numConnectors; cId++) {\n            if (cId > 0 && purpose == ChargingProfilePurposeType::ChargePointMaxProfile) {\n                continue;\n            }\n            for (unsigned int iLevel = 0; iLevel < MO_ChargeProfileMaxStackLevel; iLevel++) {\n\n                if (!SmartChargingServiceUtils::printProfileFileName(fn, MO_MAX_PATH_SIZE, cId, purpose, iLevel)) {\n                    return false;\n                }\n\n                size_t msize = 0;\n                if (filesystem->stat(fn, &msize) != 0) {\n                    continue; //There is not a profile on the stack iStack with stacklevel iLevel. Normal case, just continue.\n                }\n\n                auto profileDoc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag());\n                if (!profileDoc) {\n                    success = false;\n                    MO_DBG_ERR(\"profile corrupt: %s, remove\", fn);\n                    filesystem->remove(fn);\n                    continue;\n                }\n\n                JsonObject profileJson = profileDoc->as<JsonObject>();\n                auto chargingProfile = loadChargingProfile(profileJson);\n\n                bool valid = false;\n                if (chargingProfile) {\n                    valid = updateProfiles(cId, std::move(chargingProfile));\n                }\n                if (!valid) {\n                    success = false;\n                    MO_DBG_ERR(\"profile corrupt: %s, remove\", fn);\n                    filesystem->remove(fn);\n                }\n            }\n        }\n    }\n\n    return success;\n}\n\n/*\n * limitOut: the calculated maximum charge rate at the moment, or the default value if no limit is defined\n * validToOut: The begin of the next SmartCharging restriction after time t\n */\nvoid SmartChargingService::calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut){\n    \n    //initialize output parameters with the default values\n    limitOut = ChargeRate();\n    validToOut = MAX_TIME;\n\n    //get ChargePointMaxProfile with the highest stackLevel\n    for (int i = MO_ChargeProfileMaxStackLevel; i >= 0; i--) {\n        if (ChargePointMaxProfile[i]) {\n            ChargeRate crOut;\n            bool defined = ChargePointMaxProfile[i]->calculateLimit(t, crOut, validToOut);\n            if (defined) {\n                limitOut = crOut;\n                break;\n            }\n        }\n    }\n}\n\nvoid SmartChargingService::loop(){\n\n    for (size_t i = 0; i < connectors.size(); i++) {\n        connectors[i].loop();\n    }\n\n    /**\n     * check if to call onLimitChange\n     */\n    auto& tnow = context.getModel().getClock().now();\n\n    if (tnow >= nextChange){\n        \n        ChargeRate limit;\n        nextChange = MAX_TIME; //reset nextChange to default value and refresh it\n\n        calculateLimit(tnow, limit, nextChange);\n\n#if MO_DBG_LEVEL >= MO_DL_INFO\n        {\n            char timestamp1[JSONDATE_LENGTH + 1] = {'\\0'};\n            tnow.toJsonString(timestamp1, JSONDATE_LENGTH + 1);\n            char timestamp2[JSONDATE_LENGTH + 1] = {'\\0'};\n            nextChange.toJsonString(timestamp2, JSONDATE_LENGTH + 1);\n            MO_DBG_INFO(\"New limit for connector %u, scheduled at = %s, nextChange = %s, limit = {%.1f, %.1f, %i}\",\n                                0,\n                                timestamp1, timestamp2,\n                                limit.power != std::numeric_limits<float>::max() ? limit.power : -1.f,\n                                limit.current != std::numeric_limits<float>::max() ? limit.current : -1.f,\n                                limit.nphases != std::numeric_limits<int>::max() ? limit.nphases : -1);\n        }\n#endif\n\n        if (trackLimitOutput != limit) {\n            if (limitOutput) {\n\n                limitOutput(\n                    limit.power != std::numeric_limits<float>::max() ? limit.power : -1.f,\n                    limit.current != std::numeric_limits<float>::max() ? limit.current : -1.f,\n                    limit.nphases != std::numeric_limits<int>::max() ? limit.nphases : -1);\n                trackLimitOutput = limit;\n            }\n        }\n    }\n}\n\nvoid SmartChargingService::setSmartChargingOutput(unsigned int connectorId, std::function<void(float,float,int)> limitOutput) {\n    if ((connectorId > 0 && !getScConnectorById(connectorId))) {\n        MO_DBG_ERR(\"invalid args\");\n        return;\n    }\n\n    if (connectorId == 0) {\n        if (this->limitOutput) {\n            MO_DBG_WARN(\"replacing existing SmartChargingOutput\");\n        }\n        this->limitOutput = limitOutput;\n    } else {\n        getScConnectorById(connectorId)->setSmartChargingOutput(limitOutput);\n    }\n}\n\nvoid SmartChargingService::updateAllowedChargingRateUnit(bool powerSupported, bool currentSupported) {\n    if ((powerSupported != this->powerSupported) || (currentSupported != this->currentSupported)) {\n\n        auto chargingScheduleAllowedChargingRateUnitString = declareConfiguration<const char*>(\"ChargingScheduleAllowedChargingRateUnit\", \"\", CONFIGURATION_VOLATILE);\n        if (chargingScheduleAllowedChargingRateUnitString) {\n            if (powerSupported && currentSupported) {\n                chargingScheduleAllowedChargingRateUnitString->setString(\"Current,Power\");\n            } else if (powerSupported) {\n                chargingScheduleAllowedChargingRateUnitString->setString(\"Power\");\n            } else if (currentSupported) {\n                chargingScheduleAllowedChargingRateUnitString->setString(\"Current\");\n            }\n        }\n\n        this->powerSupported = powerSupported;\n        this->currentSupported = currentSupported;\n    }\n}\n\nbool SmartChargingService::setChargingProfile(unsigned int connectorId, std::unique_ptr<ChargingProfile> chargingProfile) {\n\n    if ((connectorId > 0 && !getScConnectorById(connectorId)) || !chargingProfile) {\n        MO_DBG_ERR(\"invalid args\");\n        return false;\n    }\n\n    if ((!currentSupported && chargingProfile->chargingSchedule.chargingRateUnit == ChargingRateUnitType::Amp) ||\n            (!powerSupported && chargingProfile->chargingSchedule.chargingRateUnit == ChargingRateUnitType::Watt)) {\n        MO_DBG_WARN(\"unsupported charge rate unit\");\n        return false;\n    }\n\n    int chargingProfileId = chargingProfile->getChargingProfileId();\n    clearChargingProfile([chargingProfileId] (int id, int, ChargingProfilePurposeType, int) {\n        return id == chargingProfileId;\n    });\n\n    bool success = false;\n\n    auto profilePtr = updateProfiles(connectorId, std::move(chargingProfile));\n\n    if (profilePtr) {\n        success = SmartChargingServiceUtils::storeProfile(filesystem, connectorId, profilePtr);\n\n        if (!success) {\n            clearChargingProfile([chargingProfileId] (int id, int, ChargingProfilePurposeType, int) {\n                return id == chargingProfileId;\n            });\n        }\n    }\n\n    return success;\n}\n\nbool SmartChargingService::clearChargingProfile(std::function<bool(int, int, ChargingProfilePurposeType, int)> filter) {\n    bool found = false;\n\n    for (size_t cId = 0; cId < connectors.size(); cId++) {\n        found |= connectors[cId].clearChargingProfile(filter);\n    }\n\n    ProfileStack *profileStacks [] = {&ChargePointMaxProfile, &ChargePointTxDefaultProfile};\n\n    for (auto stack : profileStacks) {\n        for (size_t iLevel = 0; iLevel < stack->size(); iLevel++) {\n            if (auto& profile = stack->at(iLevel)) {\n                if (filter(profile->getChargingProfileId(), 0, profile->getChargingProfilePurpose(), iLevel)) {\n                    found = true;\n                    SmartChargingServiceUtils::removeProfile(filesystem, 0, profile->getChargingProfilePurpose(), iLevel);\n                    profile.reset();\n                }\n            }\n        }\n    }\n\n    /**\n     * Invalidate the last limit by setting the nextChange to now. By the next loop()-call, the limit\n     * and nextChange will be recalculated and onLimitChanged will be called.\n     */\n    nextChange = context.getModel().getClock().now();\n    for (size_t i = 0; i < connectors.size(); i++) {\n        connectors[i].notifyProfilesUpdated();\n    }\n\n    return found;\n}\n\nstd::unique_ptr<ChargingSchedule> SmartChargingService::getCompositeSchedule(unsigned int connectorId, int duration, ChargingRateUnitType_Optional unit) {\n\n    if (connectorId > 0 && !getScConnectorById(connectorId)) {\n        MO_DBG_ERR(\"invalid args\");\n        return nullptr;\n    }\n    \n    if (unit == ChargingRateUnitType_Optional::None) {\n        if (powerSupported && !currentSupported) {\n            unit = ChargingRateUnitType_Optional::Watt;\n        } else if (!powerSupported && currentSupported) {\n            unit = ChargingRateUnitType_Optional::Amp;\n        }\n    }\n\n    if (connectorId > 0) {\n        return getScConnectorById(connectorId)->getCompositeSchedule(duration, unit);\n    }\n    \n    auto& startSchedule = context.getModel().getClock().now();\n\n    auto schedule = std::unique_ptr<ChargingSchedule>(new ChargingSchedule());\n    schedule->duration = duration;\n    schedule->startSchedule = startSchedule;\n    schedule->chargingProfileKind = ChargingProfileKindType::Absolute;\n    schedule->recurrencyKind = RecurrencyKindType::NOT_SET;\n\n    auto& periods = schedule->chargingSchedulePeriod;\n\n    Timestamp periodBegin = Timestamp(startSchedule);\n    Timestamp periodStop = Timestamp(startSchedule);\n\n    while (periodBegin - startSchedule < duration && periods.size() < MO_ChargingScheduleMaxPeriods) {\n\n        //calculate limit\n        ChargeRate limit;\n        calculateLimit(periodBegin, limit, periodStop);\n\n        //if the unit is still unspecified, guess by taking the unit of the first limit\n        if (unit == ChargingRateUnitType_Optional::None) {\n            if (limit.power < limit.current) {\n                unit = ChargingRateUnitType_Optional::Watt;\n            } else {\n                unit = ChargingRateUnitType_Optional::Amp;\n            }\n        }\n\n        periods.push_back(ChargingSchedulePeriod());\n        float limit_opt = unit == ChargingRateUnitType_Optional::Watt ? limit.power : limit.current;\n        periods.back().limit = limit_opt != std::numeric_limits<float>::max() ? limit_opt : -1.f;\n        periods.back().numberPhases = limit.nphases != std::numeric_limits<int>::max() ? limit.nphases : -1;\n        periods.back().startPeriod = periodBegin - startSchedule;\n\n        periodBegin = periodStop;\n    }\n\n    if (unit == ChargingRateUnitType_Optional::Watt) {\n        schedule->chargingRateUnit = ChargingRateUnitType::Watt;\n    } else {\n        schedule->chargingRateUnit = ChargingRateUnitType::Amp;\n    }\n\n    return schedule;\n}\n\nbool SmartChargingServiceUtils::printProfileFileName(char *out, size_t bufsize, unsigned int connectorId, ChargingProfilePurposeType purpose, unsigned int stackLevel) {\n    int pret = 0;\n\n    switch (purpose) {\n        case (ChargingProfilePurposeType::ChargePointMaxProfile):\n            pret = snprintf(out, bufsize, MO_FILENAME_PREFIX \"sc-cm-%u.jsn\", stackLevel);\n            break;\n        case (ChargingProfilePurposeType::TxDefaultProfile):\n            pret = snprintf(out, bufsize, MO_FILENAME_PREFIX \"sc-td-%u-%u.jsn\", connectorId, stackLevel);\n            break;\n        case (ChargingProfilePurposeType::TxProfile):\n            pret = snprintf(out, bufsize, MO_FILENAME_PREFIX \"sc-tx-%u-%u.jsn\", connectorId, stackLevel);\n            break;\n    }\n\n    if (pret < 0 || (size_t) pret >= bufsize) {\n        MO_DBG_ERR(\"fn error: %i\", pret);\n        return false;\n    }\n\n    return true;\n}\n\nbool SmartChargingServiceUtils::storeProfile(std::shared_ptr<FilesystemAdapter> filesystem, unsigned int connectorId, ChargingProfile *chargingProfile) {\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no filesystem\");\n        return true; //not an error\n    }\n\n    auto chargingProfileJson = initJsonDoc(\"v16.SmartCharging.ChargingProfile\");\n    if (!chargingProfile->toJson(chargingProfileJson)) {\n        return false;\n    }\n\n    char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n\n    if (!printProfileFileName(fn, MO_MAX_PATH_SIZE, connectorId, chargingProfile->getChargingProfilePurpose(), chargingProfile->getStackLevel())) {\n        return false;\n    }\n\n    return FilesystemUtils::storeJson(filesystem, fn, chargingProfileJson);\n}\n\nbool SmartChargingServiceUtils::removeProfile(std::shared_ptr<FilesystemAdapter> filesystem, unsigned int connectorId, ChargingProfilePurposeType purpose, unsigned int stackLevel) {\n\n    if (!filesystem) {\n        return false;\n    }\n\n    char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n\n    if (!printProfileFileName(fn, MO_MAX_PATH_SIZE, connectorId, purpose, stackLevel)) {\n        return false;\n    }\n\n    return filesystem->remove(fn);\n}\n\n\n"
  },
  {
    "path": "src/MicroOcpp/Model/SmartCharging/SmartChargingService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef SMARTCHARGINGSERVICE_H\n#define SMARTCHARGINGSERVICE_H\n\n#include <functional>\n#include <array>\n\n#include <ArduinoJson.h>\n\n#include <MicroOcpp/Model/SmartCharging/SmartChargingModel.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nenum class ChargingRateUnitType_Optional {\n    Watt,\n    Amp,\n    None\n};\n\nclass Context;\nclass Model;\n\nusing ProfileStack = std::array<std::unique_ptr<ChargingProfile>, MO_ChargeProfileMaxStackLevel + 1>;\n\nclass SmartChargingConnector : public MemoryManaged {\nprivate:\n    Model& model;\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    const unsigned int connectorId;\n\n    ProfileStack& ChargePointMaxProfile;\n    ProfileStack& ChargePointTxDefaultProfile;\n    ProfileStack TxDefaultProfile;\n    ProfileStack TxProfile;\n\n    std::function<void(float,float,int)> limitOutput;\n\n    int trackTxRmtProfileId = -1; //optional Charging Profile ID when tx is started via RemoteStartTx\n    Timestamp trackTxStart = MAX_TIME; //time basis for relative profiles\n    int trackTxId = -1; //transactionId assigned by OCPP server\n\n    Timestamp nextChange = MIN_TIME;\n\n    ChargeRate trackLimitOutput;\n\n    void calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut);\n\n    void trackTransaction();\n\npublic:\n    SmartChargingConnector(Model& model, std::shared_ptr<FilesystemAdapter> filesystem, unsigned int connectorId, ProfileStack& ChargePointMaxProfile, ProfileStack& ChargePointTxDefaultProfile);\n    SmartChargingConnector(SmartChargingConnector&&) = default;\n    ~SmartChargingConnector();\n\n    void loop();\n\n    void setSmartChargingOutput(std::function<void(float,float,int)> limitOutput); //read maximum Watt x Amps x numberPhases\n\n    ChargingProfile *updateProfiles(std::unique_ptr<ChargingProfile> chargingProfile);\n\n    void notifyProfilesUpdated();\n\n    bool clearChargingProfile(std::function<bool(int, int, ChargingProfilePurposeType, int)> filter);\n\n    std::unique_ptr<ChargingSchedule> getCompositeSchedule(int duration, ChargingRateUnitType_Optional unit);\n\n    size_t getChargingProfilesCount();\n};\n\nclass SmartChargingService : public MemoryManaged {\nprivate:\n    Context& context;\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    Vector<SmartChargingConnector> connectors; //connectorId 0 excluded\n    SmartChargingConnector *getScConnectorById(unsigned int connectorId);\n    unsigned int numConnectors; //connectorId 0 included\n    \n    ProfileStack ChargePointMaxProfile;\n    ProfileStack ChargePointTxDefaultProfile;\n\n    std::function<void(float,float,int)> limitOutput;\n    ChargeRate trackLimitOutput;\n    bool powerSupported = false;\n    bool currentSupported = false;\n\n    Timestamp nextChange = MIN_TIME;\n\n    ChargingProfile *updateProfiles(unsigned int connectorId, std::unique_ptr<ChargingProfile> chargingProfile);\n    bool loadProfiles();\n\n    void calculateLimit(const Timestamp &t, ChargeRate& limitOut, Timestamp& validToOut);\n  \npublic:\n    SmartChargingService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem, unsigned int numConnectors);\n    ~SmartChargingService();\n\n    void loop();\n\n    void setSmartChargingOutput(unsigned int connectorId, std::function<void(float,float,int)> limitOutput); //read maximum Watt x Amps x numberPhases\n    void updateAllowedChargingRateUnit(bool powerSupported, bool currentSupported); //set supported measurand of SmartChargingOutput\n\n    bool setChargingProfile(unsigned int connectorId, std::unique_ptr<ChargingProfile> chargingProfile);\n\n    bool clearChargingProfile(std::function<bool(int, int, ChargingProfilePurposeType, int)> filter);\n\n    std::unique_ptr<ChargingSchedule> getCompositeSchedule(unsigned int connectorId, int duration, ChargingRateUnitType_Optional unit = ChargingRateUnitType_Optional::None);\n};\n\n//filesystem-related helper functions\nnamespace SmartChargingServiceUtils {\nbool printProfileFileName(char *out, size_t bufsize, unsigned int connectorId, ChargingProfilePurposeType purpose, unsigned int stackLevel);\nbool storeProfile(std::shared_ptr<FilesystemAdapter> filesystem, unsigned int connectorId, ChargingProfile *chargingProfile);\nbool removeProfile(std::shared_ptr<FilesystemAdapter> filesystem, unsigned int connectorId, ChargingProfilePurposeType purpose, unsigned int stackLevel);\n}\n\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Transactions/Transaction.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nbool Transaction::setIdTag(const char *idTag) {\n    auto ret = snprintf(this->idTag, IDTAG_LEN_MAX + 1, \"%s\", idTag);\n    return ret >= 0 && ret < IDTAG_LEN_MAX + 1;\n}\n\nbool Transaction::setParentIdTag(const char *idTag) {\n    auto ret = snprintf(this->parentIdTag, IDTAG_LEN_MAX + 1, \"%s\", idTag);\n    return ret >= 0 && ret < IDTAG_LEN_MAX + 1;\n}\n\nbool Transaction::setStopIdTag(const char *idTag) {\n    auto ret = snprintf(stop_idTag, IDTAG_LEN_MAX + 1, \"%s\", idTag);\n    return ret >= 0 && ret < IDTAG_LEN_MAX + 1;\n}\n\nbool Transaction::setStopReason(const char *reason) {\n    auto ret = snprintf(stop_reason, REASON_LEN_MAX + 1, \"%s\", reason);\n    return ret >= 0 && ret < REASON_LEN_MAX + 1;\n}\n\nbool Transaction::commit() {\n    return context.commit(this);\n}\n\n#if MO_ENABLE_V201\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nconst char *serializeTransactionStoppedReason(Transaction::StoppedReason stoppedReason) {\n    const char *stoppedReasonCstr = nullptr;\n    switch (stoppedReason) {\n        case Transaction::StoppedReason::UNDEFINED:\n            // optional, okay\n            break;\n        case Transaction::StoppedReason::Local:\n            stoppedReasonCstr = \"Local\";\n            break;\n        case Transaction::StoppedReason::DeAuthorized:\n            stoppedReasonCstr = \"DeAuthorized\";\n            break;\n        case Transaction::StoppedReason::EmergencyStop:\n            stoppedReasonCstr = \"EmergencyStop\";\n            break;\n        case Transaction::StoppedReason::EnergyLimitReached:\n            stoppedReasonCstr = \"EnergyLimitReached\";\n            break;\n        case Transaction::StoppedReason::EVDisconnected:\n            stoppedReasonCstr = \"EVDisconnected\";\n            break;\n        case Transaction::StoppedReason::GroundFault:\n            stoppedReasonCstr = \"GroundFault\";\n            break;\n        case Transaction::StoppedReason::ImmediateReset:\n            stoppedReasonCstr = \"ImmediateReset\";\n            break;\n        case Transaction::StoppedReason::LocalOutOfCredit:\n            stoppedReasonCstr = \"LocalOutOfCredit\";\n            break;\n        case Transaction::StoppedReason::MasterPass:\n            stoppedReasonCstr = \"MasterPass\";\n            break;\n        case Transaction::StoppedReason::Other:\n            stoppedReasonCstr = \"Other\";\n            break;\n        case Transaction::StoppedReason::OvercurrentFault:\n            stoppedReasonCstr = \"OvercurrentFault\";\n            break;\n        case Transaction::StoppedReason::PowerLoss:\n            stoppedReasonCstr = \"PowerLoss\";\n            break;\n        case Transaction::StoppedReason::PowerQuality:\n            stoppedReasonCstr = \"PowerQuality\";\n            break;\n        case Transaction::StoppedReason::Reboot:\n            stoppedReasonCstr = \"Reboot\";\n            break;\n        case Transaction::StoppedReason::Remote:\n            stoppedReasonCstr = \"Remote\";\n            break;\n        case Transaction::StoppedReason::SOCLimitReached:\n            stoppedReasonCstr = \"SOCLimitReached\";\n            break;\n        case Transaction::StoppedReason::StoppedByEV:\n            stoppedReasonCstr = \"StoppedByEV\";\n            break;\n        case Transaction::StoppedReason::TimeLimitReached:\n            stoppedReasonCstr = \"TimeLimitReached\";\n            break;\n        case Transaction::StoppedReason::Timeout:\n            stoppedReasonCstr = \"Timeout\";\n            break;\n    }\n\n    return stoppedReasonCstr;\n}\nbool deserializeTransactionStoppedReason(const char *stoppedReasonCstr, Transaction::StoppedReason& stoppedReasonOut) {\n    if (!stoppedReasonCstr || !*stoppedReasonCstr) {\n        stoppedReasonOut = Transaction::StoppedReason::UNDEFINED;\n    } else if (!strcmp(stoppedReasonCstr, \"DeAuthorized\")) {\n        stoppedReasonOut = Transaction::StoppedReason::DeAuthorized;\n    } else if (!strcmp(stoppedReasonCstr, \"EmergencyStop\")) {\n        stoppedReasonOut = Transaction::StoppedReason::EmergencyStop;\n    } else if (!strcmp(stoppedReasonCstr, \"EnergyLimitReached\")) {\n        stoppedReasonOut = Transaction::StoppedReason::EnergyLimitReached;\n    } else if (!strcmp(stoppedReasonCstr, \"EVDisconnected\")) {\n        stoppedReasonOut = Transaction::StoppedReason::EVDisconnected;\n    } else if (!strcmp(stoppedReasonCstr, \"GroundFault\")) {\n        stoppedReasonOut = Transaction::StoppedReason::GroundFault;\n    } else if (!strcmp(stoppedReasonCstr, \"ImmediateReset\")) {\n        stoppedReasonOut = Transaction::StoppedReason::ImmediateReset;\n    } else if (!strcmp(stoppedReasonCstr, \"Local\")) {\n        stoppedReasonOut = Transaction::StoppedReason::Local;\n    } else if (!strcmp(stoppedReasonCstr, \"LocalOutOfCredit\")) {\n        stoppedReasonOut = Transaction::StoppedReason::LocalOutOfCredit;\n    } else if (!strcmp(stoppedReasonCstr, \"MasterPass\")) {\n        stoppedReasonOut = Transaction::StoppedReason::MasterPass;\n    } else if (!strcmp(stoppedReasonCstr, \"Other\")) {\n        stoppedReasonOut = Transaction::StoppedReason::Other;\n    } else if (!strcmp(stoppedReasonCstr, \"OvercurrentFault\")) {\n        stoppedReasonOut = Transaction::StoppedReason::OvercurrentFault;\n    } else if (!strcmp(stoppedReasonCstr, \"PowerLoss\")) {\n        stoppedReasonOut = Transaction::StoppedReason::PowerLoss;\n    } else if (!strcmp(stoppedReasonCstr, \"PowerQuality\")) {\n        stoppedReasonOut = Transaction::StoppedReason::PowerQuality;\n    } else if (!strcmp(stoppedReasonCstr, \"Reboot\")) {\n        stoppedReasonOut = Transaction::StoppedReason::Reboot;\n    } else if (!strcmp(stoppedReasonCstr, \"Remote\")) {\n        stoppedReasonOut = Transaction::StoppedReason::Remote;\n    } else if (!strcmp(stoppedReasonCstr, \"SOCLimitReached\")) {\n        stoppedReasonOut = Transaction::StoppedReason::SOCLimitReached;\n    } else if (!strcmp(stoppedReasonCstr, \"StoppedByEV\")) {\n        stoppedReasonOut = Transaction::StoppedReason::StoppedByEV;\n    } else if (!strcmp(stoppedReasonCstr, \"TimeLimitReached\")) {\n        stoppedReasonOut = Transaction::StoppedReason::TimeLimitReached;\n    } else if (!strcmp(stoppedReasonCstr, \"Timeout\")) {\n        stoppedReasonOut = Transaction::StoppedReason::Timeout;\n    } else {\n        MO_DBG_ERR(\"deserialization error\");\n        return false;\n    }\n    return true;\n}\n\nconst char *serializeTransactionEventType(TransactionEventData::Type type) {\n    const char *typeCstr = \"\";\n    switch (type) {\n        case TransactionEventData::Type::Ended:\n            typeCstr = \"Ended\";\n            break;\n        case TransactionEventData::Type::Started:\n            typeCstr = \"Started\";\n            break;\n        case TransactionEventData::Type::Updated:\n            typeCstr = \"Updated\";\n            break;\n    }\n    return typeCstr;\n}\nbool deserializeTransactionEventType(const char *typeCstr, TransactionEventData::Type& typeOut) {\n    if (!strcmp(typeCstr, \"Ended\")) {\n        typeOut = TransactionEventData::Type::Ended;\n    } else if (!strcmp(typeCstr, \"Started\")) {\n        typeOut = TransactionEventData::Type::Started;\n    } else if (!strcmp(typeCstr, \"Updated\")) {\n        typeOut = TransactionEventData::Type::Updated;\n    } else {\n        MO_DBG_ERR(\"deserialization error\");\n        return false;\n    }\n    return true;\n}\n\nconst char *serializeTransactionEventTriggerReason(TransactionEventTriggerReason triggerReason) {\n\n    const char *triggerReasonCstr = nullptr;\n    switch(triggerReason) {\n        case TransactionEventTriggerReason::UNDEFINED:\n            break;\n        case TransactionEventTriggerReason::Authorized:\n            triggerReasonCstr = \"Authorized\";\n            break;\n        case TransactionEventTriggerReason::CablePluggedIn:\n            triggerReasonCstr = \"CablePluggedIn\";\n            break;\n        case TransactionEventTriggerReason::ChargingRateChanged:\n            triggerReasonCstr = \"ChargingRateChanged\";\n            break;\n        case TransactionEventTriggerReason::ChargingStateChanged:\n            triggerReasonCstr = \"ChargingStateChanged\";\n            break;\n        case TransactionEventTriggerReason::Deauthorized:\n            triggerReasonCstr = \"Deauthorized\";\n            break;\n        case TransactionEventTriggerReason::EnergyLimitReached:\n            triggerReasonCstr = \"EnergyLimitReached\";\n            break;\n        case TransactionEventTriggerReason::EVCommunicationLost:\n            triggerReasonCstr = \"EVCommunicationLost\";\n            break;\n        case TransactionEventTriggerReason::EVConnectTimeout:\n            triggerReasonCstr = \"EVConnectTimeout\";\n            break;\n        case TransactionEventTriggerReason::MeterValueClock:\n            triggerReasonCstr = \"MeterValueClock\";\n            break;\n        case TransactionEventTriggerReason::MeterValuePeriodic:\n            triggerReasonCstr = \"MeterValuePeriodic\";\n            break;\n        case TransactionEventTriggerReason::TimeLimitReached:\n            triggerReasonCstr = \"TimeLimitReached\";\n            break;\n        case TransactionEventTriggerReason::Trigger:\n            triggerReasonCstr = \"Trigger\";\n            break;\n        case TransactionEventTriggerReason::UnlockCommand:\n            triggerReasonCstr = \"UnlockCommand\";\n            break;\n        case TransactionEventTriggerReason::StopAuthorized:\n            triggerReasonCstr = \"StopAuthorized\";\n            break;\n        case TransactionEventTriggerReason::EVDeparted:\n            triggerReasonCstr = \"EVDeparted\";\n            break;\n        case TransactionEventTriggerReason::EVDetected:\n            triggerReasonCstr = \"EVDetected\";\n            break;\n        case TransactionEventTriggerReason::RemoteStop:\n            triggerReasonCstr = \"RemoteStop\";\n            break;\n        case TransactionEventTriggerReason::RemoteStart:\n            triggerReasonCstr = \"RemoteStart\";\n            break;\n        case TransactionEventTriggerReason::AbnormalCondition:\n            triggerReasonCstr = \"AbnormalCondition\";\n            break;\n        case TransactionEventTriggerReason::SignedDataReceived:\n            triggerReasonCstr = \"SignedDataReceived\";\n            break;\n        case TransactionEventTriggerReason::ResetCommand:\n            triggerReasonCstr = \"ResetCommand\";\n            break;\n    }\n\n    return triggerReasonCstr;\n}\nbool deserializeTransactionEventTriggerReason(const char *triggerReasonCstr, TransactionEventTriggerReason& triggerReasonOut) {\n    if (!triggerReasonCstr || !*triggerReasonCstr) {\n        triggerReasonOut = TransactionEventTriggerReason::UNDEFINED;\n    } else if (!strcmp(triggerReasonCstr, \"Authorized\")) {\n        triggerReasonOut = TransactionEventTriggerReason::Authorized;\n    } else if (!strcmp(triggerReasonCstr, \"CablePluggedIn\")) {\n        triggerReasonOut = TransactionEventTriggerReason::CablePluggedIn;\n    } else if (!strcmp(triggerReasonCstr, \"ChargingRateChanged\")) {\n        triggerReasonOut = TransactionEventTriggerReason::ChargingRateChanged;\n    } else if (!strcmp(triggerReasonCstr, \"ChargingStateChanged\")) {\n        triggerReasonOut = TransactionEventTriggerReason::ChargingStateChanged;\n    } else if (!strcmp(triggerReasonCstr, \"Deauthorized\")) {\n        triggerReasonOut = TransactionEventTriggerReason::Deauthorized;\n    } else if (!strcmp(triggerReasonCstr, \"EnergyLimitReached\")) {\n        triggerReasonOut = TransactionEventTriggerReason::EnergyLimitReached;\n    } else if (!strcmp(triggerReasonCstr, \"EVCommunicationLost\")) {\n        triggerReasonOut = TransactionEventTriggerReason::EVCommunicationLost;\n    } else if (!strcmp(triggerReasonCstr, \"EVConnectTimeout\")) {\n        triggerReasonOut = TransactionEventTriggerReason::EVConnectTimeout;\n    } else if (!strcmp(triggerReasonCstr, \"MeterValueClock\")) {\n        triggerReasonOut = TransactionEventTriggerReason::MeterValueClock;\n    } else if (!strcmp(triggerReasonCstr, \"MeterValuePeriodic\")) {\n        triggerReasonOut = TransactionEventTriggerReason::MeterValuePeriodic;\n    } else if (!strcmp(triggerReasonCstr, \"TimeLimitReached\")) {\n        triggerReasonOut = TransactionEventTriggerReason::TimeLimitReached;\n    } else if (!strcmp(triggerReasonCstr, \"Trigger\")) {\n        triggerReasonOut = TransactionEventTriggerReason::Trigger;\n    } else if (!strcmp(triggerReasonCstr, \"UnlockCommand\")) {\n        triggerReasonOut = TransactionEventTriggerReason::UnlockCommand;\n    } else if (!strcmp(triggerReasonCstr, \"StopAuthorized\")) {\n        triggerReasonOut = TransactionEventTriggerReason::StopAuthorized;\n    } else if (!strcmp(triggerReasonCstr, \"EVDeparted\")) {\n        triggerReasonOut = TransactionEventTriggerReason::EVDeparted;\n    } else if (!strcmp(triggerReasonCstr, \"EVDetected\")) {\n        triggerReasonOut = TransactionEventTriggerReason::EVDetected;\n    } else if (!strcmp(triggerReasonCstr, \"RemoteStop\")) {\n        triggerReasonOut = TransactionEventTriggerReason::RemoteStop;\n    } else if (!strcmp(triggerReasonCstr, \"RemoteStart\")) {\n        triggerReasonOut = TransactionEventTriggerReason::RemoteStart;\n    } else if (!strcmp(triggerReasonCstr, \"AbnormalCondition\")) {\n        triggerReasonOut = TransactionEventTriggerReason::AbnormalCondition;\n    } else if (!strcmp(triggerReasonCstr, \"SignedDataReceived\")) {\n        triggerReasonOut = TransactionEventTriggerReason::SignedDataReceived;\n    } else if (!strcmp(triggerReasonCstr, \"ResetCommand\")) {\n        triggerReasonOut = TransactionEventTriggerReason::ResetCommand;\n    } else {\n        MO_DBG_ERR(\"deserialization error\");\n        return false;\n    }\n    return true;\n}\n\nconst char *serializeTransactionEventChargingState(TransactionEventData::ChargingState chargingState) {\n    const char *chargingStateCstr = nullptr;\n    switch (chargingState) {\n        case TransactionEventData::ChargingState::UNDEFINED:\n            // optional, okay\n            break;\n        case TransactionEventData::ChargingState::Charging:\n            chargingStateCstr = \"Charging\";\n            break;\n        case TransactionEventData::ChargingState::EVConnected:\n            chargingStateCstr = \"EVConnected\";\n            break;\n        case TransactionEventData::ChargingState::SuspendedEV:\n            chargingStateCstr = \"SuspendedEV\";\n            break;\n        case TransactionEventData::ChargingState::SuspendedEVSE:\n            chargingStateCstr = \"SuspendedEVSE\";\n            break;\n        case TransactionEventData::ChargingState::Idle:\n            chargingStateCstr = \"Idle\";\n            break;\n    }\n    return chargingStateCstr;\n}\nbool deserializeTransactionEventChargingState(const char *chargingStateCstr, TransactionEventData::ChargingState& chargingStateOut) {\n    if (!chargingStateCstr || !*chargingStateCstr) {\n        chargingStateOut = TransactionEventData::ChargingState::UNDEFINED;\n    } else if (!strcmp(chargingStateCstr, \"Charging\")) {\n        chargingStateOut = TransactionEventData::ChargingState::Charging;\n    } else if (!strcmp(chargingStateCstr, \"EVConnected\")) {\n        chargingStateOut = TransactionEventData::ChargingState::EVConnected;\n    } else if (!strcmp(chargingStateCstr, \"SuspendedEV\")) {\n        chargingStateOut = TransactionEventData::ChargingState::SuspendedEV;\n    } else if (!strcmp(chargingStateCstr, \"SuspendedEVSE\")) {\n        chargingStateOut = TransactionEventData::ChargingState::SuspendedEVSE;\n    } else if (!strcmp(chargingStateCstr, \"Idle\")) {\n        chargingStateOut = TransactionEventData::ChargingState::Idle;\n    } else {\n        MO_DBG_ERR(\"deserialization error\");\n        return false;\n    }\n    return true;\n}\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#if MO_ENABLE_V201\nbool g_ocpp_tx_compat_v201;\n\nvoid ocpp_tx_compat_setV201(bool isV201) {\n    g_ocpp_tx_compat_v201 = isV201;\n}\n#endif\n\nint ocpp_tx_getTransactionId(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        MO_DBG_ERR(\"only supported in v16\");\n        return -1;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getTransactionId();\n}\n#if MO_ENABLE_V201\nconst char *ocpp_tx_getTransactionIdV201(OCPP_Transaction *tx) {\n    if (!g_ocpp_tx_compat_v201) {\n        MO_DBG_ERR(\"only supported in v201\");\n        return nullptr;\n    }\n    return reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx)->transactionId;\n}\n#endif //MO_ENABLE_V201\nbool ocpp_tx_isAuthorized(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        return reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx)->isAuthorized;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->isAuthorized();\n}\nbool ocpp_tx_isIdTagDeauthorized(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        return reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx)->isDeauthorized;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->isIdTagDeauthorized();\n}\n\nbool ocpp_tx_isRunning(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        auto transaction = reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx);\n        return transaction->started && !transaction->stopped;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->isRunning();\n}\nbool ocpp_tx_isActive(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        return reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx)->active;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->isActive();\n}\nbool ocpp_tx_isAborted(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        auto transaction = reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx);\n        return !transaction->active && !transaction->started;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->isAborted();\n}\nbool ocpp_tx_isCompleted(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        auto transaction = reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx);\n        return transaction->stopped && transaction->seqNos.empty();\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->isCompleted();\n}\n\nconst char *ocpp_tx_getIdTag(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        auto transaction = reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx);\n        return transaction->idToken.get();\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getIdTag();\n}\n\nconst char *ocpp_tx_getParentIdTag(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        MO_DBG_ERR(\"only supported in v16\");\n        return nullptr;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getParentIdTag();\n}\n\nbool ocpp_tx_getBeginTimestamp(OCPP_Transaction *tx, char *buf, size_t len) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        return reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx)->beginTimestamp.toJsonString(buf, len);\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getBeginTimestamp().toJsonString(buf, len);\n}\n\nint32_t ocpp_tx_getMeterStart(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        MO_DBG_ERR(\"only supported in v16\");\n        return -1;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getMeterStart();\n}\n\nbool ocpp_tx_getStartTimestamp(OCPP_Transaction *tx, char *buf, size_t len) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        MO_DBG_ERR(\"only supported in v16\");\n        return -1;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getStartTimestamp().toJsonString(buf, len);\n}\n\nconst char *ocpp_tx_getStopIdTag(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        auto transaction = reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx);\n        return transaction->stopIdToken ? transaction->stopIdToken->get() : \"\";\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getStopIdTag();\n}\n\nint32_t ocpp_tx_getMeterStop(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        MO_DBG_ERR(\"only supported in v16\");\n        return -1;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getMeterStop();\n}\n\nvoid ocpp_tx_setMeterStop(OCPP_Transaction* tx, int32_t meter) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        MO_DBG_ERR(\"only supported in v16\");\n        return;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->setMeterStop(meter);\n}\n\nbool ocpp_tx_getStopTimestamp(OCPP_Transaction *tx, char *buf, size_t len) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        MO_DBG_ERR(\"only supported in v16\");\n        return -1;\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getStopTimestamp().toJsonString(buf, len);\n}\n\nconst char *ocpp_tx_getStopReason(OCPP_Transaction *tx) {\n    #if MO_ENABLE_V201\n    if (g_ocpp_tx_compat_v201) {\n        auto transaction = reinterpret_cast<MicroOcpp::Ocpp201::Transaction*>(tx);\n        return serializeTransactionStoppedReason(transaction->stoppedReason);\n    }\n    #endif //MO_ENABLE_V201\n    return reinterpret_cast<MicroOcpp::Transaction*>(tx)->getStopReason();\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Transactions/Transaction.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef TRANSACTION_H\n#define TRANSACTION_H\n\n#include <MicroOcpp/Version.h>\n\n/* General Tx defs */\n#ifdef __cplusplus\nextern \"C\" {\n#endif //__cplusplus\n\n//TxNotification - event from MO to the main firmware to notify it about transaction state changes\ntypedef enum {\n    TxNotification_UNDEFINED,\n\n    //Authorization events\n    TxNotification_Authorized, //success\n    TxNotification_AuthorizationRejected, //IdTag/token not authorized\n    TxNotification_AuthorizationTimeout, //authorization failed - offline\n    TxNotification_ReservationConflict, //connector/evse reserved for other IdTag\n\n    TxNotification_ConnectionTimeout, //user took to long to plug vehicle after the authorization\n    TxNotification_DeAuthorized, //server rejected StartTx/TxEvent\n    TxNotification_RemoteStart, //authorized via RemoteStartTx/RequestStartTx\n    TxNotification_RemoteStop, //stopped via RemoteStopTx/RequestStopTx\n\n    //Tx lifecycle events\n    TxNotification_StartTx, //entered running state (StartTx/TxEvent was initiated)\n    TxNotification_StopTx, //left running state (StopTx/TxEvent was initiated)\n}   TxNotification;\n\n#ifdef __cplusplus\n}\n#endif //__cplusplus\n\n#ifdef __cplusplus\n\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Operations/CiStrings.h>\n\n#define MAX_TX_CNT 100000U //upper limit of txNr (internal usage). Must be at least 2*MO_TXRECORD_SIZE+1\n\nnamespace MicroOcpp {\n\n/*\n * A transaction is initiated by the client (charging station) and processed by the server (central system).\n * The client side of a transaction is all data that is generated or collected at the charging station. The\n * server side is all transaction data that is assigned by the central system.\n * \n * See OCPP 1.6 Specification - Edition 2, sections 3.6, 4.8, 4.10 and 5.11. \n */\n\nclass ConnectorTransactionStore;\n\nclass SendStatus {\nprivate:\n    bool requested = false;\n    bool confirmed = false;\n    \n    unsigned int opNr = 0;\n    unsigned int attemptNr = 0;\n    Timestamp attemptTime = MIN_TIME;\npublic:\n    void setRequested() {this->requested = true;}\n    bool isRequested() {return requested;}\n    void confirm() {confirmed = true;}\n    bool isConfirmed() {return confirmed;}\n    void setOpNr(unsigned int opNr) {this->opNr = opNr;}\n    unsigned int getOpNr() {return opNr;}\n    void advanceAttemptNr() {attemptNr++;}\n    void setAttemptNr(unsigned int attemptNr) {this->attemptNr = attemptNr;}\n    unsigned int getAttemptNr() {return attemptNr;}\n    const Timestamp& getAttemptTime() {return attemptTime;}\n    void setAttemptTime(const Timestamp& timestamp) {attemptTime = timestamp;}\n};\n\nclass Transaction : public MemoryManaged {\nprivate:\n    ConnectorTransactionStore& context;\n\n    bool active = true; //once active is false, the tx must stop (or cannot start at all)\n\n    /*\n     * Attributes existing before StartTransaction\n     */\n    char idTag [IDTAG_LEN_MAX + 1] = {'\\0'};\n    char parentIdTag [IDTAG_LEN_MAX + 1] = {'\\0'};\n    bool authorized = false;    //if the given idTag was authorized\n    bool deauthorized = false;  //if the server revoked a local authorization\n    Timestamp begin_timestamp = MIN_TIME;\n    int reservationId = -1;\n    int txProfileId = -1;\n\n    /*\n     * Attributes of StartTransaction\n     */\n    SendStatus start_sync;\n    int32_t start_meter = -1;           //meterStart of StartTx\n    Timestamp start_timestamp = MIN_TIME;      //timestamp of StartTx; can be set before actually initiating\n    uint16_t start_bootNr = 0;\n    int transactionId = -1; //only valid if confirmed = true\n\n    /*\n     * Attributes of StopTransaction\n     */\n    SendStatus stop_sync;\n    char stop_idTag [IDTAG_LEN_MAX + 1] = {'\\0'};\n    int32_t stop_meter = -1;\n    Timestamp stop_timestamp = MIN_TIME;\n    uint16_t stop_bootNr = 0;\n    char stop_reason [REASON_LEN_MAX + 1] = {'\\0'};\n\n    /*\n     * General attributes\n     */\n    unsigned int connectorId = 0;\n    unsigned int txNr = 0; //client-side key of this tx object (!= transactionId)\n\n    bool silent = false; //silent Tx: process tx locally, without reporting to the server\n\npublic:\n    Transaction(ConnectorTransactionStore& context, unsigned int connectorId, unsigned int txNr, bool silent = false) : \n                MemoryManaged(\"v16.Transactions.Transaction\"),\n                context(context),\n                connectorId(connectorId), \n                txNr(txNr),\n                silent(silent) {}\n\n    /*\n     * data assigned by OCPP server\n     */\n    int getTransactionId() {return transactionId;}\n    bool isAuthorized() {return authorized;} //Authorize has been accepted\n    bool isIdTagDeauthorized() {return deauthorized;} //StartTransaction has been rejected\n\n    /*\n     * Transaction life cycle\n     */\n    bool isRunning() {return start_sync.isRequested() && !stop_sync.isRequested();} //tx is running\n    bool isActive() {return active;} //tx continues to run or is preparing\n    bool isAborted() {return !start_sync.isRequested() && !active;} //tx ended before startTx was sent\n    bool isCompleted() {return stop_sync.isConfirmed();} //tx ended and startTx and stopTx have been confirmed by server\n\n    /*\n     * After modifying a field of tx, commit to make the data persistent\n     */\n    bool commit();\n\n    /*\n     * Getters and setters for (mostly) internal use\n     */\n    void setInactive() {active = false;}\n\n    bool setIdTag(const char *idTag);\n    const char *getIdTag() {return idTag;}\n\n    bool setParentIdTag(const char *idTag);\n    const char *getParentIdTag() {return parentIdTag;}\n\n    void setAuthorized() {authorized = true;}\n    void setIdTagDeauthorized() {deauthorized = true;}\n\n    void setBeginTimestamp(Timestamp timestamp) {begin_timestamp = timestamp;}\n    const Timestamp& getBeginTimestamp() {return begin_timestamp;}\n\n    void setReservationId(int reservationId) {this->reservationId = reservationId;}\n    int getReservationId() {return reservationId;}\n\n    void setTxProfileId(int txProfileId) {this->txProfileId = txProfileId;}\n    int getTxProfileId() {return txProfileId;}\n\n    SendStatus& getStartSync() {return start_sync;}\n\n    void setMeterStart(int32_t meter) {start_meter = meter;}\n    bool isMeterStartDefined() {return start_meter >= 0;}\n    int32_t getMeterStart() {return start_meter;}\n\n    void setStartTimestamp(Timestamp timestamp) {start_timestamp = timestamp;}\n    const Timestamp& getStartTimestamp() {return start_timestamp;}\n\n    void setStartBootNr(uint16_t bootNr) {start_bootNr = bootNr;} \n    uint16_t getStartBootNr() {return start_bootNr;}\n\n    void setTransactionId(int transactionId) {this->transactionId = transactionId;}\n\n    SendStatus& getStopSync() {return stop_sync;}\n\n    bool setStopIdTag(const char *idTag);\n    const char *getStopIdTag() {return stop_idTag;}\n\n    void setMeterStop(int32_t meter) {stop_meter = meter;}\n    bool isMeterStopDefined() {return stop_meter >= 0;}\n    int32_t getMeterStop() {return stop_meter;}\n\n    void setStopTimestamp(Timestamp timestamp) {stop_timestamp = timestamp;}\n    const Timestamp& getStopTimestamp() {return stop_timestamp;}\n\n    void setStopBootNr(uint16_t bootNr) {stop_bootNr = bootNr;} \n    uint16_t getStopBootNr() {return stop_bootNr;}\n\n    bool setStopReason(const char *reason);\n    const char *getStopReason() {return stop_reason;}\n\n    void setConnectorId(unsigned int connectorId) {this->connectorId = connectorId;}\n    unsigned int getConnectorId() {return connectorId;}\n\n    void setTxNr(unsigned int txNr) {this->txNr = txNr;}\n    unsigned int getTxNr() {return txNr;} //internal primary key of this tx object\n\n    void setSilent() {silent = true;}\n    bool isSilent() {return silent;} //no data will be sent to server and server will not assign transactionId\n};\n\n} // namespace MicroOcpp\n\n#if MO_ENABLE_V201\n\n#include <memory>\n#include <limits>\n\n#include <MicroOcpp/Model/Transactions/TransactionDefs.h>\n#include <MicroOcpp/Model/Authorization/IdToken.h>\n#include <MicroOcpp/Model/ConnectorBase/EvseId.h>\n#include <MicroOcpp/Model/Metering/MeterValuesV201.h>\n\n#ifndef MO_SAMPLEDDATATXENDED_SIZE_MAX\n#define MO_SAMPLEDDATATXENDED_SIZE_MAX 5\n#endif\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\n// TriggerReasonEnumType (3.82)\nenum class TransactionEventTriggerReason : uint8_t {\n    UNDEFINED, // not part of OCPP\n    Authorized,\n    CablePluggedIn,\n    ChargingRateChanged,\n    ChargingStateChanged,\n    Deauthorized,\n    EnergyLimitReached,\n    EVCommunicationLost,\n    EVConnectTimeout,\n    MeterValueClock,\n    MeterValuePeriodic,\n    TimeLimitReached,\n    Trigger,\n    UnlockCommand,\n    StopAuthorized,\n    EVDeparted,\n    EVDetected,\n    RemoteStop,\n    RemoteStart,\n    AbnormalCondition,\n    SignedDataReceived,\n    ResetCommand\n};\n\nclass Transaction : public MemoryManaged {\npublic:\n\n    // ReasonEnumType (3.67)\n    enum class StoppedReason : uint8_t {\n        UNDEFINED, // not part of OCPP\n        DeAuthorized,\n        EmergencyStop,\n        EnergyLimitReached,\n        EVDisconnected,\n        GroundFault,\n        ImmediateReset,\n        Local,\n        LocalOutOfCredit,\n        MasterPass,\n        Other,\n        OvercurrentFault,\n        PowerLoss,\n        PowerQuality,\n        Reboot,\n        Remote,\n        SOCLimitReached,\n        StoppedByEV,\n        TimeLimitReached,\n        Timeout\n    };\n\n//private:\n    /*\n     * Transaction substates. Notify server about any change when transaction is running\n     */\n    //bool trackParkingBayOccupancy; // not supported\n    bool trackEvConnected = false;\n    bool trackAuthorized = false;\n    bool trackDataSigned = false;\n    bool trackPowerPathClosed = false;\n    bool trackEnergyTransfer = false;\n\n    /*\n     * Transaction lifecycle\n     */\n    bool active = true; //once active is false, the tx must stop (or cannot start at all)\n    bool started = false; //if a TxEvent with event type TxStarted has been initiated\n    bool stopped = false; //if a TxEvent with event type TxEnded has been initiated\n\n    /*\n     * Global transaction data\n     */\n    bool isAuthorizationActive = false; //period between beginAuthorization and endAuthorization\n    bool isAuthorized = false;    //if the given idToken was authorized\n    bool isDeauthorized = false;  //if the server revoked a local authorization\n    IdToken idToken;\n    Timestamp beginTimestamp = MIN_TIME;\n    char transactionId [MO_TXID_LEN_MAX + 1] = {'\\0'};\n    int remoteStartId = -1;\n\n    //if to fill next TxEvent with optional fields\n    bool notifyEvseId = false;\n    bool notifyIdToken = false;\n    bool notifyStopIdToken = false;\n    bool notifyReservationId = false;\n    bool notifyChargingState = false;\n    bool notifyRemoteStartId = false;\n\n    bool evConnectionTimeoutListen = true;\n\n    StoppedReason stoppedReason = StoppedReason::UNDEFINED;\n    TransactionEventTriggerReason stopTrigger = TransactionEventTriggerReason::UNDEFINED;\n    std::unique_ptr<IdToken> stopIdToken; // if null, then stopIdToken equals idToken\n\n    /*\n     * Tx-related metering\n     */\n\n    Vector<std::unique_ptr<Ocpp201::MeterValue>> sampledDataTxEnded;\n\n    unsigned long lastSampleTimeTxUpdated = 0; //0 means not charging right now\n    unsigned long lastSampleTimeTxEnded = 0;\n\n    /*\n     * Attributes for internal store\n     */\n    unsigned int evseId = 0;\n    unsigned int txNr = 0; //internal key attribute (!= transactionId); {evseId*txNr} is unique key\n\n    unsigned int seqNoEnd = 0; // increment by 1 for each event\n    Vector<unsigned int> seqNos; //track stored txEvents\n\n    bool silent = false; //silent Tx: process tx locally, without reporting to the server\n\n    Transaction() :\n            MemoryManaged(\"v201.Transactions.Transaction\"),\n            sampledDataTxEnded(makeVector<std::unique_ptr<Ocpp201::MeterValue>>(getMemoryTag())),\n            seqNos(makeVector<unsigned int>(getMemoryTag())) { }\n\n    void addSampledDataTxEnded(std::unique_ptr<Ocpp201::MeterValue> mv) {\n        if (sampledDataTxEnded.size() >= MO_SAMPLEDDATATXENDED_SIZE_MAX) {\n            int deltaMin = std::numeric_limits<int>::max();\n            size_t indexMin = sampledDataTxEnded.size();\n            for (size_t i = 1; i + 1 <= sampledDataTxEnded.size(); i++) {\n                size_t t0 = sampledDataTxEnded.size() - i - 1;\n                size_t t1 = sampledDataTxEnded.size() - i;\n\n                auto delta = sampledDataTxEnded[t1]->getTimestamp() - sampledDataTxEnded[t0]->getTimestamp();\n\n                if (delta < deltaMin) {\n                    deltaMin = delta;\n                    indexMin = t1;\n                }\n            }\n\n            sampledDataTxEnded.erase(sampledDataTxEnded.begin() + indexMin);\n        }\n\n        sampledDataTxEnded.push_back(std::move(mv));\n    }\n};\n\n// TransactionEventRequest (1.60.1)\nclass TransactionEventData : public MemoryManaged {\npublic:\n\n    // TransactionEventEnumType (3.80)\n    enum class Type : uint8_t {\n        Ended,\n        Started,\n        Updated\n    };\n\n    // ChargingStateEnumType (3.16)\n    enum class ChargingState : uint8_t {\n        UNDEFINED, // not part of OCPP\n        Charging,\n        EVConnected,\n        SuspendedEV,\n        SuspendedEVSE,\n        Idle\n    };\n\n//private:\n    Transaction *transaction;\n    Type eventType;\n    Timestamp timestamp;\n    uint16_t bootNr = 0;\n    TransactionEventTriggerReason triggerReason;\n    const unsigned int seqNo;\n    bool offline = false;\n    int numberOfPhasesUsed = -1;\n    int cableMaxCurrent = -1;\n    int reservationId = -1;\n    int remoteStartId = -1;\n\n    // TransactionType (2.48)\n    ChargingState chargingState = ChargingState::UNDEFINED;\n    //int timeSpentCharging = 0; // not supported\n    std::unique_ptr<IdToken> idToken;\n    EvseId evse = -1;\n    //meterValue not supported\n    Vector<std::unique_ptr<MeterValue>> meterValue;\n    \n    unsigned int opNr = 0;\n    unsigned int attemptNr = 0;\n    Timestamp attemptTime = MIN_TIME;\n\n    TransactionEventData(Transaction *transaction, unsigned int seqNo) : MemoryManaged(\"v201.Transactions.TransactionEventData\"), transaction(transaction), seqNo(seqNo), meterValue(makeVector<std::unique_ptr<MeterValue>>(getMemoryTag())) { }\n};\n\nconst char *serializeTransactionStoppedReason(Transaction::StoppedReason stoppedReason);\nbool deserializeTransactionStoppedReason(const char *stoppedReasonCstr, Transaction::StoppedReason& stoppedReasonOut);\n\nconst char *serializeTransactionEventType(TransactionEventData::Type type);\nbool deserializeTransactionEventType(const char *typeCstr, TransactionEventData::Type& typeOut);\n\nconst char *serializeTransactionEventTriggerReason(TransactionEventTriggerReason triggerReason);\nbool deserializeTransactionEventTriggerReason(const char *triggerReasonCstr, TransactionEventTriggerReason& triggerReasonOut);\n\nconst char *serializeTransactionEventChargingState(TransactionEventData::ChargingState chargingState);\nbool deserializeTransactionEventChargingState(const char *chargingStateCstr, TransactionEventData::ChargingState& chargingStateOut);\n\n\n} // namespace Ocpp201\n} // namespace MicroOcpp\n\n#endif // MO_ENABLE_V201\n\nextern \"C\" {\n#endif //__cplusplus\n\nstruct OCPP_Transaction;\ntypedef struct OCPP_Transaction OCPP_Transaction;\n\n/*\n * Compat mode for transactions. This means that all following C-wrapper functions will interprete the handle as v201 transactions\n */\n#if MO_ENABLE_V201\nvoid ocpp_tx_compat_setV201(bool isV201); //if set, all OCPP_Transaction* handles are treated as v201 transactions\n#endif\n\nint ocpp_tx_getTransactionId(OCPP_Transaction *tx);\n#if MO_ENABLE_V201\nconst char *ocpp_tx_getTransactionIdV201(OCPP_Transaction *tx);\n#endif\n\nbool ocpp_tx_isAuthorized(OCPP_Transaction *tx);\nbool ocpp_tx_isIdTagDeauthorized(OCPP_Transaction *tx);\n\nbool ocpp_tx_isRunning(OCPP_Transaction *tx);\nbool ocpp_tx_isActive(OCPP_Transaction *tx);\nbool ocpp_tx_isAborted(OCPP_Transaction *tx);\nbool ocpp_tx_isCompleted(OCPP_Transaction *tx);\n\nconst char *ocpp_tx_getIdTag(OCPP_Transaction *tx);\n\nconst char *ocpp_tx_getParentIdTag(OCPP_Transaction *tx);\n\nbool ocpp_tx_getBeginTimestamp(OCPP_Transaction *tx, char *buf, size_t len);\n\nint32_t ocpp_tx_getMeterStart(OCPP_Transaction *tx);\n\nbool ocpp_tx_getStartTimestamp(OCPP_Transaction *tx, char *buf, size_t len);\n\nconst char *ocpp_tx_getStopIdTag(OCPP_Transaction *tx);\n\nint32_t ocpp_tx_getMeterStop(OCPP_Transaction *tx);\nvoid ocpp_tx_setMeterStop(OCPP_Transaction* tx, int32_t meter);\n\nbool ocpp_tx_getStopTimestamp(OCPP_Transaction *tx, char *buf, size_t len);\n\nconst char *ocpp_tx_getStopReason(OCPP_Transaction *tx);\n\n#ifdef __cplusplus\n} //end extern \"C\"\n#endif\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Transactions/TransactionDefs.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_TRANSACTIONDEFS_H\n#define MO_TRANSACTIONDEFS_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#define MO_TXID_LEN_MAX 36\n\n#endif //MO_ENABLE_V201\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Transactions/TransactionDeserialize.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <limits>\n\n#include <MicroOcpp/Model/Transactions/TransactionDeserialize.h>\n#include <MicroOcpp/Debug.h>\n\nnamespace MicroOcpp {\n\nbool serializeSendStatus(SendStatus& status, JsonObject out) {\n    if (status.isRequested()) {\n        out[\"requested\"] = true;\n    }\n    if (status.isConfirmed()) {\n        out[\"confirmed\"] = true;\n    }\n    out[\"opNr\"] = status.getOpNr();\n    if (status.getAttemptNr() != 0) {\n        out[\"attemptNr\"] = status.getAttemptNr();\n    }\n    if (status.getAttemptTime() > MIN_TIME) {\n        char attemptTime [JSONDATE_LENGTH + 1];\n        status.getAttemptTime().toJsonString(attemptTime, sizeof(attemptTime));\n        out[\"attemptTime\"] = attemptTime;\n    }\n    return true;\n}\n\nbool deserializeSendStatus(SendStatus& status, JsonObject in) {\n    if (in[\"requested\"] | false) {\n        status.setRequested();\n    }\n    if (in[\"confirmed\"] | false) {\n        status.confirm();\n    }\n    unsigned int opNr = in[\"opNr\"] | (unsigned int)0;\n    if (opNr >= 10) { //10 is first valid tx-related opNr\n        status.setOpNr(opNr);\n    }\n    status.setAttemptNr(in[\"attemptNr\"] | (unsigned int)0);\n    if (in.containsKey(\"attemptTime\")) {\n        Timestamp attemptTime;\n        if (!attemptTime.setTime(in[\"attemptTime\"] | \"_Invalid\")) {\n            MO_DBG_ERR(\"deserialization error\");\n            return false;\n        }\n        status.setAttemptTime(attemptTime);\n    }\n    return true;\n}\n\nbool serializeTransaction(Transaction& tx, JsonDoc& out) {\n    out = initJsonDoc(\"v16.Transactions.TransactionDeserialize\", 1024);\n    JsonObject state = out.to<JsonObject>();\n\n    JsonObject sessionState = state.createNestedObject(\"session\");\n    if (!tx.isActive()) {\n        sessionState[\"active\"] = false;\n    }\n    if (tx.getIdTag()[0] != '\\0') {\n        sessionState[\"idTag\"] = tx.getIdTag();\n    }\n    if (tx.getParentIdTag()[0] != '\\0') {\n        sessionState[\"parentIdTag\"] = tx.getParentIdTag();\n    }\n    if (tx.isAuthorized()) {\n        sessionState[\"authorized\"] = true;\n    }\n    if (tx.isIdTagDeauthorized()) {\n        sessionState[\"deauthorized\"] = true;\n    }\n    if (tx.getBeginTimestamp() > MIN_TIME) {\n        char timeStr [JSONDATE_LENGTH + 1] = {'\\0'};\n        tx.getBeginTimestamp().toJsonString(timeStr, JSONDATE_LENGTH + 1);\n        sessionState[\"timestamp\"] = timeStr;\n    }\n    if (tx.getReservationId() >= 0) {\n        sessionState[\"reservationId\"] = tx.getReservationId();\n    }\n    if (tx.getTxProfileId() >= 0) {\n        sessionState[\"txProfileId\"] = tx.getTxProfileId();\n    }\n\n    JsonObject txStart = state.createNestedObject(\"start\");\n\n    if (!serializeSendStatus(tx.getStartSync(), txStart)) {\n        return false;\n    }\n\n    if (tx.isMeterStartDefined()) {\n        txStart[\"meter\"] = tx.getMeterStart();\n    }\n\n    char startTimeStr [JSONDATE_LENGTH + 1] = {'\\0'};\n    tx.getStartTimestamp().toJsonString(startTimeStr, JSONDATE_LENGTH + 1);\n    txStart[\"timestamp\"] = startTimeStr;\n\n    txStart[\"bootNr\"] = tx.getStartBootNr();\n\n    if (tx.getStartSync().isConfirmed()) {\n        txStart[\"transactionId\"] = tx.getTransactionId();\n    }\n\n    JsonObject txStop = state.createNestedObject(\"stop\");\n\n    if (!serializeSendStatus(tx.getStopSync(), txStop)) {\n        return false;\n    }\n\n    if (tx.getStopIdTag()[0] != '\\0') {\n        txStop[\"idTag\"] = tx.getStopIdTag();\n    }\n\n    if (tx.isMeterStopDefined()) {\n        txStop[\"meter\"] = tx.getMeterStop();\n    }\n\n    char stopTimeStr [JSONDATE_LENGTH + 1] = {'\\0'};\n    tx.getStopTimestamp().toJsonString(stopTimeStr, JSONDATE_LENGTH + 1);\n    txStop[\"timestamp\"] = stopTimeStr;\n    \n    txStop[\"bootNr\"] = tx.getStopBootNr();\n\n    if (tx.getStopReason()[0] != '\\0') {\n        txStop[\"reason\"] = tx.getStopReason();\n    }\n\n    if (tx.isSilent()) {\n        state[\"silent\"] = true;\n    }\n\n    if (out.overflowed()) {\n        MO_DBG_ERR(\"JSON capacity exceeded\");\n        return false;\n    }\n\n    return true;\n}\n\nbool deserializeTransaction(Transaction& tx, JsonObject state) {\n\n    JsonObject sessionState = state[\"session\"];\n\n    if (!(sessionState[\"active\"] | true)) {\n        tx.setInactive();\n    }\n\n    if (sessionState.containsKey(\"idTag\")) {\n        if (!tx.setIdTag(sessionState[\"idTag\"] | \"\")) {\n            MO_DBG_ERR(\"read err\");\n            return false;\n        }\n    }\n\n    if (sessionState.containsKey(\"parentIdTag\")) {\n        if (!tx.setParentIdTag(sessionState[\"parentIdTag\"] | \"\")) {\n            MO_DBG_ERR(\"read err\");\n            return false;\n        }\n    }\n\n    if (sessionState[\"authorized\"] | false) {\n        tx.setAuthorized();\n    }\n\n    if (sessionState[\"deauthorized\"] | false) {\n        tx.setIdTagDeauthorized();\n    }\n\n    if (sessionState.containsKey(\"timestamp\")) {\n        Timestamp timestamp;\n        if (!timestamp.setTime(sessionState[\"timestamp\"] | \"Invalid\")) {\n            MO_DBG_ERR(\"read err\");\n            return false;\n        }\n        tx.setBeginTimestamp(timestamp);\n    }\n\n    if (sessionState.containsKey(\"reservationId\")) {\n        tx.setReservationId(sessionState[\"reservationId\"] | -1);\n    }\n\n    if (sessionState.containsKey(\"txProfileId\")) {\n        tx.setTxProfileId(sessionState[\"txProfileId\"] | -1);\n    }\n\n    JsonObject txStart = state[\"start\"];\n\n    if (!deserializeSendStatus(tx.getStartSync(), txStart)) {\n        return false;\n    }\n\n    if (txStart.containsKey(\"meter\")) {\n        tx.setMeterStart(txStart[\"meter\"] | 0);\n    }\n\n    if (txStart.containsKey(\"timestamp\")) {\n        Timestamp timestamp;\n        if (!timestamp.setTime(txStart[\"timestamp\"] | \"Invalid\")) {\n            MO_DBG_ERR(\"read err\");\n            return false;\n        }\n        tx.setStartTimestamp(timestamp);\n    }\n\n    if (txStart.containsKey(\"bootNr\")) {\n        int bootNrIn = txStart[\"bootNr\"];\n        if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits<uint16_t>::max()) {\n            tx.setStartBootNr((uint16_t) bootNrIn);\n        } else {\n            MO_DBG_ERR(\"read err\");\n            return false;\n        }\n    }\n\n    if (txStart.containsKey(\"transactionId\")) {\n        tx.setTransactionId(txStart[\"transactionId\"] | -1);\n    }\n\n    JsonObject txStop = state[\"stop\"];\n\n    if (!deserializeSendStatus(tx.getStopSync(), txStop)) {\n        return false;\n    }\n\n    if (txStop.containsKey(\"idTag\")) {\n        if (!tx.setStopIdTag(txStop[\"idTag\"] | \"\")) {\n            MO_DBG_ERR(\"read err\");\n            return false;\n        }\n    }\n\n    if (txStop.containsKey(\"meter\")) {\n        tx.setMeterStop(txStop[\"meter\"] | 0);\n    }\n\n    if (txStop.containsKey(\"timestamp\")) {\n        Timestamp timestamp;\n        if (!timestamp.setTime(txStop[\"timestamp\"] | \"Invalid\")) {\n            MO_DBG_ERR(\"read err\");\n            return false;\n        }\n        tx.setStopTimestamp(timestamp);\n    }\n\n    if (txStop.containsKey(\"bootNr\")) {\n        int bootNrIn = txStop[\"bootNr\"];\n        if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits<uint16_t>::max()) {\n            tx.setStopBootNr((uint16_t) bootNrIn);\n        } else {\n            MO_DBG_ERR(\"read err\");\n            return false;\n        }\n    }\n\n    if (txStop.containsKey(\"reason\")) {\n        if (!tx.setStopReason(txStop[\"reason\"] | \"\")) {\n            MO_DBG_ERR(\"read err\");\n            return false;\n        }\n    }\n\n    if (state[\"silent\"] | false) {\n        tx.setSilent();\n    }\n\n    MO_DBG_DEBUG(\"DUMP TX (%s)\", tx.getIdTag() ? tx.getIdTag() : \"idTag missing\");\n    MO_DBG_DEBUG(\"Session   | idTag %s, active: %i, authorized: %i, deauthorized: %i\", tx.getIdTag(), tx.isActive(), tx.isAuthorized(), tx.isIdTagDeauthorized());\n    MO_DBG_DEBUG(\"Start RPC | req: %i, conf: %i\", tx.getStartSync().isRequested(), tx.getStartSync().isConfirmed());\n    MO_DBG_DEBUG(\"Stop  RPC | req: %i, conf: %i\",  tx.getStopSync().isRequested(), tx.getStopSync().isConfirmed());\n    if (tx.isSilent()) {\n        MO_DBG_DEBUG(\"          | silent Tx\");\n    }\n\n    return true;\n}\n\n}\n"
  },
  {
    "path": "src/MicroOcpp/Model/Transactions/TransactionDeserialize.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_TRANSACTIONDESERIALIZE_H\n#define MO_TRANSACTIONDESERIALIZE_H\n\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#include <ArduinoJson.h>\n\nnamespace MicroOcpp {\n\nbool serializeTransaction(Transaction& tx, JsonDoc& out);\nbool deserializeTransaction(Transaction& tx, JsonObject in);\n\n}\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Transactions/TransactionService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n\n#include <string.h>\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Operations/Authorize.h>\n#include <MicroOcpp/Operations/TransactionEvent.h>\n#include <MicroOcpp/Operations/RequestStartTransaction.h>\n#include <MicroOcpp/Operations/RequestStopTransaction.h>\n#include <MicroOcpp/Debug.h>\n\n#ifndef MO_TX_CLEAN_ABORTED\n#define MO_TX_CLEAN_ABORTED 1\n#endif\n\nusing namespace MicroOcpp;\nusing namespace MicroOcpp::Ocpp201;\n\nTransactionService::Evse::Evse(Context& context, TransactionService& txService, Ocpp201::TransactionStoreEvse& txStore, unsigned int evseId) :\n        MemoryManaged(\"v201.Transactions.TransactionServiceEvse\"),\n        context(context),\n        txService(txService),\n        txStore(txStore),\n        evseId(evseId) {\n\n    context.getRequestQueue().addSendQueue(this); //register at RequestQueue as Request emitter\n\n    txStore.discoverStoredTx(txNrBegin, txNrEnd); //initializes txNrBegin and txNrEnd\n    txNrFront = txNrBegin;\n    MO_DBG_DEBUG(\"found %u transactions for evseId %u. Internal range from %u to %u (exclusive)\", (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT, evseId, txNrBegin, txNrEnd);\n\n    unsigned int txNrLatest = (txNrEnd + MAX_TX_CNT - 1) % MAX_TX_CNT; //txNr of the most recent tx on flash\n    transaction = txStore.loadTransaction(txNrLatest); //returns nullptr if txNrLatest does not exist on flash\n}\n\nTransactionService::Evse::~Evse() {\n    \n}\n\nbool TransactionService::Evse::beginTransaction() {\n\n    if (transaction) {\n        MO_DBG_ERR(\"transaction still running\");\n        return false;\n    }\n\n    std::unique_ptr<Ocpp201::Transaction> tx;\n\n    char txId [sizeof(Ocpp201::Transaction::transactionId)];\n\n    //simple clock-based hash\n    int v = context.getModel().getClock().now() - Timestamp(2020,0,0,0,0,0);\n    unsigned int h = v;\n    h += mocpp_tick_ms();\n    h *= 749572633U;\n    h %= 24593209U;\n    for (size_t i = 0; i < sizeof(tx->transactionId) - 3; i += 2) {\n        snprintf(txId + i, 3, \"%02X\", (uint8_t)h);\n        h *= 749572633U;\n        h %= 24593209U;\n    }\n\n    //clean possible aborted tx\n    unsigned int txr = txNrEnd;\n    unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT;\n    for (unsigned int i = 0; i < txSize; i++) {\n        txr = (txr + MAX_TX_CNT - 1) % MAX_TX_CNT; //decrement by 1\n\n        std::unique_ptr<Ocpp201::Transaction> intermediateTx;\n\n        Ocpp201::Transaction *txhist = nullptr;\n        if (transaction && transaction->txNr == txr) {\n            txhist = transaction.get();\n        } else if (txFront && txFront->txNr == txr) {\n            txhist = txFront;\n        } else {\n            intermediateTx = txStore.loadTransaction(txr);\n            txhist = intermediateTx.get();\n        }\n\n        //check if dangling silent tx, aborted tx, or corrupted entry (txhist == null)\n        if (!txhist || txhist->silent || (!txhist->active && !txhist->started && MO_TX_CLEAN_ABORTED)) {\n            //yes, remove\n            if (txStore.remove(txr)) {\n                if (txNrFront == txNrEnd) {\n                    txNrFront = txr;\n                }\n                txNrEnd = txr;\n                MO_DBG_WARN(\"deleted dangling silent or aborted tx for new transaction\");\n            } else {\n                MO_DBG_ERR(\"memory corruption\");\n                break;\n            }\n        } else {\n            //no, tx record trimmed, end\n            break;\n        }\n    }\n\n    txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT; //refresh after cleaning txs\n\n    //try to create new transaction\n    if (txSize < MO_TXRECORD_SIZE) {\n        tx = txStore.createTransaction(txNrEnd, txId);\n    }\n\n    if (!tx) {\n        //could not create transaction - now, try to replace tx history entry\n\n        unsigned int txl = txNrBegin;\n        txSize = (txNrEnd + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT;\n\n        for (unsigned int i = 0; i < txSize; i++) {\n\n            if (tx) {\n                //success, finished here\n                break;\n            }\n\n            //no transaction allocated, delete history entry to make space\n            std::unique_ptr<Ocpp201::Transaction> intermediateTx;\n\n            Ocpp201::Transaction *txhist = nullptr;\n            if (transaction && transaction->txNr == txl) {\n                txhist = transaction.get();\n            } else if (txFront && txFront->txNr == txl) {\n                txhist = txFront;\n            } else {\n                intermediateTx = txStore.loadTransaction(txl);\n                txhist = intermediateTx.get();\n            }\n\n            //oldest entry, now check if it's history and can be removed or corrupted entry\n            if (!txhist || (txhist->stopped && txhist->seqNos.empty()) || (!txhist->active && !txhist->started) || (txhist->silent && txhist->stopped)) {\n                //yes, remove\n\n                if (txStore.remove(txl)) {\n                    txNrBegin = (txl + 1) % MAX_TX_CNT;\n                    if (txNrFront == txl) {\n                        txNrFront = txNrBegin;\n                    }\n                    MO_DBG_DEBUG(\"deleted tx history entry for new transaction\");\n                    MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n\n                    tx = txStore.createTransaction(txNrEnd, txId);\n                } else {\n                    MO_DBG_ERR(\"memory corruption\");\n                    break;\n                }\n            } else {\n                //no, end of history reached, don't delete further tx\n                MO_DBG_DEBUG(\"cannot delete more tx\");\n                break;\n            }\n\n            txl++;\n            txl %= MAX_TX_CNT;\n        }\n    }\n\n    if (!tx) {\n        //couldn't create normal transaction -> check if to start charging without real transaction\n        if (txService.silentOfflineTransactionsBool && txService.silentOfflineTransactionsBool->getBool()) {\n            //try to handle charging session without sending StartTx or StopTx to the server\n            tx = txStore.createTransaction(txNrEnd, txId);\n\n            if (tx) {\n                tx->silent = true;\n                MO_DBG_DEBUG(\"created silent transaction\");\n            }\n        }\n    }\n\n    if (!tx) {\n        MO_DBG_ERR(\"transaction queue full\");\n        return false;\n    }\n\n    tx->beginTimestamp = context.getModel().getClock().now();\n\n    if (!txStore.commit(tx.get())) {\n        MO_DBG_ERR(\"fs error\");\n        return false;\n    }\n\n    transaction = std::move(tx);\n\n    txNrEnd = (txNrEnd + 1) % MAX_TX_CNT;\n    MO_DBG_DEBUG(\"advance txNrEnd %u-%u\", evseId, txNrEnd);\n    MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n\n    return true;\n}\n\nbool TransactionService::Evse::endTransaction(Ocpp201::Transaction::StoppedReason stoppedReason = Ocpp201::Transaction::StoppedReason::Other, Ocpp201::TransactionEventTriggerReason stopTrigger = Ocpp201::TransactionEventTriggerReason::AbnormalCondition) {\n\n    if (!transaction || !transaction->active) {\n        //transaction already ended / not active anymore\n        return false;\n    }\n\n    MO_DBG_DEBUG(\"End transaction started by idTag %s\",\n                            transaction->idToken.get());\n\n    transaction->active = false;\n    transaction->stopTrigger = stopTrigger;\n    transaction->stoppedReason = stoppedReason;\n    txStore.commit(transaction.get());\n\n    return true;\n}\n\nvoid TransactionService::Evse::loop() {\n\n    if (transaction && !transaction->active && !transaction->started) {\n        MO_DBG_DEBUG(\"collect aborted transaction %u-%s\", evseId, transaction->transactionId);\n        if (txFront == transaction.get()) {\n            MO_DBG_DEBUG(\"pass ownership from tx to txFront\");\n            txFrontCache = std::move(transaction);\n        }\n        transaction = nullptr;\n    }\n\n    if (transaction && transaction->stopped) {\n        MO_DBG_DEBUG(\"collect obsolete transaction %u-%s\", evseId, transaction->transactionId);\n        if (txFront == transaction.get()) {\n            MO_DBG_DEBUG(\"pass ownership from tx to txFront\");\n            txFrontCache = std::move(transaction);\n        }\n        transaction = nullptr;\n    }\n\n    // tx-related behavior\n    if (transaction) {\n        if (connectorPluggedInput) {\n            if (connectorPluggedInput()) {\n                // if cable has been plugged at least once, EVConnectionTimeout will never get triggered\n                transaction->evConnectionTimeoutListen = false;\n            }\n\n            if (transaction->active &&\n                    transaction->evConnectionTimeoutListen &&\n                    transaction->beginTimestamp > MIN_TIME &&\n                    txService.evConnectionTimeOutInt && txService.evConnectionTimeOutInt->getInt() > 0 &&\n                    !connectorPluggedInput() &&\n                    context.getModel().getClock().now() - transaction->beginTimestamp >= txService.evConnectionTimeOutInt->getInt()) {\n\n                MO_DBG_INFO(\"Session mngt: timeout\");\n                endTransaction(Ocpp201::Transaction::StoppedReason::Timeout, TransactionEventTriggerReason::EVConnectTimeout);\n\n                updateTxNotification(TxNotification_ConnectionTimeout);\n            }\n\n            if (transaction->active &&\n                    transaction->isDeauthorized &&\n                    !transaction->started &&\n                    (txService.isTxStartPoint(TxStartStopPoint::Authorized) || txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed) ||\n                     txService.isTxStopPoint(TxStartStopPoint::Authorized)  || txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed))) {\n                \n                MO_DBG_INFO(\"Session mngt: Deauthorized before start\");\n                endTransaction(Ocpp201::Transaction::StoppedReason::DeAuthorized, TransactionEventTriggerReason::Deauthorized);\n            }\n        }\n    }\n\n    std::unique_ptr<TransactionEventData> txEvent;\n\n    bool txStopCondition = false;\n\n    {\n        // stop tx?\n\n        TransactionEventTriggerReason triggerReason = TransactionEventTriggerReason::UNDEFINED;\n        Ocpp201::Transaction::StoppedReason stoppedReason = Ocpp201::Transaction::StoppedReason::UNDEFINED;\n\n        if (transaction && !transaction->active) {\n            // tx ended via endTransaction\n            txStopCondition = true;\n            triggerReason = transaction->stopTrigger;\n            stoppedReason = transaction->stoppedReason;\n        } else if ((txService.isTxStopPoint(TxStartStopPoint::EVConnected) ||\n                    txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) &&\n                    connectorPluggedInput && !connectorPluggedInput() &&\n                    (txService.stopTxOnEVSideDisconnectBool->getBool() || !transaction || !transaction->started)) {\n            txStopCondition = true;\n            triggerReason = TransactionEventTriggerReason::EVCommunicationLost;\n            stoppedReason = Ocpp201::Transaction::StoppedReason::EVDisconnected;\n        } else if ((txService.isTxStopPoint(TxStartStopPoint::Authorized) ||\n                    txService.isTxStopPoint(TxStartStopPoint::PowerPathClosed)) &&\n                    (!transaction || !transaction->isAuthorizationActive)) {\n            // user revoked authorization (or EV or any \"local\" entity)\n            txStopCondition = true;\n            triggerReason = TransactionEventTriggerReason::StopAuthorized;\n            stoppedReason = Ocpp201::Transaction::StoppedReason::Local;\n        } else if (txService.isTxStopPoint(TxStartStopPoint::EnergyTransfer) &&\n                    evReadyInput && !evReadyInput()) {\n            txStopCondition = true;\n            triggerReason = TransactionEventTriggerReason::ChargingStateChanged;\n            stoppedReason = Ocpp201::Transaction::StoppedReason::StoppedByEV;\n        } else if (txService.isTxStopPoint(TxStartStopPoint::EnergyTransfer) &&\n                    (evReadyInput || evseReadyInput) && // at least one of the two defined\n                    !(evReadyInput && evReadyInput()) &&\n                    !(evseReadyInput && evseReadyInput())) {\n            txStopCondition = true;\n            triggerReason = TransactionEventTriggerReason::ChargingStateChanged;\n            stoppedReason = Ocpp201::Transaction::StoppedReason::Other;\n        } else if (txService.isTxStopPoint(TxStartStopPoint::Authorized) &&\n                    transaction && transaction->isDeauthorized &&\n                    txService.stopTxOnInvalidIdBool->getBool()) {\n            // OCPP server rejected authorization\n            txStopCondition = true;\n            triggerReason = TransactionEventTriggerReason::Deauthorized;\n            stoppedReason = Ocpp201::Transaction::StoppedReason::DeAuthorized;\n        }\n\n        if (txStopCondition &&\n                transaction && transaction->started && transaction->active) {\n\n            MO_DBG_INFO(\"Session mngt: TxStopPoint reached\");\n            endTransaction(stoppedReason, triggerReason);\n        }\n\n        if (transaction &&\n                transaction->started && !transaction->stopped && !transaction->active &&\n                (!stopTxReadyInput || stopTxReadyInput())) {\n            // yes, stop running tx\n\n            txEvent = txStore.createTransactionEvent(*transaction);\n            if (!txEvent) {\n                // OOM\n                return;\n            }\n\n            transaction->stopTrigger = triggerReason;\n            transaction->stoppedReason = stoppedReason;\n\n            txEvent->eventType = TransactionEventData::Type::Ended;\n            txEvent->triggerReason = triggerReason;\n        }\n    } \n    \n    if (!txStopCondition) {\n        // start tx?\n\n        bool txStartCondition = false;\n\n        TransactionEventTriggerReason triggerReason = TransactionEventTriggerReason::UNDEFINED;\n\n        // tx should be started?\n        if (txService.isTxStartPoint(TxStartStopPoint::PowerPathClosed) &&\n                    (!connectorPluggedInput || connectorPluggedInput()) &&\n                    transaction && transaction->isAuthorizationActive && transaction->isAuthorized) {\n            txStartCondition = true;\n            if (transaction->remoteStartId >= 0) {\n                triggerReason = TransactionEventTriggerReason::RemoteStart;\n            } else {\n                triggerReason = TransactionEventTriggerReason::CablePluggedIn;\n            }\n        } else if (txService.isTxStartPoint(TxStartStopPoint::Authorized) &&\n                    transaction && transaction->isAuthorizationActive && transaction->isAuthorized) {\n            txStartCondition = true;\n            if (transaction->remoteStartId >= 0) {\n                triggerReason = TransactionEventTriggerReason::RemoteStart;\n            } else {\n                triggerReason = TransactionEventTriggerReason::Authorized;\n            }\n        } else if (txService.isTxStartPoint(TxStartStopPoint::EVConnected) &&\n                    connectorPluggedInput && connectorPluggedInput()) {\n            txStartCondition = true;\n            triggerReason = TransactionEventTriggerReason::CablePluggedIn;\n        } else if (txService.isTxStartPoint(TxStartStopPoint::EnergyTransfer) &&\n                    (evReadyInput || evseReadyInput) && // at least one of the two defined\n                    (!evReadyInput || evReadyInput()) &&\n                    (!evseReadyInput || evseReadyInput())) {\n            txStartCondition = true;\n            triggerReason = TransactionEventTriggerReason::ChargingStateChanged;\n        }\n\n        if (txStartCondition &&\n                (!transaction || (transaction->active && !transaction->started)) &&\n                (!startTxReadyInput || startTxReadyInput())) {\n            // start tx\n\n            if (!transaction) {\n                beginTransaction();\n                if (!transaction) {\n                    // OOM\n                    return;\n                }\n                if (evseId > 0) {\n                    transaction->notifyEvseId = true;\n                }\n            }\n\n            txEvent = txStore.createTransactionEvent(*transaction);\n            if (!txEvent) {\n                // OOM\n                return;\n            }\n\n            txEvent->eventType = TransactionEventData::Type::Started;\n            txEvent->triggerReason = triggerReason;\n        }\n    }\n\n    TransactionEventData::ChargingState chargingState = TransactionEventData::ChargingState::Idle;\n    if (connectorPluggedInput && !connectorPluggedInput()) {\n        chargingState = TransactionEventData::ChargingState::Idle;\n    } else if (!transaction || !transaction->isAuthorizationActive || !transaction->isAuthorized) {\n        chargingState = TransactionEventData::ChargingState::EVConnected;\n    } else if (evseReadyInput && !evseReadyInput()) { \n        chargingState = TransactionEventData::ChargingState::SuspendedEVSE;\n    } else if (evReadyInput && !evReadyInput()) {\n        chargingState = TransactionEventData::ChargingState::SuspendedEV;\n    } else if (ocppPermitsCharge()) {\n        chargingState = TransactionEventData::ChargingState::Charging;\n    }\n\n    //General Metering behavior. There is another section for TxStarted, Updated and TxEnded MeterValues\n    std::unique_ptr<MicroOcpp::Ocpp201::MeterValue> mvTxUpdated;\n\n    if (transaction) {\n\n        if (txService.sampledDataTxUpdatedInterval && txService.sampledDataTxUpdatedInterval->getInt() > 0 && mocpp_tick_ms() - transaction->lastSampleTimeTxUpdated >= (unsigned long)txService.sampledDataTxUpdatedInterval->getInt() * 1000UL) {\n            transaction->lastSampleTimeTxUpdated = mocpp_tick_ms();\n            auto meteringService = context.getModel().getMeteringServiceV201();\n            auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr;\n            mvTxUpdated = meteringEvse ? meteringEvse->takeTxUpdatedMeterValue() : nullptr;\n        }\n\n        if (transaction->started && !transaction->stopped &&\n                 txService.sampledDataTxEndedInterval && txService.sampledDataTxEndedInterval->getInt() > 0 &&\n                 mocpp_tick_ms() - transaction->lastSampleTimeTxEnded >= (unsigned long)txService.sampledDataTxEndedInterval->getInt() * 1000UL) {\n            transaction->lastSampleTimeTxEnded = mocpp_tick_ms();\n            auto meteringService = context.getModel().getMeteringServiceV201();\n            auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr;\n            auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_SamplePeriodic) : nullptr;\n            if (mvTxEnded) {\n                transaction->addSampledDataTxEnded(std::move(mvTxEnded));\n            }\n        }\n    }\n\n    if (transaction) {\n        // update tx?\n\n        bool txUpdateCondition = false;\n\n        TransactionEventTriggerReason triggerReason = TransactionEventTriggerReason::UNDEFINED;\n\n        if (chargingState != trackChargingState) {\n            txUpdateCondition = true;\n            triggerReason = TransactionEventTriggerReason::ChargingStateChanged;\n            transaction->notifyChargingState = true;\n        }\n        trackChargingState = chargingState;\n\n        if ((transaction->isAuthorizationActive && transaction->isAuthorized) && !transaction->trackAuthorized) {\n            transaction->trackAuthorized = true;\n            txUpdateCondition = true;\n            if (transaction->remoteStartId >= 0) {\n                triggerReason = TransactionEventTriggerReason::RemoteStart;\n            } else {\n                triggerReason = TransactionEventTriggerReason::Authorized;\n            }\n        } else if (connectorPluggedInput && connectorPluggedInput() && !transaction->trackEvConnected) {\n            transaction->trackEvConnected = true;\n            txUpdateCondition = true;\n            triggerReason = TransactionEventTriggerReason::CablePluggedIn;\n        } else if (connectorPluggedInput && !connectorPluggedInput() && transaction->trackEvConnected) {\n            transaction->trackEvConnected = false;\n            txUpdateCondition = true;\n            triggerReason = TransactionEventTriggerReason::EVCommunicationLost;\n        } else if (!(transaction->isAuthorizationActive && transaction->isAuthorized) && transaction->trackAuthorized) {\n            transaction->trackAuthorized = false;\n            txUpdateCondition = true;\n            triggerReason = TransactionEventTriggerReason::StopAuthorized;\n        } else if (mvTxUpdated) {\n            txUpdateCondition = true;\n            triggerReason = TransactionEventTriggerReason::MeterValuePeriodic;\n        } else if (evReadyInput && evReadyInput() && !transaction->trackPowerPathClosed) {\n            transaction->trackPowerPathClosed = true;\n        } else if (evReadyInput && !evReadyInput() && transaction->trackPowerPathClosed) {\n            transaction->trackPowerPathClosed = false;\n        }\n\n        if (txUpdateCondition && !txEvent && transaction->started && !transaction->stopped) {\n            // yes, updated\n\n            txEvent = txStore.createTransactionEvent(*transaction);\n            if (!txEvent) {\n                // OOM\n                return;\n            }\n\n            txEvent->eventType = TransactionEventData::Type::Updated;\n            txEvent->triggerReason = triggerReason;\n        }\n    }\n\n    if (txEvent) {\n        txEvent->timestamp = context.getModel().getClock().now();\n        if (transaction->notifyChargingState) {\n            txEvent->chargingState = chargingState;\n            transaction->notifyChargingState = false;\n        }\n        if (transaction->notifyEvseId) {\n            txEvent->evse = EvseId(evseId, 1);\n            transaction->notifyEvseId = false;\n        }\n        if (transaction->notifyRemoteStartId) {\n            txEvent->remoteStartId = transaction->remoteStartId;\n            transaction->notifyRemoteStartId = false;\n        }\n        if (txEvent->eventType == TransactionEventData::Type::Started) {\n            auto meteringService = context.getModel().getMeteringServiceV201();\n            auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr;\n            auto mvTxStarted = meteringEvse ? meteringEvse->takeTxStartedMeterValue() : nullptr;\n            if (mvTxStarted) {\n                txEvent->meterValue.push_back(std::move(mvTxStarted));\n            }\n            auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_TransactionBegin) : nullptr;\n            if (mvTxEnded) {\n                transaction->addSampledDataTxEnded(std::move(mvTxEnded));\n            }\n            transaction->lastSampleTimeTxEnded = mocpp_tick_ms();\n            transaction->lastSampleTimeTxUpdated = mocpp_tick_ms();\n        } else if (txEvent->eventType == TransactionEventData::Type::Ended) {\n            auto meteringService = context.getModel().getMeteringServiceV201();\n            auto meteringEvse = meteringService ? meteringService->getEvse(evseId) : nullptr;\n            auto mvTxEnded = meteringEvse ? meteringEvse->takeTxEndedMeterValue(ReadingContext_TransactionEnd) : nullptr;\n            if (mvTxEnded) {\n                transaction->addSampledDataTxEnded(std::move(mvTxEnded));\n            }\n            transaction->lastSampleTimeTxEnded = mocpp_tick_ms();\n        }\n        if (mvTxUpdated) {\n            txEvent->meterValue.push_back(std::move(mvTxUpdated));\n        }\n\n        if (transaction->notifyStopIdToken && transaction->stopIdToken) {\n            txEvent->idToken = std::unique_ptr<IdToken>(new IdToken(*transaction->stopIdToken.get(), getMemoryTag()));\n            transaction->notifyStopIdToken = false;\n        }  else if (transaction->notifyIdToken) {\n            txEvent->idToken = std::unique_ptr<IdToken>(new IdToken(transaction->idToken, getMemoryTag()));\n            transaction->notifyIdToken = false;\n        }\n    }\n\n    if (txEvent) {\n        if (txEvent->eventType == TransactionEventData::Type::Started) {\n            transaction->started = true;\n        } else if (txEvent->eventType == TransactionEventData::Type::Ended) {\n            transaction->stopped = true;\n        }\n    }\n\n    if (txEvent) {\n        txEvent->opNr = context.getRequestQueue().getNextOpNr();\n        MO_DBG_DEBUG(\"enqueueing new txEvent at opNr %u\", txEvent->opNr);\n    }\n\n    if (txEvent) {\n        txStore.commit(txEvent.get());\n    }\n\n    if (txEvent) {\n        if (txEvent->eventType == TransactionEventData::Type::Started) {\n            updateTxNotification(TxNotification_StartTx);\n        } else if (txEvent->eventType == TransactionEventData::Type::Ended) {\n            updateTxNotification(TxNotification_StartTx);\n        }\n    }\n\n    //try to pass ownership to front txEvent immediatley\n    if (txEvent && !txEventFront &&\n            transaction->txNr == txNrFront &&\n            !transaction->seqNos.empty() && transaction->seqNos.front() == txEvent->seqNo) {\n\n        //txFront set up?\n        if (!txFront) {\n            txFront = transaction.get();\n        }\n\n        //keep txEvent loaded (otherwise ReqEmitter would load it again from flash)\n        MO_DBG_DEBUG(\"new txEvent is front element\");\n        txEventFront = std::move(txEvent);\n    }\n}\n\nvoid TransactionService::Evse::setConnectorPluggedInput(std::function<bool()> connectorPlugged) {\n    this->connectorPluggedInput = connectorPlugged;\n}\n\nvoid TransactionService::Evse::setEvReadyInput(std::function<bool()> evRequestsEnergy) {\n    this->evReadyInput = evRequestsEnergy;\n}\n\nvoid TransactionService::Evse::setEvseReadyInput(std::function<bool()> connectorEnergized) {\n    this->evseReadyInput = connectorEnergized;\n}\n\nvoid TransactionService::Evse::setTxNotificationOutput(std::function<void(Ocpp201::Transaction*, TxNotification)> txNotificationOutput) {\n    this->txNotificationOutput = txNotificationOutput;\n}\n\nvoid TransactionService::Evse::updateTxNotification(TxNotification event) {\n    if (txNotificationOutput) {\n        txNotificationOutput(transaction.get(), event);\n    }\n}\n\nbool TransactionService::Evse::beginAuthorization(IdToken idToken, bool validateIdToken) {\n    MO_DBG_DEBUG(\"begin auth: %s\", idToken.get());\n\n    if (transaction && transaction->isAuthorizationActive) {\n        MO_DBG_WARN(\"tx process still running. Please call endTransaction(...) before\");\n        return false;\n    }\n\n    if (!transaction) {\n        beginTransaction();\n        if (!transaction) {\n            MO_DBG_ERR(\"could not allocate Tx\");\n            return false;\n        }\n        if (evseId > 0) {\n            transaction->notifyEvseId = true;\n        }\n    }\n\n    transaction->isAuthorizationActive = true;\n    transaction->idToken = idToken;\n    transaction->beginTimestamp = context.getModel().getClock().now();\n\n    if (validateIdToken) {\n        auto authorize = makeRequest(new Authorize(context.getModel(), idToken));\n        if (!authorize) {\n            // OOM\n            abortTransaction();\n            return false;\n        }\n\n        char txId [sizeof(transaction->transactionId)]; //capture txId to check if transaction reference is still the same\n        snprintf(txId, sizeof(txId), \"%s\", transaction->transactionId);\n\n        authorize->setOnReceiveConfListener([this, txId] (JsonObject response) {\n            auto tx = getTransaction();\n            if (!tx || strcmp(tx->transactionId, txId)) {\n                MO_DBG_INFO(\"dangling Authorize -- discard\");\n                return;\n            }\n\n            if (strcmp(response[\"idTokenInfo\"][\"status\"] | \"_Undefined\", \"Accepted\")) {\n                MO_DBG_DEBUG(\"Authorize rejected (%s), abort tx process\", tx->idToken.get());\n                tx->isDeauthorized = true;\n                txStore.commit(tx);\n\n                updateTxNotification(TxNotification_AuthorizationRejected);\n                return;\n            }\n\n            MO_DBG_DEBUG(\"Authorized tx with validation (%s)\", tx->idToken.get());\n            tx->isAuthorized = true;\n            tx->notifyIdToken = true;\n            txStore.commit(tx);\n\n            updateTxNotification(TxNotification_Authorized);\n        });\n        authorize->setOnAbortListener([this, txId] () {\n            auto tx = getTransaction();\n            if (!tx || strcmp(tx->transactionId, txId)) {\n                MO_DBG_INFO(\"dangling Authorize -- discard\");\n                return;\n            }\n\n            MO_DBG_DEBUG(\"Authorize timeout (%s)\", tx->idToken.get());\n            tx->isDeauthorized = true;\n            txStore.commit(tx);\n\n            updateTxNotification(TxNotification_AuthorizationTimeout);\n        });\n        authorize->setTimeout(20 * 1000);\n        context.initiateRequest(std::move(authorize));\n    } else {\n        MO_DBG_DEBUG(\"Authorized tx directly (%s)\", transaction->idToken.get());\n        transaction->isAuthorized = true;\n        transaction->notifyIdToken = true;\n        txStore.commit(transaction.get());\n\n        updateTxNotification(TxNotification_Authorized);\n    }\n\n    return true;\n}\nbool TransactionService::Evse::endAuthorization(IdToken idToken, bool validateIdToken) {\n\n    if (!transaction || !transaction->isAuthorizationActive) {\n        //transaction already ended / not active anymore\n        return false;\n    }\n\n    MO_DBG_DEBUG(\"End session started by idTag %s\",\n                            transaction->idToken.get());\n    \n    if (transaction->idToken.equals(idToken)) {\n        // use same idToken like tx start\n        transaction->isAuthorizationActive = false;\n        transaction->notifyIdToken = true;\n        txStore.commit(transaction.get());\n\n        updateTxNotification(TxNotification_Authorized);\n    } else if (!validateIdToken) {\n        transaction->stopIdToken = std::unique_ptr<IdToken>(new IdToken(idToken, getMemoryTag()));\n        transaction->isAuthorizationActive = false;\n        transaction->notifyStopIdToken = true;\n        txStore.commit(transaction.get());\n\n        updateTxNotification(TxNotification_Authorized);\n    } else {\n        // use a different idToken for stopping the tx\n\n        auto authorize = makeRequest(new Authorize(context.getModel(), idToken));\n        if (!authorize) {\n            // OOM\n            abortTransaction();\n            return false;\n        }\n\n        char txId [sizeof(transaction->transactionId)]; //capture txId to check if transaction reference is still the same\n        snprintf(txId, sizeof(txId), \"%s\", transaction->transactionId);\n\n        authorize->setOnReceiveConfListener([this, txId, idToken] (JsonObject response) {\n            auto tx = getTransaction();\n            if (!tx || strcmp(tx->transactionId, txId)) {\n                MO_DBG_INFO(\"dangling Authorize -- discard\");\n                return;\n            }\n\n            if (strcmp(response[\"idTokenInfo\"][\"status\"] | \"_Undefined\", \"Accepted\")) {\n                MO_DBG_DEBUG(\"Authorize rejected (%s), don't stop tx\", idToken.get());\n\n                updateTxNotification(TxNotification_AuthorizationRejected);\n                return;\n            }\n\n            MO_DBG_DEBUG(\"Authorized transaction stop (%s)\", idToken.get());\n\n            tx->stopIdToken = std::unique_ptr<IdToken>(new IdToken(idToken, getMemoryTag()));\n            if (!tx->stopIdToken) {\n                // OOM\n                if (tx->active) {\n                    abortTransaction();\n                }\n                return;\n            }\n\n            tx->isAuthorizationActive = false;\n            tx->notifyStopIdToken = true;\n            txStore.commit(tx);\n\n            updateTxNotification(TxNotification_Authorized);\n        });\n        authorize->setOnTimeoutListener([this, txId] () {\n            auto tx = getTransaction();\n            if (!tx || strcmp(tx->transactionId, txId)) {\n                MO_DBG_INFO(\"dangling Authorize -- discard\");\n                return;\n            }\n\n            updateTxNotification(TxNotification_AuthorizationTimeout);\n        });\n        authorize->setTimeout(20 * 1000);\n        context.initiateRequest(std::move(authorize));\n    }\n\n    return true;\n}\nbool TransactionService::Evse::abortTransaction(Ocpp201::Transaction::StoppedReason stoppedReason, TransactionEventTriggerReason stopTrigger) {\n    return endTransaction(stoppedReason, stopTrigger);\n}\nMicroOcpp::Ocpp201::Transaction *TransactionService::Evse::getTransaction() {\n    return transaction.get();\n}\n\nbool TransactionService::Evse::ocppPermitsCharge() {\n    return transaction &&\n           transaction->active &&\n           transaction->isAuthorizationActive &&\n           transaction->isAuthorized &&\n           !transaction->isDeauthorized;\n}\n\nunsigned int TransactionService::Evse::getFrontRequestOpNr() {\n\n    if (txEventFront) {\n        return txEventFront->opNr;\n    }\n\n    /*\n     * Advance front transaction?\n     */\n\n    unsigned int txSize = (txNrEnd + MAX_TX_CNT - txNrFront) % MAX_TX_CNT;\n\n    if (txFront && txSize == 0) {\n        //catch edge case where txBack has been rolled back and txFront was equal to txBack\n        MO_DBG_DEBUG(\"collect front transaction %u-%u after tx rollback\", evseId, txFront->txNr);\n        MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n        txEventFront = nullptr;\n        txFrontCache = nullptr;\n        txFront = nullptr;\n    }\n\n    for (unsigned int i = 0; i < txSize; i++) {\n\n        if (!txFront) {\n            if (transaction && transaction->txNr == txNrFront) {\n                txFront = transaction.get();\n            } else {\n                txFrontCache = txStore.loadTransaction(txNrFront);\n                txFront = txFrontCache.get();\n            }\n\n            if (txFront) {\n                MO_DBG_DEBUG(\"load front transaction %u-%u\", evseId, txFront->txNr);\n                (void)0;\n            }\n        }\n\n        if (!txFront || (txFront && ((!txFront->active && !txFront->started) || (txFront->stopped && txFront->seqNos.empty()) || txFront->silent))) {\n            //advance front\n            MO_DBG_DEBUG(\"collect front transaction %u-%u\", evseId, txNrFront);\n            txEventFront = nullptr;\n            txFrontCache = nullptr;\n            txFront = nullptr;\n            txNrFront = (txNrFront + 1) % MAX_TX_CNT;\n            MO_DBG_VERBOSE(\"txNrBegin=%u, txNrFront=%u, txNrEnd=%u\", txNrBegin, txNrFront, txNrEnd);\n        } else {\n            //front is accurate. Done here\n            break;\n        }\n    }\n\n    if (txFront && !txFront->seqNos.empty()) {\n        MO_DBG_DEBUG(\"load front txEvent %u-%u-%u from flash\", evseId, txFront->txNr, txFront->seqNos.front());\n        txEventFront = txStore.loadTransactionEvent(*txFront, txFront->seqNos.front());\n    }\n\n    if (txEventFront) {\n        return txEventFront->opNr;\n    }\n\n    return NoOperation;\n}\n\nstd::unique_ptr<Request> TransactionService::Evse::fetchFrontRequest() {\n\n    if (!txEventFront) {\n        return nullptr;\n    }\n\n    if (txFront && txFront->silent) {\n        return nullptr;\n    }\n\n    if (txEventFront->seqNo == 0 &&\n            txEventFront->timestamp < MIN_TIME &&\n            txEventFront->bootNr != context.getModel().getBootNr()) {\n        //time not set, cannot be restored anymore -> invalid tx\n        MO_DBG_ERR(\"cannot recover tx from previous power cycle\");\n\n        txFront->silent = true;\n        txFront->active = false;\n        txStore.commit(txFront);\n\n        //clean txEvents early\n        auto seqNos = txFront->seqNos;\n        for (size_t i = 0; i < seqNos.size(); i++) {\n            txStore.remove(*txFront, seqNos[i]);\n        }\n        //last remove should keep tx201 file with only tx record and without txEvent\n\n        //next getFrontRequestOpNr() call will collect txFront\n        return nullptr;\n    }\n\n    if ((int)txEventFront->attemptNr >= txService.messageAttemptsTransactionEventInt->getInt()) {\n        MO_DBG_WARN(\"exceeded TransactionMessageAttempts. Discard txEvent\");\n\n        txStore.remove(*txFront, txEventFront->seqNo);\n        txEventFront = nullptr;\n        return nullptr;\n    }\n\n    Timestamp nextAttempt = txEventFront->attemptTime +\n                            txEventFront->attemptNr * std::max(0, txService.messageAttemptIntervalTransactionEventInt->getInt());\n\n    if (nextAttempt > context.getModel().getClock().now()) {\n        return nullptr;\n    }\n\n    if (txEventFrontIsRequested) {\n        //ensure that only one TransactionEvent request is being executed at the same time\n        return nullptr;\n    }\n\n    txEventFront->attemptNr++;\n    txEventFront->attemptTime = context.getModel().getClock().now();\n    txStore.commit(txEventFront.get());\n\n    auto txEventRequest = makeRequest(new TransactionEvent(context.getModel(), txEventFront.get()));\n    txEventRequest->setOnReceiveConfListener([this] (JsonObject) {\n        MO_DBG_DEBUG(\"completed front txEvent\");\n        txStore.remove(*txFront, txEventFront->seqNo);\n        txEventFront = nullptr;\n        txEventFrontIsRequested = false;\n    });\n    txEventRequest->setOnAbortListener([this] () {\n        MO_DBG_DEBUG(\"unsuccessful front txEvent\");\n        txEventFrontIsRequested = false;\n    });\n    txEventRequest->setTimeout(std::min(20, std::max(5, txService.messageAttemptIntervalTransactionEventInt->getInt())) * 1000);\n\n    txEventFrontIsRequested = true;\n\n    return txEventRequest;\n}\n\nbool TransactionService::isTxStartPoint(TxStartStopPoint check) {\n    for (auto& v : txStartPointParsed) {\n        if (v == check) {\n            return true;\n        }\n    }\n    return false;\n}\nbool TransactionService::isTxStopPoint(TxStartStopPoint check) {\n    for (auto& v : txStopPointParsed) {\n        if (v == check) {\n            return true;\n        }\n    }\n    return false;\n}\n\nbool TransactionService::parseTxStartStopPoint(const char *csl, Vector<TxStartStopPoint>& dst) {\n    dst.clear();\n\n    while (*csl == ',') {\n        csl++;\n    }\n\n    while (*csl) {\n        if (!strncmp(csl, \"ParkingBayOccupancy\", sizeof(\"ParkingBayOccupancy\") - 1)\n                && (csl[sizeof(\"ParkingBayOccupancy\") - 1] == '\\0' || csl[sizeof(\"ParkingBayOccupancy\") - 1] == ',')) {\n            dst.push_back(TxStartStopPoint::ParkingBayOccupancy);\n            csl += sizeof(\"ParkingBayOccupancy\") - 1;\n        } else if (!strncmp(csl, \"EVConnected\", sizeof(\"EVConnected\") - 1)\n                && (csl[sizeof(\"EVConnected\") - 1] == '\\0' || csl[sizeof(\"EVConnected\") - 1] == ',')) {\n            dst.push_back(TxStartStopPoint::EVConnected);\n            csl += sizeof(\"EVConnected\") - 1;\n        } else if (!strncmp(csl, \"Authorized\", sizeof(\"Authorized\") - 1)\n                && (csl[sizeof(\"Authorized\") - 1] == '\\0' || csl[sizeof(\"Authorized\") - 1] == ',')) {\n            dst.push_back(TxStartStopPoint::Authorized);\n            csl += sizeof(\"Authorized\") - 1;\n        } else if (!strncmp(csl, \"DataSigned\", sizeof(\"DataSigned\") - 1)\n                && (csl[sizeof(\"DataSigned\") - 1] == '\\0' || csl[sizeof(\"DataSigned\") - 1] == ',')) {\n            dst.push_back(TxStartStopPoint::DataSigned);\n            csl += sizeof(\"DataSigned\") - 1;\n        } else if (!strncmp(csl, \"PowerPathClosed\", sizeof(\"PowerPathClosed\") - 1)\n                && (csl[sizeof(\"PowerPathClosed\") - 1] == '\\0' || csl[sizeof(\"PowerPathClosed\") - 1] == ',')) {\n            dst.push_back(TxStartStopPoint::PowerPathClosed);\n            csl += sizeof(\"PowerPathClosed\") - 1;\n        } else if (!strncmp(csl, \"EnergyTransfer\", sizeof(\"EnergyTransfer\") - 1)\n                && (csl[sizeof(\"EnergyTransfer\") - 1] == '\\0' || csl[sizeof(\"EnergyTransfer\") - 1] == ',')) {\n            dst.push_back(TxStartStopPoint::EnergyTransfer);\n            csl += sizeof(\"EnergyTransfer\") - 1;\n        } else {\n            MO_DBG_ERR(\"unkown TxStartStopPoint\");\n            dst.clear();\n            return false;\n        }\n\n        while (*csl == ',') {\n            csl++;\n        }\n    }\n\n    return true;\n}\n\nnamespace MicroOcpp {\n\nbool validateTxStartStopPoint(const char *value, void *userPtr) {\n    auto txService = static_cast<TransactionService*>(userPtr);\n\n    auto validated = makeVector<TransactionService::TxStartStopPoint>(\"v201.Transactions.TransactionService\");\n    return txService->parseTxStartStopPoint(value, validated);\n}\n\nbool validateUnsignedInt(int val, void*) {\n    return val >= 0;\n}\n\n} //namespace MicroOcpp\n\nusing namespace MicroOcpp;\n\nTransactionService::TransactionService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem, unsigned int numEvseIds) :\n        MemoryManaged(\"v201.Transactions.TransactionService\"),\n        context(context),\n        txStore(filesystem, numEvseIds),\n        txStartPointParsed(makeVector<TxStartStopPoint>(getMemoryTag())),\n        txStopPointParsed(makeVector<TxStartStopPoint>(getMemoryTag())) {\n    auto varService = context.getModel().getVariableService();\n\n    txStartPointString = varService->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"PowerPathClosed\");\n    txStopPointString  = varService->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\",  \"PowerPathClosed\");\n    stopTxOnInvalidIdBool = varService->declareVariable<bool>(\"TxCtrlr\", \"StopTxOnInvalidId\", true);\n    stopTxOnEVSideDisconnectBool = varService->declareVariable<bool>(\"TxCtrlr\", \"StopTxOnEVSideDisconnect\", true);\n    evConnectionTimeOutInt = varService->declareVariable<int>(\"TxCtrlr\", \"EVConnectionTimeOut\", 30);\n    sampledDataTxUpdatedInterval = varService->declareVariable<int>(\"SampledDataCtrlr\", \"TxUpdatedInterval\", 0);\n    sampledDataTxEndedInterval = varService->declareVariable<int>(\"SampledDataCtrlr\", \"TxEndedInterval\", 0);\n    messageAttemptsTransactionEventInt = varService->declareVariable<int>(\"OCPPCommCtrlr\", \"MessageAttempts\", 3);\n    messageAttemptIntervalTransactionEventInt = varService->declareVariable<int>(\"OCPPCommCtrlr\", \"MessageAttemptInterval\", 60);\n    silentOfflineTransactionsBool = varService->declareVariable<bool>(\"CustomizationCtrlr\", \"SilentOfflineTransactions\", false);\n\n    varService->declareVariable<bool>(\"AuthCtrlr\", \"AuthorizeRemoteStart\", false, Variable::Mutability::ReadOnly, false);\n\n    varService->registerValidator<const char*>(\"TxCtrlr\", \"TxStartPoint\", validateTxStartStopPoint, this);\n    varService->registerValidator<const char*>(\"TxCtrlr\", \"TxStopPoint\", validateTxStartStopPoint, this);\n    varService->registerValidator<int>(\"SampledDataCtrlr\", \"TxUpdatedInterval\", validateUnsignedInt);\n    varService->registerValidator<int>(\"SampledDataCtrlr\", \"TxEndedInterval\", validateUnsignedInt);\n\n    for (unsigned int evseId = 0; evseId < std::min(numEvseIds, (unsigned int)MO_NUM_EVSEID); evseId++) {\n        if (!txStore.getEvse(evseId)) {\n            MO_DBG_ERR(\"initialization error\");\n            break;\n        }\n        evses[evseId] = new Evse(context, *this, *txStore.getEvse(evseId), evseId);\n    }\n\n    //make sure EVSE 0 will only trigger transactions if TxStartPoint is Authorized\n    if (evses[0]) {\n        evses[0]->connectorPluggedInput = [] () {return false;};\n        evses[0]->evReadyInput = [] () {return false;};\n        evses[0]->evseReadyInput = [] () {return false;};\n    }\n}\n\nTransactionService::~TransactionService() {\n    for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) {\n        delete evses[evseId];\n    }\n}\n\nvoid TransactionService::loop() {\n    for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) {\n        evses[evseId]->loop();\n    }\n\n    if (txStartPointString->getWriteCount() != trackTxStartPoint) {\n        parseTxStartStopPoint(txStartPointString->getString(), txStartPointParsed);\n    }\n\n    if (txStopPointString->getWriteCount() != trackTxStopPoint) {\n        parseTxStartStopPoint(txStopPointString->getString(), txStopPointParsed);\n    }\n\n    // assign tx on evseId 0 to an EVSE\n    if (evses[0]->transaction) {\n        //pending tx on evseId 0\n        if (evses[0]->transaction->active) {\n            for (unsigned int evseId = 1; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) {\n                if (!evses[evseId]->getTransaction() && \n                        (!evses[evseId]->connectorPluggedInput || evses[evseId]->connectorPluggedInput())) {\n                    MO_DBG_INFO(\"assign tx to evse %u\", evseId);\n                    evses[0]->transaction->notifyEvseId = true;\n                    evses[0]->transaction->evseId = evseId;\n                    evses[evseId]->transaction = std::move(evses[0]->transaction);\n                }\n            }\n        }\n    }\n}\n\nTransactionService::Evse *TransactionService::getEvse(unsigned int evseId) {\n    if (evseId >= MO_NUM_EVSEID) {\n        return nullptr;\n    }\n    return evses[evseId];\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Model/Transactions/TransactionService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Implementation of the UCs E01 - E12\n */\n\n#ifndef MO_TRANSACTIONSERVICE_H\n#define MO_TRANSACTIONSERVICE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Model/Metering/MeterValuesV201.h>\n#include <MicroOcpp/Core/RequestQueue.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#include <memory>\n#include <functional>\n\n#ifndef MO_TXRECORD_SIZE_V201\n#define MO_TXRECORD_SIZE_V201 4 //maximum number of tx to hold on flash storage\n#endif\n\nnamespace MicroOcpp {\n\nclass Context;\nclass FilesystemAdapter;\nclass Variable;\n\nclass TransactionService : public MemoryManaged {\npublic:\n\n    class Evse : public RequestEmitter, public MemoryManaged {\n    private:\n        Context& context;\n        TransactionService& txService;\n        Ocpp201::TransactionStoreEvse& txStore;\n        const unsigned int evseId;\n        unsigned int txNrCounter = 0;\n        std::unique_ptr<Ocpp201::Transaction> transaction;\n        Ocpp201::TransactionEventData::ChargingState trackChargingState = Ocpp201::TransactionEventData::ChargingState::UNDEFINED;\n\n        std::function<bool()> connectorPluggedInput;\n        std::function<bool()> evReadyInput;\n        std::function<bool()> evseReadyInput;\n\n        std::function<bool()> startTxReadyInput;\n        std::function<bool()> stopTxReadyInput;\n\n        std::function<void(Ocpp201::Transaction*,TxNotification)> txNotificationOutput;\n\n        bool beginTransaction();\n        bool endTransaction(Ocpp201::Transaction::StoppedReason stoppedReason, Ocpp201::TransactionEventTriggerReason stopTrigger);\n\n        unsigned int txNrBegin = 0; //oldest (historical) transaction on flash. Has no function, but is useful for error diagnosis\n        unsigned int txNrFront = 0; //oldest transaction which is still queued to be sent to the server\n        unsigned int txNrEnd = 0; //one position behind newest transaction\n\n        Ocpp201::Transaction *txFront = nullptr;\n        std::unique_ptr<Ocpp201::Transaction> txFrontCache; //helper owner for txFront. Empty if txFront == transaction.get()\n        std::unique_ptr<Ocpp201::TransactionEventData> txEventFront;\n        bool txEventFrontIsRequested = false;\n\n    public:\n        Evse(Context& context, TransactionService& txService, Ocpp201::TransactionStoreEvse& txStore, unsigned int evseId);\n        virtual ~Evse();\n\n        void loop();\n\n        void setConnectorPluggedInput(std::function<bool()> connectorPlugged);\n        void setEvReadyInput(std::function<bool()> evRequestsEnergy);\n        void setEvseReadyInput(std::function<bool()> connectorEnergized);\n\n        void setTxNotificationOutput(std::function<void(Ocpp201::Transaction*,TxNotification)> txNotificationOutput);\n        void updateTxNotification(TxNotification event);\n\n        bool beginAuthorization(IdToken idToken, bool validateIdToken = true); // authorize by swipe RFID\n        bool endAuthorization(IdToken idToken = IdToken(), bool validateIdToken = false); // stop authorization by swipe RFID\n\n        // stop transaction, but neither upon user request nor OCPP server request (e.g. after PowerLoss)\n        bool abortTransaction(Ocpp201::Transaction::StoppedReason stoppedReason = Ocpp201::Transaction::StoppedReason::Other, Ocpp201::TransactionEventTriggerReason stopTrigger = Ocpp201::TransactionEventTriggerReason::AbnormalCondition);\n\n        Ocpp201::Transaction *getTransaction();\n\n        bool ocppPermitsCharge();\n\n        unsigned int getFrontRequestOpNr() override;\n        std::unique_ptr<Request> fetchFrontRequest() override;\n\n        friend TransactionService;\n    };\n\n    // TxStartStopPoint (2.6.4.1)\n    enum class TxStartStopPoint : uint8_t {\n        ParkingBayOccupancy,\n        EVConnected,\n        Authorized,\n        DataSigned,\n        PowerPathClosed,\n        EnergyTransfer\n    };\n\nprivate:\n    Context& context;\n    Ocpp201::TransactionStore txStore;\n    Evse *evses [MO_NUM_EVSEID] = {nullptr};\n\n    Variable *txStartPointString = nullptr;\n    Variable *txStopPointString = nullptr;\n    Variable *stopTxOnInvalidIdBool = nullptr;\n    Variable *stopTxOnEVSideDisconnectBool = nullptr;\n    Variable *evConnectionTimeOutInt = nullptr;\n    Variable *sampledDataTxUpdatedInterval = nullptr;\n    Variable *sampledDataTxEndedInterval = nullptr;\n    Variable *messageAttemptsTransactionEventInt = nullptr;\n    Variable *messageAttemptIntervalTransactionEventInt = nullptr;\n    Variable *silentOfflineTransactionsBool = nullptr;\n    uint16_t trackTxStartPoint = -1;\n    uint16_t trackTxStopPoint = -1;\n    Vector<TxStartStopPoint> txStartPointParsed;\n    Vector<TxStartStopPoint> txStopPointParsed;\n    bool isTxStartPoint(TxStartStopPoint check);\n    bool isTxStopPoint(TxStartStopPoint check);\npublic:\n    TransactionService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem, unsigned int numEvseIds);\n    ~TransactionService();\n\n    void loop();\n\n    Evse *getEvse(unsigned int evseId);\n\n    bool parseTxStartStopPoint(const char *src, Vector<TxStartStopPoint>& dst);\n};\n\n} // namespace MicroOcpp\n\n#endif // MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Transactions/TransactionStore.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Model/Transactions/TransactionDeserialize.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nConnectorTransactionStore::ConnectorTransactionStore(TransactionStore& context, unsigned int connectorId, std::shared_ptr<FilesystemAdapter> filesystem) :\n        MemoryManaged(\"v16.Transactions.TransactionStore\"),\n        context(context),\n        connectorId(connectorId),\n        filesystem(filesystem),\n        transactions{makeVector<std::weak_ptr<Transaction>>(getMemoryTag())} {\n\n}\n\nConnectorTransactionStore::~ConnectorTransactionStore() {\n\n}\n\nstd::shared_ptr<Transaction> ConnectorTransactionStore::getTransaction(unsigned int txNr) {\n\n    //check for most recent element of cache first because of temporal locality\n    if (!transactions.empty()) {\n        if (auto cached = transactions.back().lock()) {\n            if (cached->getTxNr() == txNr) {\n                //cache hit\n                return cached;\n            }\n        }\n    }\n\n    //check all other elements (and free up unused entries)\n    auto cached = transactions.begin();\n    while (cached != transactions.end()) {\n        if (auto tx = cached->lock()) {\n            if (tx->getTxNr() == txNr) {\n                //cache hit\n                return tx;\n            }\n            cached++;\n        } else {\n            //collect outdated cache reference\n            cached = transactions.erase(cached);\n        }\n    }\n\n    //cache miss - load tx from flash if existent\n    \n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no FS adapter\");\n        return nullptr;\n    }\n\n    char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n    auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"tx\" \"-%u-%u.json\", connectorId, txNr);\n    if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n        MO_DBG_ERR(\"fn error: %i\", ret);\n        return nullptr;\n    }\n\n    size_t msize;\n    if (filesystem->stat(fn, &msize) != 0) {\n        MO_DBG_DEBUG(\"%u-%u does not exist\", connectorId, txNr);\n        return nullptr;\n    }\n\n    auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag());\n\n    if (!doc) {\n        MO_DBG_ERR(\"memory corruption\");\n        return nullptr;\n    }\n\n    auto transaction = std::allocate_shared<Transaction>(makeAllocator<Transaction>(getMemoryTag()), *this, connectorId, txNr);\n    JsonObject txJson = doc->as<JsonObject>();\n    if (!deserializeTransaction(*transaction, txJson)) {\n        MO_DBG_ERR(\"deserialization error\");\n        return nullptr;\n    }\n\n    //before adding new entry, clean cache\n    cached = transactions.begin();\n    while (cached != transactions.end()) {\n        if (cached->expired()) {\n            //collect outdated cache reference\n            cached = transactions.erase(cached);\n        } else {\n            cached++;\n        }\n    }\n\n    transactions.push_back(transaction);\n    return transaction;\n}\n\nstd::shared_ptr<Transaction> ConnectorTransactionStore::createTransaction(unsigned int txNr, bool silent) {\n\n    auto transaction = std::allocate_shared<Transaction>(makeAllocator<Transaction>(getMemoryTag()), *this, connectorId, txNr, silent);\n\n    if (!commit(transaction.get())) {\n        MO_DBG_ERR(\"FS error\");\n        return nullptr;\n    }\n\n    //before adding new entry, clean cache\n    auto cached = transactions.begin();\n    while (cached != transactions.end()) {\n        if (cached->expired()) {\n            //collect outdated cache reference\n            cached = transactions.erase(cached);\n        } else {\n            cached++;\n        }\n    }\n\n    transactions.push_back(transaction);\n    return transaction;\n}\n\nbool ConnectorTransactionStore::commit(Transaction *transaction) {\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no FS: nothing to commit\");\n        return true;\n    }\n\n    char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n    auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"tx\" \"-%u-%u.json\", connectorId, transaction->getTxNr());\n    if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n        MO_DBG_ERR(\"fn error: %i\", ret);\n        return false;\n    }\n    \n    auto txDoc = initJsonDoc(getMemoryTag());\n    if (!serializeTransaction(*transaction, txDoc)) {\n        MO_DBG_ERR(\"Serialization error\");\n        return false;\n    }\n\n    if (!FilesystemUtils::storeJson(filesystem, fn, txDoc)) {\n        MO_DBG_ERR(\"FS error\");\n        return false;\n    }\n\n    //success\n    return true;\n}\n\nbool ConnectorTransactionStore::remove(unsigned int txNr) {\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no FS: nothing to remove\");\n        return true;\n    }\n\n    char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n    auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"tx\" \"-%u-%u.json\", connectorId, txNr);\n    if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n        MO_DBG_ERR(\"fn error: %i\", ret);\n        return false;\n    }\n\n    size_t msize;\n    if (filesystem->stat(fn, &msize) != 0) {\n        MO_DBG_DEBUG(\"%s already removed\", fn);\n        return true;\n    }\n\n    MO_DBG_DEBUG(\"remove %s\", fn);\n    \n    return filesystem->remove(fn);\n}\n\nTransactionStore::TransactionStore(unsigned int nConnectors, std::shared_ptr<FilesystemAdapter> filesystem) :\n        MemoryManaged{\"v16.Transactions.TransactionStore\"}, connectors{makeVector<std::unique_ptr<ConnectorTransactionStore>>(getMemoryTag())} {\n\n    for (unsigned int i = 0; i < nConnectors; i++) {\n        connectors.push_back(std::unique_ptr<ConnectorTransactionStore>(\n            new ConnectorTransactionStore(*this, i, filesystem)));\n    }\n}\n\nbool TransactionStore::commit(Transaction *transaction) {\n    if (!transaction) {\n        MO_DBG_ERR(\"Invalid arg\");\n        return false;\n    }\n    auto connectorId = transaction->getConnectorId();\n    if (connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"Invalid tx\");\n        return false;\n    }\n    return connectors[connectorId]->commit(transaction);\n}\n\nstd::shared_ptr<Transaction> TransactionStore::getTransaction(unsigned int connectorId, unsigned int txNr) {\n    if (connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"Invalid connectorId\");\n        return nullptr;\n    }\n    return connectors[connectorId]->getTransaction(txNr);\n}\n\nstd::shared_ptr<Transaction> TransactionStore::createTransaction(unsigned int connectorId, unsigned int txNr, bool silent) {\n    if (connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"Invalid connectorId\");\n        return nullptr;\n    }\n    return connectors[connectorId]->createTransaction(txNr, silent);\n}\n\nbool TransactionStore::remove(unsigned int connectorId, unsigned int txNr) {\n    if (connectorId >= connectors.size()) {\n        MO_DBG_ERR(\"Invalid connectorId\");\n        return false;\n    }\n    return connectors[connectorId]->remove(txNr);\n}\n\n#if MO_ENABLE_V201\n\n#include <algorithm>\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nbool TransactionStoreEvse::serializeTransaction(Transaction& tx, JsonObject txJson) {\n\n    if (tx.trackEvConnected) {\n        txJson[\"trackEvConnected\"] = tx.trackEvConnected;\n    }\n\n    if (tx.trackAuthorized) {\n        txJson[\"trackAuthorized\"] = tx.trackAuthorized;\n    }\n\n    if (tx.trackDataSigned) {\n        txJson[\"trackDataSigned\"] = tx.trackDataSigned;\n    }\n\n    if (tx.trackPowerPathClosed) {\n        txJson[\"trackPowerPathClosed\"] = tx.trackPowerPathClosed;\n    }\n\n    if (tx.trackEnergyTransfer) {\n        txJson[\"trackEnergyTransfer\"] = tx.trackEnergyTransfer;\n    }\n\n    if (tx.active) {\n        txJson[\"active\"] = true;\n    }\n    if (tx.started) {\n        txJson[\"started\"] = true;\n    }\n    if (tx.stopped) {\n        txJson[\"stopped\"] = true;\n    }\n\n    if (tx.isAuthorizationActive) {\n        txJson[\"isAuthorizationActive\"] = true;\n    }\n    if (tx.isAuthorized) {\n        txJson[\"isAuthorized\"] = true;\n    }\n    if (tx.isDeauthorized) {\n        txJson[\"isDeauthorized\"] = true;\n    }\n\n    if (tx.idToken.get()) {\n        txJson[\"idToken\"][\"idToken\"] = tx.idToken.get();\n        txJson[\"idToken\"][\"type\"] = tx.idToken.getTypeCstr();\n    }\n\n    if (tx.beginTimestamp > MIN_TIME) {\n        char timeStr [JSONDATE_LENGTH + 1] = {'\\0'};\n        tx.beginTimestamp.toJsonString(timeStr, JSONDATE_LENGTH + 1);\n        txJson[\"beginTimestamp\"] = timeStr;\n    }\n\n    if (tx.remoteStartId >= 0) {\n        txJson[\"remoteStartId\"] = tx.remoteStartId;\n    }\n\n    if (tx.evConnectionTimeoutListen) {\n        txJson[\"evConnectionTimeoutListen\"] = true;\n    }\n\n    if (serializeTransactionStoppedReason(tx.stoppedReason)) { // optional\n        txJson[\"stoppedReason\"] = serializeTransactionStoppedReason(tx.stoppedReason);\n    }\n\n    if (serializeTransactionEventTriggerReason(tx.stopTrigger)) {\n        txJson[\"stopTrigger\"] = serializeTransactionEventTriggerReason(tx.stopTrigger);\n    }\n\n    if (tx.stopIdToken) {\n        JsonObject stopIdToken = txJson.createNestedObject(\"stopIdToken\");\n        stopIdToken[\"idToken\"] = tx.stopIdToken->get();\n        stopIdToken[\"type\"] = tx.stopIdToken->getTypeCstr();\n    }\n\n    //sampledDataTxEnded not supported yet\n\n    if (tx.silent) {\n        txJson[\"silent\"] = true;\n    }\n\n    txJson[\"txId\"] = (const char*)tx.transactionId; //force zero-copy\n\n    return true;\n}\n\nbool TransactionStoreEvse::deserializeTransaction(Transaction& tx, JsonObject txJson) {\n\n    if (txJson.containsKey(\"trackEvConnected\") && !txJson[\"trackEvConnected\"].is<bool>()) {\n        return false;\n    }\n    tx.trackEvConnected = txJson[\"trackEvConnected\"] | false;\n\n    if (txJson.containsKey(\"trackAuthorized\") && !txJson[\"trackAuthorized\"].is<bool>()) {\n        return false;\n    }\n    tx.trackAuthorized = txJson[\"trackAuthorized\"] | false;\n\n    if (txJson.containsKey(\"trackDataSigned\") && !txJson[\"trackDataSigned\"].is<bool>()) {\n        return false;\n    }\n    tx.trackDataSigned = txJson[\"trackDataSigned\"] | false;\n\n    if (txJson.containsKey(\"trackPowerPathClosed\") && !txJson[\"trackPowerPathClosed\"].is<bool>()) {\n        return false;\n    }\n    tx.trackPowerPathClosed = txJson[\"trackPowerPathClosed\"] | false;\n\n    if (txJson.containsKey(\"trackEnergyTransfer\") && !txJson[\"trackEnergyTransfer\"].is<bool>()) {\n        return false;\n    }\n    tx.trackEnergyTransfer = txJson[\"trackEnergyTransfer\"] | false;\n\n    if (txJson.containsKey(\"active\") && !txJson[\"active\"].is<bool>()) {\n        return false;\n    }\n    tx.active = txJson[\"active\"] | false;\n    if (txJson.containsKey(\"started\") && !txJson[\"started\"].is<bool>()) {\n        return false;\n    }\n    tx.started = txJson[\"started\"] | false;\n\n    if (txJson.containsKey(\"stopped\") && !txJson[\"stopped\"].is<bool>()) {\n        return false;\n    }\n    tx.stopped = txJson[\"stopped\"] | false;\n\n    if (txJson.containsKey(\"isAuthorizationActive\") && !txJson[\"isAuthorizationActive\"].is<bool>()) {\n        return false;\n    }\n    tx.isAuthorizationActive = txJson[\"isAuthorizationActive\"] | false;\n    if (txJson.containsKey(\"isAuthorized\") && !txJson[\"isAuthorized\"].is<bool>()) {\n        return false;\n    }\n    tx.isAuthorized = txJson[\"isAuthorized\"] | false;\n\n    if (txJson.containsKey(\"isDeauthorized\") && !txJson[\"isDeauthorized\"].is<bool>()) {\n        return false;\n    }\n    tx.isDeauthorized = txJson[\"isDeauthorized\"] | false;\n\n    if (txJson.containsKey(\"idToken\")) {\n        IdToken idToken;\n        if (!idToken.parseCstr(\n                    txJson[\"idToken\"][\"idToken\"] | (const char*)nullptr, \n                    txJson[\"idToken\"][\"type\"]    | (const char*)nullptr)) {\n            return false;\n        }\n        tx.idToken = idToken;\n    }\n\n    if (txJson.containsKey(\"beginTimestamp\")) {\n        if (!tx.beginTimestamp.setTime(txJson[\"beginTimestamp\"] | \"_Undefined\")) {\n            return false;\n        }\n    }\n\n    if (txJson.containsKey(\"remoteStartId\")) {\n        int remoteStartIdIn = txJson[\"remoteStartId\"] | -1;\n        if (remoteStartIdIn < 0) {\n            return false;\n        }\n        tx.remoteStartId = remoteStartIdIn;\n    }\n\n    if (txJson.containsKey(\"evConnectionTimeoutListen\") && !txJson[\"evConnectionTimeoutListen\"].is<bool>()) {\n        return false;\n    }\n    tx.evConnectionTimeoutListen = txJson[\"evConnectionTimeoutListen\"] | false;\n\n    Transaction::StoppedReason stoppedReason;\n    if (!deserializeTransactionStoppedReason(txJson[\"stoppedReason\"] | (const char*)nullptr, stoppedReason)) {\n        return false;\n    }\n    tx.stoppedReason = stoppedReason;\n\n    TransactionEventTriggerReason stopTrigger;\n    if (!deserializeTransactionEventTriggerReason(txJson[\"stopTrigger\"] | (const char*)nullptr, stopTrigger)) {\n        return false;\n    }\n    tx.stopTrigger = stopTrigger;\n\n    if (txJson.containsKey(\"stopIdToken\")) {\n        auto stopIdToken = std::unique_ptr<IdToken>(new IdToken());\n        if (!stopIdToken) {\n            MO_DBG_ERR(\"OOM\");\n            return false;\n        }\n        if (!stopIdToken->parseCstr(\n                    txJson[\"stopIdToken\"][\"idToken\"] | (const char*)nullptr, \n                    txJson[\"stopIdToken\"][\"type\"]    | (const char*)nullptr)) {\n            return false;\n        }\n        tx.stopIdToken = std::move(stopIdToken);\n    }\n\n    //sampledDataTxEnded not supported yet\n\n    if (auto txId = txJson[\"txId\"] | (const char*)nullptr) {\n        auto ret = snprintf(tx.transactionId, sizeof(tx.transactionId), \"%s\", txId);\n        if (ret < 0 || (size_t)ret >= sizeof(tx.transactionId)) {\n            return false;\n        }\n    } else {\n        return false;\n    }\n\n    if (txJson.containsKey(\"silent\") && !txJson[\"silent\"].is<bool>()) {\n        return false;\n    }\n    tx.silent = txJson[\"silent\"] | false;\n\n    return true;\n}\n\nbool TransactionStoreEvse::serializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) {\n    \n    if (txEvent.eventType != TransactionEventData::Type::Updated) {\n        txEventJson[\"eventType\"] = serializeTransactionEventType(txEvent.eventType);\n    }\n\n    if (txEvent.timestamp > MIN_TIME) {\n        char timeStr [JSONDATE_LENGTH + 1] = {'\\0'};\n        txEvent.timestamp.toJsonString(timeStr, JSONDATE_LENGTH + 1);\n        txEventJson[\"timestamp\"] = timeStr;\n    }\n\n    txEventJson[\"bootNr\"] = txEvent.bootNr;\n\n    if (serializeTransactionEventTriggerReason(txEvent.triggerReason)) {\n        txEventJson[\"triggerReason\"] = serializeTransactionEventTriggerReason(txEvent.triggerReason);\n    }\n    \n    if (txEvent.offline) {\n        txEventJson[\"offline\"] = true;\n    }\n\n    if (txEvent.numberOfPhasesUsed >= 0) {\n        txEventJson[\"numberOfPhasesUsed\"] = txEvent.numberOfPhasesUsed;\n    }\n\n    if (txEvent.cableMaxCurrent >= 0) {\n        txEventJson[\"cableMaxCurrent\"] = txEvent.cableMaxCurrent;\n    }\n\n    if (txEvent.reservationId >= 0) {\n        txEventJson[\"reservationId\"] = txEvent.reservationId;\n    }\n\n    if (txEvent.remoteStartId >= 0) {\n        txEventJson[\"remoteStartId\"] = txEvent.remoteStartId;\n    }\n\n    if (serializeTransactionEventChargingState(txEvent.chargingState)) { // optional\n        txEventJson[\"chargingState\"] = serializeTransactionEventChargingState(txEvent.chargingState);\n    }\n\n    if (txEvent.idToken) {\n        JsonObject idToken = txEventJson.createNestedObject(\"idToken\");\n        idToken[\"idToken\"] = txEvent.idToken->get();\n        idToken[\"type\"] = txEvent.idToken->getTypeCstr();\n    }\n\n    if (txEvent.evse.id >= 0) {\n        JsonObject evse = txEventJson.createNestedObject(\"evse\");\n        evse[\"id\"] = txEvent.evse.id;\n        if (txEvent.evse.connectorId >= 0) {\n            evse[\"connectorId\"] = txEvent.evse.connectorId;\n        }\n    }\n\n    //meterValue not supported yet\n\n    txEventJson[\"opNr\"] = txEvent.opNr;\n    txEventJson[\"attemptNr\"] = txEvent.attemptNr;\n    \n    if (txEvent.attemptTime > MIN_TIME) {\n        char timeStr [JSONDATE_LENGTH + 1] = {'\\0'};\n        txEvent.attemptTime.toJsonString(timeStr, JSONDATE_LENGTH + 1);\n        txEventJson[\"attemptTime\"] = timeStr;\n    }\n\n    return true;\n}\n\nbool TransactionStoreEvse::deserializeTransactionEvent(TransactionEventData& txEvent, JsonObject txEventJson) {\n\n    TransactionEventData::Type eventType;\n    if (!deserializeTransactionEventType(txEventJson[\"eventType\"] | \"Updated\", eventType)) {\n        return false;\n    }\n    txEvent.eventType = eventType;\n\n    if (txEventJson.containsKey(\"timestamp\")) {\n        if (!txEvent.timestamp.setTime(txEventJson[\"timestamp\"] | \"_Undefined\")) {\n            return false;\n        }\n    }\n\n    int bootNrIn = txEventJson[\"bootNr\"] | -1;\n    if (bootNrIn >= 0 && bootNrIn <= std::numeric_limits<uint16_t>::max()) {\n        txEvent.bootNr = (uint16_t)bootNrIn;\n    } else {\n        return false;\n    }\n\n    TransactionEventTriggerReason triggerReason;\n    if (!deserializeTransactionEventTriggerReason(txEventJson[\"triggerReason\"] | \"_Undefined\", triggerReason)) {\n        return false;\n    }\n    txEvent.triggerReason = triggerReason;\n    \n    if (txEventJson.containsKey(\"offline\") && !txEventJson[\"offline\"].is<bool>()) {\n        return false;\n    }\n    txEvent.offline = txEventJson[\"offline\"] | false;\n\n    if (txEventJson.containsKey(\"numberOfPhasesUsed\")) {\n        int numberOfPhasesUsedIn = txEventJson[\"numberOfPhasesUsed\"] | -1;\n        if (numberOfPhasesUsedIn < 0) {\n            return false;\n        }\n        txEvent.numberOfPhasesUsed = numberOfPhasesUsedIn;\n    }\n\n    if (txEventJson.containsKey(\"cableMaxCurrent\")) {\n        int cableMaxCurrentIn = txEventJson[\"cableMaxCurrent\"] | -1;\n        if (cableMaxCurrentIn < 0) {\n            return false;\n        }\n        txEvent.cableMaxCurrent = cableMaxCurrentIn;\n    }\n\n    if (txEventJson.containsKey(\"reservationId\")) {\n        int reservationIdIn = txEventJson[\"reservationId\"] | -1;\n        if (reservationIdIn < 0) {\n            return false;\n        }\n        txEvent.reservationId = reservationIdIn;\n    }\n\n    if (txEventJson.containsKey(\"remoteStartId\")) {\n        int remoteStartIdIn = txEventJson[\"remoteStartId\"] | -1;\n        if (remoteStartIdIn < 0) {\n            return false;\n        }\n        txEvent.remoteStartId = remoteStartIdIn;\n    }\n\n    TransactionEventData::ChargingState chargingState;\n    if (!deserializeTransactionEventChargingState(txEventJson[\"chargingState\"] | (const char*)nullptr, chargingState)) {\n        return false;\n    }\n    txEvent.chargingState = chargingState;\n\n    if (txEventJson.containsKey(\"idToken\")) {\n        auto idToken = std::unique_ptr<IdToken>(new IdToken());\n        if (!idToken) {\n            MO_DBG_ERR(\"OOM\");\n            return false;\n        }\n        if (!idToken->parseCstr(\n                    txEventJson[\"idToken\"][\"idToken\"] | (const char*)nullptr, \n                    txEventJson[\"idToken\"][\"type\"]    | (const char*)nullptr)) {\n            return false;\n        }\n        txEvent.idToken = std::move(idToken);\n    }\n\n    if (txEventJson.containsKey(\"evse\")) {\n        int evseId = txEventJson[\"evse\"][\"id\"] | -1;\n        if (evseId < 0) {\n            return false;\n        }\n        if (txEventJson[\"evse\"].containsKey(\"connectorId\")) {\n            int connectorId = txEventJson[\"evse\"][\"connectorId\"] | -1;\n            if (connectorId < 0) {\n                return false;\n            }\n            txEvent.evse = EvseId(evseId, connectorId);\n        } else {\n            txEvent.evse = EvseId(evseId);\n        }\n    }\n\n    //meterValue not supported yet\n\n    int opNrIn = txEventJson[\"opNr\"] | -1;\n    if (opNrIn >= 0) {\n        txEvent.opNr = (unsigned int)opNrIn;\n    } else {\n        return false;\n    }\n\n    int attemptNrIn = txEventJson[\"attemptNr\"] | -1;\n    if (attemptNrIn >= 0) {\n        txEvent.attemptNr = (unsigned int)attemptNrIn;\n    } else {\n        return false;\n    }\n\n    if (txEventJson.containsKey(\"attemptTime\")) {\n        if (!txEvent.attemptTime.setTime(txEventJson[\"attemptTime\"] | \"_Undefined\")) {\n            return false;\n        }\n    }\n\n    return true;\n}\n\nTransactionStoreEvse::TransactionStoreEvse(TransactionStore& txStore, unsigned int evseId, std::shared_ptr<FilesystemAdapter> filesystem) :\n        MemoryManaged(\"v201.Transactions.TransactionStore\"),\n        txStore(txStore),\n        evseId(evseId),\n        filesystem(filesystem) {\n\n}\n\nbool TransactionStoreEvse::discoverStoredTx(unsigned int& txNrBeginOut, unsigned int& txNrEndOut) {\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no FS adapter\");\n        return true;\n    }\n\n    char fnPrefix [MO_MAX_PATH_SIZE];\n    snprintf(fnPrefix, sizeof(fnPrefix), \"tx201-%u-\", evseId);\n    size_t fnPrefixLen = strlen(fnPrefix);\n\n    unsigned int txNrPivot = std::numeric_limits<unsigned int>::max();\n    unsigned int txNrBegin = 0, txNrEnd = 0;\n\n    auto ret = filesystem->ftw_root([fnPrefix, fnPrefixLen, &txNrPivot, &txNrBegin, &txNrEnd] (const char *fn) {\n        if (!strncmp(fn, fnPrefix, fnPrefixLen)) {\n            unsigned int parsedTxNr = 0;\n            for (size_t i = fnPrefixLen; fn[i] >= '0' && fn[i] <= '9'; i++) {\n                parsedTxNr *= 10;\n                parsedTxNr += fn[i] - '0';\n            }\n\n            if (txNrPivot == std::numeric_limits<unsigned int>::max()) {\n                txNrPivot = parsedTxNr;\n                txNrBegin = parsedTxNr;\n                txNrEnd = (parsedTxNr + 1) % MAX_TX_CNT;\n                return 0;\n            }\n\n            if ((parsedTxNr + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT < MAX_TX_CNT / 2) {\n                //parsedTxNr is after pivot point\n                if ((parsedTxNr + 1 + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT > (txNrEnd + MAX_TX_CNT - txNrPivot) % MAX_TX_CNT) {\n                    txNrEnd = (parsedTxNr + 1) % MAX_TX_CNT;\n                }\n            } else if ((txNrPivot + MAX_TX_CNT - parsedTxNr) % MAX_TX_CNT < MAX_TX_CNT / 2) {\n                //parsedTxNr is before pivot point\n                if ((txNrPivot + MAX_TX_CNT - parsedTxNr) % MAX_TX_CNT > (txNrPivot + MAX_TX_CNT - txNrBegin) % MAX_TX_CNT) {\n                    txNrBegin = parsedTxNr;\n                }\n            }\n\n            MO_DBG_DEBUG(\"found %s%u-*.json - Internal range from %u to %u (exclusive)\", fnPrefix, parsedTxNr, txNrBegin, txNrEnd);\n        }\n        return 0;\n    });\n\n    if (ret == 0) {\n        txNrBeginOut = txNrBegin;\n        txNrEndOut = txNrEnd;\n        return true;\n    } else {\n        MO_DBG_ERR(\"fs error\");\n        return false;\n    }\n}\n\nstd::unique_ptr<Transaction> TransactionStoreEvse::loadTransaction(unsigned int txNr) {\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no FS adapter\");\n        return nullptr;\n    }\n\n    char fnPrefix [MO_MAX_PATH_SIZE];\n    auto ret= snprintf(fnPrefix, sizeof(fnPrefix), \"tx201-%u-%u-\", evseId, txNr);\n    if (ret < 0 || (size_t)ret >= sizeof(fnPrefix)) {\n        MO_DBG_ERR(\"fn error\");\n        return nullptr;\n    }\n    size_t fnPrefixLen = strlen(fnPrefix);\n\n    Vector<unsigned int> seqNos = makeVector<unsigned int>(getMemoryTag());\n\n    filesystem->ftw_root([fnPrefix, fnPrefixLen, &seqNos] (const char *fn) {\n        if (!strncmp(fn, fnPrefix, fnPrefixLen)) {\n            unsigned int parsedSeqNo = 0;\n            for (size_t i = fnPrefixLen; fn[i] >= '0' && fn[i] <= '9'; i++) {\n                parsedSeqNo *= 10;\n                parsedSeqNo += fn[i] - '0';\n            }\n\n            seqNos.push_back(parsedSeqNo);\n        }\n        return 0;\n    });\n\n    if (seqNos.empty()) {\n        MO_DBG_DEBUG(\"no tx at tx201-%u-%u\", evseId, txNr);\n        return nullptr;\n    }\n\n    std::sort(seqNos.begin(), seqNos.end());\n\n    char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n    ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"tx201\" \"-%u-%u-%u.json\", evseId, txNr, seqNos.back());\n    if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n        MO_DBG_ERR(\"fn error: %i\", ret);\n        return nullptr;\n    }\n\n    size_t msize;\n    if (filesystem->stat(fn, &msize) != 0) {\n        MO_DBG_ERR(\"tx201-%u-%u memory corruption\", evseId, txNr);\n        return nullptr;\n    }\n\n    auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag());\n\n    if (!doc) {\n        MO_DBG_ERR(\"memory corruption\");\n        return nullptr;\n    }\n\n    auto transaction = std::unique_ptr<Transaction>(new Transaction());\n    if (!transaction) {\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n\n    transaction->evseId = evseId;\n    transaction->txNr = txNr;\n    transaction->seqNos = std::move(seqNos);\n\n    JsonObject txJson = (*doc)[\"tx\"];\n\n    if (!deserializeTransaction(*transaction, txJson)) {\n        MO_DBG_ERR(\"deserialization error\");\n        return nullptr;\n    }\n\n    //determine seqNoEnd and trim seqNos record\n    if (doc->containsKey(\"txEvent\")) {\n        //last tx201 file contains txEvent -> txNoEnd is one place after tx201 file and seqNos is accurate\n        transaction->seqNoEnd = transaction->seqNos.back() + 1;\n    } else {\n        //last tx201 file contains only tx status information, but no txEvent -> remove from seqNos record and set seqNoEnd to this\n        transaction->seqNoEnd = transaction->seqNos.back();\n        transaction->seqNos.pop_back();\n    }\n\n    MO_DBG_DEBUG(\"loaded tx %u-%u, seqNos.size()=%zu\", evseId, txNr, transaction->seqNos.size());\n\n    return transaction;\n}\n\nstd::unique_ptr<Ocpp201::Transaction> TransactionStoreEvse::createTransaction(unsigned int txNr, const char *txId) {\n\n    //clean data which could still be here from a rolled-back transaction\n    if (!remove(txNr)) {\n        MO_DBG_ERR(\"txNr not clean\");\n        return nullptr;\n    }\n\n    auto transaction = std::unique_ptr<Transaction>(new Transaction());\n    if (!transaction) {\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n\n    transaction->evseId = evseId;\n    transaction->txNr = txNr;\n\n    auto ret = snprintf(transaction->transactionId, sizeof(transaction->transactionId), \"%s\", txId);\n    if (ret < 0 || (size_t)ret >= sizeof(transaction->transactionId)) {\n        MO_DBG_ERR(\"invalid arg\");\n        return nullptr;\n    }\n\n    if (!commit(transaction.get())) {\n        MO_DBG_ERR(\"FS error\");\n        return nullptr;\n    }\n\n    return transaction;\n}\n\nstd::unique_ptr<TransactionEventData> TransactionStoreEvse::createTransactionEvent(Transaction& tx) {\n\n    auto txEvent = std::unique_ptr<TransactionEventData>(new TransactionEventData(&tx, tx.seqNoEnd));\n    if (!txEvent) {\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n\n    //success\n    return txEvent;\n}\n\nstd::unique_ptr<TransactionEventData> TransactionStoreEvse::loadTransactionEvent(Transaction& tx, unsigned int seqNo) {\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no FS adapter\");\n        return nullptr;\n    }\n\n    bool found = false;\n    for (size_t i = 0; i < tx.seqNos.size(); i++) {\n        if (tx.seqNos[i] == seqNo) {\n            found = true;\n        }\n    }\n    if (!found) {\n        MO_DBG_DEBUG(\"%u-%u-%u does not exist\", evseId, tx.txNr, seqNo);\n        return nullptr;\n    }\n\n    char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n    auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"tx201\" \"-%u-%u-%u.json\", evseId, tx.txNr, seqNo);\n    if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n        MO_DBG_ERR(\"fn error: %i\", ret);\n        return nullptr;\n    }\n\n    size_t msize;\n    if (filesystem->stat(fn, &msize) != 0) {\n        MO_DBG_ERR(\"seqNos out of sync: could not find %u-%u-%u\", evseId, tx.txNr, seqNo);\n        return nullptr;\n    }\n\n    auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag());\n\n    if (!doc) {\n        MO_DBG_ERR(\"memory corruption\");\n        return nullptr;\n    }\n\n    if (!doc->containsKey(\"txEvent\")) {\n        MO_DBG_DEBUG(\"%u-%u-%u does not contain txEvent\", evseId, tx.txNr, seqNo);\n        return nullptr;\n    }\n\n    auto txEvent = std::unique_ptr<TransactionEventData>(new TransactionEventData(&tx, seqNo));\n    if (!txEvent) {\n        MO_DBG_ERR(\"OOM\");\n        return nullptr;\n    }\n\n    if (!deserializeTransactionEvent(*txEvent, (*doc)[\"txEvent\"])) {\n        MO_DBG_ERR(\"deserialization error\");\n        return nullptr;\n    }\n\n    return txEvent;\n}\n\nbool TransactionStoreEvse::commit(Transaction& tx, TransactionEventData *txEvent) {\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no FS: nothing to commit\");\n        return true;\n    }\n\n    unsigned int seqNo = 0;\n\n    if (txEvent) {\n        seqNo = txEvent->seqNo;\n    } else {\n        //update tx state in new or reused tx201 file\n        seqNo = tx.seqNoEnd;\n    }\n\n    size_t seqNosNewSize = tx.seqNos.size() + 1;\n    for (size_t i = 0; i < tx.seqNos.size(); i++) {\n        if (tx.seqNos[i] == seqNo) {\n            seqNosNewSize -= 1;\n            break;\n        }\n    }\n\n    // Check if to delete intermediate offline txEvent\n    if (seqNosNewSize > MO_TXEVENTRECORD_SIZE_V201) {\n        auto deltaMin = std::numeric_limits<unsigned int>::max();\n        size_t indexMin = tx.seqNos.size();\n        for (size_t i = 2; i + 1 <= tx.seqNos.size(); i++) { //always keep first and final txEvent\n            size_t t0 = tx.seqNos.size() - i - 1;\n            size_t t1 = tx.seqNos.size() - i;\n            size_t t2 = tx.seqNos.size() - i + 1;\n\n            auto delta = tx.seqNos[t2] - tx.seqNos[t0];\n\n            if (delta < deltaMin) {\n                deltaMin = delta;\n                indexMin = t1;\n            }\n        }\n\n        if (indexMin < tx.seqNos.size()) {\n            MO_DBG_DEBUG(\"delete intermediate txEvent %u-%u-%u - delta=%u\", evseId, tx.txNr, tx.seqNos[indexMin], deltaMin);\n            remove(tx, tx.seqNos[indexMin]); //remove can call commit() again. Ensure that remove is not executed for last element\n        } else {\n            MO_DBG_ERR(\"internal error\");\n            return false;\n        }\n    }\n\n    char fn [MO_MAX_PATH_SIZE] = {'\\0'};\n    auto ret = snprintf(fn, MO_MAX_PATH_SIZE, MO_FILENAME_PREFIX \"tx201\" \"-%u-%u-%u.json\", evseId, tx.txNr, seqNo);\n    if (ret < 0 || ret >= MO_MAX_PATH_SIZE) {\n        MO_DBG_ERR(\"fn error: %i\", ret);\n        return false;\n    }\n\n    auto txDoc = initJsonDoc(\"v201.Transactions.TransactionStoreEvse\", 2048);\n\n    if (!serializeTransaction(tx, txDoc.createNestedObject(\"tx\"))) {\n        MO_DBG_ERR(\"Serialization error\");\n        return false;\n    }\n\n    if (txEvent && !serializeTransactionEvent(*txEvent, txDoc.createNestedObject(\"txEvent\"))) {\n        MO_DBG_ERR(\"Serialization error\");\n        return false;\n    }\n\n    if (!FilesystemUtils::storeJson(filesystem, fn, txDoc)) {\n        MO_DBG_ERR(\"FS error\");\n        return false;\n    }\n\n    if (txEvent && seqNo == tx.seqNoEnd) {\n        tx.seqNos.push_back(seqNo);\n        tx.seqNoEnd++;\n    }\n\n    MO_DBG_DEBUG(\"comitted tx %u-%u-%u\", evseId, tx.txNr, seqNo);\n\n    //success\n    return true;\n}\n\nbool TransactionStoreEvse::commit(Transaction *transaction) {\n    return commit(*transaction, nullptr);\n}\n\nbool TransactionStoreEvse::commit(TransactionEventData *txEvent) {\n    return commit(*txEvent->transaction, txEvent);\n}\n\nbool TransactionStoreEvse::remove(unsigned int txNr) {\n\n    if (!filesystem) {\n        MO_DBG_DEBUG(\"no FS: nothing to remove\");\n        return true;\n    }\n\n    char fnPrefix [MO_MAX_PATH_SIZE];\n    auto ret= snprintf(fnPrefix, sizeof(fnPrefix), \"tx201-%u-%u-\", evseId, txNr);\n    if (ret < 0 || (size_t)ret >= sizeof(fnPrefix)) {\n        MO_DBG_ERR(\"fn error\");\n        return false;\n    }\n    size_t fnPrefixLen = strlen(fnPrefix);\n\n    auto success = FilesystemUtils::remove_if(filesystem, [fnPrefix, fnPrefixLen] (const char *fn) {\n        return !strncmp(fn, fnPrefix, fnPrefixLen);\n    });\n\n    return success;\n}\n\nbool TransactionStoreEvse::remove(Transaction& tx, unsigned int seqNo) {\n\n    if (tx.seqNos.empty()) {\n        //nothing to do\n        return true;\n    }\n\n    if (tx.seqNos.back() == seqNo) {\n        //special case: deletion of last tx201 file could also delete information about tx. Make sure all tx-related\n        //information is commited into tx201 file at seqNoEnd, then delete file at seqNo\n\n        char fn [MO_MAX_PATH_SIZE];\n        auto ret = snprintf(fn, sizeof(fn), \"%stx201-%u-%u-%u.json\", MO_FILENAME_PREFIX, evseId, tx.txNr, tx.seqNoEnd);\n        if (ret < 0 || (size_t)ret >= sizeof(fn)) {\n            MO_DBG_ERR(\"fn error\");\n            return false;\n        }\n\n        auto doc = FilesystemUtils::loadJson(filesystem, fn, getMemoryTag());\n        \n        if (!doc || !doc->containsKey(\"tx\")) {\n            //no valid tx201 file at seqNoEnd. Commit tx into file seqNoEnd, then remove file at seqNo\n\n            if (!commit(tx, nullptr)) {\n                MO_DBG_ERR(\"fs error\");\n                return false;\n            }\n        }\n\n        //seqNoEnd contains all tx data which should be persisted. Continue\n    }\n\n    bool found = false;\n    for (size_t i = 0; i < tx.seqNos.size(); i++) {\n        if (tx.seqNos[i] == seqNo) {\n            found = true;\n        }\n    }\n    if (!found) {\n        MO_DBG_DEBUG(\"%u-%u-%u does not exist\", evseId, tx.txNr, seqNo);\n        return true;\n    }\n\n    bool success = true;\n\n    if (filesystem) {\n        char fn [MO_MAX_PATH_SIZE];\n        auto ret = snprintf(fn, sizeof(fn), \"%stx201-%u-%u-%u.json\", MO_FILENAME_PREFIX, evseId, tx.txNr, seqNo);\n        if (ret < 0 || (size_t)ret >= sizeof(fn)) {\n            MO_DBG_ERR(\"fn error\");\n            return false;\n        }\n\n        size_t msize;\n        if (filesystem->stat(fn, &msize) == 0) {\n            success &= filesystem->remove(fn);\n        } else {\n            MO_DBG_ERR(\"internal error: seqNos out of sync\");\n            (void)0;\n        }\n    }\n\n    if (success) {\n        auto it = tx.seqNos.begin();\n        while (it != tx.seqNos.end()) {\n            if (*it == seqNo) {\n                it = tx.seqNos.erase(it);\n            } else {\n                it++;\n            }\n        }\n    }\n\n    return success;\n}\n\nTransactionStore::TransactionStore(std::shared_ptr<FilesystemAdapter> filesystem, size_t numEvses) :\n        MemoryManaged{\"v201.Transactions.TransactionStore\"} {\n\n    for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID && (size_t)evseId < numEvses; evseId++) {\n        evses[evseId] = new TransactionStoreEvse(*this, evseId, filesystem);\n    }\n}\n\nTransactionStore::~TransactionStore() {\n    for (unsigned int evseId = 0; evseId < MO_NUM_EVSEID && evses[evseId]; evseId++) {\n        delete evses[evseId];\n    }\n}\n\nTransactionStoreEvse *TransactionStore::getEvse(unsigned int evseId) {\n    if (evseId >= MO_NUM_EVSEID) {\n        return nullptr;\n    }\n    return evses[evseId];\n}\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Model/Transactions/TransactionStore.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_TRANSACTIONSTORE_H\n#define MO_TRANSACTIONSTORE_H\n\n#include <MicroOcpp/Version.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass TransactionStore;\n\nclass ConnectorTransactionStore : public MemoryManaged {\nprivate:\n    TransactionStore& context;\n    const unsigned int connectorId;\n\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    \n    Vector<std::weak_ptr<Transaction>> transactions;\n\npublic:\n    ConnectorTransactionStore(TransactionStore& context, unsigned int connectorId, std::shared_ptr<FilesystemAdapter> filesystem);\n    ConnectorTransactionStore(const ConnectorTransactionStore&) = delete;\n    ConnectorTransactionStore(ConnectorTransactionStore&&) = delete;\n    ConnectorTransactionStore& operator=(const ConnectorTransactionStore&) = delete;\n\n    ~ConnectorTransactionStore();\n\n    bool commit(Transaction *transaction);\n\n    std::shared_ptr<Transaction> getTransaction(unsigned int txNr);\n    std::shared_ptr<Transaction> createTransaction(unsigned int txNr, bool silent = false);\n\n    bool remove(unsigned int txNr);\n};\n\nclass TransactionStore : public MemoryManaged {\nprivate:\n    Vector<std::unique_ptr<ConnectorTransactionStore>> connectors;\npublic:\n    TransactionStore(unsigned int nConnectors, std::shared_ptr<FilesystemAdapter> filesystem);\n\n    bool commit(Transaction *transaction);\n\n    std::shared_ptr<Transaction> getTransaction(unsigned int connectorId, unsigned int txNr);\n    std::shared_ptr<Transaction> createTransaction(unsigned int connectorId, unsigned int txNr, bool silent = false);\n\n    bool remove(unsigned int connectorId, unsigned int txNr);\n};\n\n}\n\n#if MO_ENABLE_V201\n\n#ifndef MO_TXEVENTRECORD_SIZE_V201\n#define MO_TXEVENTRECORD_SIZE_V201 10 //maximum number of of txEvents per tx to hold on flash storage\n#endif\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nclass TransactionStore;\n\nclass TransactionStoreEvse : public MemoryManaged {\nprivate:\n    TransactionStore& txStore;\n    const unsigned int evseId;\n\n    std::shared_ptr<FilesystemAdapter> filesystem;\n\n    bool serializeTransaction(Transaction& tx, JsonObject out);\n    bool serializeTransactionEvent(TransactionEventData& txEvent, JsonObject out);\n    bool deserializeTransaction(Transaction& tx, JsonObject in);\n    bool deserializeTransactionEvent(TransactionEventData& txEvent, JsonObject in);\n\n    bool commit(Transaction& transaction, TransactionEventData *transactionEvent);\n\npublic:\n    TransactionStoreEvse(TransactionStore& txStore, unsigned int evseId, std::shared_ptr<FilesystemAdapter> filesystem);\n\n    bool discoverStoredTx(unsigned int& txNrBeginOut, unsigned int& txNrEndOut);\n\n    bool commit(Transaction *transaction);\n    bool commit(TransactionEventData *transactionEvent);\n\n    std::unique_ptr<Transaction> loadTransaction(unsigned int txNr);\n    std::unique_ptr<Transaction> createTransaction(unsigned int txNr, const char *txId);\n\n    std::unique_ptr<TransactionEventData> createTransactionEvent(Transaction& tx);\n    std::unique_ptr<TransactionEventData> loadTransactionEvent(Transaction& tx, unsigned int seqNo);\n\n    bool remove(unsigned int txNr);\n    bool remove(Transaction& tx, unsigned int seqNo);\n};\n\nclass TransactionStore : public MemoryManaged {\nprivate:\n    TransactionStoreEvse *evses [MO_NUM_EVSEID] = {nullptr};\npublic:\n    TransactionStore(std::shared_ptr<FilesystemAdapter> filesystem, size_t numEvses);\n\n    ~TransactionStore();\n\n    TransactionStoreEvse *getEvse(unsigned int evseId);\n};\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Variables/Variable.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Implementation of the UCs B05 - B06\n */\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Variables/Variable.h>\n\n#include <string.h>\n\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nComponentId::ComponentId(const char *name) : name(name) { }\nComponentId::ComponentId(const char *name, EvseId evse) : name(name), evse(evse) { }\n\nbool ComponentId::equals(const ComponentId& other) const {\n    return !strcmp(name, other.name) &&\n        ((evse.id < 0 && other.evse.id < 0) || (evse.id == other.evse.id)) && // evseId undefined or equal\n        ((evse.connectorId < 0 && other.evse.connectorId < 0) || (evse.connectorId == other.evse.connectorId)); // connectorId undefined or equal\n}\n\nbool Variable::AttributeTypeSet::has(AttributeType type) {\n    switch(type) {\n        case AttributeType::Actual:\n            return flag & (1 << 0);\n        case AttributeType::Target:\n            return flag & (1 << 1);\n        case AttributeType::MinSet:\n            return flag & (1 << 2);\n        case AttributeType::MaxSet:\n            return flag & (1 << 3);\n    }\n    MO_DBG_ERR(\"internal error\");\n    return false;\n}\n\nVariable::AttributeTypeSet& Variable::AttributeTypeSet::set(AttributeType type) {\n    switch(type) {\n        case AttributeType::Actual:\n            flag |= (1 << 0);\n            break;\n        case AttributeType::Target:\n            flag |= (1 << 1);\n            break;\n        case AttributeType::MinSet:\n            flag |= (1 << 2);\n            break;\n        case AttributeType::MaxSet:\n            flag |= (1 << 3);\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            break;\n    }\n    return *this;\n}\n\nsize_t Variable::AttributeTypeSet::count() {\n    return (flag & (1 << 0) ? 1 : 0) +\n           (flag & (1 << 1) ? 1 : 0) +\n           (flag & (1 << 2) ? 1 : 0) +\n           (flag & (1 << 3) ? 1 : 0);\n}\n\nVariable::AttributeTypeSet::AttributeTypeSet(AttributeType attrType) {\n    set(attrType);\n}\n\nVariable::Variable(AttributeTypeSet attributes) : attributes(attributes) { }\n\nVariable::~Variable() {\n\n}\n\nvoid Variable::setName(const char *name) {\n    this->variableName = name;\n    updateMemoryTag(\"v201.Variables.Variable.\", name);\n}\nconst char *Variable::getName() const {\n    return variableName;\n}\n\nvoid Variable::setComponentId(const ComponentId& componentId) {\n    this->component = componentId;\n}\nconst ComponentId& Variable::getComponentId() const {\n    return component;\n}\n\nvoid Variable::setInt(int val, AttributeType) {\n    MO_DBG_ERR(\"type err\");\n}\nvoid Variable::setBool(bool val, AttributeType) {\n    MO_DBG_ERR(\"type err\");\n}\nbool Variable::setString(const char *val, AttributeType) {\n    MO_DBG_ERR(\"type err\");\n    return false;\n}\n\nint Variable::getInt(AttributeType) {\n    MO_DBG_ERR(\"type err\");\n    return 0;\n}\nbool Variable::getBool(AttributeType) {\n    MO_DBG_ERR(\"type err\");\n    return false;\n}\nconst char *Variable::getString(AttributeType) {\n    MO_DBG_ERR(\"type err\");\n    return nullptr;\n}\n\nbool Variable::hasAttribute(AttributeType attrType) {\n    return attributes.has(attrType);\n}\n\nvoid Variable::setVariableDataType(VariableCharacteristics::DataType dataType) {\n    this->dataType = dataType;\n}\nVariableCharacteristics::DataType Variable::getVariableDataType() {\n    return dataType;\n}\n\nbool Variable::getSupportsMonitoring() {\n    return supportsMonitoring;\n}\nvoid Variable::setSupportsMonitoring() {\n    supportsMonitoring = true;\n}\n\nbool Variable::isRebootRequired() {\n    return rebootRequired;\n}\nvoid Variable::setRebootRequired() {\n    rebootRequired = true;\n}\n\nvoid Variable::setMutability(Mutability m) {\n    this->mutability = m;\n}\nVariable::Mutability Variable::getMutability() {\n    return mutability;\n}\n\nvoid Variable::setPersistent() {\n    persistent = true;\n}\nbool Variable::isPersistent() {\n    return persistent;\n}\n\nvoid Variable::setConstant() {\n    constant = true;\n}\nbool Variable::isConstant() {\n    return constant;\n}\n\ntemplate <class T>\nstruct VariableSingleData {\n    T value = 0;\n\n    T& get(Variable::AttributeType attribute) {\n        return value;\n    }\n};\n\ntemplate <class T>\nstruct VariableFullData {\n    T actual = 0;\n    T target = 0;\n    T minSet = 0;\n    T maxSet = 0;\n\n    T& get(Variable::AttributeType attribute) {\n        switch(attribute) {\n            case Variable::AttributeType::Actual:\n                return actual;\n            case Variable::AttributeType::Target:\n                return target;\n            case Variable::AttributeType::MinSet:\n                return minSet;\n            case Variable::AttributeType::MaxSet:\n                return maxSet;\n        }\n        MO_DBG_ERR(\"internal error\");\n        return actual;\n    }\n};\n\ntemplate <template<class T> class VariableData>\nclass VariableInt : public Variable {\nprivate:\n    VariableData<int> value;\n    uint16_t writeCount = 0;\n\n    #if MO_VARIABLE_TYPECHECK\n    AttributeTypeSet attributes;\n    #endif\npublic:\n    VariableInt(AttributeTypeSet attributes) :\n        Variable(attributes)\n        #if MO_VARIABLE_TYPECHECK\n        , attributes(attributes)\n        #endif\n    {\n\n    }\n\n    void setInt(int val, AttributeType attrType) override {\n        #if MO_VARIABLE_TYPECHECK\n        if (!attributes.has(attrType)) {\n            MO_DBG_ERR(\"type err\");\n            return;\n        }\n        #endif\n        value.get(attrType) = val;\n        writeCount++;\n    }\n\n    int getInt(AttributeType attrType) override {\n        #if MO_VARIABLE_TYPECHECK\n        if (!attributes.has(attrType)) {\n            MO_DBG_ERR(\"type err\");\n            return 0;\n        }\n        #endif\n        return value.get(attrType);\n    }\n\n    InternalDataType getInternalDataType() override {\n        return InternalDataType::Int;\n    }\n\n    uint16_t getWriteCount() override {\n        return writeCount;\n    }\n};\n\ntemplate <template<class T> class VariableData>\nclass VariableBool : public Variable {\nprivate:\n    VariableData<bool> value;\n    uint16_t writeCount = 0;\n\n    #if MO_VARIABLE_TYPECHECK\n    AttributeTypeSet attributes;\n    #endif\npublic:\n    VariableBool(AttributeTypeSet attributes) :\n        Variable(attributes)\n        #if MO_VARIABLE_TYPECHECK\n        , attributes(attributes)\n        #endif\n    {\n\n    }\n\n    void setBool(bool val, AttributeType attrType) override {\n        #if MO_VARIABLE_TYPECHECK\n        if (!attributes.has(attrType)) {\n            MO_DBG_ERR(\"type err\");\n            return;\n        }\n        #endif\n        value.get(attrType) = val;\n        writeCount++;\n    }\n\n    bool getBool(AttributeType attrType) override {\n        #if MO_VARIABLE_TYPECHECK\n        if (!attributes.has(attrType)) {\n            MO_DBG_ERR(\"type err\");\n            return 0;\n        }\n        #endif\n        return value.get(attrType);\n    }\n\n    InternalDataType getInternalDataType() override {\n        return InternalDataType::Bool;\n    }\n\n    uint16_t getWriteCount() override {\n        return writeCount;\n    }\n};\n\ntemplate <template<class T> class VariableData>\nclass VariableString : public Variable {\nprivate:\n    VariableData<char*> value;\n    uint16_t writeCount = 0;\n\n    #if MO_VARIABLE_TYPECHECK\n    AttributeTypeSet attributes;\n    #endif\npublic:\n    VariableString(AttributeTypeSet attributes) :\n        Variable(attributes)\n        #if MO_VARIABLE_TYPECHECK\n        , attributes(attributes)\n        #endif\n    {\n\n    }\n\n    ~VariableString() {\n        MO_FREE(value.get(AttributeType::Actual));\n        value.get(AttributeType::Actual) = nullptr;\n        MO_FREE(value.get(AttributeType::Target));\n        value.get(AttributeType::Target) = nullptr;\n        MO_FREE(value.get(AttributeType::MinSet));\n        value.get(AttributeType::MinSet) = nullptr;\n        MO_FREE(value.get(AttributeType::MaxSet));\n        value.get(AttributeType::MaxSet) = nullptr;\n    }\n\n    bool setString(const char *val, AttributeType attrType) override {\n        #if MO_VARIABLE_TYPECHECK\n        if (!attributes.has(attrType)) {\n            MO_DBG_ERR(\"type err\");\n            return false;\n        }\n        #endif\n\n        size_t len = strlen(val);\n        char *valNew = nullptr;\n        if (len != 0) {\n            size_t size = len + 1;\n            valNew = static_cast<char*>(MO_MALLOC(getMemoryTag(), size));\n            if (!valNew) {\n                MO_DBG_ERR(\"OOM\");\n                return false;\n            }\n            memcpy(valNew, val, size);\n        }\n        MO_FREE(value.get(attrType));\n        value.get(attrType) = valNew;\n        writeCount++;\n        return true;\n    }\n\n    const char *getString(AttributeType attrType) override {\n        #if MO_VARIABLE_TYPECHECK\n        if (!attributes.has(attrType)) {\n            MO_DBG_ERR(\"type err\");\n            return 0;\n        }\n        #endif\n        return value.get(attrType) ? value.get(attrType) : \"\";\n    }\n\n    InternalDataType getInternalDataType() override {\n        return InternalDataType::String;\n    }\n\n    uint16_t getWriteCount() override {\n        return writeCount;\n    }\n};\n\nstd::unique_ptr<Variable> MicroOcpp::makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes) {\n    switch(dtype) {\n        case Variable::InternalDataType::Int:\n            if (supportAttributes.count() > 1) {\n                return std::unique_ptr<Variable>(new VariableInt<VariableFullData>(supportAttributes));\n            } else {\n                return std::unique_ptr<Variable>(new VariableInt<VariableSingleData>(supportAttributes));\n            }\n        case Variable::InternalDataType::Bool:\n            if (supportAttributes.count() > 1) {\n                return std::unique_ptr<Variable>(new VariableBool<VariableFullData>(supportAttributes));\n            } else {\n                return std::unique_ptr<Variable>(new VariableBool<VariableSingleData>(supportAttributes));\n            }\n        case Variable::InternalDataType::String:\n            if (supportAttributes.count() > 1) {\n                return std::unique_ptr<Variable>(new VariableString<VariableFullData>(supportAttributes));\n            } else {\n                return std::unique_ptr<Variable>(new VariableString<VariableSingleData>(supportAttributes));\n            }\n    }\n\n    MO_DBG_ERR(\"internal error\");\n    return nullptr;\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Model/Variables/Variable.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Implementation of the UCs B05 - B06\n */\n\n#ifndef MO_VARIABLE_H\n#define MO_VARIABLE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <stdint.h>\n#include <memory>\n#include <limits>\n\n#include <MicroOcpp/Model/ConnectorBase/EvseId.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#ifndef MO_VARIABLE_TYPECHECK\n#define MO_VARIABLE_TYPECHECK 1\n#endif\n\nnamespace MicroOcpp {\n\n// VariableCharacteristicsType (2.51)\nstruct VariableCharacteristics : public MemoryManaged {\n\n    // DataEnumType (3.26)\n    enum class DataType : uint8_t {\n        string,\n        decimal,\n        integer,\n        dateTime,\n        boolean,\n        OptionList,\n        SequenceList,\n        MemberList\n    };\n\n    const char *unit = nullptr; //no copy\n    //DataType dataType; //stored in Variable\n    int minLimit = std::numeric_limits<int>::min();\n    int maxLimit = std::numeric_limits<int>::max();\n    const char *valuesList = nullptr; //no copy\n    //bool supportsMonitoring; //stored in Variable\n\n    VariableCharacteristics() : MemoryManaged(\"v201.Variables.VariableCharacteristics\") { }\n};\n\n// SetVariableStatusEnumType (3.79)\nenum class SetVariableStatus : uint8_t {\n    Accepted,\n    Rejected,\n    UnknownComponent,\n    UnknownVariable,\n    NotSupportedAttributeType,\n    RebootRequired\n};\n\n// GetVariableStatusEnumType (3.41)\nenum class GetVariableStatus : uint8_t {\n    Accepted,\n    Rejected,\n    UnknownComponent,\n    UnknownVariable,\n    NotSupportedAttributeType\n};\n\n// ReportBaseEnumType (3.70)\ntypedef enum ReportBase {\n    ReportBase_ConfigurationInventory,\n    ReportBase_FullInventory,\n    ReportBase_SummaryInventory\n}   ReportBase;\n\n// GenericDeviceModelStatus (3.34)\ntypedef enum GenericDeviceModelStatus {\n    GenericDeviceModelStatus_Accepted,\n    GenericDeviceModelStatus_Rejected,\n    GenericDeviceModelStatus_NotSupported,\n    GenericDeviceModelStatus_EmptyResultSet\n}   GenericDeviceModelStatus;\n\n// VariableMonitoringType (2.52)\nclass VariableMonitor {\npublic:\n    //MonitorEnumType (3.55)\n    enum class Type {\n        UpperThreshold,\n        LowerThreshold,\n        Delta,\n        Periodic,\n        PeriodicClockAligned\n    };\nprivate:\n    int id;\n    bool transaction;\n    float value;\n    Type type;\n    int severity;\npublic:\n    VariableMonitor() = delete;\n    VariableMonitor(int id, bool transaction, float value, Type type, int severity) :\n            id(id), transaction(transaction), value(value), type(type), severity(severity) { }\n};\n\n// ComponentType (2.16)\nstruct ComponentId {\n    const char *name; // zero copy\n    //const char *instance; // not supported in this implementation\n    EvseId evse {-1};\n\n    ComponentId(const char *name = nullptr);\n    ComponentId(const char *name, EvseId evse);\n\n    bool equals(const ComponentId& other) const;\n};\n\n/*\n * Corresponds to VariableType (2.53)\n *\n * Template method pattern: this is a super-class which has hook-methods for storing and fetching\n * the value of the variable. To make it use the host system's key-value store, extend this class\n * with a custom implementation of the virtual methods and pass its instances to MO.\n */\nclass Variable : public MemoryManaged {\npublic:\n    //AttributeEnumType (3.2)\n    enum class AttributeType : uint8_t {\n        Actual,\n        Target,\n        MinSet,\n        MaxSet\n    };\n\n    struct AttributeTypeSet {\n        uint8_t flag = 0;\n\n        bool has(Variable::AttributeType type);\n        AttributeTypeSet& set(Variable::AttributeType type);\n        size_t count();\n\n        AttributeTypeSet(AttributeType attrType = AttributeType::Actual);\n    };\n\n    //MutabilityEnumType (3.58)\n    enum class Mutability : uint8_t {\n        ReadOnly,\n        WriteOnly,\n        ReadWrite\n    };\n\n    //MO-internal optimization: if value is only in int range, store it in more compact representation\n    enum class InternalDataType : uint8_t {\n        Int,\n        Bool,\n        String\n    };\nprivate:\n    const char *variableName = nullptr;\n    ComponentId component;\n\n    // VariableCharacteristicsType (2.51)\n    std::unique_ptr<VariableCharacteristics> characteristics; //optional VariableCharacteristics\n    VariableCharacteristics::DataType dataType; //mandatory\n    bool supportsMonitoring = false; //mandatory\n    bool rebootRequired = false; //MO-internal: if to respond status RebootRequired on SetVariables\n\n    // VariableAttributeType (2.50)\n    Mutability mutability = Mutability::ReadWrite;\n    bool persistent = false;\n    bool constant = false;\n\n    AttributeTypeSet attributes;\n\n    // VariableMonitoringType (2.52)\n    //std::vector<VariableMonitor> monitors; // uncomment when testing Monitors\npublic:\n    Variable(AttributeTypeSet attributes);\n\n    virtual ~Variable();\n\n    void setName(const char *name); //zero-copy\n    const char *getName() const;\n\n    void setComponentId(const ComponentId& componentId); //zero-copy\n    const ComponentId& getComponentId() const;\n\n    // set Value of Variable\n    virtual void setInt(int val, AttributeType attrType = AttributeType::Actual);\n    virtual void setBool(bool val, AttributeType attrType = AttributeType::Actual);\n    virtual bool setString(const char *val, AttributeType attrType = AttributeType::Actual);\n\n    // get Value of Variable\n    virtual int getInt(AttributeType attrType = AttributeType::Actual);\n    virtual bool getBool(AttributeType attrType = AttributeType::Actual);\n    virtual const char *getString(AttributeType attrType = AttributeType::Actual); //always returns c-string (empty if undefined)\n\n    virtual InternalDataType getInternalDataType() = 0; //corresponds to MO internal value representation\n    bool hasAttribute(AttributeType attrType);\n\n    void setVariableDataType(VariableCharacteristics::DataType dataType); //corresponds to OCPP DataEnumType (3.26)\n    VariableCharacteristics::DataType getVariableDataType(); //corresponds to OCPP DataEnumType (3.26)\n    bool getSupportsMonitoring();\n    void setSupportsMonitoring();\n    bool isRebootRequired();\n    void setRebootRequired();\n\n    void setMutability(Mutability m);\n    Mutability getMutability();\n\n    void setPersistent();\n    bool isPersistent();\n\n    void setConstant();\n    bool isConstant();\n\n    //bool addMonitor(int id, bool transaction, float value, VariableMonitor::Type type, int severity);\n    \n    virtual uint16_t getWriteCount() = 0; //get write count (use this as a pre-check if the value changed)\n};\n\nstd::unique_ptr<Variable> makeVariable(Variable::InternalDataType dtype, Variable::AttributeTypeSet supportAttributes);\n\n} // namespace MicroOcpp\n\n#endif // MO_ENABLE_V201\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Variables/VariableContainer.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Implementation of the UCs B05 - B06\n */\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Variables/VariableContainer.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n\n#include <string.h>\n\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nVariableContainer::~VariableContainer() {\n\n}\n\nbool VariableContainer::commit() {\n    return true;\n}\n\nVariableContainerNonOwning::VariableContainerNonOwning() :\n            VariableContainer(), MemoryManaged(\"v201.Variables.VariableContainerNonOwning\"), variables(makeVector<Variable*>(getMemoryTag())) {\n\n}\n\nsize_t VariableContainerNonOwning::size() {\n    return variables.size();\n}\n\nVariable *VariableContainerNonOwning::getVariable(size_t i) {\n    return variables[i];\n}\n\nVariable *VariableContainerNonOwning::getVariable(const ComponentId& component, const char *variableName) {\n    for (size_t i = 0; i < variables.size(); i++) {\n        auto& var = variables[i];\n        if (!strcmp(var->getName(), variableName) &&\n                var->getComponentId().equals(component)) {\n            return var;\n        }\n    }\n    return nullptr;\n}\n\nbool VariableContainerNonOwning::add(Variable *variable) {\n    variables.push_back(variable);\n    return true;\n}\n\nbool VariableContainerOwning::checkWriteCountUpdated() {\n\n    decltype(trackWriteCount) writeCount = 0;\n\n    for (size_t i = 0; i < variables.size(); i++) {\n        writeCount += variables[i]->getWriteCount();\n    }\n\n    bool updated = writeCount != trackWriteCount;\n\n    trackWriteCount = writeCount;\n\n    return updated;\n}\n\nVariableContainerOwning::VariableContainerOwning() :\n            VariableContainer(), MemoryManaged(\"v201.Variables.VariableContainerOwning\"), variables(makeVector<std::unique_ptr<Variable>>(getMemoryTag())) {\n\n}\n\nVariableContainerOwning::~VariableContainerOwning() {\n    MO_FREE(filename);\n    filename = nullptr;\n}\n\nsize_t VariableContainerOwning::size() {\n    return variables.size();\n}\n\nVariable *VariableContainerOwning::getVariable(size_t i) {\n    return variables[i].get();\n}\n\nVariable *VariableContainerOwning::getVariable(const ComponentId& component, const char *variableName) {\n    for (size_t i = 0; i < variables.size(); i++) {\n        auto& var = variables[i];\n        if (!strcmp(var->getName(), variableName) &&\n                var->getComponentId().equals(component)) {\n            return var.get();\n        }\n    }\n    return nullptr;\n}\n\nbool VariableContainerOwning::add(std::unique_ptr<Variable> variable) {\n    variables.push_back(std::move(variable));\n    return true;\n}\n\nbool VariableContainerOwning::enablePersistency(std::shared_ptr<FilesystemAdapter> filesystem, const char *filename) {\n    this->filesystem = filesystem;\n\n    MO_FREE(this->filename);\n    this->filename = nullptr;\n\n    size_t fnsize = strlen(filename) + 1;\n\n    this->filename = static_cast<char*>(MO_MALLOC(getMemoryTag(), fnsize));\n    if (!this->filename) {\n        MO_DBG_ERR(\"OOM\");\n        return false;\n    }\n\n    snprintf(this->filename, fnsize, \"%s\", filename);\n    return true;\n}\n\nbool VariableContainerOwning::load() {\n    if (loaded) {\n        return true;\n    }\n\n    if (!filesystem || !filename) {\n        return true; //persistency disabled - nothing to do\n    }\n\n    size_t file_size = 0;\n    if (filesystem->stat(filename, &file_size) != 0 // file does not exist\n            || file_size == 0) {                         // file exists, but empty\n        MO_DBG_DEBUG(\"Populate FS: create variables file\");\n        return commit();\n    }\n\n    auto doc = FilesystemUtils::loadJson(filesystem, filename, getMemoryTag());\n    if (!doc) {\n        MO_DBG_ERR(\"failed to load %s\", filename);\n        return false;\n    }\n    \n    JsonArray variablesJson = (*doc)[\"variables\"];\n\n    for (JsonObject stored : variablesJson) {\n\n        const char *component = stored[\"component\"] | (const char*)nullptr;\n        int evseId = stored[\"evseId\"] | -1;\n        const char *name = stored[\"name\"] | (const char*)nullptr;\n\n        if (!component || !name) {\n            MO_DBG_ERR(\"corrupt entry: %s\", filename);\n            continue;\n        }\n\n        auto variablePtr = getVariable(ComponentId(component, EvseId(evseId)), name);\n        if (!variablePtr) {\n            MO_DBG_ERR(\"loaded variable does not exist: %s, %s, %s\", filename, component, name);\n            continue;\n        }\n\n        auto& variable = *variablePtr;\n\n        switch (variable.getInternalDataType()) {\n            case Variable::InternalDataType::Int:\n                if (variable.hasAttribute(Variable::AttributeType::Actual)) variable.setInt(stored[\"valActual\"] | 0, Variable::AttributeType::Actual);\n                if (variable.hasAttribute(Variable::AttributeType::Target)) variable.setInt(stored[\"valTarget\"] | 0, Variable::AttributeType::Target);\n                if (variable.hasAttribute(Variable::AttributeType::MinSet)) variable.setInt(stored[\"valMinSet\"] | 0, Variable::AttributeType::MinSet);\n                if (variable.hasAttribute(Variable::AttributeType::MaxSet)) variable.setInt(stored[\"valMaxSet\"] | 0, Variable::AttributeType::MaxSet);\n                break;\n            case Variable::InternalDataType::Bool:\n                if (variable.hasAttribute(Variable::AttributeType::Actual)) variable.setBool(stored[\"valActual\"] | false, Variable::AttributeType::Actual);\n                if (variable.hasAttribute(Variable::AttributeType::Target)) variable.setBool(stored[\"valTarget\"] | false, Variable::AttributeType::Target);\n                if (variable.hasAttribute(Variable::AttributeType::MinSet)) variable.setBool(stored[\"valMinSet\"] | false, Variable::AttributeType::MinSet);\n                if (variable.hasAttribute(Variable::AttributeType::MaxSet)) variable.setBool(stored[\"valMaxSet\"] | false, Variable::AttributeType::MaxSet);\n                break;\n            case Variable::InternalDataType::String:\n                bool success = true;\n                if (variable.hasAttribute(Variable::AttributeType::Actual)) success &= variable.setString(stored[\"valActual\"] | \"\", Variable::AttributeType::Actual);\n                if (variable.hasAttribute(Variable::AttributeType::Target)) success &= variable.setString(stored[\"valTarget\"] | \"\", Variable::AttributeType::Target);\n                if (variable.hasAttribute(Variable::AttributeType::MinSet)) success &= variable.setString(stored[\"valMinSet\"] | \"\", Variable::AttributeType::MinSet);\n                if (variable.hasAttribute(Variable::AttributeType::MaxSet)) success &= variable.setString(stored[\"valMaxSet\"] | \"\", Variable::AttributeType::MaxSet);\n                if (!success) {\n                    MO_DBG_ERR(\"value error: %s, %s, %s\", filename, component, name);\n                    continue;\n                }\n                break;\n        }\n    }\n\n    checkWriteCountUpdated(); // update trackWriteCount after load is completed\n\n    MO_DBG_DEBUG(\"Initialization finished\");\n    loaded = true;\n    return true;\n}\n\nbool VariableContainerOwning::commit() {\n    if (!filesystem || !filename) {\n        //persistency disabled - nothing to do\n        return true;\n    }\n\n    if (!checkWriteCountUpdated()) {\n        return true; //nothing to be done\n    }\n\n    size_t jsonCapacity = JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(0);\n    size_t variableCapacity = 0;\n    for (size_t i = 0; i < variables.size(); i++) {\n        auto& variable = *variables[i];\n\n        if (!variable.isPersistent()) {\n            continue;\n        }\n\n        size_t addedJsonCapacity = JSON_ARRAY_SIZE(variableCapacity + 1) - JSON_ARRAY_SIZE(variableCapacity);\n\n        size_t storedEntities = 2; //component name, variable name will always be stored\n        storedEntities += variable.getComponentId().evse.id >= 0 ?                 1 : 0;\n        storedEntities += variable.hasAttribute(Variable::AttributeType::Actual) ? 1 : 0;\n        storedEntities += variable.hasAttribute(Variable::AttributeType::Target) ? 1 : 0;\n        storedEntities += variable.hasAttribute(Variable::AttributeType::MinSet) ? 1 : 0;\n        storedEntities += variable.hasAttribute(Variable::AttributeType::MaxSet) ? 1 : 0;\n\n        addedJsonCapacity += JSON_OBJECT_SIZE(storedEntities);\n\n        if (jsonCapacity + addedJsonCapacity <= MO_MAX_JSON_CAPACITY) {\n            jsonCapacity += addedJsonCapacity;\n            variableCapacity++;\n        } else {\n            MO_DBG_ERR(\"configs JSON exceeds maximum capacity (%s, %zu entries). Crop configs file (by FCFS)\", filename, variables.size());\n            break;\n        }\n    }\n\n    auto doc = initJsonDoc(getMemoryTag(), jsonCapacity);\n\n    JsonArray variablesJson = doc.createNestedArray(\"variables\");\n\n    for (size_t i = 0; i < variableCapacity; i++) {\n        auto& variable = *variables[i];\n\n        if (!variable.isPersistent()) {\n            continue;\n        }\n\n        auto stored = variablesJson.createNestedObject();\n\n        stored[\"component\"] = variable.getComponentId().name;\n        if (variable.getComponentId().evse.id >= 0) {\n            stored[\"evseId\"] = variable.getComponentId().evse.id;\n        }\n        stored[\"name\"] = variable.getName();\n        \n        switch (variable.getInternalDataType()) {\n            case Variable::InternalDataType::Int:\n                if (variable.hasAttribute(Variable::AttributeType::Actual)) stored[\"valActual\"] = variable.getInt(Variable::AttributeType::Actual);\n                if (variable.hasAttribute(Variable::AttributeType::Target)) stored[\"valTarget\"] = variable.getInt(Variable::AttributeType::Target);\n                if (variable.hasAttribute(Variable::AttributeType::MinSet)) stored[\"valMinSet\"] = variable.getInt(Variable::AttributeType::MinSet);\n                if (variable.hasAttribute(Variable::AttributeType::MaxSet)) stored[\"valMaxSet\"] = variable.getInt(Variable::AttributeType::MaxSet);\n                break;\n            case Variable::InternalDataType::Bool:\n                if (variable.hasAttribute(Variable::AttributeType::Actual)) stored[\"valActual\"] = variable.getBool(Variable::AttributeType::Actual);\n                if (variable.hasAttribute(Variable::AttributeType::Target)) stored[\"valTarget\"] = variable.getBool(Variable::AttributeType::Target);\n                if (variable.hasAttribute(Variable::AttributeType::MinSet)) stored[\"valMinSet\"] = variable.getBool(Variable::AttributeType::MinSet);\n                if (variable.hasAttribute(Variable::AttributeType::MaxSet)) stored[\"valMaxSet\"] = variable.getBool(Variable::AttributeType::MaxSet);\n                break;\n            case Variable::InternalDataType::String:\n                if (variable.hasAttribute(Variable::AttributeType::Actual)) stored[\"valActual\"] = variable.getString(Variable::AttributeType::Actual);\n                if (variable.hasAttribute(Variable::AttributeType::Target)) stored[\"valTarget\"] = variable.getString(Variable::AttributeType::Target);\n                if (variable.hasAttribute(Variable::AttributeType::MinSet)) stored[\"valMinSet\"] = variable.getString(Variable::AttributeType::MinSet);\n                if (variable.hasAttribute(Variable::AttributeType::MaxSet)) stored[\"valMaxSet\"] = variable.getString(Variable::AttributeType::MaxSet);\n                break;\n        }\n    }\n\n\n    bool success = FilesystemUtils::storeJson(filesystem, filename, doc);\n\n    if (success) {\n        MO_DBG_DEBUG(\"Saving variables finished\");\n    } else {\n        MO_DBG_ERR(\"could not save variables file: %s\", filename);\n    }\n\n    return success;\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Model/Variables/VariableContainer.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Implementation of the UCs B05 - B06\n */\n\n#ifndef MO_VARIABLECONTAINER_H\n#define MO_VARIABLECONTAINER_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <memory>\n\n#include <MicroOcpp/Model/Variables/Variable.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass VariableContainer {\npublic:\n    ~VariableContainer();\n    virtual size_t size() = 0;\n    virtual Variable *getVariable(size_t i) = 0;\n    virtual Variable *getVariable(const ComponentId& component, const char *variableName) = 0;\n\n    virtual bool commit();\n};\n\nclass VariableContainerNonOwning : public VariableContainer, public MemoryManaged {\nprivate:\n    Vector<Variable*> variables;\npublic:\n    VariableContainerNonOwning();\n\n    size_t size() override;\n    Variable *getVariable(size_t i) override;\n    Variable *getVariable(const ComponentId& component, const char *variableName) override;\n\n    bool add(Variable *variable);\n};\n\nclass VariableContainerOwning : public VariableContainer, public MemoryManaged {\nprivate:\n    Vector<std::unique_ptr<Variable>> variables;\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    char *filename = nullptr;\n\n    uint16_t trackWriteCount = 0;\n    bool checkWriteCountUpdated();\n\n    bool loaded = false;\n\npublic:\n    VariableContainerOwning();\n    ~VariableContainerOwning();\n\n    size_t size() override;\n    Variable *getVariable(size_t i) override;\n    Variable *getVariable(const ComponentId& component, const char *variableName) override;\n\n    bool add(std::unique_ptr<Variable> variable);\n\n    bool enablePersistency(std::shared_ptr<FilesystemAdapter> filesystem, const char *filename); \n    bool load(); //load variables from flash\n    bool commit() override;\n};\n\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Model/Variables/VariableService.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Implementation of the UCs B05 - B06\n */\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Operations/SetVariables.h>\n#include <MicroOcpp/Operations/GetVariables.h>\n#include <MicroOcpp/Operations/GetBaseReport.h>\n#include <MicroOcpp/Operations/NotifyReport.h>\n#include <MicroOcpp/Core/Request.h>\n\n#include <cstring>\n#include <cctype>\n\n#include <MicroOcpp/Debug.h>\n\nnamespace MicroOcpp {\n\ntemplate <class T>\nVariableValidator<T>::VariableValidator(const ComponentId& component, const char *name, bool (*validateFn)(T, void*), void *userPtr) :\n        MemoryManaged(\"v201.Variables.VariableValidator.\", name), component(component), name(name), userPtr(userPtr), validateFn(validateFn) {\n\n}\n\ntemplate <class T>\nbool VariableValidator<T>::validate(T v) {\n    return validateFn(v, userPtr);\n}\n\ntemplate <class T>\nVariableValidator<T> *getVariableValidator(Vector<VariableValidator<T>>& collection, const ComponentId& component, const char *name) {\n    for (size_t i = 0; i < collection.size(); i++) {\n        auto& validator = collection[i];\n        if (!strcmp(name, validator.name) && component.equals(validator.component)) {\n            return &validator;\n        }\n    }\n    return nullptr;\n}\n\nVariableValidator<int> *VariableService::getValidatorInt(const ComponentId& component, const char *name) {\n    return getVariableValidator<int>(validatorInt, component, name);\n}\n\nVariableValidator<bool> *VariableService::getValidatorBool(const ComponentId& component, const char *name) {\n    return getVariableValidator<bool>(validatorBool, component, name);\n}\n\nVariableValidator<const char*> *VariableService::getValidatorString(const ComponentId& component, const char *name) {\n    return getVariableValidator<const char*>(validatorString, component, name);\n}\n\nVariableContainerOwning& VariableService::getContainerInternalByVariable(const ComponentId& component, const char *name) {\n    unsigned int hash = 0;\n    for (size_t i = 0; i < strlen(component.name); i++) {\n        hash += (unsigned int)component.name[i];\n    }\n    if (component.evse.id >= 0)\n        hash += (unsigned int)component.evse.id;\n    if (component.evse.connectorId >= 0)\n        hash += (unsigned int)component.evse.connectorId;\n    for (size_t i = 0; i < strlen(name); i++) {\n        hash += (unsigned int)name[i];\n    }\n    return containersInternal[hash % MO_VARIABLESTORE_BUCKETS];\n}\n\nvoid VariableService::addContainer(VariableContainer *container) {\n    containers.push_back(container);\n}\n\ntemplate <class T>\nbool registerVariableValidator(Vector<VariableValidator<T>>& collection, const ComponentId& component, const char *name, bool (*validate)(T, void*), void *userPtr) {\n    for (auto it = collection.begin(); it != collection.end(); it++) {\n        if (!strcmp(name, it->name) && component.equals(it->component)) {\n            collection.erase(it);\n            break;\n        }\n    }\n    collection.emplace_back(component, name, validate, userPtr);\n    return true;\n}\n\ntemplate <>\nbool VariableService::registerValidator<int>(const ComponentId& component, const char *name, bool (*validate)(int, void*), void *userPtr) {\n    return registerVariableValidator<int>(validatorInt, component, name, validate, userPtr);\n}\n\ntemplate <>\nbool VariableService::registerValidator<bool>(const ComponentId& component, const char *name, bool (*validate)(bool, void*), void *userPtr) {\n    return registerVariableValidator<bool>(validatorBool, component, name, validate, userPtr);\n}\n\ntemplate <>\nbool VariableService::registerValidator<const char*>(const ComponentId& component, const char *name, bool (*validate)(const char*, void*), void *userPtr) {\n    return registerVariableValidator<const char*>(validatorString, component, name, validate, userPtr);\n}\n\nVariable *VariableService::getVariable(const ComponentId& component, const char *name) {\n\n    if (auto variable = getContainerInternalByVariable(component, name).getVariable(component, name)) {\n        return variable;\n    }\n\n    for (size_t i = 0; i < containers.size(); i++) {\n        auto container = containers[containers.size() - i - 1]; //search from back, because internal containers at front don't contain variable\n        if (auto variable = container->getVariable(component, name)) {\n            return variable;\n        }\n    }\n\n    return nullptr;\n}\n\nVariableService::VariableService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem) :\n            MemoryManaged(\"v201.Variables.VariableService\"),\n            context(context), filesystem(filesystem),\n            containers(makeVector<VariableContainer*>(getMemoryTag())),\n            validatorInt(makeVector<VariableValidator<int>>(getMemoryTag())),\n            validatorBool(makeVector<VariableValidator<bool>>(getMemoryTag())),\n            validatorString(makeVector<VariableValidator<const char*>>(getMemoryTag())) {\n    \n    containers.reserve(MO_VARIABLESTORE_BUCKETS + 1);\n\n    for (unsigned int i = 0; i < MO_VARIABLESTORE_BUCKETS; i++) {\n        char fn [MO_MAX_PATH_SIZE];\n        auto ret = snprintf(fn, sizeof(fn), \"%s%02x%s\", MO_VARIABLESTORE_FN_PREFIX, i, MO_VARIABLESTORE_FN_SUFFIX);\n        if (ret < 0 || (size_t)ret >= sizeof(fn)) {\n            MO_DBG_ERR(\"fn error\");\n            continue;\n        }\n        containersInternal[i].enablePersistency(filesystem, fn);\n        containers.push_back(&containersInternal[i]);\n    }\n    containers.push_back(&containerExternal);\n\n    context.getOperationRegistry().registerOperation(\"SetVariables\", [this] () {\n        return new Ocpp201::SetVariables(*this);});\n    context.getOperationRegistry().registerOperation(\"GetVariables\", [this] () {\n        return new Ocpp201::GetVariables(*this);});\n    context.getOperationRegistry().registerOperation(\"GetBaseReport\", [this] () {\n        return new Ocpp201::GetBaseReport(*this);});\n}\n\ntemplate<class T>\nbool loadVariableFactoryDefault(Variable& variable, T factoryDef);\n\ntemplate<>\nbool loadVariableFactoryDefault<int>(Variable& variable, int factoryDef) {\n    variable.setInt(factoryDef);\n    return true;\n}\n\ntemplate<>\nbool loadVariableFactoryDefault<bool>(Variable& variable, bool factoryDef) {\n    variable.setBool(factoryDef);\n    return true;\n}\n\ntemplate<>\nbool loadVariableFactoryDefault<const char*>(Variable& variable, const char *factoryDef) {\n    return variable.setString(factoryDef);\n}\n\nvoid loadVariableCharacteristics(Variable& variable, Variable::Mutability mutability, bool persistent, bool rebootRequired, Variable::InternalDataType defaultDataType) {\n    if (variable.getMutability() == Variable::Mutability::ReadWrite) {\n        variable.setMutability(mutability);\n    }\n\n    if (persistent) {\n        variable.setPersistent();\n    }\n\n    if (rebootRequired) {\n        variable.setRebootRequired();\n    }\n\n    switch (defaultDataType) {\n        case Variable::InternalDataType::Int:\n            variable.setVariableDataType(MicroOcpp::VariableCharacteristics::DataType::integer);\n            break;\n        case Variable::InternalDataType::Bool:\n            variable.setVariableDataType(MicroOcpp::VariableCharacteristics::DataType::boolean);\n            break;\n        case Variable::InternalDataType::String:\n            variable.setVariableDataType(MicroOcpp::VariableCharacteristics::DataType::string);\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            break;\n    }\n}\n\ntemplate<class T>\nVariable::InternalDataType getInternalDataType();\n\ntemplate<> Variable::InternalDataType getInternalDataType<int>() {return Variable::InternalDataType::Int;}\ntemplate<> Variable::InternalDataType getInternalDataType<bool>() {return Variable::InternalDataType::Bool;}\ntemplate<> Variable::InternalDataType getInternalDataType<const char*>() {return Variable::InternalDataType::String;}\n\ntemplate<class T>\nVariable *VariableService::declareVariable(const ComponentId& component, const char *name, T factoryDefault, Variable::Mutability mutability, bool persistent, Variable::AttributeTypeSet attributes, bool rebootRequired) {\n\n    auto res = getVariable(component, name);\n    if (!res) {\n        auto variable = makeVariable(getInternalDataType<T>(), attributes);\n        if (!variable) {\n            MO_DBG_ERR(\"OOM\");\n            return nullptr;\n        }\n\n        variable->setName(name);\n        variable->setComponentId(component);\n\n        if (!loadVariableFactoryDefault<T>(*variable, factoryDefault)) {\n            return nullptr;\n        }\n\n        res = variable.get();\n\n        if (!getContainerInternalByVariable(component, name).add(std::move(variable))) {\n            return nullptr;\n        }\n    }\n\n    loadVariableCharacteristics(*res, mutability, persistent, rebootRequired, getInternalDataType<T>());\n    return res;\n}\n\ntemplate Variable *VariableService::declareVariable<int>(        const ComponentId&, const char*, int,         Variable::Mutability, bool, Variable::AttributeTypeSet, bool);\ntemplate Variable *VariableService::declareVariable<bool>(       const ComponentId&, const char*, bool,        Variable::Mutability, bool, Variable::AttributeTypeSet, bool);\ntemplate Variable *VariableService::declareVariable<const char*>(const ComponentId&, const char*, const char*, Variable::Mutability, bool, Variable::AttributeTypeSet, bool);\n\nbool VariableService::addVariable(Variable *variable) {\n    return containerExternal.add(variable);\n}\n\nbool VariableService::addVariable(std::unique_ptr<Variable> variable) {\n    return getContainerInternalByVariable(variable->getComponentId(), variable->getName()).add(std::move(variable));\n}\n\nbool VariableService::load() {\n    bool success = true;\n\n    for (size_t i = 0; i < MO_VARIABLESTORE_BUCKETS; i++) {\n        if (!containersInternal[i].load()) {\n            success = false;\n        }\n    }\n\n    return success;\n}\n\nbool VariableService::commit() {\n    bool success = true;\n\n    for (size_t i = 0; i < containers.size(); i++) {\n        if (!containers[i]->commit()) {\n            success = false;\n        }\n    }\n\n    return success;\n}\n\nSetVariableStatus VariableService::setVariable(Variable::AttributeType attrType, const char *value, const ComponentId& component, const char *variableName) {\n\n    Variable *variable = nullptr;\n\n    bool foundComponent = false;\n    for (size_t i = 0; i < containers.size(); i++) {\n        auto container = containers[i];\n\n        for (size_t i = 0; i < container->size(); i++) {\n            auto entry = container->getVariable(i);\n\n            if (entry->getComponentId().equals(component)) {\n                foundComponent = true;\n\n                if (!strcmp(entry->getName(), variableName)) {\n                    // found variable. Search terminated in this block\n\n                    variable = entry;\n                    break;\n                }\n            }\n        }\n        if (variable) {\n            // result found in inner for-loop\n            break;\n        }\n    }\n\n    if (!variable) {\n        if (foundComponent) {\n            return SetVariableStatus::UnknownVariable;\n        } else {\n            return SetVariableStatus::UnknownComponent; \n        }\n    }\n\n    if (variable->getMutability() == Variable::Mutability::ReadOnly) {\n        return SetVariableStatus::Rejected;\n    }\n\n    if (!variable->hasAttribute(attrType)) {\n        return SetVariableStatus::NotSupportedAttributeType;\n    }\n\n    //write config\n\n    /*\n     * Try to interpret input as number\n     */\n\n    bool convertibleInt = true;\n    int numInt = 0;\n    bool convertibleBool = true;\n    bool numBool = false;\n\n    int nDigits = 0, nNonDigits = 0, nDots = 0, nSign = 0; //\"-1.234\" has 4 digits, 0 nonDigits, 1 dot and 1 sign. Don't allow comma as seperator. Don't allow e-expressions (e.g. 1.23e-7)\n    for (const char *c = value; *c; ++c) {\n        if (*c >= '0' && *c <= '9') {\n            //int interpretation\n            if (nDots == 0) { //only append number if before floating point\n                nDigits++;\n                numInt *= 10;\n                numInt += *c - '0';\n            }\n        } else if (*c == '.') {\n            nDots++;\n        } else if (c == value && *c == '-') {\n            nSign++;\n        } else {\n            nNonDigits++;\n        }\n    }\n\n    if (nSign == 1) {\n        numInt = -numInt;\n    }\n\n    int INT_MAXDIGITS; //plausibility check: this allows a numerical range of (-999,999,999 to 999,999,999), or (-9,999 to 9,999) respectively\n    if (sizeof(int) >= 4UL)\n        INT_MAXDIGITS = 9;\n    else\n        INT_MAXDIGITS = 4;\n\n    if (nNonDigits > 0 || nDigits == 0 || nSign > 1 || nDots > 1) {\n        convertibleInt = false;\n    }\n\n    if (nDigits > INT_MAXDIGITS) {\n        MO_DBG_DEBUG(\"Possible integer overflow: key = %s, value = %s\", variableName, value);\n        convertibleInt = false;\n    }\n\n    if (tolower(value[0]) == 't' && tolower(value[1]) == 'r' && tolower(value[2]) == 'u' && tolower(value[3]) == 'e' && !value[4]) {\n        numBool = true;\n    } else if (tolower(value[0]) == 'f' && tolower(value[1]) == 'a' && tolower(value[2]) == 'l' && tolower(value[3]) == 's' && tolower(value[4]) == 'e' && !value[5]) {\n        numBool = false;\n    } else if (convertibleInt) {\n        numBool = numInt != 0;\n    } else {\n        convertibleBool = false;\n    }\n\n    // validate and store (parsed) value to Config\n\n    if (variable->getInternalDataType() == Variable::InternalDataType::Int && convertibleInt) {\n        auto validator = getValidatorInt(component, variableName);\n        if (validator && !validator->validate(numInt)) {\n            MO_DBG_WARN(\"validation failed for variable=%s\", variableName);\n            return SetVariableStatus::Rejected;\n        }\n        variable->setInt(numInt);\n    } else if (variable->getInternalDataType() == Variable::InternalDataType::Bool && convertibleBool) {\n        auto validator = getValidatorBool(component, variableName);\n        if (validator && !validator->validate(numBool)) {\n            MO_DBG_WARN(\"validation failed for variable=%s\", variableName);\n            return SetVariableStatus::Rejected;\n        }\n        variable->setBool(numBool);\n    } else if (variable->getInternalDataType() == Variable::InternalDataType::String) {\n        auto validator = getValidatorString(component, variableName);\n        if (validator && !validator->validate(value)) {\n            MO_DBG_WARN(\"validation failed for variable=%s\", variableName);\n            return SetVariableStatus::Rejected;\n        }\n        variable->setString(value);\n    } else {\n        MO_DBG_WARN(\"Value has incompatible type\");\n        return SetVariableStatus::Rejected;\n    }\n\n    if (variable->isRebootRequired()) {\n        return SetVariableStatus::RebootRequired;\n    }\n\n    return SetVariableStatus::Accepted;\n}\n\nGetVariableStatus VariableService::getVariable(Variable::AttributeType attrType, const ComponentId& component, const char *variableName, Variable **result) {\n\n    bool foundComponent = false;\n    for (size_t i = 0; i < containers.size(); i++) {\n        auto container = containers[i];\n\n        for (size_t i = 0; i < container->size(); i++) {\n            auto variable = container->getVariable(i);\n\n            if (variable->getComponentId().equals(component)) {\n                foundComponent = true;\n\n                if (!strcmp(variable->getName(), variableName)) {\n                    // found variable. Search terminated in this block\n\n                    if (variable->getMutability() == Variable::Mutability::WriteOnly) {\n                        return GetVariableStatus::Rejected;\n                    }\n\n                    if (variable->hasAttribute(attrType)) {\n                        *result = variable;\n                        return GetVariableStatus::Accepted;\n                    } else {\n                        return GetVariableStatus::NotSupportedAttributeType;\n                    }\n                }\n            }\n        }\n    }\n\n    if (foundComponent) {\n        return GetVariableStatus::UnknownVariable;\n    } else {\n        return GetVariableStatus::UnknownComponent; \n    }\n}\n\nGenericDeviceModelStatus VariableService::getBaseReport(int requestId, ReportBase reportBase) {\n\n    if (reportBase == ReportBase_SummaryInventory) {\n        return GenericDeviceModelStatus_NotSupported;\n    }\n\n    Vector<Variable*> variables = makeVector<Variable*>(getMemoryTag());\n\n    for (size_t i = 0; i < containers.size(); i++) {\n        auto container = containers[i];\n\n        for (size_t i = 0; i < container->size(); i++) {\n            auto variable = container->getVariable(i);\n\n            if (reportBase == ReportBase_ConfigurationInventory && variable->getMutability() == Variable::Mutability::ReadOnly) {\n                continue;\n            }\n\n            variables.push_back(variable);\n        }\n    }\n\n    if (variables.empty()) {\n        return GenericDeviceModelStatus_EmptyResultSet;\n    }\n\n    auto notifyReport = makeRequest(new Ocpp201::NotifyReport(\n            context.getModel(), \n            requestId,\n            context.getModel().getClock().now(),\n            false,\n            0,\n            variables));\n\n    context.initiateRequest(std::move(notifyReport));\n\n    return GenericDeviceModelStatus_Accepted;\n}\n\n} // namespace MicroOcpp\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Model/Variables/VariableService.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * Implementation of the UCs B05 - B06\n */\n\n#ifndef MO_VARIABLESERVICE_H\n#define MO_VARIABLESERVICE_H\n\n#include <stdint.h>\n#include <memory>\n#include <limits>\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Variables/Variable.h>\n#include <MicroOcpp/Model/Variables/VariableContainer.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#ifndef MO_VARIABLESTORE_FN_PREFIX\n#define MO_VARIABLESTORE_FN_PREFIX (MO_FILENAME_PREFIX \"ocpp-vars-\")\n#endif\n\n#ifndef MO_VARIABLESTORE_FN_SUFFIX\n#define MO_VARIABLESTORE_FN_SUFFIX \".jsn\"\n#endif\n\nnamespace MicroOcpp {\n\ntemplate <class T>\nstruct VariableValidator : public MemoryManaged {\n    ComponentId component;\n    const char *name;\n    void *userPtr;\n    bool (*validateFn)(T, void*);\n    VariableValidator(const ComponentId& component, const char *name, bool (*validate)(T, void*), void *userPtr);\n    bool validate(T);\n};\n\nclass Context;\n\n#ifndef MO_VARIABLESTORE_BUCKETS\n#define MO_VARIABLESTORE_BUCKETS 8\n#endif\n\nclass VariableService : public MemoryManaged {\nprivate:\n    Context& context;\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    Vector<VariableContainer*> containers;\n    VariableContainerNonOwning containerExternal;\n    VariableContainerOwning containersInternal [MO_VARIABLESTORE_BUCKETS];\n    VariableContainerOwning& getContainerInternalByVariable(const ComponentId& component, const char *name);\n\n    Vector<VariableValidator<int>> validatorInt;\n    Vector<VariableValidator<bool>> validatorBool;\n    Vector<VariableValidator<const char*>> validatorString;\n\n    VariableValidator<int> *getValidatorInt(const ComponentId& component, const char *name);\n    VariableValidator<bool> *getValidatorBool(const ComponentId& component, const char *name);\n    VariableValidator<const char*> *getValidatorString(const ComponentId& component, const char *name);\npublic:\n    VariableService(Context& context, std::shared_ptr<FilesystemAdapter> filesystem);\n\n    //Get Variable. If not existent, create Variable owned by MO and return\n    template <class T> \n    Variable *declareVariable(const ComponentId& component, const char *name, T factoryDefault, Variable::Mutability mutability = Variable::Mutability::ReadWrite, bool persistent = true, Variable::AttributeTypeSet attributes = Variable::AttributeTypeSet(), bool rebootRequired = false);\n\n    bool addVariable(Variable *variable); //Add Variable without transferring ownership\n    bool addVariable(std::unique_ptr<Variable> variable); //Add Variable and transfer ownership\n\n    //Get Variable. If not existent, return nullptr\n    Variable *getVariable(const ComponentId& component, const char *name);\n\n    bool load();\n    bool commit();\n\n    void addContainer(VariableContainer *container);\n\n    template <class T>\n    bool registerValidator(const ComponentId& component, const char *name, bool (*validate)(T, void*), void *userPtr = nullptr);\n\n    SetVariableStatus setVariable(Variable::AttributeType attrType, const char *attrVal, const ComponentId& component, const char *variableName);\n\n    GetVariableStatus getVariable(Variable::AttributeType attrType, const ComponentId& component, const char *variableName, Variable **result);\n\n    GenericDeviceModelStatus getBaseReport(int requestId, ReportBase reportBase);\n};\n\n} // namespace MicroOcpp\n\n#endif // MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/Authorize.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/Authorize.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nAuthorize::Authorize(Model& model, const char *idTagIn) : MemoryManaged(\"v16.Operation.\", \"Authorize\"), model(model) {\n    if (idTagIn && strnlen(idTagIn, IDTAG_LEN_MAX + 2) <= IDTAG_LEN_MAX) {\n        snprintf(idTag, IDTAG_LEN_MAX + 1, \"%s\", idTagIn);\n    } else {\n        MO_DBG_WARN(\"Format violation of idTag. Discard idTag\");\n    }\n}\n\nconst char* Authorize::getOperationType(){\n    return \"Authorize\";\n}\n\nstd::unique_ptr<JsonDoc> Authorize::createReq() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + (IDTAG_LEN_MAX + 1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"idTag\"] = idTag;\n    return doc;\n}\n\nvoid Authorize::processConf(JsonObject payload){\n    const char *idTagInfo = payload[\"idTagInfo\"][\"status\"] | \"not specified\";\n\n    if (!strcmp(idTagInfo, \"Accepted\")) {\n        MO_DBG_INFO(\"Request has been accepted\");\n    } else {\n        MO_DBG_INFO(\"Request has been denied. Reason: %s\", idTagInfo);\n    }\n\n#if MO_ENABLE_LOCAL_AUTH\n    if (auto authService = model.getAuthorizationService()) {\n        authService->notifyAuthorization(idTag, payload[\"idTagInfo\"]);\n    }\n#endif //MO_ENABLE_LOCAL_AUTH\n}\n\nvoid Authorize::processReq(JsonObject payload){\n    /*\n     * Ignore Contents of this Req-message, because this is for debug purposes only\n     */\n}\n\nstd::unique_ptr<JsonDoc> Authorize::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), 2 * JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    JsonObject idTagInfo = payload.createNestedObject(\"idTagInfo\");\n    idTagInfo[\"status\"] = \"Accepted\";\n    return doc;\n}\n\n} // namespace Ocpp16\n} // namespace MicroOcpp\n\n#if MO_ENABLE_V201\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nAuthorize::Authorize(Model& model, const IdToken& idToken) : MemoryManaged(\"v201.Operation.Authorize\"), model(model) {\n    this->idToken = idToken;\n}\n\nconst char* Authorize::getOperationType(){\n    return \"Authorize\";\n}\n\nstd::unique_ptr<JsonDoc> Authorize::createReq() {\n    auto doc = makeJsonDoc(getMemoryTag(),\n            JSON_OBJECT_SIZE(1) +\n            JSON_OBJECT_SIZE(2));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"idToken\"][\"idToken\"] = idToken.get();\n    payload[\"idToken\"][\"type\"] = idToken.getTypeCstr();\n    return doc;\n}\n\nvoid Authorize::processConf(JsonObject payload){\n    const char *idTagInfo = payload[\"idTokenInfo\"][\"status\"] | \"_Undefined\";\n\n    if (!strcmp(idTagInfo, \"Accepted\")) {\n        MO_DBG_INFO(\"Request has been accepted\");\n    } else {\n        MO_DBG_INFO(\"Request has been denied. Reason: %s\", idTagInfo);\n    }\n\n    //if (model.getAuthorizationService()) {\n    //    model.getAuthorizationService()->notifyAuthorization(idTag, payload[\"idTagInfo\"]);\n    //}\n}\n\nvoid Authorize::processReq(JsonObject payload){\n    /*\n     * Ignore Contents of this Req-message, because this is for debug purposes only\n     */\n}\n\nstd::unique_ptr<JsonDoc> Authorize::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), 2 * JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    JsonObject idTagInfo = payload.createNestedObject(\"idTokenInfo\");\n    idTagInfo[\"status\"] = \"Accepted\";\n    return doc;\n}\n\n} // namespace Ocpp201\n} // namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/Authorize.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef AUTHORIZE_H\n#define AUTHORIZE_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Operations/CiStrings.h>\n#include <MicroOcpp/Version.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass Authorize : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    char idTag [IDTAG_LEN_MAX + 1] = {'\\0'};\npublic:\n    Authorize(Model& model, const char *idTag);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Authorization/IdToken.h>\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nclass Authorize : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    IdToken idToken;\npublic:\n    Authorize(Model& model, const IdToken& idToken);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n};\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/BootNotification.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/BootNotification.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Boot/BootService.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Version.h>\n#include <MicroOcpp/Debug.h>\n\n#include <string.h>\n\nusing MicroOcpp::Ocpp16::BootNotification;\nusing MicroOcpp::JsonDoc;\n\nBootNotification::BootNotification(Model& model, std::unique_ptr<JsonDoc> payload) : MemoryManaged(\"v16.Operation.\", \"BootNotification\"), model(model), credentials(std::move(payload)) {\n    \n}\n\nconst char* BootNotification::getOperationType(){\n    return \"BootNotification\";\n}\n\nstd::unique_ptr<JsonDoc> BootNotification::createReq() {\n    if (credentials) {\n#if MO_ENABLE_V201\n        if (model.getVersion().major == 2) {\n            auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2) + credentials->memoryUsage());\n            JsonObject payload = doc->to<JsonObject>();\n            payload[\"reason\"] = \"PowerUp\";\n            payload[\"chargingStation\"] = *credentials;\n            return doc;\n        }\n#endif\n        return std::unique_ptr<JsonDoc>(new JsonDoc(*credentials));\n    } else {\n        MO_DBG_ERR(\"payload undefined\");\n        return createEmptyDocument();\n    }\n}\n\nvoid BootNotification::processConf(JsonObject payload) {\n    const char* currentTime = payload[\"currentTime\"] | \"Invalid\";\n    if (strcmp(currentTime, \"Invalid\")) {\n        if (model.getClock().setTime(currentTime)) {\n            //success\n        } else {\n            MO_DBG_ERR(\"Time string format violation. Expect format like 2022-02-01T20:53:32.486Z\");\n            errorCode = \"PropertyConstraintViolation\";\n            return;\n        }\n    } else {\n        MO_DBG_ERR(\"Missing attribute currentTime\");\n        errorCode = \"FormationViolation\";\n        return;\n    }\n    \n    int interval = payload[\"interval\"] | -1;\n    if (interval < 0) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    RegistrationStatus status = deserializeRegistrationStatus(payload[\"status\"] | \"Invalid\");\n    \n    if (status == RegistrationStatus::UNDEFINED) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    if (status == RegistrationStatus::Accepted) {\n        //only write if in valid range\n        if (interval >= 1) {\n            auto heartbeatIntervalInt = declareConfiguration<int>(\"HeartbeatInterval\", 86400);\n            if (heartbeatIntervalInt && interval != heartbeatIntervalInt->getInt()) {\n                heartbeatIntervalInt->setInt(interval);\n                configuration_save();\n            }\n        }\n    }\n\n    if (auto bootService = model.getBootService()) {\n\n        if (status != RegistrationStatus::Accepted) {\n            bootService->setRetryInterval(interval);\n        }\n\n        bootService->notifyRegistrationStatus(status);\n    }\n\n    MO_DBG_INFO(\"request has been %s\", status == RegistrationStatus::Accepted ? \"Accepted\" :\n                                       status == RegistrationStatus::Pending ? \"replied with Pending\" :\n                                       \"Rejected\");\n}\n\nvoid BootNotification::processReq(JsonObject payload){\n    /*\n     * Ignore Contents of this Req-message, because this is for debug purposes only\n     */\n}\n\nstd::unique_ptr<JsonDoc> BootNotification::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(3) + (JSONDATE_LENGTH + 1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    //safety mechanism; in some test setups the library has to answer BootNotifications without valid system time\n    Timestamp ocppTimeReference = Timestamp(2022,0,27,11,59,55); \n    Timestamp ocppSelect = ocppTimeReference;\n    auto& ocppTime = model.getClock();\n    Timestamp ocppNow = ocppTime.now();\n    if (ocppNow > ocppTimeReference) {\n        //time has already been set\n        ocppSelect = ocppNow;\n    }\n\n    char ocppNowJson [JSONDATE_LENGTH + 1] = {'\\0'};\n    ocppSelect.toJsonString(ocppNowJson, JSONDATE_LENGTH + 1);\n    payload[\"currentTime\"] = ocppNowJson;\n\n    payload[\"interval\"] = 86400; //heartbeat send interval - not relevant for JSON variant of OCPP so send dummy value that likely won't break\n    payload[\"status\"] = \"Accepted\";\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/BootNotification.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_BOOTNOTIFICATION_H\n#define MO_BOOTNOTIFICATION_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Operations/CiStrings.h>\n\n#define CP_MODEL_LEN_MAX        CiString20TypeLen\n#define CP_SERIALNUMBER_LEN_MAX CiString25TypeLen\n#define CP_VENDOR_LEN_MAX       CiString20TypeLen\n#define FW_VERSION_LEN_MAX      CiString50TypeLen\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass BootNotification : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    std::unique_ptr<JsonDoc> credentials;\n    const char *errorCode = nullptr;\npublic:\n    BootNotification(Model& model, std::unique_ptr<JsonDoc> payload);\n\n    ~BootNotification() = default;\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/CancelReservation.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_RESERVATION\n\n#include <MicroOcpp/Operations/CancelReservation.h>\n#include <MicroOcpp/Model/Reservation/ReservationService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::CancelReservation;\nusing MicroOcpp::JsonDoc;\n\nCancelReservation::CancelReservation(ReservationService& reservationService) : MemoryManaged(\"v16.Operation.\", \"CancelReservation\"), reservationService(reservationService) {\n  \n}\n\nconst char* CancelReservation::getOperationType() {\n    return \"CancelReservation\";\n}\n\nvoid CancelReservation::processReq(JsonObject payload) {\n    if (!payload.containsKey(\"reservationId\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    if (auto reservation = reservationService.getReservationById(payload[\"reservationId\"])) {\n        found = true;\n        reservation->clear();\n    }\n}\n\nstd::unique_ptr<JsonDoc> CancelReservation::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    if (found) {\n        payload[\"status\"] = \"Accepted\";\n    } else {\n        payload[\"status\"] = \"Rejected\";\n    }\n    return doc;\n}\n\n#endif //MO_ENABLE_RESERVATION\n"
  },
  {
    "path": "src/MicroOcpp/Operations/CancelReservation.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CANCELRESERVATION_H\n#define MO_CANCELRESERVATION_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_RESERVATION\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass ReservationService;\n\nnamespace Ocpp16 {\n\nclass CancelReservation : public Operation, public MemoryManaged {\nprivate:\n    ReservationService& reservationService;\n    bool found = false;\n    const char *errorCode = nullptr;\npublic:\n    CancelReservation(ReservationService& reservationService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_RESERVATION\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ChangeAvailability.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/ChangeAvailability.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Version.h>\n\n#include <functional>\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nChangeAvailability::ChangeAvailability(Model& model) : MemoryManaged(\"v16.Operation.\", \"ChangeAvailability\"), model(model) {\n\n}\n\nconst char* ChangeAvailability::getOperationType(){\n    return \"ChangeAvailability\";\n}\n\nvoid ChangeAvailability::processReq(JsonObject payload) {\n    int connectorIdRaw = payload[\"connectorId\"] | -1;\n    if (connectorIdRaw < 0) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n    unsigned int connectorId = (unsigned int)connectorIdRaw;\n\n    if (connectorId >= model.getNumConnectors()) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    const char *type = payload[\"type\"] | \"INVALID\";\n    bool available = false;\n\n    if (!strcmp(type, \"Operative\")) {\n        accepted = true;\n        available = true;\n    } else if (!strcmp(type, \"Inoperative\")) {\n        accepted = true;\n        available = false;\n    } else {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    if (connectorId == 0) {\n        for (unsigned int cId = 0; cId < model.getNumConnectors(); cId++) {\n            auto connector = model.getConnector(cId);\n            connector->setAvailability(available);\n            if (connector->isOperative() && !available) {\n                scheduled = true;\n            }\n        }\n    } else {\n        auto connector = model.getConnector(connectorId);\n        connector->setAvailability(available);\n        if (connector->isOperative() && !available) {\n            scheduled = true;\n        }\n    }\n}\n\nstd::unique_ptr<JsonDoc> ChangeAvailability::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    if (!accepted) {\n        payload[\"status\"] = \"Rejected\";\n    } else if (scheduled) {\n        payload[\"status\"] = \"Scheduled\";\n    } else {\n        payload[\"status\"] = \"Accepted\";\n    }\n        \n    return doc;\n}\n\n} // namespace Ocpp16\n} // namespace MicroOcpp\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Availability/AvailabilityService.h>\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nChangeAvailability::ChangeAvailability(AvailabilityService& availabilityService) : MemoryManaged(\"v201.Operation.\", \"ChangeAvailability\"), availabilityService(availabilityService) {\n\n}\n\nconst char* ChangeAvailability::getOperationType(){\n    return \"ChangeAvailability\";\n}\n\nvoid ChangeAvailability::processReq(JsonObject payload) {\n\n    unsigned int evseId = 0;\n    \n    if (payload.containsKey(\"evse\")) {\n        int evseIdRaw = payload[\"evse\"][\"id\"] | -1;\n        if (evseIdRaw < 0) {\n            errorCode = \"FormationViolation\";\n            return;\n        }\n        evseId = (unsigned int)evseIdRaw;\n\n        if ((payload[\"evse\"][\"connectorId\"] | 1) != 1) {\n            errorCode = \"PropertyConstraintViolation\";\n            return;\n        }\n    }\n\n    auto availabilityEvse = availabilityService.getEvse(evseId);\n    if (!availabilityEvse) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    const char *type = payload[\"operationalStatus\"] | \"_Undefined\";\n\n    bool operative = false;\n\n    if (!strcmp(type, \"Operative\")) {\n        operative = true;\n    } else if (!strcmp(type, \"Inoperative\")) {\n        operative = false;\n    } else {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    status = availabilityEvse->changeAvailability(operative);\n}\n\nstd::unique_ptr<JsonDoc> ChangeAvailability::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    switch (status) {\n        case ChangeAvailabilityStatus::Accepted:\n            payload[\"status\"] = \"Accepted\";\n            break;\n        case ChangeAvailabilityStatus::Scheduled:\n            payload[\"status\"] = \"Scheduled\";\n            break;\n        case ChangeAvailabilityStatus::Rejected:\n            payload[\"status\"] = \"Rejected\";\n            break;\n    }\n\n    return doc;\n}\n\n} // namespace Ocpp201\n} // namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ChangeAvailability.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CHANGEAVAILABILITY_H\n#define MO_CHANGEAVAILABILITY_H\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass ChangeAvailability : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    bool scheduled = false;\n    bool accepted = false;\n\n    const char *errorCode {nullptr};\npublic:\n    ChangeAvailability(Model& model);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/Availability/ChangeAvailabilityStatus.h>\n#include <MicroOcpp/Model/Authorization/IdToken.h>\n\nnamespace MicroOcpp {\n\nclass AvailabilityService;\n\nnamespace Ocpp201 {\n\nclass ChangeAvailability : public Operation, public MemoryManaged {\nprivate:\n    AvailabilityService& availabilityService;\n    ChangeAvailabilityStatus status = ChangeAvailabilityStatus::Rejected;\n\n    const char *errorCode {nullptr};\npublic:\n    ChangeAvailability(AvailabilityService& availabilityService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ChangeConfiguration.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/ChangeConfiguration.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Debug.h>\n\n#include <cctype> //for tolower\n\nusing MicroOcpp::Ocpp16::ChangeConfiguration;\nusing MicroOcpp::JsonDoc;\n\nChangeConfiguration::ChangeConfiguration() : MemoryManaged(\"v16.Operation.\", \"ChangeConfiguration\") {\n  \n}\n\nconst char* ChangeConfiguration::getOperationType(){\n    return \"ChangeConfiguration\";\n}\n\nvoid ChangeConfiguration::processReq(JsonObject payload) {\n    const char *key = payload[\"key\"] | \"\";\n    if (!*key) {\n        errorCode = \"FormationViolation\";\n        MO_DBG_WARN(\"Could not read key\");\n        return;\n    }\n\n    if (!payload[\"value\"].is<const char *>()) {\n        errorCode = \"FormationViolation\";\n        MO_DBG_WARN(\"Message is lacking value\");\n        return;\n    }\n\n    const char *value = payload[\"value\"];\n\n    auto configuration = getConfigurationPublic(key);\n\n    if (!configuration) {\n        //configuration not found or hidden configuration\n        notSupported = true;\n        return;\n    }\n\n    if (configuration->isReadOnly()) {\n        MO_DBG_WARN(\"Trying to override readonly value\");\n        readOnly = true;\n        return;\n    }\n\n    //write config\n\n    /*\n    * Try to interpret input as number\n    */\n\n    bool convertibleInt = true;\n    int numInt = 0;\n    bool convertibleBool = true;\n    bool numBool = false;\n\n    int nDigits = 0, nNonDigits = 0, nDots = 0, nSign = 0; //\"-1.234\" has 4 digits, 0 nonDigits, 1 dot and 1 sign. Don't allow comma as seperator. Don't allow e-expressions (e.g. 1.23e-7)\n    for (const char *c = value; *c; ++c) {\n        if (*c >= '0' && *c <= '9') {\n            //int interpretation\n            if (nDots == 0) { //only append number if before floating point\n                nDigits++;\n                numInt *= 10;\n                numInt += *c - '0';\n            }\n        } else if (*c == '.') {\n            nDots++;\n        } else if (c == value && *c == '-') {\n            nSign++;\n        } else {\n            nNonDigits++;\n        }\n    }\n\n    if (nSign == 1) {\n        numInt = -numInt;\n    }\n\n    int INT_MAXDIGITS; //plausibility check: this allows a numerical range of (-999,999,999 to 999,999,999), or (-9,999 to 9,999) respectively\n    if (sizeof(int) >= 4UL)\n        INT_MAXDIGITS = 9;\n    else\n        INT_MAXDIGITS = 4;\n\n    if (nNonDigits > 0 || nDigits == 0 || nSign > 1 || nDots > 1) {\n        convertibleInt = false;\n    }\n\n    if (nDigits > INT_MAXDIGITS) {\n        MO_DBG_DEBUG(\"Possible integer overflow: key = %s, value = %s\", key, value);\n        convertibleInt = false;\n    }\n\n    if (tolower(value[0]) == 't' && tolower(value[1]) == 'r' && tolower(value[2]) == 'u' && tolower(value[3]) == 'e' && !value[4]) {\n        numBool = true;\n    } else if (tolower(value[0]) == 'f' && tolower(value[1]) == 'a' && tolower(value[2]) == 'l' && tolower(value[3]) == 's' && tolower(value[4]) == 'e' && !value[5]) {\n        numBool = false;\n    } else {\n        convertibleBool = false;\n    }\n\n    //check against optional validator\n\n    auto validator = getConfigurationValidator(key);\n    if (validator && !(*validator)(value)) {\n        //validator exists and validation fails\n        reject = true;\n        MO_DBG_WARN(\"validation failed for key=%s value=%s\", key, value);\n        return;\n    }\n\n    //Store (parsed) value to Config\n\n    if (configuration->getType() == TConfig::Int && convertibleInt) {\n        configuration->setInt(numInt);\n    } else if (configuration->getType() == TConfig::Bool && convertibleBool) {\n        configuration->setBool(numBool);\n    } else if (configuration->getType() == TConfig::String) {\n        configuration->setString(value);\n    } else {\n        reject = true;\n        MO_DBG_WARN(\"Value has incompatible type\");\n        return;\n    }\n\n    if (!configuration_save()) {\n        MO_DBG_ERR(\"could not write changes to flash\");\n        errorCode = \"InternalError\";\n        return;\n    }\n\n    if (configuration->isRebootRequired()) {\n        rebootRequired = true;\n    }\n}\n\nstd::unique_ptr<JsonDoc> ChangeConfiguration::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    if (notSupported) {\n        payload[\"status\"] = \"NotSupported\";\n    } else if (reject || readOnly) {\n        payload[\"status\"] = \"Rejected\";\n    } else if (rebootRequired) {\n        payload[\"status\"] = \"RebootRequired\";\n    } else {\n        payload[\"status\"] = \"Accepted\";\n    }\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ChangeConfiguration.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CHANGECONFIGURATION_H\n#define MO_CHANGECONFIGURATION_H\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nclass ChangeConfiguration : public Operation, public MemoryManaged {\nprivate:\n    bool reject = false;\n    bool rebootRequired = false;\n    bool readOnly = false;\n    bool notSupported = false;\n\n    const char *errorCode = nullptr;\npublic:\n    ChangeConfiguration();\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/CiStrings.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n/*\n * A collection of the fixed-length string types in the OCPP specification\n */\n\n#ifndef MO_CI_STRINGS_H\n#define MO_CI_STRINGS_H\n\n#define CiString20TypeLen 20\n#define CiString25TypeLen 25\n#define CiString50TypeLen 50\n#define CiString255TypeLen 255\n#define CiString500TypeLen 500\n\n//specified by OCPP\n#define IDTAG_LEN_MAX CiString20TypeLen\n#define CONF_KEYLEN_MAX CiString50TypeLen\n\n//not specified by OCPP\n#define REASON_LEN_MAX 15\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ClearCache.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/ClearCache.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::ClearCache;\nusing MicroOcpp::JsonDoc;\n\nClearCache::ClearCache(std::shared_ptr<FilesystemAdapter> filesystem) : MemoryManaged(\"v16.Operation.\", \"ClearCache\"), filesystem(filesystem) {\n  \n}\n\nconst char* ClearCache::getOperationType(){\n    return \"ClearCache\";\n}\n\nvoid ClearCache::processReq(JsonObject payload) {\n    MO_DBG_WARN(\"Clear transaction log (Authorization Cache not supported)\");\n\n    if (!filesystem) {\n        //no persistency - nothing to do\n        return;\n    }\n\n    success = FilesystemUtils::remove_if(filesystem, [] (const char *fname) -> bool {\n        return !strncmp(fname, \"sd\", strlen(\"sd\")) ||\n               !strncmp(fname, \"tx\", strlen(\"tx\")) ||\n               !strncmp(fname, \"op\", strlen(\"op\"));\n    });\n}\n\nstd::unique_ptr<JsonDoc> ClearCache::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    if (success) {\n        payload[\"status\"] = \"Accepted\"; //\"Accepted\", because the intended postcondition is true\n    } else {\n        payload[\"status\"] = \"Rejected\";\n    }\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ClearCache.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CLEARCACHE_H\n#define MO_CLEARCACHE_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nclass ClearCache : public Operation, public MemoryManaged {\nprivate:\n    std::shared_ptr<FilesystemAdapter> filesystem;\n    bool success = true;\npublic:\n    ClearCache(std::shared_ptr<FilesystemAdapter> filesystem);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ClearChargingProfile.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/ClearChargingProfile.h>\n#include <MicroOcpp/Model/SmartCharging/SmartChargingService.h>\n#include <MicroOcpp/Debug.h>\n\n#include <functional>\n\nusing MicroOcpp::Ocpp16::ClearChargingProfile;\nusing MicroOcpp::JsonDoc;\n\nClearChargingProfile::ClearChargingProfile(SmartChargingService& scService) : MemoryManaged(\"v16.Operation.\", \"ClearChargingProfile\"), scService(scService) {\n\n}\n\nconst char* ClearChargingProfile::getOperationType(){\n    return \"ClearChargingProfile\";\n}\n\nvoid ClearChargingProfile::processReq(JsonObject payload) {\n\n    std::function<bool(int, int, ChargingProfilePurposeType, int)> filter = [payload]\n            (int chargingProfileId, int connectorId, ChargingProfilePurposeType chargingProfilePurpose, int stackLevel) {\n        \n        if (payload.containsKey(\"id\")) {\n            if (chargingProfileId == (payload[\"id\"] | -1)) {\n                return true;\n            } else {\n                return false;\n            }\n        }\n\n        if (payload.containsKey(\"connectorId\")) {\n            if (connectorId != (payload[\"connectorId\"] | -1)) {\n                return false;\n            }\n        }\n\n        if (payload.containsKey(\"chargingProfilePurpose\")) {\n            switch (chargingProfilePurpose) {\n                case ChargingProfilePurposeType::ChargePointMaxProfile:\n                    if (strcmp(payload[\"chargingProfilePurpose\"] | \"INVALID\", \"ChargePointMaxProfile\")) {\n                        return false;\n                    }\n                    break;\n                case ChargingProfilePurposeType::TxDefaultProfile:\n                    if (strcmp(payload[\"chargingProfilePurpose\"] | \"INVALID\", \"TxDefaultProfile\")) {\n                        return false;\n                    }\n                    break;\n                case ChargingProfilePurposeType::TxProfile:\n                    if (strcmp(payload[\"chargingProfilePurpose\"] | \"INVALID\", \"TxProfile\")) {\n                        return false;\n                    }\n                    break;\n            }\n        }\n\n        if (payload.containsKey(\"stackLevel\")) {\n            if (stackLevel != (payload[\"stackLevel\"] | -1)) {\n                return false;\n            }\n        }\n\n        return true;\n    };\n\n    matchingProfilesFound = scService.clearChargingProfile(filter);\n}\n\nstd::unique_ptr<JsonDoc> ClearChargingProfile::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    if (matchingProfilesFound)\n        payload[\"status\"] = \"Accepted\";\n    else\n        payload[\"status\"] = \"Unknown\";\n        \n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ClearChargingProfile.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CLEARCHARGINGPROFILE_H\n#define MO_CLEARCHARGINGPROFILE_H\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass SmartChargingService;\n\nnamespace Ocpp16 {\n\nclass ClearChargingProfile : public Operation, public MemoryManaged {\nprivate:\n    SmartChargingService& scService;\n    bool matchingProfilesFound = false;\npublic:\n    ClearChargingProfile(SmartChargingService& scService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n    \n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/CustomOperation.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/CustomOperation.h>\n\nusing MicroOcpp::Ocpp16::CustomOperation;\nusing MicroOcpp::JsonDoc;\n\nCustomOperation::CustomOperation(const char *operationType,\n            std::function<std::unique_ptr<JsonDoc> ()> fn_createReq,\n            std::function<void (JsonObject)> fn_processConf,\n            std::function<bool (const char*, const char*, JsonObject)> fn_processErr) :\n        MemoryManaged(\"Operation.Custom.\", operationType),\n        operationType{makeString(getMemoryTag(), operationType)},\n        fn_createReq{fn_createReq},\n        fn_processConf{fn_processConf},\n        fn_processErr{fn_processErr} {\n    \n}\n\nCustomOperation::CustomOperation(const char *operationType,\n            std::function<void (JsonObject)> fn_processReq,\n            std::function<std::unique_ptr<JsonDoc> ()> fn_createConf,\n            std::function<const char* ()> fn_getErrorCode,\n            std::function<const char* ()> fn_getErrorDescription,\n            std::function<std::unique_ptr<JsonDoc> ()> fn_getErrorDetails) :\n        MemoryManaged(\"Operation.Custom.\", operationType),\n        operationType{makeString(getMemoryTag(), operationType)},\n        fn_processReq{fn_processReq},\n        fn_createConf{fn_createConf},\n        fn_getErrorCode{fn_getErrorCode},\n        fn_getErrorDescription{fn_getErrorDescription},\n        fn_getErrorDetails{fn_getErrorDetails} {\n    \n}\n\nCustomOperation::~CustomOperation() {\n\n}\n\nconst char* CustomOperation::getOperationType() {\n    return operationType.c_str();\n}\n\nstd::unique_ptr<JsonDoc> CustomOperation::createReq() {\n    return fn_createReq();\n}\n\nvoid CustomOperation::processConf(JsonObject payload) {\n    return fn_processConf(payload);\n}\n\nbool CustomOperation::processErr(const char *code, const char *description, JsonObject details) {\n    if (fn_processErr) {\n        return fn_processErr(code, description, details);\n    }\n    return true;\n}\n\nvoid CustomOperation::processReq(JsonObject payload) {\n    return fn_processReq(payload);\n}\n\nstd::unique_ptr<JsonDoc> CustomOperation::createConf() {\n    return fn_createConf();\n}\n\nconst char *CustomOperation::getErrorCode() {\n    if (fn_getErrorCode) {\n        return fn_getErrorCode();\n    } else {\n        return nullptr;\n    }\n}\n\nconst char *CustomOperation::getErrorDescription() {\n    if (fn_getErrorDescription) {\n        return fn_getErrorDescription();\n    } else {\n        return \"\";\n    }\n}\n\nstd::unique_ptr<JsonDoc> CustomOperation::getErrorDetails() {\n    if (fn_getErrorDetails) {\n        return fn_getErrorDetails();\n    } else {\n        return createEmptyDocument();\n    }\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/CustomOperation.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_CUSTOMOPERATION_H\n#define MO_CUSTOMOPERATION_H\n\n#include <MicroOcpp/Core/Operation.h>\n\n#include <functional>\n\nnamespace MicroOcpp {\n\nnamespace Ocpp16 {\n\nclass CustomOperation : public Operation, public MemoryManaged {\nprivate:\n    String operationType;\n    std::function<std::unique_ptr<JsonDoc> ()> fn_createReq;\n    std::function<void (JsonObject)> fn_processConf;\n    std::function<bool (const char*, const char*, JsonObject)> fn_processErr;  //optional\n    std::function<void (JsonObject)> fn_processReq;\n    std::function<std::unique_ptr<JsonDoc> ()> fn_createConf;\n    std::function<const char* ()> fn_getErrorCode;            //optional\n    std::function<const char* ()> fn_getErrorDescription;     //optional\n    std::function<std::unique_ptr<JsonDoc> ()> fn_getErrorDetails; //optional\npublic:\n\n    //for operations initiated at this device\n    CustomOperation(const char *operationType,\n            std::function<std::unique_ptr<JsonDoc> ()> fn_createReq,\n            std::function<void (JsonObject)> fn_processConf,\n            std::function<bool (const char*, const char*, JsonObject)> fn_processErr = nullptr);\n    \n    //for operations receied from remote\n    CustomOperation(const char *operationType,\n            std::function<void (JsonObject)> fn_processReq,\n            std::function<std::unique_ptr<JsonDoc> ()> fn_createConf,\n            std::function<const char* ()> fn_getErrorCode = nullptr,\n            std::function<const char* ()> fn_getErrorDescription = nullptr,\n            std::function<std::unique_ptr<JsonDoc> ()> fn_getErrorDetails = nullptr);\n    \n    ~CustomOperation();\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    bool processErr(const char *code, const char *description, JsonObject details) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n    const char *getErrorCode() override;\n    const char *getErrorDescription() override;\n    std::unique_ptr<JsonDoc> getErrorDetails() override;\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/DataTransfer.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/DataTransfer.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::DataTransfer;\nusing MicroOcpp::JsonDoc;\n\nDataTransfer::DataTransfer() : MemoryManaged(\"v16.Operation.\", \"DataTransfer\") {\n\n}\n\nDataTransfer::DataTransfer(const String &msg) : MemoryManaged(\"v16.Operation.\", \"DataTransfer\"), msg{makeString(getMemoryTag(), msg.c_str())} {\n\n}\n\nconst char* DataTransfer::getOperationType(){\n    return \"DataTransfer\";\n}\n\nstd::unique_ptr<JsonDoc> DataTransfer::createReq() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2) + (msg.length() + 1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"vendorId\"] = \"CustomVendor\";\n    payload[\"data\"] = msg;\n    return doc;\n}\n\nvoid DataTransfer::processConf(JsonObject payload){\n    const char *status = payload[\"status\"] | \"Invalid\";\n\n    if (!strcmp(status, \"Accepted\")) {\n        MO_DBG_DEBUG(\"Request has been accepted\");\n    } else {\n        MO_DBG_INFO(\"Request has been denied\");\n    }\n}\n\nvoid DataTransfer::processReq(JsonObject payload) {\n    // Do nothing - we're just required to reject these DataTransfer requests\n}\n\nstd::unique_ptr<JsonDoc> DataTransfer::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = \"Rejected\";\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/DataTransfer.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_DATATRANSFER_H\n#define MO_DATATRANSFER_H\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nclass DataTransfer : public Operation, public MemoryManaged {\nprivate:\n    String msg;\npublic:\n    DataTransfer();\n    DataTransfer(const String &msg);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n    \n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/DeleteCertificate.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/DeleteCertificate.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <MicroOcpp/Model/Certificates/Certificate.h>\n#include <MicroOcpp/Model/Certificates/CertificateService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp201::DeleteCertificate;\nusing MicroOcpp::JsonDoc;\n\nDeleteCertificate::DeleteCertificate(CertificateService& certService) : MemoryManaged(\"v201.Operation.\", \"DeleteCertificate\"), certService(certService) {\n\n}\n\nvoid DeleteCertificate::processReq(JsonObject payload) {\n\n    JsonObject certIdJson = payload[\"certificateHashData\"];\n\n    if (!certIdJson.containsKey(\"hashAlgorithm\") ||\n            !certIdJson.containsKey(\"issuerNameHash\") ||\n            !certIdJson.containsKey(\"issuerKeyHash\") ||\n            !certIdJson.containsKey(\"serialNumber\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    const char *hashAlgorithm = certIdJson[\"hashAlgorithm\"] | \"_Invalid\";\n\n    if (!certIdJson[\"issuerNameHash\"].is<const char*>() ||\n            !certIdJson[\"issuerKeyHash\"].is<const char*>() ||\n            !certIdJson[\"serialNumber\"].is<const char*>()) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    CertificateHash cert;\n\n    if (!strcmp(hashAlgorithm, \"SHA256\")) {\n        cert.hashAlgorithm = HashAlgorithmType_SHA256;\n    } else if (!strcmp(hashAlgorithm, \"SHA384\")) {\n        cert.hashAlgorithm = HashAlgorithmType_SHA384;\n    } else if (!strcmp(hashAlgorithm, \"SHA512\")) {\n        cert.hashAlgorithm = HashAlgorithmType_SHA512;\n    } else {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    auto retIN = ocpp_cert_set_issuerNameHash(&cert, certIdJson[\"issuerNameHash\"] | \"_Invalid\", cert.hashAlgorithm);\n    auto retIK = ocpp_cert_set_issuerKeyHash(&cert, certIdJson[\"issuerKeyHash\"] | \"_Invalid\", cert.hashAlgorithm);\n    auto retSN = ocpp_cert_set_serialNumber(&cert, certIdJson[\"serialNumber\"] | \"_Invalid\");\n    if (retIN < 0 || retIK < 0 || retSN < 0) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    auto certStore = certService.getCertificateStore();\n    if (!certStore) {\n        errorCode = \"NotSupported\";\n        return;\n    }\n\n    auto status = certStore->deleteCertificate(cert);\n\n    switch (status) {\n        case DeleteCertificateStatus_Accepted:\n            this->status = \"Accepted\";\n            break;\n        case DeleteCertificateStatus_Failed:\n            this->status = \"Failed\";\n            break;\n        case DeleteCertificateStatus_NotFound:\n            this->status = \"NotFound\";\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            errorCode = \"InternalError\";\n            return;\n    }\n\n    //operation executed successfully\n}\n\nstd::unique_ptr<JsonDoc> DeleteCertificate::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = status;\n    return doc;\n}\n\n#endif //MO_ENABLE_CERT_MGMT\n"
  },
  {
    "path": "src/MicroOcpp/Operations/DeleteCertificate.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_DELETECERTIFICATE_H\n#define MO_DELETECERTIFICATE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass CertificateService;\n\nnamespace Ocpp201 {\n\nclass DeleteCertificate : public Operation, public MemoryManaged {\nprivate:\n    CertificateService& certService;\n    const char *status = nullptr;\n    const char *errorCode = nullptr;\npublic:\n    DeleteCertificate(CertificateService& certService);\n\n    const char* getOperationType() override {return \"DeleteCertificate\";}\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_CERT_MGMT\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/DiagnosticsStatusNotification.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Diagnostics/DiagnosticsService.h>\n\nusing MicroOcpp::Ocpp16::DiagnosticsStatusNotification;\nusing MicroOcpp::JsonDoc;\n\nDiagnosticsStatusNotification::DiagnosticsStatusNotification(DiagnosticsStatus status) : MemoryManaged(\"v16.Operation.\", \"DiagnosticsStatusNotification\"), status(status) {\n    \n}\n\nconst char *DiagnosticsStatusNotification::cstrFromStatus(DiagnosticsStatus status) {\n    switch (status) {\n        case (DiagnosticsStatus::Idle):\n            return \"Idle\";\n            break;\n        case (DiagnosticsStatus::Uploaded):\n            return \"Uploaded\";\n            break;\n        case (DiagnosticsStatus::UploadFailed):\n            return \"UploadFailed\";\n            break;\n        case (DiagnosticsStatus::Uploading):\n            return \"Uploading\";\n            break;\n    }\n    return nullptr; //cannot be reached\n}\n\nstd::unique_ptr<JsonDoc> DiagnosticsStatusNotification::createReq() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = cstrFromStatus(status);\n    return doc;\n}\n\nvoid DiagnosticsStatusNotification::processConf(JsonObject payload){\n    // no payload, nothing to do\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/DiagnosticsStatusNotification.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Model/Diagnostics/DiagnosticsStatus.h>\n\n#ifndef MO_DIAGNOSTICSSTATUSNOTIFICATION_H\n#define MO_DIAGNOSTICSSTATUSNOTIFICATION_H\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nclass DiagnosticsStatusNotification : public Operation, public MemoryManaged {\nprivate:\n    DiagnosticsStatus status = DiagnosticsStatus::Idle;\n    static const char *cstrFromStatus(DiagnosticsStatus status);\npublic:\n    DiagnosticsStatusNotification(DiagnosticsStatus status);\n\n    const char* getOperationType() override {return \"DiagnosticsStatusNotification\"; }\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/FirmwareStatusNotification.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/FirmwareStatusNotification.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>\n\nusing MicroOcpp::Ocpp16::FirmwareStatusNotification;\nusing MicroOcpp::JsonDoc;\n\nFirmwareStatusNotification::FirmwareStatusNotification(FirmwareStatus status) : MemoryManaged(\"v16.Operation.\", \"FirmwareStatusNotification\"), status{status} {\n\n}\n\nconst char *FirmwareStatusNotification::cstrFromFwStatus(FirmwareStatus status) {\n    switch (status) {\n        case (FirmwareStatus::Downloaded):\n            return \"Downloaded\";\n            break;\n        case (FirmwareStatus::DownloadFailed):\n            return \"DownloadFailed\";\n            break;\n        case (FirmwareStatus::Downloading):\n            return \"Downloading\";\n            break;\n        case (FirmwareStatus::Idle):\n            return \"Idle\";\n            break;\n        case (FirmwareStatus::InstallationFailed):\n            return \"InstallationFailed\";\n            break;\n        case (FirmwareStatus::Installing):\n            return \"Installing\";\n            break;\n        case (FirmwareStatus::Installed):\n            return \"Installed\";\n            break;\n    }\n    return NULL; //cannot be reached\n}\n\nstd::unique_ptr<JsonDoc> FirmwareStatusNotification::createReq() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = cstrFromFwStatus(status);\n    return doc;\n}\n\nvoid FirmwareStatusNotification::processConf(JsonObject payload){\n    // no payload, nothing to do\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/FirmwareStatusNotification.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Core/Operation.h>\n\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareStatus.h>\n\n#ifndef MO_FIRMWARESTATUSNOTIFICATION_H\n#define MO_FIRMWARESTATUSNOTIFICATION_H\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nclass FirmwareStatusNotification : public Operation, public MemoryManaged {\nprivate:\n    FirmwareStatus status = FirmwareStatus::Idle;\n    static const char *cstrFromFwStatus(FirmwareStatus status);\npublic:\n    FirmwareStatusNotification(FirmwareStatus status);\n\n    const char* getOperationType() override {return \"FirmwareStatusNotification\"; }\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetBaseReport.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Operations/GetBaseReport.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp201::GetBaseReport;\nusing MicroOcpp::JsonDoc;\n\nGetBaseReport::GetBaseReport(VariableService& variableService) : MemoryManaged(\"v201.Operation.\", \"GetBaseReport\"), variableService(variableService) {\n  \n}\n\nconst char* GetBaseReport::getOperationType(){\n    return \"GetBaseReport\";\n}\n\nvoid GetBaseReport::processReq(JsonObject payload) {\n\n    int requestId = payload[\"requestId\"] | -1;\n    if (requestId < 0) {\n        errorCode = \"FormationViolation\";\n        MO_DBG_ERR(\"invalid requestId\");\n        return;\n    }\n\n    ReportBase reportBase;\n\n    const char *reportBaseCstr = payload[\"reportBase\"] | \"\";\n    if (!strcmp(reportBaseCstr, \"ConfigurationInventory\")) {\n        reportBase = ReportBase_ConfigurationInventory;\n    } else if (!strcmp(reportBaseCstr, \"FullInventory\")) {\n        reportBase = ReportBase_FullInventory;\n    } else if (!strcmp(reportBaseCstr, \"SummaryInventory\")) {\n        reportBase = ReportBase_SummaryInventory;\n    } else {\n        errorCode = \"FormationViolation\";\n        MO_DBG_ERR(\"invalid reportBase\");\n        return;\n    }\n\n    status = variableService.getBaseReport(requestId, reportBase);\n}\n\nstd::unique_ptr<JsonDoc> GetBaseReport::createConf(){\n\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    const char *statusCstr = \"\";\n\n    switch (status) {\n        case GenericDeviceModelStatus_Accepted:\n            statusCstr = \"Accepted\";\n            break;\n        case GenericDeviceModelStatus_Rejected:\n            statusCstr = \"Rejected\";\n            break;\n        case GenericDeviceModelStatus_NotSupported:\n            statusCstr = \"NotSupported\";\n            break;\n        case GenericDeviceModelStatus_EmptyResultSet:\n            statusCstr = \"EmptyResultSet\";\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            break;\n    }\n\n    payload[\"status\"] = statusCstr;\n\n    return doc;\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetBaseReport.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_GETBASEREPORT_H\n#define MO_GETBASEREPORT_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Model/Variables/Variable.h>\n\nnamespace MicroOcpp {\n\nclass VariableService;\n\nnamespace Ocpp201 {\n\nclass GetBaseReport : public Operation, public MemoryManaged {\nprivate:\n    VariableService& variableService;\n\n    GenericDeviceModelStatus status;\n\n    const char *errorCode = nullptr;\npublic:\n    GetBaseReport(VariableService& variableService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n\n};\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetCompositeSchedule.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/GetCompositeSchedule.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/SmartCharging/SmartChargingService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::GetCompositeSchedule;\nusing MicroOcpp::JsonDoc;\n\nGetCompositeSchedule::GetCompositeSchedule(Model& model, SmartChargingService& scService) : MemoryManaged(\"v16.Operation.\", \"GetCompositeSchedule\"), model(model), scService(scService) {\n\n}\n\nconst char* GetCompositeSchedule::getOperationType() {\n    return \"GetCompositeSchedule\";\n}\n\nvoid GetCompositeSchedule::processReq(JsonObject payload) {\n\n    connectorId = payload[\"connectorId\"] | -1;\n    duration = payload[\"duration\"] | 0;\n\n    if (connectorId < 0 || !payload.containsKey(\"duration\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    if ((unsigned int) connectorId >= model.getNumConnectors()) {\n        errorCode = \"PropertyConstraintViolation\";\n    }\n\n    const char *unitStr =  payload[\"chargingRateUnit\"] | \"_Undefined\";\n\n    if (unitStr[0] == 'A' || unitStr[0] == 'a') {\n        chargingRateUnit = ChargingRateUnitType_Optional::Amp;\n    } else if (unitStr[0] == 'W' || unitStr[0] == 'w') {\n        chargingRateUnit = ChargingRateUnitType_Optional::Watt;\n    }\n}\n\nstd::unique_ptr<JsonDoc> GetCompositeSchedule::createConf(){\n\n    bool success = false;\n\n    auto chargingSchedule = scService.getCompositeSchedule((unsigned int) connectorId, duration, chargingRateUnit);\n    JsonDoc chargingScheduleDoc {0};\n\n    if (chargingSchedule) {\n        success = chargingSchedule->toJson(chargingScheduleDoc);\n    }\n\n    char scheduleStart_str [JSONDATE_LENGTH + 1] = {'\\0'};\n\n    if (success && chargingSchedule) {\n        success = chargingSchedule->startSchedule.toJsonString(scheduleStart_str, JSONDATE_LENGTH + 1);\n    }\n\n    if (success && chargingSchedule) {\n        auto doc = makeJsonDoc(getMemoryTag(),\n                        JSON_OBJECT_SIZE(4) +\n                        chargingScheduleDoc.memoryUsage());\n        JsonObject payload = doc->to<JsonObject>();\n        payload[\"status\"] = \"Accepted\";\n        payload[\"connectorId\"] = connectorId;\n        payload[\"scheduleStart\"] = scheduleStart_str;\n        payload[\"chargingSchedule\"] = chargingScheduleDoc;\n        return doc;\n    } else {\n        auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n        JsonObject payload = doc->to<JsonObject>();\n        payload[\"status\"] = \"Rejected\";\n        return doc;\n    }\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetCompositeSchedule.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_GETCOMPOSITESCHEDULE_H\n#define MO_GETCOMPOSITESCHEDULE_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Model/SmartCharging/SmartChargingService.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass GetCompositeSchedule : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    SmartChargingService& scService;\n    int connectorId = -1;\n    int duration = -1;\n    ChargingRateUnitType_Optional chargingRateUnit = ChargingRateUnitType_Optional::None;\n\n    const char *errorCode {nullptr};\npublic:\n    GetCompositeSchedule(Model& model, SmartChargingService& scService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetConfiguration.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/GetConfiguration.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::GetConfiguration;\nusing MicroOcpp::JsonDoc;\n\nGetConfiguration::GetConfiguration() : MemoryManaged(\"v16.Operation.\", \"GetConfiguration\"), keys{makeVector<String>(getMemoryTag())} {\n\n}\n\nconst char* GetConfiguration::getOperationType(){\n    return \"GetConfiguration\";\n}\n\nvoid GetConfiguration::processReq(JsonObject payload) {\n\n    JsonArray requestedKeys = payload[\"key\"];\n    for (size_t i = 0; i < requestedKeys.size(); i++) {\n        keys.push_back(makeString(getMemoryTag(), requestedKeys[i].as<const char*>()));\n    }\n}\n\nstd::unique_ptr<JsonDoc> GetConfiguration::createConf(){\n\n    Vector<Configuration*> configurations = makeVector<Configuration*>(getMemoryTag());\n    Vector<const char*> unknownKeys = makeVector<const char*>(getMemoryTag());\n\n    auto containers = getConfigurationContainersPublic();\n\n    if (keys.empty()) {\n        //return all existing keys\n        for (auto container : containers) {\n            for (size_t i = 0; i < container->size(); i++) {\n                if (!container->getConfiguration(i)->getKey()) {\n                    MO_DBG_ERR(\"invalid config\");\n                    continue;\n                }\n                if (!container->getConfiguration(i)->isReadable()) {\n                    continue;\n                }\n                configurations.push_back(container->getConfiguration(i));\n            }\n        }\n    } else {\n        //only return keys that were searched using the \"key\" parameter\n        for (auto& key : keys) {\n            Configuration *res = nullptr;\n            for (auto container : containers) {\n                if ((res = container->getConfiguration(key.c_str()).get())) {\n                    break;\n                }\n            }\n\n            if (res && res->isReadable()) {\n                configurations.push_back(res);\n            } else {\n                unknownKeys.push_back(key.c_str());\n            }\n        }\n    }\n\n    #define VALUE_BUFSIZE 30\n\n    //capacity of the resulting document\n    size_t jcapacity = JSON_OBJECT_SIZE(2); //document root: configurationKey, unknownKey\n\n    jcapacity += JSON_ARRAY_SIZE(configurations.size()) + configurations.size() * JSON_OBJECT_SIZE(3); //configurationKey: [{\"key\":...},{\"key\":...}]\n    for (auto config : configurations) {\n        //need to store ints by copied string: measure necessary capacity\n        if (config->getType() == TConfig::Int) {\n            char vbuf [VALUE_BUFSIZE];\n            auto ret = snprintf(vbuf, VALUE_BUFSIZE, \"%i\", config->getInt());\n            if (ret < 0 || ret >= VALUE_BUFSIZE) {\n                continue;\n            }\n            jcapacity += ret + 1;\n        }\n    }\n\n    jcapacity += JSON_ARRAY_SIZE(unknownKeys.size());\n    \n    MO_DBG_DEBUG(\"GetConfiguration capacity: %zu\", jcapacity);\n\n    std::unique_ptr<JsonDoc> doc;\n\n    if (jcapacity <= MO_MAX_JSON_CAPACITY) {\n        doc = makeJsonDoc(getMemoryTag(), jcapacity);\n    }\n\n    if (!doc || doc->capacity() < jcapacity) {\n        if (doc) {\n            MO_DBG_ERR(\"OOM\");\n        }\n\n        errorCode = \"InternalError\";\n        errorDescription = \"Query too big. Try fewer keys\";\n        return nullptr;\n    }\n\n    JsonObject payload = doc->to<JsonObject>();\n    \n    JsonArray jsonConfigurationKey = payload.createNestedArray(\"configurationKey\");\n    for (auto config : configurations) {\n        char vbuf [VALUE_BUFSIZE];\n        const char *v = \"\";\n        switch (config->getType()) {\n            case TConfig::Int: {\n                auto ret = snprintf(vbuf, VALUE_BUFSIZE, \"%i\", config->getInt());\n                if (ret < 0 || ret >= VALUE_BUFSIZE) {\n                    MO_DBG_ERR(\"value error\");\n                    continue;\n                }\n                v = vbuf;\n                break;\n            }\n            case TConfig::Bool:\n                v = config->getBool() ? \"true\" : \"false\";\n                break;\n            case TConfig::String:\n                v = config->getString();\n                break;\n        }\n\n        JsonObject jconfig = jsonConfigurationKey.createNestedObject();\n        jconfig[\"key\"] = config->getKey();\n        jconfig[\"readonly\"] = config->isReadOnly();\n        if (v == vbuf) {\n            //value points to buffer on stack, needs to be copied into JSON memory pool\n            jconfig[\"value\"] = (char*) v;\n        } else {\n            //value is static, no-copy mode\n            jconfig[\"value\"] = v;\n        }\n    }\n\n    if (!unknownKeys.empty()) {\n        JsonArray jsonUnknownKey = payload.createNestedArray(\"unknownKey\");\n        for (auto key : unknownKeys) {\n            MO_DBG_DEBUG(\"Unknown key: %s\", key);\n            jsonUnknownKey.add(key);\n        }\n    }\n\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetConfiguration.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_GETCONFIGURATION_H\n#define MO_GETCONFIGURATION_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\nnamespace Ocpp16 {\n\nclass GetConfiguration : public Operation, public MemoryManaged {\nprivate:\n    Vector<String> keys;\n\n    const char *errorCode {nullptr};\n    const char *errorDescription = \"\";\npublic:\n    GetConfiguration();\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n    const char *getErrorDescription() override {return errorDescription;}\n\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetDiagnostics.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/GetDiagnostics.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Diagnostics/DiagnosticsService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::GetDiagnostics;\nusing MicroOcpp::JsonDoc;\n\nGetDiagnostics::GetDiagnostics(DiagnosticsService& diagService) : MemoryManaged(\"v16.Operation.\", \"GetDiagnostics\"), diagService(diagService), fileName(makeString(getMemoryTag())) {\n\n}\n\nvoid GetDiagnostics::processReq(JsonObject payload) {\n\n    const char *location = payload[\"location\"] | \"\";\n    //check location URL. Maybe introduce Same-Origin-Policy?\n    if (!*location) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n    \n    int retries = payload[\"retries\"] | 1;\n    int retryInterval = payload[\"retryInterval\"] | 180;\n    if (retries < 0 || retryInterval < 0) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    Timestamp startTime;\n    if (payload.containsKey(\"startTime\")) {\n        if (!startTime.setTime(payload[\"startTime\"] | \"Invalid\")) {\n            errorCode = \"PropertyConstraintViolation\";\n            MO_DBG_WARN(\"bad time format\");\n            return;\n        }\n    }\n\n    Timestamp stopTime;\n    if (payload.containsKey(\"stopTime\")) {\n        if (!stopTime.setTime(payload[\"stopTime\"] | \"Invalid\")) {\n            errorCode = \"PropertyConstraintViolation\";\n            MO_DBG_WARN(\"bad time format\");\n            return;\n        }\n    }\n\n    fileName = diagService.requestDiagnosticsUpload(location, (unsigned int) retries, (unsigned int) retryInterval, startTime, stopTime);\n}\n\nstd::unique_ptr<JsonDoc> GetDiagnostics::createConf(){\n    if (fileName.empty()) {\n        return createEmptyDocument();\n    } else {\n        auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n        JsonObject payload = doc->to<JsonObject>();\n        payload[\"fileName\"] = fileName.c_str();\n        return doc;\n    }\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetDiagnostics.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_GETDIAGNOSTICS_H\n#define MO_GETDIAGNOSTICS_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Time.h>\n\nnamespace MicroOcpp {\n\nclass DiagnosticsService;\n\nnamespace Ocpp16 {\n\nclass GetDiagnostics : public Operation, public MemoryManaged {\nprivate:\n    DiagnosticsService& diagService;\n    String fileName;\n\n    const char *errorCode = nullptr;\npublic:\n    GetDiagnostics(DiagnosticsService& diagService);\n\n    const char* getOperationType() override {return \"GetDiagnostics\";}\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/GetInstalledCertificateIds.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <MicroOcpp/Model/Certificates/Certificate.h>\n#include <MicroOcpp/Model/Certificates/CertificateService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp201::GetInstalledCertificateIds;\nusing MicroOcpp::JsonDoc;\n\nGetInstalledCertificateIds::GetInstalledCertificateIds(CertificateService& certService) : MemoryManaged(\"v201.Operation.\", \"GetInstalledCertificateIds\"), certService(certService), certificateHashDataChain(makeVector<CertificateChainHash>(getMemoryTag())) {\n\n}\n\nvoid GetInstalledCertificateIds::processReq(JsonObject payload) {\n\n    if (!payload.containsKey(\"certificateType\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    auto certificateType = makeVector<GetCertificateIdType>(getMemoryTag());\n    for (const char *certificateTypeCstr : payload[\"certificateType\"].as<JsonArray>()) {\n        if (!strcmp(certificateTypeCstr, \"V2GRootCertificate\")) {\n            certificateType.push_back(GetCertificateIdType_V2GRootCertificate);\n        } else if (!strcmp(certificateTypeCstr, \"MORootCertificate\")) {\n            certificateType.push_back(GetCertificateIdType_MORootCertificate);\n        } else if (!strcmp(certificateTypeCstr, \"CSMSRootCertificate\")) {\n            certificateType.push_back(GetCertificateIdType_CSMSRootCertificate);\n        } else if (!strcmp(certificateTypeCstr, \"V2GCertificateChain\")) {\n            certificateType.push_back(GetCertificateIdType_V2GCertificateChain);\n        } else if (!strcmp(certificateTypeCstr, \"ManufacturerRootCertificate\")) {\n            certificateType.push_back(GetCertificateIdType_ManufacturerRootCertificate);\n        } else {\n            errorCode = \"FormationViolation\";\n            return;\n        }\n    }\n\n    auto certStore = certService.getCertificateStore();\n    if (!certStore) {\n        errorCode = \"NotSupported\";\n        return;\n    }\n\n    auto status = certStore->getCertificateIds(certificateType, certificateHashDataChain);\n\n    switch (status) {\n        case GetInstalledCertificateStatus_Accepted:\n            this->status = \"Accepted\";\n            break;\n        case GetInstalledCertificateStatus_NotFound:\n            this->status = \"NotFound\";\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            errorCode = \"InternalError\";\n            return;\n    }\n\n    //operation executed successfully\n}\n\nstd::unique_ptr<JsonDoc> GetInstalledCertificateIds::createConf() {\n\n    size_t capacity =\n            JSON_OBJECT_SIZE(2) + //payload root\n            JSON_ARRAY_SIZE(certificateHashDataChain.size()); //array for field certificateHashDataChain\n    for (auto& cch : certificateHashDataChain) {\n        capacity +=\n                JSON_OBJECT_SIZE(2) + //certificateHashDataChain root\n                JSON_OBJECT_SIZE(4) +  //certificateHashData\n                (2 * HashAlgorithmSize(cch.certificateHashData.hashAlgorithm) + //issuerNameHash and issuerKeyHash\n                    cch.certificateHashData.serialNumberLen)\n                    * 2 + 3; //issuerNameHash, issuerKeyHash and serialNumber as hex-endoded cstring\n    }\n\n    auto doc = makeJsonDoc(getMemoryTag(), capacity);\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = status;\n\n    for (auto& chainElem : certificateHashDataChain) {\n        JsonObject certHashJson = payload[\"certificateHashDataChain\"].createNestedObject();\n\n        const char *certificateTypeCstr = \"\";\n        switch (chainElem.certificateType) {\n            case GetCertificateIdType_V2GRootCertificate:\n                certificateTypeCstr = \"V2GRootCertificate\";\n                break;\n            case GetCertificateIdType_MORootCertificate:\n                certificateTypeCstr = \"MORootCertificate\";\n                break;\n            case GetCertificateIdType_CSMSRootCertificate:\n                certificateTypeCstr = \"CSMSRootCertificate\";\n                break;\n            case GetCertificateIdType_V2GCertificateChain:\n                certificateTypeCstr = \"V2GCertificateChain\";\n                break;\n            case GetCertificateIdType_ManufacturerRootCertificate:\n                certificateTypeCstr = \"ManufacturerRootCertificate\";\n                break;\n        }\n\n        certHashJson[\"certificateType\"] = (const char*) certificateTypeCstr; //use JSON zero-copy mode\n        certHashJson[\"certificateHashData\"][\"hashAlgorithm\"] = HashAlgorithmLabel(chainElem.certificateHashData.hashAlgorithm);\n\n        char buf [MO_CERT_HASH_ISSUER_NAME_KEY_SIZE];\n\n        ocpp_cert_print_issuerNameHash(&chainElem.certificateHashData, buf, sizeof(buf));\n        certHashJson[\"certificateHashData\"][\"issuerNameHash\"] = buf;\n\n        ocpp_cert_print_issuerKeyHash(&chainElem.certificateHashData, buf, sizeof(buf));\n        certHashJson[\"certificateHashData\"][\"issuerKeyHash\"] = buf;\n\n        ocpp_cert_print_serialNumber(&chainElem.certificateHashData, buf, sizeof(buf));\n        certHashJson[\"certificateHashData\"][\"serialNumber\"] = buf;\n        \n        if (!chainElem.childCertificateHashData.empty()) {\n            MO_DBG_ERR(\"only sole root certs supported\");\n        }\n    }\n\n    return doc;\n}\n\n#endif //MO_ENABLE_CERT_MGMT\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetInstalledCertificateIds.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_GETINSTALLEDCERTIFICATEIDS_H\n#define MO_GETINSTALLEDCERTIFICATEIDS_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Model/Certificates/Certificate.h>\n\nnamespace MicroOcpp {\n\nclass CertificateService;\n\nnamespace Ocpp201 {\n\nclass GetInstalledCertificateIds : public Operation, public MemoryManaged {\nprivate:\n    CertificateService& certService;\n    Vector<CertificateChainHash> certificateHashDataChain;\n    const char *status = nullptr;\n    const char *errorCode = nullptr;\npublic:\n    GetInstalledCertificateIds(CertificateService& certService);\n\n    const char* getOperationType() override {return \"GetInstalledCertificateIds\";}\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_CERT_MGMT\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetLocalListVersion.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Operations/GetLocalListVersion.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::GetLocalListVersion;\nusing MicroOcpp::JsonDoc;\n\nGetLocalListVersion::GetLocalListVersion(Model& model) : MemoryManaged(\"v16.Operation.\", \"GetLocalListVersion\"), model(model) {\n  \n}\n\nconst char* GetLocalListVersion::getOperationType(){\n    return \"GetLocalListVersion\";\n}\n\nvoid GetLocalListVersion::processReq(JsonObject payload) {\n    //empty payload\n}\n\nstd::unique_ptr<JsonDoc> GetLocalListVersion::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    auto authService = model.getAuthorizationService();\n    if (authService && authService->localAuthListEnabled()) {\n        payload[\"listVersion\"] = authService->getLocalListVersion();\n    } else {\n        //TC_042_1_CS Get Local List Version (not supported)\n        payload[\"listVersion\"] = -1;\n    }\n    return doc;\n}\n\n#endif //MO_ENABLE_LOCAL_AUTH\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetLocalListVersion.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_GETLOCALLISTVERSION_H\n#define MO_GETLOCALLISTVERSION_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass GetLocalListVersion : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\npublic:\n    GetLocalListVersion(Model& model);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_LOCAL_AUTH\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetVariables.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Operations/GetVariables.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Debug.h>\n\n#include <cctype> //for tolower\n\nusing MicroOcpp::Ocpp201::GetVariableData;\nusing MicroOcpp::Ocpp201::GetVariables;\nusing MicroOcpp::JsonDoc;\n\nGetVariableData::GetVariableData(const char *memory_tag) : componentName{makeString(memory_tag)}, variableName{makeString(memory_tag)} {\n\n}\n\nGetVariables::GetVariables(VariableService& variableService) : MemoryManaged(\"v201.Operation.\", \"GetVariables\"), variableService(variableService), queries(makeVector<GetVariableData>(getMemoryTag())) {\n\n}\n\nconst char* GetVariables::getOperationType(){\n    return \"GetVariables\";\n}\n\nvoid GetVariables::processReq(JsonObject payload) {\n    for (JsonObject getVariable : payload[\"getVariableData\"].as<JsonArray>()) {\n\n        queries.emplace_back(getMemoryTag());\n        auto& data = queries.back();\n\n        if (getVariable.containsKey(\"attributeType\")) {\n            const char *attributeTypeCstr = getVariable[\"attributeType\"] | \"_Undefined\";\n            if (!strcmp(attributeTypeCstr, \"Actual\")) {\n                data.attributeType = Variable::AttributeType::Actual;\n            } else if (!strcmp(attributeTypeCstr, \"Target\")) {\n                data.attributeType = Variable::AttributeType::Target;\n            } else if (!strcmp(attributeTypeCstr, \"MinSet\")) {\n                data.attributeType = Variable::AttributeType::MinSet;\n            } else if (!strcmp(attributeTypeCstr, \"MaxSet\")) {\n                data.attributeType = Variable::AttributeType::MaxSet;\n            } else {\n                errorCode = \"FormationViolation\";\n                MO_DBG_ERR(\"invalid attributeType\");\n                return;\n            }\n        }\n\n        const char *componentNameCstr = getVariable[\"component\"][\"name\"] | (const char*) nullptr;\n        const char *variableNameCstr = getVariable[\"variable\"][\"name\"] | (const char*) nullptr;\n\n        if (!componentNameCstr ||\n                !variableNameCstr) {\n            errorCode = \"FormationViolation\";\n            return;\n        }\n\n        data.componentName = componentNameCstr;\n        data.variableName = variableNameCstr;\n\n        // TODO check against ConfigurationValueSize\n\n        data.componentEvseId = getVariable[\"component\"][\"evse\"][\"id\"] | -1;\n        data.componentEvseConnectorId = getVariable[\"component\"][\"evse\"][\"connectorId\"] | -1;\n\n        if (getVariable[\"component\"].containsKey(\"evse\") && data.componentEvseId < 0) {\n            errorCode = \"FormationViolation\";\n            MO_DBG_ERR(\"malformatted / missing evseId\");\n            return;\n        }\n    }\n\n    if (queries.empty()) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n}\n\nstd::unique_ptr<JsonDoc> GetVariables::createConf(){\n\n    // process GetVariables queries\n    for (auto& query : queries) {\n        query.attributeStatus = variableService.getVariable(\n                query.attributeType,\n                ComponentId(query.componentName.c_str(), \n                    EvseId(query.componentEvseId, query.componentEvseConnectorId)),\n                query.variableName.c_str(),\n                &query.variable);\n    }\n\n    #define VALUE_BUFSIZE 30 // for primitives (int)\n\n    size_t capacity = JSON_ARRAY_SIZE(queries.size());\n    for (const auto& data : queries) {\n        size_t valueCapacity = 0;\n        if (data.variable) {\n            switch (data.variable->getInternalDataType()) {\n                case Variable::InternalDataType::Int: {\n                    // measure int size by printing to a dummy buf\n                    char valbuf [VALUE_BUFSIZE];\n                    auto ret = snprintf(valbuf, VALUE_BUFSIZE, \"%i\", data.variable->getInt());\n                    if (ret < 0 || ret >= VALUE_BUFSIZE) {\n                        continue;\n                    }\n                    valueCapacity = (size_t) ret + 1;\n                    break;\n                }\n                case Variable::InternalDataType::Bool:\n                    // bool will be stored in zero-copy mode (string literal \"true\" or \"false\")\n                    valueCapacity = 0;\n                    break;\n                case Variable::InternalDataType::String:\n                    valueCapacity = strlen(data.variable->getString()); // TODO limit by ReportingValueSize\n                    break;\n                default:\n                    MO_DBG_ERR(\"internal error\");\n                    break;\n            }\n        }\n\n        capacity += \n            JSON_OBJECT_SIZE(5) + // getVariableResult\n                valueCapacity + // capacity needed for storing the value\n                JSON_OBJECT_SIZE(2) + // component\n                    data.componentName.length() + 1 +\n                    JSON_OBJECT_SIZE(2) + // evse\n                JSON_OBJECT_SIZE(2) + // variable\n                    data.variableName.length() + 1;\n    }\n\n    auto doc = makeJsonDoc(getMemoryTag(), capacity);\n\n    JsonObject payload = doc->to<JsonObject>();\n    JsonArray getVariableResult = payload.createNestedArray(\"getVariableResult\");\n\n    for (const auto& data : queries) {\n        JsonObject getVariable = getVariableResult.createNestedObject();\n\n        const char *attributeStatusCstr = \"Rejected\";\n        switch (data.attributeStatus) {\n            case GetVariableStatus::Accepted:\n                attributeStatusCstr = \"Accepted\";\n                break;\n            case GetVariableStatus::Rejected:\n                attributeStatusCstr = \"Rejected\";\n                break;\n            case GetVariableStatus::UnknownComponent:\n                attributeStatusCstr = \"UnknownComponent\";\n                break;\n            case GetVariableStatus::UnknownVariable:\n                attributeStatusCstr = \"UnknownVariable\";\n                break;\n            case GetVariableStatus::NotSupportedAttributeType:\n                attributeStatusCstr = \"NotSupportedAttributeType\";\n                break;\n            default:\n                MO_DBG_ERR(\"internal error\");\n                break;\n        }\n        getVariable[\"attributeStatus\"] = attributeStatusCstr;\n\n        const char *attributeTypeCstr = nullptr;\n        switch (data.attributeType) {\n            case Variable::AttributeType::Actual:\n                // leave blank when Actual\n                break;\n            case Variable::AttributeType::Target:\n                attributeTypeCstr = \"Target\";\n                break;\n            case Variable::AttributeType::MinSet:\n                attributeTypeCstr = \"MinSet\";\n                break;\n            case Variable::AttributeType::MaxSet:\n                attributeTypeCstr = \"MaxSet\";\n                break;\n            default:\n                MO_DBG_ERR(\"internal error\");\n                break;\n        }\n        if (attributeTypeCstr) {\n            getVariable[\"attributeType\"] = attributeTypeCstr;\n        }\n\n        if (data.variable) {\n            switch (data.variable->getInternalDataType()) {\n                case Variable::InternalDataType::Int: {\n                    char valbuf [VALUE_BUFSIZE];\n                    auto ret = snprintf(valbuf, VALUE_BUFSIZE, \"%i\", data.variable->getInt());\n                    if (ret < 0 || ret >= VALUE_BUFSIZE) {\n                        break;\n                    }\n                    getVariable[\"attributeValue\"] = valbuf;\n                    break;\n                }\n                case Variable::InternalDataType::Bool:\n                    getVariable[\"attributeValue\"] = data.variable->getBool() ? \"true\" : \"false\";\n                    break;\n                case Variable::InternalDataType::String:\n                    getVariable[\"attributeValue\"] = (char*) data.variable->getString(); // force zero-copy mode\n                    break;\n                default:\n                    MO_DBG_ERR(\"internal error\");\n                    break;\n            }\n        }\n\n        getVariable[\"component\"][\"name\"] = (char*) data.componentName.c_str(); // force copy-mode\n\n        if (data.componentEvseId >= 0) {\n            getVariable[\"component\"][\"evse\"][\"id\"] = data.componentEvseId;\n        }\n\n        if (data.componentEvseConnectorId >= 0) {\n            getVariable[\"component\"][\"evse\"][\"connectorId\"] = data.componentEvseConnectorId;\n        }\n\n        getVariable[\"variable\"][\"name\"] = (char*) data.variableName.c_str(); // force copy-mode\n    }\n\n    return doc;\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/GetVariables.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_GETVARIABLES_H\n#define MO_GETVARIABLES_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Model/Variables/Variable.h>\n\nnamespace MicroOcpp {\n\nclass VariableService;\n\nnamespace Ocpp201 {\n\n// GetVariableDataType (2.25) and\n// GetVariableResultType (2.26)\nstruct GetVariableData {\n    // GetVariableDataType\n    Variable::AttributeType attributeType = Variable::AttributeType::Actual;\n    String componentName;\n    int componentEvseId = -1;\n    int componentEvseConnectorId = -1;\n    String variableName;\n\n    // GetVariableResultType\n    GetVariableStatus attributeStatus;\n    Variable *variable = nullptr;\n\n    GetVariableData(const char *memory_tag = nullptr);\n};\n\nclass GetVariables : public Operation, public MemoryManaged {\nprivate:\n    VariableService& variableService;\n    Vector<GetVariableData> queries;\n\n    const char *errorCode = nullptr;\npublic:\n    GetVariables(VariableService& variableService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n\n};\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/Heartbeat.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/Heartbeat.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Debug.h>\n#include <string.h>\n\nusing MicroOcpp::Ocpp16::Heartbeat;\nusing MicroOcpp::JsonDoc;\n\nHeartbeat::Heartbeat(Model& model) : MemoryManaged(\"v16.Operation.\", \"Heartbeat\"), model(model) {\n  \n}\n\nconst char* Heartbeat::getOperationType(){\n    return \"Heartbeat\";\n}\n\nstd::unique_ptr<JsonDoc> Heartbeat::createReq() {\n    return createEmptyDocument();\n}\n\nvoid Heartbeat::processConf(JsonObject payload) {\n  \n    const char* currentTime = payload[\"currentTime\"] | \"Invalid\";\n    if (strcmp(currentTime, \"Invalid\")) {\n        if (model.getClock().setTime(currentTime)) {\n            //success\n            MO_DBG_DEBUG(\"Request has been accepted\");\n        } else {\n            MO_DBG_WARN(\"Could not read time string. Expect format like 2020-02-01T20:53:32.486Z\");\n        }\n    } else {\n        MO_DBG_WARN(\"Missing field currentTime. Expect format like 2020-02-01T20:53:32.486Z\");\n    }\n}\n\nvoid Heartbeat::processReq(JsonObject payload) {\n\n    /**\n     * Ignore Contents of this Req-message, because this is for debug purposes only\n     */\n\n}\n\nstd::unique_ptr<JsonDoc> Heartbeat::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + (JSONDATE_LENGTH + 1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    //safety mechanism; in some test setups the library could have to answer Heartbeats without valid system time\n    Timestamp ocppTimeReference = Timestamp(2019,10,0,11,59,55); \n    Timestamp ocppSelect = ocppTimeReference;\n    auto& ocppNow = model.getClock().now();\n    if (ocppNow > ocppTimeReference) {\n        //time has already been set\n        ocppSelect = ocppNow;\n    }\n\n    char ocppNowJson [JSONDATE_LENGTH + 1] = {'\\0'};\n    ocppSelect.toJsonString(ocppNowJson, JSONDATE_LENGTH + 1);\n    payload[\"currentTime\"] = ocppNowJson;\n\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/Heartbeat.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_HEARTBEAT_H\n#define MO_HEARTBEAT_H\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass Heartbeat : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\npublic:\n    Heartbeat(Model& model);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/InstallCertificate.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/InstallCertificate.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <MicroOcpp/Model/Certificates/CertificateService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp201::InstallCertificate;\nusing MicroOcpp::JsonDoc;\n\nInstallCertificate::InstallCertificate(CertificateService& certService) : MemoryManaged(\"v201.Operation.\", \"InstallCertificate\"), certService(certService) {\n\n}\n\nvoid InstallCertificate::processReq(JsonObject payload) {\n\n    if (!payload.containsKey(\"certificateType\") ||\n            !payload.containsKey(\"certificate\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    InstallCertificateType certificateType;\n\n    const char *certificateTypeCstr = payload[\"certificateType\"] | \"_Invalid\";\n\n    if (!strcmp(certificateTypeCstr, \"V2GRootCertificate\")) {\n        certificateType = InstallCertificateType_V2GRootCertificate;\n    } else if (!strcmp(certificateTypeCstr, \"MORootCertificate\")) {\n        certificateType = InstallCertificateType_MORootCertificate;\n    } else if (!strcmp(certificateTypeCstr, \"CSMSRootCertificate\")) {\n        certificateType = InstallCertificateType_CSMSRootCertificate;\n    } else if (!strcmp(certificateTypeCstr, \"ManufacturerRootCertificate\")) {\n        certificateType = InstallCertificateType_ManufacturerRootCertificate;\n    } else {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    if (!payload[\"certificate\"].is<const char*>()) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    const char *certificate = payload[\"certificate\"];\n\n    auto certStore = certService.getCertificateStore();\n    if (!certStore) {\n        errorCode = \"NotSupported\";\n        return;\n    }\n\n    auto status = certStore->installCertificate(certificateType, certificate);\n\n    switch (status) {\n        case InstallCertificateStatus_Accepted:\n            this->status = \"Accepted\";\n            break;\n        case InstallCertificateStatus_Rejected:\n            this->status = \"Rejected\";\n            break;\n        case InstallCertificateStatus_Failed:\n            this->status = \"Failed\";\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            errorCode = \"InternalError\";\n            return;\n    }\n\n    //operation executed successfully\n}\n\nstd::unique_ptr<JsonDoc> InstallCertificate::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = status;\n    return doc;\n}\n\n#endif //MO_ENABLE_CERT_MGMT\n"
  },
  {
    "path": "src/MicroOcpp/Operations/InstallCertificate.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_INSTALLCERTIFICATE_H\n#define MO_INSTALLCERTIFICATE_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_CERT_MGMT\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass CertificateService;\n\nnamespace Ocpp201 {\n\nclass InstallCertificate : public Operation, public MemoryManaged {\nprivate:\n    CertificateService& certService;\n    const char *status = nullptr;\n    const char *errorCode = nullptr;\npublic:\n    InstallCertificate(CertificateService& certService);\n\n    const char* getOperationType() override {return \"InstallCertificate\";}\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_CERT_MGMT\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/MeterValues.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/MeterValues.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Metering/MeterValue.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::MeterValues;\nusing MicroOcpp::JsonDoc;\n\n//can only be used for echo server debugging\nMeterValues::MeterValues(Model& model) : MemoryManaged(\"v16.Operation.\", \"MeterValues\"), model(model) {\n    \n}\n\nMeterValues::MeterValues(Model& model, MeterValue *meterValue, unsigned int connectorId, std::shared_ptr<Transaction> transaction) \n      : MemoryManaged(\"v16.Operation.\", \"MeterValues\"), model(model), meterValue{meterValue}, connectorId{connectorId}, transaction{transaction} {\n    \n}\n\nMeterValues::MeterValues(Model& model, std::unique_ptr<MeterValue> meterValue, unsigned int connectorId, std::shared_ptr<Transaction> transaction)\n      : MeterValues(model, meterValue.get(), connectorId, transaction) {\n    this->meterValueOwnership = std::move(meterValue);\n}\n\nMeterValues::~MeterValues(){\n\n}\n\nconst char* MeterValues::getOperationType(){\n    return \"MeterValues\";\n}\n\nstd::unique_ptr<JsonDoc> MeterValues::createReq() {\n\n    size_t capacity = 0;\n\n    std::unique_ptr<JsonDoc> meterValueJson;\n\n    if (meterValue) {\n\n        if (meterValue->getTimestamp() < MIN_TIME) {\n            MO_DBG_DEBUG(\"adjust preboot MeterValue timestamp\");\n            Timestamp adjusted = model.getClock().adjustPrebootTimestamp(meterValue->getTimestamp());\n            meterValue->setTimestamp(adjusted);\n        }\n\n        meterValueJson = meterValue->toJson();\n        if (meterValueJson) {\n            capacity += meterValueJson->capacity();\n        } else {\n            MO_DBG_ERR(\"Energy meter reading not convertible to JSON\");\n        }\n    }\n\n    capacity += JSON_OBJECT_SIZE(3);\n    capacity += JSON_ARRAY_SIZE(1);\n\n    auto doc = makeJsonDoc(getMemoryTag(), capacity);\n    auto payload = doc->to<JsonObject>();\n    payload[\"connectorId\"] = connectorId;\n\n    if (transaction && transaction->getTransactionId() > 0) { //add txId if MVs are assigned to a tx with txId\n        payload[\"transactionId\"] = transaction->getTransactionId();\n    }\n\n    auto meterValueArray = payload.createNestedArray(\"meterValue\");\n    if (meterValueJson) {\n        meterValueArray.add(*meterValueJson);\n    }\n\n    return doc;\n}\n\nvoid MeterValues::processConf(JsonObject payload) {\n    MO_DBG_DEBUG(\"Request has been confirmed\");\n}\n\n\nvoid MeterValues::processReq(JsonObject payload) {\n\n    /**\n     * Ignore Contents of this Req-message, because this is for debug purposes only\n     */\n\n}\n\nstd::unique_ptr<JsonDoc> MeterValues::createConf(){\n    return createEmptyDocument();\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/MeterValues.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_METERVALUES_H\n#define MO_METERVALUES_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Core/Time.h>\n\nnamespace MicroOcpp {\n\nclass Model;\nclass MeterValue;\nclass Transaction;\n\nnamespace Ocpp16 {\n\nclass MeterValues : public Operation, public MemoryManaged {\nprivate:\n    Model& model; //for adjusting the timestamp if MeterValue has been created before BootNotification\n    MeterValue *meterValue = nullptr;\n    std::unique_ptr<MeterValue> meterValueOwnership;\n\n    unsigned int connectorId = 0;\n\n    std::shared_ptr<Transaction> transaction;\n\npublic:\n    MeterValues(Model& model, MeterValue *meterValue, unsigned int connectorId, std::shared_ptr<Transaction> transaction = nullptr);\n    MeterValues(Model& model, std::unique_ptr<MeterValue> meterValue, unsigned int connectorId, std::shared_ptr<Transaction> transaction = nullptr);\n\n    MeterValues(Model& model); //for debugging only. Make this for the server pendant\n\n    ~MeterValues();\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/NotifyReport.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Operations/NotifyReport.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Variables/Variable.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp::Ocpp201;\nusing MicroOcpp::JsonDoc;\n\nNotifyReport::NotifyReport(Model& model, int requestId, const Timestamp& generatedAt, bool tbc, int seqNo, const Vector<Variable*>& reportData)\n        : MemoryManaged(\"v201.Operation.\", \"NotifyReport\"), model(model), requestId(requestId), generatedAt(generatedAt), tbc(tbc), seqNo(seqNo), reportData(reportData) {\n\n}\n\nconst char* NotifyReport::getOperationType() {\n    return \"NotifyReport\";\n}\n\nstd::unique_ptr<JsonDoc> NotifyReport::createReq() {\n\n    #define VALUE_BUFSIZE 30 // for primitives (int)\n\n    const Variable::AttributeType enumerateAttributeTypes [] = {\n        Variable::AttributeType::Actual,\n        Variable::AttributeType::Target,\n        Variable::AttributeType::MinSet,\n        Variable::AttributeType::MaxSet\n    };\n\n    size_t capacity = \n            JSON_OBJECT_SIZE(5) + //total of 5 fields\n            JSONDATE_LENGTH + 1; //timestamp string\n\n    capacity += JSON_ARRAY_SIZE(reportData.size());\n    for (auto variable : reportData) {\n        capacity += JSON_OBJECT_SIZE(4); //total of 4 fields\n        capacity += 2 * JSON_OBJECT_SIZE(2); //component composite\n        capacity += JSON_OBJECT_SIZE(1); //variable composite\n\n        size_t nAttributes = 0;\n        size_t valueCapacity = 0;\n        for (auto attributeType : enumerateAttributeTypes) {\n            if (!variable->hasAttribute(attributeType)) {\n                continue;\n            }\n            nAttributes++;\n            switch (variable->getInternalDataType()) {\n                case Variable::InternalDataType::Int: {\n                    // measure int size by printing to a dummy buf\n                    char valbuf [VALUE_BUFSIZE];\n                    auto ret = snprintf(valbuf, VALUE_BUFSIZE, \"%i\", variable->getInt());\n                    if (ret < 0 || ret >= VALUE_BUFSIZE) {\n                        continue;\n                    }\n                    valueCapacity = (size_t) ret + 1;\n                    break;\n                }\n                case Variable::InternalDataType::Bool:\n                    // bool will be stored in zero-copy mode (string literal \"true\" or \"false\")\n                    valueCapacity = 0;\n                    break;\n                case Variable::InternalDataType::String:\n                    valueCapacity = strlen(variable->getString()); // TODO limit by ReportingValueSize\n                    break;\n                default:\n                    MO_DBG_ERR(\"internal error\");\n                    break;\n            }\n        }\n\n        capacity += nAttributes * JSON_OBJECT_SIZE(5); //variableAttribute composite\n        capacity += valueCapacity; //variableAttribute value total size\n\n        capacity += JSON_OBJECT_SIZE(2); //variableCharacteristics composite: only send two data fields\n    }\n\n    auto doc = makeJsonDoc(getMemoryTag(), capacity);\n\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"requestId\"] = requestId;\n\n    char generatedAtCstr [JSONDATE_LENGTH + 1];\n    generatedAt.toJsonString(generatedAtCstr, sizeof(generatedAtCstr));\n    payload[\"generatedAt\"] = generatedAtCstr;\n\n    if (tbc) {\n        payload[\"tbc\"] = true;\n    }\n\n    payload[\"seqNo\"] = seqNo;\n\n    JsonArray reportDataJsonArray = payload.createNestedArray(\"reportData\");\n\n    for (auto variable : reportData) {\n        JsonObject reportDataJson = reportDataJsonArray.createNestedObject();\n\n        reportDataJson[\"component\"][\"name\"] = (char*) variable->getComponentId().name; // force copy-mode\n\n        if (variable->getComponentId().evse.id >= 0) {\n            reportDataJson[\"component\"][\"evse\"][\"id\"] = variable->getComponentId().evse.id;\n        }\n\n        if (variable->getComponentId().evse.connectorId >= 0) {\n            reportDataJson[\"component\"][\"evse\"][\"connectorId\"] = variable->getComponentId().evse.connectorId;\n        }\n\n        reportDataJson[\"variable\"][\"name\"] = (char*) variable->getName(); // force copy-mode\n\n        JsonArray variableAttribute = reportDataJson.createNestedArray(\"variableAttribute\");\n\n        for (auto attributeType : enumerateAttributeTypes) {\n            if (!variable->hasAttribute(attributeType)) {\n                continue;\n            }\n\n            JsonObject attribute = variableAttribute.createNestedObject();\n\n            const char *attributeTypeCstr = nullptr;\n            switch (attributeType) {\n                case Variable::AttributeType::Actual:\n                    // leave blank when Actual\n                    break;\n                case Variable::AttributeType::Target:\n                    attributeTypeCstr = \"Target\";\n                    break;\n                case Variable::AttributeType::MinSet:\n                    attributeTypeCstr = \"MinSet\";\n                    break;\n                case Variable::AttributeType::MaxSet:\n                    attributeTypeCstr = \"MaxSet\";\n                    break;\n                default:\n                    MO_DBG_ERR(\"internal error\");\n                    break;\n            }\n            if (attributeTypeCstr) {\n                attribute[\"type\"] = attributeTypeCstr;\n            }\n\n            if (variable->getMutability() != Variable::Mutability::WriteOnly) {\n                switch (variable->getInternalDataType()) {\n                    case Variable::InternalDataType::Int: {\n                        char valbuf [VALUE_BUFSIZE];\n                        auto ret = snprintf(valbuf, VALUE_BUFSIZE, \"%i\", variable->getInt());\n                        if (ret < 0 || ret >= VALUE_BUFSIZE) {\n                            break;\n                        }\n                        attribute[\"value\"] = valbuf;\n                        break;\n                    }\n                    case Variable::InternalDataType::Bool:\n                        attribute[\"value\"] = variable->getBool() ? \"true\" : \"false\";\n                        break;\n                    case Variable::InternalDataType::String:\n                        attribute[\"value\"] = (char*) variable->getString(); // force zero-copy mode\n                        break;\n                    default:\n                        MO_DBG_ERR(\"internal error\");\n                        break;\n                }\n            }\n\n            const char *mutabilityCstr = nullptr;\n            switch (variable->getMutability()) {\n                case Variable::Mutability::ReadOnly:\n                    mutabilityCstr = \"ReadOnly\";\n                    break;\n                case Variable::Mutability::WriteOnly:\n                    mutabilityCstr = \"WriteOnly\";\n                    break;\n                case Variable::Mutability::ReadWrite:\n                    // leave blank when ReadWrite\n                    break;\n                default:\n                    MO_DBG_ERR(\"internal error\");\n                    break;\n            }\n            if (mutabilityCstr) {\n                attribute[\"mutability\"] = mutabilityCstr;\n            }\n\n            if (variable->isPersistent()) {\n                attribute[\"persistent\"] = true;\n            }\n\n            if (variable->isConstant()) {\n                attribute[\"constant\"] = true;\n            }\n        }\n\n        JsonObject variableCharacteristics = reportDataJson.createNestedObject(\"variableCharacteristics\");\n\n        const char *dataTypeCstr = \"\";\n        switch (variable->getVariableDataType()) {\n            case VariableCharacteristics::DataType::string:\n                dataTypeCstr = \"string\";\n                break;\n            case VariableCharacteristics::DataType::decimal:\n                dataTypeCstr = \"decimal\";\n                break;\n            case VariableCharacteristics::DataType::integer:\n                dataTypeCstr = \"integer\";\n                break;\n            case VariableCharacteristics::DataType::dateTime:\n                dataTypeCstr = \"dateTime\";\n                break;\n            case VariableCharacteristics::DataType::boolean:\n                dataTypeCstr = \"boolean\";\n                break;\n            case VariableCharacteristics::DataType::OptionList:\n                dataTypeCstr = \"OptionList\";\n                break;\n            case VariableCharacteristics::DataType::SequenceList:\n                dataTypeCstr = \"SequenceList\";\n                break;\n            case VariableCharacteristics::DataType::MemberList:\n                dataTypeCstr = \"MemberList\";\n                break;\n            default:\n                MO_DBG_ERR(\"internal error\");\n                break; \n        }\n        variableCharacteristics[\"dataType\"] = dataTypeCstr;\n\n        variableCharacteristics[\"supportsMonitoring\"] = variable->getSupportsMonitoring();\n    }\n\n    return doc;\n}\n\nvoid NotifyReport::processConf(JsonObject payload) {\n    // empty payload\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/NotifyReport.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_TRANSACTIONEVENT_H\n#define MO_TRANSACTIONEVENT_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Model;\nclass Variable;\n\nnamespace Ocpp201 {\n\nclass NotifyReport : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n\n    int requestId;\n    Timestamp generatedAt;\n    bool tbc;\n    int seqNo;\n    Vector<Variable*> reportData;\npublic:\n\n    NotifyReport(Model& model, int requestId, const Timestamp& generatedAt, bool tbc, int seqNo, const Vector<Variable*>& reportData);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n};\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n#endif // MO_ENABLE_V201\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/RemoteStartTransaction.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n\n#include <MicroOcpp/Operations/RemoteStartTransaction.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Model/SmartCharging/SmartChargingService.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::RemoteStartTransaction;\nusing MicroOcpp::JsonDoc;\n\nRemoteStartTransaction::RemoteStartTransaction(Model& model) : MemoryManaged(\"v16.Operation.\", \"RemoteStartTransaction\"), model(model) {\n  \n}\n\nconst char* RemoteStartTransaction::getOperationType() {\n    return \"RemoteStartTransaction\";\n}\n\nvoid RemoteStartTransaction::processReq(JsonObject payload) {\n    int connectorId = payload[\"connectorId\"] | -1;\n\n    // OCPP 1.6 specification: connectorId SHALL be > 0 (TC_027_CS)\n    if (connectorId == 0) {\n        MO_DBG_INFO(\"RemoteStartTransaction rejected: connectorId SHALL not be 0\");\n        accepted = false;\n        return;\n    }\n\n    if (!payload.containsKey(\"idTag\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    const char *idTag = payload[\"idTag\"] | \"\";\n    size_t len = strnlen(idTag, IDTAG_LEN_MAX + 1);\n    if (len == 0 || len > IDTAG_LEN_MAX) {\n        errorCode = \"PropertyConstraintViolation\";\n        errorDescription = \"idTag empty or too long\";\n        return;\n    }\n\n    std::unique_ptr<ChargingProfile> chargingProfile;\n\n    if (payload.containsKey(\"chargingProfile\") && model.getSmartChargingService()) {\n        MO_DBG_INFO(\"Setting Charging profile via RemoteStartTransaction\");\n\n        JsonObject chargingProfileJson = payload[\"chargingProfile\"];\n        chargingProfile = loadChargingProfile(chargingProfileJson);\n\n        if (!chargingProfile) {\n            errorCode = \"PropertyConstraintViolation\";\n            errorDescription = \"chargingProfile validation failed\";\n            return;\n        }\n\n        if (chargingProfile->getChargingProfilePurpose() != ChargingProfilePurposeType::TxProfile) {\n            errorCode = \"PropertyConstraintViolation\";\n            errorDescription = \"Can only set TxProfile here\";\n            return;\n        }\n\n        if (chargingProfile->getChargingProfileId() < 0) {\n            errorCode = \"PropertyConstraintViolation\";\n            errorDescription = \"RemoteStartTx profile requires non-negative chargingProfileId\";\n            return;\n        }\n    }\n\n    Connector *selectConnector = nullptr;\n    if (connectorId >= 1) {\n        //connectorId specified for given connector, try to start Transaction here\n        if (auto connector = model.getConnector(connectorId)){\n            if (!connector->getTransaction() &&\n                        connector->isOperative()) {\n                selectConnector = connector;\n            }\n        }\n    } else {\n        //connectorId not specified. Find free connector\n        for (unsigned int cid = 1; cid < model.getNumConnectors(); cid++) {\n            auto connector = model.getConnector(cid);\n            if (!connector->getTransaction() &&\n                        connector->isOperative()) {\n                selectConnector = connector;\n                connectorId = cid;\n                break;\n            }\n        }\n    }\n\n    if (selectConnector) {\n\n        bool success = true;\n\n        int chargingProfileId = -1; //keep Id after moving charging profile to SCService\n\n        if (chargingProfile && model.getSmartChargingService()) {\n            chargingProfileId = chargingProfile->getChargingProfileId();\n            success = model.getSmartChargingService()->setChargingProfile(connectorId, std::move(chargingProfile));\n        }\n\n        if (success) {\n            std::shared_ptr<MicroOcpp::Transaction> tx;\n            auto authorizeRemoteTxRequests = declareConfiguration<bool>(\"AuthorizeRemoteTxRequests\", false);\n            if (authorizeRemoteTxRequests && authorizeRemoteTxRequests->getBool()) {\n                tx = selectConnector->beginTransaction(idTag);\n            } else {\n                tx = selectConnector->beginTransaction_authorized(idTag);\n            }\n            selectConnector->updateTxNotification(TxNotification_RemoteStart);\n            if (tx) {\n                if (chargingProfileId >= 0) {\n                    tx->setTxProfileId(chargingProfileId);\n                }\n            } else {\n                success = false;\n            }\n        }\n\n        accepted = success;\n    } else {\n        MO_DBG_INFO(\"No connector to start transaction\");\n        accepted = false;\n    }\n}\n\nstd::unique_ptr<JsonDoc> RemoteStartTransaction::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    if (accepted) {\n        payload[\"status\"] = \"Accepted\";\n    } else {\n        payload[\"status\"] = \"Rejected\";\n    }\n    return doc;\n}\n\nstd::unique_ptr<JsonDoc> RemoteStartTransaction::createReq() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    payload[\"idTag\"] = \"A0000000\";\n\n    return doc;\n}\n\nvoid RemoteStartTransaction::processConf(JsonObject payload){\n    \n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/RemoteStartTransaction.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_REMOTESTARTTRANSACTION_H\n#define MO_REMOTESTARTTRANSACTION_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Operations/CiStrings.h>\n\nnamespace MicroOcpp {\n\nclass Model;\nclass ChargingProfile;\n\nnamespace Ocpp16 {\n\nclass RemoteStartTransaction : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n\n    bool accepted = false;\n    \n    const char *errorCode {nullptr};\n    const char *errorDescription = \"\";\npublic:\n    RemoteStartTransaction(Model& model);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n    const char *getErrorDescription() override {return errorDescription;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/RemoteStopTransaction.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/RemoteStopTransaction.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n\nusing MicroOcpp::Ocpp16::RemoteStopTransaction;\nusing MicroOcpp::JsonDoc;\n\nRemoteStopTransaction::RemoteStopTransaction(Model& model) : MemoryManaged(\"v16.Operation.\", \"RemoteStopTransaction\"), model(model) {\n  \n}\n\nconst char* RemoteStopTransaction::getOperationType(){\n    return \"RemoteStopTransaction\";\n}\n\nvoid RemoteStopTransaction::processReq(JsonObject payload) {\n\n    if (!payload.containsKey(\"transactionId\")) {\n        errorCode = \"FormationViolation\";\n    }\n    int transactionId = payload[\"transactionId\"];\n\n    for (unsigned int cId = 0; cId < model.getNumConnectors(); cId++) {\n        auto connector = model.getConnector(cId);\n        if (connector->getTransaction() &&\n                connector->getTransaction()->getTransactionId() == transactionId) {\n            connector->endTransaction(nullptr, \"Remote\");\n            accepted = true;\n        }\n    }\n}\n\nstd::unique_ptr<JsonDoc> RemoteStopTransaction::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    if (accepted){\n        payload[\"status\"] = \"Accepted\";\n    } else {\n        payload[\"status\"] = \"Rejected\";\n    }\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/RemoteStopTransaction.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_REMOTESTOPTRANSACTION_H\n#define MO_REMOTESTOPTRANSACTION_H\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass RemoteStopTransaction : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    bool accepted = false;\n\n    const char *errorCode = nullptr;\npublic:\n    RemoteStopTransaction(Model& model);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/RequestStartTransaction.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Operations/RequestStartTransaction.h>\n#include <MicroOcpp/Model/RemoteControl/RemoteControlService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp201::RequestStartTransaction;\nusing MicroOcpp::JsonDoc;\n\nRequestStartTransaction::RequestStartTransaction(RemoteControlService& rcService) : MemoryManaged(\"v201.Operation.\", \"RequestStartTransaction\"), rcService(rcService) {\n  \n}\n\nconst char* RequestStartTransaction::getOperationType(){\n    return \"RequestStartTransaction\";\n}\n\nvoid RequestStartTransaction::processReq(JsonObject payload) {\n\n    int evseId = payload[\"evseId\"] | 0;\n    if (evseId < 0 || evseId >= MO_NUM_EVSEID) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    int remoteStartId = payload[\"remoteStartId\"] | 0;\n    if (remoteStartId < 0) {\n        errorCode = \"PropertyConstraintViolation\";\n        MO_DBG_ERR(\"IDs must be >= 0\");\n        return;\n    }\n\n    IdToken idToken;\n    if (!idToken.parseCstr(payload[\"idToken\"][\"idToken\"] | (const char*)nullptr, payload[\"idToken\"][\"type\"] | (const char*)nullptr)) { //parseCstr rejects nullptr as argument\n        MO_DBG_ERR(\"could not parse idToken\");\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    status = rcService.requestStartTransaction(evseId, remoteStartId, idToken, transactionId, sizeof(transactionId));\n}\n\nstd::unique_ptr<JsonDoc> RequestStartTransaction::createConf(){\n\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(2));\n    JsonObject payload = doc->to<JsonObject>();\n\n    const char *statusCstr = \"\";\n\n    switch (status) {\n        case RequestStartStopStatus_Accepted:\n            statusCstr = \"Accepted\";\n            break;\n        case RequestStartStopStatus_Rejected:\n            statusCstr = \"Rejected\";\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            break;\n    }\n\n    payload[\"status\"] = statusCstr;\n\n    if (transaction) {\n        payload[\"transactionId\"] = (const char*)transaction->transactionId; //force zero-copy mode\n    }\n\n    return doc;\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/RequestStartTransaction.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_REQUESTSTARTTRANSACTION_H\n#define MO_REQUESTSTARTTRANSACTION_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Model/RemoteControl/RemoteControlDefs.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n\nnamespace MicroOcpp {\n\nclass RemoteControlService;\n\nnamespace Ocpp201 {\n\nclass RequestStartTransaction : public Operation, public MemoryManaged {\nprivate:\n    RemoteControlService& rcService;\n\n    RequestStartStopStatus status;\n    std::shared_ptr<Ocpp201::Transaction> transaction;\n    char transactionId [MO_TXID_LEN_MAX + 1] = {'\\0'};\n\n    const char *errorCode = nullptr;\npublic:\n    RequestStartTransaction(RemoteControlService& rcService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n\n};\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/RequestStopTransaction.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Operations/RequestStopTransaction.h>\n#include <MicroOcpp/Model/RemoteControl/RemoteControlService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp201::RequestStopTransaction;\nusing MicroOcpp::JsonDoc;\n\nRequestStopTransaction::RequestStopTransaction(RemoteControlService& rcService) : MemoryManaged(\"v201.Operation.\", \"RequestStopTransaction\"), rcService(rcService) {\n  \n}\n\nconst char* RequestStopTransaction::getOperationType(){\n    return \"RequestStopTransaction\";\n}\n\nvoid RequestStopTransaction::processReq(JsonObject payload) {\n\n    if (!payload.containsKey(\"transactionId\") || \n            !payload[\"transactionId\"].is<const char*>() ||\n            strlen(payload[\"transactionId\"].as<const char*>()) > MO_TXID_LEN_MAX) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    status = rcService.requestStopTransaction(payload[\"transactionId\"].as<const char*>());\n}\n\nstd::unique_ptr<JsonDoc> RequestStopTransaction::createConf(){\n\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    const char *statusCstr = \"\";\n\n    switch (status) {\n        case RequestStartStopStatus_Accepted:\n            statusCstr = \"Accepted\";\n            break;\n        case RequestStartStopStatus_Rejected:\n            statusCstr = \"Rejected\";\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            break;\n    }\n\n    payload[\"status\"] = statusCstr;\n\n    return doc;\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/RequestStopTransaction.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_REQUESTSTOPTRANSACTION_H\n#define MO_REQUESTSTOPTRANSACTION_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Model/RemoteControl/RemoteControlDefs.h>\n\nnamespace MicroOcpp {\n\nclass RemoteControlService;\n\nnamespace Ocpp201 {\n\nclass RequestStopTransaction : public Operation, public MemoryManaged {\nprivate:\n    RemoteControlService& rcService;\n\n    RequestStartStopStatus status;\n\n    const char *errorCode = nullptr;\npublic:\n    RequestStopTransaction(RemoteControlService& rcService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n\n};\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ReserveNow.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_RESERVATION\n\n#include <MicroOcpp/Operations/ReserveNow.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Reservation/ReservationService.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Platform.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::ReserveNow;\nusing MicroOcpp::JsonDoc;\n\nReserveNow::ReserveNow(Model& model) : MemoryManaged(\"v16.Operation.\", \"ReserveNow\"), model(model) {\n  \n}\n\nReserveNow::~ReserveNow() {\n  \n}\n\nconst char* ReserveNow::getOperationType(){\n    return \"ReserveNow\";\n}\n\nvoid ReserveNow::processReq(JsonObject payload) {\n    if (!payload.containsKey(\"connectorId\") ||\n            payload[\"connectorId\"] < 0 ||\n            !payload.containsKey(\"expiryDate\") ||\n            !payload.containsKey(\"idTag\") ||\n            //parentIdTag is optional\n            !payload.containsKey(\"reservationId\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    int connectorId = payload[\"connectorId\"] | -1;\n    if (connectorId < 0 || (unsigned int) connectorId >= model.getNumConnectors()) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    Timestamp expiryDate;\n    if (!expiryDate.setTime(payload[\"expiryDate\"])) {\n        MO_DBG_WARN(\"bad time format\");\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    const char *idTag = payload[\"idTag\"] | \"\";\n    if (!*idTag) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    const char *parentIdTag = nullptr;\n    if (payload.containsKey(\"parentIdTag\")) {\n        parentIdTag = payload[\"parentIdTag\"];\n    }\n\n    int reservationId = payload[\"reservationId\"] | -1;\n\n    if (model.getReservationService() &&\n                model.getNumConnectors() > 0) {\n        auto rService = model.getReservationService();\n        auto chargePoint = model.getConnector(0);\n\n        auto reserveConnectorZeroSupportedBool = declareConfiguration<bool>(\"ReserveConnectorZeroSupported\", true, CONFIGURATION_VOLATILE);\n        if (connectorId == 0 && (!reserveConnectorZeroSupportedBool || !reserveConnectorZeroSupportedBool->getBool())) {\n            reservationStatus = \"Rejected\";\n            return;\n        }\n\n        if (auto reservation = rService->getReservationById(reservationId)) {\n            reservation->update(reservationId, (unsigned int) connectorId, expiryDate, idTag, parentIdTag);\n            reservationStatus = \"Accepted\";\n            return;\n        }\n\n        Connector *connector = nullptr;\n        \n        if (connectorId > 0) {\n            connector = model.getConnector((unsigned int) connectorId);\n        }\n\n        if (chargePoint->getStatus() == ChargePointStatus_Faulted ||\n                (connector && connector->getStatus() == ChargePointStatus_Faulted)) {\n            reservationStatus = \"Faulted\";\n            return;\n        }\n\n        if (chargePoint->getStatus() == ChargePointStatus_Unavailable ||\n                (connector && connector->getStatus() == ChargePointStatus_Unavailable)) {\n            reservationStatus = \"Unavailable\";\n            return;\n        }\n\n        if (connector && connector->getStatus() != ChargePointStatus_Available) {\n            reservationStatus = \"Occupied\";\n            return;\n        }\n\n        if (rService->updateReservation(reservationId, (unsigned int) connectorId, expiryDate, idTag, parentIdTag)) {\n            reservationStatus = \"Accepted\";\n        } else {\n            reservationStatus = \"Occupied\";\n        }\n    } else {\n        errorCode = \"InternalError\";\n    }\n}\n\nstd::unique_ptr<JsonDoc> ReserveNow::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    if (reservationStatus) {\n        payload[\"status\"] = reservationStatus;\n    } else {\n        MO_DBG_ERR(\"didn't set reservationStatus\");\n        payload[\"status\"] = \"Rejected\";\n    }\n    \n    return doc;\n}\n\n#endif //MO_ENABLE_RESERVATION\n"
  },
  {
    "path": "src/MicroOcpp/Operations/ReserveNow.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_RESERVENOW_H\n#define MO_RESERVENOW_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_RESERVATION\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass ReserveNow : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    const char *errorCode = nullptr;\n    const char *reservationStatus = nullptr;\npublic:\n    ReserveNow(Model& model);\n\n    ~ReserveNow();\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_RESERVATION\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/Reset.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/Reset.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Reset/ResetService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::Reset;\nusing MicroOcpp::JsonDoc;\n\nReset::Reset(Model& model) : MemoryManaged(\"v16.Operation.\", \"Reset\"), model(model) {\n  \n}\n\nconst char* Reset::getOperationType(){\n    return \"Reset\";\n}\n\nvoid Reset::processReq(JsonObject payload) {\n    /*\n     * Process the application data here. Note: you have to implement the device reset procedure in your client code. You have to set\n     * a onSendConfListener in which you initiate a reset (e.g. calling ESP.reset() )\n     */\n    bool isHard = !strcmp(payload[\"type\"] | \"undefined\", \"Hard\");\n\n    if (auto rService = model.getResetService()) {\n        if (!rService->getExecuteReset()) {\n            MO_DBG_ERR(\"No reset handler set. Abort operation\");\n            //resetAccepted remains false\n        } else {\n            if (!rService->getPreReset() || rService->getPreReset()(isHard) || isHard) {\n                resetAccepted = true;\n                rService->initiateReset(isHard);\n                for (unsigned int cId = 0; cId < model.getNumConnectors(); cId++) {\n                    model.getConnector(cId)->endTransaction(nullptr, isHard ? \"HardReset\" : \"SoftReset\");\n                }\n            }\n        }\n    } else {\n        resetAccepted = true; //assume that onReceiveReset is set\n    }\n}\n\nstd::unique_ptr<JsonDoc> Reset::createConf() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = resetAccepted ? \"Accepted\" : \"Rejected\";\n    return doc;\n}\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/ConnectorBase/EvseId.h>\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nReset::Reset(ResetService& resetService) : MemoryManaged(\"v201.Operation.\", \"Reset\"), resetService(resetService) {\n  \n}\n\nconst char* Reset::getOperationType(){\n    return \"Reset\";\n}\n\nvoid Reset::processReq(JsonObject payload) {\n\n    ResetType type;\n    const char *typeCstr = payload[\"type\"] | \"_Undefined\";\n    \n    if (!strcmp(typeCstr, \"Immediate\")) {\n        type = ResetType_Immediate;\n    } else if (!strcmp(typeCstr, \"OnIdle\")) {\n        type = ResetType_OnIdle;\n    } else {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    int evseIdRaw = payload[\"evseId\"] | 0;\n\n    if (evseIdRaw < 0 || evseIdRaw >= MO_NUM_EVSEID) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    unsigned int evseId = (unsigned int) evseIdRaw;\n\n    status = resetService.initiateReset(type, evseId);\n}\n\nstd::unique_ptr<JsonDoc> Reset::createConf() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    const char *statusCstr = \"\";\n    switch (status) {\n        case ResetStatus_Accepted:\n            statusCstr = \"Accepted\";\n            break;\n        case ResetStatus_Rejected:\n            statusCstr = \"Rejected\";\n            break;\n        case ResetStatus_Scheduled:\n            statusCstr = \"Scheduled\";\n            break;\n        default:\n            MO_DBG_ERR(\"internal error\");\n            break;\n    }\n    payload[\"status\"] = statusCstr;\n    return doc;\n}\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n#endif //MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/Reset.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef RESET_H\n#define RESET_H\n\n#include <MicroOcpp/Version.h>\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Model/Reset/ResetDefs.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass Reset : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    bool resetAccepted {false};\npublic:\n    Reset(Model& model);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#if MO_ENABLE_V201\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nclass ResetService;\n\nclass Reset : public Operation, public MemoryManaged {\nprivate:\n    ResetService& resetService;\n    ResetStatus status;\n    const char *errorCode = nullptr;\npublic:\n    Reset(ResetService& resetService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/SecurityEventNotification.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Operations/SecurityEventNotification.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp201::SecurityEventNotification;\nusing MicroOcpp::JsonDoc;\n\nSecurityEventNotification::SecurityEventNotification(const char *type, const Timestamp& timestamp) : MemoryManaged(\"v201.Operation.\", \"SecurityEventNotification\"), type(makeString(getMemoryTag(), type ? type : \"\")), timestamp(timestamp) {\n\n}\n\nconst char* SecurityEventNotification::getOperationType(){\n    return \"SecurityEventNotification\";\n}\n\nstd::unique_ptr<JsonDoc> SecurityEventNotification::createReq() {\n\n    auto doc = makeJsonDoc(getMemoryTag(),\n                JSON_OBJECT_SIZE(2) +\n                JSONDATE_LENGTH + 1);\n\n    JsonObject payload = doc->to<JsonObject>();\n\n    payload[\"type\"] = type.c_str();\n\n    char timestampStr [JSONDATE_LENGTH + 1];\n    timestamp.toJsonString(timestampStr, sizeof(timestampStr));\n    payload[\"timestamp\"] = timestampStr;\n\n    return doc;\n}\n\nvoid SecurityEventNotification::processConf(JsonObject) {\n    //empty payload\n}\n\nvoid SecurityEventNotification::processReq(JsonObject payload) {\n    /**\n     * Ignore Contents of this Req-message, because this is for debug purposes only\n     */\n}\n\nstd::unique_ptr<JsonDoc> SecurityEventNotification::createConf() {\n    return createEmptyDocument();\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/SecurityEventNotification.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_SECURITYEVENTNOTIFICATION_H\n#define MO_SECURITYEVENTNOTIFICATION_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Core/Time.h>\n\nnamespace MicroOcpp {\n\nnamespace Ocpp201 {\n\nclass SecurityEventNotification : public Operation, public MemoryManaged {\nprivate:\n    String type;\n    Timestamp timestamp;\n\n    const char *errorCode = nullptr;\npublic:\n    SecurityEventNotification(const char *type, const Timestamp& timestamp);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    const char *getErrorCode() override {return errorCode;}\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n};\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/SendLocalList.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Operations/SendLocalList.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n\nusing MicroOcpp::Ocpp16::SendLocalList;\nusing MicroOcpp::JsonDoc;\n\nSendLocalList::SendLocalList(AuthorizationService& authService) : MemoryManaged(\"v16.Operation.\", \"SendLocalList\"), authService(authService) {\n  \n}\n\nSendLocalList::~SendLocalList() {\n  \n}\n\nconst char* SendLocalList::getOperationType(){\n    return \"SendLocalList\";\n}\n\nvoid SendLocalList::processReq(JsonObject payload) {\n\n    //TC_043_1_CS Send Local Authorization List - NotSupported\n    if (!authService.localAuthListEnabled()) {\n        errorCode = \"NotSupported\";\n        return;\n    }\n\n    if (!payload.containsKey(\"listVersion\") || !payload.containsKey(\"updateType\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    if (payload.containsKey(\"localAuthorizationList\") && !payload[\"localAuthorizationList\"].is<JsonArray>()) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    JsonArray localAuthorizationList = payload[\"localAuthorizationList\"];\n\n    if (localAuthorizationList.size() > MO_SendLocalListMaxLength) {\n        errorCode = \"OccurenceConstraintViolation\";\n        return;\n    }\n\n    bool differential = !strcmp(\"Differential\", payload[\"updateType\"] | \"Invalid\"); //updateType Differential or Full\n\n    int listVersion = payload[\"listVersion\"];\n\n    if (differential && authService.getLocalListVersion() >= listVersion) {\n        versionMismatch = true;\n        return;\n    }\n\n    updateFailure = !authService.updateLocalList(localAuthorizationList, listVersion, differential);\n}\n\nstd::unique_ptr<JsonDoc> SendLocalList::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    if (versionMismatch) {\n        payload[\"status\"] = \"VersionMismatch\";\n    } else if (updateFailure) {\n        payload[\"status\"] = \"Failed\";\n    } else {\n        payload[\"status\"] = \"Accepted\";\n    }\n    \n    return doc;\n}\n\n#endif //MO_ENABLE_LOCAL_AUTH\n"
  },
  {
    "path": "src/MicroOcpp/Operations/SendLocalList.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_SENDLOCALLIST_H\n#define MO_SENDLOCALLIST_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass AuthorizationService;\n\nnamespace Ocpp16 {\n\nclass SendLocalList : public Operation, public MemoryManaged {\nprivate:\n    AuthorizationService& authService;\n    const char *errorCode = nullptr;\n    bool updateFailure = true;\n    bool versionMismatch = false;\npublic:\n    SendLocalList(AuthorizationService& authService);\n\n    ~SendLocalList();\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_LOCAL_AUTH\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/SetChargingProfile.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/SetChargingProfile.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/SmartCharging/SmartChargingService.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::SetChargingProfile;\nusing MicroOcpp::JsonDoc;\n\nSetChargingProfile::SetChargingProfile(Model& model, SmartChargingService& scService) : MemoryManaged(\"v16.Operation.\", \"SetChargingProfile\"), model(model), scService(scService) {\n\n}\n\nSetChargingProfile::~SetChargingProfile() {\n\n}\n\nconst char* SetChargingProfile::getOperationType(){\n    return \"SetChargingProfile\";\n}\n\nvoid SetChargingProfile::processReq(JsonObject payload) {\n\n    int connectorId = payload[\"connectorId\"] | -1;\n    if (connectorId < 0 || !payload.containsKey(\"csChargingProfiles\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    if ((unsigned int) connectorId >= model.getNumConnectors()) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    JsonObject csChargingProfiles = payload[\"csChargingProfiles\"];\n\n    auto chargingProfile = loadChargingProfile(csChargingProfiles);\n    if (!chargingProfile) {\n        errorCode = \"PropertyConstraintViolation\";\n        errorDescription = \"csChargingProfiles validation failed\";\n        return;\n    }\n\n    if (chargingProfile->getChargingProfilePurpose() == ChargingProfilePurposeType::TxProfile) {\n        // if TxProfile, check if a transaction is running\n\n        if (connectorId == 0) {\n            errorCode = \"PropertyConstraintViolation\";\n            errorDescription = \"Cannot set TxProfile at connectorId 0\";\n            return;\n        }\n        Connector *connector = model.getConnector(connectorId);\n        if (!connector) {\n            errorCode = \"PropertyConstraintViolation\";\n            return;\n        }\n\n        auto& transaction = connector->getTransaction();\n        if (!transaction || !connector->getTransaction()->isRunning()) {\n            //no transaction running, reject profile\n            accepted = false;\n            return;\n        }\n\n        if (chargingProfile->getTransactionId() >= 0 &&\n                chargingProfile->getTransactionId() != transaction->getTransactionId()) {\n            //transactionId undefined / mismatch\n            accepted = false;\n            return;\n        }\n\n        //seems good\n    } else if (chargingProfile->getChargingProfilePurpose() == ChargingProfilePurposeType::ChargePointMaxProfile) {\n        if (connectorId > 0) {\n            errorCode = \"PropertyConstraintViolation\";\n            errorDescription = \"Cannot set ChargePointMaxProfile at connectorId > 0\";\n            return;\n        }\n    }\n\n    accepted = scService.setChargingProfile(connectorId, std::move(chargingProfile));\n}\n\nstd::unique_ptr<JsonDoc> SetChargingProfile::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    if (accepted) {\n        payload[\"status\"] = \"Accepted\";\n    } else {\n        payload[\"status\"] = \"Rejected\";\n    }\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/SetChargingProfile.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_SETCHARGINGPROFILE_H\n#define MO_SETCHARGINGPROFILE_H\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass Model;\nclass SmartChargingService;\n\nnamespace Ocpp16 {\n\nclass SetChargingProfile : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    SmartChargingService& scService;\n\n    bool accepted = false;\n    const char *errorCode = nullptr;\n    const char *errorDescription = \"\";\npublic:\n    SetChargingProfile(Model& model, SmartChargingService& scService);\n\n    ~SetChargingProfile();\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n    const char *getErrorDescription() override {return errorDescription;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/SetVariables.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Operations/SetVariables.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp201::SetVariableData;\nusing MicroOcpp::Ocpp201::SetVariables;\nusing MicroOcpp::JsonDoc;\n\nSetVariableData::SetVariableData(const char *memory_tag) : componentName{makeString(memory_tag)}, variableName{makeString(memory_tag)} {\n\n}\n\nSetVariables::SetVariables(VariableService& variableService) : MemoryManaged(\"v201.Operation.\", \"SetVariables\"), variableService(variableService), queries(makeVector<SetVariableData>(getMemoryTag())) {\n  \n}\n\nconst char* SetVariables::getOperationType(){\n    return \"SetVariables\";\n}\n\nvoid SetVariables::processReq(JsonObject payload) {\n    for (JsonObject setVariable : payload[\"setVariableData\"].as<JsonArray>()) {\n\n        queries.emplace_back(getMemoryTag());\n        auto& data = queries.back();\n\n        if (setVariable.containsKey(\"attributeType\")) {\n            const char *attributeTypeCstr = setVariable[\"attributeType\"] | \"_Undefined\";\n            if (!strcmp(attributeTypeCstr, \"Actual\")) {\n                data.attributeType = Variable::AttributeType::Actual;\n            } else if (!strcmp(attributeTypeCstr, \"Target\")) {\n                data.attributeType = Variable::AttributeType::Target;\n            } else if (!strcmp(attributeTypeCstr, \"MinSet\")) {\n                data.attributeType = Variable::AttributeType::MinSet;\n            } else if (!strcmp(attributeTypeCstr, \"MaxSet\")) {\n                data.attributeType = Variable::AttributeType::MaxSet;\n            } else {\n                errorCode = \"FormationViolation\";\n                MO_DBG_ERR(\"invalid attributeType\");\n                return;\n            }\n        }\n\n        const char *attributeValueCstr = setVariable[\"attributeValue\"] | (const char*) nullptr;\n        const char *componentNameCstr = setVariable[\"component\"][\"name\"] | (const char*) nullptr;\n        const char *variableNameCstr = setVariable[\"variable\"][\"name\"] | (const char*) nullptr;\n\n        if (!attributeValueCstr ||\n                !componentNameCstr ||\n                !variableNameCstr) {\n            errorCode = \"FormationViolation\";\n            return;\n        }\n\n        data.attributeValue = attributeValueCstr;\n        data.componentName = componentNameCstr;\n        data.variableName = variableNameCstr;\n\n        // TODO check against ConfigurationValueSize\n\n        data.componentEvseId = setVariable[\"component\"][\"evse\"][\"id\"] | -1;\n        data.componentEvseConnectorId = setVariable[\"component\"][\"evse\"][\"connectorId\"] | -1;\n\n        if (setVariable[\"component\"].containsKey(\"evse\") && data.componentEvseId < 0) {\n            errorCode = \"FormationViolation\";\n            MO_DBG_ERR(\"malformatted / missing evseId\");\n            return;\n        }\n    }\n\n    if (queries.empty()) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    MO_DBG_DEBUG(\"processing %zu setVariable queries\", queries.size());\n\n    for (auto& query : queries) {\n        query.attributeStatus = variableService.setVariable(\n                query.attributeType,\n                query.attributeValue,\n                ComponentId(query.componentName.c_str(), \n                    EvseId(query.componentEvseId, query.componentEvseConnectorId)),\n                query.variableName.c_str());\n    }\n\n    if (!variableService.commit()) {\n        errorCode = \"InternalError\";\n        MO_DBG_ERR(\"Variables could not be stored. Rollback not possible\");\n        return;\n    }\n}\n\nstd::unique_ptr<JsonDoc> SetVariables::createConf(){\n    size_t capacity = JSON_ARRAY_SIZE(queries.size());\n    for (const auto& data : queries) {\n        capacity += \n            JSON_OBJECT_SIZE(5) + // setVariableResult\n                JSON_OBJECT_SIZE(2) + // component\n                    data.componentName.length() + 1 +\n                    JSON_OBJECT_SIZE(2) + // evse\n                JSON_OBJECT_SIZE(2) + // variable\n                    data.variableName.length() + 1;\n    }\n    auto doc = makeJsonDoc(getMemoryTag(), capacity);\n\n    JsonObject payload = doc->to<JsonObject>();\n    JsonArray setVariableResult = payload.createNestedArray(\"setVariableResult\");\n\n    for (const auto& data : queries) {\n        JsonObject setVariable = setVariableResult.createNestedObject();\n\n        const char *attributeTypeCstr = nullptr;\n        switch (data.attributeType) {\n            case Variable::AttributeType::Actual:\n                // leave blank when Actual\n                break;\n            case Variable::AttributeType::Target:\n                attributeTypeCstr = \"Target\";\n                break;\n            case Variable::AttributeType::MinSet:\n                attributeTypeCstr = \"MinSet\";\n                break;\n            case Variable::AttributeType::MaxSet:\n                attributeTypeCstr = \"MaxSet\";\n                break;\n            default:\n                MO_DBG_ERR(\"internal error\");\n                break;\n        }\n        if (attributeTypeCstr) {\n            setVariable[\"attributeType\"] = attributeTypeCstr;\n        }\n\n        const char *attributeStatusCstr = \"Rejected\";\n        switch (data.attributeStatus) {\n            case SetVariableStatus::Accepted:\n                attributeStatusCstr = \"Accepted\";\n                break;\n            case SetVariableStatus::Rejected:\n                attributeStatusCstr = \"Rejected\";\n                break;\n            case SetVariableStatus::UnknownComponent:\n                attributeStatusCstr = \"UnknownComponent\";\n                break;\n            case SetVariableStatus::UnknownVariable:\n                attributeStatusCstr = \"UnknownVariable\";\n                break;\n            case SetVariableStatus::NotSupportedAttributeType:\n                attributeStatusCstr = \"NotSupportedAttributeType\";\n                break;\n            case SetVariableStatus::RebootRequired:\n                attributeStatusCstr = \"RebootRequired\";\n                break;\n            default:\n                MO_DBG_ERR(\"internal error\");\n                break;\n        }\n        setVariable[\"attributeStatus\"] = attributeStatusCstr;\n\n        setVariable[\"component\"][\"name\"] = (char*) data.componentName.c_str(); // force copy-mode\n\n        if (data.componentEvseId >= 0) {\n            setVariable[\"component\"][\"evse\"][\"id\"] = data.componentEvseId;\n        }\n\n        if (data.componentEvseConnectorId >= 0) {\n            setVariable[\"component\"][\"evse\"][\"connectorId\"] = data.componentEvseConnectorId;\n        }\n\n        setVariable[\"variable\"][\"name\"] = (char*) data.variableName.c_str(); // force copy-mode\n    }\n\n    return doc;\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/SetVariables.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_SETVARIABLES_H\n#define MO_SETVARIABLES_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Model/Variables/Variable.h>\n\nnamespace MicroOcpp {\n\nclass VariableService;\n\nnamespace Ocpp201 {\n\n// SetVariableDataType (2.44) and\n// SetVariableResultType (2.45)\nstruct SetVariableData {\n    // SetVariableDataType\n    Variable::AttributeType attributeType = Variable::AttributeType::Actual;\n    const char *attributeValue; // will become invalid after processReq\n    String componentName;\n    int componentEvseId = -1;\n    int componentEvseConnectorId = -1;\n    String variableName;\n\n    // SetVariableResultType\n    SetVariableStatus attributeStatus;\n\n    SetVariableData(const char *memory_tag = nullptr);\n};\n\nclass SetVariables : public Operation, public MemoryManaged {\nprivate:\n    VariableService& variableService;\n    Vector<SetVariableData> queries;\n\n    const char *errorCode = nullptr;\npublic:\n    SetVariables(VariableService& variableService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n\n};\n\n} //namespace Ocpp201\n} //namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/StartTransaction.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/StartTransaction.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n#include <MicroOcpp/Model/Metering/MeteringService.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Debug.h>\n#include <MicroOcpp/Version.h>\n\nusing MicroOcpp::Ocpp16::StartTransaction;\nusing MicroOcpp::JsonDoc;\n\n\nStartTransaction::StartTransaction(Model& model, std::shared_ptr<Transaction> transaction) : MemoryManaged(\"v16.Operation.\", \"StartTransaction\"), model(model), transaction(transaction) {\n    \n}\n\nStartTransaction::~StartTransaction() {\n    \n}\n\nconst char* StartTransaction::getOperationType() {\n    return \"StartTransaction\";\n}\n\nstd::unique_ptr<JsonDoc> StartTransaction::createReq() {\n\n    auto doc = makeJsonDoc(getMemoryTag(),\n                JSON_OBJECT_SIZE(6) + \n                (IDTAG_LEN_MAX + 1) +\n                (JSONDATE_LENGTH + 1));\n                \n    JsonObject payload = doc->to<JsonObject>();\n\n    payload[\"connectorId\"] = transaction->getConnectorId();\n    payload[\"idTag\"] = (char*) transaction->getIdTag();\n    payload[\"meterStart\"] = transaction->getMeterStart();\n\n    if (transaction->getReservationId() >= 0) {\n        payload[\"reservationId\"] = transaction->getReservationId();\n    }\n\n    if (transaction->getStartTimestamp() < MIN_TIME &&\n            transaction->getStartBootNr() == model.getBootNr()) {\n        MO_DBG_DEBUG(\"adjust preboot StartTx timestamp\");\n        Timestamp adjusted = model.getClock().adjustPrebootTimestamp(transaction->getStartTimestamp());\n        transaction->setStartTimestamp(adjusted);\n    }\n\n    char timestamp[JSONDATE_LENGTH + 1] = {'\\0'};\n    transaction->getStartTimestamp().toJsonString(timestamp, JSONDATE_LENGTH + 1);\n    payload[\"timestamp\"] = timestamp;\n\n    return doc;\n}\n\nvoid StartTransaction::processConf(JsonObject payload) {\n\n    const char* idTagInfoStatus = payload[\"idTagInfo\"][\"status\"] | \"not specified\";\n    if (!strcmp(idTagInfoStatus, \"Accepted\")) {\n        MO_DBG_INFO(\"Request has been accepted\");\n    } else {\n        MO_DBG_INFO(\"Request has been denied. Reason: %s\", idTagInfoStatus);\n        transaction->setIdTagDeauthorized();\n    }\n\n    int transactionId = payload[\"transactionId\"] | -1;\n    transaction->setTransactionId(transactionId);\n\n    if (payload[\"idTagInfo\"].containsKey(\"parentIdTag\"))\n    {\n        transaction->setParentIdTag(payload[\"idTagInfo\"][\"parentIdTag\"]);\n    }\n\n    transaction->getStartSync().confirm();\n    transaction->commit();\n\n#if MO_ENABLE_LOCAL_AUTH\n    if (auto authService = model.getAuthorizationService()) {\n        authService->notifyAuthorization(transaction->getIdTag(), payload[\"idTagInfo\"]);\n    }\n#endif //MO_ENABLE_LOCAL_AUTH\n}\n\nvoid StartTransaction::processReq(JsonObject payload) {\n\n  /**\n   * Ignore Contents of this Req-message, because this is for debug purposes only\n   */\n\n}\n\nstd::unique_ptr<JsonDoc> StartTransaction::createConf() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2));\n    JsonObject payload = doc->to<JsonObject>();\n\n    JsonObject idTagInfo = payload.createNestedObject(\"idTagInfo\");\n    idTagInfo[\"status\"] = \"Accepted\";\n    static int uniqueTxId = 1000;\n    payload[\"transactionId\"] = uniqueTxId++; //sample data for debug purpose\n\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/StartTransaction.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_STARTTRANSACTION_H\n#define MO_STARTTRANSACTION_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Operations/CiStrings.h>\n#include <MicroOcpp/Model/Metering/SampledValue.h>\n\nnamespace MicroOcpp {\n\nclass Model;\nclass Transaction;\n\nnamespace Ocpp16 {\n\nclass StartTransaction : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    std::shared_ptr<Transaction> transaction;\npublic:\n\n    StartTransaction(Model& model, std::shared_ptr<Transaction> transaction);\n\n    ~StartTransaction();\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/StatusNotification.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/StatusNotification.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Debug.h>\n\n#include <string.h>\n\nnamespace MicroOcpp {\n\n//helper function\nconst char *cstrFromOcppEveState(ChargePointStatus state) {\n    switch (state) {\n        case (ChargePointStatus_Available):\n            return \"Available\";\n        case (ChargePointStatus_Preparing):\n            return \"Preparing\";\n        case (ChargePointStatus_Charging):\n            return \"Charging\";\n        case (ChargePointStatus_SuspendedEVSE):\n            return \"SuspendedEVSE\";\n        case (ChargePointStatus_SuspendedEV):\n            return \"SuspendedEV\";\n        case (ChargePointStatus_Finishing):\n            return \"Finishing\";\n        case (ChargePointStatus_Reserved):\n            return \"Reserved\";\n        case (ChargePointStatus_Unavailable):\n            return \"Unavailable\";\n        case (ChargePointStatus_Faulted):\n            return \"Faulted\";\n#if MO_ENABLE_V201\n        case (ChargePointStatus_Occupied):\n            return \"Occupied\";\n#endif\n        default:\n            MO_DBG_ERR(\"ChargePointStatus not specified\");\n            /* fall through */\n        case (ChargePointStatus_UNDEFINED):\n            return \"UNDEFINED\";\n    }\n}\n\nnamespace Ocpp16 {\n\nStatusNotification::StatusNotification(int connectorId, ChargePointStatus currentStatus, const Timestamp &timestamp, ErrorData errorData)\n        : MemoryManaged(\"v16.Operation.\", \"StatusNotification\"), connectorId(connectorId), currentStatus(currentStatus), timestamp(timestamp), errorData(errorData) {\n    \n    if (currentStatus != ChargePointStatus_UNDEFINED) {\n        MO_DBG_INFO(\"New status: %s (connectorId %d)\", cstrFromOcppEveState(currentStatus), connectorId);\n    }\n}\n\nconst char* StatusNotification::getOperationType(){\n    return \"StatusNotification\";\n}\n\nstd::unique_ptr<JsonDoc> StatusNotification::createReq() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(7) + (JSONDATE_LENGTH + 1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    payload[\"connectorId\"] = connectorId;\n    if (errorData.isError) {\n        if (errorData.errorCode) {\n            payload[\"errorCode\"] = errorData.errorCode;\n        }\n        if (errorData.info) {\n            payload[\"info\"] = errorData.info;\n        }\n        if (errorData.vendorId) {\n            payload[\"vendorId\"] = errorData.vendorId;\n        }\n        if (errorData.vendorErrorCode) {\n            payload[\"vendorErrorCode\"] = errorData.vendorErrorCode;\n        }\n    } else if (currentStatus == ChargePointStatus_UNDEFINED) {\n        MO_DBG_ERR(\"Reporting undefined status\");\n        payload[\"errorCode\"] = \"InternalError\";\n    } else {\n        payload[\"errorCode\"] = \"NoError\";\n    }\n\n    payload[\"status\"] = cstrFromOcppEveState(currentStatus);\n\n    char timestamp_cstr[JSONDATE_LENGTH + 1] = {'\\0'};\n    timestamp.toJsonString(timestamp_cstr, JSONDATE_LENGTH + 1);\n    payload[\"timestamp\"] = timestamp_cstr;\n\n    return doc;\n}\n\nvoid StatusNotification::processConf(JsonObject payload) {\n    /*\n    * Empty payload\n    */\n}\n\n/*\n * For debugging only\n */\nvoid StatusNotification::processReq(JsonObject payload) {\n\n}\n\n/*\n * For debugging only\n */\nstd::unique_ptr<JsonDoc> StatusNotification::createConf(){\n    return createEmptyDocument();\n}\n\n} // namespace Ocpp16\n} // namespace MicroOcpp\n\n#if MO_ENABLE_V201\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nStatusNotification::StatusNotification(EvseId evseId, ChargePointStatus currentStatus, const Timestamp &timestamp)\n        : MemoryManaged(\"v201.Operation.\", \"StatusNotification\"), evseId(evseId), timestamp(timestamp), currentStatus(currentStatus) {\n\n}\n\nconst char* StatusNotification::getOperationType(){\n    return \"StatusNotification\";\n}\n\nstd::unique_ptr<JsonDoc> StatusNotification::createReq() {\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(4) + (JSONDATE_LENGTH + 1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    char timestamp_cstr[JSONDATE_LENGTH + 1] = {'\\0'};\n    timestamp.toJsonString(timestamp_cstr, JSONDATE_LENGTH + 1);\n    payload[\"timestamp\"] = timestamp_cstr;\n    payload[\"connectorStatus\"] = cstrFromOcppEveState(currentStatus);\n    payload[\"evseId\"] = evseId.id;\n    payload[\"connectorId\"] = evseId.id == 0 ? 0 : evseId.connectorId >= 0 ? evseId.connectorId : 1;\n\n    return doc;\n}\n\n\nvoid StatusNotification::processConf(JsonObject payload) {\n    /*\n    * Empty payload\n    */\n}\n\n} // namespace Ocpp201\n} // namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/StatusNotification.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef STATUSNOTIFICATION_H\n#define STATUSNOTIFICATION_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Model/ConnectorBase/ChargePointStatus.h>\n#include <MicroOcpp/Model/ConnectorBase/ChargePointErrorData.h>\n#include <MicroOcpp/Version.h>\n\nnamespace MicroOcpp {\n    \nconst char *cstrFromOcppEveState(ChargePointStatus state);\n\nnamespace Ocpp16 {\n\nclass StatusNotification : public Operation, public MemoryManaged {\nprivate:\n    int connectorId = 1;\n    ChargePointStatus currentStatus = ChargePointStatus_UNDEFINED;\n    Timestamp timestamp;\n    ErrorData errorData;\npublic:\n    StatusNotification(int connectorId, ChargePointStatus currentStatus, const Timestamp &timestamp, ErrorData errorData = nullptr);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    int getConnectorId() {\n        return connectorId;\n    }\n};\n\n} // namespace Ocpp16\n} // namespace MicroOcpp\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Model/ConnectorBase/EvseId.h>\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nclass StatusNotification : public Operation, public MemoryManaged {\nprivate:\n    EvseId evseId;\n    Timestamp timestamp;\n    ChargePointStatus currentStatus = ChargePointStatus_UNDEFINED;\npublic:\n    StatusNotification(EvseId evseId, ChargePointStatus currentStatus, const Timestamp &timestamp);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n};\n\n} // namespace Ocpp201\n} // namespace MicroOcpp\n\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/StopTransaction.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/StopTransaction.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n#include <MicroOcpp/Model/Metering/MeteringService.h>\n#include <MicroOcpp/Model/Metering/MeterValue.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Debug.h>\n#include <MicroOcpp/Version.h>\n\nusing MicroOcpp::Ocpp16::StopTransaction;\nusing MicroOcpp::JsonDoc;\n\nStopTransaction::StopTransaction(Model& model, std::shared_ptr<Transaction> transaction)\n        : MemoryManaged(\"v16.Operation.\", \"StopTransaction\"), model(model), transaction(transaction) {\n\n}\n\nStopTransaction::StopTransaction(Model& model, std::shared_ptr<Transaction> transaction, Vector<std::unique_ptr<MicroOcpp::MeterValue>> transactionData)\n        : MemoryManaged(\"v16.Operation.\", \"StopTransaction\"), model(model), transaction(transaction), transactionData(std::move(transactionData)) {\n\n}\n\nconst char* StopTransaction::getOperationType() {\n    return \"StopTransaction\";\n}\n\nstd::unique_ptr<JsonDoc> StopTransaction::createReq() {\n\n    /*\n     * Adjust timestamps in case they were taken before initial Clock setting\n     */\n    if (transaction->getStopTimestamp() < MIN_TIME) {\n        //Timestamp taken before Clock value defined. Determine timestamp\n        if (transaction->getStopBootNr() == model.getBootNr()) {\n            //possible to calculate real timestamp\n            Timestamp adjusted = model.getClock().adjustPrebootTimestamp(transaction->getStopTimestamp());\n            transaction->setStopTimestamp(adjusted);\n        } else if (transaction->getStartTimestamp() >= MIN_TIME) {\n            MO_DBG_WARN(\"set stopTime = startTime because correct time is not available\");\n            transaction->setStopTimestamp(transaction->getStartTimestamp() + 1); //1s behind startTime to keep order in backend DB\n        } else {\n            MO_DBG_ERR(\"failed to determine StopTx timestamp\");\n            //send invalid value\n        }\n    }\n\n    // if StopTx timestamp is before StartTx timestamp, something probably went wrong. Restore reasonable temporal order\n    if (transaction->getStopTimestamp() < transaction->getStartTimestamp()) {\n        MO_DBG_WARN(\"set stopTime = startTime because stopTime was before startTime\");\n        transaction->setStopTimestamp(transaction->getStartTimestamp() + 1); //1s behind startTime to keep order in backend DB\n    }\n\n    for (auto mv = transactionData.begin(); mv != transactionData.end(); mv++) {\n        if ((*mv)->getTimestamp() < MIN_TIME) {\n            //time off. Try to adjust, otherwise send invalid value\n            if ((*mv)->getReadingContext() == ReadingContext_TransactionBegin) {\n                (*mv)->setTimestamp(transaction->getStartTimestamp());\n            } else if ((*mv)->getReadingContext() == ReadingContext_TransactionEnd) {\n                (*mv)->setTimestamp(transaction->getStopTimestamp());\n            } else {\n                (*mv)->setTimestamp(transaction->getStartTimestamp() + 1);\n            }\n        }\n    }\n\n    auto txDataJson = makeVector<std::unique_ptr<JsonDoc>>(getMemoryTag());\n    size_t txDataJson_size = 0;\n    for (auto mv = transactionData.begin(); mv != transactionData.end(); mv++) {\n        auto mvJson = (*mv)->toJson();\n        if (!mvJson) {\n            return nullptr;\n        }\n        txDataJson_size += mvJson->capacity();\n        txDataJson.emplace_back(std::move(mvJson));\n    }\n\n    auto txDataDoc = initJsonDoc(getMemoryTag(), JSON_ARRAY_SIZE(txDataJson.size()) + txDataJson_size);\n    for (auto mvJson = txDataJson.begin(); mvJson != txDataJson.end(); mvJson++) {\n        txDataDoc.add(**mvJson);\n    }\n\n    auto doc = makeJsonDoc(getMemoryTag(),\n                JSON_OBJECT_SIZE(6) + //total of 6 fields\n                (IDTAG_LEN_MAX + 1) + //stop idTag\n                (JSONDATE_LENGTH + 1) + //timestamp string\n                (REASON_LEN_MAX + 1) + //reason string\n                txDataDoc.capacity());\n    JsonObject payload = doc->to<JsonObject>();\n\n    if (transaction->getStopIdTag() && *transaction->getStopIdTag()) {\n        payload[\"idTag\"] = (char*) transaction->getStopIdTag();\n    }\n\n    payload[\"meterStop\"] = transaction->getMeterStop();\n\n    char timestamp[JSONDATE_LENGTH + 1] = {'\\0'};\n    transaction->getStopTimestamp().toJsonString(timestamp, JSONDATE_LENGTH + 1);\n    payload[\"timestamp\"] = timestamp;\n\n    payload[\"transactionId\"] = transaction->getTransactionId();\n    \n    if (transaction->getStopReason() && *transaction->getStopReason()) {\n        payload[\"reason\"] = (char*) transaction->getStopReason();\n    }\n\n    if (!transactionData.empty()) {\n        payload[\"transactionData\"] = txDataDoc;\n    }\n\n    return doc;\n}\n\nvoid StopTransaction::processConf(JsonObject payload) {\n\n    if (transaction) {\n        transaction->getStopSync().confirm();\n        transaction->commit();\n    }\n\n    MO_DBG_INFO(\"Request has been accepted!\");\n\n#if MO_ENABLE_LOCAL_AUTH\n    if (auto authService = model.getAuthorizationService()) {\n        authService->notifyAuthorization(transaction->getIdTag(), payload[\"idTagInfo\"]);\n    }\n#endif //MO_ENABLE_LOCAL_AUTH\n}\n\nbool StopTransaction::processErr(const char *code, const char *description, JsonObject details) {\n\n    if (transaction) {\n        transaction->getStopSync().confirm(); //no retry behavior for now; consider data \"arrived\" at server\n        transaction->commit();\n    }\n\n    MO_DBG_ERR(\"Server error, data loss!\");\n\n    return false;\n}\n\nvoid StopTransaction::processReq(JsonObject payload) {\n    /**\n     * Ignore Contents of this Req-message, because this is for debug purposes only\n     */\n}\n\nstd::unique_ptr<JsonDoc> StopTransaction::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), 2 * JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n\n    JsonObject idTagInfo = payload.createNestedObject(\"idTagInfo\");\n    idTagInfo[\"status\"] = \"Accepted\";\n\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/StopTransaction.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_STOPTRANSACTION_H\n#define MO_STOPTRANSACTION_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Core/Time.h>\n#include <MicroOcpp/Operations/CiStrings.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nclass SampledValue;\nclass MeterValue;\n\nclass Transaction;\n\nnamespace Ocpp16 {\n\nclass StopTransaction : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    std::shared_ptr<Transaction> transaction;\n    Vector<std::unique_ptr<MeterValue>> transactionData;\npublic:\n\n    StopTransaction(Model& model, std::shared_ptr<Transaction> transaction);\n\n    StopTransaction(Model& model, std::shared_ptr<Transaction> transaction, Vector<std::unique_ptr<MicroOcpp::MeterValue>> transactionData);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    bool processErr(const char *code, const char *description, JsonObject details) override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/TransactionEvent.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Operations/TransactionEvent.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp::Ocpp201;\nusing MicroOcpp::JsonDoc;\n\nTransactionEvent::TransactionEvent(Model& model, TransactionEventData *txEvent)\n        : MemoryManaged(\"v201.Operation.\", \"TransactionEvent\"), model(model), txEvent(txEvent) {\n\n}\n\nconst char* TransactionEvent::getOperationType() {\n    return \"TransactionEvent\";\n}\n\nstd::unique_ptr<JsonDoc> TransactionEvent::createReq() {\n\n    size_t capacity = 0;\n\n    if (txEvent->eventType == TransactionEventData::Type::Ended) {\n        for (size_t i = 0; i < txEvent->transaction->sampledDataTxEnded.size(); i++) {\n            JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); //just measure, create again for serialization later\n            txEvent->transaction->sampledDataTxEnded[i]->toJson(meterValueJson);\n            capacity += meterValueJson.capacity();\n        }\n    }\n\n    for (size_t i = 0; i < txEvent->meterValue.size(); i++) {\n        JsonDoc meterValueJson = initJsonDoc(getMemoryTag()); //just measure, create again for serialization later\n        txEvent->meterValue[i]->toJson(meterValueJson);\n        capacity += meterValueJson.capacity();\n    }\n\n    capacity +=\n            JSON_OBJECT_SIZE(12) + //total of 12 fields\n            JSONDATE_LENGTH + 1 + //timestamp string\n            JSON_OBJECT_SIZE(5) + //transactionInfo\n                MO_TXID_LEN_MAX + 1 + //transactionId\n            MO_IDTOKEN_LEN_MAX + 1; //idToken\n\n    auto doc = makeJsonDoc(getMemoryTag(), capacity);\n    JsonObject payload = doc->to<JsonObject>();\n\n    payload[\"eventType\"] = serializeTransactionEventType(txEvent->eventType);\n\n    char timestamp [JSONDATE_LENGTH + 1];\n    txEvent->timestamp.toJsonString(timestamp, JSONDATE_LENGTH + 1);\n    payload[\"timestamp\"] = timestamp;\n\n    if (serializeTransactionEventTriggerReason(txEvent->triggerReason)) {\n        payload[\"triggerReason\"] = serializeTransactionEventTriggerReason(txEvent->triggerReason);\n    } else {\n        MO_DBG_ERR(\"serialization error\");\n    }\n\n    payload[\"seqNo\"] = txEvent->seqNo;\n\n    if (txEvent->offline) {\n        payload[\"offline\"] = txEvent->offline;\n    }\n\n    if (txEvent->numberOfPhasesUsed >= 0) {\n        payload[\"numberOfPhasesUsed\"] = txEvent->numberOfPhasesUsed;\n    }\n\n    if (txEvent->cableMaxCurrent >= 0) {\n        payload[\"cableMaxCurrent\"] = txEvent->cableMaxCurrent;\n    }\n\n    if (txEvent->reservationId >= 0) {\n        payload[\"reservationId\"] = txEvent->reservationId;\n    }\n\n    JsonObject transactionInfo = payload.createNestedObject(\"transactionInfo\");\n    transactionInfo[\"transactionId\"] = txEvent->transaction->transactionId;\n\n    if (serializeTransactionEventChargingState(txEvent->chargingState)) { // optional\n        transactionInfo[\"chargingState\"] = serializeTransactionEventChargingState(txEvent->chargingState);\n    }\n\n    if (txEvent->transaction->stoppedReason != Transaction::StoppedReason::Local &&\n            serializeTransactionStoppedReason(txEvent->transaction->stoppedReason)) { // optional\n        transactionInfo[\"stoppedReason\"] = serializeTransactionStoppedReason(txEvent->transaction->stoppedReason);\n    }\n\n    if (txEvent->remoteStartId >= 0) {\n        transactionInfo[\"remoteStartId\"] = txEvent->transaction->remoteStartId;\n    }\n\n    if (txEvent->idToken) {\n        JsonObject idToken = payload.createNestedObject(\"idToken\");\n        idToken[\"idToken\"] = txEvent->idToken->get();\n        idToken[\"type\"] = txEvent->idToken->getTypeCstr();\n    }\n\n    if (txEvent->evse.id >= 0) {\n        JsonObject evse = payload.createNestedObject(\"evse\");\n        evse[\"id\"] = txEvent->evse.id;\n        if (txEvent->evse.connectorId >= 0) {\n            evse[\"connectorId\"] = txEvent->evse.connectorId;\n        }\n    }\n\n    if (txEvent->eventType == TransactionEventData::Type::Ended) {\n        for (size_t i = 0; i < txEvent->transaction->sampledDataTxEnded.size(); i++) {\n            JsonDoc meterValueJson = initJsonDoc(getMemoryTag());\n            txEvent->transaction->sampledDataTxEnded[i]->toJson(meterValueJson);\n            payload[\"meterValue\"].add(meterValueJson);\n        }\n    }\n\n    for (size_t i = 0; i < txEvent->meterValue.size(); i++) {\n        JsonDoc meterValueJson = initJsonDoc(getMemoryTag());\n        txEvent->meterValue[i]->toJson(meterValueJson);\n        payload[\"meterValue\"].add(meterValueJson);\n    }\n\n    return doc;\n}\n\nvoid TransactionEvent::processConf(JsonObject payload) {\n\n    if (payload.containsKey(\"idTokenInfo\")) {\n        if (strcmp(payload[\"idTokenInfo\"][\"status\"], \"Accepted\")) {\n            MO_DBG_INFO(\"transaction deAuthorized\");\n            txEvent->transaction->active = false;\n            txEvent->transaction->isDeauthorized = true;\n        }\n    }\n}\n\nvoid TransactionEvent::processReq(JsonObject payload) {\n    /**\n     * Ignore Contents of this Req-message, because this is for debug purposes only\n     */\n}\n\nstd::unique_ptr<JsonDoc> TransactionEvent::createConf() {\n    return createEmptyDocument();\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/TransactionEvent.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_TRANSACTIONEVENT_H\n#define MO_TRANSACTIONEVENT_H\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp/Core/Operation.h>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp201 {\n\nclass TransactionEventData;\n\nclass TransactionEvent : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n    TransactionEventData *txEvent;\n\n    const char *errorCode = nullptr;\npublic:\n\n    TransactionEvent(Model& model, TransactionEventData *txEvent);\n\n    const char* getOperationType() override;\n\n    std::unique_ptr<JsonDoc> createReq() override;\n\n    void processConf(JsonObject payload) override;\n\n    const char *getErrorCode() override {return errorCode;}\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n};\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n#endif // MO_ENABLE_V201\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/TriggerMessage.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/TriggerMessage.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Model/Metering/MeteringService.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::TriggerMessage;\nusing MicroOcpp::JsonDoc;\n\nTriggerMessage::TriggerMessage(Context& context) : MemoryManaged(\"v16.Operation.\", \"TriggerMessage\"), context(context) {\n\n}\n\nconst char* TriggerMessage::getOperationType(){\n    return \"TriggerMessage\";\n}\n\nvoid TriggerMessage::processReq(JsonObject payload) {\n\n    const char *requestedMessage = payload[\"requestedMessage\"] | \"Invalid\";\n    const int connectorId = payload[\"connectorId\"] | -1;\n\n    MO_DBG_INFO(\"Execute for message type %s, connectorId = %i\", requestedMessage, connectorId);\n\n    statusMessage = \"Rejected\";\n\n    if (!strcmp(requestedMessage, \"MeterValues\")) {\n        if (auto mService = context.getModel().getMeteringService()) {\n            if (connectorId < 0) {\n                auto nConnectors = mService->getNumConnectors();\n                for (decltype(nConnectors) cId = 0; cId < nConnectors; cId++) {\n                    if (auto meterValues = mService->takeTriggeredMeterValues(cId)) {\n                        context.getRequestQueue().sendRequestPreBoot(std::move(meterValues));\n                        statusMessage = \"Accepted\";\n                    }\n                }\n            } else if (connectorId < mService->getNumConnectors()) {\n                if (auto meterValues = mService->takeTriggeredMeterValues(connectorId)) {\n                    context.getRequestQueue().sendRequestPreBoot(std::move(meterValues));\n                    statusMessage = \"Accepted\";\n                }\n            } else {\n                errorCode = \"PropertyConstraintViolation\";\n            }\n        }\n    } else if (!strcmp(requestedMessage, \"StatusNotification\")) {\n        unsigned int cIdRangeBegin = 0, cIdRangeEnd = 0;\n        if (connectorId < 0) {\n            cIdRangeEnd = context.getModel().getNumConnectors();\n        } else if ((unsigned int) connectorId < context.getModel().getNumConnectors()) {\n            cIdRangeBegin = connectorId;\n            cIdRangeEnd = connectorId + 1;\n        } else {\n            errorCode = \"PropertyConstraintViolation\";\n        }\n\n        for (auto i = cIdRangeBegin; i < cIdRangeEnd; i++) {\n            auto connector = context.getModel().getConnector(i);\n            if (connector->triggerStatusNotification()) {\n                statusMessage = \"Accepted\";\n            }\n        }\n    } else {\n        auto msg = context.getOperationRegistry().deserializeOperation(requestedMessage);\n        if (msg) {\n            context.getRequestQueue().sendRequestPreBoot(std::move(msg));\n            statusMessage = \"Accepted\";\n        } else {\n            statusMessage = \"NotImplemented\";\n        }\n    }\n}\n\nstd::unique_ptr<JsonDoc> TriggerMessage::createConf(){\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = statusMessage;\n    return doc;\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/TriggerMessage.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_TRIGGERMESSAGE_H\n#define MO_TRIGGERMESSAGE_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Memory.h>\n\nnamespace MicroOcpp {\n\nclass Context;\n\nnamespace Ocpp16 {\n\nclass TriggerMessage : public Operation, public MemoryManaged {\nprivate:\n    Context& context;\n    const char *statusMessage = nullptr;\n\n    const char *errorCode = nullptr;\npublic:\n    TriggerMessage(Context& context);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/UnlockConnector.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/UnlockConnector.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::UnlockConnector;\nusing MicroOcpp::JsonDoc;\n\nUnlockConnector::UnlockConnector(Model& model) : MemoryManaged(\"v16.Operation.\", \"UnlockConnector\"), model(model) {\n  \n}\n\nconst char* UnlockConnector::getOperationType(){\n    return \"UnlockConnector\";\n}\n\nvoid UnlockConnector::processReq(JsonObject payload) {\n\n#if MO_ENABLE_CONNECTOR_LOCK\n    {\n        auto connectorId = payload[\"connectorId\"] | -1;\n\n        auto connector = model.getConnector(connectorId);\n\n        if (!connector) {\n            // NotSupported\n            return;\n        }\n\n        unlockConnector = connector->getOnUnlockConnector();\n\n        if (!unlockConnector) {\n            // NotSupported\n            return;\n        }\n\n        connector->endTransaction(nullptr, \"UnlockCommand\");\n        connector->updateTxNotification(TxNotification_RemoteStop);\n\n        cbUnlockResult = unlockConnector();\n\n        timerStart = mocpp_tick_ms();\n    }\n#endif //MO_ENABLE_CONNECTOR_LOCK\n}\n\nstd::unique_ptr<JsonDoc> UnlockConnector::createConf() {\n\n    const char *status = \"NotSupported\";\n\n#if MO_ENABLE_CONNECTOR_LOCK\n    if (unlockConnector) {\n\n        if (mocpp_tick_ms() - timerStart < MO_UNLOCK_TIMEOUT) {\n            //do poll and if more time is needed, delay creation of conf msg\n\n            if (cbUnlockResult == UnlockConnectorResult_Pending) {\n                cbUnlockResult = unlockConnector();\n                if (cbUnlockResult == UnlockConnectorResult_Pending) {\n                    return nullptr; //no result yet - delay confirmation response\n                }\n            }\n        }\n\n        if (cbUnlockResult == UnlockConnectorResult_Unlocked) {\n            status = \"Unlocked\";\n        } else {\n            status = \"UnlockFailed\";\n        }\n    }\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = status;\n    return doc;\n}\n\n\n#if MO_ENABLE_V201\n#if MO_ENABLE_CONNECTOR_LOCK\n\n#include <MicroOcpp/Model/RemoteControl/RemoteControlService.h>\n\nnamespace MicroOcpp {\nnamespace Ocpp201 {\n\nUnlockConnector::UnlockConnector(RemoteControlService& rcService) : MemoryManaged(\"v201.Operation.UnlockConnector\"), rcService(rcService) {\n\n}\n\nconst char* UnlockConnector::getOperationType(){\n    return \"UnlockConnector\";\n}\n\nvoid UnlockConnector::processReq(JsonObject payload) {\n\n    int evseId = payload[\"evseId\"] | -1;\n    int connectorId = payload[\"connectorId\"] | -1;\n\n    if (evseId < 1 || evseId >= MO_NUM_EVSEID || connectorId < 1) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    if (connectorId != 1) {\n        status = UnlockStatus_UnknownConnector;\n        return;\n    }\n\n    rcEvse = rcService.getEvse(evseId);\n    if (!rcEvse) {\n        status = UnlockStatus_UnlockFailed;\n        return;\n    }\n\n    status = rcEvse->unlockConnector();\n    timerStart = mocpp_tick_ms();\n}\n\nstd::unique_ptr<JsonDoc> UnlockConnector::createConf() {\n\n    if (rcEvse && status == UnlockStatus_PENDING && mocpp_tick_ms() - timerStart < MO_UNLOCK_TIMEOUT) {\n        status = rcEvse->unlockConnector();\n\n        if (status == UnlockStatus_PENDING) {\n            return nullptr; //no result yet - delay confirmation response\n        }\n    }\n\n    const char *statusStr = \"\";\n    switch (status) {\n        case UnlockStatus_Unlocked:\n            statusStr = \"Unlocked\";\n            break;\n        case UnlockStatus_UnlockFailed:\n            statusStr = \"UnlockFailed\";\n            break;\n        case UnlockStatus_OngoingAuthorizedTransaction:\n            statusStr = \"OngoingAuthorizedTransaction\";\n            break;\n        case UnlockStatus_UnknownConnector:\n            statusStr = \"UnknownConnector\";\n            break;\n        case UnlockStatus_PENDING:\n            MO_DBG_ERR(\"UnlockConnector timeout\");\n            statusStr = \"UnlockFailed\";\n            break;\n    }\n\n    auto doc = makeJsonDoc(getMemoryTag(), JSON_OBJECT_SIZE(1));\n    JsonObject payload = doc->to<JsonObject>();\n    payload[\"status\"] = statusStr;\n    return doc;\n}\n\n} // namespace Ocpp201\n} // namespace MicroOcpp\n\n#endif //MO_ENABLE_CONNECTOR_LOCK\n#endif //MO_ENABLE_V201\n"
  },
  {
    "path": "src/MicroOcpp/Operations/UnlockConnector.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef UNLOCKCONNECTOR_H\n#define UNLOCKCONNECTOR_H\n\n#include <MicroOcpp/Version.h>\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h>\n#include <functional>\n\nnamespace MicroOcpp {\n\nclass Model;\n\nnamespace Ocpp16 {\n\nclass UnlockConnector : public Operation, public MemoryManaged {\nprivate:\n    Model& model;\n\n#if MO_ENABLE_CONNECTOR_LOCK\n    std::function<UnlockConnectorResult ()> unlockConnector;\n    UnlockConnectorResult cbUnlockResult;\n    unsigned long timerStart = 0; //for timeout\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n    const char *errorCode = nullptr;\npublic:\n    UnlockConnector(Model& model);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#if MO_ENABLE_V201\n#if MO_ENABLE_CONNECTOR_LOCK\n\n#include <MicroOcpp/Model/RemoteControl/RemoteControlDefs.h>\n\nnamespace MicroOcpp {\n\nclass RemoteControlService;\nclass RemoteControlServiceEvse;\n\nnamespace Ocpp201 {\n\nclass UnlockConnector : public Operation, public MemoryManaged {\nprivate:\n    RemoteControlService& rcService;\n    RemoteControlServiceEvse *rcEvse = nullptr;\n\n    UnlockStatus status;\n    unsigned long timerStart = 0; //for timeout\n\n    const char *errorCode = nullptr;\npublic:\n    UnlockConnector(RemoteControlService& rcService);\n\n    const char* getOperationType() override;\n\n    void processReq(JsonObject payload) override;\n\n    std::unique_ptr<JsonDoc> createConf() override;\n\n    const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp201\n} //end namespace MicroOcpp\n\n#endif //MO_ENABLE_CONNECTOR_LOCK\n#endif //MO_ENABLE_V201\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Operations/UpdateFirmware.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Operations/UpdateFirmware.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>\n#include <MicroOcpp/Debug.h>\n\nusing MicroOcpp::Ocpp16::UpdateFirmware;\nusing MicroOcpp::JsonDoc;\n\nUpdateFirmware::UpdateFirmware(FirmwareService& fwService) : MemoryManaged(\"v16.Operation.\", \"UpdateFirmware\"), fwService(fwService) {\n\n}\n\nvoid UpdateFirmware::processReq(JsonObject payload) {\n\n    const char *location = payload[\"location\"] | \"\";\n    //check location URL. Maybe introduce Same-Origin-Policy?\n    if (!*location) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n    \n    int retries = payload[\"retries\"] | 1;\n    int retryInterval = payload[\"retryInterval\"] | 180;\n    if (retries < 0 || retryInterval < 0) {\n        errorCode = \"PropertyConstraintViolation\";\n        return;\n    }\n\n    //check the integrity of retrieveDate\n    if (!payload.containsKey(\"retrieveDate\")) {\n        errorCode = \"FormationViolation\";\n        return;\n    }\n\n    Timestamp retrieveDate;\n    if (!retrieveDate.setTime(payload[\"retrieveDate\"] | \"Invalid\")) {\n        errorCode = \"PropertyConstraintViolation\";\n        MO_DBG_WARN(\"bad time format\");\n        return;\n    }\n    \n    fwService.scheduleFirmwareUpdate(location, retrieveDate, (unsigned int) retries, (unsigned int) retryInterval);\n}\n\nstd::unique_ptr<JsonDoc> UpdateFirmware::createConf(){\n    return createEmptyDocument();\n}\n"
  },
  {
    "path": "src/MicroOcpp/Operations/UpdateFirmware.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_UPDATEFIRMWARE_H\n#define MO_UPDATEFIRMWARE_H\n\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Time.h>\n\nnamespace MicroOcpp {\n\nclass FirmwareService;\n\nnamespace Ocpp16 {\n\nclass UpdateFirmware : public Operation, public MemoryManaged {\nprivate:\n  FirmwareService& fwService;\n\n  const char *errorCode = nullptr;\npublic:\n  UpdateFirmware(FirmwareService& fwService);\n\n  const char* getOperationType() override {return \"UpdateFirmware\";}\n\n  void processReq(JsonObject payload) override;\n\n  std::unique_ptr<JsonDoc> createConf() override;\n\n  const char *getErrorCode() override {return errorCode;}\n};\n\n} //end namespace Ocpp16\n} //end namespace MicroOcpp\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Platform.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Platform.h>\n\n#ifdef MO_CUSTOM_CONSOLE\n\nchar _mo_console_msg_buf [MO_CUSTOM_CONSOLE_MAXMSGSIZE];\n\nnamespace MicroOcpp {\nvoid (*mocpp_console_out_impl)(const char *msg) = nullptr;\n}\n\nvoid _mo_console_out(const char *msg) {\n    if (MicroOcpp::mocpp_console_out_impl) {\n        MicroOcpp::mocpp_console_out_impl(msg);\n    }\n}\n\nvoid mocpp_set_console_out(void (*console_out)(const char *msg)) {\n    MicroOcpp::mocpp_console_out_impl = console_out;\n    if (console_out) {\n        console_out(\"[OCPP] console initialized\\n\");\n    }\n}\n\n#endif\n\n#ifdef MO_CUSTOM_TIMER\nunsigned long (*mocpp_tick_ms_impl)() = nullptr;\n\nvoid mocpp_set_timer(unsigned long (*get_ms)()) {\n    mocpp_tick_ms_impl = get_ms;\n}\n\nunsigned long mocpp_tick_ms_custom() {\n    if (mocpp_tick_ms_impl) {\n        return mocpp_tick_ms_impl();\n    } else {\n        return 0;\n    }\n}\n#else\n\n#if MO_PLATFORM == MO_PLATFORM_ESPIDF\n#include \"freertos/FreeRTOS.h\"\n#include \"freertos/task.h\"\n\nnamespace MicroOcpp {\n\ndecltype(xTaskGetTickCount()) mocpp_ticks_count = 0;\nunsigned long mocpp_millis_count = 0;\n\n}\n\nunsigned long mocpp_tick_ms_espidf() {\n    auto ticks_now = xTaskGetTickCount();\n    MicroOcpp::mocpp_millis_count += ((ticks_now - MicroOcpp::mocpp_ticks_count) * 1000UL) / configTICK_RATE_HZ;\n    MicroOcpp::mocpp_ticks_count = ticks_now;\n    return MicroOcpp::mocpp_millis_count;\n}\n\n#elif MO_PLATFORM == MO_PLATFORM_UNIX\n#include <chrono>\n\nnamespace MicroOcpp {\n\nstd::chrono::steady_clock::time_point clock_reference;\nbool clock_initialized = false;\n\n}\n\nunsigned long mocpp_tick_ms_unix() {\n    if (!MicroOcpp::clock_initialized) {\n        MicroOcpp::clock_reference = std::chrono::steady_clock::now();\n        MicroOcpp::clock_initialized = true;\n    }\n    std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(\n        std::chrono::steady_clock::now() - MicroOcpp::clock_reference);\n    return (unsigned long) ms.count();\n}\n#endif\n#endif\n\n#ifdef MO_CUSTOM_RNG\nuint32_t (*mocpp_rng_impl)() = nullptr;\n\nvoid mocpp_set_rng(uint32_t (*rng)()) {\n    mocpp_rng_impl = rng;\n}\n\nuint32_t mocpp_rng_custom(void) {\n    if (mocpp_rng_impl) {\n        return mocpp_rng_impl();\n    } else {\n        return 0;\n    }\n}\n#else\n\n// Time-based Pseudo RNG.\n// Contains internal state which is mixed with the current timestamp\n// each time it is called. Then this is passed through a multiply-with-carry\n// PRNG operation to get a pseudo-random number.\nuint32_t mocpp_time_based_prng(void) {\n    static uint32_t prng_state = 1;\n    uint32_t entropy = mocpp_tick_ms();\n    prng_state = (prng_state ^ entropy)*1664525U + 1013904223U; // assuming complement-2 integers and non-signaling overflow\n    return prng_state;\n}\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Platform.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_PLATFORM_H\n#define MO_PLATFORM_H\n\n#include <stdint.h>\n\n#define MO_PLATFORM_NONE    0\n#define MO_PLATFORM_ARDUINO 1\n#define MO_PLATFORM_ESPIDF  2\n#define MO_PLATFORM_UNIX    3\n\n#ifndef MO_PLATFORM\n#define MO_PLATFORM MO_PLATFORM_ARDUINO\n#endif\n\n#ifdef __cplusplus\n#define MO_EXTERN_C extern \"C\"\n#else\n#define MO_EXTERN_C\n#endif\n\n#if MO_PLATFORM == MO_PLATFORM_NONE\n#ifndef MO_CUSTOM_CONSOLE\n#define MO_CUSTOM_CONSOLE\n#endif\n#ifndef MO_CUSTOM_TIMER\n#define MO_CUSTOM_TIMER\n#endif\n#endif\n\n#ifdef MO_CUSTOM_CONSOLE\n#include <stdio.h>\n\n#ifndef MO_CUSTOM_CONSOLE_MAXMSGSIZE\n#define MO_CUSTOM_CONSOLE_MAXMSGSIZE 256\n#endif\n\nextern char _mo_console_msg_buf [MO_CUSTOM_CONSOLE_MAXMSGSIZE]; //define msg_buf in data section to save memory (see https://github.com/matth-x/MicroOcpp/pull/304)\nMO_EXTERN_C void _mo_console_out(const char *msg);\n\nMO_EXTERN_C void mocpp_set_console_out(void (*console_out)(const char *msg));\n\n#define MO_CONSOLE_PRINTF(X, ...) \\\n            do { \\\n                auto _mo_ret = snprintf(_mo_console_msg_buf, MO_CUSTOM_CONSOLE_MAXMSGSIZE, X, ##__VA_ARGS__); \\\n                if (_mo_ret < 0 || _mo_ret >= MO_CUSTOM_CONSOLE_MAXMSGSIZE) { \\\n                    sprintf(_mo_console_msg_buf + MO_CUSTOM_CONSOLE_MAXMSGSIZE - 7, \" [...]\"); \\\n                } \\\n                _mo_console_out(_mo_console_msg_buf); \\\n            } while (0)\n#else\n#define mocpp_set_console_out(X) \\\n            do { \\\n                X(\"[OCPP] CONSOLE ERROR: mocpp_set_console_out ignored if MO_CUSTOM_CONSOLE \" \\\n                  \"not defined\\n\"); \\\n                char msg [196]; \\\n                snprintf(msg, 196, \"     > see %s:%i\",__FILE__,__LINE__); \\\n                X(msg); \\\n                X(\"\\n     > see MicroOcpp/Platform.h\\n\"); \\\n            } while (0)\n\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO\n#include <Arduino.h>\n#ifndef MO_USE_SERIAL\n#define MO_USE_SERIAL Serial\n#endif\n\n#define MO_CONSOLE_PRINTF(X, ...) MO_USE_SERIAL.printf_P(PSTR(X), ##__VA_ARGS__)\n#elif MO_PLATFORM == MO_PLATFORM_ESPIDF\n#include \"esp_log.h\"\n\n#define MO_CONSOLE_PRINTF(X, ...) esp_log_write(ESP_LOG_INFO, \"MicroOcpp\", X, ##__VA_ARGS__)\n#elif MO_PLATFORM == MO_PLATFORM_UNIX\n#include <stdio.h>\n\n#define MO_CONSOLE_PRINTF(X, ...) printf(X, ##__VA_ARGS__)\n#endif\n#endif\n\n#ifdef MO_CUSTOM_TIMER\nMO_EXTERN_C void mocpp_set_timer(unsigned long (*get_ms)());\n\nMO_EXTERN_C unsigned long mocpp_tick_ms_custom();\n#define mocpp_tick_ms mocpp_tick_ms_custom\n#else\n\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO\n#include <Arduino.h>\n#define mocpp_tick_ms millis\n#elif MO_PLATFORM == MO_PLATFORM_ESPIDF\nMO_EXTERN_C unsigned long mocpp_tick_ms_espidf();\n#define mocpp_tick_ms mocpp_tick_ms_espidf\n#elif MO_PLATFORM == MO_PLATFORM_UNIX\nMO_EXTERN_C unsigned long mocpp_tick_ms_unix();\n#define mocpp_tick_ms mocpp_tick_ms_unix\n#endif\n#endif\n\n#ifdef MO_CUSTOM_RNG\nMO_EXTERN_C void mocpp_set_rng(uint32_t (*rng)());\nMO_EXTERN_C uint32_t mocpp_rng_custom();\n#define mocpp_rng mocpp_rng_custom\n#else\nMO_EXTERN_C uint32_t mocpp_time_based_prng(void);\n#define mocpp_rng mocpp_time_based_prng\n#endif\n\n#ifndef MO_MAX_JSON_CAPACITY\n#if MO_PLATFORM == MO_PLATFORM_UNIX\n#define MO_MAX_JSON_CAPACITY 16384\n#else\n#define MO_MAX_JSON_CAPACITY 4096\n#endif\n#endif\n\n#ifndef MO_ENABLE_MBEDTLS\n#define MO_ENABLE_MBEDTLS 0\n#endif\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp/Version.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_VERSION_H\n#define MO_VERSION_H\n\n/*\n * Version specification of MicroOcpp library (not related with the OCPP version)\n */\n#define MO_VERSION \"1.2.0\"\n\n/*\n * Enable OCPP 2.0.1 support. If enabled, library can be initialized with both v1.6 and v2.0.1. The choice\n * of the protocol is done dynamically during initialization\n */\n#ifndef MO_ENABLE_V201\n#define MO_ENABLE_V201 0\n#endif\n\n#ifdef __cplusplus\n\nnamespace MicroOcpp {\n\n/*\n * OCPP version type, defined in Model\n */\nstruct ProtocolVersion {\n    const int major, minor, patch;\n    ProtocolVersion(int major = 1, int minor = 6, int patch = 0) : major(major), minor(minor), patch(patch) { }\n};\n\n}\n\n#endif //__cplusplus\n\n// Certificate Management (UCs M03 - M05). Works with OCPP 1.6 and 2.0.1\n#ifndef MO_ENABLE_CERT_MGMT\n#define MO_ENABLE_CERT_MGMT MO_ENABLE_V201\n#endif\n\n// Reservations\n#ifndef MO_ENABLE_RESERVATION\n#define MO_ENABLE_RESERVATION 1\n#endif\n\n// Local Authorization, i.e. feature profile LocalAuthListManagement\n#ifndef MO_ENABLE_LOCAL_AUTH\n#define MO_ENABLE_LOCAL_AUTH 1\n#endif\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include \"MicroOcpp.h\"\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Metering/MeteringService.h>\n#include <MicroOcpp/Model/SmartCharging/SmartChargingService.h>\n#include <MicroOcpp/Model/ConnectorBase/ConnectorsCommon.h>\n#include <MicroOcpp/Model/Heartbeat/HeartbeatService.h>\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>\n#include <MicroOcpp/Model/Diagnostics/DiagnosticsService.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n#include <MicroOcpp/Model/Reservation/ReservationService.h>\n#include <MicroOcpp/Model/Boot/BootService.h>\n#include <MicroOcpp/Model/Reset/ResetService.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n#include <MicroOcpp/Model/Certificates/CertificateService.h>\n#include <MicroOcpp/Model/Certificates/CertificateMbedTLS.h>\n#include <MicroOcpp/Model/Availability/AvailabilityService.h>\n#include <MicroOcpp/Model/RemoteControl/RemoteControlService.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/OperationRegistry.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Ftp.h>\n#include <MicroOcpp/Core/FtpMbedTLS.h>\n\n#include <MicroOcpp/Operations/Authorize.h>\n#include <MicroOcpp/Operations/StartTransaction.h>\n#include <MicroOcpp/Operations/StopTransaction.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n\n#include <MicroOcpp/Debug.h>\n\nnamespace MicroOcpp {\nnamespace Facade {\n\n#ifndef MO_CUSTOM_WS\nWebSocketsClient *webSocket {nullptr};\nConnection *connection {nullptr};\n#endif\n\nContext *context {nullptr};\nstd::shared_ptr<FilesystemAdapter> filesystem;\n\n#ifndef MO_NUMCONNECTORS\n#define MO_NUMCONNECTORS 2\n#endif\n\n#define OCPP_ID_OF_CP 0\n#define OCPP_ID_OF_CONNECTOR 1\n\n} //end namespace MicroOcpp::Facade\n} //end namespace MicroOcpp\n\n#if MO_ENABLE_HEAP_PROFILER\n#ifndef MO_HEAP_PROFILER_EXTERNAL_CONTROL\n#define MO_HEAP_PROFILER_EXTERNAL_CONTROL 0 //enable if you want to manually reset the heap profiler (e.g. for keeping stats over multiple MO lifecycles)\n#endif\n#endif\n\nusing namespace MicroOcpp;\nusing namespace MicroOcpp::Facade;\nusing namespace MicroOcpp::Ocpp16;\n\n#ifndef MO_CUSTOM_WS\nvoid mocpp_initialize(const char *backendUrl, const char *chargeBoxId, const char *chargePointModel, const char *chargePointVendor, FilesystemOpt fsOpt, const char *password, const char *CA_cert, bool autoRecover) {\n    if (context) {\n        MO_DBG_WARN(\"already initialized. To reinit, call mocpp_deinitialize() before\");\n        return;\n    }\n\n    if (!backendUrl || !chargePointModel || !chargePointVendor) {\n        MO_DBG_ERR(\"invalid args\");\n        return;\n    }\n\n    if (!chargeBoxId) {\n        chargeBoxId = \"\";\n    }\n\n    /*\n     * parse backendUrl so that it suits the links2004/arduinoWebSockets interface\n     */\n    auto url = makeString(\"MicroOcpp.cpp\", backendUrl);\n\n    //tolower protocol specifier\n    for (auto c = url.begin(); *c != ':' && c != url.end(); c++) {\n        *c = tolower(*c);\n    }\n\n    bool isTLS = true;\n    if (!strncmp(url.c_str(),\"wss://\",strlen(\"wss://\"))) {\n        isTLS = true;\n    } else if (!strncmp(url.c_str(),\"ws://\",strlen(\"ws://\"))) {\n        isTLS = false;\n    } else {\n        MO_DBG_ERR(\"only ws:// and wss:// supported\");\n        return;\n    }\n\n    //parse host, port\n    auto host_port_path = url.substr(url.find_first_of(\"://\") + strlen(\"://\"));\n    auto host_port = host_port_path.substr(0, host_port_path.find_first_of('/'));\n    auto path = host_port_path.substr(host_port.length());\n    auto host = host_port.substr(0, host_port.find_first_of(':'));\n    if (host.empty()) {\n        MO_DBG_ERR(\"could not parse host: %s\", url.c_str());\n        return;\n    }\n    uint16_t port = 0;\n    auto port_str = host_port.substr(host.length());\n    if (port_str.empty()) {\n        port = isTLS ? 443U : 80U;\n    } else {\n        //skip leading ':'\n        port_str = port_str.substr(1);\n        for (auto c = port_str.begin(); c != port_str.end(); c++) {\n            if (*c < '0' || *c > '9') {\n                MO_DBG_ERR(\"could not parse port: %s\", url.c_str());\n                return; \n            }\n            auto p = port * 10U + (*c - '0');\n            if (p < port) {\n                MO_DBG_ERR(\"could not parse port (overflow): %s\", url.c_str());\n                return;\n            }\n            port = p;\n        }\n    }\n\n    if (path.empty()) {\n        path = \"/\";\n    }\n\n    if ((!*chargeBoxId) == '\\0') {\n        if (path.back() != '/') {\n            path += '/';\n        }\n\n        path += chargeBoxId;\n    }\n\n    MO_DBG_INFO(\"connecting to %s -- (host: %s, port: %u, path: %s)\", url.c_str(), host.c_str(), port, path.c_str());\n\n    if (!webSocket)\n        webSocket = new WebSocketsClient();\n\n    if (isTLS) {\n        // server address, port, path and TLS certificate\n        webSocket->beginSslWithCA(host.c_str(), port, path.c_str(), CA_cert, \"ocpp1.6\");\n    } else {\n        // server address, port, path\n        webSocket->begin(host.c_str(), port, path.c_str(), \"ocpp1.6\");\n    }\n\n    // try ever 5000 again if connection has failed\n    webSocket->setReconnectInterval(5000);\n\n    // start heartbeat (optional)\n    // ping server every 15000 ms\n    // expect pong from server within 3000 ms\n    // consider connection disconnected if pong is not received 2 times\n    webSocket->enableHeartbeat(15000, 3000, 2); //comment this one out to for specific OCPP servers\n\n    // add authentication data (optional)\n    if (password && strlen(password) + strlen(chargeBoxId) >= 4) {\n        webSocket->setAuthorization(chargeBoxId, password);\n    }\n\n    delete connection;\n    connection = new EspWiFi::WSClient(webSocket);\n\n    mocpp_initialize(*connection, ChargerCredentials(chargePointModel, chargePointVendor), makeDefaultFilesystemAdapter(fsOpt), autoRecover);\n}\n#endif\n\nChargerCredentials::ChargerCredentials(const char *cpModel, const char *cpVendor, const char *fWv, const char *cpSNr, const char *meterSNr, const char *meterType, const char *cbSNr, const char *iccid, const char *imsi) {\n\n    StaticJsonDocument<512> creds;\n    if (cbSNr)\n        creds[\"chargeBoxSerialNumber\"] = cbSNr;\n    if (cpModel)\n        creds[\"chargePointModel\"] = cpModel;\n    if (cpSNr)\n        creds[\"chargePointSerialNumber\"] = cpSNr;\n    if (cpVendor)\n        creds[\"chargePointVendor\"] = cpVendor;\n    if (fWv)\n        creds[\"firmwareVersion\"] = fWv;\n    if (iccid)\n        creds[\"iccid\"] = iccid;\n    if (imsi)\n        creds[\"imsi\"] = imsi;\n    if (meterSNr)\n        creds[\"meterSerialNumber\"] = meterSNr;\n    if (meterType)\n        creds[\"meterType\"] = meterType;\n    \n    if (creds.overflowed()) {\n        MO_DBG_ERR(\"Charger Credentials too long\");\n    }\n\n    size_t written = serializeJson(creds, payload, 512);\n\n    if (written < 2) {\n        MO_DBG_ERR(\"Charger Credentials could not be written\");\n        sprintf(payload, \"{}\");\n    }\n}\n\nChargerCredentials ChargerCredentials::v201(const char *cpModel, const char *cpVendor, const char *fWv, const char *cpSNr, const char *meterSNr, const char *meterType, const char *cbSNr, const char *iccid, const char *imsi) {\n\n    ChargerCredentials res;\n\n    StaticJsonDocument<512> creds;\n    if (cpSNr)\n        creds[\"serialNumber\"] = cpSNr;\n    if (cpModel)\n        creds[\"model\"] = cpModel;\n    if (cpVendor)\n        creds[\"vendorName\"] = cpVendor;\n    if (fWv)\n        creds[\"firmwareVersion\"] = fWv;\n    if (iccid)\n        creds[\"modem\"][\"iccid\"] = iccid;\n    if (imsi)\n        creds[\"modem\"][\"imsi\"] = imsi;\n\n    if (creds.overflowed()) {\n        MO_DBG_ERR(\"Charger Credentials too long\");\n    }\n\n    size_t written = serializeJson(creds, res.payload, 512);\n\n    if (written < 2) {\n        MO_DBG_ERR(\"Charger Credentials could not be written\");\n        sprintf(res.payload, \"{}\");\n    }\n\n    return res;\n}\n\nvoid mocpp_initialize(Connection& connection, const char *bootNotificationCredentials, std::shared_ptr<FilesystemAdapter> fs, bool autoRecover, MicroOcpp::ProtocolVersion version) {\n    if (context) {\n        MO_DBG_WARN(\"already initialized. To reinit, call mocpp_deinitialize() before\");\n        return;\n    }\n\n    MO_DBG_DEBUG(\"initialize OCPP\");\n\n    filesystem = fs;\n    MO_DBG_DEBUG(\"filesystem %s\", filesystem ? \"loaded\" : \"deactivated\");\n\n    BootStats bootstats;\n    BootService::loadBootStats(filesystem, bootstats);\n\n    if (autoRecover && bootstats.getBootFailureCount() > 3) {\n        BootService::recover(filesystem, bootstats);\n        bootstats = BootStats();\n    }\n\n    BootService::migrate(filesystem, bootstats);\n\n    bootstats.bootNr++; //assign new boot number to this run\n    BootService::storeBootStats(filesystem, bootstats);\n\n    configuration_init(filesystem); //call before each other library call\n\n    context = new Context(connection, filesystem, bootstats.bootNr, version);\n\n#if MO_ENABLE_MBEDTLS\n    context->setFtpClient(makeFtpClientMbedTLS());\n#endif //MO_ENABLE_MBEDTLS\n\n    auto& model = context->getModel();\n\n    model.setBootService(std::unique_ptr<BootService>(\n        new BootService(*context, filesystem)));\n\n#if MO_ENABLE_V201\n    if (version.major == 2) {\n        model.setAvailabilityService(std::unique_ptr<AvailabilityService>(\n            new AvailabilityService(*context, MO_NUM_EVSEID)));\n        model.setVariableService(std::unique_ptr<VariableService>(\n            new VariableService(*context, filesystem)));\n        model.setTransactionService(std::unique_ptr<TransactionService>(\n            new TransactionService(*context, filesystem, MO_NUM_EVSEID)));\n        model.setRemoteControlService(std::unique_ptr<RemoteControlService>(\n            new RemoteControlService(*context, MO_NUM_EVSEID)));\n        model.setResetServiceV201(std::unique_ptr<Ocpp201::ResetService>(\n            new Ocpp201::ResetService(*context)));\n    } else\n#endif\n    {\n        model.setTransactionStore(std::unique_ptr<TransactionStore>(\n            new TransactionStore(MO_NUMCONNECTORS, filesystem)));\n        model.setConnectorsCommon(std::unique_ptr<ConnectorsCommon>(\n            new ConnectorsCommon(*context, MO_NUMCONNECTORS, filesystem)));\n        auto connectors = makeVector<std::unique_ptr<Connector>>(\"v16.ConnectorBase.Connector\");\n        for (unsigned int connectorId = 0; connectorId < MO_NUMCONNECTORS; connectorId++) {\n            connectors.emplace_back(new Connector(*context, filesystem, connectorId));\n        }\n        model.setConnectors(std::move(connectors));\n\n#if MO_ENABLE_LOCAL_AUTH\n        model.setAuthorizationService(std::unique_ptr<AuthorizationService>(\n            new AuthorizationService(*context, filesystem)));\n#endif //MO_ENABLE_LOCAL_AUTH\n\n#if MO_ENABLE_RESERVATION\n        model.setReservationService(std::unique_ptr<ReservationService>(\n            new ReservationService(*context, MO_NUMCONNECTORS)));\n#endif\n\n        model.setResetService(std::unique_ptr<ResetService>(\n            new ResetService(*context)));\n    }\n\n    model.setHeartbeatService(std::unique_ptr<HeartbeatService>(\n        new HeartbeatService(*context)));\n\n#if MO_ENABLE_CERT_MGMT && MO_ENABLE_CERT_STORE_MBEDTLS\n    std::unique_ptr<CertificateStore> certStore = makeCertificateStoreMbedTLS(filesystem);\n    if (certStore) {\n        model.setCertificateService(std::unique_ptr<CertificateService>(\n            new CertificateService(*context)));\n    }\n    if (certStore && model.getCertificateService()) {\n        model.getCertificateService()->setCertificateStore(std::move(certStore));\n    }\n#endif\n\n#if !defined(MO_CUSTOM_UPDATER)\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS\n    model.setFirmwareService(\n        makeDefaultFirmwareService(*context)); //instantiate FW service + ESP installation routine\n#elif MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP8266)\n    model.setFirmwareService(\n        makeDefaultFirmwareService(*context)); //instantiate FW service + ESP installation routine\n#endif //MO_PLATFORM\n#endif //!defined(MO_CUSTOM_UPDATER)\n\n#if !defined(MO_CUSTOM_DIAGNOSTICS)\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO && defined(ESP32) && MO_ENABLE_MBEDTLS\n    model.setDiagnosticsService(\n        makeDefaultDiagnosticsService(*context, filesystem)); //instantiate Diag service + ESP hardware diagnostics\n#elif MO_ENABLE_MBEDTLS\n    model.setDiagnosticsService(\n        makeDefaultDiagnosticsService(*context, filesystem)); //instantiate Diag service \n#endif //MO_PLATFORM\n#endif //!defined(MO_CUSTOM_DIAGNOSTICS)\n\n#if MO_PLATFORM == MO_PLATFORM_ARDUINO && (defined(ESP32) || defined(ESP8266))\n    setOnResetExecute(makeDefaultResetFn());\n#endif\n\n    model.getBootService()->setChargePointCredentials(bootNotificationCredentials);\n\n    auto credsJson = model.getBootService()->getChargePointCredentials();\n    if (model.getFirmwareService() && credsJson && credsJson->containsKey(\"firmwareVersion\")) {\n        model.getFirmwareService()->setBuildNumber((*credsJson)[\"firmwareVersion\"]);\n    }\n    credsJson.reset();\n\n    configuration_load();\n\n#if MO_ENABLE_V201\n    if (version.major == 2) {\n        model.getVariableService()->load();\n    }\n#endif //MO_ENABLE_V201\n\n    MO_DBG_INFO(\"initialized MicroOcpp v\" MO_VERSION \" running OCPP %i.%i.%i\", version.major, version.minor, version.patch);\n}\n\nvoid mocpp_deinitialize() {\n\n    if (context) {\n        //release bootstats recovery mechanism\n        BootStats bootstats;\n        BootService::loadBootStats(filesystem, bootstats);\n        if (bootstats.lastBootSuccess != bootstats.bootNr) {\n            MO_DBG_DEBUG(\"boot success timer override\");\n            bootstats.lastBootSuccess = bootstats.bootNr;\n            BootService::storeBootStats(filesystem, bootstats);\n        }\n    }\n    \n    delete context;\n    context = nullptr;\n\n#ifndef MO_CUSTOM_WS\n    delete connection;\n    connection = nullptr;\n    delete webSocket;\n    webSocket = nullptr;\n#endif\n\n    filesystem.reset();\n\n    configuration_deinit();\n\n#if !MO_HEAP_PROFILER_EXTERNAL_CONTROL\n    MO_MEM_DEINIT();\n#endif\n\n    MO_DBG_DEBUG(\"deinitialized OCPP\\n\");\n}\n\nvoid mocpp_loop() {\n    if (!context) {\n        MO_DBG_WARN(\"need to call mocpp_initialize before\");\n        return;\n    }\n\n    context->loop();\n}\n\nbool beginTransaction(const char *idTag, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return false;\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (!idTag || strnlen(idTag, MO_IDTOKEN_LEN_MAX + 2) > MO_IDTOKEN_LEN_MAX) {\n            MO_DBG_ERR(\"idTag format violation. Expect c-style string with at most %u characters\", MO_IDTOKEN_LEN_MAX);\n            return false;\n        }\n        TransactionService::Evse *evse = nullptr;\n        if (auto txService = context->getModel().getTransactionService()) {\n            evse = txService->getEvse(connectorId);\n        }\n        if (!evse) {\n            MO_DBG_ERR(\"could not find EVSE\");\n            return false;\n        }\n        return evse->beginAuthorization(idTag, true);\n    }\n    #endif\n\n    if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) {\n        MO_DBG_ERR(\"idTag format violation. Expect c-style string with at most %u characters\", IDTAG_LEN_MAX);\n        return false;\n    }\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return false;\n    }\n\n    return connector->beginTransaction(idTag) != nullptr;\n}\n\nbool beginTransaction_authorized(const char *idTag, const char *parentIdTag, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return false;\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (!idTag || strnlen(idTag, MO_IDTOKEN_LEN_MAX + 2) > MO_IDTOKEN_LEN_MAX) {\n            MO_DBG_ERR(\"idTag format violation. Expect c-style string with at most %u characters\", MO_IDTOKEN_LEN_MAX);\n            return false;\n        }\n        TransactionService::Evse *evse = nullptr;\n        if (auto txService = context->getModel().getTransactionService()) {\n            evse = txService->getEvse(connectorId);\n        }\n        if (!evse) {\n            MO_DBG_ERR(\"could not find EVSE\");\n            return false;\n        }\n        return evse->beginAuthorization(idTag, false);\n    }\n    #endif\n\n    if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX ||\n        (parentIdTag && strnlen(parentIdTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX)) {\n        MO_DBG_ERR(\"(parent)idTag format violation. Expect c-style string with at most %u characters\", IDTAG_LEN_MAX);\n        return false;\n    }\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return false;\n    }\n    \n    return connector->beginTransaction_authorized(idTag, parentIdTag) != nullptr;\n}\n\nbool endTransaction(const char *idTag, const char *reason, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return false;\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (!idTag || strnlen(idTag, MO_IDTOKEN_LEN_MAX + 2) > MO_IDTOKEN_LEN_MAX) {\n            MO_DBG_ERR(\"idTag format violation. Expect c-style string with at most %u characters\", MO_IDTOKEN_LEN_MAX);\n            return false;\n        }\n        TransactionService::Evse *evse = nullptr;\n        if (auto txService = context->getModel().getTransactionService()) {\n            evse = txService->getEvse(connectorId);\n        }\n        if (!evse) {\n            MO_DBG_ERR(\"could not find EVSE\");\n            return false;\n        }\n        return evse->endAuthorization(idTag, true);\n    }\n    #endif\n\n    bool res = false;\n    if (isTransactionActive(connectorId) && getTransactionIdTag(connectorId)) {\n        //end transaction now if either idTag is nullptr (i.e. force stop) or the idTag matches beginTransaction\n        if (!idTag || !strcmp(idTag, getTransactionIdTag(connectorId))) {\n            res = endTransaction_authorized(idTag, reason, connectorId);\n        } else {\n            auto tx = getTransaction(connectorId);\n            const char *parentIdTag = tx->getParentIdTag();\n            if (strlen(parentIdTag) > 0)\n            {\n                // We have a parent ID tag, so we need to check if this new card also has one\n                auto authorize = makeRequest(new Ocpp16::Authorize(context->getModel(), idTag));\n                auto idTag_capture = makeString(\"MicroOcpp.cpp\", idTag);\n                auto reason_capture = makeString(\"MicroOcpp.cpp\", reason ? reason : \"\");\n                authorize->setOnReceiveConfListener([idTag_capture, reason_capture, connectorId, tx] (JsonObject response) {\n                    JsonObject idTagInfo = response[\"idTagInfo\"];\n\n                    if (strcmp(\"Accepted\", idTagInfo[\"status\"] | \"UNDEFINED\")) {\n                        //Authorization rejected, do nothing\n                        MO_DBG_DEBUG(\"Authorize rejected (%s), continue transaction\", idTag_capture.c_str());\n                        auto connector = context->getModel().getConnector(connectorId);\n                        if (connector) {\n                            connector->updateTxNotification(TxNotification_AuthorizationRejected);\n                        }\n                        return;\n                    }\n                    if (idTagInfo.containsKey(\"parentIdTag\") && !strcmp(idTagInfo[\"parenIdTag\"], tx->getParentIdTag()))\n                    {\n                        endTransaction_authorized(idTag_capture.c_str(), reason_capture.empty() ? (const char*)nullptr : reason_capture.c_str(), connectorId);\n                    }\n                });\n\n                authorize->setOnTimeoutListener([idTag_capture, connectorId] () {\n                    //Authorization timed out, do nothing\n                    MO_DBG_DEBUG(\"Authorization timeout (%s), continue transaction\", idTag_capture.c_str());\n                    auto connector = context->getModel().getConnector(connectorId);\n                    if (connector) {\n                        connector->updateTxNotification(TxNotification_AuthorizationTimeout);\n                    }\n                });\n\n                auto authorizationTimeoutInt = declareConfiguration<int>(MO_CONFIG_EXT_PREFIX \"AuthorizationTimeout\", 20);\n                authorize->setTimeout(authorizationTimeoutInt && authorizationTimeoutInt->getInt() > 0 ? authorizationTimeoutInt->getInt() * 1000UL : 20UL * 1000UL);\n\n                context->initiateRequest(std::move(authorize));\n                res = true;\n            } else {\n                MO_DBG_INFO(\"endTransaction: idTag doesn't match\");\n                (void)0;\n            }\n        }\n    }\n    return res;\n}\n\nbool endTransaction_authorized(const char *idTag, const char *reason, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return false;\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (!idTag || strnlen(idTag, MO_IDTOKEN_LEN_MAX + 2) > MO_IDTOKEN_LEN_MAX) {\n            MO_DBG_ERR(\"idTag format violation. Expect c-style string with at most %u characters\", MO_IDTOKEN_LEN_MAX);\n            return false;\n        }\n        TransactionService::Evse *evse = nullptr;\n        if (auto txService = context->getModel().getTransactionService()) {\n            evse = txService->getEvse(connectorId);\n        }\n        if (!evse) {\n            MO_DBG_ERR(\"could not find EVSE\");\n            return false;\n        }\n        return evse->endAuthorization(idTag, false);\n    }\n    #endif\n\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return false;\n    }\n    auto res = isTransactionActive(connectorId);\n    connector->endTransaction(idTag, reason);\n    return res;\n}\n\nbool isTransactionActive(unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return false;\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        TransactionService::Evse *evse = nullptr;\n        if (auto txService = context->getModel().getTransactionService()) {\n            evse = txService->getEvse(connectorId);\n        }\n        if (!evse) {\n            MO_DBG_ERR(\"could not find EVSE\");\n            return false;\n        }\n        return evse->getTransaction() && evse->getTransaction()->active;\n    }\n    #endif\n\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return false;\n    }\n    auto& tx = connector->getTransaction();\n    return tx ? tx->isActive() : false;\n}\n\nbool isTransactionRunning(unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return false;\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        TransactionService::Evse *evse = nullptr;\n        if (auto txService = context->getModel().getTransactionService()) {\n            evse = txService->getEvse(connectorId);\n        }\n        if (!evse) {\n            MO_DBG_ERR(\"could not find EVSE\");\n            return false;\n        }\n        return evse->getTransaction() && evse->getTransaction()->started && !evse->getTransaction()->stopped;\n    }\n    #endif\n\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return false;\n    }\n    auto& tx = connector->getTransaction();\n    return tx ? tx->isRunning() : false;\n}\n\nconst char *getTransactionIdTag(unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return nullptr;\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        TransactionService::Evse *evse = nullptr;\n        if (auto txService = context->getModel().getTransactionService()) {\n            evse = txService->getEvse(connectorId);\n        }\n        if (!evse) {\n            MO_DBG_ERR(\"could not find EVSE\");\n            return nullptr;\n        }\n        return evse->getTransaction() ? evse->getTransaction()->idToken.get() : nullptr;\n    }\n    #endif\n\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return nullptr;\n    }\n    auto& tx = connector->getTransaction();\n    return tx ? tx->getIdTag() : nullptr;\n}\n\nstd::shared_ptr<Transaction> mocpp_undefinedTx;\n\nstd::shared_ptr<Transaction>& getTransaction(unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_WARN(\"OCPP uninitialized\");\n        return mocpp_undefinedTx;\n    }\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        MO_DBG_ERR(\"only supported in v16\");\n        return mocpp_undefinedTx;\n    }\n    #endif\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return mocpp_undefinedTx;\n    }\n    return connector->getTransaction();\n}\n\n#if MO_ENABLE_V201\nOcpp201::Transaction *getTransactionV201(unsigned int evseId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return nullptr;\n    }\n\n    if (context->getVersion().major != 2) {\n        MO_DBG_ERR(\"only supported in v201\");\n        return nullptr;\n    }\n\n    TransactionService::Evse *evse = nullptr;\n    if (auto txService = context->getModel().getTransactionService()) {\n        evse = txService->getEvse(evseId);\n    }\n    if (!evse) {\n        MO_DBG_ERR(\"could not find EVSE\");\n        return nullptr;\n    }\n    return evse->getTransaction();\n}\n#endif //MO_ENABLE_V201\n\nbool ocppPermitsCharge(unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_WARN(\"OCPP uninitialized\");\n        return false;\n    }\n#if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        TransactionService::Evse *evse = nullptr;\n        if (auto txService = context->getModel().getTransactionService()) {\n            evse = txService->getEvse(connectorId);\n        }\n        if (!evse) {\n            MO_DBG_ERR(\"could not find EVSE\");\n            return false;\n        }\n        return evse->ocppPermitsCharge();\n    }\n#endif\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return false;\n    }\n    return connector->ocppPermitsCharge();\n}\n\nChargePointStatus getChargePointStatus(unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_WARN(\"OCPP uninitialized\");\n        return ChargePointStatus_UNDEFINED;\n    }\n#if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (auto availabilityService = context->getModel().getAvailabilityService()) {\n            if (auto evse = availabilityService->getEvse(connectorId)) {\n                return evse->getStatus();\n            }\n        }\n    }\n#endif\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return ChargePointStatus_UNDEFINED;\n    }\n    return connector->getStatus();\n}\n\nvoid setConnectorPluggedInput(std::function<bool()> pluggedInput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n#if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (auto availabilityService = context->getModel().getAvailabilityService()) {\n            if (auto evse = availabilityService->getEvse(connectorId)) {\n                evse->setConnectorPluggedInput(pluggedInput);\n            }\n        }\n        if (auto txService = context->getModel().getTransactionService()) {\n            if (auto evse = txService->getEvse(connectorId)) {\n                evse->setConnectorPluggedInput(pluggedInput);\n            }\n        }\n        return;\n    }\n#endif\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->setConnectorPluggedInput(pluggedInput);\n}\n\nvoid setEnergyMeterInput(std::function<int()> energyInput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        addMeterValueInput([energyInput] () {return static_cast<float>(energyInput());}, \"Energy.Active.Import.Register\", \"Wh\", nullptr, nullptr, connectorId);\n        return;\n    }\n    #endif\n\n    SampledValueProperties meterProperties;\n    meterProperties.setMeasurand(\"Energy.Active.Import.Register\");\n    meterProperties.setUnit(\"Wh\");\n    auto mvs = std::unique_ptr<SampledValueSamplerConcrete<int32_t, SampledValueDeSerializer<int32_t>>>(\n                           new SampledValueSamplerConcrete<int32_t, SampledValueDeSerializer<int32_t>>(\n            meterProperties,\n            [energyInput] (ReadingContext) {return energyInput();}\n    ));\n    addMeterValueInput(std::move(mvs), connectorId);\n}\n\nvoid setPowerMeterInput(std::function<float()> powerInput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        addMeterValueInput([powerInput] () {return static_cast<float>(powerInput());}, \"Power.Active.Import\", \"W\", nullptr, nullptr, connectorId);\n        return;\n    }\n    #endif\n\n    SampledValueProperties meterProperties;\n    meterProperties.setMeasurand(\"Power.Active.Import\");\n    meterProperties.setUnit(\"W\");\n    auto mvs = std::unique_ptr<SampledValueSamplerConcrete<float, SampledValueDeSerializer<float>>>(\n                           new SampledValueSamplerConcrete<float, SampledValueDeSerializer<float>>(\n            meterProperties,\n            [powerInput] (ReadingContext) {return powerInput();}\n    ));\n    addMeterValueInput(std::move(mvs), connectorId);\n}\n\nvoid setSmartChargingPowerOutput(std::function<void(float)> chargingLimitOutput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    if (!context->getModel().getConnector(connectorId)) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n\n    if (chargingLimitOutput) {\n        setSmartChargingOutput([chargingLimitOutput] (float power, float current, int nphases) -> void {\n            chargingLimitOutput(power);\n        }, connectorId);\n    } else {\n        setSmartChargingOutput(nullptr, connectorId);\n    }\n\n    if (auto scService = context->getModel().getSmartChargingService()) {\n        if (chargingLimitOutput) {\n            scService->updateAllowedChargingRateUnit(true, false);\n        } else {\n            scService->updateAllowedChargingRateUnit(false, false);\n        }\n    }\n}\n\nvoid setSmartChargingCurrentOutput(std::function<void(float)> chargingLimitOutput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    if (!context->getModel().getConnector(connectorId)) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n\n    if (chargingLimitOutput) {\n        setSmartChargingOutput([chargingLimitOutput] (float power, float current, int nphases) -> void {\n            chargingLimitOutput(current);\n        }, connectorId);\n    } else {\n        setSmartChargingOutput(nullptr, connectorId);\n    }\n\n    if (auto scService = context->getModel().getSmartChargingService()) {\n        if (chargingLimitOutput) {\n            scService->updateAllowedChargingRateUnit(false, true);\n        } else {\n            scService->updateAllowedChargingRateUnit(false, false);\n        }\n    }\n}\n\nvoid setSmartChargingOutput(std::function<void(float,float,int)> chargingLimitOutput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    if (!context->getModel().getConnector(connectorId)) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n\n    auto& model = context->getModel();\n    if (!model.getSmartChargingService() && chargingLimitOutput) {\n        model.setSmartChargingService(std::unique_ptr<SmartChargingService>(\n            new SmartChargingService(*context, filesystem, MO_NUMCONNECTORS)));\n    }\n\n    if (auto scService = context->getModel().getSmartChargingService()) {\n        scService->setSmartChargingOutput(connectorId, chargingLimitOutput);\n        if (chargingLimitOutput) {\n            scService->updateAllowedChargingRateUnit(true, true);\n        } else {\n            scService->updateAllowedChargingRateUnit(false, false);\n        }\n    }\n}\n\nvoid setEvReadyInput(std::function<bool()> evReadyInput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n#if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (auto txService = context->getModel().getTransactionService()) {\n            if (auto evse = txService->getEvse(connectorId)) {\n                evse->setEvReadyInput(evReadyInput);\n            }\n        }\n        return;\n    }\n#endif\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->setEvReadyInput(evReadyInput);\n}\n\nvoid setEvseReadyInput(std::function<bool()> evseReadyInput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n#if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (auto txService = context->getModel().getTransactionService()) {\n            if (auto evse = txService->getEvse(connectorId)) {\n                evse->setEvseReadyInput(evseReadyInput);\n            }\n        }\n        return;\n    }\n#endif\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->setEvseReadyInput(evseReadyInput);\n}\n\nvoid addErrorCodeInput(std::function<const char*()> errorCodeInput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->addErrorCodeInput(errorCodeInput);\n}\n\nvoid addErrorDataInput(std::function<MicroOcpp::ErrorData()> errorDataInput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->addErrorDataInput(errorDataInput);\n}\n\nvoid addMeterValueInput(std::function<float ()> valueInput, const char *measurand, const char *unit, const char *location, const char *phase, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n\n    if (!valueInput) {\n        MO_DBG_ERR(\"value undefined\");\n        return;\n    }\n\n    if (!measurand) {\n        measurand = \"Energy.Active.Import.Register\";\n        MO_DBG_WARN(\"measurand unspecified; assume %s\", measurand);\n    }\n\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        auto& model = context->getModel();\n        if (!model.getMeteringServiceV201()) {\n            model.setMeteringServiceV201(std::unique_ptr<Ocpp201::MeteringService>(\n                new Ocpp201::MeteringService(context->getModel(), MO_NUM_EVSEID)));\n        }\n        if (auto mEvse = model.getMeteringServiceV201()->getEvse(connectorId)) {\n            \n            Ocpp201::SampledValueProperties properties;\n            properties.setMeasurand(measurand); //mandatory for MO\n\n            if (unit)\n                properties.setUnitOfMeasureUnit(unit);\n            if (location)\n                properties.setLocation(location);\n            if (phase)\n                properties.setPhase(phase);\n\n            mEvse->addMeterValueInput([valueInput] (ReadingContext) {return static_cast<double>(valueInput());}, properties);\n        } else {\n            MO_DBG_ERR(\"inalid arg\");\n        }\n        return;\n    }\n    #endif\n\n    SampledValueProperties properties;\n    properties.setMeasurand(measurand); //mandatory for MO\n\n    if (unit)\n        properties.setUnit(unit);\n    if (location)\n        properties.setLocation(location);\n    if (phase)\n        properties.setPhase(phase);\n\n    auto valueSampler = std::unique_ptr<MicroOcpp::SampledValueSamplerConcrete<float, MicroOcpp::SampledValueDeSerializer<float>>>(\n                                    new MicroOcpp::SampledValueSamplerConcrete<float, MicroOcpp::SampledValueDeSerializer<float>>(\n                properties,\n                [valueInput] (ReadingContext) {return valueInput();}));\n    addMeterValueInput(std::move(valueSampler), connectorId);\n}\n\nvoid addMeterValueInput(std::unique_ptr<SampledValueSampler> valueInput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        MO_DBG_ERR(\"addMeterValueInput(std::unique_ptr<SampledValueSampler>...) not compatible with v201. Use addMeterValueInput(std::function<float>...) instead\");\n        return;\n    }\n    #endif\n    auto& model = context->getModel();\n    if (!model.getMeteringService()) {\n        model.setMeteringSerivce(std::unique_ptr<MeteringService>(\n            new MeteringService(*context, MO_NUMCONNECTORS, filesystem)));\n    }\n    model.getMeteringService()->addMeterValueSampler(connectorId, std::move(valueInput));\n}\n\nvoid setOccupiedInput(std::function<bool()> occupied, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n#if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (auto availabilityService = context->getModel().getAvailabilityService()) {\n            if (auto evse = availabilityService->getEvse(connectorId)) {\n                evse->setOccupiedInput(occupied);\n            }\n        }\n        return;\n    }\n#endif\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->setOccupiedInput(occupied);\n}\n\nvoid setStartTxReadyInput(std::function<bool()> startTxReady, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->setStartTxReadyInput(startTxReady);\n}\n\nvoid setStopTxReadyInput(std::function<bool()> stopTxReady, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->setStopTxReadyInput(stopTxReady);\n}\n\nvoid setTxNotificationOutput(std::function<void(MicroOcpp::Transaction*,TxNotification)> notificationOutput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    #if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        MO_DBG_ERR(\"only supported in v16\");\n        return;\n    }\n    #endif\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->setTxNotificationOutput(notificationOutput);\n}\n\n#if MO_ENABLE_V201\nvoid setTxNotificationOutputV201(std::function<void(MicroOcpp::Ocpp201::Transaction*,TxNotification)> notificationOutput, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n\n    if (context->getVersion().major != 2) {\n        MO_DBG_ERR(\"only supported in v201\");\n        return;\n    }\n\n    TransactionService::Evse *evse = nullptr;\n    if (auto txService = context->getModel().getTransactionService()) {\n        evse = txService->getEvse(connectorId);\n    }\n    if (!evse) {\n        MO_DBG_ERR(\"could not find EVSE\");\n        return;\n    }\n    evse->setTxNotificationOutput(notificationOutput);\n}\n#endif //MO_ENABLE_V201\n\n#if MO_ENABLE_CONNECTOR_LOCK\nvoid setOnUnlockConnectorInOut(std::function<UnlockConnectorResult()> onUnlockConnectorInOut, unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    auto connector = context->getModel().getConnector(connectorId);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return;\n    }\n    connector->setOnUnlockConnector(onUnlockConnectorInOut);\n}\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\nbool isOperative(unsigned int connectorId) {\n    if (!context) {\n        MO_DBG_WARN(\"OCPP uninitialized\");\n        return true; //assume \"true\" as default state\n    }\n#if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (auto availabilityService = context->getModel().getAvailabilityService()) {\n            auto chargePoint = availabilityService->getEvse(OCPP_ID_OF_CP);\n            auto connector = availabilityService->getEvse(connectorId);\n            if (!chargePoint || !connector) {\n                MO_DBG_ERR(\"could not find connector\");\n                return true; //assume \"true\" as default state\n            }\n            return chargePoint->isAvailable() && connector->isAvailable();\n        }\n    }\n#endif\n    auto& model = context->getModel();\n    auto chargePoint = model.getConnector(OCPP_ID_OF_CP);\n    auto connector = model.getConnector(connectorId);\n    if (!chargePoint || !connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return true; //assume \"true\" as default state\n    }\n    return chargePoint->isOperative() && connector->isOperative();\n}\n\nvoid setOnResetNotify(std::function<bool(bool)> onResetNotify) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n\n#if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (auto rService = context->getModel().getResetServiceV201()) {\n            rService->setNotifyReset([onResetNotify] (ResetType) {return onResetNotify(true);});\n        }\n        return;\n    }\n#endif\n\n    if (auto rService = context->getModel().getResetService()) {\n        rService->setPreReset(onResetNotify);\n    }\n}\n\nvoid setOnResetExecute(std::function<void(bool)> onResetExecute) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n\n#if MO_ENABLE_V201\n    if (context->getVersion().major == 2) {\n        if (auto rService = context->getModel().getResetServiceV201()) {\n            rService->setExecuteReset([onResetExecute] () {onResetExecute(true); return true;});\n        }\n        return;\n    }\n#endif\n\n    if (auto rService = context->getModel().getResetService()) {\n        rService->setExecuteReset(onResetExecute);\n    }\n}\n\nFirmwareService *getFirmwareService() {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return nullptr;\n    }\n\n    auto& model = context->getModel();\n    if (!model.getFirmwareService()) {\n        model.setFirmwareService(std::unique_ptr<FirmwareService>(\n            new FirmwareService(*context)));\n    }\n\n    return model.getFirmwareService();\n}\n\nDiagnosticsService *getDiagnosticsService() {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return nullptr;\n    }\n\n    auto& model = context->getModel();\n    if (!model.getDiagnosticsService()) {\n        model.setDiagnosticsService(std::unique_ptr<DiagnosticsService>(\n            new DiagnosticsService(*context)));\n    }\n\n    return model.getDiagnosticsService();\n}\n\n#if MO_ENABLE_CERT_MGMT\n\nvoid setCertificateStore(std::unique_ptr<MicroOcpp::CertificateStore> certStore) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n\n    auto& model = context->getModel();\n    if (!model.getCertificateService()) {\n        model.setCertificateService(std::unique_ptr<CertificateService>(\n            new CertificateService(*context)));\n    }\n    if (auto certService = model.getCertificateService()) {\n        certService->setCertificateStore(std::move(certStore));\n    } else {\n        MO_DBG_ERR(\"OOM\");\n    }\n}\n#endif //MO_ENABLE_CERT_MGMT\n\nContext *getOcppContext() {\n    return context;\n}\n\nvoid setOnReceiveRequest(const char *operationType, OnReceiveReqListener onReceiveReq) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    if (!operationType) {\n        MO_DBG_ERR(\"invalid args\");\n        return;\n    }\n    context->getOperationRegistry().setOnRequest(operationType, onReceiveReq);\n}\n\nvoid setOnSendConf(const char *operationType, OnSendConfListener onSendConf) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    if (!operationType) {\n        MO_DBG_ERR(\"invalid args\");\n        return;\n    }\n    context->getOperationRegistry().setOnResponse(operationType, onSendConf);\n}\n\nvoid sendRequest(const char *operationType,\n            std::function<std::unique_ptr<JsonDoc> ()> fn_createReq,\n            std::function<void (JsonObject)> fn_processConf) {\n\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    if (!operationType || !fn_createReq || !fn_processConf) {\n        MO_DBG_ERR(\"invalid args\");\n        return;\n    }\n\n    auto request = makeRequest(new CustomOperation(operationType, fn_createReq, fn_processConf));\n    context->initiateRequest(std::move(request));\n}\n\nvoid setRequestHandler(const char *operationType,\n            std::function<void (JsonObject)> fn_processReq,\n            std::function<std::unique_ptr<JsonDoc> ()> fn_createConf) {\n\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    if (!operationType || !fn_processReq || !fn_createConf) {\n        MO_DBG_ERR(\"invalid args\");\n        return;\n    }\n\n    auto captureOpType = makeString(\"MicroOcpp.cpp\", operationType);\n\n    context->getOperationRegistry().registerOperation(operationType, [captureOpType, fn_processReq, fn_createConf] () {\n        return new CustomOperation(captureOpType.c_str(), fn_processReq, fn_createConf);\n    });\n}\n\nvoid authorize(const char *idTag, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, unsigned int timeout) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return;\n    }\n    if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) {\n        MO_DBG_ERR(\"idTag format violation. Expect c-style string with at most %u characters\", IDTAG_LEN_MAX);\n        return;\n    }\n    auto authorize = makeRequest(\n        new Authorize(context->getModel(), idTag));\n    if (onConf)\n        authorize->setOnReceiveConfListener(onConf);\n    if (onAbort)\n        authorize->setOnAbortListener(onAbort);\n    if (onTimeout)\n        authorize->setOnTimeoutListener(onTimeout);\n    if (onError)\n        authorize->setOnReceiveErrorListener(onError);\n    if (timeout)\n        authorize->setTimeout(timeout);\n    else\n        authorize->setTimeout(20000);\n    context->initiateRequest(std::move(authorize));\n}\n\nbool startTransaction(const char *idTag, OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, unsigned int timeout) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return false;\n    }\n    if (!idTag || strnlen(idTag, IDTAG_LEN_MAX + 2) > IDTAG_LEN_MAX) {\n        MO_DBG_ERR(\"idTag format violation. Expect c-style string with at most %u characters\", IDTAG_LEN_MAX);\n        return false;\n    }\n    auto connector = context->getModel().getConnector(OCPP_ID_OF_CONNECTOR);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return false;\n    }\n    auto transaction = connector->getTransaction();\n    if (transaction) {\n        if (transaction->getStartSync().isRequested()) {\n            MO_DBG_ERR(\"transaction already in progress. Must call stopTransaction()\");\n            return false;\n        }\n        transaction->setIdTag(idTag);\n    } else {\n        beginTransaction_authorized(idTag); //request new transaction object\n        transaction = connector->getTransaction();\n        if (!transaction) {\n            MO_DBG_WARN(\"transaction queue full\");\n            return false;\n        }\n    }\n\n    if (auto mService = context->getModel().getMeteringService()) {\n        auto meterStart = mService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionBegin);\n        if (meterStart && *meterStart) {\n            transaction->setMeterStart(meterStart->toInteger());\n        } else {\n            MO_DBG_ERR(\"meterStart undefined\");\n        }\n    }\n\n    transaction->setStartTimestamp(context->getModel().getClock().now());\n\n    transaction->commit();\n    \n    auto startTransaction = makeRequest(\n        new StartTransaction(context->getModel(), transaction));\n    if (onConf)\n        startTransaction->setOnReceiveConfListener(onConf);\n    if (onAbort)\n        startTransaction->setOnAbortListener(onAbort);\n    if (onTimeout)\n        startTransaction->setOnTimeoutListener(onTimeout);\n    if (onError)\n        startTransaction->setOnReceiveErrorListener(onError);\n    if (timeout)\n        startTransaction->setTimeout(timeout);\n    else\n        startTransaction->setTimeout(0);\n    context->initiateRequest(std::move(startTransaction));\n\n    return true;\n}\n\nbool stopTransaction(OnReceiveConfListener onConf, OnAbortListener onAbort, OnTimeoutListener onTimeout, OnReceiveErrorListener onError, unsigned int timeout) {\n    if (!context) {\n        MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n        return false;\n    }\n    auto connector = context->getModel().getConnector(OCPP_ID_OF_CONNECTOR);\n    if (!connector) {\n        MO_DBG_ERR(\"could not find connector\");\n        return false;\n    }\n\n    auto transaction = connector->getTransaction();\n    if (!transaction || !transaction->isRunning()) {\n        MO_DBG_ERR(\"no running Tx to stop\");\n        return false;\n    }\n\n    connector->endTransaction(transaction->getIdTag(), \"Local\");\n\n    const char *idTag = transaction->getIdTag();\n    if (idTag) {\n        transaction->setStopIdTag(idTag);\n    }\n\n    if (auto mService = context->getModel().getMeteringService()) {\n        auto meterStop = mService->readTxEnergyMeter(transaction->getConnectorId(), ReadingContext_TransactionEnd);\n        if (meterStop && *meterStop) {\n            transaction->setMeterStop(meterStop->toInteger());\n        } else {\n            MO_DBG_ERR(\"meterStop undefined\");\n        }\n    }\n\n    transaction->setStopTimestamp(context->getModel().getClock().now());\n\n    transaction->commit();\n\n    auto stopTransaction = makeRequest(\n        new StopTransaction(context->getModel(), transaction));\n    if (onConf)\n        stopTransaction->setOnReceiveConfListener(onConf);\n    if (onAbort)\n        stopTransaction->setOnAbortListener(onAbort);\n    if (onTimeout)\n        stopTransaction->setOnTimeoutListener(onTimeout);\n    if (onError)\n        stopTransaction->setOnReceiveErrorListener(onError);\n    if (timeout)\n        stopTransaction->setTimeout(timeout);\n    else\n        stopTransaction->setTimeout(0);\n    context->initiateRequest(std::move(stopTransaction));\n\n    return true;\n}\n"
  },
  {
    "path": "src/MicroOcpp.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_MICROOCPP_H\n#define MO_MICROOCPP_H\n\n#include <ArduinoJson.h>\n#include <memory>\n#include <functional>\n\n#include <MicroOcpp/Core/ConfigurationOptions.h>\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/RequestCallbacks.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <MicroOcpp/Model/Metering/SampledValue.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Model/ConnectorBase/ChargePointErrorData.h>\n#include <MicroOcpp/Model/ConnectorBase/ChargePointStatus.h>\n#include <MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h>\n#include <MicroOcpp/Version.h>\n#include <MicroOcpp/Model/Certificates/Certificate.h>\n\nusing MicroOcpp::OnReceiveConfListener;\nusing MicroOcpp::OnReceiveReqListener;\nusing MicroOcpp::OnSendConfListener;\nusing MicroOcpp::OnAbortListener;\nusing MicroOcpp::OnTimeoutListener;\nusing MicroOcpp::OnReceiveErrorListener;\n\n#ifndef MO_CUSTOM_WS\n//use links2004/WebSockets library\n\n/*\n * Initialize the library with the OCPP URL, EVSE voltage and filesystem configuration.\n * \n * If the connections fails, please refer to \n * https://github.com/matth-x/MicroOcpp/issues/36#issuecomment-989716573 for recommendations on\n * how to track down the issue with the connection.\n * \n * This is a convenience function only available for Arduino.\n */\nvoid mocpp_initialize(\n            const char *backendUrl,    //e.g. \"wss://example.com:8443/steve/websocket/CentralSystemService\"\n            const char *chargeBoxId,   //e.g. \"charger001\"\n            const char *chargePointModel = \"Demo Charger\",     //model name of this charger\n            const char *chargePointVendor = \"My Company Ltd.\", //brand name\n            MicroOcpp::FilesystemOpt fsOpt = MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail, //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h\n            const char *password = nullptr, //password present in the websocket message header\n            const char *CA_cert = nullptr, //TLS certificate\n            bool autoRecover = false); //automatically sanitize the local data store when the lib detects recurring crashes. Not recommended during development\n#endif\n\n/*\n * Convenience initialization: use this for passing the BootNotification payload JSON to the mocpp_initialize(...) below\n *\n * Example usage:\n * \n *     mocpp_initialize(osock, ChargerCredentials(\"Demo Charger\", \"My Company Ltd.\"));\n * \n * For a description of the fields, refer to OCPP 1.6 Specification - Edition 2 p. 60\n */\nstruct ChargerCredentials {\n    ChargerCredentials(\n            const char *chargePointModel = \"Demo Charger\",\n            const char *chargePointVendor = \"My Company Ltd.\",\n            const char *firmwareVersion = nullptr,\n            const char *chargePointSerialNumber = nullptr,\n            const char *meterSerialNumber = nullptr,\n            const char *meterType = nullptr,\n            const char *chargeBoxSerialNumber = nullptr,\n            const char *iccid = nullptr,\n            const char *imsi = nullptr);\n    \n    /*\n     * OCPP 2.0.1 compatible charger credentials. Use this if initializing the library with ProtocolVersion(2,0,1)\n     */\n    static ChargerCredentials v201(\n            const char *chargePointModel = \"Demo Charger\",\n            const char *chargePointVendor = \"My Company Ltd.\",\n            const char *firmwareVersion = nullptr,\n            const char *chargePointSerialNumber = nullptr,\n            const char *meterSerialNumber = nullptr,\n            const char *meterType = nullptr,\n            const char *chargeBoxSerialNumber = nullptr,\n            const char *iccid = nullptr,\n            const char *imsi = nullptr);\n\n    operator const char *() {return payload;}\n\nprivate:\n    char payload [512] = {'{', '}', '\\0'};\n};\n\n/*\n * Initialize the library with a WebSocket connection which is configured with protocol=ocpp1.6\n * (=Connection), EVSE voltage and filesystem configuration. This library requires that you handle\n * establishing the connection and keeping it alive. Please refer to\n * https://github.com/matth-x/MicroOcpp/tree/main/examples/ESP-TLS for an example how to use it.\n * \n * This GitHub project also delivers an Connection implementation based on links2004/WebSockets. If\n * you need another WebSockets implementation, you can subclass the Connection class and pass it to\n * this initialize() function. Please refer to\n * https://github.com/OpenEVSE/ESP32_WiFi_V4.x/blob/master/src/MongooseConnectionClient.cpp for\n * an example.\n */\nvoid mocpp_initialize(\n            MicroOcpp::Connection& connection, //WebSocket adapter for MicroOcpp\n            const char *bootNotificationCredentials = ChargerCredentials(\"Demo Charger\", \"My Company Ltd.\"), //e.g. '{\"chargePointModel\":\"Demo Charger\",\"chargePointVendor\":\"My Company Ltd.\"}' (refer to OCPP 1.6 Specification - Edition 2 p. 60)\n            std::shared_ptr<MicroOcpp::FilesystemAdapter> filesystem =\n                MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail), //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h\n            bool autoRecover = false, //automatically sanitize the local data store when the lib detects recurring crashes. Not recommended during development\n            MicroOcpp::ProtocolVersion version = MicroOcpp::ProtocolVersion(1,6));\n\n/*\n * Stop the OCPP library and release allocated resources.\n */\nvoid mocpp_deinitialize();\n\n/*\n * To be called in the main loop (e.g. place it inside loop())\n */\nvoid mocpp_loop();\n\n/*\n * Transaction management.\n * \n * OCPP 1.6 (2.0.1 see below):\n * Begin the transaction process and prepare it. When all conditions for the transaction are true,\n * eventually send a StartTransaction request to the OCPP server.\n * Conditions:\n *     1) the connector is operative (no faults reported, not set \"Unavailable\" by the backend)\n *     2) no reservation blocks the connector\n *     3) the idTag is authorized for charging. The transaction process will send an Authorize message\n *        to the server for approval, except if the charger is offline, then the Local Authorization\n *        rules will apply as in the specification.\n *     4) the vehicle is already plugged or will be plugged soon (only applicable if the\n *        ConnectorPlugged Input is set)\n * \n * See beginTransaction_authorized for skipping steps 1) to 3)\n * \n * Returns true if it was possible to create the transaction process. Returns\n * false if either another transaction process is still active or you need to try it again later.\n * \n * OCPP 2.0.1:\n * Authorize a transaction. Like the OCPP 1.6 behavior, this should be called when the user swipes the\n * card to start charging, but the semantic is slightly different. This function begins the authorized\n * phase, but a transaction may already have started due to an earlier transaction start point.\n */\nbool beginTransaction(const char *idTag, unsigned int connectorId = 1);\n\n/*\n * Begin the transaction process and skip the OCPP-side authorization. See beginTransaction(...) for a\n * complete description\n */\nbool beginTransaction_authorized(const char *idTag, const char *parentIdTag = nullptr, unsigned int connectorId = 1);\n\n/*\n * OCPP 1.6 (2.0.1 see below):\n * End the transaction process if idTag is authorized to stop the transaction. The OCPP lib sends\n * a StopTransaction request if the following conditions are true:\n * Conditions:\n *     1) Currently, a transaction is running which hasn't been terminated yet AND\n *     2) idTag is either\n *         - nullptr OR\n *         - matches the idTag of beginTransaction (or RemoteStartTransaction) OR\n *         - [Planned, not released yet] is part of the current LocalList and the parentIdTag\n *           matches with the parentIdTag of beginTransaction.\n *         - [Planned, not released yet] If none of step 2) applies, then the OCPP lib will check\n *           the authorization status via an Authorize request\n * \n * See endTransaction_authorized for skipping the authorization check, i.e. step 2)\n * \n * If the transaction is ended by swiping an RFID card, then idTag should contain its identifier. If\n * charging stops for a different reason than swiping the card, idTag should be null or empty.\n * \n * Please refer to OCPP 1.6 Specification - Edition 2 p. 90 for a list of valid reasons. `reason`\n * can also be nullptr.\n * \n * It is safe to call this function at any time, i.e. when no transaction runs or when the transaction\n * has already been ended. For example you can place\n *     `endTransaction(nullptr, \"Reboot\");`\n * in the beginning of the program just to ensure that there is no transaction from a previous run.\n * \n * If called with idTag=nullptr, this is functionally equivalent to\n *     `endTransaction_authorized(nullptr, reason);`\n * \n * Returns true if there is a transaction which could eventually be ended by this action\n * \n * OCPP 2.0.1:\n * End the user authorization. Like when running with OCPP 1.6, this should be called when the user\n * swipes the card to stop charging. The difference between the 1.6/2.0.1 behavior is that in 1.6,\n * endTransaction always sets the transaction inactive so that it wants to stop. In 2.0.1, this only\n * revokes the user authorization which may terminate the transaction but doesn't have to if the\n * transaction stop point is set to EvConnected.\n * \n * Note: the stop reason parameter is ignored when running with OCPP 2.0.1. It's always Local\n */\nbool endTransaction(const char *idTag = nullptr, const char *reason = nullptr, unsigned int connectorId = 1);\n\n/*\n * End the transaction process definitely without authorization check. See endTransaction(...) for a\n * complete description.\n * \n * Use this function if you manage authorization on your own and want to bypass the Authorization\n * management of this lib.\n */\nbool endTransaction_authorized(const char *idTag, const char *reason = nullptr, unsigned int connectorId = 1);\n\n/*\n * Get information about the current Transaction lifecycle. A transaction can enter the following\n * states:\n *     - Idle: no transaction running or being started\n *     - Preparing: before a potential transaction\n *     - Aborted: transaction not started and never will be started\n *     - Running: transaction started and running\n *     - Running/StopTxAwait: transaction still running but will end at the next possible time\n *     - Finished: transaction stopped\n * \n * isTransactionActive() and isTransactionRunning() give the status by combining them:\n * \n *     State               | isTransactionActive() | isTransactionRunning()\n *     --------------------+-----------------------+-----------------------\n *     Preparing           | true                  | false\n *     Running             | true                  | true\n *     Running/StopTxAwait | false                 | true\n *     Finished / Aborted  |                       |\n *                  / Idle | false                 | false\n */\nbool isTransactionActive(unsigned int connectorId = 1);\nbool isTransactionRunning(unsigned int connectorId = 1);\n\n/*\n * Get the idTag which has been used to start the transaction. If no transaction process is\n * running, this function returns nullptr\n */\nconst char *getTransactionIdTag(unsigned int connectorId = 1);\n\n/*\n * Returns the current transaction process. Returns nullptr if no transaction is running, preparing or finishing\n *\n * See the class definition in MicroOcpp/Model/Transactions/Transaction.h for possible uses of this object\n * \n * Examples:\n * auto tx = getTransaction(); //fetch tx object\n * if (tx) { //check if tx object exists\n *     bool active = tx->isActive(); //active tells if the transaction is preparing or continuing to run\n *                                   //inactive means that the transaction is about to stop, stopped or won't be started anymore\n *     int transactionId = tx->getTransactionId(); //the transactionId as assigned by the OCPP server\n *     bool deauthorized = tx->isIdTagDeauthorized(); //if StartTransaction has been rejected\n * }\n */\nstd::shared_ptr<MicroOcpp::Transaction>& getTransaction(unsigned int connectorId = 1);\n\n#if MO_ENABLE_V201\n/*\n * OCPP 2.0.1 version of getTransaction(). Note that the return transaction object is of another type\n * and unlike the 1.6 version, this function does not give ownership.\n */\nMicroOcpp::Ocpp201::Transaction *getTransactionV201(unsigned int evseId = 1);\n#endif //MO_ENABLE_V201\n\n/* \n * Returns if the OCPP library allows the EVSE to charge at the moment.\n *\n * If you integrate it into a J1772 charger, true means that the Control Pilot can send the PWM signal\n * and false means that the Control Pilot must be at a DC voltage.\n */\nbool ocppPermitsCharge(unsigned int connectorId = 1);\n\n/*\n * Returns the latest ChargePointStatus as reported via StatusNotification (standard OCPP data type)\n */\nChargePointStatus getChargePointStatus(unsigned int connectorId = 1);\n\n/*\n * Define the Inputs and Outputs of this library.\n * \n * This library interacts with the hardware of your charger by Inputs and Outputs. Inputs and Outputs\n * are tiny function-objects which read information from the EVSE or control the behavior of the EVSE.\n * \n * An Input is a function which returns the current state of a variable of the EVSE. For example, if\n * the energy meter stores the energy register in the global variable `e_reg`, then you can allow\n * this library to read it by defining the Input \n *     `[] () {return e_reg;}`\n * and passing it to the library.\n * \n * An Output is a function which gets a state value from the OCPP library and applies it to the EVSE.\n * For example, to let Smart Charging control the PWM signal of the Control Pilot, define the Output\n *     `[] (float p_max) {pwm = p_max / PWM_FACTOR;}` (simplified example)\n * and pass it to the library.\n * \n * Configure the library with Inputs and Outputs once in the setup() function.\n */\n\nvoid setConnectorPluggedInput(std::function<bool()> pluggedInput, unsigned int connectorId = 1); //Input about if an EV is plugged to this EVSE\n\nvoid setEnergyMeterInput(std::function<int()> energyInput, unsigned int connectorId = 1); //Input of the electricity meter register in Wh\n\nvoid setPowerMeterInput(std::function<float()> powerInput, unsigned int connectorId = 1); //Input of the power meter reading in W\n\n//Smart Charging Output, alternative for Watts only, Current only, or Watts x Current x numberPhases.\n//Only one of the Smart Charging Outputs can be set at a time.\n//MO will execute the callback whenever the OCPP charging limit changes and will pass the limit for now\n//to the callback. If OCPP does not define a limit, then MO passes the value -1 for \"undefined\".\nvoid setSmartChargingPowerOutput(std::function<void(float)> chargingLimitOutput, unsigned int connectorId = 1); //Output (in Watts) for the Smart Charging limit\nvoid setSmartChargingCurrentOutput(std::function<void(float)> chargingLimitOutput, unsigned int connectorId = 1); //Output (in Amps) for the Smart Charging limit\nvoid setSmartChargingOutput(std::function<void(float,float,int)> chargingLimitOutput, unsigned int connectorId = 1); //Output (in Watts, Amps, numberPhases) for the Smart Charging limit\n\n/*\n * Define the Inputs and Outputs of this library. (Advanced)\n * \n * These Inputs and Outputs are optional depending on the use case of your charger.\n */\n\nvoid setEvReadyInput(std::function<bool()> evReadyInput, unsigned int connectorId = 1); //Input if EV is ready to charge (= J1772 State C)\n\nvoid setEvseReadyInput(std::function<bool()> evseReadyInput, unsigned int connectorId = 1); //Input if EVSE allows charge (= PWM signal on)\n\nvoid addErrorCodeInput(std::function<const char*()> errorCodeInput, unsigned int connectorId = 1); //Input for Error codes (please refer to OCPP 1.6, Edit2, p. 71 and 72 for valid error codes)\nvoid addErrorDataInput(std::function<MicroOcpp::ErrorData()> errorDataInput, unsigned int connectorId = 1);\n\nvoid addMeterValueInput(std::function<float ()> valueInput, const char *measurand = nullptr, const char *unit = nullptr, const char *location = nullptr, const char *phase = nullptr, unsigned int connectorId = 1); //integrate further metering Inputs\n\nvoid addMeterValueInput(std::unique_ptr<MicroOcpp::SampledValueSampler> valueInput, unsigned int connectorId = 1); //integrate further metering Inputs (more extensive alternative)\n\nvoid setOccupiedInput(std::function<bool()> occupied, unsigned int connectorId = 1); //Input if instead of Available, send StatusNotification Preparing / Finishing\n\nvoid setStartTxReadyInput(std::function<bool()> startTxReady, unsigned int connectorId = 1); //Input if the charger is ready for StartTransaction\n\nvoid setStopTxReadyInput(std::function<bool()> stopTxReady, unsigned int connectorId = 1); //Input if charger is ready for StopTransaction\n\nvoid setTxNotificationOutput(std::function<void(MicroOcpp::Transaction*,TxNotification)> notificationOutput, unsigned int connectorId = 1); //called when transaction state changes (see TxNotification for possible events). Transaction can be null\n\n#if MO_ENABLE_V201\nvoid setTxNotificationOutputV201(std::function<void(MicroOcpp::Ocpp201::Transaction*,TxNotification)> notificationOutput, unsigned int connectorId = 1);\n#endif //MO_ENABLE_V201\n\n#if MO_ENABLE_CONNECTOR_LOCK\n/*\n * Set an InputOutput (reads and sets information at the same time) for forcing to unlock the\n * connector. Called as part of the OCPP operation \"UnlockConnector\"\n * Return values:\n *     - UnlockConnectorResult_Pending if action needs more time to complete (MO will call this cb again later or eventually time out)\n *     - UnlockConnectorResult_Unlocked if successful\n *     - UnlockConnectorResult_UnlockFailed if not successful (e.g. lock stuck)\n */\nvoid setOnUnlockConnectorInOut(std::function<UnlockConnectorResult()> onUnlockConnectorInOut, unsigned int connectorId = 1);\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n/*\n * Access further information about the internal state of the library\n */\n\nbool isOperative(unsigned int connectorId = 1); //if the charge point is operative (see OCPP1.6 Edit2, p. 45) and ready for transactions\n\n/*\n * Configure the device management\n */\n\nvoid setOnResetNotify(std::function<bool(bool)> onResetNotify); //call onResetNotify(isHard) before Reset. If you return false, Reset will be aborted. Optional\n\nvoid setOnResetExecute(std::function<void(bool)> onResetExecute); //reset handler. This function should reboot this controller immediately. Already defined for the ESP32 on Arduino\n\n\nnamespace MicroOcpp {\nclass FirmwareService;\nclass DiagnosticsService;\n}\n\n/*\n * You need to configure this object if FW updates are relevant for you. This project already\n * brings a simple configuration for the ESP32 and ESP8266 for prototyping purposes, however\n * for the productive system you will have to develop a configuration targeting the specific\n * OCPP backend.\n * See MicroOcpp/Model/FirmwareManagement/FirmwareService.h\n * \n * Lazy initialization: The FW Service will be created at the first call to this function\n * \n * To use, add `#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>`\n */\nMicroOcpp::FirmwareService *getFirmwareService();\n\n/*\n * This library implements the OCPP messaging side of Diagnostics, but no logging or the\n * log upload to your backend.\n * To integrate Diagnostics, see MicroOcpp/Model/Diagnostics/DiagnosticsService.h\n * \n * Lazy initialization: The Diag Service will be created at the first call to this function\n * \n * To use, add `#include <MicroOcpp/Model/Diagnostics/DiagnosticsService.h>`\n */\nMicroOcpp::DiagnosticsService *getDiagnosticsService();\n\n#if MO_ENABLE_CERT_MGMT\n/*\n * Set a custom Certificate Store which implements certificate updates on the host system. \n * MicroOcpp will forward OCPP-side update requests to the certificate store, as well as\n * query the certificate store upon server request.\n *\n * To enable OCPP-side certificate updates (UCs M03 - M05), set the build flag\n * MO_ENABLE_CERT_MGMT=1 so that this function becomes accessible.\n * \n * To use the built-in certificate store (depends on MbedTLS), set the build flag\n * MO_ENABLE_MBEDTLS=1. To not use the built-in implementation, but still enable MbedTLS,\n * additionally set MO_ENABLE_CERT_STORE_MBEDTLS=0.\n */\nvoid setCertificateStore(std::unique_ptr<MicroOcpp::CertificateStore> certStore);\n#endif //MO_ENABLE_CERT_MGMT\n\n/*\n * Add features and customize the behavior of the OCPP client\n */\n\nnamespace MicroOcpp {\nclass Context;\n}\n\n//Get access to internal functions and data structures. The returned Context object allows\n//you to bypass the facade functions of this header and implement custom functionality.\n//To use, add `#include <MicroOcpp/Core/Context.h>`\nMicroOcpp::Context *getOcppContext();\n\n/*\n * Set a listener which is notified when the OCPP lib processes an incoming operation of type\n * operationType. After the operation has been interpreted, onReceiveReq will be called with\n * the original message from the OCPP server.\n * \n * Example usage:\n * \n * setOnReceiveRequest(\"SetChargingProfile\", [] (JsonObject payload) {\n *     Serial.print(\"[main] received charging profile for connector: \"; //Arduino print function\n *     Serial.printf(\"update connector %i with chargingProfileId %i\\n\",\n *             payload[\"connectorId\"],                                  //ArduinoJson object access\n *             payload[\"csChargingProfiles\"][\"chargingProfileId\"]);\n * });\n */\nvoid setOnReceiveRequest(const char *operationType, OnReceiveReqListener onReceiveReq);\n\n/*\n * Set a listener which is notified when the OCPP lib sends the confirmation to an incoming\n * operation of type operation type. onSendConf will be passed the original output of the\n * OCPP lib.\n * \n * Example usage:\n * \n * setOnSendConf(\"RemoteStopTransaction\", [] (JsonObject payload) -> void {\n *     if (!strcmp(payload[\"status\"], \"Rejected\")) {\n *         //the OCPP lib rejected the RemoteStopTransaction command. In this example, the customer\n *         //wishes to stop the running transaction in any case and to log this case\n *         endTransaction(nullptr, \"Remote\"); //end transaction and send StopTransaction\n *         Serial.println(\"[main] override rejected RemoteStopTransaction\"); //Arduino print function\n *     }\n * });\n * \n */\nvoid setOnSendConf(const char *operationType, OnSendConfListener onSendConf);\n\n/*\n * Create and send an operation without using the built-in Operation class. This function bypasses\n * the business logic which comes with this library. E.g. you can send unknown operations, extend\n * OCPP or replace parts of the business logic with custom behavior.\n * \n * Use case 1, extend the library by sending additional operations. E.g. DataTransfer:\n * \n * sendRequest(\"DataTransfer\", [] () -> std::unique_ptr<MicroOcpp::JsonDoc> {\n *     //will be called to create the request once this operation is being sent out\n *     size_t capacity = JSON_OBJECT_SIZE(3) +\n *                       JSON_OBJECT_SIZE(2); //for calculating the required capacity, see https://arduinojson.org/v6/assistant/\n *     auto res = std::unique_ptr<MicroOcpp::JsonDoc>(new MicroOcpp::JsonDoc(capacity)); \n *     JsonObject request = *res;\n *     request[\"vendorId\"] = \"My company Ltd.\";\n *     request[\"messageId\"] = \"TargetValues\";\n *     request[\"data\"][\"battery_capacity\"] = 89;\n *     request[\"data\"][\"battery_soc\"] = 34;\n *     return res;\n * }, [] (JsonObject response) -> void {\n *     //will be called with the confirmation response of the server\n *     if (!strcmp(response[\"status\"], \"Accepted\")) {\n *         //DataTransfer has been accepted\n *         int max_energy = response[\"data\"][\"max_energy\"];\n *     }\n * });\n * \n * Use case 2, bypass the business logic of this library for custom behavior. E.g. StartTransaction:\n * \n * sendRequest(\"StartTransaction\", [] () -> std::unique_ptr<MicroOcpp::JsonDoc> {\n *     //will be called to create the request once this operation is being sent out\n *     size_t capacity = JSON_OBJECT_SIZE(4); //for calculating the required capacity, see https://arduinojson.org/v6/assistant/\n *     auto res = std::unique_ptr<MicroOcpp::JsonDoc>(new MicroOcpp::JsonDoc(capacity)); \n *     JsonObject request = res->to<JsonObject>();\n *     request[\"connectorId\"] = 1;\n *     request[\"idTag\"] = \"A9C3CE1D7B71EA\";\n *     request[\"meterStart\"] = 1234;\n *     request[\"timestamp\"] = \"2023-06-01T11:07:43Z\"; //e.g. some historic transaction\n *     return res;\n * }, [] (JsonObject response) -> void {\n *     //will be called with the confirmation response of the server\n *     const char *status = response[\"idTagInfo\"][\"status\"];\n *     int transactionId = response[\"transactionId\"];\n * });\n * \n * In Use case 2, the library won't send any further StatusNotification or StopTransaction on\n * its own.\n */\nvoid sendRequest(const char *operationType,\n            std::function<std::unique_ptr<MicroOcpp::JsonDoc> ()> fn_createReq,\n            std::function<void (JsonObject)> fn_processConf);\n\n/*\n * Set a custom handler for an incoming operation type. This will update the core Operation registry\n * of the library, potentially replacing the built-in Operation handler and bypassing the\n * business logic of this library.\n * \n * Note that when replacing an operation handler, the attached listeners will be reset.\n * \n * Example usage:\n * \n * setRequestHandler(\"DataTransfer\", [] (JsonObject request) -> void {\n *     //will be called with the request message from the server\n *     const char *vendorId = request[\"vendorId\"];\n *     const char *messageId = request[\"messageId\"];\n *     int battery_capacity = request[\"data\"][\"battery_capacity\"];\n *     int battery_soc = request[\"data\"][\"battery_soc\"];\n * }, [] () -> std::unique_ptr<MicroOcpp::JsonDoc> {\n *     //will be called  to create the response once this operation is being sent out\n *     size_t capacity = JSON_OBJECT_SIZE(2) +\n *                       JSON_OBJECT_SIZE(1); //for calculating the required capacity, see https://arduinojson.org/v6/assistant/\n *     auto res = std::unique_ptr<MicroOcpp::JsonDoc>(new MicroOcpp::JsonDoc(capacity)); \n *     JsonObject response = res->to<JsonObject>();\n *     response[\"status\"] = \"Accepted\";\n *     response[\"data\"][\"max_energy\"] = 59;\n *     return res;\n * });\n */\nvoid setRequestHandler(const char *operationType,\n            std::function<void (JsonObject)> fn_processReq,\n            std::function<std::unique_ptr<MicroOcpp::JsonDoc> ()> fn_createConf);\n\n/*\n * Send OCPP operations manually not bypassing the internal business logic\n * \n * On receipt of the .conf() response the library calls the callback function\n * \"OnReceiveConfListener onConf\" and passes the OCPP payload to it.\n * \n * For your first EVSE integration, the `onReceiveConfListener` is probably sufficient. For\n * advanced EVSE projects, the other listeners likely become relevant:\n * - `onAbortListener`: will be called whenever the engine stops trying to finish an operation\n *           normally which was initiated by this device.\n * - `onTimeoutListener`: will be executed when the operation is not answered until the timeout\n *           expires. Note that timeouts also trigger the `onAbortListener`.\n * - `onReceiveErrorListener`: will be called when the Central System returns a CallError.\n *           Again, each error also triggers the `onAbortListener`.\n * \n * The functions for sending OCPP operations are non-blocking. The program will resume immediately\n * with the code after with the subsequent code in any case.\n */\n\nvoid authorize(\n            const char *idTag,                           //RFID tag (e.g. ISO 14443 UID tag with 4 or 7 bytes)\n            OnReceiveConfListener onConf = nullptr,      //callback (confirmation received)\n            OnAbortListener onAbort = nullptr,           //callback (confirmation not received), optional\n            OnTimeoutListener onTimeout = nullptr,       //callback (timeout expired), optional\n            OnReceiveErrorListener onError = nullptr,    //callback (error code received), optional\n            unsigned int timeout = 0); //custom timeout behavior, optional\n\nbool startTransaction(\n            const char *idTag,\n            OnReceiveConfListener onConf = nullptr,\n            OnAbortListener onAbort = nullptr,\n            OnTimeoutListener onTimeout = nullptr,\n            OnReceiveErrorListener onError = nullptr,\n            unsigned int timeout = 0);\n\nbool stopTransaction(\n            OnReceiveConfListener onConf = nullptr,\n            OnAbortListener onAbort = nullptr,\n            OnTimeoutListener onTimeout = nullptr,\n            OnReceiveErrorListener onError = nullptr,\n            unsigned int timeout = 0);\n\n#endif\n"
  },
  {
    "path": "src/MicroOcpp_c.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include \"MicroOcpp_c.h\"\n#include \"MicroOcpp.h\"\n\n#include <MicroOcpp/Version.h>\n#include <MicroOcpp/Model/Certificates/Certificate_c.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Memory.h>\n\n#include <MicroOcpp/Platform.h>\n#include <MicroOcpp/Debug.h>\n\nMicroOcpp::Connection *ocppSocket = nullptr;\n\nvoid ocpp_initialize(OCPP_Connection *conn, const char *chargePointModel, const char *chargePointVendor, struct OCPP_FilesystemOpt fsopt, bool autoRecover, bool ocpp201) {\n    ocpp_initialize_full(conn, ocpp201 ?\n                                    ChargerCredentials::v201(chargePointModel, chargePointVendor) :\n                                    ChargerCredentials(chargePointModel, chargePointVendor),\n                               fsopt, autoRecover, ocpp201);\n}\n\nvoid ocpp_initialize_full(OCPP_Connection *conn, const char *bootNotificationCredentials, struct OCPP_FilesystemOpt fsopt, bool autoRecover, bool ocpp201) {\n    if (!conn) {\n        MO_DBG_ERR(\"conn is null\");\n    }\n\n    ocppSocket = reinterpret_cast<MicroOcpp::Connection*>(conn);\n\n    MicroOcpp::FilesystemOpt adaptFsopt = fsopt;\n\n    mocpp_initialize(*ocppSocket, bootNotificationCredentials, MicroOcpp::makeDefaultFilesystemAdapter(adaptFsopt), autoRecover,\n            ocpp201 ?\n                MicroOcpp::ProtocolVersion(2,0,1) :\n                MicroOcpp::ProtocolVersion(1,6));\n}\n\nvoid ocpp_initialize_full2(OCPP_Connection *conn, const char *bootNotificationCredentials, FilesystemAdapterC *filesystem, bool autoRecover, bool ocpp201) {\n    if (!conn) {\n        MO_DBG_ERR(\"conn is null\");\n    }\n\n    ocppSocket = reinterpret_cast<MicroOcpp::Connection*>(conn);\n\n    mocpp_initialize(*ocppSocket, bootNotificationCredentials, *reinterpret_cast<std::shared_ptr<MicroOcpp::FilesystemAdapter>*>(filesystem), autoRecover,\n            ocpp201 ?\n                MicroOcpp::ProtocolVersion(2,0,1) :\n                MicroOcpp::ProtocolVersion(1,6));\n}\n\nvoid ocpp_deinitialize() {\n    mocpp_deinitialize();\n}\n\nbool ocpp_is_initialized() {\n    return getOcppContext() != nullptr;\n}\n\nvoid ocpp_loop() {\n    mocpp_loop();\n}\n\n/*\n * Helper functions for transforming callback functions from C-style to C++style\n */\n\nstd::function<bool()> adaptFn(InputBool fn) {\n    return fn;\n}\n\nstd::function<bool()> adaptFn(unsigned int connectorId, InputBool_m fn) {\n    return [fn, connectorId] () {return fn(connectorId);};\n}\n\nstd::function<const char*()> adaptFn(InputString fn) {\n    return fn;\n}\n\nstd::function<const char*()> adaptFn(unsigned int connectorId, InputString_m fn) {\n    return [fn, connectorId] () {return fn(connectorId);};\n}\n\nstd::function<float()> adaptFn(InputFloat fn) {\n    return fn;\n}\n\nstd::function<float()> adaptFn(unsigned int connectorId, InputFloat_m fn) {\n    return [fn, connectorId] () {return fn(connectorId);};\n}\n\nstd::function<int()> adaptFn(InputInt fn) {\n    return fn;\n}\n\nstd::function<int()> adaptFn(unsigned int connectorId, InputInt_m fn) {\n    return [fn, connectorId] () {return fn(connectorId);};\n}\n\nstd::function<void(float)> adaptFn(OutputFloat fn) {\n    return fn;\n}\n\nstd::function<void(float, float, int)> adaptFn(OutputSmartCharging fn) {\n    return fn;\n}\n\nstd::function<void(float, float, int)> adaptFn(unsigned int connectorId, OutputSmartCharging_m fn) {\n    return [fn, connectorId] (float power, float current, int nphases) {fn(connectorId, power, current, nphases);};\n}\n\nstd::function<void(float)> adaptFn(unsigned int connectorId, OutputFloat_m fn) {\n    return [fn, connectorId] (float value) {return fn(connectorId, value);};\n}\n\nstd::function<void(void)> adaptFn(void (*fn)(void)) {\n    return fn;\n}\n\n#ifndef MO_RECEIVE_PAYLOAD_BUFSIZE\n#define MO_RECEIVE_PAYLOAD_BUFSIZE 512\n#endif\n\nchar ocpp_recv_payload_buff [MO_RECEIVE_PAYLOAD_BUFSIZE] = {'\\0'};\n\nstd::function<void(JsonObject)> adaptFn(OnMessage fn) {\n    if (!fn) return nullptr;\n    return [fn] (JsonObject payload) {\n        auto len = serializeJson(payload, ocpp_recv_payload_buff, MO_RECEIVE_PAYLOAD_BUFSIZE);\n        if (len <= 0) {\n            MO_DBG_WARN(\"Received payload buffer exceeded. Continue without payload\");\n        }\n        fn(len > 0 ? ocpp_recv_payload_buff : \"\", len);\n    };\n}\n\nMicroOcpp::OnReceiveErrorListener adaptFn(OnCallError fn) {\n    if (!fn) return nullptr;\n    return [fn] (const char *code, const char *description, JsonObject details) {\n        auto len = serializeJson(details, ocpp_recv_payload_buff, MO_RECEIVE_PAYLOAD_BUFSIZE);\n        if (len <= 0) {\n            MO_DBG_WARN(\"Received payload buffer exceeded. Continue without payload\");\n        }\n        fn(code, description, len > 0 ? ocpp_recv_payload_buff : \"\", len);\n    };\n}\n\n#if MO_ENABLE_CONNECTOR_LOCK\nstd::function<UnlockConnectorResult()> adaptFn(PollUnlockResult fn) {\n    return [fn] () {return fn();};\n}\n\nstd::function<UnlockConnectorResult()> adaptFn(unsigned int connectorId, PollUnlockResult_m fn) {\n    return [fn, connectorId] () {return fn(connectorId);};\n}\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\nbool ocpp_beginTransaction(const char *idTag) {\n    return beginTransaction(idTag);\n}\nbool ocpp_beginTransaction_m(unsigned int connectorId, const char *idTag) {\n    return beginTransaction(idTag, connectorId);\n}\n\nbool ocpp_beginTransaction_authorized(const char *idTag, const char *parentIdTag) {\n    return beginTransaction_authorized(idTag, parentIdTag);\n}\nbool ocpp_beginTransaction_authorized_m(unsigned int connectorId, const char *idTag, const char *parentIdTag) {\n    return beginTransaction_authorized(idTag, parentIdTag, connectorId);\n}\n\nbool ocpp_endTransaction(const char *idTag, const char *reason) {\n    return endTransaction(idTag, reason);\n}\nbool ocpp_endTransaction_m(unsigned int connectorId, const char *idTag, const char *reason) {\n    return endTransaction(idTag, reason, connectorId);\n}\n\nbool ocpp_endTransaction_authorized(const char *idTag, const char *reason) {\n    return endTransaction_authorized(idTag, reason);\n}\nbool ocpp_endTransaction_authorized_m(unsigned int connectorId, const char *idTag, const char *reason) {\n    return endTransaction_authorized(idTag, reason, connectorId);\n}\n\nbool ocpp_isTransactionActive() {\n    return isTransactionActive();\n}\nbool ocpp_isTransactionActive_m(unsigned int connectorId) {\n    return isTransactionActive(connectorId);\n}\n\nbool ocpp_isTransactionRunning() {\n    return isTransactionRunning();\n}\nbool ocpp_isTransactionRunning_m(unsigned int connectorId) {\n    return isTransactionRunning(connectorId);\n}\n\nconst char *ocpp_getTransactionIdTag() {\n    return getTransactionIdTag();\n}\nconst char *ocpp_getTransactionIdTag_m(unsigned int connectorId) {\n    return getTransactionIdTag(connectorId);\n}\n\nOCPP_Transaction *ocpp_getTransaction() {\n    return ocpp_getTransaction_m(1);\n}\nOCPP_Transaction *ocpp_getTransaction_m(unsigned int connectorId) {\n    #if MO_ENABLE_V201\n    {\n        if (!getOcppContext()) {\n            MO_DBG_ERR(\"OCPP uninitialized\"); //need to call mocpp_initialize before\n            return nullptr;\n        }\n        if (getOcppContext()->getModel().getVersion().major == 2) {\n            ocpp_tx_compat_setV201(true); //set the ocpp_tx C-API into v201 mode globally\n            if (getTransactionV201(connectorId)) {\n                return reinterpret_cast<OCPP_Transaction*>(getTransactionV201(connectorId));\n            } else {\n                return nullptr;\n            }\n        } else {\n            ocpp_tx_compat_setV201(false); //set the ocpp_tx C-API into v16 mode globally\n            //continue with V16 implementation\n        }\n    }\n    #endif //MO_ENABLE_V201\n    if (getTransaction(connectorId)) {\n        return reinterpret_cast<OCPP_Transaction*>(getTransaction(connectorId).get());\n    } else {\n        return nullptr;\n    }\n}\n\nbool ocpp_ocppPermitsCharge() {\n    return ocppPermitsCharge();\n}\nbool ocpp_ocppPermitsCharge_m(unsigned int connectorId) {\n    return ocppPermitsCharge(connectorId);\n}\n\nChargePointStatus ocpp_getChargePointStatus() {\n    return getChargePointStatus();\n}\n\nChargePointStatus ocpp_getChargePointStatus_m(unsigned int connectorId) {\n    return getChargePointStatus(connectorId);\n}\n\nvoid ocpp_setConnectorPluggedInput(InputBool pluggedInput) {\n    setConnectorPluggedInput(adaptFn(pluggedInput));\n}\nvoid ocpp_setConnectorPluggedInput_m(unsigned int connectorId, InputBool_m pluggedInput) {\n    setConnectorPluggedInput(adaptFn(connectorId, pluggedInput), connectorId);\n}\n\nvoid ocpp_setEnergyMeterInput(InputInt energyInput) {\n    setEnergyMeterInput(adaptFn(energyInput));\n}\nvoid ocpp_setEnergyMeterInput_m(unsigned int connectorId, InputInt_m energyInput) {\n    setEnergyMeterInput(adaptFn(connectorId, energyInput), connectorId);\n}\n\nvoid ocpp_setPowerMeterInput(InputFloat powerInput) {\n    setPowerMeterInput(adaptFn(powerInput));\n}\nvoid ocpp_setPowerMeterInput_m(unsigned int connectorId, InputFloat_m powerInput) {\n    setPowerMeterInput(adaptFn(connectorId, powerInput), connectorId);\n}\n\nvoid ocpp_setSmartChargingPowerOutput(OutputFloat maxPowerOutput) {\n    setSmartChargingPowerOutput(adaptFn(maxPowerOutput));\n}\nvoid ocpp_setSmartChargingPowerOutput_m(unsigned int connectorId, OutputFloat_m maxPowerOutput) {\n    setSmartChargingPowerOutput(adaptFn(connectorId, maxPowerOutput), connectorId);\n}\nvoid ocpp_setSmartChargingCurrentOutput(OutputFloat maxCurrentOutput) {\n    setSmartChargingCurrentOutput(adaptFn(maxCurrentOutput));\n}\nvoid ocpp_setSmartChargingCurrentOutput_m(unsigned int connectorId, OutputFloat_m maxCurrentOutput) {\n    setSmartChargingCurrentOutput(adaptFn(connectorId, maxCurrentOutput), connectorId);\n}\nvoid ocpp_setSmartChargingOutput(OutputSmartCharging chargingLimitOutput) {\n    setSmartChargingOutput(adaptFn(chargingLimitOutput));\n}\nvoid ocpp_setSmartChargingOutput_m(unsigned int connectorId, OutputSmartCharging_m chargingLimitOutput) {\n    setSmartChargingOutput(adaptFn(connectorId, chargingLimitOutput), connectorId);\n}\n\nvoid ocpp_setEvReadyInput(InputBool evReadyInput) {\n    setEvReadyInput(adaptFn(evReadyInput));\n}\nvoid ocpp_setEvReadyInput_m(unsigned int connectorId, InputBool_m evReadyInput) {\n    setEvReadyInput(adaptFn(connectorId, evReadyInput), connectorId);\n}\n\nvoid ocpp_setEvseReadyInput(InputBool evseReadyInput) {\n    setEvseReadyInput(adaptFn(evseReadyInput));\n}\nvoid ocpp_setEvseReadyInput_m(unsigned int connectorId, InputBool_m evseReadyInput) {\n    setEvseReadyInput(adaptFn(connectorId, evseReadyInput), connectorId);\n}\n\nvoid ocpp_addErrorCodeInput(InputString errorCodeInput) {\n    addErrorCodeInput(adaptFn(errorCodeInput));\n}\nvoid ocpp_addErrorCodeInput_m(unsigned int connectorId, InputString_m errorCodeInput) {\n    addErrorCodeInput(adaptFn(connectorId, errorCodeInput), connectorId);\n}\n\nvoid ocpp_addMeterValueInputFloat(InputFloat valueInput, const char *measurand, const char *unit, const char *location, const char *phase) {\n    addMeterValueInput(adaptFn(valueInput), measurand, unit, location, phase, 1);\n}\nvoid ocpp_addMeterValueInputFloat_m(unsigned int connectorId, InputFloat_m valueInput, const char *measurand, const char *unit, const char *location, const char *phase) {\n    addMeterValueInput(adaptFn(connectorId, valueInput), measurand, unit, location, phase, connectorId);\n}\n\nvoid ocpp_addMeterValueInputIntTx(int (*valueInput)(ReadingContext), const char *measurand, const char *unit, const char *location, const char *phase) {\n    MicroOcpp::SampledValueProperties props;\n    props.setMeasurand(measurand);\n    props.setUnit(unit);\n    props.setLocation(location);\n    props.setPhase(phase);\n    auto mvs = std::unique_ptr<MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>>(\n                           new MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>(\n            props,\n            [valueInput] (ReadingContext readingContext) {return valueInput(readingContext);}\n    ));\n    addMeterValueInput(std::move(mvs));\n}\nvoid ocpp_addMeterValueInputIntTx_m(unsigned int connectorId, int (*valueInput)(unsigned int cId, ReadingContext), const char *measurand, const char *unit, const char *location, const char *phase) {\n    MicroOcpp::SampledValueProperties props;\n    props.setMeasurand(measurand);\n    props.setUnit(unit);\n    props.setLocation(location);\n    props.setPhase(phase);\n    auto mvs = std::unique_ptr<MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>>(\n                           new MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>(\n            props,\n            [valueInput, connectorId] (ReadingContext readingContext) {return valueInput(connectorId, readingContext);}\n    ));\n    addMeterValueInput(std::move(mvs), connectorId);\n}\n\nvoid ocpp_addMeterValueInput(MeterValueInput *meterValueInput) {\n    ocpp_addMeterValueInput_m(1, meterValueInput);\n}\nvoid ocpp_addMeterValueInput_m(unsigned int connectorId, MeterValueInput *meterValueInput) {\n    auto svs = std::unique_ptr<MicroOcpp::SampledValueSampler>(\n        reinterpret_cast<MicroOcpp::SampledValueSampler*>(meterValueInput));\n    \n    addMeterValueInput(std::move(svs), connectorId);\n}\n\n\n#if MO_ENABLE_CONNECTOR_LOCK\nvoid ocpp_setOnUnlockConnectorInOut(PollUnlockResult onUnlockConnectorInOut) {\n    setOnUnlockConnectorInOut(adaptFn(onUnlockConnectorInOut));\n}\nvoid ocpp_setOnUnlockConnectorInOut_m(unsigned int connectorId, PollUnlockResult_m onUnlockConnectorInOut) {\n    setOnUnlockConnectorInOut(adaptFn(connectorId, onUnlockConnectorInOut), connectorId);\n}\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\nvoid ocpp_setStartTxReadyInput(InputBool startTxReady) {\n    setStartTxReadyInput(adaptFn(startTxReady));\n}\nvoid ocpp_setStartTxReadyInput_m(unsigned int connectorId, InputBool_m startTxReady) {\n    setStartTxReadyInput(adaptFn(connectorId, startTxReady), connectorId);\n}\n\nvoid ocpp_setStopTxReadyInput(InputBool stopTxReady) {\n    setStopTxReadyInput(adaptFn(stopTxReady));\n}\nvoid ocpp_setStopTxReadyInput_m(unsigned int connectorId, InputBool_m stopTxReady) {\n    setStopTxReadyInput(adaptFn(connectorId, stopTxReady), connectorId);\n}\n\nvoid ocpp_setTxNotificationOutput(void (*notificationOutput)(OCPP_Transaction*, TxNotification)) {\n    setTxNotificationOutput([notificationOutput] (MicroOcpp::Transaction *tx, TxNotification notification) {\n        notificationOutput(reinterpret_cast<OCPP_Transaction*>(tx), notification);\n    });\n}\nvoid ocpp_setTxNotificationOutput_m(unsigned int connectorId, void (*notificationOutput)(unsigned int, OCPP_Transaction*, TxNotification)) {\n    setTxNotificationOutput([notificationOutput, connectorId] (MicroOcpp::Transaction *tx, TxNotification notification) {\n        notificationOutput(connectorId, reinterpret_cast<OCPP_Transaction*>(tx), notification);\n    }, connectorId);\n}\n\nvoid ocpp_setOccupiedInput(InputBool occupied) {\n    setOccupiedInput(adaptFn(occupied));\n}\nvoid ocpp_setOccupiedInput_m(unsigned int connectorId, InputBool_m occupied) {\n    setOccupiedInput(adaptFn(connectorId, occupied), connectorId);\n}\n\nbool ocpp_isOperative() {\n    return isOperative();\n}\nbool ocpp_isOperative_m(unsigned int connectorId) {\n    return isOperative(connectorId);\n}\nvoid ocpp_setOnResetNotify(bool (*onResetNotify)(bool)) {\n    setOnResetNotify([onResetNotify] (bool isHard) {return onResetNotify(isHard);});\n}\n\nvoid ocpp_setOnResetExecute(void (*onResetExecute)(bool)) {\n    setOnResetExecute([onResetExecute] (bool isHard) {onResetExecute(isHard);});\n}\n\n#if MO_ENABLE_CERT_MGMT\nvoid ocpp_setCertificateStore(ocpp_cert_store *certs) {\n    std::unique_ptr<MicroOcpp::CertificateStore> certsCwrapper;\n    if (certs) {\n        certsCwrapper = MicroOcpp::makeCertificateStoreCwrapper(certs);\n    }\n    setCertificateStore(std::move(certsCwrapper));\n}\n#endif //MO_ENABLE_CERT_MGMT\n\nvoid ocpp_setOnReceiveRequest(const char *operationType, OnMessage onRequest) {\n    setOnReceiveRequest(operationType, adaptFn(onRequest));\n}\n\nvoid ocpp_setOnSendConf(const char *operationType, OnMessage onConfirmation) {\n    setOnSendConf(operationType, adaptFn(onConfirmation));\n}\n\nvoid ocpp_authorize(const char *idTag, AuthorizeConfCallback onConfirmation, AuthorizeAbortCallback onAbort, AuthorizeTimeoutCallback onTimeout, AuthorizeErrorCallback onError, void *user_data) {\n    \n    auto idTag_capture = MicroOcpp::makeString(\"MicroOcpp_c.cpp\", idTag);\n\n    authorize(idTag,\n            onConfirmation ? [onConfirmation, idTag_capture, user_data] (JsonObject payload) {\n                    auto len = serializeJson(payload, ocpp_recv_payload_buff, MO_RECEIVE_PAYLOAD_BUFSIZE);\n                    if (len <= 0) {MO_DBG_WARN(\"Received payload buffer exceeded. Continue without payload\");}\n                    onConfirmation(idTag_capture.c_str(), len > 0 ? ocpp_recv_payload_buff : \"\", len, user_data);\n                } : OnReceiveConfListener(nullptr),\n            onAbort ? [onAbort, idTag_capture, user_data] () -> void {\n                    onAbort(idTag_capture.c_str(), user_data);\n                } : OnAbortListener(nullptr),\n            onTimeout ? [onTimeout, idTag_capture, user_data] () {\n                    onTimeout(idTag_capture.c_str(), user_data);\n                } : OnTimeoutListener(nullptr),\n            onError ? [onError, idTag_capture, user_data] (const char *code, const char *description, JsonObject details) {\n                    auto len = serializeJson(details, ocpp_recv_payload_buff, MO_RECEIVE_PAYLOAD_BUFSIZE);\n                    if (len <= 0) {MO_DBG_WARN(\"Received payload buffer exceeded. Continue without payload\");}\n                    onError(idTag_capture.c_str(), code, description, len > 0 ? ocpp_recv_payload_buff : \"\", len, user_data);\n                } : OnReceiveErrorListener(nullptr));\n}\n\nvoid ocpp_startTransaction(const char *idTag, OnMessage onConfirmation, OnAbort onAbort, OnTimeout onTimeout, OnCallError onError) {\n    startTransaction(idTag, adaptFn(onConfirmation), adaptFn(onAbort), adaptFn(onTimeout), adaptFn(onError));\n}\n\nvoid ocpp_stopTransaction(OnMessage onConfirmation, OnAbort onAbort, OnTimeout onTimeout, OnCallError onError) {\n    stopTransaction(adaptFn(onConfirmation), adaptFn(onAbort), adaptFn(onTimeout), adaptFn(onError));\n}\n"
  },
  {
    "path": "src/MicroOcpp_c.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_MICROOCPP_C_H\n#define MO_MICROOCPP_C_H\n\n#include <stddef.h>\n\n#include <MicroOcpp/Core/ConfigurationOptions.h>\n#include <MicroOcpp/Model/ConnectorBase/ChargePointStatus.h>\n#include <MicroOcpp/Model/ConnectorBase/UnlockConnectorResult.h>\n#include <MicroOcpp/Model/Transactions/Transaction.h>\n#include <MicroOcpp/Model/Certificates/Certificate_c.h>\n#include <MicroOcpp/Model/Metering/ReadingContext.h>\n\nstruct OCPP_Connection;\ntypedef struct OCPP_Connection OCPP_Connection;\n\nstruct MeterValueInput;\ntypedef struct MeterValueInput MeterValueInput;\n\nstruct FilesystemAdapterC;\ntypedef struct FilesystemAdapterC FilesystemAdapterC;\n\ntypedef void (*OnMessage) (const char *payload, size_t len);\ntypedef void (*OnAbort)   ();\ntypedef void (*OnTimeout) ();\ntypedef void (*OnCallError)   (const char *code, const char *description, const char *details_json, size_t details_len);\ntypedef void (*AuthorizeConfCallback)    (const char *idTag, const char *payload, size_t len, void *user_data);\ntypedef void (*AuthorizeAbortCallback)   (const char *idTag, void* user_data);\ntypedef void (*AuthorizeTimeoutCallback) (const char *idTag, void* user_data);\ntypedef void (*AuthorizeErrorCallback)   (const char *idTag, const char *code, const char *description, const char *details_json, size_t details_len, void* user_data);\n\ntypedef float (*InputFloat)();\ntypedef float (*InputFloat_m)(unsigned int connectorId); //multiple connectors version\ntypedef int   (*InputInt)();\ntypedef int   (*InputInt_m)(unsigned int connectorId);\ntypedef bool  (*InputBool)();\ntypedef bool  (*InputBool_m)(unsigned int connectorId);\ntypedef const char* (*InputString)();\ntypedef const char* (*InputString_m)(unsigned int connectorId);\ntypedef void (*OutputFloat)(float limit);\ntypedef void (*OutputFloat_m)(unsigned int connectorId, float limit);\ntypedef void (*OutputSmartCharging)(float power, float current, int nphases);\ntypedef void (*OutputSmartCharging_m)(unsigned int connectorId, float power, float current, int nphases);\n\n#if MO_ENABLE_CONNECTOR_LOCK\ntypedef UnlockConnectorResult (*PollUnlockResult)();\ntypedef UnlockConnectorResult (*PollUnlockResult_m)(unsigned int connectorId);\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/*\n * Please refer to MicroOcpp.h for the documentation\n */\n\nvoid ocpp_initialize(\n            OCPP_Connection *conn,  //WebSocket adapter for MicroOcpp\n            const char *chargePointModel,     //model name of this charger (e.g. \"My Charger\")\n            const char *chargePointVendor, //brand name (e.g. \"My Company Ltd.\")\n            struct OCPP_FilesystemOpt fsopt, //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h\n            bool autoRecover, //automatically sanitize the local data store when the lib detects recurring crashes. During development, `false` is recommended\n            bool ocpp201); //true to select OCPP 2.0.1, false for OCPP 1.6\n\n//same as above, but more fields for the BootNotification\nvoid ocpp_initialize_full(\n            OCPP_Connection *conn,  //WebSocket adapter for MicroOcpp\n            const char *bootNotificationCredentials, //e.g. '{\"chargePointModel\":\"Demo Charger\",\"chargePointVendor\":\"My Company Ltd.\"}' (refer to OCPP 1.6 Specification - Edition 2 p. 60)\n            struct OCPP_FilesystemOpt fsopt, //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h\n            bool autoRecover, //automatically sanitize the local data store when the lib detects recurring crashes. During development, `false` is recommended\n            bool ocpp201); //true to select OCPP 2.0.1, false for OCPP 1.6\n\n//same as above, but pass FS handle instead of FS options\nvoid ocpp_initialize_full2(\n            OCPP_Connection *conn,  //WebSocket adapter for MicroOcpp\n            const char *bootNotificationCredentials, //e.g. '{\"chargePointModel\":\"Demo Charger\",\"chargePointVendor\":\"My Company Ltd.\"}' (refer to OCPP 1.6 Specification - Edition 2 p. 60)\n            FilesystemAdapterC *filesystem, //FilesystemAdapter handle initialized by client. MO takes ownership and deletes it during deinitialization\n            bool autoRecover, //automatically sanitize the local data store when the lib detects recurring crashes. During development, `false` is recommended\n            bool ocpp201); //true to select OCPP 2.0.1, false for OCPP 1.6\n\nvoid ocpp_deinitialize();\n\nbool ocpp_is_initialized();\n\nvoid ocpp_loop();\n\n/*\n * Charging session management\n */\n\nbool ocpp_beginTransaction(const char *idTag);\nbool ocpp_beginTransaction_m(unsigned int connectorId, const char *idTag); //multiple connectors version\n\nbool ocpp_beginTransaction_authorized(const char *idTag, const char *parentIdTag);\nbool ocpp_beginTransaction_authorized_m(unsigned int connectorId, const char *idTag, const char *parentIdTag);\n\nbool ocpp_endTransaction(const char *idTag, const char *reason); //idTag, reason can be NULL\nbool ocpp_endTransaction_m(unsigned int connectorId, const char *idTag, const char *reason); //idTag, reason can be NULL\n\nbool ocpp_endTransaction_authorized(const char *idTag, const char *reason); //idTag, reason can be NULL\nbool ocpp_endTransaction_authorized_m(unsigned int connectorId, const char *idTag, const char *reason); //idTag, reason can be NULL\n\nbool ocpp_isTransactionActive();\nbool ocpp_isTransactionActive_m(unsigned int connectorId);\n\nbool ocpp_isTransactionRunning();\nbool ocpp_isTransactionRunning_m(unsigned int connectorId);\n\nconst char *ocpp_getTransactionIdTag();\nconst char *ocpp_getTransactionIdTag_m(unsigned int connectorId);\n\nOCPP_Transaction *ocpp_getTransaction();\nOCPP_Transaction *ocpp_getTransaction_m(unsigned int connectorId);\n\nbool ocpp_ocppPermitsCharge();\nbool ocpp_ocppPermitsCharge_m(unsigned int connectorId);\n\nChargePointStatus ocpp_getChargePointStatus();\nChargePointStatus ocpp_getChargePointStatus_m(unsigned int connectorId);\n\n/*\n * Define the Inputs and Outputs of this library.\n */\n\nvoid ocpp_setConnectorPluggedInput(InputBool pluggedInput);\nvoid ocpp_setConnectorPluggedInput_m(unsigned int connectorId, InputBool_m pluggedInput);\n\nvoid ocpp_setEnergyMeterInput(InputInt energyInput);\nvoid ocpp_setEnergyMeterInput_m(unsigned int connectorId, InputInt_m energyInput);\n\nvoid ocpp_setPowerMeterInput(InputFloat powerInput);\nvoid ocpp_setPowerMeterInput_m(unsigned int connectorId, InputFloat_m powerInput);\n\nvoid ocpp_setSmartChargingPowerOutput(OutputFloat maxPowerOutput);\nvoid ocpp_setSmartChargingPowerOutput_m(unsigned int connectorId, OutputFloat_m maxPowerOutput);\nvoid ocpp_setSmartChargingCurrentOutput(OutputFloat maxCurrentOutput);\nvoid ocpp_setSmartChargingCurrentOutput_m(unsigned int connectorId, OutputFloat_m maxCurrentOutput);\nvoid ocpp_setSmartChargingOutput(OutputSmartCharging chargingLimitOutput);\nvoid ocpp_setSmartChargingOutput_m(unsigned int connectorId, OutputSmartCharging_m chargingLimitOutput);\n\n/*\n * Define the Inputs and Outputs of this library. (Advanced)\n */\n\nvoid ocpp_setEvReadyInput(InputBool evReadyInput);\nvoid ocpp_setEvReadyInput_m(unsigned int connectorId, InputBool_m evReadyInput);\n\nvoid ocpp_setEvseReadyInput(InputBool evseReadyInput);\nvoid ocpp_setEvseReadyInput_m(unsigned int connectorId, InputBool_m evseReadyInput);\n\nvoid ocpp_addErrorCodeInput(InputString errorCodeInput);\nvoid ocpp_addErrorCodeInput_m(unsigned int connectorId, InputString_m errorCodeInput);\n\nvoid ocpp_addMeterValueInputFloat(InputFloat valueInput, const char *measurand, const char *unit, const char *location, const char *phase); //measurand, unit, location and phase can be NULL\nvoid ocpp_addMeterValueInputFloat_m(unsigned int connectorId, InputFloat_m valueInput, const char *measurand, const char *unit, const char *location, const char *phase); //measurand, unit, location and phase can be NULL\n\nvoid ocpp_addMeterValueInputIntTx(int (*valueInput)(ReadingContext), const char *measurand, const char *unit, const char *location, const char *phase); //measurand, unit, location and phase can be NULL\nvoid ocpp_addMeterValueInputIntTx_m(unsigned int connectorId, int (*valueInput)(unsigned int cId, ReadingContext), const char *measurand, const char *unit, const char *location, const char *phase); //measurand, unit, location and phase can be NULL\n\nvoid ocpp_addMeterValueInput(MeterValueInput *meterValueInput); //takes ownership of meterValueInput\nvoid ocpp_addMeterValueInput_m(unsigned int connectorId, MeterValueInput *meterValueInput); //takes ownership of meterValueInput\n\nvoid ocpp_setOccupiedInput(InputBool occupied);\nvoid ocpp_setOccupiedInput_m(unsigned int connectorId, InputBool_m occupied);\n\nvoid ocpp_setStartTxReadyInput(InputBool startTxReady);\nvoid ocpp_setStartTxReadyInput_m(unsigned int connectorId, InputBool_m startTxReady);\n\nvoid ocpp_setStopTxReadyInput(InputBool stopTxReady);\nvoid ocpp_setStopTxReadyInput_m(unsigned int connectorId, InputBool_m stopTxReady);\n\nvoid ocpp_setTxNotificationOutput(void (*notificationOutput)(OCPP_Transaction*, TxNotification));\nvoid ocpp_setTxNotificationOutput_m(unsigned int connectorId, void (*notificationOutput)(unsigned int, OCPP_Transaction*, TxNotification));\n\n#if MO_ENABLE_CONNECTOR_LOCK\nvoid ocpp_setOnUnlockConnectorInOut(PollUnlockResult onUnlockConnectorInOut);\nvoid ocpp_setOnUnlockConnectorInOut_m(unsigned int connectorId, PollUnlockResult_m onUnlockConnectorInOut);\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n/*\n * Access further information about the internal state of the library\n */\n\nbool ocpp_isOperative();\nbool ocpp_isOperative_m(unsigned int connectorId);\n\nvoid ocpp_setOnResetNotify(bool (*onResetNotify)(bool));\n\nvoid ocpp_setOnResetExecute(void (*onResetExecute)(bool));\n\n#if MO_ENABLE_CERT_MGMT\nvoid ocpp_setCertificateStore(ocpp_cert_store *certs);\n#endif //MO_ENABLE_CERT_MGMT\n\nvoid ocpp_setOnReceiveRequest(const char *operationType, OnMessage onRequest);\n\nvoid ocpp_setOnSendConf(const char *operationType, OnMessage onConfirmation);\n\n/*\n * Send OCPP operations\n */\nvoid ocpp_authorize(const char *idTag, AuthorizeConfCallback onConfirmation, AuthorizeAbortCallback onAbort, AuthorizeTimeoutCallback onTimeout, AuthorizeErrorCallback onError, void *user_data);\n\nvoid ocpp_startTransaction(const char *idTag, OnMessage onConfirmation, OnAbort onAbort, OnTimeout onTimeout, OnCallError onError);\n\nvoid ocpp_stopTransaction(OnMessage onConfirmation, OnAbort onAbort, OnTimeout onTimeout, OnCallError onError);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "tests/Api.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Debug.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#include <array>\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n#define SCPROFILE \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":0,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":0,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"ChargePointMaxProfile\\\",\\\"chargingProfileKind\\\":\\\"Absolute\\\",\\\"chargingSchedule\\\":{\\\"duration\\\":1000000,\\\"startSchedule\\\":\\\"2023-01-01T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"W\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":16,\\\"numberPhases\\\":3}]}}}]\"\n\nTEST_CASE( \"C++ API test\" ) {\n    printf(\"\\nRun %s\\n\",  \"C++ API test\");\n\n    //initialize Context with dummy socket\n    MicroOcpp::LoopbackConnection loopback;\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n    auto context = getOcppContext();\n    auto& model = context->getModel();\n\n    mocpp_set_timer(custom_timer_cb);\n\n    model.getClock().setTime(BASE_TIME);\n\n    endTransaction();\n\n    SECTION(\"Run all functions\") {\n\n        //Set all possible Inputs and outputs\n        std::array<bool, 1024> checkpoints {false};\n        size_t ncheck = 0;\n\n        setConnectorPluggedInput([c = &checkpoints[ncheck++]] () -> bool {*c = true; return true;});\n        setEnergyMeterInput([c = &checkpoints[ncheck++]] () -> float {*c = true; return 0.f;});\n        setPowerMeterInput([c = &checkpoints[ncheck++]] () -> float {*c = true; return 0.f;});\n        setSmartChargingPowerOutput([] (float) {}); //overridden by CurrentOutput\n        setSmartChargingCurrentOutput([] (float) {}); //overridden by generic SmartChargingOutput\n        setSmartChargingOutput([c = &checkpoints[ncheck++]] (float, float, int) {*c = true;});\n        setEvReadyInput([c = &checkpoints[ncheck++]] () -> bool {*c = true; return true;});\n        setEvseReadyInput([c = &checkpoints[ncheck++]] () -> bool {*c = true; return true;});\n        addErrorCodeInput([c = &checkpoints[ncheck++]] () -> const char* {*c = true; return nullptr;});\n        addErrorDataInput([c = &checkpoints[ncheck++]] () -> MicroOcpp::ErrorData {*c = true; return nullptr;});\n        addMeterValueInput([c = &checkpoints[ncheck++]] () -> float {*c = true; return 0.f;}, \"Current.Import\");\n\n        MicroOcpp::SampledValueProperties svprops;\n        svprops.setMeasurand(\"Current.Offered\");\n        auto valueSampler = std::unique_ptr<MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>>(\n                                        new MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>(\n                    svprops,\n                    [c = &checkpoints[ncheck++]] (ReadingContext) -> int32_t {*c = true; return 0;}));\n        addMeterValueInput(std::move(valueSampler));\n\n        setOccupiedInput([c = &checkpoints[ncheck++]] () -> bool {*c = true; return false;});\n        setStartTxReadyInput([c = &checkpoints[ncheck++]] () -> bool {*c = true; return true;});\n        setStopTxReadyInput([c = &checkpoints[ncheck++]] () -> bool {*c = true; return true;});\n        setTxNotificationOutput([c = &checkpoints[ncheck++]] (MicroOcpp::Transaction*, TxNotification) {*c = true;});\n\n#if MO_ENABLE_CONNECTOR_LOCK\n        setOnUnlockConnectorInOut([c = &checkpoints[ncheck++]] () -> UnlockConnectorResult {*c = true; return UnlockConnectorResult_Unlocked;});\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n        setOnResetNotify([c = &checkpoints[ncheck++]] (bool) -> bool {*c = true; return true;});\n        setOnResetExecute([c = &checkpoints[ncheck++]] (bool) {*c = true;});\n\n        REQUIRE( getFirmwareService() != nullptr );\n        REQUIRE( getDiagnosticsService() != nullptr );\n        REQUIRE( getOcppContext() != nullptr );\n\n        setOnReceiveRequest(\"StatusNotification\", [c = &checkpoints[ncheck++]] (JsonObject) {*c = true;});\n        setOnSendConf(\"StatusNotification\", [c = &checkpoints[ncheck++]] (JsonObject) {*c = true;});\n        sendRequest(\"DataTransfer\", [c = &checkpoints[ncheck++]] () {\n            *c = true;\n            auto doc = MicroOcpp::makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n            doc->to<JsonObject>();\n            return doc;\n        }, [c = &checkpoints[ncheck++]] (JsonObject) {*c = true;});\n        setRequestHandler(\"DataTransfer\", [c = &checkpoints[ncheck++]] (JsonObject) {*c = true;}, [c = &checkpoints[ncheck++]] () {\n            *c = true;\n            auto doc = MicroOcpp::makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n            doc->to<JsonObject>();\n            return doc;\n        });\n\n        //set configuration which uses all Inputs and Outputs\n\n        auto MeterValuesSampledDataString = MicroOcpp::declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Energy.Active.Import.Register,Power.Active.Import,Current.Import,Current.Offered\");\n\n        loopback.sendTXT(SCPROFILE, strlen(SCPROFILE));\n\n        //run tx management\n\n        mocpp_loop();\n\n        loop();\n\n        beginTransaction(\"mIdTag\");\n\n        loop();\n\n        REQUIRE(isTransactionActive());\n        REQUIRE(isTransactionRunning());\n        REQUIRE(getTransactionIdTag() != nullptr);\n        REQUIRE(getTransaction() != nullptr);\n        REQUIRE(ocppPermitsCharge());\n\n        endTransaction();\n\n        loop();\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n\n        mtime += 3600 * 1000;\n\n        loop();\n\n        endTransaction();\n\n        loop();\n\n        authorize(\"mIdTag\", [c = &checkpoints[ncheck++]] (JsonObject) {*c = true;});\n        startTransaction(\"mIdTag\", [c = &checkpoints[ncheck++]] (JsonObject) {*c = true;});\n\n        loop();\n\n        stopTransaction([c = &checkpoints[ncheck++]] (JsonObject) {*c = true;});\n\n        //occupied Input will be validated when vehiclePlugged is false or undefined\n        setConnectorPluggedInput(nullptr);\n\n        loop();\n\n        //run device management\n\n        REQUIRE(isOperative());\n\n        sendRequest(\"UnlockConnector\", [] () {\n            auto doc = MicroOcpp::makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n            (*doc)[\"connectorId\"] = 1;\n            return doc;\n        }, [] (JsonObject) {});\n\n        sendRequest(\"Reset\", [] () {\n            auto doc = MicroOcpp::makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n            (*doc)[\"type\"] = \"Hard\";\n            return doc;\n        }, [] (JsonObject) {});\n\n        loop();\n\n        mtime += 3600 * 1000;\n\n        loop();\n        \n        MO_DBG_DEBUG(\"added %zu checkpoints\", ncheck);\n\n        bool checkpointsPassed = true;\n        for (unsigned int i = 0; i < ncheck; i++) {\n            if (!checkpoints[i]) {\n                MO_DBG_ERR(\"missed checkpoint %u\", i);\n                checkpointsPassed = false;\n            }\n        }\n\n        REQUIRE(checkpointsPassed);\n    }\n\n    mocpp_deinitialize();\n\n    REQUIRE(!getOcppContext());\n}\n\n#include <MicroOcpp_c.h>\n#include <MicroOcpp/Core/ConfigurationOptions.h>\n\nstd::array<bool, 1024> checkpointsc {false};\nsize_t ncheckc = 0;\n\nTEST_CASE( \"C API test\" ) {\n\n    //initialize Context with dummy socket\n    struct OCPP_FilesystemOpt fsopt;\n    fsopt.use = true;\n    fsopt.mount = true;\n    fsopt.formatFsOnFail = true;\n\n    MicroOcpp::LoopbackConnection loopback;\n    ocpp_initialize(reinterpret_cast<OCPP_Connection*>(&loopback), \"test-runner1234\", \"vendor\", fsopt, false, false);\n    \n    auto context = getOcppContext();\n    auto& model = context->getModel();\n\n    mocpp_set_timer(custom_timer_cb);\n\n    model.getClock().setTime(BASE_TIME);\n\n    ocpp_endTransaction(NULL, NULL);\n\n    SECTION(\"Run all functions\") {\n\n        ocpp_setConnectorPluggedInput([] () -> bool {checkpointsc[0] = true; return true;}); ncheckc++;\n        ocpp_setConnectorPluggedInput_m(2, [] (unsigned int) -> bool {checkpointsc[1] = true; return true;}); ncheckc++;\n        ocpp_setEnergyMeterInput([] () -> int {checkpointsc[2] = true; return 0;}); ncheckc++;\n        ocpp_setEnergyMeterInput_m(2, [] (unsigned int) -> int {checkpointsc[3] = true; return 0;}); ncheckc++;\n        ocpp_setPowerMeterInput([] () -> float {checkpointsc[4] = true; return 0.f;}); ncheckc++;\n        ocpp_setPowerMeterInput_m(2, [] (unsigned int) -> float {checkpointsc[5] = true; return 0.f;}); ncheckc++;\n        ocpp_setSmartChargingPowerOutput([] (float) {}); //overridden by CurrentOutput\n        ocpp_setSmartChargingPowerOutput_m(2, [] (unsigned int, float) {}); //overridden by CurrentOutput\n        ocpp_setSmartChargingCurrentOutput([] (float) {}); //overridden by generic SmartChargingOutput\n        ocpp_setSmartChargingCurrentOutput_m(2, [] (unsigned int, float) {}); //overridden by generic SmartChargingOutput\n        ocpp_setSmartChargingOutput([] (float, float, int) {checkpointsc[6] = true;}); ncheckc++;\n        ocpp_setSmartChargingOutput_m(2, [] (unsigned int, float, float, int) {checkpointsc[7] = true;}); ncheckc++;\n        ocpp_setEvReadyInput([] () -> bool {checkpointsc[8] = true; return true;}); ncheckc++;\n        ocpp_setEvReadyInput_m(2, [] (unsigned int) -> bool {checkpointsc[9] = true; return true;}); ncheckc++;\n        ocpp_setEvseReadyInput([] () -> bool {checkpointsc[10] = true; return true;}); ncheckc++;\n        ocpp_setEvseReadyInput_m(2, [] (unsigned int) -> bool {checkpointsc[11] = true; return true;}); ncheckc++;\n        ocpp_addErrorCodeInput([] () -> const char* {checkpointsc[12] = true; return nullptr;}); ncheckc++;\n        ocpp_addErrorCodeInput_m(2, [] (unsigned int) -> const char* {checkpointsc[13] = true; return nullptr;}); ncheckc++;\n        ocpp_addMeterValueInputFloat([] () -> float {checkpointsc[14] = true; return 0.f;}, \"Current.Import\", \"A\", NULL, NULL); ncheckc++;\n        ocpp_addMeterValueInputFloat_m(2, [] (unsigned int) -> float {checkpointsc[15] = true; return 0.f;}, \"Current.Import\", \"A\", NULL, NULL); ncheckc++;\n        \n        MicroOcpp::SampledValueProperties svprops;\n        svprops.setMeasurand(\"Current.Offered\");\n        auto valueSampler = std::unique_ptr<MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>>(\n                                        new MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>(\n                    svprops,\n                    [] (ReadingContext) -> int32_t {checkpointsc[16] = true; return 0;})); ncheckc++;\n        ocpp_addMeterValueInput(reinterpret_cast<MeterValueInput*>(valueSampler.release()));\n\n        valueSampler = std::unique_ptr<MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>>(\n                                        new MicroOcpp::SampledValueSamplerConcrete<int32_t, MicroOcpp::SampledValueDeSerializer<int32_t>>(\n                    svprops,\n                    [] (ReadingContext) -> int32_t {checkpointsc[17] = true; return 0;})); ncheckc++;\n        ocpp_addMeterValueInput_m(2, reinterpret_cast<MeterValueInput*>(valueSampler.release()));\n\n        ocpp_setOccupiedInput([] () -> bool {checkpointsc[18] = true; return true;}); ncheckc++;\n        ocpp_setOccupiedInput_m(2, [] (unsigned int) -> bool {checkpointsc[19] = true; return true;}); ncheckc++;\n        ocpp_setStartTxReadyInput([] () -> bool {checkpointsc[20] = true; return true;}); ncheckc++;\n        ocpp_setStartTxReadyInput_m(2, [] (unsigned int) -> bool {checkpointsc[21] = true; return true;}); ncheckc++;\n        ocpp_setStopTxReadyInput([] () -> bool {checkpointsc[22] = true; return true;}); ncheckc++;\n        ocpp_setStopTxReadyInput_m(2, [] (unsigned int) -> bool {checkpointsc[23] = true; return true;}); ncheckc++;\n        ocpp_setTxNotificationOutput([] (OCPP_Transaction*, TxNotification) {checkpointsc[24] = true;}); ncheckc++;\n        ocpp_setTxNotificationOutput_m(2, [] (unsigned int, OCPP_Transaction*, TxNotification) {checkpointsc[25] = true;}); ncheckc++;\n\n#if MO_ENABLE_CONNECTOR_LOCK\n        ocpp_setOnUnlockConnectorInOut([] () -> UnlockConnectorResult {checkpointsc[26] = true; return UnlockConnectorResult_Unlocked;}); ncheckc++;\n        ocpp_setOnUnlockConnectorInOut_m(2, [] (unsigned int) -> UnlockConnectorResult {checkpointsc[27] = true; return UnlockConnectorResult_Unlocked;}); ncheckc++;\n#else\n        checkpointsc[26] = true;\n        checkpointsc[27] = true;\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n        ocpp_setOnResetNotify([] (bool) -> bool {checkpointsc[28] = true; return true;}); ncheckc++;\n        ocpp_setOnResetExecute([] (bool) {checkpointsc[29] = true;}); ncheckc++;\n\n        ocpp_setOnReceiveRequest(\"StatusNotification\", [] (const char*,size_t) {checkpointsc[30] = true;}); ncheckc++;\n        ocpp_setOnSendConf(\"StatusNotification\", [] (const char*,size_t) {checkpointsc[31] = true;}); ncheckc++;\n\n        //set configuration which uses all Inputs and Outputs\n\n        auto MeterValuesSampledDataString = MicroOcpp::declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Energy.Active.Import.Register,Power.Active.Import,Current.Import,Current.Offered\");\n\n        loopback.sendTXT(SCPROFILE, strlen(SCPROFILE));\n\n        //run tx management\n\n        ocpp_loop();\n\n        loop();\n\n        ocpp_beginTransaction(\"mIdTag\");\n        ocpp_beginTransaction_m(2, \"mIdTag\");\n\n        loop();\n\n        REQUIRE(ocpp_isTransactionActive());\n        REQUIRE(ocpp_isTransactionActive_m(2));\n        REQUIRE(ocpp_isTransactionRunning());\n        REQUIRE(ocpp_isTransactionRunning_m(2));\n        REQUIRE(ocpp_getTransactionIdTag() != nullptr);\n        REQUIRE(ocpp_getTransactionIdTag_m(2) != nullptr);\n        REQUIRE(ocpp_getTransaction() != nullptr);\n        REQUIRE(ocpp_getTransaction_m(2) != nullptr);\n        REQUIRE(ocpp_ocppPermitsCharge());\n        REQUIRE(ocpp_ocppPermitsCharge_m(2));\n\n        ocpp_endTransaction(\"mIdTag\", NULL);\n        ocpp_endTransaction_m(2, \"mIdTag\", NULL);\n\n        loop();\n\n        ocpp_beginTransaction_authorized(\"mIdTag\", NULL);\n        ocpp_beginTransaction_authorized_m(2, \"mIdTag\", NULL);\n\n        loop();\n\n        mtime += 3600 * 1000;\n\n        loop();\n\n        ocpp_endTransaction_authorized(NULL, NULL);\n        ocpp_endTransaction_authorized_m(2, NULL, NULL);\n\n        loop();\n\n        ocpp_authorize(\"mIdTag\", [] (const char*,const char*,size_t,void*) {checkpointsc[32] = true;}, NULL, NULL, NULL, NULL); ncheckc++;\n        ocpp_startTransaction(\"mIdTag\", [] (const char*,size_t) {checkpointsc[33] = true;}, NULL, NULL, NULL); ncheckc++;\n\n        loop();\n\n        ocpp_stopTransaction([] (const char*,size_t) {checkpointsc[34] = true;}, NULL, NULL, NULL); ncheckc++;\n\n        //occupied Input will be validated when vehiclePlugged is false or undefined\n        ocpp_setConnectorPluggedInput([] () {return false;});\n        ocpp_setConnectorPluggedInput_m(2,[] (unsigned int) {return false;});\n\n        loop();\n\n        //run device management\n\n        REQUIRE(ocpp_isOperative());\n        REQUIRE(ocpp_isOperative_m(2));\n\n        sendRequest(\"UnlockConnector\", [] () {\n            auto doc = MicroOcpp::makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n            (*doc)[\"connectorId\"] = 1;\n            return doc;\n        }, [] (JsonObject) {});\n        sendRequest(\"UnlockConnector\", [] () {\n            auto doc = MicroOcpp::makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n            (*doc)[\"connectorId\"] = 2;\n            return doc;\n        }, [] (JsonObject) {});\n\n        sendRequest(\"Reset\", [] () {\n            auto doc = MicroOcpp::makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n            (*doc)[\"type\"] = \"Hard\";\n            return doc;\n        }, [] (JsonObject) {});\n\n        loop();\n\n        mtime += 3600 * 1000;\n\n        loop();\n        \n        MO_DBG_DEBUG(\"added %zu checkpoints\", ncheckc);\n\n        bool checkpointsPassed = true;\n        for (unsigned int i = 0; i < ncheckc; i++) {\n            if (!checkpointsc[i]) {\n                MO_DBG_ERR(\"missed checkpoint %u\", i);\n                checkpointsPassed = false;\n            }\n        }\n\n        REQUIRE(checkpointsPassed);\n    }\n\n    ocpp_deinitialize();\n\n    REQUIRE(!getOcppContext());\n}\n"
  },
  {
    "path": "tests/Boot.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Operations/BootNotification.h>\n#include <MicroOcpp/Operations/StatusNotification.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Model/Boot/BootService.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Debug.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#define CHARGEPOINTMODEL \"Test model\"\n#define CHARGEPOINTVENDOR \"Test vendor\"\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\n#define GET_CONFIGURATION \"[2,\\\"msgId01\\\",\\\"GetConfiguration\\\",{\\\"key\\\":[]}]\"\n#define TRIGGER_MESSAGE \"[2,\\\"msgId02\\\",\\\"TriggerMessage\\\",{\\\"requestedMessage\\\":\\\"TriggeredOperation\\\"}]\"\n\nusing namespace MicroOcpp;\n\n//dummy operation type to test TriggerMessage\nclass TriggeredOperation : public Operation {\nprivate:\n    bool& checkExecuted;\npublic:\n    TriggeredOperation(bool& checkExecuted) : checkExecuted(checkExecuted) { }\n    const char* getOperationType() override {return \"TriggeredOperation\";}\n    std::unique_ptr<JsonDoc> createReq() override {\n        checkExecuted = true;\n        return createEmptyDocument();\n    }\n    void processConf(JsonObject) override {}\n    void processReq(JsonObject) override {}\n    std::unique_ptr<JsonDoc> createConf() override {return createEmptyDocument();}\n};\n\n\nTEST_CASE( \"Boot Behavior\" ) {\n    printf(\"\\nRun %s\\n\",  \"Boot Behavior\");\n\n    //clean state\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n\n\n    mocpp_initialize(loopback, ChargerCredentials(CHARGEPOINTMODEL, CHARGEPOINTVENDOR), filesystem);\n\n    mocpp_set_timer(custom_timer_cb);\n\n    SECTION(\"BootNotification - Accepted\") {\n\n        bool checkProcessed = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"BootNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"BootNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        checkProcessed = true;\n                        REQUIRE( !strcmp(payload[\"chargePointModel\"] | \"_Undefined\", CHARGEPOINTMODEL) );\n                        REQUIRE( !strcmp(payload[\"chargePointVendor\"] | \"_Undefined\", CHARGEPOINTVENDOR) );\n                    },\n                    [] () {\n                        //create conf\n                        auto conf = makeJsonDoc(UNIT_MEM_TAG, 1024);\n                        (*conf)[\"currentTime\"] = BASE_TIME;\n                        (*conf)[\"interval\"] = 3600;\n                        (*conf)[\"status\"] = \"Accepted\";\n                        return conf;\n                    });\n            });\n        \n        loop();\n\n        REQUIRE(checkProcessed);\n        REQUIRE(getOcppContext()->getModel().getClock().now() >= MIN_TIME);\n    }\n\n    SECTION(\"BootNotification - Pending\") {\n\n        MO_DBG_INFO(\"Queue messages before BootNotification to see if they come through\");\n\n        loop(); //normal BootNotification run\n\n        REQUIRE( isOperative() ); //normal BN succeeded\n\n        loopback.setConnected( false );\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n\n        endTransaction();\n\n        mocpp_deinitialize();\n\n        loopback.setConnected( true );\n\n        MO_DBG_INFO(\"Start charger again with queued transaction messages, also init non-tx-related msg, but now delay BN procedure\");\n\n        mocpp_initialize(loopback, ChargerCredentials());\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"BootNotification\",\n            [] () {\n                return new Ocpp16::CustomOperation(\"BootNotification\",\n                    [] (JsonObject payload) {\n                        //ignore req\n                    },\n                    [] () {\n                        //create conf\n                        auto conf = makeJsonDoc(UNIT_MEM_TAG, 1024);\n                        (*conf)[\"currentTime\"] = BASE_TIME;\n                        (*conf)[\"interval\"] = 3600;\n                        (*conf)[\"status\"] = \"Pending\";\n                        return conf;\n                    });\n            });\n\n        bool sentTxMsg = false;\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StartTransaction\",\n            [&sentTxMsg] (JsonObject) {\n                sentTxMsg = true;\n            });\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StopTransaction\",\n            [&sentTxMsg] (JsonObject) {\n                sentTxMsg = true;\n            });\n        \n        bool checkProcessedHeartbeat = false;\n\n        auto heartbeat = makeRequest(new Ocpp16::CustomOperation(\n                \"Heartbeat\",\n                [] () {\n                    //create req\n                    return createEmptyDocument();},\n                [&checkProcessedHeartbeat] (JsonObject) {\n                    //process conf\n                    checkProcessedHeartbeat = true;\n                }));\n        heartbeat->setTimeout(0); //disable timeout and check if message will be sent later\n        \n        getOcppContext()->initiateRequest(std::move(heartbeat));\n\n        bool sentNonTxMsg = false;\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"Heartbeat\",\n            [&sentNonTxMsg] (JsonObject) {\n                sentNonTxMsg = true;\n            });\n\n        loop();\n\n        REQUIRE( !sentTxMsg );\n        REQUIRE( !sentNonTxMsg );\n        REQUIRE( !checkProcessedHeartbeat );\n\n        MO_DBG_INFO(\"Check if charger still responds to server-side messages and executes TriggerMessages\");\n\n        bool reactedToServerMsg = false;\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"GetConfiguration\",\n            [&reactedToServerMsg] (JsonObject) {\n                reactedToServerMsg = true;\n            });\n\n        loopback.sendTXT(GET_CONFIGURATION, sizeof(GET_CONFIGURATION) - 1);\n\n        loop();\n\n        REQUIRE( reactedToServerMsg );\n\n        bool executedTriggerMessage = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"TriggeredOperation\",\n            [&executedTriggerMessage] () {return new TriggeredOperation(executedTriggerMessage);});\n        \n        loopback.sendTXT(TRIGGER_MESSAGE, sizeof(TRIGGER_MESSAGE) - 1);\n\n        loop();\n\n        REQUIRE( executedTriggerMessage );\n\n        //other messages still didn't get through?\n        REQUIRE( !sentTxMsg );\n        REQUIRE( !sentNonTxMsg );\n        REQUIRE( !checkProcessedHeartbeat );\n\n        MO_DBG_INFO(\"Now, accept BN and check if all queued messages finally arrive\");\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"BootNotification\",\n            [] () {\n                return new Ocpp16::CustomOperation(\"BootNotification\",\n                    [] (JsonObject payload) {\n                        //ignore req\n                    },\n                    [] () {\n                        //create conf\n                        auto conf = makeJsonDoc(UNIT_MEM_TAG, 1024);\n                        (*conf)[\"currentTime\"] = BASE_TIME;\n                        (*conf)[\"interval\"] = 3600;\n                        (*conf)[\"status\"] = \"Accepted\";\n                        return conf;\n                    });\n            });\n\n        mtime += 3600 * 1000;\n\n        loop();\n\n        REQUIRE( sentTxMsg );\n        REQUIRE( sentNonTxMsg );\n        REQUIRE( checkProcessedHeartbeat );\n    }\n\n    SECTION(\"PreBoot transactions\") {\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true)->setBool(true);\n        declareConfiguration<bool>(\"AllowOfflineTxForUnknownId\", true)->setBool(true);\n\n        unsigned int startTxCount = 0;\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StartTransaction\",\n            [&startTxCount] (JsonObject) {\n                startTxCount++;\n            });\n\n        //start one transaction in full offline mode\n\n        loopback.setConnected( false );\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n\n        beginTransaction(\"mIdTag\");\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging ); \n\n        endTransaction(\"mIdTag\");\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n\n        //start another transaction while BN is pending\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"BootNotification\",\n            [] () {\n                return new Ocpp16::CustomOperation(\"BootNotification\",\n                    [] (JsonObject payload) {\n                        //ignore req\n                    },\n                    [] () {\n                        //create conf\n                        auto conf = makeJsonDoc(UNIT_MEM_TAG, 1024);\n                        (*conf)[\"currentTime\"] = BASE_TIME;\n                        (*conf)[\"interval\"] = 3600;\n                        (*conf)[\"status\"] = \"Pending\";\n                        return conf;\n                    });\n            });\n\n        loopback.setConnected( true );\n        loop();\n        REQUIRE( startTxCount == 0 );\n\n        beginTransaction(\"mIdTag2\");\n        mtime += 20 * 1000;\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging ); \n\n        endTransaction();\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n\n        REQUIRE( startTxCount == 0 );\n\n        //Now, accept BN and check again\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"BootNotification\",\n            [] () {\n                return new Ocpp16::CustomOperation(\"BootNotification\",\n                    [] (JsonObject payload) {\n                        //ignore req\n                    },\n                    [] () {\n                        //create conf\n                        auto conf = makeJsonDoc(UNIT_MEM_TAG, 1024);\n                        (*conf)[\"currentTime\"] = BASE_TIME;\n                        (*conf)[\"interval\"] = 3600;\n                        (*conf)[\"status\"] = \"Accepted\";\n                        return conf;\n                    });\n            });\n\n        mtime += 3600 * 1000;\n\n        loop();\n        REQUIRE( startTxCount == 2 );\n\n    }\n\n    SECTION(\"Auto recovery\") {\n\n        //start transaction which will persist a few boot cycles, but then will be wiped by auto recovery\n        loop();\n        beginTransaction(\"mIdTag\");\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging );\n\n        declareConfiguration<const char*>(\"keepConfigOverRecovery\", \"originalVal\");\n        configuration_save();\n\n        mocpp_deinitialize();\n\n        //MO has 2 unexpected power cycles. Probably just back luck - keep the local state and configuration\n\n        //Increase the power cycle counter manually because it's not possible to interrupt the MO lifecycle during unit tests\n        BootStats bootstats;\n        BootService::loadBootStats(filesystem, bootstats);\n        bootstats.bootNr += 2;\n        BootService::storeBootStats(filesystem, bootstats);\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, /*enable auto recovery*/ true);\n        BootService::loadBootStats(filesystem, bootstats);\n        REQUIRE( bootstats.getBootFailureCount() == 2 + 1 ); //two boot failures have been measured, +1 because each power cycle is counted as potentially failing until reaching the long runtime barrier\n\n        loop();\n\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging );\n\n        REQUIRE( !strcmp(declareConfiguration<const char*>(\"keepConfigOverRecovery\", \"otherVal\")->getString(), \"originalVal\") );\n\n        //check that the power cycle counter has been updated properly after the controller has been running stable over a long time\n        mtime += MO_BOOTSTATS_LONGTIME_MS;\n        loop();\n        BootService::loadBootStats(filesystem, bootstats);\n        REQUIRE( bootstats.getBootFailureCount() == 0 );\n\n        mocpp_deinitialize();\n\n        //MO has 10 power cycles without running for at least 3 minutes and wipes the local state, but keeps the configuration\n\n        BootStats bootstats2;\n        BootService::loadBootStats(filesystem, bootstats2);\n        bootstats2.bootNr += 10;\n        BootService::storeBootStats(filesystem, bootstats2);\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, /*enable auto recovery*/ true);\n\n        REQUIRE( !strcmp(declareConfiguration<const char*>(\"keepConfigOverRecovery\", \"otherVal\")->getString(), \"originalVal\") );\n        BootStats bootstats3;\n        BootService::loadBootStats(filesystem, bootstats3);\n        REQUIRE( bootstats3.getBootFailureCount() == 0 + 1 ); //failure count is reset, but +1 because each power cycle is counted as potentially failing until reaching the long runtime barrier\n\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n\n    }\n\n    SECTION(\"Migration\") {\n\n        //migration removes files from previous MO versions which were running on the controller. This includes the\n        //transaction cache, but configs are preserved\n\n        auto old_opstore = filesystem->open(MO_FILENAME_PREFIX \"opstore.jsn\", \"w\"); //the opstore has been removed in MO v1.2.0\n        old_opstore->write(\"example content\", sizeof(\"example content\") - 1);\n        old_opstore.reset(); //flushes the file\n\n        loop();\n        beginTransaction(\"mIdTag\"); //tx store will also be removed\n        auto tx = getTransaction();\n        auto txNr = tx->getTxNr(); //remember this for later usage\n        tx.reset(); //reset this smart pointer\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging );\n        endTransaction();\n        loop();\n\n        REQUIRE( getOcppContext()->getModel().getTransactionStore()->getTransaction(1, txNr) != nullptr ); //tx exists on flash\n\n        declareConfiguration<const char*>(\"keepConfigOverMigration\", \"originalVal\"); //migration keeps configs\n        configuration_save();\n\n        mocpp_deinitialize();\n\n        //After a FW update, the tracked version number has changed\n        BootStats bootstats;\n        BootService::loadBootStats(filesystem, bootstats);\n        snprintf(bootstats.microOcppVersion, sizeof(bootstats.microOcppVersion), \"oldFwVers\");\n        BootService::storeBootStats(filesystem, bootstats);\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem); //MO migrates here\n\n        size_t msize = 0;\n        REQUIRE( filesystem->stat(MO_FILENAME_PREFIX \"opstore.jsn\", &msize) != 0 ); //opstore has been removed\n\n        REQUIRE( getOcppContext()->getModel().getTransactionStore()->getTransaction(1, txNr) == nullptr ); //tx history entry has been removed\n\n        REQUIRE( !strcmp(declareConfiguration<const char*>(\"keepConfigOverMigration\", \"otherVal\")->getString(), \"originalVal\") ); //config has been preserved\n    }\n\n    SECTION(\"Clean unused configs\") {\n\n        declareConfiguration<const char*>(\"neverDeclaredInsideMO\", \"originalVal\"); //unused configs will be cleared automatically after the controller has been running for a long time\n        configuration_save();\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem); //all configs are loaded here, including the test config of this section\n        loop();\n\n        //unused configs will be cleared automatically after long time\n        mtime += MO_BOOTSTATS_LONGTIME_MS;\n        loop();\n\n        REQUIRE( !strcmp(declareConfiguration<const char*>(\"neverDeclaredInsideMO\", \"newVal\")->getString(), \"newVal\") ); //config has been removed\n    }\n\n    SECTION(\"Boot with v201\") {\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials::v201(CHARGEPOINTMODEL, CHARGEPOINTVENDOR), filesystem, false, ProtocolVersion(2,0,1));\n\n        bool checkProcessed = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"BootNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"BootNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        checkProcessed = true;\n                        REQUIRE( !strcmp(payload[\"reason\"] | \"_Undefined\", \"PowerUp\") );\n                        REQUIRE( !strcmp(payload[\"chargingStation\"][\"model\"] | \"_Undefined\", CHARGEPOINTMODEL) );\n                        REQUIRE( !strcmp(payload[\"chargingStation\"][\"vendorName\"] | \"_Undefined\", CHARGEPOINTVENDOR) );\n                    },\n                    [] () {\n                        //create conf\n                        auto conf = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(3));\n                        (*conf)[\"currentTime\"] = BASE_TIME;\n                        (*conf)[\"interval\"] = 3600;\n                        (*conf)[\"status\"] = \"Accepted\";\n                        return conf;\n                    });\n            });\n\n        MO_MEM_RESET();\n        \n        loop();\n\n        REQUIRE(checkProcessed);\n        REQUIRE(getOcppContext()->getModel().getClock().now() >= MIN_TIME);\n\n        MO_MEM_PRINT_STATS();\n\n        MO_MEM_RESET();\n\n        mtime += 3600 * 1000;\n        loop();\n\n        MO_DBG_INFO(\"Memory requirements UC G02:\");\n        MO_MEM_PRINT_STATS();\n    }\n\n    mocpp_deinitialize();\n}\n"
  },
  {
    "path": "tests/Certificates.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Platform.h>\n\n#if MO_ENABLE_MBEDTLS\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Core/Request.h>\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Configuration.h>\n\n#include <MicroOcpp/Model/Certificates/CertificateService.h>\n#include <MicroOcpp/Model/Certificates/CertificateMbedTLS.h>\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\n//ISRG Root X1\nconst char *root_cert = R\"(-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\nWhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\nZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\nMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\nh77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\nA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\nT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\nB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\nB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\nKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\nOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\njh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\nqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\nrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\nhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\nubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\nNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\nORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\nTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\njNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\noyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\nmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\nemyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n-----END CERTIFICATE-----\n)\";\n\n//precomputed identifiers of root cert above, based on Open Certificate Status Protocol (OCSP)\nconst char *root_cert_hash_algorithm = \"SHA256\"; //algorithm used for the following hashes\nconst char *root_cert_hash_issuer_name = \"F6DB2FBD9DD85D9259DDB3C6DE7D7B2FEC3F3E0CEF1761BCBF3320571E2D30F8\";\nconst char *root_cert_hash_issuer_key = \"F4593A1E07CC9CCEFFBED9C11DC5218356F7814D9B22949DE745E629990C6C60\";\nconst char *root_cert_hash_serial_number = \"8210CFB0D240E3594463E0BB63828B00\";\n\nusing namespace MicroOcpp;\n\nTEST_CASE( \"M - Certificates\" ) {\n    printf(\"\\nRun %s\\n\",  \"M - Certificates\");\n\n    //clean state\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n\n    mocpp_set_timer(custom_timer_cb);\n\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner\"));\n    auto& model = getOcppContext()->getModel();\n    auto certService = model.getCertificateService();\n    SECTION(\"CertificateService initialized\") {\n        REQUIRE(certService != nullptr);\n    }\n    auto certs = certService->getCertificateStore();\n    SECTION(\"CertificateStore initialized\") {\n        REQUIRE(certs != nullptr);\n    }\n\n    auto connector = model.getConnector(1);\n    model.getClock().setTime(BASE_TIME);\n\n    loop();\n\n    SECTION(\"M05 Install CA cert -- sent cert is valid\") {\n        auto ret = certs->installCertificate(InstallCertificateType_CSMSRootCertificate, root_cert);\n        REQUIRE(ret == InstallCertificateStatus_Accepted);\n\n        size_t msize;\n        char fn [MO_MAX_PATH_SIZE];\n        printCertFn(MO_CERT_FN_CSMS_ROOT, 0, fn, MO_MAX_PATH_SIZE);\n        REQUIRE(filesystem->stat(fn, &msize) == 0);\n        REQUIRE(msize == strlen(root_cert));\n    }\n\n    SECTION(\"M03 Retrieve list of available certs -- one cert available\") {\n        auto ret1 = certs->installCertificate(InstallCertificateType_CSMSRootCertificate, root_cert);\n        REQUIRE(ret1 == InstallCertificateStatus_Accepted);\n\n        auto chain = makeVector<CertificateChainHash>(\"UnitTests\");\n        auto ret2 = certs->getCertificateIds({GetCertificateIdType_CSMSRootCertificate}, chain);\n\n        REQUIRE(ret2 == GetInstalledCertificateStatus_Accepted);\n        REQUIRE(chain.size() == 1);\n\n        auto& chainElem = chain.front();\n\n        REQUIRE(chainElem.certificateType == GetCertificateIdType_CSMSRootCertificate);\n        auto& certHash = chainElem.certificateHashData;\n\n        REQUIRE(!strcmp(HashAlgorithmLabel(certHash.hashAlgorithm), root_cert_hash_algorithm)); //if this fails, please update the precomputed test hashes\n\n        char buf [MO_CERT_HASH_ISSUER_NAME_KEY_SIZE];\n\n        ocpp_cert_print_issuerNameHash(&certHash, buf, sizeof(buf));\n        REQUIRE(!strcmp(buf, root_cert_hash_issuer_name));\n\n        ocpp_cert_print_issuerKeyHash(&certHash, buf, sizeof(buf));\n        REQUIRE(!strcmp(buf, root_cert_hash_issuer_key));\n\n        ocpp_cert_print_serialNumber(&certHash, buf, sizeof(buf));\n        REQUIRE(!strcmp(buf, root_cert_hash_serial_number));\n\n        REQUIRE(chainElem.childCertificateHashData.empty()); //no sub certs sent\n    }\n\n    SECTION(\"M04 Delete a specific cert -- specified cert exists\") {\n        auto ret1 = certs->installCertificate(InstallCertificateType_CSMSRootCertificate, root_cert);\n        REQUIRE(ret1 == InstallCertificateStatus_Accepted);\n\n        auto chain = makeVector<CertificateChainHash>(\"UnitTests\");\n        auto ret2 = certs->getCertificateIds({GetCertificateIdType_CSMSRootCertificate}, chain);\n        REQUIRE(ret2 == GetInstalledCertificateStatus_Accepted);\n\n        REQUIRE(chain.size() == 1);\n\n        auto ret3 = certs->deleteCertificate(chain.front().certificateHashData);\n        REQUIRE(ret3 == DeleteCertificateStatus_Accepted);\n\n        ret2 = certs->getCertificateIds({GetCertificateIdType_CSMSRootCertificate}, chain);\n        REQUIRE(ret2 == GetInstalledCertificateStatus_NotFound);\n\n        REQUIRE(chain.size() == 0);\n\n        size_t msize;\n        char fn [MO_MAX_PATH_SIZE];\n        printCertFn(MO_CERT_FN_CSMS_ROOT, 0, fn, MO_MAX_PATH_SIZE);\n        REQUIRE(filesystem->stat(fn, &msize) != 0);\n    }\n\n    SECTION(\"M05 InstallCertificate operation\") {\n\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"InstallCertificate\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\",\n                            JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"certificateType\"] = \"CSMSRootCertificate\"; //of InstallCertificateTypeEnumType\n                    payload[\"certificate\"] = root_cert;\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        size_t msize;\n        char fn [MO_MAX_PATH_SIZE];\n        printCertFn(MO_CERT_FN_CSMS_ROOT, 0, fn, MO_MAX_PATH_SIZE);\n        REQUIRE(filesystem->stat(fn, &msize) == 0);\n        REQUIRE(msize == strlen(root_cert));\n    }\n\n    SECTION(\"M04 DeleteCertificate operation\") {\n        auto ret = certs->installCertificate(InstallCertificateType_CSMSRootCertificate, root_cert);\n        REQUIRE(ret == InstallCertificateStatus_Accepted);\n\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"DeleteCertificate\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\",\n                            JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(4));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"certificateHashData\"][\"hashAlgorithm\"] = root_cert_hash_algorithm; //of HashAlgorithmType\n                    payload[\"certificateHashData\"][\"issuerNameHash\"] = root_cert_hash_issuer_name;\n                    payload[\"certificateHashData\"][\"issuerKeyHash\"] = root_cert_hash_issuer_key;\n                    payload[\"certificateHashData\"][\"serialNumber\"] = root_cert_hash_serial_number;\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n    }\n\n    SECTION(\"M03 GetInstalledCertificateIds operation\") {\n        auto ret = certs->installCertificate(InstallCertificateType_CSMSRootCertificate, root_cert);\n        REQUIRE(ret == InstallCertificateStatus_Accepted);\n\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"GetInstalledCertificateIds\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\",\n                            JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"certificateType\"][0] = \"CSMSRootCertificate\"; //of GetCertificateIdTypeEnumType\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                    REQUIRE( payload[\"certificateHashDataChain\"].size() == 1 );\n                    JsonObject certificateHashDataChain = payload[\"certificateHashDataChain\"][0];\n                    REQUIRE( !strcmp(certificateHashDataChain[\"certificateType\"] | \"_Undefined\", \"CSMSRootCertificate\") );\n                    JsonObject certificateHashData = certificateHashDataChain[\"certificateHashData\"];\n                    REQUIRE( !strcmp(certificateHashData[\"hashAlgorithm\"] | \"_Undefined\", root_cert_hash_algorithm) ); //if this fails, please update the precomputed test hashes\n                    REQUIRE( !strcmp(certificateHashData[\"issuerNameHash\"] | \"_Undefined\", root_cert_hash_issuer_name) );\n                    REQUIRE( !strcmp(certificateHashData[\"issuerKeyHash\"] | \"_Undefined\", root_cert_hash_issuer_key) );\n                    REQUIRE( !strcmp(certificateHashData[\"serialNumber\"] | \"_Undefined\", root_cert_hash_serial_number) );\n                    REQUIRE( !certificateHashDataChain.containsKey(\"childCertificateHashData\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n    }\n\n    mocpp_deinitialize();\n}\n\n#else\n#warning Certificates unit tests depend on MbedTLS\n#endif //MO_ENABLE_MBEDTLS\n"
  },
  {
    "path": "tests/ChargePointError.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Debug.h>\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Configuration.h>\n\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>\n\n#define BASE_TIME     \"2023-01-01T00:00:00.000Z\"\n#define BASE_TIME_1H  \"2023-01-01T01:00:00.000Z\"\n#define FTP_URL       \"ftps://localhost/firmware.bin\"\n\n#define ERROR_INFO_EXAMPLE \"error description\"\n#define ERROR_INFO_LOW_1   \"low severity 1\"\n#define ERROR_INFO_LOW_2   \"low severity 2\"\n#define ERROR_INFO_HIGH    \"high severity\"\n\n#define ERROR_VENDOR_ID    \"mVendorId\"\n#define ERROR_VENDOR_CODE  \"mVendorErrorCode\"\n\nusing namespace MicroOcpp;\n\nTEST_CASE( \"ChargePointError\" ) {\n    printf(\"\\nRun %s\\n\",  \"ChargePointError\");\n\n    //clean state\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n\n    mocpp_set_timer(custom_timer_cb);\n\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner\"));\n    auto& model = getOcppContext()->getModel();\n    auto fwService = getFirmwareService();\n    SECTION(\"FirmwareService initialized\") {\n        REQUIRE(fwService != nullptr);\n    }\n\n    model.getClock().setTime(BASE_TIME);\n\n    loop();\n\n    SECTION(\"Err and resolve (soft error)\") {\n\n        bool errorCondition = false;\n\n        addErrorDataInput([&errorCondition] () -> ErrorData {\n            if (errorCondition) {\n                ErrorData error = \"OtherError\";\n                error.isFaulted = false;\n                error.info = ERROR_INFO_EXAMPLE;\n                error.vendorId = ERROR_VENDOR_ID;\n                error.vendorErrorCode = ERROR_VENDOR_CODE;\n                return error;\n            }\n            return nullptr;\n        });\n\n        //test error condition during transaction to check if status remains unchanged\n\n        beginTransaction(\"mIdTag\");\n\n        loop();\n\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging );\n        REQUIRE( isOperative() );\n\n        bool checkProcessed = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StatusNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"StatusNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        checkProcessed = true;\n                        REQUIRE( !strcmp(payload[\"errorCode\"] | \"_Undefined\", \"OtherError\") );\n                        REQUIRE( !strcmp(payload[\"info\"] | \"_Undefined\", ERROR_INFO_EXAMPLE) );\n                        REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Charging\") );\n                        REQUIRE( !strcmp(payload[\"vendorId\"] | \"_Undefined\", ERROR_VENDOR_ID) );\n                        REQUIRE( !strcmp(payload[\"vendorErrorCode\"] | \"_Undefined\", ERROR_VENDOR_CODE) );\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n\n        errorCondition = true;\n\n        loop();\n\n        REQUIRE( checkProcessed );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging );\n        REQUIRE( isOperative() );\n\n#if MO_REPORT_NOERROR\n        checkProcessed = false;\n        getOcppContext()->getOperationRegistry().registerOperation(\"StatusNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"StatusNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        checkProcessed = true;\n                        REQUIRE( !strcmp(payload[\"errorCode\"] | \"_Undefined\", \"NoError\") );\n                        REQUIRE( !payload.containsKey(\"info\") );\n                        REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Charging\") );\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n#else\n        checkProcessed = true;\n#endif //MO_REPORT_NOERROR\n\n        errorCondition = false;\n\n        loop();\n\n        REQUIRE( checkProcessed );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging );\n        REQUIRE( isOperative() );\n\n    }\n\n    SECTION(\"Err and resolve (fatal)\") {\n\n        bool errorCondition = false;\n\n        addErrorCodeInput([&errorCondition] () {\n            return errorCondition ? \"OtherError\" : nullptr;\n        });\n\n        loop();\n\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n        REQUIRE( isOperative() );\n\n        errorCondition = true;\n\n        loop();\n\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Faulted );\n        REQUIRE( !isOperative() );\n\n        errorCondition = false;\n\n        loop();\n\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n        REQUIRE( isOperative() );\n    }\n\n    SECTION(\"Error severity\") {\n\n        bool errorConditionLow1 = false;\n        bool errorConditionLow2 = false;\n        bool errorConditionHigh = false;\n\n        addErrorDataInput([&errorConditionLow1] () -> ErrorData {\n            if (errorConditionLow1) {\n                ErrorData error = \"OtherError\";\n                error.severity = 1;\n                error.info = ERROR_INFO_LOW_1;\n                return error;\n            }\n            return nullptr;\n        });\n\n        addErrorDataInput([&errorConditionLow2] () -> ErrorData {\n            if (errorConditionLow2) {\n                ErrorData error = \"OtherError\";\n                error.severity = 1;\n                error.info = ERROR_INFO_LOW_2;\n                return error;\n            }\n            return nullptr;\n        });\n\n        addErrorDataInput([&errorConditionHigh] () -> ErrorData {\n            if (errorConditionHigh) {\n                ErrorData error = \"OtherError\";\n                error.severity = 2;\n                error.info = ERROR_INFO_HIGH;\n                return error;\n            }\n            return nullptr;\n        });\n\n        const char *errorCode = \"*\";\n        bool checkErrorCode = false;\n        const char *errorInfo = \"*\";\n        bool checkErrorInfo = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StatusNotification\",\n            [&checkErrorCode, &checkErrorInfo, &errorInfo, &errorCode] () {\n                return new Ocpp16::CustomOperation(\"StatusNotification\",\n                    [&checkErrorCode, &checkErrorInfo, &errorInfo, &errorCode] (JsonObject payload) {\n                        //process req\n                        if (strcmp(errorInfo, \"*\")) {\n                            MO_DBG_DEBUG(\"expect \\\"%s\\\", got \\\"%s\\\"\", errorInfo, payload[\"info\"] | \"_Undefined\");\n                            REQUIRE( !strcmp(payload[\"info\"] | \"_Undefined\", errorInfo) );\n                            checkErrorInfo = true;\n                        }\n                        if (strcmp(errorCode, \"*\")) {\n                            MO_DBG_DEBUG(\"expect \\\"%s\\\", got \\\"%s\\\"\", errorCode, payload[\"errorCode\"] | \"_Undefined\");\n                            REQUIRE( !strcmp(payload[\"errorCode\"] | \"_Undefined\", errorCode) );\n                            checkErrorCode = true;\n                        }\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n        \n        //sequence: low-level error 1, low-level error 2, then severe error  -- all errors should go through\n        MO_DBG_INFO(\"test sequence: low-level error 1, low-level error 2, then severe error\");\n\n        errorConditionLow1 = true;\n        errorInfo = ERROR_INFO_LOW_1;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n        \n        errorConditionLow2 = true;\n        errorInfo = ERROR_INFO_LOW_2;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n        \n        errorConditionHigh = true;\n        errorInfo = ERROR_INFO_HIGH;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n\n        errorConditionLow1 = false;\n        errorConditionLow2 = false;\n        errorConditionHigh = false;\n        errorInfo = \"*\";\n        loop();\n        \n        //sequence: low-level error 1, severe error, then low-level error 2 -- last error gets muted until severe error is resolved\n        MO_DBG_INFO(\"test sequence: low-level error 1, severe error, then low-level error 2\");\n\n        errorConditionLow1 = true;\n        errorInfo = ERROR_INFO_LOW_1;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n        \n        errorConditionHigh = true;\n        errorInfo = ERROR_INFO_HIGH;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n        \n        errorConditionLow2 = true;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( !checkErrorInfo );\n\n        errorConditionHigh = false;\n        errorInfo = ERROR_INFO_LOW_2;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n\n        errorConditionLow1 = false;\n        errorConditionLow2 = false;\n        errorConditionHigh = false;\n        errorInfo = \"*\";\n        loop();\n        \n        //sequence: low-level error 1, severe error, then severe error gets resolved -- low-level error is reported again\n        MO_DBG_INFO(\"test sequence: low-level error 1, severe error, then severe error gets resolved\");\n\n        errorConditionLow1 = true;\n        errorInfo = ERROR_INFO_LOW_1;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n        \n        errorConditionHigh = true;\n        errorInfo = ERROR_INFO_HIGH;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n        \n        errorConditionHigh = false;\n        errorInfo = ERROR_INFO_LOW_1;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n\n        errorConditionLow1 = false;\n        errorConditionLow2 = false;\n        errorConditionHigh = false;\n        errorInfo = \"*\";\n        loop();\n\n        //sequence: error, then error gets resolved -- report NoError\n        MO_DBG_INFO(\"test sequence: error, then error gets resolved\");\n\n        errorConditionLow1 = true;\n        errorInfo = ERROR_INFO_LOW_1;\n        checkErrorInfo = false;\n        loop();\n        REQUIRE( checkErrorInfo );\n\n        errorConditionLow1 = false;\n        errorInfo = \"*\";\n        errorCode = \"NoError\";\n        checkErrorCode = false;\n        loop();\n        REQUIRE( checkErrorCode );\n    }\n\n    endTransaction();\n    mocpp_deinitialize();\n\n}\n"
  },
  {
    "path": "tests/ChargingSessions.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Operations/BootNotification.h>\n#include <MicroOcpp/Operations/StatusNotification.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Model/Transactions/TransactionStore.h>\n#include <MicroOcpp/Debug.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#include <array>\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\nusing namespace MicroOcpp;\n\n\nTEST_CASE( \"Charging sessions\" ) {\n    printf(\"\\nRun %s\\n\",  \"Charging sessions\");\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n    auto engine = getOcppContext();\n    auto& checkMsg = engine->getOperationRegistry();\n\n    mocpp_set_timer(custom_timer_cb);\n\n    auto connectionTimeOutInt = declareConfiguration<int>(\"ConnectionTimeOut\", 30, CONFIGURATION_FN);\n    connectionTimeOutInt->setInt(30);\n    auto minimumStatusDurationInt = declareConfiguration<int>(\"MinimumStatusDuration\", 0, CONFIGURATION_FN);\n    minimumStatusDurationInt->setInt(0);\n    \n    std::array<const char*, 2> expectedSN {\"Available\", \"Available\"};\n    std::array<bool, 2> checkedSN {false, false};\n    checkMsg.registerOperation(\"StatusNotification\", [] () -> Operation* {return new Ocpp16::StatusNotification(0, ChargePointStatus_UNDEFINED, MIN_TIME);});\n    checkMsg.setOnRequest(\"StatusNotification\",\n        [&checkedSN, &expectedSN] (JsonObject request) {\n            int connectorId = request[\"connectorId\"] | -1;\n            if (connectorId == 0 || connectorId == 1) { //only test single connector case here\n                checkedSN[connectorId] = !strcmp(request[\"status\"] | \"Invalid\", expectedSN[connectorId]);\n            }\n        });\n\n    SECTION(\"Check idle state\"){\n\n        bool checkedBN = false;\n        checkMsg.registerOperation(\"BootNotification\", [engine] () -> Operation* {return new Ocpp16::BootNotification(engine->getModel(), makeJsonDoc(\"UnitTests\"));});\n        checkMsg.setOnRequest(\"BootNotification\",\n            [&checkedBN] (JsonObject request) {\n                checkedBN = !strcmp(request[\"chargePointModel\"] | \"Invalid\", \"test-runner1234\");\n            });\n        \n        REQUIRE( !isOperative() ); //not operative before reaching loop stage\n\n        loop();\n        loop();\n        REQUIRE( checkedBN );\n        REQUIRE( checkedSN[0] );\n        REQUIRE( checkedSN[1] );\n        REQUIRE( isOperative() );\n        REQUIRE( !getTransaction() );\n        REQUIRE( !ocppPermitsCharge() );\n    }\n\n    loop();\n\n    SECTION(\"StartTx\") {\n        SECTION(\"StartTx directly\"){\n            startTransaction(\"mIdTag\");\n            loop();\n            REQUIRE(ocppPermitsCharge());\n        }\n\n        SECTION(\"StartTx via session management - plug in first\") {\n            expectedSN[1] = \"Preparing\";\n            setConnectorPluggedInput([] () {return true;});\n            loop();\n            REQUIRE(checkedSN[1]);\n\n            checkedSN[1] = false;\n            expectedSN[1] = \"Charging\";\n            beginTransaction(\"mIdTag\");\n            loop();\n            REQUIRE(checkedSN[1]);\n            REQUIRE(ocppPermitsCharge());\n        }\n\n        SECTION(\"StartTx via session management - authorization first\") {\n\n            expectedSN[1] = \"Preparing\";\n            setConnectorPluggedInput([] () {return false;});\n            beginTransaction(\"mIdTag\");\n            loop();\n            REQUIRE(checkedSN[1]);\n\n            checkedSN[1] = false;\n            expectedSN[1] = \"Charging\";\n            setConnectorPluggedInput([] () {return true;});\n            loop();\n            REQUIRE(checkedSN[1]);\n            REQUIRE(ocppPermitsCharge());\n        }\n\n        SECTION(\"StartTx via session management - no plug\") {\n            expectedSN[1] = \"Charging\";\n            beginTransaction(\"mIdTag\");\n            loop();\n            REQUIRE(checkedSN[1]);\n        }\n\n        SECTION(\"StartTx via session management - ConnectionTimeOut\") {\n            expectedSN[1] = \"Preparing\";\n            setConnectorPluggedInput([] () {return false;});\n            beginTransaction(\"mIdTag\");\n            loop();\n            REQUIRE(checkedSN[1]);\n\n            checkedSN[1] = false;\n            expectedSN[1] = \"Available\";\n            mtime += connectionTimeOutInt->getInt() * 1000;\n            loop();\n            REQUIRE(checkedSN[1]);\n        }\n\n        loop();\n        if (ocppPermitsCharge()) {\n            stopTransaction();\n        }\n        loop();\n    }\n\n    SECTION(\"StopTx\") {\n        startTransaction(\"mIdTag\");\n        loop();\n        expectedSN[1] = \"Available\";\n\n        SECTION(\"directly\") {\n            stopTransaction();\n            loop();\n            REQUIRE(checkedSN[1]);\n            REQUIRE(!ocppPermitsCharge());\n        }\n\n        SECTION(\"via session management - deauthorize\") {\n            endTransaction();\n            loop();\n            REQUIRE(checkedSN[1]);\n            REQUIRE(!ocppPermitsCharge());\n        }\n\n        SECTION(\"via session management - deauthorize first\") {\n            expectedSN[1] = \"Finishing\";\n            setConnectorPluggedInput([] () {return true;});\n            endTransaction();\n            loop();\n            REQUIRE(checkedSN[1]);\n            REQUIRE(!ocppPermitsCharge());\n\n            checkedSN[1] = false;\n            expectedSN[1] = \"Available\";\n            setConnectorPluggedInput([] () {return false;});\n            loop();\n            REQUIRE(checkedSN[1]);\n            REQUIRE(!ocppPermitsCharge());\n        }\n\n        SECTION(\"via session management - plug out\") {\n            setConnectorPluggedInput([] () {return false;});\n            loop();\n            REQUIRE(checkedSN[1]);\n            REQUIRE(!ocppPermitsCharge());\n        }\n\n        if (ocppPermitsCharge()) {\n            stopTransaction();\n        }\n        loop();\n    }\n\n    SECTION(\"Preboot transactions - tx before BootNotification\") {\n        mocpp_deinitialize();\n\n        loopback.setOnline(false);\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true, CONFIGURATION_FN)->setBool(true);\n        configuration_save();\n\n        loop();\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n\n        REQUIRE(isTransactionRunning());\n\n        mtime += 3600 * 1000; //transaction duration ~1h\n\n        endTransaction();\n\n        loop();\n\n        mtime += 3600 * 1000; //set base time one hour later\n\n        bool checkStartProcessed = false;\n\n        getOcppContext()->getModel().getClock().setTime(BASE_TIME);\n        Timestamp basetime = Timestamp();\n        basetime.setTime(BASE_TIME);\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StartTransaction\", \n            [&checkStartProcessed, basetime] (JsonObject payload) {\n                checkStartProcessed = true;\n                Timestamp timestamp;\n                timestamp.setTime(payload[\"timestamp\"].as<const char*>());                \n\n                auto adjustmentDelay = basetime - timestamp;\n                REQUIRE((adjustmentDelay > 2 * 3600 - 10 && adjustmentDelay < 2 * 3600 + 10));\n            });\n        \n        bool checkStopProcessed = false;\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StopTransaction\", \n            [&checkStopProcessed, basetime] (JsonObject payload) {\n                checkStopProcessed = true;\n                Timestamp timestamp;\n                timestamp.setTime(payload[\"timestamp\"].as<const char*>());                \n\n                auto adjustmentDelay = basetime - timestamp;\n                REQUIRE((adjustmentDelay > 3600 - 10 && adjustmentDelay < 3600 + 10));\n            });\n        \n        loopback.setOnline(true);\n        loop();\n\n        REQUIRE(checkStartProcessed);\n        REQUIRE(checkStopProcessed);\n    }\n\n    SECTION(\"Preboot transactions - lose StartTx timestamp\") {\n\n        mocpp_deinitialize();\n\n        loopback.setOnline(false);\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true, CONFIGURATION_FN)->setBool(true);\n        configuration_save();\n\n        loop();\n\n        beginTransaction_authorized(\"mIdTag\");\n        loop();\n\n        REQUIRE(isTransactionRunning());\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true, CONFIGURATION_FN)->setBool(true);\n        configuration_save();\n\n        bool checkProcessed = false;\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StartTransaction\", [&checkProcessed] (JsonObject) {\n            checkProcessed = true;\n        });\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StopTransaction\", [&checkProcessed] (JsonObject) {\n            checkProcessed = true;\n        });\n\n        loopback.setOnline(true);\n\n        loop();\n\n        REQUIRE(!isTransactionRunning());\n        REQUIRE(!checkProcessed);\n    }\n\n    SECTION(\"Preboot transactions - lose StopTx timestamp\") {\n        \n        const char *starTxTimestampStr = \"2023-02-01T00:00:00.000Z\";\n        getOcppContext()->getModel().getClock().setTime(starTxTimestampStr);\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n\n        REQUIRE(isTransactionRunning());\n\n        mocpp_deinitialize();\n\n        loopback.setOnline(false);\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true, CONFIGURATION_FN)->setBool(true);\n        configuration_save();\n\n        loop();\n\n        REQUIRE(isTransactionRunning());\n\n        endTransaction();\n\n        loop();\n\n        REQUIRE(!isTransactionRunning());\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true, CONFIGURATION_FN)->setBool(true);\n        configuration_save();\n\n        bool checkProcessed = false;\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StopTransaction\",\n            [&checkProcessed, starTxTimestampStr] (JsonObject payload) {\n                checkProcessed = true;\n                Timestamp timestamp;\n                timestamp.setTime(payload[\"timestamp\"].as<const char*>());\n\n                Timestamp starTxTimestamp = Timestamp();\n                starTxTimestamp.setTime(starTxTimestampStr);\n\n                auto adjustmentDelay = timestamp - starTxTimestamp;\n                REQUIRE(adjustmentDelay == 1);\n            });\n\n        loopback.setOnline(true);\n\n        loop();\n\n        REQUIRE(checkProcessed);\n    }\n\n    SECTION(\"Preboot transactions - reject tx if limit exceeded\") {\n        mocpp_deinitialize();\n\n        loopback.setConnected(false);\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true, CONFIGURATION_FN)->setBool(true);\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"SilentOfflineTransactions\", false, CONFIGURATION_FN)->setBool(false); // do not start more txs if tx journal is full\n        configuration_save();\n\n        loop();\n\n        for (size_t i = 0; i < MO_TXRECORD_SIZE; i++) {\n            beginTransaction_authorized(\"mIdTag\");\n\n            loop();\n\n            REQUIRE(isTransactionRunning());\n\n            endTransaction();\n\n            loop();\n\n            REQUIRE(!isTransactionRunning());\n        }\n\n        // now, tx journal is full. Block any further charging session\n\n        auto tx_success = beginTransaction_authorized(\"mIdTag\");\n        REQUIRE( !tx_success );\n\n        loop();\n\n        REQUIRE(!isTransactionRunning());\n        REQUIRE(!ocppPermitsCharge());\n\n        // Check if all 4 cached transctions are transmitted after going online\n\n        const int txId_base = 10000;\n        int txId_generate = txId_base;\n        int txId_confirm = txId_base;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StartTransaction\", [&txId_generate] () {\n            return new Ocpp16::CustomOperation(\"StartTransaction\",\n                [] (JsonObject payload) {}, //ignore req\n                [&txId_generate] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2));\n                    JsonObject payload = doc->to<JsonObject>();\n\n                    JsonObject idTagInfo = payload.createNestedObject(\"idTagInfo\");\n                    idTagInfo[\"status\"] = \"Accepted\";\n                    txId_generate++;\n                    payload[\"transactionId\"] = txId_generate;\n                    return doc;\n                });});\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StopTransaction\", [&txId_generate, &txId_confirm] () {\n            return new Ocpp16::CustomOperation(\"StopTransaction\",\n                [&txId_generate, &txId_confirm] (JsonObject payload) {\n                    //receive req\n                    REQUIRE( payload[\"transactionId\"].as<int>() == txId_generate );\n                    REQUIRE( payload[\"transactionId\"].as<int>() == txId_confirm + 1 );\n                    txId_confirm = payload[\"transactionId\"].as<int>();\n                }, \n                [] () {\n                    //create conf\n                    return createEmptyDocument();\n                });});\n\n        loopback.setConnected(true);\n        loop();\n\n        REQUIRE( txId_confirm == txId_base + MO_TXRECORD_SIZE );\n    }\n\n    SECTION(\"Preboot transactions - charge without tx if limit exceeded\") {\n        mocpp_deinitialize();\n\n        loopback.setConnected(false);\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true, CONFIGURATION_FN)->setBool(true);\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"SilentOfflineTransactions\", false, CONFIGURATION_FN)->setBool(true); // don't report further transactions to server but charge anyway\n        configuration_save();\n\n        loop();\n\n        for (size_t i = 0; i < MO_TXRECORD_SIZE; i++) {\n            beginTransaction_authorized(\"mIdTag\");\n\n            loop();\n\n            REQUIRE(isTransactionRunning());\n\n            endTransaction();\n\n            loop();\n\n            REQUIRE(!isTransactionRunning());\n        }\n\n        // now, tx journal is full. Block any further charging session\n\n        auto tx_success = beginTransaction_authorized(\"mIdTag\");\n        REQUIRE( tx_success );\n\n        loop();\n\n        REQUIRE(isTransactionRunning());\n        REQUIRE(ocppPermitsCharge());\n\n        endTransaction();\n\n        loop();\n\n        // Check if all 4 cached transctions are transmitted after going online\n\n        const int txId_base = 10000;\n        int txId_generate = txId_base;\n        int txId_confirm = txId_base;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StartTransaction\", [&txId_generate] () {\n            return new Ocpp16::CustomOperation(\"StartTransaction\",\n                [] (JsonObject payload) {}, //ignore req\n                [&txId_generate] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2));\n                    JsonObject payload = doc->to<JsonObject>();\n\n                    JsonObject idTagInfo = payload.createNestedObject(\"idTagInfo\");\n                    idTagInfo[\"status\"] = \"Accepted\";\n                    txId_generate++;\n                    payload[\"transactionId\"] = txId_generate;\n                    return doc;\n                });});\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StopTransaction\", [&txId_generate, &txId_confirm] () {\n            return new Ocpp16::CustomOperation(\"StopTransaction\",\n                [&txId_generate, &txId_confirm] (JsonObject payload) {\n                    //receive req\n                    REQUIRE( payload[\"transactionId\"].as<int>() == txId_generate );\n                    REQUIRE( payload[\"transactionId\"].as<int>() == txId_confirm + 1 );\n                    txId_confirm = payload[\"transactionId\"].as<int>();\n                }, \n                [] () {\n                    //create conf\n                    return createEmptyDocument();\n                });});\n\n        loopback.setConnected(true);\n        loop();\n\n        REQUIRE( txId_confirm == txId_base + MO_TXRECORD_SIZE );\n    }\n\n    SECTION(\"Preboot transactions - mix PreBoot with Offline tx\") {\n        \n        /*\n         * The charger boots and connects to the OCPP server normally. It looses connection and then starts\n         * transaction #1 which is persisted on flash. Then a power loss occurs, but the charger doesn't reconnect.\n         * Start transaction #2 in PreBoot mode. Trigger another power loss, start transaction #3 while still\n         * being offline and then, after reconnection to the server, transaction #4.\n         * \n         * Tx #1 can be fully restored. The timestamp information for Tx #2 is missing, so it is discarded. Tx #3 is\n         * missing absolute timestamps at first, but after reconnection with the server, the timestamps get updated\n         * with absolute values from the server. Tx #4 is the standard case for transactions and should start normally.\n         */\n\n        // use idTags to identify the transactions\n        const char *tx1_idTag = \"Tx#1\";\n        const char *tx2_idTag = \"Tx#2\";\n        const char *tx3_idTag = \"Tx#3\";\n        const char *tx4_idTag = \"Tx#4\";\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true)->setBool(true);\n        declareConfiguration<bool>(\"AllowOfflineTxForUnknownId\", true)->setBool(true);\n        configuration_save();\n        loop();\n\n        // start Tx #1 (offline tx)\n        loopback.setConnected(false);\n\n        MO_DBG_DEBUG(\"begin tx (%s)\", tx1_idTag);\n        beginTransaction(tx1_idTag);\n        loop();\n        REQUIRE(isTransactionRunning());\n        endTransaction();\n        loop();\n        REQUIRE(!isTransactionRunning());\n\n        // first power cycle\n        mocpp_deinitialize();\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        loop();\n\n        // start Tx #2 (PreBoot tx, won't get timestamp)\n\n        MO_DBG_DEBUG(\"begin tx (%s)\", tx2_idTag);\n        beginTransaction(tx2_idTag);\n        loop();\n        REQUIRE(isTransactionRunning());\n        endTransaction();\n        loop();\n        REQUIRE(!isTransactionRunning());\n\n        // second power cycle\n        mocpp_deinitialize();\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        loop();\n\n        // start Tx #3 (PreBoot tx, will eventually get timestamp)\n\n        MO_DBG_DEBUG(\"begin tx (%s)\", tx3_idTag);\n        beginTransaction(tx3_idTag);\n        loop();\n        REQUIRE(isTransactionRunning());\n        endTransaction();\n        loop();\n        REQUIRE(!isTransactionRunning());\n\n        // set up checks before getting online and starting Tx #4\n        bool check_1 = false, check_2 = false, check_3 = false, check_4 = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StartTransaction\", \n                [&check_1, &check_2, &check_3, &check_4,\n                        tx1_idTag, tx2_idTag, tx3_idTag, tx4_idTag] () {\n            return new Ocpp16::CustomOperation(\"StartTransaction\",\n                [&check_1, &check_2, &check_3, &check_4,\n                        tx1_idTag, tx2_idTag, tx3_idTag, tx4_idTag] (JsonObject payload) {\n                    //process req\n                    const char *idTag = payload[\"idTag\"] | \"_Undefined\";\n                    if (!strcmp(idTag, tx1_idTag )) {\n                        check_1 = true;\n                    } else if (!strcmp(idTag, tx2_idTag )) {\n                        check_2 = true;\n                    } else if (!strcmp(idTag, tx3_idTag )) {\n                        check_3 = true;\n                    } else if (!strcmp(idTag, tx4_idTag )) {\n                        check_4 = true;\n                    }\n                },\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2));\n                    JsonObject payload = doc->to<JsonObject>();\n\n                    JsonObject idTagInfo = payload.createNestedObject(\"idTagInfo\");\n                    idTagInfo[\"status\"] = \"Accepted\";\n                    static int uniqueTxId = 1000;\n                    payload[\"transactionId\"] = uniqueTxId++; //sample data for debug purpose\n                    return doc;\n                });});\n        \n        // get online\n        loopback.setConnected(true);\n        loop();\n\n        // start Tx #4\n        MO_DBG_DEBUG(\"begin tx (%s)\", tx4_idTag);\n        beginTransaction(tx4_idTag);\n        loop();\n        REQUIRE(isTransactionRunning());\n        endTransaction();\n        loop();\n        REQUIRE(!isTransactionRunning());\n\n        // evaluate results\n        REQUIRE( check_1 );\n        REQUIRE( !check_2 ); // critical data for Tx #2 got lost so it must be discarded\n        REQUIRE( check_3 );\n        REQUIRE( check_4 );\n    }\n\n    SECTION(\"Set Unavaible\"){\n\n        beginTransaction(\"mIdTag\");\n\n        loop();\n\n        auto connector = getOcppContext()->getModel().getConnector(1);\n        REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n        REQUIRE(isOperative());\n\n        bool checkProcessed = false;\n\n        auto changeAvailability = makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeAvailability\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"connectorId\"] = 1;\n                    payload[\"type\"] = \"Inoperative\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE(!strcmp(payload[\"status\"], \"Scheduled\"));}));\n\n        getOcppContext()->initiateRequest(std::move(changeAvailability));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n        REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n        REQUIRE(isOperative());\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        connector = getOcppContext()->getModel().getConnector(1);\n\n        loop();\n\n        REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n        REQUIRE(isOperative());\n\n        endTransaction();\n\n        loop();\n\n        REQUIRE(connector->getStatus() == ChargePointStatus_Unavailable);\n        REQUIRE(!isOperative());\n\n        connector->setAvailability(true);\n\n        REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n        REQUIRE(isOperative());\n    }\n\n    SECTION(\"UnlockConnector\") {\n        // UnlockConnector handler\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n        REQUIRE( isTransactionRunning() );\n\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new MicroOcpp::Ocpp16::CustomOperation(\"UnlockConnector\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"connectorId\"] = 1;\n                    return doc;\n                },\n                [&checkProcessed] (JsonObject payload) {\n                    //process conf\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"NotSupported\") );\n                })));\n        \n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( isTransactionRunning() ); // NotSupported doesn't lead to transaction stop\n\n#if MO_ENABLE_CONNECTOR_LOCK\n\n        setOnUnlockConnectorInOut([] () -> UnlockConnectorResult {\n            // connector lock fails\n            return UnlockConnectorResult_UnlockFailed;\n        });\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new MicroOcpp::Ocpp16::CustomOperation(\"UnlockConnector\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"connectorId\"] = 1;\n                    return doc;\n                },\n                [&checkProcessed] (JsonObject payload) {\n                    //process conf\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"UnlockFailed\") );\n                })));\n        \n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( !isTransactionRunning() ); // Stop tx when UnlockConnector generally supported\n\n        setOnUnlockConnectorInOut([] () -> UnlockConnectorResult {\n            // connector lock times out\n            return UnlockConnectorResult_Pending;\n        });\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new MicroOcpp::Ocpp16::CustomOperation(\"UnlockConnector\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"connectorId\"] = 1;\n                    return doc;\n                },\n                [&checkProcessed] (JsonObject payload) {\n                    //process conf\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"UnlockFailed\") );\n                })));\n        \n        loop();\n        mtime += MO_UNLOCK_TIMEOUT; // increment clock so that MO_UNLOCK_TIMEOUT expires\n        loop();\n        REQUIRE( checkProcessed );\n\n#else\n        endTransaction();\n        loop();\n#endif //MO_ENABLE_CONNECTOR_LOCK\n\n    }\n\n    SECTION(\"TxStartPoint - PowerPathClosed\") {\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"TxStartOnPowerPathClosed\", true)->setBool(true);\n\n        // precondition: charge not allowed\n        REQUIRE( !ocppPermitsCharge() );\n        REQUIRE( !isTransactionRunning() );\n\n        setConnectorPluggedInput([] () {return false;}); // TxStartOnPowerPathClosed removes ConnectorPlugged as a prerequisite of transactions\n        setEvReadyInput([] () {return false;}); // TxStartOnPowerPathClosed puts EvReady in the role of ConnectorPlugged in conventional transactions\n\n        beginTransaction(\"mIdTag\");\n\n        loop();\n\n        // in contrast to conventional tx mode, charge permission is granted before transaction. PowerPathClosed is a prerequisite of transactions\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( !isTransactionRunning() );\n\n        setConnectorPluggedInput([] () {return true;}); // ConnectorPlugged not sufficient to start tx\n\n        loop();\n\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( !isTransactionRunning() );\n\n        setEvReadyInput([] () {return true;}); // now, close PowerPath. Transaction will start now\n\n        loop();\n\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( isTransactionRunning() );\n\n        endTransaction();\n\n        loop();\n\n    }\n\n    SECTION(\"TransactionMessageAttempts-/RetryInterval\") {\n\n        /*\n         * Scenarios:\n         * - final failure to send txMsg after tx terminated\n         * - normal communication restored after a final failure\n         * - StartTx fails finally during tx\n         * - StartTx works but StopTx fails finally after tx terminated\n         * - sends attempts fail until final attempt succeeds\n         * - after reboot, continue attempting\n         */\n\n        declareConfiguration<int>(\"TransactionMessageAttempts\", 1)->setInt(1);\n\n        bool checkProcessedStartTx = false;\n        bool checkProcessedStopTx = false;\n        unsigned int txId = 1000;\n\n         /*\n         * - final failure to send txMsg after tx terminated\n         */\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StartTransaction\", [&checkProcessedStartTx, &txId] () {\n            return new Ocpp16::CustomOperation(\"StartTransaction\",\n                [&checkProcessedStartTx] (JsonObject payload) {\n                    //receive req\n                    checkProcessedStartTx = true;\n                },\n                [&txId] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2));\n                    JsonObject payload = doc->to<JsonObject>();\n\n                    JsonObject idTagInfo = payload.createNestedObject(\"idTagInfo\");\n                    idTagInfo[\"status\"] = \"Accepted\";\n                    payload[\"transactionId\"] = txId++;\n                    return doc;\n                });});\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StopTransaction\", [&checkProcessedStopTx] () {\n            return new Ocpp16::CustomOperation(\"StopTransaction\",\n                [&checkProcessedStopTx] (JsonObject payload) {\n                    //receive req\n                    checkProcessedStopTx = true;\n                }, \n                [] () {\n                    //create conf\n                    return createEmptyDocument();\n                });});\n\n        loopback.setOnline(false);\n\n        REQUIRE( !ocppPermitsCharge() );\n\n        beginTransaction_authorized(\"mIdTag\");\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n\n        endTransaction();\n        loop();\n        REQUIRE( !ocppPermitsCharge() );\n\n        mtime += 10 * 60 * 1000; //jump 10 minutes into future\n\n        loopback.setOnline(true);\n        loop();\n\n        REQUIRE( !checkProcessedStartTx );\n        REQUIRE( !checkProcessedStopTx );\n\n         /*\n         * - normal communication restored after a final failure\n         */\n        checkProcessedStartTx = false;\n        checkProcessedStopTx = false;\n        \n        beginTransaction_authorized(\"mIdTag\");\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( checkProcessedStartTx );\n\n        endTransaction();\n        loop();\n        REQUIRE( !ocppPermitsCharge() );\n\n        REQUIRE( checkProcessedStopTx );\n\n        /*\n         * - StartTx fails finally during tx\n         */\n\n        checkProcessedStartTx = false;\n        checkProcessedStopTx = false;\n        \n        loopback.setOnline(false);\n\n        REQUIRE( !ocppPermitsCharge() );\n\n        beginTransaction_authorized(\"mIdTag\");\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n\n        mtime += 10 * 60 * 1000; //jump 10 minutes into future\n        loop();\n        REQUIRE( !ocppPermitsCharge() );\n\n        loopback.setOnline(true);\n        loop();\n\n        REQUIRE( !checkProcessedStartTx );\n        REQUIRE( !checkProcessedStopTx );\n\n        /*\n         * - StartTx works but StopTx fails finally after tx terminated\n         */\n\n        checkProcessedStartTx = false;\n        checkProcessedStopTx = false;\n        \n        beginTransaction_authorized(\"mIdTag\");\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( checkProcessedStartTx );\n\n        loopback.setOnline(false);\n\n        endTransaction();\n        loop();\n        mtime += 10 * 60 * 1000; //jump 10 minutes into future\n\n        loopback.setOnline(true);\n        loop();\n        REQUIRE( !checkProcessedStopTx );\n\n        /*\n         * - sends attempts fail until final attempt succeeds\n         */\n\n        const size_t NUM_ATTEMPTS = 3;\n        const int RETRY_INTERVAL_SECS = 3600;\n\n        declareConfiguration<int>(\"TransactionMessageAttempts\", 0)->setInt(NUM_ATTEMPTS);\n        declareConfiguration<int>(\"TransactionMessageRetryInterval\", 0)->setInt(RETRY_INTERVAL_SECS);\n\n        configuration_save();\n\n        checkProcessedStartTx = false;\n        checkProcessedStopTx = false;\n\n        unsigned int attemptNr = 0;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StartTransaction\", [&checkProcessedStartTx, &txId, &attemptNr] () {\n            return new Ocpp16::CustomOperation(\"StartTransaction\",\n                [&attemptNr] (JsonObject payload) {\n                    //receive req\n                    attemptNr++;\n                },\n                [&txId] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2));\n                    JsonObject payload = doc->to<JsonObject>();\n\n                    JsonObject idTagInfo = payload.createNestedObject(\"idTagInfo\");\n                    idTagInfo[\"status\"] = \"Accepted\";\n                    payload[\"transactionId\"] = txId++;\n                    return doc;\n                },\n                [&attemptNr] () {\n                    //ErrorCode for CALLERROR\n                    return attemptNr < NUM_ATTEMPTS ? \"InternalError\" : (const char*)nullptr;\n                });});\n        \n        beginTransaction_authorized(\"mIdTag\");\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( attemptNr == 1 );\n\n        mtime += (unsigned long)RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( attemptNr == 2 );\n\n        mtime += 2 * (unsigned long)RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( attemptNr == 3 );\n\n        mtime += 100 * (unsigned long)RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( attemptNr == 3 ); //no further retry after third and successful attempt\n\n        endTransaction();\n        loop();\n        REQUIRE( !ocppPermitsCharge() );\n        REQUIRE( attemptNr == 3 );\n        REQUIRE( checkProcessedStopTx );\n\n        /*\n         * - after reboot, continue attempting\n         */\n\n        getOcppContext()->getModel().getClock().setTime(BASE_TIME); //reset system time to have roughly the same time after reboot\n\n        checkProcessedStartTx = false;\n        checkProcessedStopTx = false;\n        attemptNr = 0;\n\n        beginTransaction_authorized(\"mIdTag\");\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( attemptNr == 1 );\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        getOcppContext()->getModel().getClock().setTime(BASE_TIME);\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StartTransaction\", [&checkProcessedStartTx, &txId, &attemptNr] () {\n            return new Ocpp16::CustomOperation(\"StartTransaction\",\n                [&attemptNr] (JsonObject payload) {\n                    //receive req\n                    attemptNr++;\n                },\n                [&txId] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2));\n                    JsonObject payload = doc->to<JsonObject>();\n\n                    JsonObject idTagInfo = payload.createNestedObject(\"idTagInfo\");\n                    idTagInfo[\"status\"] = \"Accepted\";\n                    payload[\"transactionId\"] = txId++;\n                    return doc;\n                },\n                [&attemptNr] () {\n                    //ErrorCode for CALLERROR\n                    return attemptNr < NUM_ATTEMPTS ? \"InternalError\" : (const char*)nullptr;\n                });});\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"StopTransaction\", [&checkProcessedStopTx] () {\n            return new Ocpp16::CustomOperation(\"StopTransaction\",\n                [&checkProcessedStopTx] (JsonObject payload) {\n                    //receive req\n                    checkProcessedStopTx = true;\n                }, \n                [] () {\n                    //create conf\n                    return createEmptyDocument();\n                });});\n\n        loop();\n\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( attemptNr == 1 );\n\n        mtime += (unsigned long)RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( attemptNr == 2 );\n\n        mtime += 2 * (unsigned long)RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( attemptNr == 3 );\n\n        mtime += 100 * (unsigned long)RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE( ocppPermitsCharge() );\n        REQUIRE( attemptNr == 3 ); //no further retry after third and successful attempt\n\n        endTransaction();\n        loop();\n        REQUIRE( !ocppPermitsCharge() );\n        REQUIRE( attemptNr == 3 );\n        REQUIRE( checkProcessedStopTx );\n    }\n\n    SECTION(\"StatusNotification\") {\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials());\n\n        bool checkProcessed = false;\n        const char *checkStatus = \"\";\n\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StatusNotification\",\n            [&checkProcessed, &checkStatus] (JsonObject payload) {\n                //process req\n                if (payload[\"connectorId\"].as<int>() == 1) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", checkStatus) );\n                }\n            });\n\n        checkStatus = \"Available\";\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Preparing\";\n        checkProcessed = false;\n        setConnectorPluggedInput([] () {return true;});\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Available\";\n        checkProcessed = false;\n        setConnectorPluggedInput([] () {return false;});\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Preparing\";\n        checkProcessed = false;\n        beginTransaction(\"mIdTag\");\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Available\";\n        checkProcessed = false;\n        endTransaction(\"mIdTag\");\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Preparing\";\n        beginTransaction(\"mIdTag\");\n        loop();\n        checkProcessed = false;\n\n        checkStatus = \"Charging\";\n        checkProcessed = false;\n        setConnectorPluggedInput([] () {return true;});\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"SuspendedEV\";\n        checkProcessed = false;\n        setEvReadyInput([] () {return false;});\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"SuspendedEVSE\";\n        checkProcessed = false;\n        setEvReadyInput([] () {return true;});\n        setEvseReadyInput([] () {return false;});\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Charging\";\n        checkProcessed = false;\n        setEvReadyInput([] () {return true;});\n        setEvseReadyInput([] () {return true;});\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Finishing\";\n        checkProcessed = false;\n        endTransaction();\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Available\";\n        checkProcessed = false;\n        setConnectorPluggedInput([] () {return false;});\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Available\";\n        const char *checkStatus2 = checkStatus;\n        checkProcessed = false;\n        mocpp_deinitialize();\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StatusNotification\",\n            [&checkProcessed, &checkStatus, &checkStatus2] (JsonObject payload) {\n                //process req\n                if (payload[\"connectorId\"].as<int>() == 1) {\n                    checkProcessed = true;\n                    REQUIRE( (!strcmp(payload[\"status\"] | \"_Undefined\", checkStatus) || !strcmp(payload[\"status\"] | \"_Undefined\", checkStatus2)) );\n                }\n            });\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Charging\";\n        checkStatus2 = \"Preparing\";\n        checkProcessed = false;\n        beginTransaction(\"mIdTag\");\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Charging\";\n        checkStatus2 = checkStatus;\n        checkProcessed = false;\n        mocpp_deinitialize();\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StatusNotification\",\n            [&checkProcessed, &checkStatus] (JsonObject payload) {\n                //process req\n                if (payload[\"connectorId\"].as<int>() == 1) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", checkStatus) );\n                }\n            });\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkStatus = \"Available\";\n        checkStatus2 = checkStatus;\n        checkProcessed = false;\n        endTransaction();\n        mocpp_deinitialize();\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        getOcppContext()->getOperationRegistry().setOnRequest(\"StatusNotification\",\n            [&checkProcessed, &checkStatus] (JsonObject payload) {\n                //process req\n                if (payload[\"connectorId\"].as<int>() == 1) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", checkStatus) );\n                }\n            });\n        loop();\n        REQUIRE( checkProcessed );\n    }\n\n    SECTION(\"No filesystem access behavior\") {\n\n        //re-init without filesystem access\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(), MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Deactivate));\n        mocpp_set_timer(custom_timer_cb);\n        \n        loop();\n\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n        REQUIRE( !ocppPermitsCharge() );\n\n        for (size_t i = 0; i < 3; i++) {\n\n            beginTransaction(\"mIdTag\");\n            loop();\n\n            REQUIRE( getChargePointStatus() == ChargePointStatus_Charging );\n            REQUIRE( ocppPermitsCharge() );\n\n            endTransaction();\n            loop();\n\n            REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n            REQUIRE( !ocppPermitsCharge() );\n        }\n\n        //Tx status will be lost over reboot\n\n        beginTransaction(\"mIdTag\");\n        loop();\n\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging );\n        REQUIRE( ocppPermitsCharge() );\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(), MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Deactivate));\n        mocpp_set_timer(custom_timer_cb);\n\n        loop();\n\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n        REQUIRE( !ocppPermitsCharge() );\n\n        //Note: queueing offline transactions without FS is currently not implemented\n    }\n\n    mocpp_deinitialize();\n}\n"
  },
  {
    "path": "tests/Configuration.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <string.h>\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/ConfigurationContainer.h>\n#include <MicroOcpp/Core/ConfigurationContainerFlash.h>\n#include <MicroOcpp/Core/Configuration_c.h>\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Debug.h>\n\nusing namespace MicroOcpp;\n\n#define GET_CONFIG_ALL \"[2,\\\"test-msg\\\",\\\"GetConfiguration\\\",{}]\"\n#define KNOWN_KEY \"__ExistingKey\"\n#define UNKOWN_KEY \"__UnknownKey\"\n#define GET_CONFIG_KNOWN_UNKOWN \"[2,\\\"test-mst\\\",\\\"GetConfiguration\\\",{\\\"key\\\":[\\\"\" KNOWN_KEY \"\\\",\\\"\" UNKOWN_KEY \"\\\"]}]\"\n\n// some globals for the C-API tests\nbool g_checkProcessed [10];\nocpp_configuration g_configs [2];\nint g_config_values [2];\nuint16_t g_config_write_count[2];\n\nTEST_CASE( \"Configuration\" ) {\n    printf(\"\\nRun %s\\n\",  \"Configuration\");\n\n    //clean state\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n    LoopbackConnection loopback; //initialize Context with dummy socket\n\n    mocpp_set_timer(custom_timer_cb);\n\n    SECTION(\"Basic container operations\"){\n        std::unique_ptr<ConfigurationContainer> container;\n\n        SECTION(\"Volatile storage\") {\n            container = makeConfigurationContainerVolatile(CONFIGURATION_VOLATILE \"/volatile1\", true);\n        }\n\n        SECTION(\"Persistent storage\") {\n            container = makeConfigurationContainerFlash(filesystem, MO_FILENAME_PREFIX \"persistent1.jsn\", true);\n        }\n\n        //check emptyness\n        REQUIRE( container->size() == 0 );\n\n        //add first config, fetch by index\n        auto configFirst = container->createConfiguration(TConfig::Int, \"cFirst\");\n        REQUIRE( container->size() == 1 );\n        REQUIRE( container->getConfiguration((size_t) 0) == configFirst.get());\n\n        //add one config of each type\n        auto cInt = container->createConfiguration(TConfig::Int, \"cInt\");\n        auto cBool = container->createConfiguration(TConfig::Bool, \"cBool\");\n        auto cString = container->createConfiguration(TConfig::String, \"cString\");\n        \n        REQUIRE( container->size() == 4 );\n\n        //fetch config by key\n        REQUIRE( container->getConfiguration(cBool->getKey()) == cBool);\n\n        //remove config\n        container->remove(cBool.get());\n        REQUIRE( container->size() == 3 );\n        REQUIRE( container->getConfiguration(cBool->getKey()) == nullptr);\n\n        //clean container\n        container->remove(container->getConfiguration((size_t) 0));\n        container->remove(container->getConfiguration((size_t) 0));\n        container->remove(container->getConfiguration((size_t) 0));\n        REQUIRE( container->size() == 0 );\n    }\n\n    SECTION(\"Persistency on filesystem\") {\n\n        auto container = makeConfigurationContainerFlash(filesystem, MO_FILENAME_PREFIX \"persistent1.jsn\", true);\n\n        //trivial load call\n        REQUIRE( container->load() );\n        REQUIRE( container->size() == 0 );\n\n        //add config, store, load again\n        auto cString = container->createConfiguration(TConfig::String, \"cString\");\n        cString->setString(\"mValue\");\n\n        REQUIRE( container->save() ); //store\n\n        container.reset(); //destroy\n\n        //...load again\n        auto container2 = makeConfigurationContainerFlash(filesystem, MO_FILENAME_PREFIX \"persistent1.jsn\", true);\n        REQUIRE( container2->size() == 0 );\n        REQUIRE( container2->load() );\n        REQUIRE( container2->size() == 1 );\n\n        auto cString2 = container2->getConfiguration(\"cString\");\n        REQUIRE( cString2 != nullptr );\n        REQUIRE( !strcmp(cString2->getString(), \"mValue\") );\n    }\n\n    SECTION(\"Configuration API\") {\n\n        //declare configs\n        REQUIRE( configuration_init(filesystem) );\n        auto cInt = declareConfiguration<int>(\"cInt\", 42);\n        REQUIRE( cInt != nullptr );\n        declareConfiguration<bool>(\"cBool\", true);\n        declareConfiguration<const char*>(\"cString\", \"mValue\");\n\n        //fetch config\n        REQUIRE( getConfigurationPublic(\"cInt\")->getInt() == 42 );\n\n        //store, destroy, reload\n        REQUIRE( configuration_save() );\n        cInt.reset();\n        configuration_deinit();\n        REQUIRE( getConfigurationPublic(\"cInt\") == nullptr);\n\n        REQUIRE( configuration_init(filesystem) ); //reload\n\n        //fetch configs (declare with different factory default - should remain at original value)\n        auto cInt2 = declareConfiguration<int>(\"cInt\", -1);\n        auto cBool2 = declareConfiguration<bool>(\"cBool\", false);\n        auto cString2 = declareConfiguration<const char*>(\"cString\", \"no effect\");\n        REQUIRE( configuration_load() ); //load config objects with stored values\n\n        //check load result\n        REQUIRE( cInt2->getInt() == 42 );\n        REQUIRE( cBool2->getBool() == true );\n        REQUIRE( !strcmp(cString2->getString(), \"mValue\") );\n\n        //declare config twice\n        auto cInt3 = declareConfiguration<int>(\"cInt\", -1);\n        REQUIRE( cInt3 == cInt2 );\n\n        //declare config twice but in different container\n        auto cInt4 = declareConfiguration<int>(\"cInt\", -1, CONFIGURATION_VOLATILE);\n        REQUIRE( cInt4 == cInt2 );\n\n        //declare config twice but with conflicting type (will supersede old type because to simplify FW upgrades)\n        auto cNewType = declareConfiguration<const char*>(\"cInt\", \"mValue2\");\n        REQUIRE( cNewType != cInt2 );\n        REQUIRE( !strcmp(cNewType->getString(), \"mValue2\") );\n        \n        //store, destroy, reload\n        REQUIRE( configuration_save() );\n        configuration_deinit();\n        REQUIRE( getConfigurationPublic(\"cInt\") == nullptr);\n        REQUIRE( configuration_init(filesystem) ); //reload\n        auto cNewType2 = declareConfiguration<const char*>(\"cInt\", \"no effect\");\n        REQUIRE( configuration_load() );\n        REQUIRE( !strcmp(cNewType2->getString(), \"mValue2\") );\n\n        //get config before declared (container needs to be declared already at this point)\n        auto cString3 = getConfigurationPublic(\"cString\");\n        REQUIRE( !strcmp(cString3->getString(), \"mValue\") );\n        configuration_deinit();\n\n        //value needs to outlive container\n        configuration_init(filesystem);\n        auto cString4 = declareConfiguration<const char *>(\"cString2\", \"mValue3\");\n        configuration_deinit();\n        REQUIRE( !strcmp(cString4->getString(), \"mValue3\") );\n\n        FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n        //config accessibility / permissions\n        configuration_init(filesystem);\n        bool readonly = false;\n        bool rebootRequired = false;\n        bool isPublic = true;\n        auto cInt6 = declareConfiguration<int>(\"cInt\", 42, CONFIGURATION_FN, readonly, rebootRequired, isPublic);\n        REQUIRE( !cInt6->isReadOnly() );\n        REQUIRE( !cInt6->isRebootRequired() );\n        REQUIRE( getConfigurationPublic(\"cInt\") );\n\n        //revoke permissions\n        readonly = true;\n        rebootRequired = true;\n        declareConfiguration<int>(\"cInt\", 42, CONFIGURATION_FN, readonly, rebootRequired, isPublic);\n        REQUIRE( cInt6->isReadOnly() );\n        REQUIRE( cInt6->isRebootRequired() );\n\n        //revoked permissions cannot be reverted\n        readonly = false;\n        rebootRequired = false;\n        auto cInt7 = declareConfiguration<int>(\"cInt\", 42, CONFIGURATION_FN, readonly, rebootRequired, isPublic);\n        REQUIRE( cInt7->isReadOnly() );\n        REQUIRE( cInt7->isRebootRequired() );\n\n        //accessibility cannot be changed after first initialization\n        isPublic = false;\n        declareConfiguration<int>(\"cInt\", 42, CONFIGURATION_FN, false, rebootRequired, isPublic);\n        declareConfiguration<int>(\"cInt2\", 42, CONFIGURATION_FN, false, rebootRequired, isPublic);\n        REQUIRE( getConfigurationPublic(\"cInt\") );\n        REQUIRE( getConfigurationPublic(\"cInt2\") );\n\n        //create config in hidden container\n        isPublic = false;\n        auto cHidden = declareConfiguration<int>(\"cHidden\", 42, MO_FILENAME_PREFIX \"hidden.jsn\", false, false, isPublic);\n        REQUIRE( !getConfigurationPublic(\"cHidden\") );\n        \n        //hidden container cannot be fetched\n        auto configsPublic = getConfigurationContainersPublic();\n        REQUIRE( configsPublic.size() == 1 );\n\n        configuration_deinit();\n    }\n\n    SECTION(\"ContainerFlash memory optimization\") {\n\n        //key storage optimization: the static key provided by declareConfiguration is preferred. If\n        //no static key is available for the config (if the config is loaded from flash without being\n        //declared before), then a key on the heap is allocated. If the config is later allocated,\n        //the heap-key is replaced by the static key\n\n        const char *key_static = \"cInt\";\n\n        configuration_init(filesystem);\n        auto cInt = declareConfiguration<int>(key_static, 42);\n        configuration_save();\n        configuration_deinit();\n\n        configuration_init(filesystem);\n        declareConfiguration<int>(\"dummy\", 23); //dummy config to declare CONFIGURATION_FN\n        configuration_load();\n        const char *key_heap = getConfigurationPublic(key_static)->getKey();\n        REQUIRE( key_heap != key_static );\n        \n        declareConfiguration<int>(key_static, 42); //replace heap key with static key here\n\n        const char *key_replaced = getConfigurationPublic(key_static)->getKey();\n        REQUIRE( key_replaced == key_static );\n\n        configuration_deinit();\n    }\n\n    SECTION(\"Main lib integration\") {\n\n        //basic lifecycle\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        REQUIRE( getConfigurationPublic(\"ConnectionTimeOut\") );\n        REQUIRE( !getConfigurationContainersPublic().empty() );\n        mocpp_deinitialize();\n        REQUIRE( !getConfigurationPublic(\"ConnectionTimeOut\") );\n        REQUIRE( getConfigurationContainersPublic().empty() );\n\n        //modify standard config ConnectionTimeOut. This config is not modified by the main lib during normal initialization / deinitialization\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        auto config = getConfigurationPublic(\"ConnectionTimeOut\");\n\n        config->setInt(1234); //update\n        configuration_save(); //write back\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        REQUIRE( getConfigurationPublic(\"ConnectionTimeOut\")->getInt() == 1234 );\n\n        mocpp_deinitialize();\n    }\n\n    SECTION(\"GetConfiguration\") {\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        loop();\n\n        declareConfiguration<int>(KNOWN_KEY, 1234, MO_FILENAME_PREFIX \"persistent1.jsn\", false);\n\n        bool checkProcessed = false;\n\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"GetConfiguration\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    doc->to<JsonObject>();\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    JsonArray configurationKey = payload[\"configurationKey\"];\n\n                    bool foundCustomConfig = false;\n                    bool foundStandardConfig = false;\n                    for (JsonObject keyvalue : configurationKey) {\n                        MO_DBG_DEBUG(\"key %s\", keyvalue[\"key\"] | \"_Undefined\");\n                        if (!strcmp(keyvalue[\"key\"] | \"_Undefined\", KNOWN_KEY)) {\n                            foundCustomConfig = true;\n                            REQUIRE( (keyvalue[\"readonly\"] | true) == false );\n                            REQUIRE( !strcmp(keyvalue[\"value\"] | \"_Undefined\", \"1234\") );\n                        } else if (!strcmp(keyvalue[\"key\"] | \"_Undefined\", \"ConnectionTimeOut\")) {\n                            foundStandardConfig = true;\n                        }\n                    }\n\n                    REQUIRE( foundCustomConfig );\n                    REQUIRE( foundStandardConfig );\n                }\n        )));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n\n        checkProcessed = false;\n\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"GetConfiguration\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    auto key = payload.createNestedArray(\"key\");\n                    key.add(KNOWN_KEY);\n                    key.add(UNKOWN_KEY);\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    JsonArray configurationKey = payload[\"configurationKey\"];\n\n                    bool foundCustomConfig = false;\n                    for (JsonObject keyvalue : configurationKey) {\n                        if (!strcmp(keyvalue[\"key\"] | \"_Undefined\", KNOWN_KEY)) {\n                            foundCustomConfig = true;\n                            break;\n                        }\n                    }\n                    REQUIRE( foundCustomConfig );\n\n                    JsonArray unknownKey = payload[\"unknownKey\"];\n\n                    bool foundUnkownKey = false;\n                    for (const char *key : unknownKey) {\n                        if (!strcmp(key, UNKOWN_KEY)) {\n                            foundUnkownKey = true;\n                        }\n                    }\n\n                    REQUIRE( foundUnkownKey );\n                }\n        )));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n\n        mocpp_deinitialize();\n    }\n\n    SECTION(\"ChangeConfiguration\") {\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        loop();\n\n        declareConfiguration<int>(KNOWN_KEY, 0, MO_FILENAME_PREFIX \"persistent1.jsn\", false);\n\n        //update existing config\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeConfiguration\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = KNOWN_KEY;\n                    payload[\"value\"] = \"1234\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE(checkProcessed);\n        REQUIRE( getConfigurationPublic(KNOWN_KEY)->getInt() == 1234 );\n\n        //try to update not existing key\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeConfiguration\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = UNKOWN_KEY;\n                    payload[\"value\"] = \"no effect\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"NotSupported\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        //try to update config with malformatted value\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeConfiguration\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = KNOWN_KEY;\n                    payload[\"value\"] = \"not convertible to int\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Rejected\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        //try to update config with value validation\n        //value is valid if it begins with 1\n        registerConfigurationValidator(KNOWN_KEY, [] (const char *v) {\n            return v[0] == '1';\n        });\n\n        //validation success\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeConfiguration\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = KNOWN_KEY;\n                    payload[\"value\"] = \"100234\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( getConfigurationPublic(KNOWN_KEY)->getInt() == 100234 );\n\n        //validation failure\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeConfiguration\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = KNOWN_KEY;\n                    payload[\"value\"] = \"4321\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Rejected\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( getConfigurationPublic(KNOWN_KEY)->getInt() == 100234 ); //keep old value\n\n        mocpp_deinitialize();\n    }\n\n    SECTION(\"Define factory defaults for standard configs\") {\n\n        //set factory default for standard config ConnectionTimeOut\n        configuration_init(filesystem);\n        auto factoryConnectionTimeOut = declareConfiguration<int>(\"ConnectionTimeOut\", 1234, MO_FILENAME_PREFIX \"factory.jsn\");\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        auto connectionTimeout2 = declareConfiguration<int>(\"ConnectionTimeOut\", 4321);\n        REQUIRE( connectionTimeout2->getInt() == 1234 );\n        REQUIRE( connectionTimeout2 == factoryConnectionTimeOut );\n\n        configuration_save();\n        mocpp_deinitialize();\n\n        //this time, factory default is not given (will lead to duplicates, should be considered in sanitization)\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        REQUIRE( getConfigurationPublic(\"ConnectionTimeOut\")->getInt() != 1234 );\n        mocpp_deinitialize();\n\n        //provide factory default again\n        configuration_init(filesystem);\n        declareConfiguration<int>(\"ConnectionTimeOut\", 4321, MO_FILENAME_PREFIX \"factory.jsn\");\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        REQUIRE( getConfigurationPublic(\"ConnectionTimeOut\")->getInt() == 1234 );\n        mocpp_deinitialize();\n\n    }\n\n    SECTION(\"C-API\") {\n        ocpp_configuration_container container;\n        memset(&container, 0, sizeof(container));\n\n        bool check_load = false;\n\n        container.load = [] (void *user_data) {\n            g_checkProcessed[0] = true;\n            return true;\n        };\n\n        container.save = [] (void *user_data) {\n            g_checkProcessed[1] = true;\n            return true;\n        };\n\n        ocpp_configuration *config_predefined = &g_configs[0];\n        config_predefined->get_key = [] (void *user_data) -> const char* {return \"ConnectionTimeOut\";}; // existing OCPP key to use custom config store\n        config_predefined->get_type = [] (void *user_data) -> ocpp_config_datatype {return ENUM_CDT_INT;};\n        config_predefined->set_int = [] (void *user_data, int val) -> void {g_config_values[0] = val;};\n        config_predefined->get_int = [] (void *user_data) -> int {return g_config_values[0];};\n        config_predefined->get_write_count = [] (void *user_data) -> uint16_t {return g_config_write_count[0];};\n\n        container.create_configuration = [] (void *user_data, ocpp_config_datatype dt, const char *key) -> ocpp_configuration* {\n            ocpp_configuration *config_created = &g_configs[1];\n            config_created->get_key = [] (void *user_data) -> const char* {return \"MCreatedConfig\";}; // non-existing key to test create_configuration function\n            config_created->get_type = [] (void *user_data) -> ocpp_config_datatype {return ENUM_CDT_INT;};\n            config_created->set_int = [] (void *user_data, int val) -> void {g_config_values[1] = val;};\n            config_created->get_int = [] (void *user_data) -> int {return g_config_values[1];};\n            config_created->get_write_count = [] (void *user_data) -> uint16_t {return g_config_write_count[1];};\n            return config_created;\n        };\n\n        container.remove = [] (void *user_data, const char *key) -> void {\n            g_checkProcessed[2] = true;\n        };\n\n        container.size = [] (void *user_data) {\n            return sizeof(g_configs) / sizeof(g_configs[0]);\n        };\n\n        container.get_configuration = [] (void *user_data, size_t i) -> ocpp_configuration* {\n            return &g_configs[i];\n        };\n\n        container.get_configuration_by_key = [] (void *user_data, const char *key) -> ocpp_configuration* {\n            if (!strcmp(key, \"ConnectionTimeOut\")) {\n                return &g_configs[0];\n            } else if (!strcmp(key, \"MCreatedConfig\")) {\n                return g_configs[1].get_key ? \n                    &g_configs[1] : // createConfig has already been called\n                    nullptr; // config hasn't been created yet\n            }\n            return nullptr;\n        };\n\n        ocpp_configuration_container_add(&container, \"MContainerPath\", true);\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        loop();\n\n        REQUIRE( g_checkProcessed[0] );\n\n        auto test_predefined = declareConfiguration<int>(\"ConnectionTimeOut\", 0);\n\n        test_predefined->setInt(12345);\n        REQUIRE( test_predefined->getInt() == 12345 );\n        REQUIRE( config_predefined->get_int(config_predefined->user_data) == 12345 );\n\n        g_checkProcessed[1] = false; // check if store is executed\n        test_predefined->setInt(555);\n        g_config_write_count[0];\n\n        configuration_save();\n\n        REQUIRE( g_checkProcessed[1] );\n\n        // test if declaring new configs is handled\n        auto test_created = declareConfiguration<int>(\"MCreatedConfig\", 123, \"MContainerPath\");\n        REQUIRE( test_created != nullptr );\n        REQUIRE( test_created->getInt() == 123 );\n        ocpp_configuration *config_created = &g_configs[1];\n        REQUIRE( config_created->get_int(config_created->user_data) == 123 );\n\n        mocpp_deinitialize();\n    }\n\n}\n"
  },
  {
    "path": "tests/ConfigurationBehavior.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Operation.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Debug.h>\n#include <MicroOcpp/Version.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\nusing namespace MicroOcpp;\n\nclass CustomAuthorize : public Operation {\nprivate:\n    const char *status;\npublic:\n    CustomAuthorize(const char *status) : status(status) { };\n    const char *getOperationType() override {return \"Authorize\";}\n    void processReq(JsonObject payload) override {\n        //ignore payload - result is determined at construction time\n    }\n    std::unique_ptr<JsonDoc> createConf() override {\n        auto res = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n        auto payload = res->to<JsonObject>();\n        payload[\"idTagInfo\"][\"status\"] = status;\n        return res;\n    }\n};\n\nclass CustomStartTransaction : public Operation {\nprivate:\n    const char *status;\npublic:\n    CustomStartTransaction(const char *status) : status(status) { };\n    const char *getOperationType() override {return \"StartTransaction\";}\n    void processReq(JsonObject payload) override {\n        //ignore payload - result is determined at construction time\n    }\n    std::unique_ptr<JsonDoc> createConf() override {\n        auto res = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(1));\n        auto payload = res->to<JsonObject>();\n        payload[\"idTagInfo\"][\"status\"] = status;\n        payload[\"transactionId\"] = 1000;\n        return res;\n    }\n};\n\nTEST_CASE( \"Configuration Behavior\" ) {\n    printf(\"\\nRun %s\\n\",  \"Configuration Behavior\");\n\n    //clean state\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    MO_DBG_DEBUG(\"remove all\");\n    FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n    auto context = getOcppContext();\n    auto& checkMsg = context->getOperationRegistry();\n\n    auto connector = context->getModel().getConnector(1);\n\n    mocpp_set_timer(custom_timer_cb);\n\n    loop();\n\n    SECTION(\"StopTransactionOnEVSideDisconnect\") {\n        setConnectorPluggedInput([] () {return true;});\n        startTransaction(\"mIdTag\");\n        loop();\n\n        auto configBool = declareConfiguration<bool>(\"StopTransactionOnEVSideDisconnect\", true);\n\n        SECTION(\"set true\") {\n            configBool->setBool(true);\n\n            setConnectorPluggedInput([] () {return false;});\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n        }\n\n        SECTION(\"set false\") {\n            configBool->setBool(false);\n\n            setConnectorPluggedInput([] () {return false;});\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_SuspendedEV);\n\n            endTransaction();\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n        }\n    }\n\n    SECTION(\"StopTransactionOnInvalidId\") {\n        auto configBool = declareConfiguration<bool>(\"StopTransactionOnInvalidId\", true);\n\n        checkMsg.registerOperation(\"Authorize\", [] () {return new CustomAuthorize(\"Invalid\");});\n        checkMsg.registerOperation(\"StartTransaction\", [] () {return new CustomStartTransaction(\"Invalid\");});\n\n        SECTION(\"set true\") {\n            configBool->setBool(true);\n\n            beginTransaction(\"mIdTag_invalid\");\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n\n            beginTransaction_authorized(\"mIdTag_invalid2\");\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n        }\n\n        SECTION(\"set false\") {\n            configBool->setBool(false);\n\n            beginTransaction(\"mIdTag\");\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n\n            beginTransaction_authorized(\"mIdTag\");\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_SuspendedEVSE);\n\n            endTransaction();\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n        }\n    }\n\n    SECTION(\"AllowOfflineTxForUnknownId\") {\n        auto configBool = declareConfiguration<bool>(\"AllowOfflineTxForUnknownId\", true);\n        auto authorizationTimeoutInt = declareConfiguration<int>(MO_CONFIG_EXT_PREFIX \"AuthorizationTimeout\", 1);\n        authorizationTimeoutInt->setInt(1); //try normal Authorize for 1s, then enter offline mode\n\n        loopback.setOnline(false); //connection loss\n\n        SECTION(\"set true\") {\n            configBool->setBool(true);\n\n            beginTransaction(\"mIdTag\");\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n\n            endTransaction();\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n        }\n\n        SECTION(\"set false\") {\n            configBool->setBool(false);\n\n            beginTransaction(\"mIdTag\");\n            REQUIRE(connector->getStatus() == ChargePointStatus_Preparing);\n\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n        }\n\n        endTransaction();\n        loopback.setOnline(true);\n    }\n\n#if MO_ENABLE_LOCAL_AUTH\n    SECTION(\"LocalPreAuthorize\") {\n        auto configBool = declareConfiguration<bool>(\"LocalPreAuthorize\", true);\n        auto authorizationTimeoutInt = declareConfiguration<int>(MO_CONFIG_EXT_PREFIX \"AuthorizationTimeout\", 20);\n        authorizationTimeoutInt->setInt(300); //try normal Authorize for 5 minutes\n\n        declareConfiguration<bool>(\"LocalAuthorizeOffline\", true)->setBool(true);\n        declareConfiguration<bool>(\"LocalAuthListEnabled\", true)->setBool(true);\n\n        //define local auth list with entry local-idtag\n        const char *localListMsg = \"[2,\\\"testmsg-01\\\",\\\"SendLocalList\\\",{\\\"listVersion\\\":1,\\\"localAuthorizationList\\\":[{\\\"idTag\\\":\\\"local-idtag\\\",\\\"idTagInfo\\\":{\\\"status\\\":\\\"Accepted\\\"}}],\\\"updateType\\\":\\\"Full\\\"}]\";\n        loopback.sendTXT(localListMsg, strlen(localListMsg));\n        loop();\n\n        loopback.setOnline(false); //connection loss\n\n        SECTION(\"set true - accepted idtag\") {\n            configBool->setBool(true);\n\n            beginTransaction(\"local-idtag\");\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n        }\n\n        SECTION(\"set false\") {\n            configBool->setBool(false);\n\n            beginTransaction(\"local-idtag\");\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Preparing);\n\n            loopback.setOnline(true);\n            mtime += 20000; //Authorize will be retried after a few seconds\n            loop();\n\n            REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n        }\n\n        endTransaction();\n        loopback.setOnline(true);\n    }\n#endif //MO_ENABLE_LOCAL_AUTH\n\n    SECTION(\"AuthorizeRemoteTxRequests\") {\n        auto configBool = declareConfiguration<bool>(\"AuthorizeRemoteTxRequests\", false);\n\n        bool receivedAuthorize = false;\n\n        setOnReceiveRequest(\"Authorize\", [&receivedAuthorize] (JsonObject payload) {\n            receivedAuthorize = true;\n            REQUIRE( !strcmp(payload[\"idTag\"] | \"_Undefined\", \"mIdTag\") );\n        });\n\n        SECTION(\"set true\") {\n            configBool->setBool(true);\n\n            context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                    \"RemoteStartTransaction\",\n                    [] () {\n                        //create req\n                        auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                        auto payload = doc->to<JsonObject>();\n                        payload[\"idTag\"] = \"mIdTag\";\n                        return doc;},\n                    [] (JsonObject) {\n                        //ignore conf\n                    }\n            )));\n\n            loop();\n\n            REQUIRE(receivedAuthorize);\n            REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n        }\n\n        SECTION(\"set false\") {\n            configBool->setBool(false);\n\n            context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                    \"RemoteStartTransaction\",\n                    [] () {\n                        //create req\n                        auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                        auto payload = doc->to<JsonObject>();\n                        payload[\"idTag\"] = \"mIdTag\";\n                        return doc;},\n                    [] (JsonObject) {\n                        //ignore conf\n                    }\n            )));\n\n            loop();\n\n            REQUIRE(!receivedAuthorize);\n            REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n        }\n\n        endTransaction();\n        loop();\n    }\n\n    mocpp_deinitialize();\n}\n"
  },
  {
    "path": "tests/FirmwareManagement.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Core/Request.h>\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Configuration.h>\n\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>\n\n#define BASE_TIME     \"2023-01-01T00:00:00.000Z\"\n#define BASE_TIME_1H  \"2023-01-01T01:00:00.000Z\"\n#define FTP_URL       \"ftps://localhost/firmware.bin\"\n\nusing namespace MicroOcpp;\n\nTEST_CASE( \"FirmwareManagement\" ) {\n    printf(\"\\nRun %s\\n\",  \"FirmwareManagement\");\n\n    //clean state\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n\n    mocpp_set_timer(custom_timer_cb);\n\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner\"));\n    auto& model = getOcppContext()->getModel();\n    auto fwService = getFirmwareService();\n    SECTION(\"FirmwareService initialized\") {\n        REQUIRE(fwService != nullptr);\n    }\n\n    model.getClock().setTime(BASE_TIME);\n\n    loop();\n\n    SECTION(\"Unconfigured FW service\") {\n\n        bool checkProcessed = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"FirmwareStatusNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"FirmwareStatusNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        checkProcessed = true;\n                        REQUIRE((\n                            !strcmp(payload[\"status\"] | \"_Undefined\", \"DownloadFailed\") ||\n                            !strcmp(payload[\"status\"] | \"_Undefined\", \"InstallationFailed\")\n                        ));\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n        \n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"UpdateFirmware\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(4));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"location\"] = FTP_URL;\n                    payload[\"retries\"] = 1;\n                    payload[\"retrieveDate\"] = BASE_TIME;\n                    payload[\"retryInterval\"] = 1;\n                    return doc;},\n                [] (JsonObject) { } //ignore conf\n        )));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n    }\n\n    SECTION(\"Download phase only\") {\n\n        int checkProcessed = 0;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"FirmwareStatusNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"FirmwareStatusNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        if (checkProcessed == 0) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Downloading\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 1) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Downloaded\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 2) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Installed\") );\n                            checkProcessed++;\n                        }\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n\n        bool checkProcessedOnDownload = false;\n\n        fwService->setOnDownload([&checkProcessedOnDownload] (const char *location) {\n            checkProcessedOnDownload = true;\n            return true;\n        });\n\n        int checkProcessedOnDownloadStatus = 0;\n\n        fwService->setDownloadStatusInput([&checkProcessed, &checkProcessedOnDownloadStatus] () {\n            if (checkProcessed == 0) {\n                if (checkProcessedOnDownloadStatus == 0) checkProcessedOnDownloadStatus = 1;\n                return DownloadStatus::NotDownloaded;\n            } else {\n                if (checkProcessedOnDownloadStatus == 1) checkProcessedOnDownloadStatus = 2;\n                return DownloadStatus::Downloaded;\n            }\n        });\n\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"UpdateFirmware\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(4));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"location\"] = FTP_URL;\n                    payload[\"retries\"] = 1;\n                    payload[\"retrieveDate\"] = BASE_TIME;\n                    payload[\"retryInterval\"] = 1;\n                    return doc;},\n                [] (JsonObject) { } //ignore conf\n        )));\n\n        for (unsigned int i = 0; i < 10; i++) {\n            loop();\n            mtime += 5000;\n        }\n\n        REQUIRE( checkProcessed == 3 ); //all FirmwareStatusNotification messages have been received\n        REQUIRE( checkProcessedOnDownload );\n        REQUIRE( checkProcessedOnDownloadStatus == 2 );\n    }\n\n    SECTION(\"Installation phase only\") {\n\n        int checkProcessed = 0;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"FirmwareStatusNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"FirmwareStatusNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        if (checkProcessed == 0) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Installing\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 1) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Installed\") );\n                            checkProcessed++;\n                        }\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n        \n        bool checkProcessedOnInstall = false;\n\n        fwService->setOnInstall([&checkProcessedOnInstall] (const char *location) {\n            checkProcessedOnInstall = true;\n            REQUIRE( !strcmp(location, FTP_URL) );\n            return true;\n        });\n\n        int checkProcessedOnInstallStatus = 0;\n\n        fwService->setInstallationStatusInput([&checkProcessed, &checkProcessedOnInstallStatus] () {\n            if (checkProcessed == 0) {\n                if (checkProcessedOnInstallStatus == 0) checkProcessedOnInstallStatus = 1;\n                return InstallationStatus::NotInstalled;\n            } else {\n                if (checkProcessedOnInstallStatus == 1) checkProcessedOnInstallStatus = 2;\n                return InstallationStatus::Installed;\n            }\n        });\n        \n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"UpdateFirmware\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(4));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"location\"] = FTP_URL;\n                    payload[\"retries\"] = 1;\n                    payload[\"retrieveDate\"] = BASE_TIME;\n                    payload[\"retryInterval\"] = 1;\n                    return doc;},\n                [] (JsonObject) { } //ignore conf\n        )));\n\n        for (unsigned int i = 0; i < 10; i++) {\n            loop();\n            mtime += 5000;\n        }\n\n        REQUIRE( checkProcessed == 2 ); //all FirmwareStatusNotification messages have been received\n        REQUIRE( checkProcessedOnInstall );\n        REQUIRE( checkProcessedOnInstallStatus == 2 );\n    }\n\n    SECTION(\"Download and install\") {\n\n        int checkProcessed = 0;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"FirmwareStatusNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"FirmwareStatusNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        if (checkProcessed == 0) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Downloading\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 1) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Downloaded\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 2) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Installing\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 3) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Installed\") );\n                            checkProcessed++;\n                        }\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n        \n        bool checkProcessedOnDownload = false;\n        fwService->setOnDownload([&checkProcessedOnDownload] (const char *location) {\n            checkProcessedOnDownload = true;\n            return true;\n        });\n\n        int checkProcessedOnDownloadStatus = 0;\n        fwService->setDownloadStatusInput([&checkProcessed, &checkProcessedOnDownloadStatus] () {\n            if (checkProcessed == 0) {\n                if (checkProcessedOnDownloadStatus == 0) checkProcessedOnDownloadStatus = 1;\n                return DownloadStatus::NotDownloaded;\n            } else {\n                if (checkProcessedOnDownloadStatus == 1) checkProcessedOnDownloadStatus = 2;\n                return DownloadStatus::Downloaded;\n            }\n        });\n        \n        bool checkProcessedOnInstall = false;\n        fwService->setOnInstall([&checkProcessedOnInstall] (const char *location) {\n            checkProcessedOnInstall = true;\n            return true;\n        });\n\n        int checkProcessedOnInstallStatus = 0;\n        fwService->setInstallationStatusInput([&checkProcessed, &checkProcessedOnInstallStatus] () {\n            if (checkProcessed <= 2) {\n                if (checkProcessedOnInstallStatus == 0) checkProcessedOnInstallStatus = 1;\n                return InstallationStatus::NotInstalled;\n            } else {\n                if (checkProcessedOnInstallStatus == 1) checkProcessedOnInstallStatus = 2;\n                return InstallationStatus::Installed;\n            }\n        });\n        \n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"UpdateFirmware\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(4));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"location\"] = FTP_URL;\n                    payload[\"retries\"] = 1;\n                    payload[\"retrieveDate\"] = BASE_TIME;\n                    payload[\"retryInterval\"] = 1;\n                    return doc;},\n                [] (JsonObject) { } //ignore conf\n        )));\n\n        for (unsigned int i = 0; i < 10; i++) {\n            loop();\n            mtime += 5000;\n        }\n\n        REQUIRE( checkProcessed == 4 ); //all FirmwareStatusNotification messages have been received\n        REQUIRE( checkProcessedOnDownload );\n        REQUIRE( checkProcessedOnDownloadStatus == 2 );\n        REQUIRE( checkProcessedOnInstall );\n        REQUIRE( checkProcessedOnInstallStatus == 2 );\n    }\n\n    SECTION(\"Download failure (try 2 times)\") {\n\n        int checkProcessed = 0;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"FirmwareStatusNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"FirmwareStatusNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        if (checkProcessed == 0) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Downloading\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 1) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"DownloadFailed\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 2) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Downloading\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 3) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"DownloadFailed\") );\n                            checkProcessed++;\n                        }\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n\n        int checkProcessedOnDownload = 0;\n        fwService->setOnDownload([&checkProcessedOnDownload] (const char *location) {\n            checkProcessedOnDownload++;\n            return true;\n        });\n\n        int checkProcessedOnDownloadStatus = 0;\n        fwService->setDownloadStatusInput([&checkProcessed, &checkProcessedOnDownloadStatus] () {\n            if (checkProcessed == 0) {\n                if (checkProcessedOnDownloadStatus == 0) checkProcessedOnDownloadStatus = 1;\n                return DownloadStatus::NotDownloaded;\n            } else if (checkProcessed == 1) {\n                if (checkProcessedOnDownloadStatus == 1) checkProcessedOnDownloadStatus = 2;\n                return DownloadStatus::DownloadFailed;\n            } else if (checkProcessed == 2) {\n                if (checkProcessedOnDownloadStatus == 2) checkProcessedOnDownloadStatus = 3;\n                return DownloadStatus::NotDownloaded;\n            } else {\n                if (checkProcessedOnDownloadStatus == 3) checkProcessedOnDownloadStatus = 4;\n                return DownloadStatus::DownloadFailed;\n            }\n        });\n\n        bool checkProcessedOnInstall = false; // must not be executed\n        fwService->setOnInstall([&checkProcessedOnInstall] (const char *location) {\n            checkProcessedOnInstall = true;\n            return true;\n        });\n\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"UpdateFirmware\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(4));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"location\"] = FTP_URL;\n                    payload[\"retries\"] = 2;\n                    payload[\"retrieveDate\"] = BASE_TIME;\n                    payload[\"retryInterval\"] = 10;\n                    return doc;},\n                [] (JsonObject) { } //ignore conf\n        )));\n\n        for (unsigned int i = 0; i < 20; i++) {\n            loop();\n            mtime += 5000;\n        }\n\n        REQUIRE( checkProcessed == 4 ); //all FirmwareStatusNotification messages have been received\n        REQUIRE( checkProcessedOnDownload == 2 );\n        REQUIRE( checkProcessedOnDownloadStatus == 4 );\n        REQUIRE( !checkProcessedOnInstall );\n    }\n\n    SECTION(\"Installation failure (try 2 times)\") {\n\n        int checkProcessed = 0;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"FirmwareStatusNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"FirmwareStatusNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        if (checkProcessed == 0) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Installing\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 1) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"InstallationFailed\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 2) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Installing\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 3) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"InstallationFailed\") );\n                            checkProcessed++;\n                        }\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n        \n        int checkProcessedOnInstall = 0;\n\n        fwService->setOnInstall([&checkProcessedOnInstall] (const char *location) {\n            checkProcessedOnInstall++;\n            return true;\n        });\n\n        int checkProcessedOnInstallStatus = 0;\n\n        fwService->setInstallationStatusInput([&checkProcessed, &checkProcessedOnInstallStatus] () {\n            if (checkProcessed == 0) {\n                if (checkProcessedOnInstallStatus == 0) checkProcessedOnInstallStatus = 1;\n                return InstallationStatus::NotInstalled;\n            } else if (checkProcessed == 1) {\n                if (checkProcessedOnInstallStatus == 1) checkProcessedOnInstallStatus = 2;\n                return InstallationStatus::InstallationFailed;\n            } else if (checkProcessed == 2) {\n                if (checkProcessedOnInstallStatus == 2) checkProcessedOnInstallStatus = 3;\n                return InstallationStatus::NotInstalled;\n            } else {\n                if (checkProcessedOnInstallStatus == 3) checkProcessedOnInstallStatus = 4;\n                return InstallationStatus::InstallationFailed;\n            }\n        });\n        \n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"UpdateFirmware\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(4));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"location\"] = FTP_URL;\n                    payload[\"retries\"] = 2;\n                    payload[\"retrieveDate\"] = BASE_TIME;\n                    payload[\"retryInterval\"] = 10;\n                    return doc;},\n                [] (JsonObject) { } //ignore conf\n        )));\n\n        for (unsigned int i = 0; i < 10; i++) {\n            loop();\n            mtime += 5000;\n        }\n\n        REQUIRE( checkProcessed == 4 ); //all FirmwareStatusNotification messages have been received\n        REQUIRE( checkProcessedOnInstall == 2 );\n        REQUIRE( checkProcessedOnInstallStatus == 4 );\n    }\n\n    SECTION(\"Wait for retreiveDate and charging sessions end\") {\n\n        beginTransaction(\"mIdTag\");\n\n        int checkProcessed = 0;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"FirmwareStatusNotification\",\n            [&checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"FirmwareStatusNotification\",\n                    [ &checkProcessed] (JsonObject payload) {\n                        //process req\n                        if (checkProcessed == 0) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Downloading\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 1) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Downloaded\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 2) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Installing\") );\n                            checkProcessed++;\n                        } else if (checkProcessed == 3) {\n                            REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Installed\") );\n                            checkProcessed++;\n                        }\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n        \n        bool checkProcessedOnDownload = false;\n        fwService->setOnDownload([&checkProcessedOnDownload] (const char *location) {\n            checkProcessedOnDownload = true;\n            return true;\n        });\n\n        int checkProcessedOnDownloadStatus = 0;\n        fwService->setDownloadStatusInput([&checkProcessed, &checkProcessedOnDownloadStatus] () {\n            if (checkProcessed == 0) {\n                if (checkProcessedOnDownloadStatus == 0) checkProcessedOnDownloadStatus = 1;\n                return DownloadStatus::NotDownloaded;\n            } else {\n                if (checkProcessedOnDownloadStatus == 1) checkProcessedOnDownloadStatus = 2;\n                return DownloadStatus::Downloaded;\n            }\n        });\n        \n        bool checkProcessedOnInstall = false;\n        fwService->setOnInstall([&checkProcessedOnInstall] (const char *location) {\n            checkProcessedOnInstall = true;\n            return true;\n        });\n\n        int checkProcessedOnInstallStatus = 0;\n        fwService->setInstallationStatusInput([&checkProcessed, &checkProcessedOnInstallStatus] () {\n            if (checkProcessed <= 2) {\n                if (checkProcessedOnInstallStatus == 0) checkProcessedOnInstallStatus = 1;\n                return InstallationStatus::NotInstalled;\n            } else {\n                if (checkProcessedOnInstallStatus == 1) checkProcessedOnInstallStatus = 2;\n                return InstallationStatus::Installed;\n            }\n        });\n        \n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"UpdateFirmware\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(4));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"location\"] = FTP_URL;\n                    payload[\"retries\"] = 1;\n                    payload[\"retrieveDate\"] = BASE_TIME_1H;\n                    payload[\"retryInterval\"] = 1;\n                    return doc;},\n                [] (JsonObject) { } //ignore conf\n        )));\n\n        for (unsigned int i = 0; i < 10; i++) {\n            loop();\n            mtime += 5000;\n        }\n\n        //retreiveDate not reached yet\n        REQUIRE( checkProcessed == 0 );\n        REQUIRE( !checkProcessedOnDownload );\n        REQUIRE( checkProcessedOnDownloadStatus == 0 );\n        REQUIRE( !checkProcessedOnInstall );\n        REQUIRE( checkProcessedOnInstallStatus == 0 );\n\n        getOcppContext()->getModel().getClock().setTime(BASE_TIME_1H);\n\n        for (unsigned int i = 0; i < 10; i++) {\n            loop();\n            mtime += 5000;\n        }\n\n        //download-related FirmwareStatusNotification messages have been received\n        REQUIRE( checkProcessed == 2 );\n        REQUIRE( checkProcessedOnDownload );\n        REQUIRE( checkProcessedOnDownloadStatus == 2 );\n        REQUIRE( !checkProcessedOnInstall );\n        REQUIRE( checkProcessedOnInstallStatus == 0 );\n\n        endTransaction();\n\n        for (unsigned int i = 0; i < 10; i++) {\n            loop();\n            mtime += 5000;\n        }\n\n        //all FirmwareStatusNotification messages have been received\n        REQUIRE( checkProcessed == 4 );\n        REQUIRE( checkProcessedOnDownload );\n        REQUIRE( checkProcessedOnDownloadStatus == 2 );\n        REQUIRE( checkProcessedOnInstall );\n        REQUIRE( checkProcessedOnInstallStatus == 2 );\n    }\n\n    mocpp_deinitialize();\n\n}\n"
  },
  {
    "path": "tests/LocalAuthList.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_LOCAL_AUTH\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Core/Request.h>\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Model/Authorization/AuthorizationService.h>\n\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\nusing namespace MicroOcpp;\n\nvoid generateAuthList(JsonArray out, size_t size, bool compact) {\n    for (size_t i = 0; i < size; i++) {\n        JsonObject authData = out.createNestedObject();\n        JsonObject idTagInfo;\n        if (compact) {\n            //flat structure\n            idTagInfo = authData;\n        } else {\n            //nested idTagInfo\n            idTagInfo = authData[\"idTagInfo\"].to<JsonObject>();\n        }\n\n        char buf [IDTAG_LEN_MAX + 1];\n        sprintf(buf, \"mIdTag%zu\", i);\n        authData[AUTHDATA_KEY_IDTAG(compact)] = buf;\n        idTagInfo[AUTHDATA_KEY_STATUS(compact)] = \"Accepted\";\n    }\n}\n\nTEST_CASE( \"LocalAuth\" ) {\n    printf(\"\\nRun %s\\n\",  \"LocalAuth\");\n\n    //clean state\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n\n    mocpp_set_timer(custom_timer_cb);\n\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n    auto& model = getOcppContext()->getModel();\n    auto authService = model.getAuthorizationService();\n    auto connector = model.getConnector(1);\n    model.getClock().setTime(BASE_TIME);\n\n    loop();\n    \n    //enable local auth\n    declareConfiguration<bool>(\"LocalAuthListEnabled\", true)->setBool(true);\n\n    //set Authorize timeout after which the charger is considered offline\n    const unsigned long AUTH_TIMEOUT_MS = 60000;\n    MicroOcpp::declareConfiguration<int>(MO_CONFIG_EXT_PREFIX \"AuthorizationTimeout\", -1)->setInt(AUTH_TIMEOUT_MS / 1000);\n\n    //fetch local auth configs\n    auto localAuthorizeOffline = declareConfiguration<bool>(\"LocalAuthorizeOffline\", false);\n    auto localPreAuthorize = declareConfiguration<bool>(\"LocalPreAuthorize\", false);\n\n    SECTION(\"Basic local auth - LocalPreAuthorize\") {\n\n        localAuthorizeOffline->setBool(false);\n        localPreAuthorize->setBool(true);\n        \n        //set local list\n        StaticJsonDocument<256> localAuthList;\n        localAuthList[0][\"idTag\"] = \"mIdTag\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n\n        REQUIRE( authService->updateLocalList(localAuthList.as<JsonArray>(), 1, false) );\n\n        REQUIRE( authService->getLocalListSize() == 1 );\n        REQUIRE( authService->getLocalAuthorization(\"mIdTag\") != nullptr );\n        REQUIRE( authService->getLocalAuthorization(\"mIdTag\")->getAuthorizationStatus() == AuthorizationStatus::Accepted );\n\n        //check TX notification\n        bool checkTxAuthorized = false;\n        setTxNotificationOutput([&checkTxAuthorized] (Transaction*, TxNotification txNotification) {\n            if (txNotification == TxNotification_Authorized) {\n                checkTxAuthorized = true;\n            }\n        });\n\n        //begin transaction and delay Authorize request - tx should start immediately\n        loopback.setOnline(false); //Authorize delayed by short offline period\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        beginTransaction(\"mIdTag\");\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( checkTxAuthorized );\n\n        loopback.setOnline(true);\n        endTransaction();\n        loop();\n\n        //begin transaction delay Authorize request, but idTag doesn't match local list - tx should start when online again\n        checkTxAuthorized = false;\n        loopback.setOnline(false);\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        beginTransaction(\"wrong idTag\");\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Preparing );\n        REQUIRE( !checkTxAuthorized );\n\n        loopback.setOnline(true);\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( checkTxAuthorized );\n\n        endTransaction();\n        loop();\n    }\n\n    SECTION(\"Basic local auth - LocalAuthorizeOffline\") {\n\n        localAuthorizeOffline->setBool(true);\n        localPreAuthorize->setBool(false);\n\n        //set local list\n        StaticJsonDocument<256> localAuthList;\n        localAuthList[0][\"idTag\"] = \"mIdTag\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), 1, false);\n\n        //check TX notification\n        bool checkTxAuthorized = false;\n        setTxNotificationOutput([&checkTxAuthorized] (Transaction*, TxNotification txNotification) {\n            if (txNotification == TxNotification_Authorized) {\n                checkTxAuthorized = true;\n            }\n        });\n\n        //make charger offline and begin tx - tx should begin after Authorize timeout\n        loopback.setOnline(false);\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        unsigned long t_before = mocpp_tick_ms();\n\n        beginTransaction(\"mIdTag\");\n        loop();\n\n        REQUIRE( mocpp_tick_ms() - t_before < AUTH_TIMEOUT_MS ); //if this fails, increase AUTH_TIMEOUT_MS\n        REQUIRE( connector->getStatus() == ChargePointStatus_Preparing );\n        REQUIRE( !checkTxAuthorized );\n\n        mtime += AUTH_TIMEOUT_MS - (mocpp_tick_ms() - t_before); //increment clock so that auth timeout is exceeded\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( checkTxAuthorized );\n\n        loopback.setOnline(true);\n        endTransaction();\n        loop();\n\n        //make charger offline and begin tx, but idTag doesn't match - tx should be aborted\n        bool checkTxTimeout = false;\n        setTxNotificationOutput([&checkTxTimeout] (Transaction*, TxNotification txNotification) {\n            if (txNotification == TxNotification_AuthorizationTimeout) {\n                checkTxTimeout = true;\n            }\n        });\n        loopback.setOnline(false);\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        t_before = mocpp_tick_ms();\n\n        beginTransaction(\"wrong idTag\");\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Preparing );\n        REQUIRE( !checkTxTimeout );\n\n        mtime += AUTH_TIMEOUT_MS - (mocpp_tick_ms() - t_before); //increment clock so that auth timeout is exceeded\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n        REQUIRE( checkTxTimeout );\n\n        loopback.setOnline(true);\n        loop();\n    }\n\n    SECTION(\"Basic local auth - AllowOfflineTxForUnknownId\") {\n\n        localAuthorizeOffline->setBool(false);\n        localPreAuthorize->setBool(false);\n        MicroOcpp::declareConfiguration<bool>(\"AllowOfflineTxForUnknownId\", true)->setBool(true);\n\n        //check TX notification\n        bool checkTxAuthorized = false;\n        setTxNotificationOutput([&checkTxAuthorized] (Transaction*, TxNotification txNotification) {\n            if (txNotification == TxNotification_Authorized) {\n                checkTxAuthorized = true;\n            }\n        });\n\n        //make charger offline and begin tx - tx should begin after Authorize timeout\n        loopback.setOnline(false);\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        unsigned long t_before = mocpp_tick_ms();\n\n        beginTransaction(\"unknownIdTag\");\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Preparing );\n        REQUIRE( !checkTxAuthorized );\n\n        mtime += AUTH_TIMEOUT_MS - (mocpp_tick_ms() - t_before); //increment clock so that auth timeout is exceeded\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( checkTxAuthorized );\n\n        loopback.setOnline(true);\n        endTransaction();\n        loop();\n    }\n\n    SECTION(\"Local auth - check WS online status\") {\n\n        localAuthorizeOffline->setBool(false);\n        localPreAuthorize->setBool(false);\n        MicroOcpp::declareConfiguration<bool>(\"AllowOfflineTxForUnknownId\", true)->setBool(true);\n\n        //check TX notification\n        bool checkTxAuthorized = false;\n        setTxNotificationOutput([&checkTxAuthorized] (Transaction*, TxNotification txNotification) {\n            if (txNotification == TxNotification_Authorized) {\n                checkTxAuthorized = true;\n            }\n        });\n\n        //disconnect WS and begin tx - charger should enter offline mode immediately\n        loopback.setConnected(false);\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        beginTransaction(\"unknownIdTag\");\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( checkTxAuthorized );\n\n        loopback.setConnected(true);\n        endTransaction();\n        loop();\n    }\n\n    SECTION(\"Local auth list entry expired / unauthorized\") {\n\n        localPreAuthorize->setBool(true);\n\n        //set local list with expired / unauthorized entry\n        StaticJsonDocument<512> localAuthList;\n        localAuthList[0][\"idTag\"] = \"mIdTagExpired\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        localAuthList[0][\"idTagInfo\"][\"expiryDate\"] = BASE_TIME; //is in past\n        localAuthList[1][\"idTag\"] = \"mIdTagUnauthorized\";\n        localAuthList[1][\"idTagInfo\"][\"status\"] = \"Blocked\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), 1, false);\n        REQUIRE( authService->getLocalAuthorization(\"mIdTagExpired\") );\n        REQUIRE( authService->getLocalAuthorization(\"mIdTagUnauthorized\") );\n\n        REQUIRE( authService->getLocalAuthorization(\"mIdTagExpired\") );\n\n        //begin transaction and delay Authorize request - cannot PreAuthorize because entry is expired\n        loopback.setOnline(false); //Authorize delayed by short offline period\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        beginTransaction(\"mIdTagExpired\");\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Preparing );\n\n        loopback.setOnline(true);\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n\n        endTransaction();\n        loop();\n\n        //begin transaction and delay Authorize request - cannot PreAuthorize because entry is unauthorized\n        loopback.setOnline(false); //Authorize delayed by short offline period\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        beginTransaction(\"mIdTagUnauthorized\");\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Preparing );\n\n        loopback.setOnline(true);\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        endTransaction();\n        loop();\n    }\n\n    SECTION(\"Mix local authorization modes\") {\n\n        localAuthorizeOffline->setBool(true);\n        localPreAuthorize->setBool(true);\n        MicroOcpp::declareConfiguration<bool>(\"AllowOfflineTxForUnknownId\", true)->setBool(true);\n\n        //set local list with accepted and accepted entry\n        StaticJsonDocument<512> localAuthList;\n        localAuthList[0][\"idTag\"] = \"mIdTagExpired\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        localAuthList[0][\"idTagInfo\"][\"expiryDate\"] = BASE_TIME; //is in past\n        localAuthList[1][\"idTag\"] = \"mIdTagAccepted\";\n        localAuthList[1][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), 1, false);\n        REQUIRE( authService->getLocalAuthorization(\"mIdTagExpired\") );\n        REQUIRE( authService->getLocalAuthorization(\"mIdTagAccepted\") );\n\n        //begin transaction and delay Authorize request - tx should start immediately\n        loopback.setOnline(false);\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        beginTransaction(\"mIdTagAccepted\");\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n\n        loopback.setOnline(true);\n        endTransaction();\n        loop();\n\n        //begin transaction, but idTag is expired - AllowOfflineTxForUnknownId must not apply\n        loopback.setOnline(false);\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        unsigned long t_before = mocpp_tick_ms();\n\n        beginTransaction(\"mIdTagExpired\");\n        loop();\n\n        REQUIRE( mocpp_tick_ms() - t_before < AUTH_TIMEOUT_MS ); //if this fails, increase AUTH_TIMEOUT_MS\n        REQUIRE( connector->getStatus() == ChargePointStatus_Preparing );\n\n        mtime += AUTH_TIMEOUT_MS - (mocpp_tick_ms() - t_before); //increment clock so that auth timeout is exceeded\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        loopback.setOnline(true);\n        loop();\n    }\n\n    SECTION(\"DeAuthorize locally authorized tx\") {\n\n        localAuthorizeOffline->setBool(false);\n        localPreAuthorize->setBool(true);\n\n        //check TX notification\n        bool checkTxAuthorized = false;\n        setTxNotificationOutput([&checkTxAuthorized] (Transaction*, TxNotification txNotification) {\n            if (txNotification == TxNotification_Authorized) {\n                checkTxAuthorized = true;\n            }\n        });\n\n        //set local list\n        StaticJsonDocument<256> localAuthList;\n        localAuthList[0][\"idTag\"] = \"mIdTag\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), 1, false);\n\n        //patch Authorize so it will reject all idTags\n        getOcppContext()->getOperationRegistry().registerOperation(\"Authorize\", [] () {\n            return new Ocpp16::CustomOperation(\"Authorize\",\n                [] (JsonObject) {}, //ignore req\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTagInfo\"][\"status\"] = \"Blocked\";\n                    return doc;\n                });});\n\n        //begin transaction and delay Authorize request - tx should start immediately\n        loopback.setOnline(false); //Authorize delayed by short offline period\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        beginTransaction(\"mIdTag\");\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( checkTxAuthorized );\n\n        //check TX notification\n        bool checkTxRejected = false;\n        setTxNotificationOutput([&checkTxRejected] (Transaction*, TxNotification txNotification) {\n            if (txNotification == TxNotification_AuthorizationRejected) {\n                checkTxRejected = true;\n            }\n        });\n\n        loopback.setOnline(true);\n        loop();\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n        REQUIRE( checkTxRejected );\n\n        loop();\n    }\n\n    SECTION(\"LocalListConflict\") {\n\n        //patch Authorize so it will reject all idTags\n        bool checkAuthorize = false;\n        getOcppContext()->getOperationRegistry().registerOperation(\"Authorize\", [&checkAuthorize] () {\n            return new Ocpp16::CustomOperation(\"Authorize\",\n                [&checkAuthorize] (JsonObject) {\n                    checkAuthorize = true;\n                },\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTagInfo\"][\"status\"] = \"Blocked\";\n                    return doc;\n                });});\n        \n        //patch StartTransaction so it will DeAuthorize all txs\n        bool checkStartTx = false;\n        getOcppContext()->getOperationRegistry().registerOperation(\"StartTransaction\", [&checkStartTx] () {\n            return new Ocpp16::CustomOperation(\"StartTransaction\",\n                [&checkStartTx] (JsonObject) {\n                    checkStartTx = true;\n                },\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTagInfo\"][\"status\"] = \"Blocked\";\n                    payload[\"transactionId\"] = 1000;\n                    return doc;\n                });});\n        \n        //check resulting StatusNotification message\n        bool checkLocalListConflict = false;\n        getOcppContext()->getOperationRegistry().registerOperation(\"StatusNotification\", [&checkLocalListConflict] () {\n            return new Ocpp16::CustomOperation(\"StatusNotification\",\n                [&checkLocalListConflict] (JsonObject payload) {\n                    if (payload[\"connectorId\"] == 0 &&\n                            !strcmp(payload[\"errorCode\"] | \"_Undefined\", \"LocalListConflict\")) {\n                        checkLocalListConflict = true;\n                    }\n                }, \n                [] () {\n                    //create conf\n                    return createEmptyDocument();\n                });});\n\n        //set local list\n        StaticJsonDocument<256> localAuthList;\n        localAuthList[0][\"idTag\"] = \"mIdTag\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), 1, false);\n\n        //send Authorize and StartTx and check if they trigger LocalListConflict\n        beginTransaction(\"mIdTag\");\n        loop();\n        REQUIRE( checkLocalListConflict );\n        REQUIRE( checkAuthorize );\n        REQUIRE( !checkStartTx );\n\n        checkLocalListConflict = false;\n        checkAuthorize = false;\n        beginTransaction_authorized(\"mIdTag\");\n        loop();\n        REQUIRE( checkLocalListConflict );\n        REQUIRE( !checkAuthorize );\n        REQUIRE( checkStartTx );\n    }\n\n    SECTION(\"Update local list\") {\n\n        REQUIRE( authService->getLocalListSize() == 0 ); //idle, empty local list\n\n        //set local list\n        int localListVersion = 42;\n        StaticJsonDocument<256> localAuthList;\n        localAuthList[0][\"idTag\"] = \"mIdTag\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, false);\n        REQUIRE( authService->getLocalListVersion() == localListVersion );\n        REQUIRE( authService->getLocalListSize() == 1 );\n        REQUIRE( authService->getLocalAuthorization(\"mIdTag\") != nullptr );\n\n        //overwrite list\n        localListVersion++;\n        localAuthList.clear();\n        localAuthList[0][\"idTag\"] = \"mIdTag2\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, false);\n        REQUIRE( authService->getLocalListVersion() == localListVersion );\n        REQUIRE( authService->getLocalListSize() == 1 );\n        REQUIRE( authService->getLocalAuthorization(\"mIdTag\") == nullptr );\n        REQUIRE( authService->getLocalAuthorization(\"mIdTag2\") != nullptr );\n\n        //reset list\n        localListVersion++;\n        localAuthList.clear();\n        localAuthList.to<JsonArray>();\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, false);\n        REQUIRE( authService->getLocalListVersion() == 0 ); //localListVersion is ignored - empty list always resets version\n        REQUIRE( authService->getLocalListSize() == 0 );\n\n        //consecutive updates in Differential mode\n        localListVersion = 1;\n        localAuthList.clear();\n        localAuthList[0][\"idTag\"] = \"mIdTag\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, true);\n        REQUIRE( authService->getLocalListVersion() == localListVersion );\n        REQUIRE( authService->getLocalListSize() == 1 );\n\n        //append further entry\n        localListVersion++;\n        localAuthList.clear();\n        localAuthList[0][\"idTag\"] = \"mIdTag2\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, true);\n        REQUIRE( authService->getLocalListVersion() == localListVersion );\n        REQUIRE( authService->getLocalListSize() == 2 );\n\n        //overwrite previous entry\n        REQUIRE( authService->getLocalAuthorization(\"mIdTag\")->getAuthorizationStatus() == AuthorizationStatus::Accepted );\n        localListVersion++;\n        localAuthList.clear();\n        localAuthList[0][\"idTag\"] = \"mIdTag\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Blocked\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, true);\n        REQUIRE( authService->getLocalListVersion() == localListVersion );\n        REQUIRE( authService->getLocalListSize() == 2 );\n        REQUIRE( authService->getLocalAuthorization(\"mIdTag\")->getAuthorizationStatus() == AuthorizationStatus::Blocked );\n\n        //empty update keeps previous entries\n        localListVersion++;\n        localAuthList.clear();\n        localAuthList.to<JsonArray>();\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, true);\n        REQUIRE( authService->getLocalListVersion() == localListVersion );\n        REQUIRE( authService->getLocalListSize() == 2 );\n\n        //delete entries\n        localListVersion++;\n        localAuthList.clear();\n        localAuthList[0][\"idTag\"] = \"mIdTag\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, true);\n        REQUIRE( authService->getLocalListVersion() == localListVersion );\n        REQUIRE( authService->getLocalListSize() == 1 );\n\n        localListVersion++;\n        localAuthList.clear();\n        localAuthList[0][\"idTag\"] = \"mIdTag2\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, true);\n        REQUIRE( authService->getLocalListVersion() == 0 );\n        REQUIRE( authService->getLocalListSize() == 0 );\n    }\n\n    SECTION(\"LocalList persistency\") {\n\n        int listVersion = 42;\n\n        StaticJsonDocument<512> localAuthList;\n        localAuthList[0][\"idTag\"] = \"mIdTagMinimal\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        localAuthList[1][\"idTag\"] = \"mIdTagFull\";\n        localAuthList[1][\"idTagInfo\"][\"expiryDate\"] = BASE_TIME;\n        localAuthList[1][\"idTagInfo\"][\"parentIdTag\"] = \"mParentIdTag\";\n        localAuthList[1][\"idTagInfo\"][\"status\"] = \"Blocked\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), listVersion, false);\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        authService = getOcppContext()->getModel().getAuthorizationService();\n\n        REQUIRE( authService->getLocalListVersion() == listVersion );\n        REQUIRE( authService->getLocalListSize() == 2 );\n        auto auth0 = authService->getLocalAuthorization(\"mIdTagMinimal\");\n        REQUIRE( auth0 != nullptr );\n        REQUIRE( !strcmp(auth0->getIdTag(), \"mIdTagMinimal\") );\n        REQUIRE( auth0->getExpiryDate() == nullptr );\n        REQUIRE( auth0->getParentIdTag() == nullptr );\n        REQUIRE( auth0->getAuthorizationStatus() == AuthorizationStatus::Accepted );\n        \n        auto auth1 = authService->getLocalAuthorization(\"mIdTagFull\");\n        REQUIRE( auth1 != nullptr );\n        REQUIRE( !strcmp(auth1->getIdTag(), \"mIdTagFull\") );\n        REQUIRE( auth1->getExpiryDate() != nullptr );\n        Timestamp baseTimeParsed;\n        baseTimeParsed.setTime(BASE_TIME);\n        REQUIRE( *auth1->getExpiryDate() == baseTimeParsed );\n        REQUIRE( !strcmp(auth1->getParentIdTag(), \"mParentIdTag\") );\n        REQUIRE( auth1->getAuthorizationStatus() == AuthorizationStatus::Blocked );\n    }\n\n    SECTION(\"SendLocalList\") {\n\n        int listVersion = 42;\n        size_t listSize = 2;\n        auto populatedEntryIdTag = makeString(\"UnitTests\"); //local auth list entry to be fully populated\n        \n        //Full update - happy path\n        bool checkAccepted = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"SendLocalList\",\n                [&listVersion, &listSize, &populatedEntryIdTag] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            4096);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"listVersion\"] = listVersion;\n                    generateAuthList(payload[\"localAuthorizationList\"].to<JsonArray>(), listSize, false);\n\n                    //fully populate first entry\n                    populatedEntryIdTag = payload[\"localAuthorizationList\"][0][\"idTag\"] | \"_Undefined\";\n                    payload[\"localAuthorizationList\"][0][\"idTagInfo\"][\"expiryDate\"] = BASE_TIME;\n                    payload[\"localAuthorizationList\"][0][\"idTagInfo\"][\"parentIdTag\"] = \"mParentIdTag\";\n                    payload[\"localAuthorizationList\"][0][\"idTagInfo\"][\"status\"] = \"Blocked\";\n                    payload[\"updateType\"] = \"Full\";\n                    return doc;\n                },\n                [&checkAccepted] (JsonObject payload) {\n                    //process conf\n                    checkAccepted = !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\");\n                })));\n        \n        loop();\n        REQUIRE( authService->getLocalListVersion() == listVersion );\n        REQUIRE( authService->getLocalListSize() == listSize );\n        REQUIRE( !populatedEntryIdTag.empty() );\n        auto localAuth = authService->getLocalAuthorization(populatedEntryIdTag.c_str());\n        REQUIRE( localAuth != nullptr );\n        Timestamp baseTimeParsed;\n        baseTimeParsed.setTime(BASE_TIME);\n        REQUIRE( localAuth->getExpiryDate() );\n        REQUIRE( *localAuth->getExpiryDate() == baseTimeParsed );\n        REQUIRE( !strcmp(localAuth->getIdTag(), populatedEntryIdTag.c_str()) );\n        REQUIRE( !strcmp(localAuth->getParentIdTag(), \"mParentIdTag\") );\n        REQUIRE( localAuth->getAuthorizationStatus() == AuthorizationStatus::Blocked );\n        REQUIRE( checkAccepted );\n\n        //Differential update - happy path\n        listVersion++;\n        listSize++; //add one extra entry and update all others\n\n        checkAccepted = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"SendLocalList\",\n                [&listVersion, &listSize] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            1024);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"listVersion\"] = listVersion;\n                    generateAuthList(payload[\"localAuthorizationList\"].to<JsonArray>(), listSize, false);\n                    payload[\"updateType\"] = \"Differential\";\n                    return doc;\n                },\n                [&checkAccepted] (JsonObject payload) {\n                    //process conf\n                    checkAccepted = !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\");\n                })));\n        \n        loop();\n        REQUIRE( authService->getLocalListVersion() == listVersion );\n        REQUIRE( authService->getLocalListSize() == listSize );\n        REQUIRE( checkAccepted );\n\n        //Differential update - version mismatch\n        size_t listSizeInvalid = listSize + 1;\n\n        bool checkVersionMismatch = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"SendLocalList\",\n                [&listVersion, &listSizeInvalid] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            1024);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"listVersion\"] = listVersion;\n                    generateAuthList(payload[\"localAuthorizationList\"].to<JsonArray>(), listSizeInvalid, false);\n                    payload[\"updateType\"] = \"Differential\";\n                    return doc;\n                },\n                [&checkVersionMismatch] (JsonObject payload) {\n                    //process conf\n                    checkVersionMismatch = !strcmp(payload[\"status\"] | \"_Undefined\", \"VersionMismatch\");\n                })));\n        \n        loop();\n        REQUIRE( authService->getLocalListVersion() == listVersion );\n        REQUIRE( authService->getLocalListSize() == listSize );\n        REQUIRE( checkVersionMismatch );\n\n        //Boundary check - maximum entries per SendLocalList\n        listVersion = 42;\n        listSize = (size_t) declareConfiguration<int>(\"SendLocalListMaxLength\", -1)->getInt();\n\n        checkAccepted = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"SendLocalList\",\n                [&listVersion, &listSize] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            4096);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"listVersion\"] = listVersion;\n                    generateAuthList(payload[\"localAuthorizationList\"].to<JsonArray>(), listSize, false);\n                    payload[\"updateType\"] = \"Full\";\n                    return doc;\n                },\n                [&checkAccepted] (JsonObject payload) {\n                    //process conf\n                    checkAccepted = !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\");\n                })));\n        \n        loop();\n        REQUIRE( authService->getLocalListVersion() == listVersion );\n        REQUIRE( authService->getLocalListSize() == listSize );\n        REQUIRE( checkAccepted );\n\n        //Boundary check - maximum entries per SendLocalList in Differential mode\n        listVersion++;\n\n        checkAccepted = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"SendLocalList\",\n                [&listVersion, &listSize] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            4096);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"listVersion\"] = listVersion;\n                    generateAuthList(payload[\"localAuthorizationList\"].to<JsonArray>(), listSize, false);\n                    payload[\"updateType\"] = \"Differential\";\n                    return doc;\n                },\n                [&checkAccepted] (JsonObject payload) {\n                    //process conf\n                    checkAccepted = !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\");\n                })));\n        \n        loop();\n        REQUIRE( authService->getLocalListVersion() == listVersion );\n        REQUIRE( authService->getLocalListSize() == listSize );\n        REQUIRE( checkAccepted );\n\n        //Boundary check - exceed maximum entries per SendLocalList\n        int listVersionInvalid = listVersion + 1;\n        listSizeInvalid = listSize + 1;\n\n        bool errOccurence = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"SendLocalList\",\n                [&listVersionInvalid, &listSizeInvalid] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            4096);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"listVersion\"] = listVersionInvalid;\n                    generateAuthList(payload[\"localAuthorizationList\"].to<JsonArray>(), listSizeInvalid, false);\n                    payload[\"updateType\"] = \"Full\";\n                    return doc;\n                },\n                [] (JsonObject) { }, //ignore conf\n                [&errOccurence] (const char *errorCode, const char*, JsonObject) {\n                    errOccurence = !strcmp(errorCode, \"OccurenceConstraintViolation\");\n                    return true;\n                })));\n        \n        loop();\n        REQUIRE( authService->getLocalListVersion() == listVersion );\n        REQUIRE( authService->getLocalListSize() == listSize );\n        REQUIRE( errOccurence );\n\n        //Boundary check - exceed maximum local list size by multiple Differerntial updates\n\n        //clear local list\n        StaticJsonDocument<64> emptyDoc;\n        emptyDoc.to<JsonArray>();\n        authService->updateLocalList(emptyDoc.as<JsonArray>(), 1, false);\n\n        size_t localAuthListMaxLength = (size_t) declareConfiguration<int>(\"LocalAuthListMaxLength\", -1)->getInt();\n\n        //send Differential lists with 2 entries: update an existing entry and add a new entry\n        for (size_t i = 1; i < localAuthListMaxLength; i++) {\n            //Full update - happy path\n            bool checkAccepted = false;\n            getOcppContext()->initiateRequest(makeRequest(\n                new Ocpp16::CustomOperation(\"SendLocalList\",\n                    [&i] () {\n                        //create req\n                        auto doc = makeJsonDoc(\"UnitTests\", \n                                1024);\n                        auto payload = doc->to<JsonObject>();\n                        payload[\"listVersion\"] = (int) i;\n\n                        char buf [IDTAG_LEN_MAX + 1];\n                        sprintf(buf, \"mIdTag%zu\", i-1);\n                        payload[\"localAuthorizationList\"][0][\"idTag\"] = buf;\n                        payload[\"localAuthorizationList\"][0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n\n                        sprintf(buf, \"mIdTag%zu\", i);\n                        payload[\"localAuthorizationList\"][1][\"idTag\"] = buf;\n                        payload[\"localAuthorizationList\"][1][\"idTagInfo\"][\"status\"] = \"Accepted\";\n\n                        payload[\"updateType\"] = \"Differential\";\n                        return doc;\n                    },\n                    [&checkAccepted] (JsonObject payload) {\n                        //process conf\n                        checkAccepted = !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\");\n                    })));\n            \n            loop();\n            REQUIRE( authService->getLocalListVersion() == (int)i );\n            REQUIRE( authService->getLocalListSize() == i + 1 );\n            REQUIRE( checkAccepted );\n        }\n\n        //now exceed local list by sending overflow entry\n        listVersion = authService->getLocalListVersion();\n        listVersionInvalid = listVersion + 1;\n        listSize = authService->getLocalListSize();\n\n        bool checkFailed = false;\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"SendLocalList\",\n                [&listVersionInvalid] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            1024);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"listVersion\"] = listVersionInvalid;\n\n                    //update already existing entry\n                    char buf [IDTAG_LEN_MAX + 1];\n                    sprintf(buf, \"mIdTag%zu\", 0UL);\n                    payload[\"localAuthorizationList\"][0][\"idTag\"] = buf;\n                    payload[\"localAuthorizationList\"][0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n\n                    //additional overflowing entry\n                    payload[\"localAuthorizationList\"][1][\"idTag\"] = \"overflow idTag\";\n                    payload[\"localAuthorizationList\"][1][\"idTagInfo\"][\"status\"] = \"Accepted\";\n\n                    payload[\"updateType\"] = \"Differential\";\n                    return doc;\n                },\n                [&checkFailed] (JsonObject payload) {\n                    //process conf\n                    checkFailed = !strcmp(payload[\"status\"] | \"_Undefined\", \"Failed\");\n                })));\n        loop();\n        REQUIRE( authService->getLocalListVersion() == listVersion );\n        REQUIRE( authService->getLocalListSize() == listSize );\n        REQUIRE( checkFailed );\n    }\n\n    SECTION(\"GetLocalListVersion\") {\n\n        int localListVersion = 42;\n        StaticJsonDocument<256> localAuthList;\n        localAuthList[0][\"idTag\"] = \"mIdTag\";\n        localAuthList[0][\"idTagInfo\"][\"status\"] = \"Accepted\";\n        authService->updateLocalList(localAuthList.as<JsonArray>(), localListVersion, false);\n\n        int checkListVerion = -1;\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"GetLocalListVersion\",\n                [] () {\n                    //create req\n                    return createEmptyDocument();\n                },\n                [&checkListVerion] (JsonObject payload) {\n                    //process conf\n                    checkListVerion = payload[\"listVersion\"] | -1;\n                })));\n\n        loop();\n        REQUIRE( checkListVerion == localListVersion );\n    }\n\n    mocpp_deinitialize();\n}\n\n#endif //MO_ENABLE_LOCAL_AUTH\n"
  },
  {
    "path": "tests/Metering.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Metering/MeteringConnector.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\n#define TRIGGER_METERVALUES \"[2,\\\"msgId01\\\",\\\"TriggerMessage\\\",{\\\"requestedMessage\\\":\\\"MeterValues\\\"}]\"\n\nusing namespace MicroOcpp;\n\nTEST_CASE(\"Metering\") {\n    printf(\"\\nRun %s\\n\",  \"Metering\");\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n    auto context = getOcppContext();\n    auto& model = context->getModel();\n\n    mocpp_set_timer(custom_timer_cb);\n\n    model.getClock().setTime(BASE_TIME);\n\n    endTransaction();\n\n    SECTION(\"Configure Metering Service\") {\n\n        addMeterValueInput([] () {\n            return 0;\n        }, \"Energy.Active.Import.Register\");\n\n        addMeterValueInput([] () {\n            return 0;\n        }, \"Voltage\");\n\n        auto MeterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\");\n        MeterValuesSampledDataString->setString(\"\");\n\n        bool checkProcessed = false;\n\n        //set up measurands and check validation\n        sendRequest(\"ChangeConfiguration\",\n            [] () {\n                //create req\n                auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                auto payload = doc->to<JsonObject>();\n                payload[\"key\"] = \"MeterValuesSampledData\";\n                payload[\"value\"] = \"Energy.Active.Import.Register,INVALID,Voltage\"; //invalid request\n                return doc;\n            }, [&checkProcessed] (JsonObject payload) {\n                checkProcessed = true;\n                REQUIRE(!strcmp(payload[\"status\"], \"Rejected\"));\n            });\n\n        loop();\n\n        REQUIRE(checkProcessed);\n        REQUIRE(!strcmp(MeterValuesSampledDataString->getString(), \"\"));\n\n        checkProcessed = false;\n\n        sendRequest(\"ChangeConfiguration\",\n            [] () {\n                //create req\n                auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                auto payload = doc->to<JsonObject>();\n                payload[\"key\"] = \"MeterValuesSampledData\";\n                payload[\"value\"] = \"Voltage,Energy.Active.Import.Register\"; //valid request\n                return doc;\n            }, [&checkProcessed, &model] (JsonObject payload) {\n                checkProcessed = true;\n                REQUIRE(!strcmp(payload[\"status\"], \"Accepted\"));\n            });\n\n        loop();\n\n        REQUIRE(checkProcessed);\n        REQUIRE(!strcmp(MeterValuesSampledDataString->getString(), \"Voltage,Energy.Active.Import.Register\"));\n    }\n\n    SECTION(\"Capture Periodic data\") {\n\n        Timestamp base;\n        base.setTime(BASE_TIME);\n\n        addMeterValueInput([base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        auto MeterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Energy.Active.Import.Register\");\n\n        auto MeterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\",0, CONFIGURATION_FN);\n        MeterValueSampleIntervalInt->setInt(10);\n\n        bool checkProcessed = false;\n\n        setOnReceiveRequest(\"MeterValues\", [base, &checkProcessed] (JsonObject payload) {\n            checkProcessed = true;\n            Timestamp t0;\n            t0.setTime(payload[\"meterValue\"][0][\"timestamp\"] | \"\");\n\n            REQUIRE((t0 - base >= 10 && t0 - base <= 11));\n\n            REQUIRE(!strcmp(payload[\"meterValue\"][0][\"sampledValue\"][0][\"context\"] | \"\", \"Sample.Periodic\"));\n        });\n\n        loop();\n\n        model.getClock().setTime(BASE_TIME);\n\n        auto trackMtime = mtime;\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n\n        mtime = trackMtime + 10 * 1000;\n\n        loop();\n\n        endTransaction();\n\n        loop();\n\n        REQUIRE(checkProcessed);\n    }\n\n    SECTION(\"Capture Clock-aligned data\") {\n\n        Timestamp base;\n        base.setTime(BASE_TIME);\n\n        addMeterValueInput([base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        //disablee sampled data\n        auto MeterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\",0, CONFIGURATION_FN);\n        MeterValueSampleIntervalInt->setInt(0);\n\n        auto ClockAlignedDataIntervalInt  = declareConfiguration<int>(\"ClockAlignedDataInterval\", 0, CONFIGURATION_FN);\n        ClockAlignedDataIntervalInt->setInt(900);\n\n        auto MeterValuesAlignedDataString = declareConfiguration<const char*>(\"MeterValuesAlignedData\", \"\", CONFIGURATION_FN);\n        MeterValuesAlignedDataString->setString(\"Energy.Active.Import.Register\");\n\n        bool checkProcessed = false;\n\n        setOnReceiveRequest(\"MeterValues\", [base, &checkProcessed] (JsonObject payload) {\n            checkProcessed = true;\n            Timestamp t0;\n            t0.setTime(payload[\"meterValue\"][0][\"timestamp\"] | \"\");\n\n            REQUIRE((t0 - base >= 900 && t0 - base <= 901));\n\n            REQUIRE(!strcmp(payload[\"meterValue\"][0][\"sampledValue\"][0][\"context\"] | \"\", \"Sample.Clock\"));\n        });\n\n        model.getClock().setTime(\"2023-01-01T00:00:10Z\");\n        loop();\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n\n        model.getClock().setTime(\"2023-01-01T00:10:00Z\");\n        loop();\n\n        model.getClock().setTime(\"2023-01-01T00:15:00Z\");\n        loop();\n\n        model.getClock().setTime(\"2023-01-01T00:29:50Z\");\n\n        endTransaction();\n\n        loop();\n\n        REQUIRE(checkProcessed);\n    }\n\n    SECTION(\"Capture transaction-aligned data\") {\n\n        Timestamp base;\n        base.setTime(BASE_TIME);\n\n        addMeterValueInput([base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        auto MeterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\",0, CONFIGURATION_FN);\n        MeterValueSampleIntervalInt->setInt(10);\n\n        auto StopTxnSampledDataString = declareConfiguration<const char*>(\"StopTxnSampledData\", \"\", CONFIGURATION_FN);\n        StopTxnSampledDataString->setString(\"Energy.Active.Import.Register\");\n\n        configuration_save();\n\n        loop();\n\n        model.getClock().setTime(BASE_TIME);\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n\n        mocpp_deinitialize(); //check if StopData is stored over reboots\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        addMeterValueInput([base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        bool checkProcessed = false;\n\n        setOnReceiveRequest(\"StopTransaction\", [base, &checkProcessed] (JsonObject payload) {\n            checkProcessed = true;\n\n            REQUIRE(payload[\"transactionData\"].size() >= 2);\n\n            Timestamp t0, t1;\n            t0.setTime(payload[\"transactionData\"][0][\"timestamp\"] | \"\");\n            t1.setTime(payload[\"transactionData\"][1][\"timestamp\"] | \"\");\n\n            REQUIRE((t0 - base >= 0 && t0 - base <= 1));\n            REQUIRE((t1 - base >= 3600 && t1 - base <= 3601));\n\n            REQUIRE(!strcmp(payload[\"transactionData\"][0][\"sampledValue\"][0][\"context\"] | \"\", \"Transaction.Begin\"));\n            REQUIRE(!strcmp(payload[\"transactionData\"][1][\"sampledValue\"][0][\"context\"] | \"\", \"Transaction.End\"));\n        });\n\n        loop();\n\n        getOcppContext()->getModel().getClock().setTime(\"2023-01-01T01:00:00Z\");\n\n        endTransaction();\n\n        loop();\n\n        REQUIRE(checkProcessed);\n    }\n\n    SECTION(\"Capture measurements at connectorId 0\") {\n\n        Timestamp base;\n        base.setTime(BASE_TIME);\n\n        const unsigned int connectorId = 0;\n\n        addMeterValueInput([base] () {\n                //simulate 3600W consumption\n                return 3600;\n            }, \n            \"Power.Active.Import\",\n            nullptr,\n            nullptr,\n            nullptr,\n            connectorId);\n\n        auto MeterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Power.Active.Import\");\n\n        auto MeterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\",0, CONFIGURATION_FN);\n        MeterValueSampleIntervalInt->setInt(10);\n\n        bool checkProcessed = false;\n\n        setOnReceiveRequest(\"MeterValues\", [base, &checkProcessed] (JsonObject payload) {\n            checkProcessed = true;\n            REQUIRE( !strncmp(payload[\"meterValue\"][0][\"sampledValue\"][0][\"value\"] | \"\", \"3600\", strlen(\"3600\")) );\n        });\n\n        loop();\n\n        mtime += 10 * 1000;\n\n        loop();\n\n        REQUIRE(checkProcessed);\n    }\n\n    SECTION(\"Change measurands live\") {\n\n        Timestamp base;\n        base.setTime(BASE_TIME);\n\n        addMeterValueInput([base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        addMeterValueInput([] () {\n            return 3600;\n        }, \"Power.Active.Import\");\n\n        auto MeterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Energy.Active.Import.Register\");\n\n        auto MeterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\",0, CONFIGURATION_FN);\n        MeterValueSampleIntervalInt->setInt(10);\n\n        bool checkProcessed = false;\n\n        setOnReceiveRequest(\"MeterValues\", [base, &checkProcessed] (JsonObject payload) {\n            checkProcessed = true;\n            Timestamp t0;\n            t0.setTime(payload[\"meterValue\"][0][\"timestamp\"] | \"\");\n\n            REQUIRE((t0 - base >= 10 && t0 - base <= 11));\n            \n            REQUIRE(!strcmp(payload[\"meterValue\"][0][\"sampledValue\"][0][\"measurand\"] | \"\", \"Power.Active.Import\"));\n        });\n\n        loop();\n\n        model.getClock().setTime(BASE_TIME);\n\n        auto trackMtime = mtime;\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        MeterValuesSampledDataString->setString(\"Power.Active.Import\");\n\n        loop();\n\n        mtime = trackMtime + 10 * 1000;\n\n        loop();\n\n        endTransaction();\n\n        loop();\n\n        REQUIRE(checkProcessed);\n    }\n\n    SECTION(\"Preserve order of tx-related msgs\") {\n\n        loopback.setConnected(false);\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true)->setBool(true);\n\n        Timestamp base;\n        base.setTime(BASE_TIME);\n\n        addMeterValueInput([base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        auto MeterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Energy.Active.Import.Register\");\n\n        auto MeterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\",0, CONFIGURATION_FN);\n        MeterValueSampleIntervalInt->setInt(10);\n\n        configuration_save();\n\n        unsigned int countProcessed = 0;\n\n        setOnReceiveRequest(\"StartTransaction\", [&countProcessed] (JsonObject) {\n            REQUIRE(countProcessed == 0);\n            countProcessed++;\n        });\n\n        int assignedTxId = -1;\n\n        setOnSendConf(\"StartTransaction\", [&assignedTxId] (JsonObject conf) {\n            assignedTxId = conf[\"transactionId\"];\n        });\n\n        setOnReceiveRequest(\"MeterValues\", [&countProcessed, &assignedTxId] (JsonObject req) {\n            REQUIRE(countProcessed == 1);\n            countProcessed++;\n\n            int transactionId = req[\"transactionId\"] | -1000;\n\n            REQUIRE(assignedTxId == transactionId);\n        });\n\n        setOnReceiveRequest(\"StopTransaction\", [&countProcessed] (JsonObject) {\n            REQUIRE(countProcessed == 2);\n            countProcessed++;\n        });\n\n        loop();\n\n        auto trackMtime = mtime;\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n\n        mtime = trackMtime + 10 * 1000;\n\n        loop();\n\n        endTransaction();\n\n        loop();\n\n        loopback.setConnected(true);\n\n        loop();\n\n        REQUIRE(countProcessed == 3);\n\n        /*\n         * Combine test case with power loss. Start tx before power loss, then enqueue 1 MV, then StopTx\n         */\n\n        countProcessed = 0;\n\n        beginTransaction(\"mIdTag\");\n\n        loop();\n\n        mocpp_deinitialize();\n\n        loopback.setConnected(false);\n\n        mocpp_initialize(loopback, ChargerCredentials());\n        getOcppContext()->getModel().getClock().setTime(BASE_TIME);\n\n        base.setTime(BASE_TIME);\n\n        addMeterValueInput([base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        setOnReceiveRequest(\"MeterValues\", [&countProcessed, &assignedTxId] (JsonObject req) {\n            REQUIRE(countProcessed == 1);\n            countProcessed++;\n\n            int transactionId = req[\"transactionId\"] | -1000;\n\n            REQUIRE(assignedTxId == transactionId);\n        });\n\n        setOnReceiveRequest(\"StopTransaction\", [&countProcessed] (JsonObject) {\n            REQUIRE(countProcessed == 2);\n            countProcessed++;\n        });\n\n        trackMtime = mtime;\n\n        loop();\n\n        mtime = trackMtime + 10 * 1000;\n\n        loop();\n\n        endTransaction();\n\n        loop();\n\n        loopback.setConnected(true);\n\n        loop();\n\n        REQUIRE(countProcessed == 3);\n    }\n\n    SECTION(\"Queue multiple MeterValues\") {\n\n        Timestamp base;\n        base.setTime(BASE_TIME);\n        model.getClock().setTime(BASE_TIME);\n\n        addMeterValueInput([base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        auto MeterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Energy.Active.Import.Register\");\n\n        auto MeterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\",0, CONFIGURATION_FN);\n        MeterValueSampleIntervalInt->setInt(10);\n\n        unsigned int nrInitiated = 0;\n        unsigned int countProcessed = 0;\n\n        setOnReceiveRequest(\"MeterValues\", [&base, &nrInitiated, &countProcessed] (JsonObject payload) {\n            countProcessed++;\n\n            Timestamp t0;\n            t0.setTime(payload[\"meterValue\"][0][\"timestamp\"] | \"\");\n\n            REQUIRE((t0 - base >= 10 * ((int)nrInitiated - (MO_METERVALUES_CACHE_MAXSIZE - (int)countProcessed)) && t0 - base <= 1 + 10 * ((int)nrInitiated - (MO_METERVALUES_CACHE_MAXSIZE - (int)countProcessed))));\n        });\n\n\n        loop();\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        base = model.getClock().now();\n        auto trackMtime = mtime;\n\n        loop();\n\n        loopback.setConnected(false);\n\n        //initiate 10 more MeterValues than can be cached\n        for (unsigned long i = 1; i <= 10 + MO_METERVALUES_CACHE_MAXSIZE; i++) {\n            mtime = trackMtime + i * 10 * 1000;\n            loop();\n\n            nrInitiated++;\n        }\n\n        loopback.setConnected(true);\n\n        loop();\n\n        REQUIRE(countProcessed == MO_METERVALUES_CACHE_MAXSIZE);\n\n        endTransaction();\n\n        loop();\n\n    }\n\n    SECTION(\"Drop MeterValues for silent tx\") {\n\n        loopback.setConnected(false);\n\n        declareConfiguration<bool>(MO_CONFIG_EXT_PREFIX \"PreBootTransactions\", true)->setBool(true);\n\n        Timestamp base;\n        base.setTime(BASE_TIME);\n\n        addMeterValueInput([base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        auto MeterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Energy.Active.Import.Register\");\n\n        auto MeterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\",0, CONFIGURATION_FN);\n        MeterValueSampleIntervalInt->setInt(10);\n\n        configuration_save();\n\n        unsigned int countProcessed = 0;\n\n        setOnReceiveRequest(\"StartTransaction\", [&countProcessed] (JsonObject) {\n            countProcessed++;\n        });\n\n        int assignedTxId = -1;\n\n        setOnSendConf(\"StartTransaction\", [&assignedTxId] (JsonObject conf) {\n            assignedTxId = conf[\"transactionId\"];\n        });\n\n        setOnReceiveRequest(\"MeterValues\", [&countProcessed, &assignedTxId] (JsonObject req) {\n            countProcessed++;\n        });\n\n        setOnReceiveRequest(\"StopTransaction\", [&countProcessed] (JsonObject) {\n            REQUIRE(countProcessed == 2);\n        });\n\n        loop();\n\n        auto trackMtime = mtime;\n\n        beginTransaction_authorized(\"mIdTag\");\n        auto tx = getTransaction();\n\n        loop();\n\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Charging );\n\n        mtime = trackMtime + 10 * 1000;\n\n        loop();\n\n        endTransaction();\n\n        loop();\n\n        tx->setSilent();\n        tx->commit();\n\n        loopback.setConnected(true);\n\n        loop();\n\n        REQUIRE(countProcessed == 0);\n    }\n\n    SECTION(\"TxMsg retry behavior\") {\n        \n        Timestamp base;\n\n        addMeterValueInput([&base] () {\n            //simulate 3600W consumption\n            return getOcppContext()->getModel().getClock().now() - base;\n        }, \"Energy.Active.Import.Register\");\n\n        auto MeterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Energy.Active.Import.Register\");\n\n        auto MeterValueSampleIntervalInt = declareConfiguration<int>(\"MeterValueSampleInterval\",0, CONFIGURATION_FN);\n        MeterValueSampleIntervalInt->setInt(10);\n\n        configuration_save();\n\n        const size_t NUM_ATTEMPTS = 3;\n        const int RETRY_INTERVAL_SECS = 3600;\n\n        declareConfiguration<int>(\"TransactionMessageAttempts\", 0)->setInt(NUM_ATTEMPTS);\n        declareConfiguration<int>(\"TransactionMessageRetryInterval\", 0)->setInt(RETRY_INTERVAL_SECS);\n\n        unsigned int attemptNr = 0;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"MeterValues\", [&attemptNr] () {\n            return new Ocpp16::CustomOperation(\"MeterValues\",\n                [&attemptNr] (JsonObject payload) {\n                    //receive req\n                    attemptNr++;\n                },\n                [] () {\n                    //create conf\n                    return createEmptyDocument();\n                },\n                [] () {\n                    //ErrorCode for CALLERROR\n                    return \"InternalError\";\n                });});\n\n        loop();\n\n        auto trackMtime = mtime;\n        base = model.getClock().now();\n\n        beginTransaction(\"mIdTag\");\n\n        loop();\n\n        mtime = trackMtime + 10 * 1000;\n\n        loop();\n\n        REQUIRE(attemptNr == 1);\n\n        endTransaction();\n\n        mtime = trackMtime + 20 * 1000;\n        loop();\n        REQUIRE(attemptNr == 1);\n\n        mtime = trackMtime + 10 * 1000 + RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE(attemptNr == 2);\n\n        mtime = trackMtime + 10 * 1000 + 2 * RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE(attemptNr == 2);\n\n        mtime = trackMtime + 10 * 1000 + 3 * RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE(attemptNr == 3);\n\n        mtime = trackMtime + 10 * 1000 + 7 * RETRY_INTERVAL_SECS * 1000;\n        loop();\n        REQUIRE(attemptNr == 3);\n    }\n\n    SECTION(\"TriggerMessage\") {\n        \n        addMeterValueInput([] () {\n            return 12345;\n        }, \"Energy.Active.Import.Register\");\n\n        auto MeterValuesSampledDataString = declareConfiguration<const char*>(\"MeterValuesSampledData\",\"\", CONFIGURATION_FN);\n        MeterValuesSampledDataString->setString(\"Energy.Active.Import.Register\");\n\n        Timestamp base;\n\n        bool checkProcessed = false;\n\n        setOnReceiveRequest(\"MeterValues\", [&base, &checkProcessed] (JsonObject payload) {\n            int connectorId = payload[\"connectorId\"] | -1;\n            if (connectorId != 1) {\n                return;\n            }\n\n            checkProcessed = true;\n\n            Timestamp t0;\n            t0.setTime(payload[\"meterValue\"][0][\"timestamp\"] | \"\");\n\n            REQUIRE( std::abs(t0 - base) <= 1 );\n            REQUIRE( !strncmp(payload[\"meterValue\"][0][\"sampledValue\"][0][\"value\"] | \"\", \"12345\", strlen(\"12345\")) );\n        });\n\n        loop();\n\n        base = model.getClock().now();\n\n        loopback.sendTXT(TRIGGER_METERVALUES, sizeof(TRIGGER_METERVALUES) - 1);\n        loop();\n\n        REQUIRE(checkProcessed);\n\n    }\n\n    mocpp_deinitialize();\n}\n"
  },
  {
    "path": "tests/RemoteStartTransaction.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/OperationRegistry.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/ConnectorBase/Connector.h>\n#include <MicroOcpp/Operations/RemoteStartTransaction.h>\n#include <MicroOcpp/Debug.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\nusing namespace MicroOcpp;\n\nTEST_CASE(\"RemoteStartTransaction\") {\n    printf(\"\\nRun %s\\n\", \"RemoteStartTransaction\");\n\n    LoopbackConnection loopback;\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n    mocpp_set_timer(custom_timer_cb);\n    loop();\n\n    auto context = getOcppContext();\n    auto connector = context->getModel().getConnector(1);\n\n    SECTION(\"Basic remote start accepted\") {\n        // Ensure connector idle\n        REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n\n        context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n            \"RemoteStartTransaction\",\n            [] () {\n                auto doc = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(2));\n                auto payload = doc->to<JsonObject>();\n                payload[\"idTag\"] = \"mIdTag\";\n                return doc;},\n            [] (JsonObject) {}\n        )));\n\n        loop();\n        REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n        endTransaction();\n        loop();\n        REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n    }\n\n    SECTION(\"Same connectorId rejected when transaction active\") {\n        // Start with connector 1 busy so remote start with connectorId=1 should not auto-assign\n        beginTransaction(\"anotherId\");\n        loop();\n        REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n\n        bool checkProcessed = false;\n\n        context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n            \"RemoteStartTransaction\",\n            [] () {\n                auto doc = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(3));\n                auto payload = doc->to<JsonObject>();\n                payload[\"idTag\"] = \"mIdTag\";\n                payload[\"connectorId\"] = 1; // the same connector already in use\n                return doc;},\n            [&checkProcessed] (JsonObject response) {\n                checkProcessed = true;\n                REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Rejected\") );\n            }\n        )));\n\n        loop();\n\n        // Transaction should still be the original one only\n        REQUIRE(checkProcessed);\n        REQUIRE(connector->getTransaction());\n        REQUIRE(strcmp(connector->getTransaction()->getIdTag(), \"anotherId\") == 0);\n        REQUIRE(connector->getStatus() == ChargePointStatus_Charging);\n\n        endTransaction();\n        loop();\n        REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n    }\n\n    SECTION(\"ConnectorId 0 rejected per spec\") {\n        // RemoteStartTransaction response status is Rejected when connectorId == 0\n        REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n\n        bool checkProcessed = false;\n\n        context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n            \"RemoteStartTransaction\",\n            [] () {\n                auto doc = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(3));\n                auto payload = doc->to<JsonObject>();\n                payload[\"idTag\"] = \"mIdTag\";\n                payload[\"connectorId\"] = 0; // invalid per spec\n                return doc;},\n            [&checkProcessed] (JsonObject response) {\n                checkProcessed = true;\n                REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Rejected\") );\n            }\n        )));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n        REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n    }\n\n    SECTION(\"No free connector so rejected\") {\n        // Occupy all connectors (limit defined by MO_NUMCONNECTORS)\n        for (unsigned cId = 1; cId < context->getModel().getNumConnectors(); cId++) {\n            auto c = context->getModel().getConnector(cId);\n            if (c) {\n                c->beginTransaction_authorized(\"busyId\");\n            }\n        }\n        loop();\n\n        bool checkProcessed = false;\n        auto freeFound = false;\n        for (unsigned cId = 1; cId < context->getModel().getNumConnectors(); cId++) {\n            auto c = context->getModel().getConnector(cId);\n            if (c && !c->getTransaction()) freeFound = true;\n        }\n        REQUIRE(!freeFound); // ensure all are busy\n\n        context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n            \"RemoteStartTransaction\",\n            [] () {\n                auto doc = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(2));\n                auto payload = doc->to<JsonObject>();\n                payload[\"idTag\"] = \"mIdTag\";\n                return doc;},\n            [&checkProcessed] (JsonObject response) {\n                checkProcessed = true;\n                REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Rejected\") );\n            }\n        )));\n\n        loop();\n        REQUIRE(checkProcessed);\n\n        // No new transaction should be created; keep statuses\n        int activeTx = 0;\n        for (unsigned cId = 1; cId < context->getModel().getNumConnectors(); cId++) {\n            auto c = context->getModel().getConnector(cId);\n            if (c && c->getTransaction()) activeTx++;\n        }\n        REQUIRE(activeTx == (int)context->getModel().getNumConnectors() - 1); // all occupied\n\n        // cleanup\n        for (unsigned cId = 1; cId < context->getModel().getNumConnectors(); cId++) {\n            auto c = context->getModel().getConnector(cId);\n            if (c && c->getTransaction()) {\n                c->endTransaction();\n            }\n        }\n        loop();\n        REQUIRE(connector->getStatus() == ChargePointStatus_Available);\n    }\n\n    mocpp_deinitialize();\n}\n"
  },
  {
    "path": "tests/Reservation.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_RESERVATION\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Core/Request.h>\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Model/Reservation/ReservationService.h>\n\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\nusing namespace MicroOcpp;\n\nTEST_CASE( \"Reservation\" ) {\n    printf(\"\\nRun %s\\n\",  \"Reservation\");\n\n    //clean state\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n\n    mocpp_set_timer(custom_timer_cb);\n\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n    auto& model = getOcppContext()->getModel();\n    auto rService = model.getReservationService();\n    auto connector = model.getConnector(1);\n    model.getClock().setTime(BASE_TIME);\n\n    loop();\n\n    SECTION(\"Basic reservation\") {\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n        REQUIRE( rService );\n\n        //set reservation\n        int reservationId = 123;\n        unsigned int connectorId = 1;\n        Timestamp expiryDate = model.getClock().now() + 3600; //expires one hour in future\n        const char *idTag = \"mIdTag\";\n        const char *parentIdTag = nullptr;\n\n        rService->updateReservation(reservationId, connectorId, expiryDate, idTag, parentIdTag);\n        \n        REQUIRE( connector->getStatus() == ChargePointStatus_Reserved );\n\n        //transaction blocked by reservation\n        bool checkTxRejected = false;\n        setTxNotificationOutput([&checkTxRejected] (Transaction*, TxNotification txNotification) {\n            if (txNotification == TxNotification_ReservationConflict) {\n                checkTxRejected = true;\n            }\n        });\n\n        beginTransaction(\"wrong idTag\");\n        loop();\n        REQUIRE( connector->getStatus() == ChargePointStatus_Reserved );\n        REQUIRE( checkTxRejected );\n\n        //idTag matches reservation\n        beginTransaction(\"mIdTag\");\n        loop();\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( connector->getTransaction()->getReservationId() == reservationId );\n\n        //reservation is reset after tx\n        endTransaction();\n        loop();\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //RemoteStartTx - idTag doesn't match. The tx will start anyway assuming some start trigger in the backend prevails over reservations in the backend implementation\n        rService->updateReservation(reservationId, connectorId, expiryDate, idTag, parentIdTag);\n        REQUIRE( connector->getStatus() == ChargePointStatus_Reserved );\n\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"RemoteStartTransaction\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTag\"] = \"wrong idTag\";\n                    return doc;},\n                [] (JsonObject) { } //ignore conf\n        )));\n        loop();\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( connector->getTransaction()->getReservationId() != reservationId );\n\n        //reservation is reset after tx\n        endTransaction();\n        loop();\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //RemoteStartTx - idTag does match\n        rService->updateReservation(reservationId, connectorId, expiryDate, idTag, parentIdTag);\n        REQUIRE( connector->getStatus() == ChargePointStatus_Reserved );\n\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"RemoteStartTransaction\",\n                [idTag] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTag\"] = idTag;\n                    return doc;},\n                [] (JsonObject) { } //ignore conf\n        )));\n        loop();\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( connector->getTransaction()->getReservationId() == reservationId );\n\n        //reservation is reset after tx\n        endTransaction();\n        loop();\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n    }\n\n    SECTION(\"Tx on other connector\") {\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //set reservation\n        int reservationId = 123;\n        unsigned int connectorIdResvd = 1; //reserve connector 1\n        unsigned int connectorIdOther = 2; //start charging on other connector\n        Timestamp expiryDate = model.getClock().now() + 3600; //expires one hour in future\n        const char *idTag = \"mIdTag\";\n        const char *parentIdTag = nullptr;\n\n        rService->updateReservation(reservationId, connectorIdResvd, expiryDate, idTag, parentIdTag);\n        REQUIRE( model.getConnector(connectorIdResvd)->getStatus() == ChargePointStatus_Reserved );\n\n        beginTransaction(idTag, connectorIdOther);\n        loop();\n        REQUIRE( model.getConnector(connectorIdResvd)->getStatus() == ChargePointStatus_Available ); //reservation on first connector withdrawed\n        REQUIRE( model.getConnector(connectorIdOther)->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( getTransaction(connectorIdOther)->getReservationId() == reservationId ); //reservation transferred to other connector\n\n        endTransaction(nullptr, nullptr, connectorIdOther);\n        loop();\n        REQUIRE( model.getConnector(connectorIdOther)->getStatus() == ChargePointStatus_Available );\n    }\n\n    SECTION(\"parentIdTag\") {\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //set reservation\n        int reservationId = 123;\n        unsigned int connectorId = 1;\n        Timestamp expiryDate = model.getClock().now() + 3600; //expires one hour in future\n        const char *idTag = \"mIdTag\";\n        const char *parentIdTag = \"mParentIdTag\";\n        \n        rService->updateReservation(reservationId, connectorId, expiryDate, idTag, parentIdTag);\n        REQUIRE( connector->getStatus() == ChargePointStatus_Reserved );\n\n        bool checkProcessed = false;\n        getOcppContext()->getOperationRegistry().registerOperation(\"Authorize\",\n            [parentIdTag, &checkProcessed] () {\n                return new Ocpp16::CustomOperation(\"Authorize\",\n                    [] (JsonObject) {}, //ignore req payload\n                    [parentIdTag, &checkProcessed] () {\n                        //create conf\n                        checkProcessed = true;\n                        auto doc = makeJsonDoc(\"UnitTests\", \n                                JSON_OBJECT_SIZE(1) + //payload root\n                                JSON_OBJECT_SIZE(3)); //idTagInfo\n                        auto payload = doc->to<JsonObject>();\n                        payload[\"idTagInfo\"][\"parentIdTag\"] = parentIdTag;\n                        payload[\"idTagInfo\"][\"status\"] = \"Accepted\";\n                        return doc;});\n            });\n        beginTransaction(\"other idTag\");\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( connector->getStatus() == ChargePointStatus_Charging );\n        REQUIRE( connector->getTransaction()->getReservationId() == reservationId );\n\n        //reset tx\n        endTransaction();\n        loop();\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n    }\n\n    SECTION(\"ConnectorZero\") {\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //set reservation\n        Timestamp expiryDate = model.getClock().now() + 3600; //expires one hour in future\n        const char *idTag = \"mIdTag\";\n        const char *parentIdTag = nullptr;\n\n        //if connector 0 is reserved, accept at most one further reservation\n        REQUIRE( rService->updateReservation(1000, 0, expiryDate, idTag, parentIdTag) );\n        REQUIRE( rService->updateReservation(1001, 1, expiryDate, idTag, parentIdTag) );\n        REQUIRE( !rService->updateReservation(1002, 2, expiryDate, idTag, parentIdTag) );\n        REQUIRE( model.getConnector(2)->getStatus() == ChargePointStatus_Available );\n\n        //reset reservations\n        rService->getReservationById(1000)->clear();\n        rService->getReservationById(1001)->clear();\n        REQUIRE( model.getConnector(1)->getStatus() == ChargePointStatus_Available );\n\n        //if connector 0 is reserved, ensure that at least one physical connector remains available for the idTag of the reservation\n        REQUIRE( rService->updateReservation(1000, 0, expiryDate, idTag, parentIdTag) );\n\n        beginTransaction(\"other idTag\", 1);\n        loop();\n        REQUIRE( model.getConnector(1)->getStatus() == ChargePointStatus_Charging );\n\n        bool checkTxRejected = false;\n        setTxNotificationOutput([&checkTxRejected] (Transaction*, TxNotification txNotification) {\n            if (txNotification == TxNotification_ReservationConflict) {\n                checkTxRejected = true;\n            }\n        }, 2);\n\n        beginTransaction(\"other idTag 2\", 2);\n        loop();\n        REQUIRE( checkTxRejected );\n        REQUIRE( model.getConnector(2)->getStatus() == ChargePointStatus_Available );\n        \n\n        endTransaction(nullptr, nullptr, 1);\n        loop();\n    }\n\n    SECTION(\"Expiry date\") {\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //set reservation\n        int reservationId = 123;\n        unsigned int connectorId = 1;\n        Timestamp expiryDate = model.getClock().now() + 3600; //expires one hour in future\n        const char *idTag = \"mIdTag\";\n        const char *parentIdTag = nullptr;\n\n        rService->updateReservation(reservationId, connectorId, expiryDate, idTag, parentIdTag);\n        \n        REQUIRE( connector->getStatus() == ChargePointStatus_Reserved );\n\n        Timestamp expired = expiryDate + 1;\n        char expired_cstr [JSONDATE_LENGTH + 1];\n        expired.toJsonString(expired_cstr, JSONDATE_LENGTH + 1);\n        model.getClock().setTime(expired_cstr);\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n    }\n\n    SECTION(\"Reservation persistency\") {\n        unsigned int connectorId = 1;\n        REQUIRE( getOcppContext()->getModel().getConnector(connectorId)->getStatus() == ChargePointStatus_Available );\n\n        //set reservation\n        int reservationId = 123;\n        Timestamp expiryDate = model.getClock().now() + 3600; //expires one hour in future\n        const char *idTag = \"mIdTag\";\n        const char *parentIdTag = \"mParentIdTag\";\n\n        getOcppContext()->getModel().getReservationService()->updateReservation(reservationId, connectorId, expiryDate, idTag, parentIdTag);\n        \n        REQUIRE( getOcppContext()->getModel().getConnector(connectorId)->getStatus() == ChargePointStatus_Reserved );\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        getOcppContext()->getModel().getClock().setTime(BASE_TIME);\n        loop();\n\n        REQUIRE( getOcppContext()->getModel().getConnector(connectorId)->getStatus() == ChargePointStatus_Reserved );\n\n        auto reservation = getOcppContext()->getModel().getReservationService()->getReservationById(reservationId);\n        REQUIRE( reservation->getReservationId() == reservationId );\n        REQUIRE( reservation->getConnectorId() == (int)connectorId );\n        REQUIRE( reservation->getExpiryDate() == expiryDate );\n        REQUIRE( !strcmp(reservation->getIdTag(), idTag) );\n        REQUIRE( !strcmp(reservation->getParentIdTag(), parentIdTag) );\n\n        reservation->clear();\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n        getOcppContext()->getModel().getClock().setTime(BASE_TIME);\n        loop();\n\n        REQUIRE( getOcppContext()->getModel().getConnector(connectorId)->getStatus() == ChargePointStatus_Available );\n    }\n\n    SECTION(\"ReserveNow\") {\n\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //set reservation\n        int reservationId = 123;\n        unsigned int connectorId = 1;\n        Timestamp expiryDate = model.getClock().now() + 3600; //expires one hour in future\n        const char *idTag = \"mIdTag\";\n        const char *parentIdTag = nullptr;\n\n        //simple reservation\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ReserveNow\",\n                [reservationId, connectorId, expiryDate, idTag, parentIdTag] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            JSON_OBJECT_SIZE(5) + \n                            JSONDATE_LENGTH + 1);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"connectorId\"] = connectorId;\n                    char expiryDate_cstr [JSONDATE_LENGTH + 1];\n                    expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1);\n                    payload[\"expiryDate\"] = expiryDate_cstr;\n                    payload[\"idTag\"] = idTag;\n                    payload[\"parentIdTag\"] = parentIdTag;\n                    payload[\"reservationId\"] = reservationId;\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( connector->getStatus() == ChargePointStatus_Reserved );\n\n        model.getReservationService()->getReservationById(reservationId)->clear();\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //reserve while charger is in Faulted state\n        const char *errorCode = \"OtherError\";\n        addErrorCodeInput([&errorCode] () {return errorCode;});\n        REQUIRE( connector->getStatus() == ChargePointStatus_Faulted );\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ReserveNow\",\n                [reservationId, connectorId, expiryDate, idTag, parentIdTag] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            JSON_OBJECT_SIZE(5) + \n                            JSONDATE_LENGTH + 1);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"connectorId\"] = connectorId;\n                    char expiryDate_cstr [JSONDATE_LENGTH + 1];\n                    expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1);\n                    payload[\"expiryDate\"] = expiryDate_cstr;\n                    payload[\"idTag\"] = idTag;\n                    payload[\"parentIdTag\"] = parentIdTag;\n                    payload[\"reservationId\"] = reservationId;\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Faulted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( connector->getStatus() == ChargePointStatus_Faulted );\n\n        errorCode = nullptr; //reset error\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //reserve while connector is already occupied\n        setConnectorPluggedInput([] {return true;}); //plug EV\n        REQUIRE( connector->getStatus() == ChargePointStatus_Preparing );\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ReserveNow\",\n                [reservationId, connectorId, expiryDate, idTag, parentIdTag] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            JSON_OBJECT_SIZE(5) + \n                            JSONDATE_LENGTH + 1);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"connectorId\"] = connectorId;\n                    char expiryDate_cstr [JSONDATE_LENGTH + 1];\n                    expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1);\n                    payload[\"expiryDate\"] = expiryDate_cstr;\n                    payload[\"idTag\"] = idTag;\n                    payload[\"parentIdTag\"] = parentIdTag;\n                    payload[\"reservationId\"] = reservationId;\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Occupied\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( connector->getStatus() == ChargePointStatus_Preparing );\n\n        setConnectorPluggedInput(nullptr); //reset ConnectorPluggedInput\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //Rejected ReserveNow status not possible\n\n        //reserve while connector is inoperative\n        connector->setAvailabilityVolatile(false);\n        REQUIRE( connector->getStatus() == ChargePointStatus_Unavailable );\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ReserveNow\",\n                [reservationId, connectorId, expiryDate, idTag, parentIdTag] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", \n                            JSON_OBJECT_SIZE(5) + \n                            JSONDATE_LENGTH + 1);\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"connectorId\"] = connectorId;\n                    char expiryDate_cstr [JSONDATE_LENGTH + 1];\n                    expiryDate.toJsonString(expiryDate_cstr, JSONDATE_LENGTH + 1);\n                    payload[\"expiryDate\"] = expiryDate_cstr;\n                    payload[\"idTag\"] = idTag;\n                    payload[\"parentIdTag\"] = parentIdTag;\n                    payload[\"reservationId\"] = reservationId;\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Unavailable\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( connector->getStatus() == ChargePointStatus_Unavailable );\n\n        connector->setAvailabilityVolatile(true); //revert Unavailable status\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n    }\n\n    SECTION(\"CancelReservation\") {\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //set reservation\n        int reservationId = 123;\n        unsigned int connectorId = 1;\n        Timestamp expiryDate = model.getClock().now() + 3600; //expires one hour in future\n        const char *idTag = \"mIdTag\";\n        const char *parentIdTag = nullptr;\n\n        rService->updateReservation(reservationId, connectorId, expiryDate, idTag, parentIdTag);\n        REQUIRE( connector->getStatus() == ChargePointStatus_Reserved );\n\n        //CancelReservation successfully\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"CancelReservation\",\n                [reservationId, connectorId, expiryDate, idTag, parentIdTag] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"reservationId\"] = reservationId;\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n\n        //CancelReservation while no reservation exists\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"CancelReservation\",\n                [reservationId, connectorId, expiryDate, idTag, parentIdTag] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"reservationId\"] = reservationId;\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Rejected\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( connector->getStatus() == ChargePointStatus_Available );\n    }\n\n    mocpp_deinitialize();\n}\n\n#endif //MO_ENABLE_RESERVATION\n"
  },
  {
    "path": "tests/Reset.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n#include <MicroOcpp/Model/Reset/ResetService.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Debug.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\nusing namespace MicroOcpp;\n\n\nTEST_CASE( \"Reset\" ) {\n    printf(\"\\nRun %s\\n\",  \"Reset\");\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n    mocpp_initialize(loopback,\n            ChargerCredentials(\"test-runner1234\"),\n            makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail),\n            false,\n            ProtocolVersion(2,0,1));\n\n    auto context = getOcppContext();\n\n    mocpp_set_timer(custom_timer_cb);\n\n    getOcppContext()->getOperationRegistry().registerOperation(\"Authorize\", [] () {\n        return new Ocpp16::CustomOperation(\"Authorize\",\n            [] (JsonObject) {}, //ignore req\n            [] () {\n                //create conf\n                auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                auto payload = doc->to<JsonObject>();\n                payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                return doc;\n            });});\n\n    getOcppContext()->getOperationRegistry().registerOperation(\"TransactionEvent\", [] () {\n        return new Ocpp16::CustomOperation(\"TransactionEvent\",\n            [] (JsonObject) {}, //ignore req\n            [] () {\n                //create conf\n                auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                auto payload = doc->to<JsonObject>();\n                payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                return doc;\n            });});\n\n    // Register Reset handlers\n    bool checkNotified [MO_NUM_EVSEID] = {false};\n    bool checkExecuted [MO_NUM_EVSEID] = {false};\n\n    setOnResetNotify([&checkNotified] (bool) {\n        MO_DBG_DEBUG(\"Notify\");\n        checkNotified[0] = true;\n        return true;\n    });\n    context->getModel().getResetServiceV201()->setExecuteReset([&checkExecuted] () {\n        MO_DBG_DEBUG(\"Execute\");\n        checkExecuted[0] = true;\n        return false; // Reset fails because we're not actually exiting the process\n    });\n    \n    for (size_t i = 1; i < MO_NUM_EVSEID; i++) {\n        context->getModel().getResetServiceV201()->setNotifyReset([&checkNotified, i] (ResetType) {\n            MO_DBG_DEBUG(\"Notify %zu\", i);\n            checkNotified[i] = true;\n            return true;\n        }, i);\n        context->getModel().getResetServiceV201()->setExecuteReset([&checkExecuted, i] () {\n            MO_DBG_DEBUG(\"Execute %zu\", i);\n            checkExecuted[i] = true;\n            return true;\n        }, i);\n    }\n\n    loop();\n\n    SECTION(\"B11 - Reset - Without ongoing transaction\") {\n\n        MO_MEM_RESET();\n\n        bool checkProcessed = false;\n\n        auto resetRequest = makeRequest(new Ocpp16::CustomOperation(\n                \"Reset\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"type\"] = \"OnIdle\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE(!strcmp(payload[\"status\"], \"Accepted\"));\n                }\n        ));\n\n        context->initiateRequest(std::move(resetRequest));\n\n        loop();\n        mtime += 30000; // Reset has some delays to ensure that the WS is not cut off immediately\n        loop();\n\n        REQUIRE(checkProcessed);\n\n        for (size_t i = 0; i < MO_NUM_EVSEID; i++) {\n            REQUIRE( checkNotified[i] );\n        }\n\n        MO_MEM_PRINT_STATS();\n    }\n\n    SECTION(\"Schedule full charger Reset\") {\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n        REQUIRE( context->getModel().getTransactionService()->getEvse(2)->getTransaction() == nullptr );\n        \n        context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\");\n        setConnectorPluggedInput([] () {return true;}, 1);\n        setEvReadyInput([] () {return true;}, 1);\n        setEvseReadyInput([] () {return true;}, 1);\n\n        context->getModel().getTransactionService()->getEvse(2)->beginAuthorization(\"mIdToken2\");\n        setConnectorPluggedInput([] () {return true;}, 2);\n        setEvReadyInput([] () {return true;}, 2);\n        setEvseReadyInput([] () {return true;}, 2);\n\n        loop();\n\n        REQUIRE( ocppPermitsCharge(1) );\n        REQUIRE( ocppPermitsCharge(2) );\n\n        bool checkProcessed = false;\n\n        auto resetRequest = makeRequest(new Ocpp16::CustomOperation(\n                \"Reset\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"type\"] = \"OnIdle\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE(!strcmp(payload[\"status\"], \"Scheduled\"));\n                }\n        ));\n\n        context->initiateRequest(std::move(resetRequest));\n\n        loop();\n        mtime += 30000; // Reset has some delays to ensure that the WS is not cut off immediately\n        loop();\n\n        REQUIRE(checkProcessed);\n\n        for (size_t i = 0; i < MO_NUM_EVSEID; i++) {\n            REQUIRE( checkNotified[i] );\n        }\n\n        // Still scheduled\n        REQUIRE( ocppPermitsCharge(1) );\n        REQUIRE( ocppPermitsCharge(2) );\n\n        context->getModel().getTransactionService()->getEvse(1)->endAuthorization(\"mIdToken\");\n        setConnectorPluggedInput([] () {return false;}, 1);\n        setEvReadyInput([] () {return false;}, 1);\n        setEvseReadyInput([] () {return false;}, 1);\n        loop();\n\n        // Still scheduled\n        REQUIRE( !ocppPermitsCharge(1) );\n        REQUIRE( ocppPermitsCharge(2) );\n\n        //REQUIRE( getChargePointStatus(1) == ChargePointStatus_Unavailable ); //change: Reset doesn't lead to Unavailable state\n\n        context->getModel().getTransactionService()->getEvse(2)->endAuthorization(\"mIdToken\");\n        setConnectorPluggedInput([] () {return false;}, 2);\n        setEvReadyInput([] () {return false;}, 2);\n        setEvseReadyInput([] () {return false;}, 2);\n\n        loop();\n        mtime += 30000; // Reset has some delays to ensure that the WS is not cut off immediately\n        loop();\n\n        // Not scheduled anymore; execute Reset\n        REQUIRE( !ocppPermitsCharge(1) );\n        REQUIRE( !ocppPermitsCharge(2) );\n\n        REQUIRE( checkExecuted[0] );\n\n        // Technically, Reset failed at this point, because the program is still running. Check if connectors are Available agin\n        REQUIRE( getChargePointStatus(1) == ChargePointStatus_Available );\n        REQUIRE( getChargePointStatus(2) == ChargePointStatus_Available );\n    }\n\n    SECTION(\"Immediate full charger Reset\") {\n\n        context->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n        REQUIRE( context->getModel().getTransactionService()->getEvse(2)->getTransaction() == nullptr );\n        \n        context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\");\n\n        context->getModel().getTransactionService()->getEvse(2)->beginAuthorization(\"mIdToken2\");\n\n        loop();\n\n        MO_MEM_RESET();\n\n        REQUIRE( ocppPermitsCharge(1) );\n        REQUIRE( ocppPermitsCharge(2) );\n\n        bool checkProcessedTx = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"TransactionEvent\", [&checkProcessedTx] () {\n            return new Ocpp16::CustomOperation(\"TransactionEvent\",\n                [&checkProcessedTx] (JsonObject payload) {\n                    //process req\n                    checkProcessedTx = true;\n\n                    REQUIRE(!strcmp(payload[\"eventType\"], \"Ended\"));\n                    REQUIRE(!strcmp(payload[\"triggerReason\"], \"ResetCommand\"));\n                    REQUIRE(!strcmp(payload[\"transactionInfo\"][\"stoppedReason\"], \"ImmediateReset\"));\n                },\n                [] () {\n                    //create conf\n                    return createEmptyDocument();\n                });});\n\n        bool checkProcessed = false;\n\n        auto resetRequest = makeRequest(new Ocpp16::CustomOperation(\n                \"Reset\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"type\"] = \"Immediate\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE(!strcmp(payload[\"status\"], \"Accepted\"));\n                }\n        ));\n\n        context->initiateRequest(std::move(resetRequest));\n\n        loop();\n        mtime += 30000; // Reset has some delays to ensure that the WS is not cut off immediately\n        loop();\n\n        REQUIRE(checkProcessed);\n        REQUIRE(checkProcessedTx);\n\n        for (size_t i = 0; i < MO_NUM_EVSEID; i++) {\n            REQUIRE( checkNotified[i] );\n        }\n\n        // Stopped Tx\n        REQUIRE( !ocppPermitsCharge(1) );\n        REQUIRE( !ocppPermitsCharge(2) );\n\n        REQUIRE( checkExecuted[0] );\n\n        MO_MEM_PRINT_STATS();\n\n        loop();\n\n        // Technically, Reset failed at this point, because the program is still running. Check if connectors are Available agin\n        REQUIRE( getChargePointStatus(1) == ChargePointStatus_Available );\n        REQUIRE( getChargePointStatus(2) == ChargePointStatus_Available );\n    }\n\n    SECTION(\"Reject Reset\") {\n        \n        context->getModel().getResetServiceV201()->setNotifyReset([&checkNotified] (ResetType) {\n            MO_DBG_DEBUG(\"Reject Reset\");\n            checkNotified[2] = true;\n            return false;\n        }, 2);\n\n        bool checkProcessed = false;\n\n        auto resetRequest = makeRequest(new Ocpp16::CustomOperation(\n                \"Reset\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"type\"] = \"Immediate\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE(!strcmp(payload[\"status\"], \"Rejected\"));\n                }\n        ));\n\n        context->initiateRequest(std::move(resetRequest));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n        REQUIRE(checkNotified[2]);\n\n        REQUIRE( getChargePointStatus(0) == ChargePointStatus_Available );\n        REQUIRE( getChargePointStatus(1) == ChargePointStatus_Available );\n        REQUIRE( getChargePointStatus(2) == ChargePointStatus_Available );\n    }\n\n    SECTION(\"Reset single EVSE\") {\n\n        bool checkProcessed = false;\n\n        auto resetRequest = makeRequest(new Ocpp16::CustomOperation(\n                \"Reset\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"type\"] = \"OnIdle\";\n                    payload[\"evseId\"] = 1;\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE(!strcmp(payload[\"status\"], \"Accepted\"));\n                }\n        ));\n\n        context->initiateRequest(std::move(resetRequest));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n        REQUIRE(checkNotified[1]);\n\n        //REQUIRE( getChargePointStatus(1) == ChargePointStatus_Unavailable ); //change: Reset doesn't lead to Unavailable state\n        REQUIRE( getChargePointStatus(2) == ChargePointStatus_Available );\n\n        mtime += 30000; // Reset has some delays to ensure that the WS is not cut off immediately\n        loop();\n\n        REQUIRE(checkExecuted[1]);\n        REQUIRE( getChargePointStatus(1) == ChargePointStatus_Available );\n    }\n\n    mocpp_deinitialize();\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "tests/Security.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Operations/SecurityEventNotification.h>\n#include <MicroOcpp/Debug.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\nusing namespace MicroOcpp;\n\n\nTEST_CASE( \"Security\" ) {\n    printf(\"\\nRun %s\\n\",  \"Security\");\n\n    mocpp_set_timer(custom_timer_cb);\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    mocpp_initialize(loopback,\n            ChargerCredentials(),\n            filesystem,\n            false,\n            ProtocolVersion(2,0,1));\n\n    SECTION(\"Manual SecurityEventNotification\") {\n\n        loop();\n\n        MO_MEM_RESET();\n\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp201::SecurityEventNotification(\n                \"ReconfigurationOfSecurityParameters\",\n                getOcppContext()->getModel().getClock().now())));\n        \n        loop();\n\n        MO_MEM_PRINT_STATS();\n    }\n\n    mocpp_deinitialize();\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "tests/SmartCharging.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Model/SmartCharging/SmartChargingService.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\n#define SCPROFILE_0                            \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":1,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":0,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"TxDefaultProfile\\\",\\\"chargingProfileKind\\\":\\\"Recurring\\\",\\\"recurrencyKind\\\":\\\"Daily\\\",\\\"validFrom\\\":\\\"2022-06-12T00:00:00.000Z\\\",\\\"validTo\\\":\\\"2023-06-21T00:00:00.000Z\\\",\\\"chargingSchedule\\\":{\\\"duration\\\":1000000,\\\"startSchedule\\\":\\\"2023-06-18T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"W\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":16,\\\"numberPhases\\\":3},{\\\"startPeriod\\\":18000,\\\"limit\\\":32,\\\"numberPhases\\\":3}],\\\"minChargingRate\\\":6}}}]\"\n#define SCPROFILE_0_ALT_SAME_ID                \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":1,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":0,\\\"stackLevel\\\":1,\\\"chargingProfilePurpose\\\":\\\"TxDefaultProfile\\\",\\\"chargingProfileKind\\\":\\\"Recurring\\\",\\\"recurrencyKind\\\":\\\"Daily\\\",\\\"validFrom\\\":\\\"2022-06-12T00:00:00.000Z\\\",\\\"validTo\\\":\\\"2023-06-21T00:00:00.000Z\\\",\\\"chargingSchedule\\\":{\\\"duration\\\":1000000,\\\"startSchedule\\\":\\\"2023-06-18T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"W\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":16,\\\"numberPhases\\\":3},{\\\"startPeriod\\\":18000,\\\"limit\\\":32,\\\"numberPhases\\\":3}],\\\"minChargingRate\\\":6}}}]\"\n#define SCPROFILE_0_ALT_SAME_STACKEVEL_PURPOSE \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":1,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":1,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"TxDefaultProfile\\\",\\\"chargingProfileKind\\\":\\\"Recurring\\\",\\\"recurrencyKind\\\":\\\"Daily\\\",\\\"validFrom\\\":\\\"2022-06-12T00:00:00.000Z\\\",\\\"validTo\\\":\\\"2023-06-21T00:00:00.000Z\\\",\\\"chargingSchedule\\\":{\\\"duration\\\":1000000,\\\"startSchedule\\\":\\\"2023-06-18T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"W\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":16,\\\"numberPhases\\\":3},{\\\"startPeriod\\\":18000,\\\"limit\\\":32,\\\"numberPhases\\\":3}],\\\"minChargingRate\\\":6}}}]\"\n\n#define SCPROFILE_1_ABSOLUTE_LIMIT_16A         \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":0,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":1,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"ChargePointMaxProfile\\\",\\\"chargingProfileKind\\\":\\\"Absolute\\\",\\\"chargingSchedule\\\":{\\\"startSchedule\\\":\\\"2023-01-01T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"A\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":16,\\\"numberPhases\\\":3}]}}}]\"\n\n#define SCPROFILE_2_RELATIVE_TXDEF_24A         \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":1,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":2,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"TxDefaultProfile\\\",\\\"chargingProfileKind\\\":\\\"Relative\\\",\\\"chargingSchedule\\\":{\\\"chargingRateUnit\\\":\\\"A\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":24,\\\"numberPhases\\\":3}]}}}]\"\n\n#define SCPROFILE_3_TXPROF_TXID123_20A         \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":1,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":3,\\\"transactionId\\\":123,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"TxProfile\\\",\\\"chargingProfileKind\\\":\\\"Relative\\\",\\\"chargingSchedule\\\":{\\\"chargingRateUnit\\\":\\\"A\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":20,\\\"numberPhases\\\":3}]}}}]\"\n\n#define SCPROFILE_4_VALID_FROM_2024_16A        \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":0,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":4,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"ChargePointMaxProfile\\\",\\\"chargingProfileKind\\\":\\\"Absolute\\\",\\\"validFrom\\\":\\\"2024-01-01T00:00:00.000Z\\\",\\\"chargingSchedule\\\":{\\\"startSchedule\\\":\\\"2023-01-01T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"A\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":16,\\\"numberPhases\\\":3}]}}}]\"\n#define SCPROFILE_5_VALID_UNTIL_2022_16A       \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":0,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":5,\\\"stackLevel\\\":1,\\\"chargingProfilePurpose\\\":\\\"ChargePointMaxProfile\\\",\\\"chargingProfileKind\\\":\\\"Absolute\\\",\\\"validTo\\\":  \\\"2022-01-01T00:00:00.000Z\\\",\\\"chargingSchedule\\\":{\\\"startSchedule\\\":\\\"2023-01-01T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"A\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":16,\\\"numberPhases\\\":3}]}}}]\"\n\n#define SCPROFILE_6_MULTIPLE_PERIODS_16A_20A   \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":0,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":6,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"ChargePointMaxProfile\\\",\\\"chargingProfileKind\\\":\\\"Absolute\\\",\\\"chargingSchedule\\\":{\\\"startSchedule\\\":\\\"2023-01-01T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"A\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":16,\\\"numberPhases\\\":3},{\\\"startPeriod\\\":3600,\\\"limit\\\":20,\\\"numberPhases\\\":3}]}}}]\"\n\n#define SCPROFILE_7_RECURRING_DAY_2H_16A_20A   \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":0,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":7,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"ChargePointMaxProfile\\\",\\\"chargingProfileKind\\\":\\\"Recurring\\\",\\\"recurrencyKind\\\":\\\"Daily\\\", \\\"chargingSchedule\\\":{\\\"duration\\\":7200,\\\"startSchedule\\\":\\\"2023-01-01T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"A\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":16,\\\"numberPhases\\\":3},{\\\"startPeriod\\\":3600,\\\"limit\\\":20,\\\"numberPhases\\\":3}]}}}]\"\n#define SCPROFILE_8_RECURRING_WEEK_2H_10A_12A  \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":0,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":8,\\\"stackLevel\\\":1,\\\"chargingProfilePurpose\\\":\\\"ChargePointMaxProfile\\\",\\\"chargingProfileKind\\\":\\\"Recurring\\\",\\\"recurrencyKind\\\":\\\"Weekly\\\",\\\"chargingSchedule\\\":{\\\"duration\\\":7200,\\\"startSchedule\\\":\\\"2023-01-01T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"A\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":10,\\\"numberPhases\\\":3},{\\\"startPeriod\\\":3600,\\\"limit\\\":12,\\\"numberPhases\\\":3}]}}}]\"\n\n#define SCPROFILE_9_VIA_RMTSTARTTX_20A         \"[2,\\\"testmsg\\\",\\\"RemoteStartTransaction\\\",{\\\"connectorId\\\":1,\\\"idTag\\\":\\\"mIdTag\\\",\\\"chargingProfile\\\":{\\\"chargingProfileId\\\":9,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"TxProfile\\\",\\\"chargingProfileKind\\\":\\\"Relative\\\",\\\"chargingSchedule\\\":{\\\"chargingRateUnit\\\":\\\"A\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":20,\\\"numberPhases\\\":1}]}}}]\"\n\n#define SCPROFILE_10_ABSOLUTE_LIMIT_5KW        \"[2,\\\"testmsg\\\",\\\"SetChargingProfile\\\",{\\\"connectorId\\\":0,\\\"csChargingProfiles\\\":{\\\"chargingProfileId\\\":10,\\\"stackLevel\\\":0,\\\"chargingProfilePurpose\\\":\\\"ChargePointMaxProfile\\\",\\\"chargingProfileKind\\\":\\\"Absolute\\\",\\\"chargingSchedule\\\":{\\\"startSchedule\\\":\\\"2023-01-01T00:00:00.000Z\\\",\\\"chargingRateUnit\\\":\\\"W\\\",\\\"chargingSchedulePeriod\\\":[{\\\"startPeriod\\\":0,\\\"limit\\\":5000,\\\"numberPhases\\\":3}]}}}]\"\n\n\nusing namespace MicroOcpp;\n\n\nTEST_CASE( \"SmartCharging\" ) {\n    printf(\"\\nRun %s\\n\",  \"SmartCharging\");\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n    mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n    auto context = getOcppContext();\n    auto& model = context->getModel();\n\n    mocpp_set_timer(custom_timer_cb);\n\n    model.getClock().setTime(BASE_TIME);\n\n    endTransaction();\n\n    SECTION(\"Load Smart Charging Service\"){\n\n        REQUIRE(!model.getSmartChargingService());\n\n        setSmartChargingOutput([] (float, float, int) {});\n\n        REQUIRE(model.getSmartChargingService());\n    }\n\n    setSmartChargingOutput([] (float, float, int) {});\n    auto scService = model.getSmartChargingService();\n\n    scService->clearChargingProfile([] (int, int, ChargingProfilePurposeType, int) {\n        return true;\n    });\n\n    SECTION(\"Set Charging Profile and clear\") {\n\n        unsigned int count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return true;\n        });\n\n        REQUIRE(count == 0);\n\n        loopback.sendTXT(SCPROFILE_0, strlen(SCPROFILE_0));\n\n        //check if filter works by comparing the outcome of returning false and true and repeating the test\n        count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return false;\n        });\n\n        REQUIRE(count == 1);\n\n        count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return true;\n        });\n\n        REQUIRE(count == 1);\n\n        count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return true;\n        });\n\n        REQUIRE(count == 0);\n    }\n\n    SECTION(\"Charging Profiles persistency over reboots\") {\n\n        loopback.sendTXT(SCPROFILE_0, strlen(SCPROFILE_0));\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(\"test-runner1234\"));\n\n        setSmartChargingOutput([] (float, float, int) {});\n        scService = getOcppContext()->getModel().getSmartChargingService();\n\n        unsigned int count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return true;\n        });\n\n        REQUIRE (count == 1);\n    }\n\n    SECTION(\"Set conflicting profile\") {\n\n        loopback.sendTXT(SCPROFILE_0, strlen(SCPROFILE_0));\n\n        loopback.sendTXT(SCPROFILE_0_ALT_SAME_ID, strlen(SCPROFILE_0_ALT_SAME_ID));\n\n        unsigned int count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return true;\n        });\n\n        REQUIRE(count == 1);\n\n        loopback.sendTXT(SCPROFILE_0, strlen(SCPROFILE_0));\n\n        loopback.sendTXT(SCPROFILE_0_ALT_SAME_STACKEVEL_PURPOSE, strlen(SCPROFILE_0_ALT_SAME_STACKEVEL_PURPOSE));\n\n        count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return true;\n        });\n\n        REQUIRE(count == 1);\n    }\n\n    SECTION(\"Set charging profile via RmtStartTx\") {\n        \n        float current = -1.f;\n        setSmartChargingOutput([&current] (float, float limit_current, int) {\n            current = limit_current;\n        });\n\n        loop();\n\n        loopback.sendTXT(SCPROFILE_9_VIA_RMTSTARTTX_20A, strlen(SCPROFILE_9_VIA_RMTSTARTTX_20A));\n\n        loop();\n\n        REQUIRE((current > 19.99f && current < 20.01f));\n\n        endTransaction();\n\n        loop();\n    }\n\n    SECTION(\"Set ChargePointMaxProfile - Absolute\") {\n        \n        float current = -1.f;\n        setSmartChargingOutput([&current] (float, float limit_current, int) {\n            current = limit_current;\n        });\n\n        loopback.sendTXT(SCPROFILE_1_ABSOLUTE_LIMIT_16A, strlen(SCPROFILE_1_ABSOLUTE_LIMIT_16A));\n\n        loop();\n\n        REQUIRE((current > 15.99f && current < 16.01f));\n    }\n\n    SECTION(\"Set TxDefaultProfile - Relative\") {\n\n        float current = -1.f;\n        setSmartChargingOutput([&current] (float, float limit_current, int) {\n            current = limit_current;\n        });\n        \n        loopback.sendTXT(SCPROFILE_2_RELATIVE_TXDEF_24A, strlen(SCPROFILE_2_RELATIVE_TXDEF_24A));\n\n        loop();\n\n        REQUIRE(current < 0.f);\n\n        beginTransaction_authorized(\"mIdTag\");\n\n        loop();\n\n        REQUIRE((current > 23.99f && current < 24.01f));\n\n        endTransaction();\n\n        loop();\n\n        REQUIRE(current < 0.f);\n    }\n\n    SECTION(\"Set TxProfile - tx fit and mismatch\") {\n\n        float current = -1.f;\n        setSmartChargingOutput([&current] (float, float limit_current, int) {\n            current = limit_current;\n        });\n        \n        //send before transaction - expect rejection\n        loopback.sendTXT(SCPROFILE_3_TXPROF_TXID123_20A, strlen(SCPROFILE_3_TXPROF_TXID123_20A));\n\n        unsigned int count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return true;\n        });\n\n        REQUIRE(count == 0);\n\n        loop();\n        beginTransaction_authorized(\"mIdTag\");\n\n        //send during transaction but wrong txId - expect rejection\n        loopback.sendTXT(SCPROFILE_3_TXPROF_TXID123_20A, strlen(SCPROFILE_3_TXPROF_TXID123_20A));\n\n        loop();\n\n        count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return true;\n        });\n\n        REQUIRE(count == 0);\n\n        getTransaction()->setTransactionId(123);\n\n        //send during tx with matchin txId - accept\n        loopback.sendTXT(SCPROFILE_3_TXPROF_TXID123_20A, strlen(SCPROFILE_3_TXPROF_TXID123_20A));\n\n        loop();\n\n        REQUIRE((current > 19.99f && current < 20.01f));\n\n        endTransaction();\n\n        loop();\n\n        //check if SCService deleted TxProfiles after tx\n        count = 0;\n        scService->clearChargingProfile([&count] (int, int, ChargingProfilePurposeType, int) {\n            count++;\n            return true;\n        });\n\n        REQUIRE(count == 0);\n    }\n\n    SECTION(\"Time validity check\") {\n\n        float current = -1.f;\n        setSmartChargingOutput([&current] (float, float limit_current, int) {\n            current = limit_current;\n        });\n        \n        loopback.sendTXT(SCPROFILE_4_VALID_FROM_2024_16A, strlen(SCPROFILE_4_VALID_FROM_2024_16A));\n\n        loopback.sendTXT(SCPROFILE_5_VALID_UNTIL_2022_16A, strlen(SCPROFILE_5_VALID_UNTIL_2022_16A));\n\n        loop();\n\n        REQUIRE(current < 0.f);\n\n        //now reach validity period of future profile\n        model.getClock().setTime(\"2024-01-01T00:00:00.000Z\");\n\n        loop();\n\n        REQUIRE((current > 15.99f && current < 16.01f));\n    }\n\n    SECTION(\"Multiple periods\") {\n        \n        float current = -1.f;\n        setSmartChargingOutput([&current] (float, float limit_current, int) {\n            current = limit_current;\n        });\n\n        loopback.sendTXT(SCPROFILE_6_MULTIPLE_PERIODS_16A_20A, strlen(SCPROFILE_6_MULTIPLE_PERIODS_16A_20A));\n\n        loop();\n\n        REQUIRE((current > 15.99f && current < 16.01f));\n\n        //now reach next period\n        model.getClock().setTime(\"2023-01-01T01:00:00.000Z\");\n\n        loop();\n\n        REQUIRE((current > 19.99f && current < 20.01f));\n    }\n\n    SECTION(\"Recurring profiles - Daily\") {\n        \n        float current = -1.f;\n        setSmartChargingOutput([&current] (float, float limit_current, int) {\n            current = limit_current;\n        });\n\n        loopback.sendTXT(SCPROFILE_7_RECURRING_DAY_2H_16A_20A, strlen(SCPROFILE_7_RECURRING_DAY_2H_16A_20A));\n\n        loop();\n\n        REQUIRE((current > 15.99f && current < 16.01f));\n\n        //now exceed duration\n        model.getClock().setTime(\"2023-01-01T02:00:00.000Z\");\n\n        loop();\n\n        REQUIRE(current < 0.f);\n\n        //check second period three days afterwards\n        model.getClock().setTime(\"2023-01-04T01:00:00.000Z\");\n\n        loop();\n\n        REQUIRE((current > 19.99f && current < 20.01f));\n    }\n\n    SECTION(\"Recurring profiles - Weekly\") {\n        \n        float current = -1.f;\n        setSmartChargingOutput([&current] (float, float limit_current, int) {\n            current = limit_current;\n        });\n\n        loopback.sendTXT(SCPROFILE_8_RECURRING_WEEK_2H_10A_12A, strlen(SCPROFILE_8_RECURRING_WEEK_2H_10A_12A));\n\n        loop();\n\n        REQUIRE((current > 9.99f && current < 10.01f));\n\n        //now exceed duration\n        model.getClock().setTime(\"2023-01-01T02:00:00.000Z\");\n\n        loop();\n\n        REQUIRE(current < 0.f);\n\n        //check second period three weeks afterwards\n        model.getClock().setTime(\"2023-01-22T01:00:00.000Z\");\n\n        loop();\n\n        REQUIRE((current > 11.99f && current < 12.01f));\n    }\n\n    SECTION(\"Stacking recurring profiles\") {\n\n        float current = -1.f;\n        setSmartChargingOutput([&current] (float, float limit_current, int) {\n            current = limit_current;\n        });\n\n        loopback.sendTXT(SCPROFILE_7_RECURRING_DAY_2H_16A_20A, strlen(SCPROFILE_7_RECURRING_DAY_2H_16A_20A)); //stackLevel: 0\n\n        loopback.sendTXT(SCPROFILE_8_RECURRING_WEEK_2H_10A_12A, strlen(SCPROFILE_8_RECURRING_WEEK_2H_10A_12A)); //stackLevel: 1\n\n        loop();\n\n        REQUIRE((current > 9.99f && current < 10.01f)); //Weekly schedule prevails\n\n        //check again during the week\n        model.getClock().setTime(\"2023-01-03T00:00:00.000Z\");\n\n        loop();\n\n        REQUIRE((current > 15.99f && current < 16.01f)); //Weekly schedule out of duration, only Daily defined\n\n        //check again three weeks later\n        model.getClock().setTime(\"2023-01-22T00:00:00.000Z\");\n\n        loop();\n\n        REQUIRE((current > 9.99f && current < 10.01f)); //Weekly schedule prevails again\n\n        //check again during the week\n        model.getClock().setTime(\"2023-01-23T00:00:00.000Z\");\n\n        loop();\n\n        REQUIRE((current > 15.99f && current < 16.01f)); //Weekly schedule out of duration again, only Daily defined again\n    }\n\n    SECTION(\"TxProfile capped by ChargePointMaxProfile\") {\n\n        float current = -1.f;\n        int numberPhases = -1;\n        setSmartChargingOutput([&current, &numberPhases] (float, float limit_current, int limit_numberPhases) {\n            current = limit_current;\n            numberPhases = limit_numberPhases;\n        });\n\n        loop();\n\n        loopback.sendTXT(SCPROFILE_9_VIA_RMTSTARTTX_20A, strlen(SCPROFILE_9_VIA_RMTSTARTTX_20A));\n\n        loop();\n\n        loopback.sendTXT(SCPROFILE_1_ABSOLUTE_LIMIT_16A, strlen(SCPROFILE_1_ABSOLUTE_LIMIT_16A));\n\n        loop();\n\n        REQUIRE((current > 15.99f && current < 16.01f)); //current limited by ChargePointMaxProfile\n        REQUIRE(numberPhases == 1); //numberPhases limited by TxProfile\n\n        endTransaction();\n\n        loop();\n    }\n\n    SECTION(\"TxProfile and ChargePointMaxProfile with mixed units\") {\n\n        float power = -1.f;\n        float current = -1.f;\n        setSmartChargingOutput([&power, &current] (float limit_power, float limit_current, int) {\n            power = limit_power;\n            current = limit_current;\n        });\n\n        loop();\n\n        loopback.sendTXT(SCPROFILE_9_VIA_RMTSTARTTX_20A, strlen(SCPROFILE_9_VIA_RMTSTARTTX_20A));\n\n        loop();\n\n        loopback.sendTXT(SCPROFILE_10_ABSOLUTE_LIMIT_5KW, strlen(SCPROFILE_10_ABSOLUTE_LIMIT_5KW));\n\n        loop();\n\n        REQUIRE((power > 4999.f && power < 5001.f)); //ChargePointMaxProfile defines power\n        REQUIRE((current > 19.99f && current < 20.01f)); //TxProfile defines current\n\n        endTransaction();\n\n        loop();\n    }\n\n    SECTION(\"Get composite schedule\") {\n\n        loopback.sendTXT(SCPROFILE_6_MULTIPLE_PERIODS_16A_20A, strlen(SCPROFILE_6_MULTIPLE_PERIODS_16A_20A));\n\n        bool checkProcessed = false;\n\n        auto getCompositeSchedule = makeRequest(new Ocpp16::CustomOperation(\n                \"GetCompositeSchedule\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(3));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"connectorId\"] = 1;\n                    payload[\"duration\"] = 86400;\n                    payload[\"chargingRateUnit\"] = \"A\";\n                    return doc;},\n                [&checkProcessed, &model] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE(!strcmp(payload[\"status\"], \"Accepted\"));\n                    REQUIRE(payload[\"connectorId\"] == 1);\n                    \n                    char checkScheduleStart [JSONDATE_LENGTH + 1];\n                    model.getClock().now().toJsonString(checkScheduleStart, JSONDATE_LENGTH + 1);\n                    REQUIRE(!strcmp(payload[\"scheduleStart\"], checkScheduleStart));\n\n                    JsonObject chargingScheduleJson = payload[\"chargingSchedule\"];\n                    ChargingSchedule schedule;\n                    bool success = loadChargingSchedule(chargingScheduleJson, schedule);\n\n                    REQUIRE(success);\n                    REQUIRE(schedule.chargingSchedulePeriod.size() == 2);\n\n                    REQUIRE((schedule.chargingSchedulePeriod[0].limit > 15.99f &&\n                            schedule.chargingSchedulePeriod[0].limit < 16.01f));\n                    REQUIRE(schedule.chargingSchedulePeriod[0].startPeriod == 0);\n\n                    REQUIRE((schedule.chargingSchedulePeriod[1].limit > 19.99f &&\n                            schedule.chargingSchedulePeriod[1].limit < 20.01f));\n                    REQUIRE(schedule.chargingSchedulePeriod[1].startPeriod == 3600);\n                }\n        ));\n\n        context->initiateRequest(std::move(getCompositeSchedule));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n    }\n\n    SECTION(\"Get composite schedule with definition gap\") {\n\n        loopback.sendTXT(SCPROFILE_7_RECURRING_DAY_2H_16A_20A, strlen(SCPROFILE_7_RECURRING_DAY_2H_16A_20A));\n\n        auto schedule = scService->getCompositeSchedule(1, 86401);\n\n        REQUIRE(schedule != nullptr);\n        REQUIRE(schedule->duration == 86401);\n        REQUIRE(schedule->chargingSchedulePeriod.size() == 4);\n        \n        REQUIRE((schedule->chargingSchedulePeriod[0].limit > 15.99f &&\n                schedule->chargingSchedulePeriod[0].limit < 16.01f));\n        REQUIRE(schedule->chargingSchedulePeriod[0].startPeriod == 0);\n\n        REQUIRE((schedule->chargingSchedulePeriod[1].limit > 19.99f &&\n                schedule->chargingSchedulePeriod[1].limit < 20.01f));\n        REQUIRE(schedule->chargingSchedulePeriod[1].startPeriod == 3600);\n\n        REQUIRE(schedule->chargingSchedulePeriod[2].limit < 0.f); //undefined during this period\n        REQUIRE(schedule->chargingSchedulePeriod[2].startPeriod == 2 * 3600);\n\n        REQUIRE((schedule->chargingSchedulePeriod[3].limit > 15.99f &&\n                schedule->chargingSchedulePeriod[3].limit < 16.01f));\n        REQUIRE(schedule->chargingSchedulePeriod[3].startPeriod == 86400);\n    }\n\n    SECTION(\"SmartCharging memory limits - MaxChargingProfilesInstalled\") {\n\n        loop();\n\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    return doc;},\n                [&checkProcessed] (JsonObject response) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_0_ALT_SAME_ID);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    (*doc)[\"connectorId\"] = 2;\n                    return doc;},\n                [&checkProcessed] (JsonObject response) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_1_ABSOLUTE_LIMIT_16A);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    return doc;},\n                [&checkProcessed] (JsonObject response) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        // 3 distinct ChargingProfiles installed. Check if further Profiles are rejected correctly\n\n        for (size_t i = 0; i < 2; i++) {\n            // replace existing profile - OK\n\n            checkProcessed = false;\n            getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                    \"SetChargingProfile\",\n                    [] () {\n                        //create req\n                        StaticJsonDocument<2048> raw;\n                        deserializeJson(raw, SCPROFILE_1_ABSOLUTE_LIMIT_16A);\n                        auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                        *doc = raw[3];\n                        return doc;},\n                    [&checkProcessed] (JsonObject response) {\n                        checkProcessed = true;\n                        REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Accepted\") );\n                    }\n            )));\n            loop();\n            REQUIRE( checkProcessed );\n        }\n\n        for (size_t i = 0; i < 2; i++) {\n            // try to install additional profile - not okay\n\n            checkProcessed = false;\n            getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                    \"SetChargingProfile\",\n                    [] () {\n                        //create req\n                        StaticJsonDocument<2048> raw;\n                        deserializeJson(raw, SCPROFILE_5_VALID_UNTIL_2022_16A);\n                        auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                        *doc = raw[3];\n                        return doc;},\n                    [&checkProcessed] (JsonObject response) {\n                        checkProcessed = true;\n                        REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Rejected\") );\n                    }\n            )));\n            loop();\n            REQUIRE( checkProcessed );\n        }\n    }\n\n    SECTION(\"SmartCharging memory limits - ChargeProfileMaxStackLevel\") {\n\n        loop();\n\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    (*doc)[\"csChargingProfiles\"][\"stackLevel\"] = MO_ChargeProfileMaxStackLevel;\n                    return doc;},\n                [&checkProcessed] (JsonObject response) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    (*doc)[\"csChargingProfiles\"][\"stackLevel\"] = MO_ChargeProfileMaxStackLevel + 1;\n                    return doc;},\n                [] (JsonObject) { }, //ignore conf\n                [&checkProcessed] (const char*, const char*, JsonObject) {\n                    // process error\n                    checkProcessed = true;\n                    return true;\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n    }\n\n    SECTION(\"SmartCharging memory limits - ChargingScheduleMaxPeriods\") {\n\n        loop();\n\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    JsonArray chargingSchedulePeriod = (*doc)[\"csChargingProfiles\"][\"chargingSchedule\"][\"chargingSchedulePeriod\"];\n                    chargingSchedulePeriod.clear();\n                    for (size_t i = 0; i < MO_ChargingScheduleMaxPeriods; i++) {\n                        auto period = chargingSchedulePeriod.createNestedObject();\n                        period[\"startPeriod\"] = i;\n                        period[\"limit\"] = 16;\n                    }\n                    return doc;},\n                [&checkProcessed] (JsonObject response) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_2_RELATIVE_TXDEF_24A);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    JsonArray chargingSchedulePeriod = (*doc)[\"csChargingProfiles\"][\"chargingSchedule\"][\"chargingSchedulePeriod\"];\n                    chargingSchedulePeriod.clear();\n                    for (size_t i = 0; i < MO_ChargingScheduleMaxPeriods + 1; i++) {\n                        auto period = chargingSchedulePeriod.createNestedObject();\n                        period[\"startPeriod\"] = i;\n                        period[\"limit\"] = 16;\n                    }\n                    return doc;},\n                [] (JsonObject) { }, //ignore conf\n                [&checkProcessed] (const char*, const char*, JsonObject) {\n                    // process error\n                    checkProcessed = true;\n                    return true;\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n    }\n\n    SECTION(\"ChargingScheduleAllowedChargingRateUnit\") {\n        \n        setSmartChargingOutput(nullptr);\n        loop();\n\n        // accept power, reject current\n        setSmartChargingPowerOutput([] (float) { });\n\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_0);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    return doc;},\n                [&checkProcessed] (JsonObject response) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_1_ABSOLUTE_LIMIT_16A);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    return doc;},\n                [&checkProcessed] (JsonObject response) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Rejected\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        //  reject power, accept current\n        setSmartChargingPowerOutput(nullptr);\n        setSmartChargingCurrentOutput([] (float) { });\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_0);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    return doc;},\n                [&checkProcessed] (JsonObject response) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Rejected\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"SetChargingProfile\",\n                [] () {\n                    //create req\n                    StaticJsonDocument<2048> raw;\n                    deserializeJson(raw, SCPROFILE_1_ABSOLUTE_LIMIT_16A);\n                    auto doc = makeJsonDoc(\"UnitTests\", 2048);\n                    *doc = raw[3];\n                    return doc;},\n                [&checkProcessed] (JsonObject response) {\n                    checkProcessed = true;\n                    REQUIRE( !strcmp(response[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n    }\n\n    scService->clearChargingProfile([] (int, int, ChargingProfilePurposeType, int) {\n        return true;\n    });\n\n    mocpp_deinitialize();\n\n}\n"
  },
  {
    "path": "tests/TransactionSafety.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Core/Configuration.h>\n#include <MicroOcpp/Operations/BootNotification.h>\n#include <MicroOcpp/Operations/StatusNotification.h>\n#include <MicroOcpp/Debug.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\nusing namespace MicroOcpp;\n\n\nTEST_CASE( \"Transaction safety\" ) {\n    printf(\"\\nRun %s\\n\",  \"Transaction safety\");\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n    mocpp_initialize(loopback);\n\n    mocpp_set_timer(custom_timer_cb);\n\n    declareConfiguration<int>(\"ConnectionTimeOut\", 30)->setInt(30);\n\n    SECTION(\"Basic transaction\") {\n        MO_DBG_DEBUG(\"Basic transaction\");\n        loop();\n        startTransaction(\"mIdTag\");\n        loop();\n        REQUIRE(ocppPermitsCharge());\n        stopTransaction();\n        loop();\n        REQUIRE(!ocppPermitsCharge());\n\n        mocpp_deinitialize();\n    }\n\n    SECTION(\"Managed transaction\") {\n        MO_DBG_DEBUG(\"Managed transaction\");\n        loop();\n        setConnectorPluggedInput([] () {return true;});\n        beginTransaction(\"mIdTag\");\n        loop();\n        REQUIRE(ocppPermitsCharge());\n        endTransaction();\n        loop();\n        REQUIRE(!ocppPermitsCharge());\n        \n        mocpp_deinitialize();\n    }\n\n    SECTION(\"Reset during transaction 01 - interrupt initiation\") {\n        MO_DBG_DEBUG(\"Reset during transaction 01 - interrupt initiation\");\n        setConnectorPluggedInput([] () {return false;});\n        loop();\n        beginTransaction(\"mIdTag\");\n        loop();\n        mocpp_deinitialize(); //reset and jump to next section\n    }\n\n    SECTION(\"Reset during transaction 02 - interrupt initiation second time\") {\n        MO_DBG_DEBUG(\"Reset during transaction 02 - interrupt initiation second time\");\n        setConnectorPluggedInput([] () {return false;});\n        loop();\n        REQUIRE(!ocppPermitsCharge());\n        mocpp_deinitialize();\n    }\n\n    SECTION(\"Reset during transaction 03 - interrupt running tx\") {\n        MO_DBG_DEBUG(\"Reset during transaction 03 - interrupt running tx\");\n        setConnectorPluggedInput([] () {return true;});\n        loop();\n        REQUIRE(ocppPermitsCharge());\n        mocpp_deinitialize();\n    }\n\n    SECTION(\"Reset during transaction 04 - interrupt stopping tx\") {\n        MO_DBG_DEBUG(\"Reset during transaction 04 - interrupt stopping tx\");\n        setConnectorPluggedInput([] () {return true;});\n        loop();\n        REQUIRE(ocppPermitsCharge());\n        endTransaction();\n        mocpp_deinitialize();\n    }\n\n    SECTION(\"Reset during transaction 06 - check tx finished\") {\n        MO_DBG_DEBUG(\"Reset during transaction 06 - check tx finished\");\n        setConnectorPluggedInput([] () {return true;});\n        loop();\n        REQUIRE(!ocppPermitsCharge());\n        mocpp_deinitialize();\n    }\n\n}\n"
  },
  {
    "path": "tests/Transactions.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Model/Model.h>\n#include <MicroOcpp/Model/Transactions/TransactionService.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n#include <MicroOcpp/Debug.h>\n#include <MicroOcpp/Core/Memory.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#define BASE_TIME \"2023-01-01T00:00:00.000Z\"\n\nusing namespace MicroOcpp;\n\n\nTEST_CASE( \"Transactions\" ) {\n    printf(\"\\nRun %s\\n\",  \"Transactions\");\n\n    //initialize Context with dummy socket\n    LoopbackConnection loopback;\n    mocpp_initialize(loopback,\n            ChargerCredentials(\"test-runner1234\"),\n            makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail),\n            false,\n            ProtocolVersion(2,0,1));\n\n    auto context = getOcppContext();\n\n    mocpp_set_timer(custom_timer_cb);\n\n    getOcppContext()->getOperationRegistry().registerOperation(\"Authorize\", [] () {\n        return new Ocpp16::CustomOperation(\"Authorize\",\n            [] (JsonObject) {}, //ignore req\n            [] () {\n                //create conf\n                auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                auto payload = doc->to<JsonObject>();\n                payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                return doc;\n            });});\n    \n    getOcppContext()->getOperationRegistry().registerOperation(\"TransactionEvent\", [] () {\n        return new Ocpp16::CustomOperation(\"TransactionEvent\",\n            [] (JsonObject) {}, //ignore req\n            [] () {\n                //create conf\n                auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                auto payload = doc->to<JsonObject>();\n                payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                return doc;\n            });});\n\n    loop();\n\n    SECTION(\"Basic transaction\") {\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        MO_DBG_DEBUG(\"plug EV\");\n        setConnectorPluggedInput([] () {return true;});\n\n        loop();\n\n        MO_DBG_DEBUG(\"authorize\");\n        context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\");\n\n        loop();\n\n        MO_DBG_DEBUG(\"EV requests charge\");\n        setEvReadyInput([] () {return true;});\n\n        loop();\n\n        MO_DBG_DEBUG(\"power circuit closed\");\n        setEvseReadyInput([] () {return true;});\n\n        loop();\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction()->started );\n        REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->getTransaction()->stopped );\n\n        MO_DBG_DEBUG(\"EV idle\");\n        setEvReadyInput([] () {return false;});\n\n        loop();\n\n        MO_DBG_DEBUG(\"power circuit opened\");\n        setEvseReadyInput([] () {return false;});\n\n        loop();\n\n        MO_DBG_DEBUG(\"deauthorize\");\n        context->getModel().getTransactionService()->getEvse(1)->endAuthorization(\"mIdToken\");\n\n        loop();\n        \n        MO_DBG_DEBUG(\"unplug EV\");\n        setConnectorPluggedInput([] () {return false;});\n\n        loop();\n\n        REQUIRE( (context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr || \n                  context->getModel().getTransactionService()->getEvse(1)->getTransaction()->stopped));\n    }\n\n    SECTION(\"UC C01-04\") {\n\n        //scenario preparation\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"PowerPathClosed\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"PowerPathClosed\");\n\n        setConnectorPluggedInput([] () {return false;});\n\n        loop();\n\n        MO_MEM_RESET();\n\n        context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\");\n        loop();\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() != nullptr );\n        REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->getTransaction()->started );\n        REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->getTransaction()->stopped );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n\n        MO_DBG_INFO(\"Memory requirements UC C01-04:\");\n\n        MO_MEM_PRINT_STATS();\n\n        context->getModel().getTransactionService()->getEvse(1)->abortTransaction();\n        loop();\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n    }\n\n    SECTION(\"UC E01 - S5 / E06\") {\n\n        //scenario preparation\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"PowerPathClosed\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"PowerPathClosed\");\n\n        setConnectorPluggedInput([] () {return false;});\n\n        loop();\n\n        MO_MEM_RESET();\n\n        //run scenario\n\n        setConnectorPluggedInput([] () {return true;});\n        loop();\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Occupied );\n\n        context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\");\n        loop();\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() != nullptr );\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction()->started );\n        REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->getTransaction()->stopped );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Occupied );\n\n        MO_DBG_INFO(\"Memory requirements UC E01 - S5:\");\n\n        MO_MEM_PRINT_STATS();\n\n        MO_MEM_RESET();\n\n        setConnectorPluggedInput([] () {return false;});\n        loop();\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n\n        MO_DBG_INFO(\"Memory requirements UC E06:\");\n        MO_MEM_PRINT_STATS();\n\n    }\n\n    SECTION(\"UC G01\") {\n\n        setConnectorPluggedInput([] () {return false;});\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n        MO_MEM_RESET();\n\n        setConnectorPluggedInput([] () {return true;});\n        loop();\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Occupied );\n\n        MO_DBG_INFO(\"Memory requirements UC G01:\");\n        MO_MEM_PRINT_STATS();\n    }\n\n    SECTION(\"UC J02\") {\n\n        //scenario preparation\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Available );\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"PowerPathClosed\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"PowerPathClosed\");\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"SampledDataCtrlr\", \"TxStartedMeasurands\", \"\")->setString(\"Energy.Active.Import.Register\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"SampledDataCtrlr\", \"TxUpdatedMeasurands\", \"\")->setString(\"Power.Active.Import\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<int>(\"SampledDataCtrlr\", \"TxUpdatedInterval\", 0)->setInt(60);\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"SampledDataCtrlr\", \"TxEndedMeasurands\", \"\")->setString(\"Current.Import\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<int>(\"SampledDataCtrlr\", \"TxEndededInterval\", 0)->setInt(100);\n\n        setConnectorPluggedInput([] () {return false;});\n        setEnergyMeterInput([] () {return 100;});\n        setPowerMeterInput([] () {return 200;});\n        addMeterValueInput([] () {return 30;}, \"Current.Import\", \"A\");\n\n        Timestamp tStart, tUpdated, tEnded;\n\n        setOnReceiveRequest(\"TransactionEvent\", [&tStart, &tUpdated, &tEnded] (JsonObject request) {\n            const char *eventType = request[\"eventType\"] | (const char*)nullptr;\n            bool eventTypeError = false;\n            if (!strcmp(eventType, \"Started\")) {\n                tStart = getOcppContext()->getModel().getClock().now();\n\n                REQUIRE( request[\"meterValue\"].as<JsonArray>().size() >= 1 );\n\n                Timestamp tMv;\n                tMv.setTime(request[\"meterValue\"][0][\"timestamp\"]);\n                REQUIRE( std::abs(tStart - tMv) <= 1);\n\n                REQUIRE( request[\"meterValue\"][0][\"sampledValue\"].as<JsonArray>().size() >= 1 );\n\n                REQUIRE( !strcmp(request[\"meterValue\"][0][\"sampledValue\"][0][\"measurand\"] | \"_Undefined\", \"Energy.Active.Import.Register\") );\n                REQUIRE( !strcmp(request[\"meterValue\"][0][\"sampledValue\"][0][\"measurand\"] | \"_Undefined\", \"Energy.Active.Import.Register\") );\n            } else if (!strcmp(eventType, \"Updated\")) {\n                tUpdated = getOcppContext()->getModel().getClock().now();\n\n            } else if (!strcmp(eventType, \"Ended\")) {\n                tEnded = getOcppContext()->getModel().getClock().now();\n\n            } else {\n                eventTypeError = true;\n            }\n            REQUIRE( !eventTypeError );\n        });\n\n        loop();\n\n        MO_MEM_RESET();\n\n        //run scenario\n\n        setConnectorPluggedInput([] () {return true;});\n        context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\");\n        loop();\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() != nullptr );\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction()->started );\n        REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->getTransaction()->stopped );\n        REQUIRE( getChargePointStatus() == ChargePointStatus_Occupied );\n\n        MO_DBG_INFO(\"Memory requirements UC E01 - S5:\");\n\n        MO_MEM_PRINT_STATS();\n\n        context->getModel().getTransactionService()->getEvse(1)->endAuthorization();\n        loop();\n\n        REQUIRE( (tStart > MIN_TIME) );\n        //REQUIRE( (tUpdated > MIN_TIME) );\n        REQUIRE( (tEnded > MIN_TIME) );\n\n    }\n\n    SECTION(\"TxEvents queue\") {\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"Authorized\");\n\n        bool checkReceivedStarted = false, checkReceivedEnded = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"TransactionEvent\", [&checkReceivedStarted, &checkReceivedEnded] () {\n            return new Ocpp16::CustomOperation(\"TransactionEvent\",\n                [&checkReceivedStarted, &checkReceivedEnded] (JsonObject request) {\n                    //process req\n                    const char *eventType = request[\"eventType\"] | (const char*)nullptr;\n                    if (!strcmp(eventType, \"Started\")) {\n                        checkReceivedStarted = true;\n                    } else if (!strcmp(eventType, \"Ended\")) {\n                        checkReceivedEnded = true;\n                    }\n                },\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                    return doc;\n                });});\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        loopback.setConnected(false);\n\n        context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\", false);\n\n        loop();\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() != nullptr );\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction()->started );\n\n        context->getModel().getTransactionService()->getEvse(1)->endAuthorization();\n\n        loop();\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        loopback.setConnected(true);\n        loop();\n\n        REQUIRE( checkReceivedStarted );\n        REQUIRE( checkReceivedEnded );\n    }\n\n    SECTION(\"TxEvents queue size limit\") {\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"Authorized\");\n\n        bool checkReceivedStarted = false, checkReceivedEnded = false;\n        size_t checkSeqNosSize = 0;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"TransactionEvent\", [&checkReceivedStarted, &checkReceivedEnded, &checkSeqNosSize] () {\n            return new Ocpp16::CustomOperation(\"TransactionEvent\",\n                [&checkReceivedStarted, &checkReceivedEnded, &checkSeqNosSize] (JsonObject request) {\n                    //process req\n                    const char *eventType = request[\"eventType\"] | (const char*)nullptr;\n                    if (!strcmp(eventType, \"Started\")) {\n                        checkReceivedStarted = true;\n                    } else if (!strcmp(eventType, \"Ended\")) {\n                        checkReceivedEnded = true;\n                    }\n                    checkSeqNosSize++;\n                },\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                    return doc;\n                });});\n    \n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        loopback.setConnected(false);\n\n        context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\", false);\n\n        loop();\n\n        auto tx = context->getModel().getTransactionService()->getEvse(1)->getTransaction();\n        REQUIRE( tx != nullptr );\n\n        for (size_t i = 0; i < MO_TXEVENTRECORD_SIZE_V201 * 2; i++) {\n            setEvReadyInput([] () {return false;});\n            loop();\n            setEvReadyInput([] () {return true;});\n            loop();\n            setEvReadyInput([] () {return false;});\n            loop();\n        }\n\n        REQUIRE( tx->seqNos.size() == MO_TXEVENTRECORD_SIZE_V201 );\n\n        for (auto seqNo : tx->seqNos) {\n            MO_DBG_DEBUG(\"stored seqNo %u\", seqNo);\n            (void)seqNo;\n        }\n\n        for (size_t i = 1; i < tx->seqNos.size(); i++) {\n            auto delta = tx->seqNos[i] - tx->seqNos[i-1];\n            REQUIRE(delta <= 2 * tx->seqNos.back() / MO_TXEVENTRECORD_SIZE_V201 );\n        }\n\n        context->getModel().getTransactionService()->getEvse(1)->endAuthorization();\n\n        loop();\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        loopback.setConnected(true);\n        loop();\n\n        REQUIRE( checkReceivedStarted );\n        REQUIRE( checkReceivedEnded );\n        REQUIRE( checkSeqNosSize == MO_TXEVENTRECORD_SIZE_V201 );\n    }\n\n    SECTION(\"Tx queue\") {\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"Authorized\");\n\n        std::map<std::string,std::tuple<bool,bool>> txEventRequests;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"TransactionEvent\", [&txEventRequests] () {\n            return new Ocpp16::CustomOperation(\"TransactionEvent\",\n                [&txEventRequests] (JsonObject request) {\n                    //process req\n                    const char *eventType = request[\"eventType\"] | (const char*)nullptr;\n                    if (!strcmp(eventType, \"Started\")) {\n                        std::get<0>(txEventRequests[request[\"transactionInfo\"][\"transactionId\"] | \"_Undefined\"]) = true;\n                    } else if (!strcmp(eventType, \"Ended\")) {\n                        std::get<1>(txEventRequests[request[\"transactionInfo\"][\"transactionId\"] | \"_Undefined\"]) = true;\n                    }\n                },\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                    return doc;\n                });});\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        loopback.setConnected(false);\n\n        for (size_t i = 0; i < MO_TXRECORD_SIZE_V201; i++) {\n\n            char idTokenBuf [MO_IDTOKEN_LEN_MAX + 1];\n            snprintf(idTokenBuf, sizeof(idTokenBuf), \"mIdToken-%zu\", i);\n\n            REQUIRE( context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(idTokenBuf, false) );\n\n            loop();\n\n            auto tx = context->getModel().getTransactionService()->getEvse(1)->getTransaction();\n\n            REQUIRE( tx != nullptr );\n            REQUIRE( tx->started );\n            txEventRequests[tx->transactionId] = {false, false};\n\n            context->getModel().getTransactionService()->getEvse(1)->endAuthorization();\n\n            loop();\n\n            REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n        }\n\n        REQUIRE( !context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\", false) );\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        loopback.setConnected(true);\n        loop();\n\n        for (const auto& txReq : txEventRequests) {\n            MO_DBG_DEBUG(\"check txId %s\", txReq.first.c_str());\n            REQUIRE( std::get<0>(txReq.second) );\n            REQUIRE( std::get<1>(txReq.second) );\n        }\n\n        REQUIRE( txEventRequests.size() == MO_TXRECORD_SIZE_V201 );\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(\"mIdToken\", false) );\n        loop();\n        auto tx = context->getModel().getTransactionService()->getEvse(1)->getTransaction();\n        REQUIRE( tx != nullptr );\n        REQUIRE( tx->started );\n\n        context->getModel().getTransactionService()->getEvse(1)->endAuthorization();\n        loop();\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n    }\n\n    SECTION(\"Power loss during running transaction\") {\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"Authorized\");\n\n        REQUIRE( getOcppContext()->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        const char *idTag = \"example123\";\n\n        getOcppContext()->getModel().getTransactionService()->getEvse(1)->beginAuthorization(idTag, false);\n        loop();\n\n        auto tx = getOcppContext()->getModel().getTransactionService()->getEvse(1)->getTransaction();\n        REQUIRE( tx != nullptr );\n        REQUIRE( tx->started );\n\n        auto txNr = tx->txNr;\n        std::string txId = tx->transactionId;\n\n        //power cut\n        mocpp_deinitialize();\n\n        //power restored\n        mocpp_initialize(loopback,\n            ChargerCredentials(),\n            makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail),\n            false,\n            ProtocolVersion(2,0,1));\n        mocpp_set_timer(custom_timer_cb);\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"Authorized\");\n\n        bool checkProcessed = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"TransactionEvent\", [&checkProcessed, txId] () {\n            return new Ocpp16::CustomOperation(\"TransactionEvent\",\n                [&checkProcessed, txId] (JsonObject request) {\n                    //process req\n                    const char *eventType = request[\"eventType\"] | (const char*)nullptr;\n                    REQUIRE( strcmp(eventType, \"Started\") );\n                    if (!strcmp(eventType, \"Ended\")) {\n                        checkProcessed = true;\n                    }\n                    REQUIRE( !txId.compare(request[\"transactionInfo\"][\"transactionId\"] | \"_Undefined\") );\n                },\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                    return doc;\n                });});\n        \n        loop(); //let MO spin up and reconnect\n\n        tx = getOcppContext()->getModel().getTransactionService()->getEvse(1)->getTransaction();\n        REQUIRE( tx != nullptr );\n        REQUIRE( tx->started );\n        REQUIRE( !tx->stopped );\n        REQUIRE( tx->txNr == txNr );\n        REQUIRE( !txId.compare(tx->transactionId) );\n        REQUIRE( !strcmp(tx->idToken.get(), idTag) );\n\n        getOcppContext()->getModel().getTransactionService()->getEvse(1)->endAuthorization();\n        loop();\n\n        tx = getOcppContext()->getModel().getTransactionService()->getEvse(1)->getTransaction();\n        REQUIRE( tx == nullptr );\n        REQUIRE( checkProcessed ); //txEvent was sent\n    }\n\n    SECTION(\"Power loss with enqueued txEvents\") {\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"Authorized\");\n\n        REQUIRE( getOcppContext()->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        loopback.setConnected(false);\n\n        const char *idTag = \"example123\";\n\n        getOcppContext()->getModel().getTransactionService()->getEvse(1)->beginAuthorization(idTag, false);\n        loop();\n\n        auto tx = getOcppContext()->getModel().getTransactionService()->getEvse(1)->getTransaction();\n        REQUIRE( tx != nullptr );\n        REQUIRE( tx->started );\n\n        auto txNr = tx->txNr;\n        std::string txId = tx->transactionId;\n\n        setEvReadyInput([] () {return false;});\n        loop();\n        setEvReadyInput([] () {return true;});\n        loop();\n        setEvReadyInput([] () {return false;});\n        loop();\n\n        size_t seqNosSize = tx->seqNos.size();\n        size_t checkSeqNosSize = 0;\n\n        //power cut\n        mocpp_deinitialize();\n\n        //power restored\n        mocpp_initialize(loopback,\n            ChargerCredentials(),\n            makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail),\n            false,\n            ProtocolVersion(2,0,1));\n        mocpp_set_timer(custom_timer_cb);\n\n        loopback.setConnected(true);\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"Authorized\");\n\n        bool checkReceivedStarted = false, checkReceivedEnded = false;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"TransactionEvent\", [&checkReceivedStarted, &checkReceivedEnded, txId, &checkSeqNosSize] () {\n            return new Ocpp16::CustomOperation(\"TransactionEvent\",\n                [&checkReceivedStarted, &checkReceivedEnded, txId, &checkSeqNosSize] (JsonObject request) {\n                    //process req\n                    const char *eventType = request[\"eventType\"] | (const char*)nullptr;\n                    if (!strcmp(eventType, \"Started\")) {\n                        checkReceivedStarted = true;\n                    } else if (!strcmp(eventType, \"Ended\")) {\n                        checkReceivedEnded = true;\n                    }\n                    REQUIRE( !txId.compare(request[\"transactionInfo\"][\"transactionId\"] | \"_Undefined\") );\n                    checkSeqNosSize++;\n                },\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                    return doc;\n                });});\n        \n        loop(); //let MO spin up and reconnect\n\n        REQUIRE( checkReceivedStarted );\n        REQUIRE( (seqNosSize == checkSeqNosSize || seqNosSize + 1 == checkSeqNosSize) );\n\n        tx = getOcppContext()->getModel().getTransactionService()->getEvse(1)->getTransaction();\n        REQUIRE( tx != nullptr );\n        REQUIRE( tx->started );\n        REQUIRE( !tx->stopped );\n        REQUIRE( tx->txNr == txNr );\n        REQUIRE( !txId.compare(tx->transactionId) );\n        REQUIRE( !strcmp(tx->idToken.get(), idTag) );\n\n        getOcppContext()->getModel().getTransactionService()->getEvse(1)->endAuthorization();\n        loop();\n\n        tx = getOcppContext()->getModel().getTransactionService()->getEvse(1)->getTransaction();\n        REQUIRE( tx == nullptr );\n        REQUIRE( checkReceivedEnded ); //txEvent was sent\n    }\n\n    SECTION(\"Power loss with enqueued transactions\") {\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"Authorized\");\n\n        std::map<std::string,std::tuple<bool,bool>> txEventRequests;\n\n        REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n\n        loopback.setConnected(false);\n\n        for (size_t i = 0; i < MO_TXRECORD_SIZE_V201; i++) {\n\n            char idTokenBuf [MO_IDTOKEN_LEN_MAX + 1];\n            snprintf(idTokenBuf, sizeof(idTokenBuf), \"mIdToken-%zu\", i);\n\n            REQUIRE( context->getModel().getTransactionService()->getEvse(1)->beginAuthorization(idTokenBuf, false) );\n\n            loop();\n\n            auto tx = context->getModel().getTransactionService()->getEvse(1)->getTransaction();\n\n            REQUIRE( tx != nullptr );\n            REQUIRE( tx->started );\n            txEventRequests[tx->transactionId] = {false, false};\n\n            context->getModel().getTransactionService()->getEvse(1)->endAuthorization();\n\n            loop();\n\n            REQUIRE( context->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n        }\n\n        //power cut\n        mocpp_deinitialize();\n\n        //power restored\n        mocpp_initialize(loopback,\n            ChargerCredentials(),\n            makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail),\n            false,\n            ProtocolVersion(2,0,1));\n        mocpp_set_timer(custom_timer_cb);\n\n        loopback.setConnected(true);\n\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStartPoint\", \"\")->setString(\"Authorized\");\n        getOcppContext()->getModel().getVariableService()->declareVariable<const char*>(\"TxCtrlr\", \"TxStopPoint\", \"\")->setString(\"Authorized\");\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"TransactionEvent\", [&txEventRequests] () {\n            return new Ocpp16::CustomOperation(\"TransactionEvent\",\n                [&txEventRequests] (JsonObject request) {\n                    //process req\n                    const char *eventType = request[\"eventType\"] | (const char*)nullptr;\n                    if (!strcmp(eventType, \"Started\")) {\n                        std::get<0>(txEventRequests[request[\"transactionInfo\"][\"transactionId\"] | \"_Undefined\"]) = true;\n                    } else if (!strcmp(eventType, \"Ended\")) {\n                        std::get<1>(txEventRequests[request[\"transactionInfo\"][\"transactionId\"] | \"_Undefined\"]) = true;\n                    }\n                },\n                [] () {\n                    //create conf\n                    auto doc = makeJsonDoc(\"UnitTests\", 2 * JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"idTokenInfo\"][\"status\"] = \"Accepted\";\n                    return doc;\n                });});\n\n        loopback.setConnected(true);\n        loop();\n\n        for (const auto& txReq : txEventRequests) {\n            MO_DBG_DEBUG(\"check txId %s\", txReq.first.c_str());\n            REQUIRE( std::get<0>(txReq.second) );\n            REQUIRE( std::get<1>(txReq.second) );\n        }\n\n        REQUIRE( txEventRequests.size() == MO_TXRECORD_SIZE_V201 );\n\n        REQUIRE( getOcppContext()->getModel().getTransactionService()->getEvse(1)->getTransaction() == nullptr );\n    }\n\n    mocpp_deinitialize();\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "tests/Variables.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp/Version.h>\n\n#if MO_ENABLE_V201\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\n#include <MicroOcpp/Core/FilesystemAdapter.h>\n#include <MicroOcpp/Core/FilesystemUtils.h>\n#include <MicroOcpp/Model/Variables/VariableService.h>\n\n#include <MicroOcpp/Core/Context.h>\n#include <MicroOcpp/Core/Request.h>\n#include <MicroOcpp/Operations/CustomOperation.h>\n\nusing namespace MicroOcpp;\n\n#define GET_CONFIG_ALL \"[2,\\\"test-msg\\\",\\\"GetVariable\\\",{}]\"\n#define KNOWN_KEY \"__ExistingKey\"\n#define UNKOWN_KEY \"__UnknownKey\"\n#define GET_CONFIG_KNOWN_UNKOWN \"[2,\\\"test-mst\\\",\\\"GetVariable\\\",{\\\"key\\\":[\\\"\" KNOWN_KEY \"\\\",\\\"\" UNKOWN_KEY \"\\\"]}]\"\n\nTEST_CASE( \"Variable\" ) {\n    printf(\"\\nRun %s\\n\",  \"Variable\");\n\n    //clean state\n    auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail);\n    FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n\n    SECTION(\"Basic container operations\"){\n        auto container = std::unique_ptr<VariableContainerOwning>(new VariableContainerOwning());\n\n        //check emptyness\n        REQUIRE( container->size() == 0 );\n\n        //add first config, fetch by index\n        Variable::AttributeTypeSet attrs = Variable::AttributeType::Actual;\n        auto configFirst = makeVariable(Variable::InternalDataType::Int, attrs);\n        configFirst->setName(\"cFirst\");\n        configFirst->setComponentId(\"mComponent\");\n        auto configFirstRaw = configFirst.get();\n        REQUIRE( container->size() == 0 );\n        REQUIRE( container->add(std::move(configFirst)) );\n        REQUIRE( container->size() == 1 );\n        REQUIRE( container->getVariable((size_t) 0) == configFirstRaw);\n\n        //add one config of each type\n        auto cInt = makeVariable(Variable::InternalDataType::Int, attrs);\n        cInt->setName(\"cInt\");\n        cInt->setComponentId(\"mComponent\");\n        auto cBool = makeVariable(Variable::InternalDataType::Bool, attrs);\n        cBool->setName(\"cBool\");\n        cBool->setComponentId(\"mComponent\");\n        auto cBoolRaw = cBool.get();\n        auto cString = makeVariable(Variable::InternalDataType::String, attrs);\n        cString->setName(\"cString\");\n        cString->setComponentId(\"mComponent\");\n\n        container->add(std::move(cInt));\n        container->add(std::move(cBool));\n        container->add(std::move(cString));\n\n        REQUIRE( container->size() == 4 );\n\n        //fetch config by key\n        REQUIRE( container->getVariable(cBoolRaw->getComponentId(), cBoolRaw->getName()) == cBoolRaw);\n    }\n\n    SECTION(\"Persistency on filesystem\") {\n\n        auto container = std::unique_ptr<VariableContainerOwning>(new VariableContainerOwning());\n        container->enablePersistency(filesystem, MO_FILENAME_PREFIX \"persistent1.jsn\");\n\n        //trivial load call\n        REQUIRE( container->load() );\n        REQUIRE( container->size() == 0 );\n\n        //add config, store, load again\n        auto cString = makeVariable(Variable::InternalDataType::String, Variable::AttributeType::Actual);\n        cString->setName(\"cString\");\n        cString->setComponentId(\"mComponent\");\n        cString->setString(\"mValue\");\n        container->add(std::move(cString));\n        REQUIRE( container->size() == 1 );\n\n        REQUIRE( container->commit() ); //store\n\n        container.reset(); //destroy\n\n        //...load again\n        auto container2 = std::unique_ptr<VariableContainerOwning>(new VariableContainerOwning());\n        container2->enablePersistency(filesystem, MO_FILENAME_PREFIX \"persistent1.jsn\");\n        REQUIRE( container2->size() == 0 );\n\n        auto cString2 = makeVariable(Variable::InternalDataType::String, Variable::AttributeType::Actual);\n        cString2->setName(\"cString\");\n        cString2->setComponentId(\"mComponent\");\n        cString2->setString(\"mValue\");\n        container2->add(std::move(cString2));\n        REQUIRE( container2->size() == 1 );\n\n        REQUIRE( container2->load() );\n        REQUIRE( container2->size() == 1 );\n\n        auto cString3 = container2->getVariable(\"mComponent\", \"cString\");\n        REQUIRE( cString3 != nullptr );\n        REQUIRE( !strcmp(cString3->getString(), \"mValue\") );\n    }\n\n    LoopbackConnection loopback; //initialize Context with dummy socket\n    mocpp_set_timer(custom_timer_cb);\n\n    SECTION(\"Variable API\") {\n\n        //declare configs\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n        auto vs = getOcppContext()->getModel().getVariableService();\n        auto cInt = vs->declareVariable<int>(\"mComponent\", \"cInt\", 42);\n        REQUIRE( cInt != nullptr );\n        vs->declareVariable<bool>(\"mComponent\", \"cBool\", true);\n        vs->declareVariable<const char*>(\"mComponent\", \"cString\", \"mValue\");\n\n        //fetch config\n        REQUIRE( vs->declareVariable(\"mComponent\", \"cInt\", -1)->getInt() == 42 );\n\n#if 0\n        //store, destroy, reload\n        REQUIRE( configuration_save() );\n        cInt.reset();\n        configuration_deinit();\n        REQUIRE( getVariablePublic(\"cInt\") == nullptr);\n\n        REQUIRE( configuration_init(filesystem) ); //reload\n\n        //fetch configs (declare with different factory default - should remain at original value)\n        auto cInt2 = vs->declareVariable<int>(\"cInt\", -1);\n        auto cBool2 = vs->declareVariable<bool>(\"cBool\", false);\n        auto cString2 = vs->declareVariable<const char*>(\"cString\", \"no effect\");\n        REQUIRE( configuration_load() ); //load config objects with stored values\n\n        //check load result\n        REQUIRE( cInt2->getInt() == 42 );\n        REQUIRE( cBool2->getBool() == true );\n        REQUIRE( !strcmp(cString2->getString(), \"mValue\") );\n#else\n        auto cInt2 = cInt;\n#endif\n\n        //declare config twice\n        auto cInt3 = vs->declareVariable<int>(\"mComponent\", \"cInt\", -1);\n        REQUIRE( cInt3 == cInt2 );\n\n#if 0\n        //store, destroy, reload\n        REQUIRE( configuration_save() );\n        configuration_deinit();\n        REQUIRE( getVariablePublic(\"cInt\") == nullptr);\n        REQUIRE( configuration_init(filesystem) ); //reload\n        auto cNewType2 = vs->declareVariable<const char*>(\"cInt\", \"no effect\");\n        REQUIRE( configuration_load() );\n        REQUIRE( !strcmp(cNewType2->getString(), \"mValue2\") );\n\n        //get config before declared (container needs to be declared already at this point)\n        auto cString3 = getVariablePublic(\"cString\");\n        REQUIRE( !strcmp(cString3->getString(), \"mValue\") );\n        configuration_deinit();\n\n        //value needs to outlive container\n        configuration_init(filesystem);\n        auto cString4 = vs->declareVariable<const char *>(\"cString2\", \"mValue3\");\n        configuration_deinit();\n        REQUIRE( !strcmp(cString4->getString(), \"mValue3\") );\n\n        FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;});\n#else\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n#endif\n\n        //config accessibility / permissions\n        vs = getOcppContext()->getModel().getVariableService();\n        Variable::Mutability mutability = Variable::Mutability::ReadWrite;\n        bool persistent = false;\n        Variable::AttributeTypeSet attrs = Variable::AttributeType::Actual;\n        bool rebootRequired = false;\n        auto cInt6 = vs->declareVariable<int>(\"mComponent\", \"cInt\", 42, mutability, persistent, attrs, rebootRequired);\n        REQUIRE( cInt6->getMutability() == Variable::Mutability::ReadWrite );\n        REQUIRE( !cInt6->isPersistent() );\n        REQUIRE( !cInt6->isRebootRequired() );\n        REQUIRE( vs->declareVariable<int>(\"mComponent\", \"cInt\", 42) );\n\n        //revoke permissions\n        mutability = Variable::Mutability::ReadOnly;\n        persistent = true;\n        rebootRequired = true;\n        vs->declareVariable<int>(\"mComponent\", \"cInt\", 42, mutability, persistent, attrs, rebootRequired);\n        REQUIRE( cInt6->getMutability() == mutability );\n        REQUIRE( cInt6->isPersistent() );\n        REQUIRE( cInt6->isRebootRequired() );\n\n        //revoked permissions cannot be reverted\n        mutability = Variable::Mutability::ReadWrite;\n        persistent = false;\n        rebootRequired = false;\n        auto cInt7 = vs->declareVariable<int>(\"mComponent\", \"cInt\", 42, mutability, persistent, attrs, rebootRequired);\n        REQUIRE( cInt7->getMutability() == Variable::Mutability::ReadOnly );\n        REQUIRE( cInt6->isPersistent() );\n        REQUIRE( cInt7->isRebootRequired() );\n    }\n\n#if 0\n    SECTION(\"Main lib integration\") {\n\n        //basic lifecycle\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n        REQUIRE( getVariablePublic(\"ConnectionTimeOut\") );\n        REQUIRE( !getVariableContainersPublic().empty() );\n        mocpp_deinitialize();\n        REQUIRE( !getVariablePublic(\"ConnectionTimeOut\") );\n        REQUIRE( getVariableContainersPublic().empty() );\n\n        //modify standard config ConnectionTimeOut. This config is not modified by the main lib during normal initialization / deinitialization\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n        auto config = getVariablePublic(\"ConnectionTimeOut\");\n\n        config->setInt(1234); //update\n        configuration_save(); //write back\n\n        mocpp_deinitialize();\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n        REQUIRE( getVariablePublic(\"ConnectionTimeOut\")->getInt() == 1234 );\n\n        mocpp_deinitialize();\n    }\n#endif\n\n#if 0\n    SECTION(\"GetVariables\") {\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n        loop();\n\n        vs->declareVariable<int>(KNOWN_KEY, 1234, MO_FILENAME_PREFIX \"persistent1.jsn\", false);\n\n        bool checkProcessed = false;\n\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"GetVariables\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    JsonArray configurationKey = payload[\"configurationKey\"];\n\n                    bool foundCustomConfig = false;\n                    bool foundStandardConfig = false;\n                    for (JsonObject keyvalue : configurationKey) {\n                        MO_DBG_DEBUG(\"key %s\", keyvalue[\"key\"] | \"_Undefined\");\n                        if (!strcmp(keyvalue[\"key\"] | \"_Undefined\", KNOWN_KEY)) {\n                            foundCustomConfig = true;\n                            REQUIRE( (keyvalue[\"readonly\"] | true) == false );\n                            REQUIRE( !strcmp(keyvalue[\"value\"] | \"_Undefined\", \"1234\") );\n                        } else if (!strcmp(keyvalue[\"key\"] | \"_Undefined\", \"ConnectionTimeOut\")) {\n                            foundStandardConfig = true;\n                        }\n                    }\n\n                    REQUIRE( foundCustomConfig );\n                    REQUIRE( foundStandardConfig );\n                }\n        )));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n\n        checkProcessed = false;\n\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"GetVariable\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    auto key = payload.createNestedArray(\"key\");\n                    key.add(KNOWN_KEY);\n                    key.add(UNKOWN_KEY);\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    JsonArray configurationKey = payload[\"configurationKey\"];\n\n                    bool foundCustomConfig = false;\n                    for (JsonObject keyvalue : configurationKey) {\n                        if (!strcmp(keyvalue[\"key\"] | \"_Undefined\", KNOWN_KEY)) {\n                            foundCustomConfig = true;\n                            break;\n                        }\n                    }\n                    REQUIRE( foundCustomConfig );\n\n                    JsonArray unknownKey = payload[\"unknownKey\"];\n\n                    bool foundUnkownKey = false;\n                    for (const char *key : unknownKey) {\n                        if (!strcmp(key, UNKOWN_KEY)) {\n                            foundUnkownKey = true;\n                        }\n                    }\n\n                    REQUIRE( foundUnkownKey );\n                }\n        )));\n\n        loop();\n\n        REQUIRE(checkProcessed);\n\n        mocpp_deinitialize();\n    }\n\n    SECTION(\"ChangeVariable\") {\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n        loop();\n\n        vs->declareVariable<int>(KNOWN_KEY, 0, MO_FILENAME_PREFIX \"persistent1.jsn\", false);\n\n        //update existing config\n        bool checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeVariable\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = KNOWN_KEY;\n                    payload[\"value\"] = \"1234\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE(checkProcessed);\n        REQUIRE( getVariablePublic(KNOWN_KEY)->getInt() == 1234 );\n\n        //try to update not existing key\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeVariable\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = UNKOWN_KEY;\n                    payload[\"value\"] = \"no effect\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"NotSupported\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        //try to update config with malformatted value\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeVariable\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = KNOWN_KEY;\n                    payload[\"value\"] = \"not convertible to int\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Rejected\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n\n        //try to update config with value validation\n        //value is valid if it begins with 1\n        registerVariableValidator(KNOWN_KEY, [] (const char *v) {\n            return v[0] == '1';\n        });\n\n        //validation success\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeVariable\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = KNOWN_KEY;\n                    payload[\"value\"] = \"100234\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( getVariablePublic(KNOWN_KEY)->getInt() == 100234 );\n\n        //validation failure\n        checkProcessed = false;\n        getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation(\n                \"ChangeVariable\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\", JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"key\"] = KNOWN_KEY;\n                    payload[\"value\"] = \"4321\";\n                    return doc;},\n                [&checkProcessed] (JsonObject payload) {\n                    //receive conf\n                    checkProcessed = true;\n\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Rejected\") );\n                }\n        )));\n        loop();\n        REQUIRE( checkProcessed );\n        REQUIRE( getVariablePublic(KNOWN_KEY)->getInt() == 100234 ); //keep old value\n\n        mocpp_deinitialize();\n    }\n\n    SECTION(\"Define factory defaults for standard configs\") {\n\n        //set factory default for standard config ConnectionTimeOut\n        configuration_init(filesystem);\n        auto factoryConnectionTimeOut = vs->declareVariable<int>(\"ConnectionTimeOut\", 1234, MO_FILENAME_PREFIX \"factory.jsn\");\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n\n        auto connectionTimeout2 = vs->declareVariable<int>(\"ConnectionTimeOut\", 4321);\n        REQUIRE( connectionTimeout2->getInt() == 1234 );\n        REQUIRE( connectionTimeout2 == factoryConnectionTimeOut );\n\n        configuration_save();\n        mocpp_deinitialize();\n\n        //this time, factory default is not given (will lead to duplicates, should be considered in sanitization)\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n        REQUIRE( getVariablePublic(\"ConnectionTimeOut\")->getInt() != 1234 );\n        mocpp_deinitialize();\n\n        //provide factory default again\n        configuration_init(filesystem);\n        vs->declareVariable<int>(\"ConnectionTimeOut\", 4321, MO_FILENAME_PREFIX \"factory.jsn\");\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n        REQUIRE( getVariablePublic(\"ConnectionTimeOut\")->getInt() == 1234 );\n        mocpp_deinitialize();\n\n    }\n#endif\n\n    SECTION(\"GetVariables request\") {\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n\n        auto vs = getOcppContext()->getModel().getVariableService();\n        auto varString = vs->declareVariable<const char*>(\"mComponent\", \"mString\", \"mValue\");\n        REQUIRE( varString != nullptr );\n        REQUIRE( !strcmp(varString->getString(), \"mValue\") );\n\n        loop();\n\n        MO_MEM_RESET();\n\n        bool checkProcessed = false;\n\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"GetVariables\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\",\n                            JSON_OBJECT_SIZE(1) +\n                            JSON_ARRAY_SIZE(1) +\n                            JSON_OBJECT_SIZE(2) +\n                            JSON_OBJECT_SIZE(1) +\n                            JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    auto getVariableData = payload.createNestedArray(\"getVariableData\");\n                    getVariableData[0][\"component\"][\"name\"] = \"mComponent\";\n                    getVariableData[0][\"variable\"][\"name\"] = \"mString\";\n                    return doc;\n                },\n                [&checkProcessed] (JsonObject payload) {\n                    //process conf\n                    JsonArray getVariableResult = payload[\"getVariableResult\"];\n                    REQUIRE( !strcmp(getVariableResult[0][\"attributeStatus\"] | \"_Undefined\", \"Accepted\") );\n                    REQUIRE( !strcmp(getVariableResult[0][\"component\"][\"name\"] | \"_Undefined\", \"mComponent\") );\n                    REQUIRE( !strcmp(getVariableResult[0][\"variable\"][\"name\"] | \"_Undefined\", \"mString\") );\n                    REQUIRE( !strcmp(getVariableResult[0][\"attributeValue\"] | \"_Undefined\", \"mValue\") );\n                    checkProcessed = true;\n                })));\n        \n        loop();\n\n        REQUIRE( checkProcessed );\n\n        MO_MEM_PRINT_STATS();\n\n    }\n\n    SECTION(\"SetVariables request\") {\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n\n        auto vs = getOcppContext()->getModel().getVariableService();\n        auto varString = vs->declareVariable<const char*>(\"mComponent\", \"mString\", \"\");\n        REQUIRE( varString != nullptr );\n        REQUIRE( !strcmp(varString->getString(), \"\") );\n\n        loop();\n\n        MO_MEM_RESET();\n\n        bool checkProcessed = false;\n\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"SetVariables\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\",\n                            JSON_OBJECT_SIZE(1) +\n                            JSON_ARRAY_SIZE(1) +\n                            JSON_OBJECT_SIZE(3) +\n                            JSON_OBJECT_SIZE(1) +\n                            JSON_OBJECT_SIZE(1));\n                    auto payload = doc->to<JsonObject>();\n                    auto setVariableData = payload.createNestedArray(\"setVariableData\");\n                    setVariableData[0][\"component\"][\"name\"] = \"mComponent\";\n                    setVariableData[0][\"variable\"][\"name\"] = \"mString\";\n                    setVariableData[0][\"attributeValue\"] = \"mValue\";\n                    return doc;\n                },\n                [&checkProcessed] (JsonObject payload) {\n                    //process conf\n                    JsonArray setVariableResult = payload[\"setVariableResult\"];\n                    REQUIRE( !strcmp(setVariableResult[0][\"attributeStatus\"] | \"_Undefined\", \"Accepted\") );\n                    REQUIRE( !strcmp(setVariableResult[0][\"component\"][\"name\"] | \"_Undefined\", \"mComponent\") );\n                    REQUIRE( !strcmp(setVariableResult[0][\"variable\"][\"name\"] | \"_Undefined\", \"mString\") );\n                    checkProcessed = true;\n                })));\n        \n        loop();\n\n        REQUIRE( checkProcessed );\n\n        MO_MEM_PRINT_STATS();\n    }\n\n    SECTION(\"GetBaseReport request\") {\n\n        mocpp_initialize(loopback, ChargerCredentials(), filesystem, false, ProtocolVersion(2,0,1));\n\n        auto vs = getOcppContext()->getModel().getVariableService();\n        auto varString = vs->declareVariable<const char*>(\"mComponent\", \"mString\", \"\");\n        REQUIRE( varString != nullptr );\n        REQUIRE( !strcmp(varString->getString(), \"\") );\n\n        loop();\n\n        MO_MEM_RESET();\n\n        bool checkProcessedNotification = false;\n        Timestamp checkTimestamp;\n\n        getOcppContext()->getOperationRegistry().registerOperation(\"NotifyReport\",\n            [&checkProcessedNotification, &checkTimestamp] () {\n                return new Ocpp16::CustomOperation(\"NotifyReport\",\n                    [ &checkProcessedNotification, &checkTimestamp] (JsonObject payload) {\n                        //process req\n                        checkProcessedNotification = true;\n                        REQUIRE( (payload[\"requestId\"] | -1) == 1);\n                        checkTimestamp.setTime(payload[\"generatedAt\"] | \"_Undefined\");\n                        REQUIRE( (payload[\"seqNo\"] | -1) == 0);\n\n                        bool foundVar = false;\n                        for (auto reportData : payload[\"reportData\"].as<JsonArray>()) {\n                            if (!strcmp(reportData[\"component\"][\"name\"] | \"_Undefined\", \"mComponent\") &&\n                                    !strcmp(reportData[\"variable\"][\"name\"] | \"_Undefined\", \"mString\")) {\n                                foundVar = true;\n                            }\n                        }\n                        REQUIRE( foundVar );\n                    },\n                    [] () {\n                        //create conf\n                        return createEmptyDocument();\n                    });\n            });\n\n        bool checkProcessed = false;\n\n        getOcppContext()->initiateRequest(makeRequest(\n            new Ocpp16::CustomOperation(\"GetBaseReport\",\n                [] () {\n                    //create req\n                    auto doc = makeJsonDoc(\"UnitTests\",\n                            JSON_OBJECT_SIZE(2));\n                    auto payload = doc->to<JsonObject>();\n                    payload[\"requestId\"] = 1;\n                    payload[\"reportBase\"] = \"FullInventory\";\n                    return doc;\n                },\n                [&checkProcessed] (JsonObject payload) {\n                    //process conf\n                    REQUIRE( !strcmp(payload[\"status\"] | \"_Undefined\", \"Accepted\") );\n                    checkProcessed = true;\n                })));\n\n        loop();\n\n        REQUIRE( checkProcessed );\n        REQUIRE( checkProcessedNotification );\n        REQUIRE( std::abs(getOcppContext()->getModel().getClock().now() - checkTimestamp) <= 10 );\n\n        MO_MEM_PRINT_STATS();\n\n    }\n\n    mocpp_deinitialize();\n}\n\n#endif // MO_ENABLE_V201\n"
  },
  {
    "path": "tests/benchmarks/firmware_size/main.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp_c.h>\n#include <MicroOcpp/Model/FirmwareManagement/FirmwareService.h>\n#include <MicroOcpp/Model/Diagnostics/DiagnosticsService.h>\n\nMicroOcpp::LoopbackConnection g_loopback;\n\nvoid setup() {\n\n    ocpp_deinitialize();\n\n#if MO_ENABLE_V201\n    mocpp_initialize(g_loopback, ChargerCredentials::v201(),MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail),true,MicroOcpp::ProtocolVersion(2,0,1));\n#else\n    mocpp_initialize(g_loopback, ChargerCredentials());\n#endif\n\n    ocpp_beginTransaction(\"\");\n    ocpp_beginTransaction_authorized(\"\",\"\");\n    ocpp_endTransaction(\"\",\"\");\n    ocpp_endTransaction_authorized(\"\",\"\");\n    ocpp_isTransactionActive();\n    ocpp_isTransactionRunning();\n    ocpp_getTransactionIdTag();\n    ocpp_getTransaction();\n    ocpp_ocppPermitsCharge();\n    ocpp_getChargePointStatus();\n    ocpp_setConnectorPluggedInput([] () {return false;});\n    ocpp_setEnergyMeterInput([] () {return 0;});\n    ocpp_setPowerMeterInput([] () {return 0.f;});\n    ocpp_setSmartChargingPowerOutput([] (float) {});\n    ocpp_setSmartChargingCurrentOutput([] (float) {});\n    ocpp_setSmartChargingOutput([] (float,float,int) {});\n    ocpp_setEvReadyInput([] () {return false;});\n    ocpp_setEvseReadyInput([] () {return false;});\n    ocpp_addErrorCodeInput([] () {return (const char*)nullptr;});\n    addErrorDataInput([] () {return MicroOcpp::ErrorData(\"\");});\n    ocpp_addMeterValueInputFloat([] () {return 0.f;},\"\",\"\",\"\",\"\");\n    ocpp_setOccupiedInput([] () {return false;});\n    ocpp_setStartTxReadyInput([] () {return false;});\n    ocpp_setStopTxReadyInput([] () {return false;});\n    ocpp_setTxNotificationOutput([] (OCPP_Transaction*, TxNotification) {});\n\n#if MO_ENABLE_CONNECTOR_LOCK\n    ocpp_setOnUnlockConnectorInOut([] () {return UnlockConnectorResult_UnlockFailed;});\n#endif\n\n    isOperative();\n    setOnResetNotify([] (bool) {return false;});\n    setOnResetExecute([] (bool) {return false;});\n    getFirmwareService()->getFirmwareStatus();\n    getDiagnosticsService()->getDiagnosticsStatus();\n\n#if MO_ENABLE_CERT_MGMT\n    setCertificateStore(nullptr);\n#endif\n\n    getOcppContext();\n\n}\n\nvoid loop() {\n    mocpp_loop();\n}\n"
  },
  {
    "path": "tests/benchmarks/firmware_size/platformio.ini",
    "content": "; matth-x/MicroOcpp\n; Copyright Matthias Akstaller 2019 - 2024\n; MIT License\n\n[common]\nplatform = espressif32@6.8.1\nboard = esp-wrover-kit\nframework = arduino\nlib_deps =\n    bblanchon/ArduinoJson@6.20.1\nbuild_flags=\n    -D MO_DBG_LEVEL=MO_DL_NONE ; don't take debug messages into account\n    -D MO_CUSTOM_WS\n\n[env:v16]\nplatform = ${common.platform}\nboard = ${common.board}\nframework = ${common.framework}\nlib_deps = ${common.lib_deps}\nbuild_flags =\n    ${common.build_flags}\n    -D MO_ENABLE_MBEDTLS=1\n    -D MO_ENABLE_CERT_MGMT=1\n    -D MO_ENABLE_RESERVATION=1\n    -D MO_ENABLE_LOCAL_AUTH=1\n    -D MO_REPORT_NOERROR=1\n    -D MO_ENABLE_CONNECTOR_LOCK=1\n\n[env:v201]\nplatform = ${common.platform}\nboard = ${common.board}\nframework = ${common.framework}\nlib_deps = ${common.lib_deps}\nbuild_flags =\n    ${common.build_flags}\n    -D MO_ENABLE_V201=1\n    -D MO_ENABLE_MBEDTLS=1\n    -D MO_ENABLE_CERT_MGMT=1\n"
  },
  {
    "path": "tests/benchmarks/scripts/eval_firmware_size.py",
    "content": "import sys\nimport numpy as np\nimport pandas as pd\n\n# load data\n\nCOLUMN_BINSIZE = 'Binary size (Bytes)'\n\ndef load_compilation_units(fn):\n    df = pd.read_csv(fn, index_col=\"compileunits\").filter(like=\"lib/MicroOcpp/src/MicroOcpp\", axis=0).filter(['Module','v16','v201','vmsize'], axis=1).sort_index()\n    df.index.names = ['Compile Unit']\n    df.index = df.index.map(lambda s: s[len(\"lib/MicroOcpp/src/\"):] if s.startswith(\"lib/MicroOcpp/src/\") else s)\n    df.index = df.index.map(lambda s: s[len(\"MicroOcpp/\"):] if s.startswith(\"MicroOcpp/\") else s)\n    df.rename(columns={'vmsize': COLUMN_BINSIZE}, inplace=True)\n    return df\n    \ncunits_v16 = load_compilation_units('docs/assets/tables/bloaty_v16.csv')\ncunits_v201 = load_compilation_units('docs/assets/tables/bloaty_v201.csv')\n\n# categorize data\n\ndef categorize_table(df):\n\n    df[\"v16\"] = ' '\n    df[\"v201\"] = ' '\n    df[\"Module\"] = ''\n\n    TICK = 'x'\n\n    MODULE_GENERAL = 'General'\n    MODULE_HAL = 'General - Hardware Abstraction Layer'\n    MODULE_RPC = 'General - RPC framework'\n    MODULE_API = 'General - API'\n    MODULE_CORE = 'Core'\n    MODULE_CONFIGURATION = 'Configuration'\n    MODULE_FW_MNGT = 'Firmware Management'\n    MODULE_TRIGGERMESSAGE = 'TriggerMessage'\n    MODULE_SECURITY = 'A - Security'\n    MODULE_PROVISIONING = 'B - Provisioning'\n    MODULE_PROVISIONING_VARS = 'B - Provisioning - Variables'\n    MODULE_AUTHORIZATION = 'C - Authorization'\n    MODULE_LOCALAUTH = 'D - Local Authorization List Management'\n    MODULE_TX = 'E - Transactions'\n    MODULE_REMOTECONTROL = 'F - RemoteControl'\n    MODULE_AVAILABILITY = 'G - Availability'\n    MODULE_RESERVATION = 'H - Reservation'\n    MODULE_METERVALUES = 'J - MeterValues'\n    MODULE_SMARTCHARGING = 'K - SmartCharging'\n    MODULE_CERTS = 'M - Certificate Management'\n\n    df.at['MicroOcpp.cpp', 'v16'] = TICK\n    df.at['MicroOcpp.cpp', 'v201'] = TICK\n    df.at['MicroOcpp.cpp', 'Module'] = MODULE_API\n    df.at['Core/Configuration.cpp', 'v16'] = TICK\n    df.at['Core/Configuration.cpp', 'v201'] = TICK\n    df.at['Core/Configuration.cpp', 'Module'] = MODULE_CONFIGURATION\n    if 'Core/Configuration_c.cpp' in df.index:\n        df.at['Core/Configuration_c.cpp', 'v16'] = TICK\n        df.at['Core/Configuration_c.cpp', 'v201'] = TICK\n        df.at['Core/Configuration_c.cpp', 'Module'] = MODULE_CONFIGURATION\n    df.at['Core/ConfigurationContainer.cpp', 'v16'] = TICK\n    df.at['Core/ConfigurationContainer.cpp', 'v201'] = TICK\n    df.at['Core/ConfigurationContainer.cpp', 'Module'] = MODULE_CONFIGURATION\n    df.at['Core/ConfigurationContainerFlash.cpp', 'v16'] = TICK\n    df.at['Core/ConfigurationContainerFlash.cpp', 'v201'] = TICK\n    df.at['Core/ConfigurationContainerFlash.cpp', 'Module'] = MODULE_CONFIGURATION\n    df.at['Core/ConfigurationKeyValue.cpp', 'v16'] = TICK\n    df.at['Core/ConfigurationKeyValue.cpp', 'v201'] = TICK\n    df.at['Core/ConfigurationKeyValue.cpp', 'Module'] = MODULE_CONFIGURATION\n    df.at['Core/Connection.cpp', 'v16'] = TICK\n    df.at['Core/Connection.cpp', 'v201'] = TICK\n    df.at['Core/Connection.cpp', 'Module'] = MODULE_HAL\n    df.at['Core/Context.cpp', 'v16'] = TICK\n    df.at['Core/Context.cpp', 'v201'] = TICK\n    df.at['Core/Context.cpp', 'Module'] = MODULE_GENERAL\n    df.at['Core/FilesystemAdapter.cpp', 'v16'] = TICK\n    df.at['Core/FilesystemAdapter.cpp', 'v201'] = TICK\n    df.at['Core/FilesystemAdapter.cpp', 'Module'] = MODULE_HAL\n    df.at['Core/FilesystemUtils.cpp', 'v16'] = TICK\n    df.at['Core/FilesystemUtils.cpp', 'v201'] = TICK\n    df.at['Core/FilesystemUtils.cpp', 'Module'] = MODULE_GENERAL\n    df.at['Core/FtpMbedTLS.cpp', 'v16'] = TICK\n    df.at['Core/FtpMbedTLS.cpp', 'v201'] = TICK\n    df.at['Core/FtpMbedTLS.cpp', 'Module'] = MODULE_GENERAL\n    df.at['Core/Memory.cpp', 'v16'] = TICK\n    df.at['Core/Memory.cpp', 'v201'] = TICK\n    df.at['Core/Memory.cpp', 'Module'] = MODULE_GENERAL\n    df.at['Core/Operation.cpp', 'v16'] = TICK\n    df.at['Core/Operation.cpp', 'v201'] = TICK\n    df.at['Core/Operation.cpp', 'Module'] = MODULE_RPC\n    df.at['Core/OperationRegistry.cpp', 'v16'] = TICK\n    df.at['Core/OperationRegistry.cpp', 'v201'] = TICK\n    df.at['Core/OperationRegistry.cpp', 'Module'] = MODULE_RPC\n    df.at['Core/Request.cpp', 'v16'] = TICK\n    df.at['Core/Request.cpp', 'v201'] = TICK\n    df.at['Core/Request.cpp', 'Module'] = MODULE_RPC\n    df.at['Core/RequestQueue.cpp', 'v16'] = TICK\n    df.at['Core/RequestQueue.cpp', 'v201'] = TICK\n    df.at['Core/RequestQueue.cpp', 'Module'] = MODULE_RPC\n    df.at['Core/Time.cpp', 'v16'] = TICK\n    df.at['Core/Time.cpp', 'v201'] = TICK\n    df.at['Core/Time.cpp', 'Module'] = MODULE_GENERAL\n    df.at['Core/UuidUtils.cpp', 'v16'] = TICK\n    df.at['Core/UuidUtils.cpp', 'v201'] = TICK\n    df.at['Core/UuidUtils.cpp', 'Module'] = MODULE_GENERAL\n    if 'Debug.cpp' in df.index:\n        df.at['Debug.cpp', 'v16'] = TICK\n        df.at['Debug.cpp', 'v201'] = TICK\n        df.at['Debug.cpp', 'Module'] = MODULE_HAL\n    if 'Platform.cpp' in df.index:\n        df.at['Platform.cpp', 'v16'] = TICK\n        df.at['Platform.cpp', 'v201'] = TICK\n        df.at['Platform.cpp', 'Module'] = MODULE_HAL\n    df.at['Model/Authorization/AuthorizationData.cpp', 'v16'] = TICK\n    df.at['Model/Authorization/AuthorizationData.cpp', 'Module'] = MODULE_LOCALAUTH\n    df.at['Model/Authorization/AuthorizationList.cpp', 'v16'] = TICK\n    df.at['Model/Authorization/AuthorizationList.cpp', 'Module'] = MODULE_LOCALAUTH\n    df.at['Model/Authorization/AuthorizationService.cpp', 'v16'] = TICK\n    df.at['Model/Authorization/AuthorizationService.cpp', 'Module'] = MODULE_LOCALAUTH\n    if 'Model/Authorization/IdToken.cpp' in df.index:\n        df.at['Model/Authorization/IdToken.cpp', 'v201'] = TICK\n        df.at['Model/Authorization/IdToken.cpp', 'Module'] = MODULE_AUTHORIZATION\n    if 'Model/Availability/AvailabilityService.cpp' in df.index:\n        df.at['Model/Availability/AvailabilityService.cpp', 'v16'] = TICK\n        df.at['Model/Availability/AvailabilityService.cpp', 'v201'] = TICK\n        df.at['Model/Availability/AvailabilityService.cpp', 'Module'] = MODULE_AVAILABILITY\n    df.at['Model/Boot/BootService.cpp', 'v16'] = TICK\n    df.at['Model/Boot/BootService.cpp', 'v201'] = TICK\n    df.at['Model/Boot/BootService.cpp', 'Module'] = MODULE_PROVISIONING\n    df.at['Model/Certificates/Certificate.cpp', 'v16'] = TICK\n    df.at['Model/Certificates/Certificate.cpp', 'v201'] = TICK\n    df.at['Model/Certificates/Certificate.cpp', 'Module'] = MODULE_CERTS\n    df.at['Model/Certificates/CertificateMbedTLS.cpp', 'v16'] = TICK\n    df.at['Model/Certificates/CertificateMbedTLS.cpp', 'v201'] = TICK\n    df.at['Model/Certificates/CertificateMbedTLS.cpp', 'Module'] = MODULE_CERTS\n    if 'Model/Certificates/Certificate_c.cpp' in df.index:\n        df.at['Model/Certificates/Certificate_c.cpp', 'v16'] = TICK\n        df.at['Model/Certificates/Certificate_c.cpp', 'v201'] = TICK\n        df.at['Model/Certificates/Certificate_c.cpp', 'Module'] = MODULE_CERTS\n    df.at['Model/Certificates/CertificateService.cpp', 'v16'] = TICK\n    df.at['Model/Certificates/CertificateService.cpp', 'v201'] = TICK\n    df.at['Model/Certificates/CertificateService.cpp', 'Module'] = MODULE_CERTS\n    df.at['Model/ConnectorBase/Connector.cpp', 'v16'] = TICK\n    df.at['Model/ConnectorBase/Connector.cpp', 'Module'] = MODULE_CORE\n    df.at['Model/ConnectorBase/ConnectorsCommon.cpp', 'v16'] = TICK\n    df.at['Model/ConnectorBase/ConnectorsCommon.cpp', 'Module'] = MODULE_CORE\n    df.at['Model/Diagnostics/DiagnosticsService.cpp', 'v16'] = TICK\n    df.at['Model/Diagnostics/DiagnosticsService.cpp', 'Module'] = MODULE_FW_MNGT\n    df.at['Model/FirmwareManagement/FirmwareService.cpp', 'v16'] = TICK\n    df.at['Model/FirmwareManagement/FirmwareService.cpp', 'Module'] = MODULE_FW_MNGT\n    df.at['Model/Heartbeat/HeartbeatService.cpp', 'v16'] = TICK\n    df.at['Model/Heartbeat/HeartbeatService.cpp', 'v201'] = TICK\n    df.at['Model/Heartbeat/HeartbeatService.cpp', 'Module'] = MODULE_AVAILABILITY\n    df.at['Model/Metering/MeteringConnector.cpp', 'v16'] = TICK\n    df.at['Model/Metering/MeteringConnector.cpp', 'Module'] = MODULE_METERVALUES\n    df.at['Model/Metering/MeteringService.cpp', 'v16'] = TICK\n    df.at['Model/Metering/MeteringService.cpp', 'Module'] = MODULE_METERVALUES\n    df.at['Model/Metering/MeterStore.cpp', 'v16'] = TICK\n    df.at['Model/Metering/MeterStore.cpp', 'Module'] = MODULE_METERVALUES\n    df.at['Model/Metering/MeterValue.cpp', 'v16'] = TICK\n    df.at['Model/Metering/MeterValue.cpp', 'Module'] = MODULE_METERVALUES\n    if 'Model/Metering/MeterValuesV201.cpp' in df.index:\n        df.at['Model/Metering/MeterValuesV201.cpp', 'v201'] = TICK\n        df.at['Model/Metering/MeterValuesV201.cpp', 'Module'] = MODULE_METERVALUES\n    if 'Model/Metering/ReadingContext.cpp' in df.index:\n        df.at['Model/Metering/ReadingContext.cpp', 'v201'] = TICK\n        df.at['Model/Metering/ReadingContext.cpp', 'Module'] = MODULE_METERVALUES\n    df.at['Model/Metering/SampledValue.cpp', 'v16'] = TICK\n    df.at['Model/Metering/SampledValue.cpp', 'Module'] = MODULE_METERVALUES\n    if 'Model/RemoteControl/RemoteControlService.cpp' in df.index:\n        df.at['Model/RemoteControl/RemoteControlService.cpp', 'v201'] = TICK\n        df.at['Model/RemoteControl/RemoteControlService.cpp', 'Module'] = MODULE_REMOTECONTROL\n    df.at['Model/Model.cpp', 'v16'] = TICK\n    df.at['Model/Model.cpp', 'v201'] = TICK\n    df.at['Model/Model.cpp', 'Module'] = MODULE_GENERAL\n    df.at['Model/Reservation/Reservation.cpp', 'v16'] = TICK\n    df.at['Model/Reservation/Reservation.cpp', 'Module'] = MODULE_RESERVATION\n    df.at['Model/Reservation/ReservationService.cpp', 'v16'] = TICK\n    df.at['Model/Reservation/ReservationService.cpp', 'Module'] = MODULE_RESERVATION\n    df.at['Model/Reset/ResetService.cpp', 'v16'] = TICK\n    df.at['Model/Reset/ResetService.cpp', 'v201'] = TICK\n    df.at['Model/Reset/ResetService.cpp', 'Module'] = MODULE_PROVISIONING\n    df.at['Model/SmartCharging/SmartChargingModel.cpp', 'v16'] = TICK\n    df.at['Model/SmartCharging/SmartChargingModel.cpp', 'Module'] = MODULE_SMARTCHARGING\n    df.at['Model/SmartCharging/SmartChargingService.cpp', 'v16'] = TICK\n    df.at['Model/SmartCharging/SmartChargingService.cpp', 'Module'] = MODULE_SMARTCHARGING\n    df.at['Model/Transactions/Transaction.cpp', 'v16'] = TICK\n    df.at['Model/Transactions/Transaction.cpp', 'v201'] = TICK\n    df.at['Model/Transactions/Transaction.cpp', 'Module'] = MODULE_TX\n    df.at['Model/Transactions/TransactionDeserialize.cpp', 'v16'] = TICK\n    df.at['Model/Transactions/TransactionDeserialize.cpp', 'Module'] = MODULE_TX\n    if 'Model/Transactions/TransactionService.cpp' in df.index:\n        df.at['Model/Transactions/TransactionService.cpp', 'v201'] = TICK\n        df.at['Model/Transactions/TransactionService.cpp', 'Module'] = MODULE_TX\n    df.at['Model/Transactions/TransactionStore.cpp', 'v16'] = TICK\n    df.at['Model/Transactions/TransactionStore.cpp', 'Module'] = MODULE_TX\n    if 'Model/Variables/Variable.cpp' in df.index:\n        df.at['Model/Variables/Variable.cpp', 'v201'] = TICK\n        df.at['Model/Variables/Variable.cpp', 'Module'] = MODULE_PROVISIONING_VARS\n    if 'Model/Variables/VariableContainer.cpp' in df.index:\n        df.at['Model/Variables/VariableContainer.cpp', 'v201'] = TICK\n        df.at['Model/Variables/VariableContainer.cpp', 'Module'] = MODULE_PROVISIONING_VARS\n    if 'Model/Variables/VariableService.cpp' in df.index:\n        df.at['Model/Variables/VariableService.cpp', 'v201'] = TICK\n        df.at['Model/Variables/VariableService.cpp', 'Module'] = MODULE_PROVISIONING_VARS\n    df.at['Operations/Authorize.cpp', 'v16'] = TICK\n    df.at['Operations/Authorize.cpp', 'v201'] = TICK\n    df.at['Operations/Authorize.cpp', 'Module'] = MODULE_AUTHORIZATION\n    df.at['Operations/BootNotification.cpp', 'v16'] = TICK\n    df.at['Operations/BootNotification.cpp', 'v201'] = TICK\n    df.at['Operations/BootNotification.cpp', 'Module'] = MODULE_PROVISIONING\n    df.at['Operations/CancelReservation.cpp', 'v16'] = TICK\n    df.at['Operations/CancelReservation.cpp', 'Module'] = MODULE_RESERVATION\n    df.at['Operations/ChangeAvailability.cpp', 'v16'] = TICK\n    df.at['Operations/ChangeAvailability.cpp', 'Module'] = MODULE_AVAILABILITY\n    df.at['Operations/ChangeConfiguration.cpp', 'v16'] = TICK\n    df.at['Operations/ChangeConfiguration.cpp', 'Module'] = MODULE_CONFIGURATION\n    df.at['Operations/ClearCache.cpp', 'v16'] = TICK\n    df.at['Operations/ClearCache.cpp', 'Module'] = MODULE_CORE\n    df.at['Operations/ClearChargingProfile.cpp', 'v16'] = TICK\n    df.at['Operations/ClearChargingProfile.cpp', 'Module'] = MODULE_SMARTCHARGING\n    df.at['Operations/CustomOperation.cpp', 'v16'] = TICK\n    df.at['Operations/CustomOperation.cpp', 'v201'] = TICK\n    df.at['Operations/CustomOperation.cpp', 'Module'] = MODULE_RPC\n    df.at['Operations/DataTransfer.cpp', 'v16'] = TICK\n    df.at['Operations/DataTransfer.cpp', 'Module'] = MODULE_CORE\n    df.at['Operations/DeleteCertificate.cpp', 'v16'] = TICK\n    df.at['Operations/DeleteCertificate.cpp', 'v201'] = TICK\n    df.at['Operations/DeleteCertificate.cpp', 'Module'] = MODULE_CERTS\n    df.at['Operations/DiagnosticsStatusNotification.cpp', 'v16'] = TICK\n    df.at['Operations/DiagnosticsStatusNotification.cpp', 'Module'] = MODULE_FW_MNGT\n    df.at['Operations/FirmwareStatusNotification.cpp', 'v16'] = TICK\n    df.at['Operations/FirmwareStatusNotification.cpp', 'Module'] = MODULE_FW_MNGT\n    if 'Operations/GetBaseReport.cpp' in df.index:\n        df.at['Operations/GetBaseReport.cpp', 'v201'] = TICK\n        df.at['Operations/GetBaseReport.cpp', 'Module'] = MODULE_PROVISIONING_VARS\n    df.at['Operations/GetCompositeSchedule.cpp', 'v16'] = TICK\n    df.at['Operations/GetCompositeSchedule.cpp', 'Module'] = MODULE_SMARTCHARGING\n    df.at['Operations/GetConfiguration.cpp', 'v16'] = TICK\n    df.at['Operations/GetConfiguration.cpp', 'v201'] = TICK\n    df.at['Operations/GetConfiguration.cpp', 'Module'] = MODULE_CONFIGURATION\n    df.at['Operations/GetDiagnostics.cpp', 'v16'] = TICK\n    df.at['Operations/GetDiagnostics.cpp', 'Module'] = MODULE_FW_MNGT\n    df.at['Operations/GetInstalledCertificateIds.cpp', 'v16'] = TICK\n    df.at['Operations/GetInstalledCertificateIds.cpp', 'Module'] = MODULE_SMARTCHARGING\n    df.at['Operations/GetLocalListVersion.cpp', 'v16'] = TICK\n    df.at['Operations/GetLocalListVersion.cpp', 'Module'] = MODULE_LOCALAUTH\n    if 'Operations/GetVariables.cpp' in df.index:\n        df.at['Operations/GetVariables.cpp', 'v201'] = TICK\n        df.at['Operations/GetVariables.cpp', 'Module'] = MODULE_PROVISIONING_VARS\n    df.at['Operations/Heartbeat.cpp', 'v16'] = TICK\n    df.at['Operations/Heartbeat.cpp', 'v201'] = TICK\n    df.at['Operations/Heartbeat.cpp', 'Module'] = MODULE_AVAILABILITY\n    df.at['Operations/InstallCertificate.cpp', 'v16'] = TICK\n    df.at['Operations/InstallCertificate.cpp', 'v201'] = TICK\n    df.at['Operations/InstallCertificate.cpp', 'Module'] = MODULE_CERTS\n    df.at['Operations/MeterValues.cpp', 'v16'] = TICK\n    df.at['Operations/MeterValues.cpp', 'Module'] = MODULE_METERVALUES\n    if 'Operations/NotifyReport.cpp' in df.index:\n        df.at['Operations/NotifyReport.cpp', 'v201'] = TICK\n        df.at['Operations/NotifyReport.cpp', 'Module'] = MODULE_PROVISIONING_VARS\n    df.at['Operations/RemoteStartTransaction.cpp', 'v16'] = TICK\n    df.at['Operations/RemoteStartTransaction.cpp', 'Module'] = MODULE_TX\n    df.at['Operations/RemoteStopTransaction.cpp', 'v16'] = TICK\n    df.at['Operations/RemoteStopTransaction.cpp', 'Module'] = MODULE_TX\n    if 'Operations/RequestStartTransaction.cpp' in df.index:\n        df.at['Operations/RequestStartTransaction.cpp', 'v201'] = TICK\n        df.at['Operations/RequestStartTransaction.cpp', 'Module'] = MODULE_TX\n    if 'Operations/RequestStopTransaction.cpp' in df.index:\n        df.at['Operations/RequestStopTransaction.cpp', 'v201'] = TICK\n        df.at['Operations/RequestStopTransaction.cpp', 'Module'] = MODULE_TX\n    df.at['Operations/ReserveNow.cpp', 'v16'] = TICK\n    df.at['Operations/ReserveNow.cpp', 'Module'] = MODULE_RESERVATION\n    df.at['Operations/Reset.cpp', 'v16'] = TICK\n    df.at['Operations/Reset.cpp', 'v201'] = TICK\n    df.at['Operations/Reset.cpp', 'Module'] = MODULE_PROVISIONING\n    if 'Operations/SecurityEventNotification.cpp' in df.index:\n        df.at['Operations/SecurityEventNotification.cpp', 'v201'] = TICK\n        df.at['Operations/SecurityEventNotification.cpp', 'Module'] = MODULE_SECURITY\n    df.at['Operations/SendLocalList.cpp', 'v16'] = TICK\n    df.at['Operations/SendLocalList.cpp', 'Module'] = MODULE_LOCALAUTH\n    df.at['Operations/SetChargingProfile.cpp', 'v16'] = TICK\n    df.at['Operations/SetChargingProfile.cpp', 'Module'] = MODULE_SMARTCHARGING\n    if 'Operations/SetVariables.cpp' in df.index:\n        df.at['Operations/SetVariables.cpp', 'v201'] = TICK\n        df.at['Operations/SetVariables.cpp', 'Module'] = MODULE_PROVISIONING_VARS\n    df.at['Operations/StartTransaction.cpp', 'v16'] = TICK\n    df.at['Operations/StartTransaction.cpp', 'Module'] = MODULE_TX\n    df.at['Operations/StatusNotification.cpp', 'v16'] = TICK\n    df.at['Operations/StatusNotification.cpp', 'v201'] = TICK\n    df.at['Operations/StatusNotification.cpp', 'Module'] = MODULE_AVAILABILITY\n    df.at['Operations/StopTransaction.cpp', 'v16'] = TICK\n    df.at['Operations/StopTransaction.cpp', 'Module'] = MODULE_TX\n    if 'Operations/TransactionEvent.cpp' in df.index:\n        df.at['Operations/TransactionEvent.cpp', 'v201'] = TICK\n        df.at['Operations/TransactionEvent.cpp', 'Module'] = MODULE_TX\n    df.at['Operations/TriggerMessage.cpp', 'v16'] = TICK\n    df.at['Operations/TriggerMessage.cpp', 'Module'] = MODULE_TRIGGERMESSAGE\n    df.at['Operations/UnlockConnector.cpp', 'v16'] = TICK\n    df.at['Operations/UnlockConnector.cpp', 'Module'] = MODULE_CORE\n    df.at['Operations/UpdateFirmware.cpp', 'v16'] = TICK\n    df.at['Operations/UpdateFirmware.cpp', 'Module'] = MODULE_FW_MNGT\n    if 'MicroOcpp_c.cpp' in df.index:\n        df.at['MicroOcpp_c.cpp', 'v16'] = TICK\n        df.at['MicroOcpp_c.cpp', 'v201'] = TICK\n        df.at['MicroOcpp_c.cpp', 'Module'] = MODULE_API\n\n    print(df)\n\ncategorize_table(cunits_v16)\ncategorize_table(cunits_v201)\n\ncategorize_success = True\n\nif cunits_v16[COLUMN_BINSIZE].isnull().any():\n    print('Error: categorized the following compilation units erroneously (v16):\\n')\n    print(cunits_v16.loc[cunits_v16[COLUMN_BINSIZE].isnull()])\n    categorize_success = False\n\nif cunits_v201[COLUMN_BINSIZE].isnull().any():\n    print('Error: categorized the following compilation units erroneously (v201):\\n')\n    print(cunits_v201.loc[cunits_v201[COLUMN_BINSIZE].isnull()])\n    categorize_success = False\n\nif (cunits_v16['Module'].values == '').sum() > 0:\n    print('Error: did not categorize the following compilation units (v16):\\n')\n    print(cunits_v16.loc[cunits_v16['Module'].values == ''])\n    categorize_success = False\n\nif (cunits_v201['Module'].values == '').sum() > 0:\n    print('Error: did not categorize the following compilation units (v201):\\n')\n    print(cunits_v201.loc[cunits_v201['Module'].values == ''])\n    categorize_success = False\n\nif not categorize_success:\n    sys.exit('\\nError categorizing compilation units')\n\n# store csv with all details\n\nprint('Uncategorized compile units (v16): ', (cunits_v16['Module'].values == '').sum())\nprint('Uncategorized compile units (v201): ', (cunits_v201['Module'].values == '').sum())\n\ncunits_v16.to_csv(\"docs/assets/tables/compile_units_v16.csv\")\ncunits_v201.to_csv(\"docs/assets/tables/compile_units_v201.csv\")\n\n# store csv with size by Module for v16\n\nmodules_v16 = cunits_v16.loc[cunits_v16['v16'].values == 'x'].sort_index()\nmodules_v16_by_module = modules_v16[['Module', COLUMN_BINSIZE]].groupby('Module').sum()\nmodules_v16_by_module.loc['**Total**'] = [modules_v16_by_module[COLUMN_BINSIZE].sum()]\n\nprint(modules_v16_by_module)\n\nmodules_v16_by_module.to_csv('docs/assets/tables/modules_v16.csv')\n\n# store csv with size by Module for v201\n\nmodules_v201 = cunits_v201.loc[cunits_v201['v201'].values == 'x'].sort_index()\nmodules_v201_by_module = modules_v201[['Module', COLUMN_BINSIZE]].groupby('Module').sum()\nmodules_v201_by_module.loc['**Total**'] = [modules_v201_by_module[COLUMN_BINSIZE].sum()]\n\nprint(modules_v201_by_module)\n\nmodules_v201_by_module.to_csv('docs/assets/tables/modules_v201.csv')\n"
  },
  {
    "path": "tests/benchmarks/scripts/measure_heap.py",
    "content": "import os\nimport sys\nimport requests\nimport paramiko\nimport base64\nimport traceback\nimport io\nimport json\nimport time\nimport pandas as pd\n\n\nrequests.packages.urllib3.disable_warnings() # avoid the URL to be printed to console\n\n# Test case selection (commented out a few to simplify testing for now)\ntestcase_name_list = [\n    'TC_B_06_CS',\n    'TC_B_07_CS',\n    'TC_B_09_CS',\n    'TC_B_10_CS',\n    'TC_B_11_CS',\n    'TC_B_12_CS',\n    'TC_B_13_CS',\n    'TC_B_32_CS',\n    'TC_B_34_CS',\n    'TC_B_35_CS',\n    'TC_B_36_CS',\n    'TC_B_37_CS',\n    'TC_B_39_CS',\n    'TC_C_02_CS',\n    'TC_C_04_CS',\n    'TC_C_06_CS',\n    'TC_C_07_CS',\n    'TC_C_49_CS',\n    'TC_E_01_CS',\n    'TC_E_02_CS',\n    'TC_E_03_CS',\n    'TC_E_04_CS',\n    'TC_E_05_CS',\n    'TC_E_06_CS',\n    'TC_E_07_CS',\n    'TC_E_09_CS',\n    'TC_E_13_CS',\n    'TC_E_15_CS',\n    'TC_E_17_CS',\n    'TC_E_20_CS',\n    'TC_E_21_CS',\n    'TC_E_24_CS',\n    'TC_E_25_CS',\n    'TC_E_28_CS',\n    'TC_E_29_CS',\n    'TC_E_30_CS',\n    'TC_E_31_CS',\n    'TC_E_32_CS',\n    'TC_E_33_CS',\n    'TC_E_34_CS',\n    'TC_E_35_CS',\n    'TC_E_39_CS',\n    'TC_F_01_CS',\n    'TC_F_02_CS',\n    'TC_F_03_CS',\n    'TC_F_04_CS',\n    'TC_F_05_CS',\n    'TC_F_06_CS',\n    'TC_F_07_CS',\n    'TC_F_08_CS',\n    'TC_F_09_CS',\n    'TC_F_10_CS',\n    'TC_F_11_CS',\n    'TC_F_12_CS',\n    'TC_F_13_CS',\n    'TC_F_14_CS',\n    'TC_F_20_CS',\n    'TC_F_23_CS',\n    'TC_F_24_CS',\n    'TC_F_26_CS',\n    'TC_F_27_CS',\n    'TC_G_01_CS',\n    'TC_G_02_CS',\n    'TC_G_03_CS',\n    'TC_G_04_CS',\n    'TC_G_05_CS',\n    'TC_G_06_CS',\n    'TC_G_07_CS',\n    'TC_G_08_CS',\n    'TC_G_09_CS',\n    'TC_G_10_CS',\n    'TC_G_11_CS',\n    'TC_G_12_CS',\n    'TC_G_13_CS',\n    'TC_G_14_CS',\n    'TC_G_15_CS',\n    'TC_G_16_CS',\n    'TC_G_17_CS',\n    'TC_J_07_CS',\n    'TC_J_08_CS',\n    'TC_J_09_CS',\n    'TC_J_10_CS',\n]\n\n# Result data set\ndf = pd.DataFrame(columns=['FN_BLOCK', 'Testcase', 'Pass', 'Heap usage (Bytes)'])\ndf.index.names = ['TC_ID']\n\nmax_memory_total = 0\nmin_memory_base = 1000 * 1000 * 1000\n\ndef connect_ssh():\n\n    if not os.path.isfile(os.path.join('tests', 'benchmarks', 'scripts', 'id_ed25519')):\n        file = open(os.path.join('tests', 'benchmarks', 'scripts', 'id_ed25519'), 'w')\n        file.write(os.environ['SSH_LOCAL_PRIV'])\n        file.close()\n        print('SSH ID written to file')\n\n    client = paramiko.SSHClient()\n    client.get_host_keys().add('cicd.micro-ocpp.com', 'ssh-ed25519', paramiko.pkey.PKey.from_type_string('ssh-ed25519', base64.b64decode(os.environ['SSH_HOST_PUB'])))\n    client.connect('cicd.micro-ocpp.com', username='ocpp', key_filename=os.path.join('tests', 'benchmarks', 'scripts', 'id_ed25519'), look_for_keys=False)\n    return client\n\ndef close_ssh(client: paramiko.SSHClient):\n\n    client.close()\n\ndef deploy_simulator():\n\n    print('Deploy Simulator')\n\n    client = connect_ssh()\n\n    print('   - stop Simulator, if still running')\n    stdin, stdout, stderr = client.exec_command('killall -s SIGINT mo_simulator')\n\n    print('   - clean previous deployment')\n    stdin, stdout, stderr = client.exec_command('rm -rf ' + os.path.join('MicroOcppSimulator'))\n\n    print('   - init folder structure')\n    sftp = client.open_sftp()\n    sftp.mkdir(os.path.join('MicroOcppSimulator'))\n    sftp.mkdir(os.path.join('MicroOcppSimulator', 'build'))\n    sftp.mkdir(os.path.join('MicroOcppSimulator', 'public'))\n    sftp.mkdir(os.path.join('MicroOcppSimulator', 'mo_store'))\n\n    print('   - upload files')\n    sftp.put(  os.path.join('MicroOcppSimulator', 'build', 'mo_simulator'),\n               os.path.join('MicroOcppSimulator', 'build', 'mo_simulator'))\n    sftp.chmod(os.path.join('MicroOcppSimulator', 'build', 'mo_simulator'), 0O777)\n    sftp.put(  os.path.join('MicroOcppSimulator', 'public', 'bundle.html.gz'),\n               os.path.join('MicroOcppSimulator', 'public', 'bundle.html.gz'))\n    sftp.close()\n    close_ssh(client)\n    print('   - done')\n\ndef cleanup_simulator():\n\n    print('Clean up Simulator')\n\n    client = connect_ssh()\n\n    print('   - stop Simulator, if still running')\n    stdin, stdout, stderr = client.exec_command('killall -s SIGINT mo_simulator')\n\n    print('   - clean deployment')\n    stdin, stdout, stderr = client.exec_command('rm -rf ' + os.path.join('MicroOcppSimulator'))\n\n    close_ssh(client)\n    print('   - done')\n\ndef setup_simulator():\n\n    print('Setup Simulator')\n\n    client = connect_ssh()\n\n    print('   - stop Simulator, if still running')\n    stdin, stdout, stderr = client.exec_command('killall -s SIGINT mo_simulator')\n\n    print('   - clean state')\n    stdin, stdout, stderr = client.exec_command('rm -rf ' + os.path.join('MicroOcppSimulator', 'mo_store', '*'))\n\n    print('   - upload credentials')\n    sftp = client.open_sftp()\n    sftp.putfo(io.StringIO(os.environ['MO_SIM_CONFIG']),     os.path.join('MicroOcppSimulator', 'mo_store', 'simulator.jsn'))\n    sftp.putfo(io.StringIO(os.environ['MO_SIM_OCPP_SERVER']),os.path.join('MicroOcppSimulator', 'mo_store', 'ws-conn-v201.jsn'))\n    sftp.putfo(io.StringIO(os.environ['MO_SIM_API_CERT']),   os.path.join('MicroOcppSimulator', 'mo_store', 'api_cert.pem'))\n    sftp.putfo(io.StringIO(os.environ['MO_SIM_API_KEY']),    os.path.join('MicroOcppSimulator', 'mo_store', 'api_key.pem'))\n    sftp.putfo(io.StringIO(os.environ['MO_SIM_API_CONFIG']), os.path.join('MicroOcppSimulator', 'mo_store', 'api.jsn'))\n    sftp.close()\n\n    print('   - start Simulator')\n\n    stdin, stdout, stderr = client.exec_command('mkdir -p logs && cd ' + os.path.join('MicroOcppSimulator') + ' && ./build/mo_simulator > ~/logs/sim_\"$(date +%Y-%m-%d_%H-%M-%S.log)\"')\n    close_ssh(client)\n\n    print('   - done')\n\ndef run_measurements():\n    \n    global max_memory_total\n    global min_memory_base\n    \n    print(\"Fetch TCs from Test Driver\")\n\n    response = requests.get(os.environ['TEST_DRIVER_URL'] + '/ocpp2.0.1/CS/testcases/' + os.environ['TEST_DRIVER_CONFIG'], \n                            headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']},\n                            verify=False)\n\n    #print(json.dumps(response.json(), indent=4))\n\n    testcases = []\n\n    for i in response.json()['data']['testcasesData']:\n        for j in i['data']:\n            is_core = False\n            for k in j['certification_profiles']:\n                if k == 'Core':\n                    is_core = True\n                    break\n\n            select = False\n            for k in testcase_name_list:\n                if j['testcase_name'] == k:\n                    select = True\n                    break\n\n            if select:\n                print(i['header'] + ' --- ' + j['functional_block'] + ' --- ' + j['description'])\n                testcases.append(j)\n\n    deploy_simulator()\n\n    print('Get Simulator base memory data')\n    setup_simulator()\n\n    response = requests.post('https://cicd.micro-ocpp.com:8443/api/memory/reset', \n                             auth=(json.loads(os.environ['MO_SIM_API_CONFIG'])['user'],\n                                   json.loads(os.environ['MO_SIM_API_CONFIG'])['pass']))\n    print(f'Simulator API /memory/reset:\\n > {response.status_code}')\n\n    response = requests.get('https://cicd.micro-ocpp.com:8443/api/memory/info', \n                             auth=(json.loads(os.environ['MO_SIM_API_CONFIG'])['user'],\n                                   json.loads(os.environ['MO_SIM_API_CONFIG'])['pass']))\n    print(f'Simulator API /memory/info:\\n > {response.status_code}, current heap={response.json()[\"total_current\"]}, max heap={response.json()[\"total_max\"]}')\n    base_memory_level = response.json()[\"total_max\"]\n    min_memory_base = min(min_memory_base, response.json()[\"total_max\"])\n\n    print(\"Start Test Driver\")\n\n    response = requests.post(os.environ['TEST_DRIVER_URL'] + '/ocpp2.0.1/CS/session/start/' + os.environ['TEST_DRIVER_CONFIG'], \n                             headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']},\n                             verify=False)\n    print(f'Test Driver /*/*/session/start/*:\\n > {response.status_code}')\n    #print(json.dumps(response.json(), indent=4))\n\n    for testcase in testcases:\n        print('\\nRun ' + testcase['functional_block'] + ' > ' + testcase['description'] + ' (' + testcase['testcase_name'] + ')')\n\n        if testcase['testcase_name'] in df.index:\n            print('Test case already executed - skip')\n            continue\n\n        setup_simulator()\n        time.sleep(1)\n\n        # Check connection\n        simulator_connected = False\n        for i in range(5):\n            response = requests.get(os.environ['TEST_DRIVER_URL'] + '/sut_connection_status', \n                                    headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']},\n                                    verify=False)\n            print(f'Test Driver /sut_connection_status:\\n > {response.status_code}')\n            #print(json.dumps(response.json(), indent=4))\n            if response.status_code == 200:\n                simulator_connected = True\n                break\n            else:\n                print(f'Waiting for the Simulator to connect ({i}) ...')\n                time.sleep(3)\n        \n        if not simulator_connected:\n            print('Simulator could not connect to Test Driver')\n            raise Exception()\n        \n        response = requests.post('https://cicd.micro-ocpp.com:8443/api/memory/reset', \n                             auth=(json.loads(os.environ['MO_SIM_API_CONFIG'])['user'],\n                                   json.loads(os.environ['MO_SIM_API_CONFIG'])['pass']))\n        print(f'Simulator API /memory/reset:\\n > {response.status_code}')\n        \n        test_response = requests.post(os.environ['TEST_DRIVER_URL'] + '/testcases/' + testcase['testcase_name'] + '/execute', \n                                 headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']},\n                                 verify=False)\n        print(f'Test Driver /testcases/{testcase[\"testcase_name\"]}/execute:\\n > {test_response.status_code}')\n        #try:\n        #    print(json.dumps(test_response.json(), indent=4))\n        #except:\n        #    print(' > No JSON')\n\n        sim_response = requests.get('https://cicd.micro-ocpp.com:8443/api/memory/info', \n                             auth=(json.loads(os.environ['MO_SIM_API_CONFIG'])['user'],\n                                   json.loads(os.environ['MO_SIM_API_CONFIG'])['pass']))\n        print(f'Simulator API /memory/info:\\n > {sim_response.status_code}, current heap={sim_response.json()[\"total_current\"]}, max heap={sim_response.json()[\"total_max\"]}')\n\n        df.loc[testcase['testcase_name']] = [testcase['functional_block'], testcase['description'], 'x' if test_response.status_code == 200 and test_response.json()['data'][0]['verdict'] == \"pass\" else '-', str(sim_response.json()[\"total_max\"] - min(base_memory_level, sim_response.json()[\"total_current\"]))]\n\n        max_memory_total = max(max_memory_total, sim_response.json()[\"total_max\"])\n        min_memory_base = min(min_memory_base, sim_response.json()[\"total_current\"])\n\n    print(\"Stop Test Driver\")\n    \n    response = requests.post(os.environ['TEST_DRIVER_URL'] + '/session/stop', \n                             headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']},\n                             verify=False)\n    print(f'Test Driver /session/stop:\\n > {response.status_code}')\n    #print(json.dumps(response.json(), indent=4))\n\n    cleanup_simulator()\n\n    print('Store test results')\n\n    # Add some meta information\n    max_memory = 0\n    for index, row in df.iterrows():\n        memory = row['Heap usage (Bytes)']\n        if memory.isdigit():\n            max_memory = max(max_memory, int(memory))\n\n    functional_blocks = set()\n    for index, row in df.iterrows():\n        functional_blocks.add(row['FN_BLOCK'])\n\n    print(functional_blocks)\n\n    for i in functional_blocks:\n        df.loc[f'TC_{i[0]}'] = [i, f'**{i}**', ' ', ' ']\n\n    df.loc['|MO_SIM_000'] = ['-', '**Simulator stats**', ' ', ' ']\n    df.loc['|MO_SIM_010'] = ['-', 'Base memory occupation', ' ', str(min_memory_base)]\n    df.loc['|MO_SIM_020'] = ['-', 'Test case maximum', ' ', str(max_memory)]\n    df.loc['|MO_SIM_030'] = ['-', 'Total memory maximum', ' ', str(max_memory_total)]\n\n    df.sort_index(inplace=True)\n    \n    print(df)\n\n    df.to_csv('docs/assets/tables/heap_v201.csv',index=False,columns=['Testcase','Pass','Heap usage (Bytes)'])\n\n    print('Stored test results to CSV')\n\ndef run_measurements_and_retry():\n\n    if (    'TEST_DRIVER_URL'    not in os.environ or\n            'TEST_DRIVER_CONFIG' not in os.environ or\n            'TEST_DRIVER_KEY'    not in os.environ or\n            'MO_SIM_CONFIG'      not in os.environ or\n            'MO_SIM_OCPP_SERVER' not in os.environ or\n            'MO_SIM_API_CERT'    not in os.environ or\n            'MO_SIM_API_KEY'     not in os.environ or\n            'MO_SIM_API_CONFIG'  not in os.environ or\n            'SSH_LOCAL_PRIV'     not in os.environ or\n            'SSH_HOST_PUB'       not in os.environ):\n        sys.exit('\\nCould not read environment variables')\n\n    n_tries = 3\n\n    for i in range(n_tries):\n\n        try:\n            run_measurements()\n            print('\\n **Test cases executed successfully**')\n            break\n        except:\n            print(f'Error detected ({i+1})')\n\n            traceback.print_exc()\n\n            print(\"Stop Test Driver\")    \n            response = requests.post(os.environ['TEST_DRIVER_URL'] + '/session/stop', \n                                    headers={'Authorization': 'Bearer ' + os.environ['TEST_DRIVER_KEY']},\n                                    verify=False)\n            print(f'Test Driver /session/stop:\\n > {response.status_code}')\n            #print(json.dumps(response.json(), indent=4))\n\n            cleanup_simulator()\n\n            if i + 1 < n_tries:\n                print('Retry test cases')\n            else:\n                print('\\n **Test case execution aborted**')\n                sys.exit('\\nError running test cases')\n\nrun_measurements_and_retry()\n"
  },
  {
    "path": "tests/catch2/catch.hpp",
    "content": "/*\n *  Catch v2.13.9\n *  Generated: 2022-04-12 22:37:23.260201\n *  ----------------------------------------------------------\n *  This file has been merged from multiple headers. Please don't edit it directly\n *  Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved.\n *\n *  Distributed under the Boost Software License, Version 1.0. (See accompanying\n *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n */\n#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n// start catch.hpp\n\n\n#define CATCH_VERSION_MAJOR 2\n#define CATCH_VERSION_MINOR 13\n#define CATCH_VERSION_PATCH 9\n\n#ifdef __clang__\n#    pragma clang system_header\n#elif defined __GNUC__\n#    pragma GCC system_header\n#endif\n\n// start catch_suppress_warnings.h\n\n#ifdef __clang__\n#   ifdef __ICC // icpc defines the __clang__ macro\n#       pragma warning(push)\n#       pragma warning(disable: 161 1682)\n#   else // __ICC\n#       pragma clang diagnostic push\n#       pragma clang diagnostic ignored \"-Wpadded\"\n#       pragma clang diagnostic ignored \"-Wswitch-enum\"\n#       pragma clang diagnostic ignored \"-Wcovered-switch-default\"\n#    endif\n#elif defined __GNUC__\n     // Because REQUIREs trigger GCC's -Wparentheses, and because still\n     // supported version of g++ have only buggy support for _Pragmas,\n     // Wparentheses have to be suppressed globally.\n#    pragma GCC diagnostic ignored \"-Wparentheses\" // See #674 for details\n\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wunused-variable\"\n#    pragma GCC diagnostic ignored \"-Wpadded\"\n#endif\n// end catch_suppress_warnings.h\n#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)\n#  define CATCH_IMPL\n#  define CATCH_CONFIG_ALL_PARTS\n#endif\n\n// In the impl file, we want to have access to all parts of the headers\n// Can also be used to sanely support PCHs\n#if defined(CATCH_CONFIG_ALL_PARTS)\n#  define CATCH_CONFIG_EXTERNAL_INTERFACES\n#  if defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#    undef CATCH_CONFIG_DISABLE_MATCHERS\n#  endif\n#  if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)\n#    define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER\n#  endif\n#endif\n\n#if !defined(CATCH_CONFIG_IMPL_ONLY)\n// start catch_platform.h\n\n// See e.g.:\n// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html\n#ifdef __APPLE__\n#  include <TargetConditionals.h>\n#  if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \\\n      (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1)\n#    define CATCH_PLATFORM_MAC\n#  elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1)\n#    define CATCH_PLATFORM_IPHONE\n#  endif\n\n#elif defined(linux) || defined(__linux) || defined(__linux__)\n#  define CATCH_PLATFORM_LINUX\n\n#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)\n#  define CATCH_PLATFORM_WINDOWS\n#endif\n\n// end catch_platform.h\n\n#ifdef CATCH_IMPL\n#  ifndef CLARA_CONFIG_MAIN\n#    define CLARA_CONFIG_MAIN_NOT_DEFINED\n#    define CLARA_CONFIG_MAIN\n#  endif\n#endif\n\n// start catch_user_interfaces.h\n\nnamespace Catch {\n    unsigned int rngSeed();\n}\n\n// end catch_user_interfaces.h\n// start catch_tag_alias_autoregistrar.h\n\n// start catch_common.h\n\n// start catch_compiler_capabilities.h\n\n// Detect a number of compiler features - by compiler\n// The following features are defined:\n//\n// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?\n// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported?\n// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported?\n// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled?\n// ****************\n// Note to maintainers: if new toggles are added please document them\n// in configuration.md, too\n// ****************\n\n// In general each macro has a _NO_<feature name> form\n// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature.\n// Many features, at point of detection, define an _INTERNAL_ macro, so they\n// can be combined, en-mass, with the _NO_ forms later.\n\n#ifdef __cplusplus\n\n#  if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)\n#    define CATCH_CPP14_OR_GREATER\n#  endif\n\n#  if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)\n#    define CATCH_CPP17_OR_GREATER\n#  endif\n\n#endif\n\n// Only GCC compiler should be used in this block, so other compilers trying to\n// mask themselves as GCC should be ignored.\n#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__)\n#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( \"GCC diagnostic push\" )\n#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  _Pragma( \"GCC diagnostic pop\" )\n\n#    define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__)\n\n#endif\n\n#if defined(__clang__)\n\n#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( \"clang diagnostic push\" )\n#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  _Pragma( \"clang diagnostic pop\" )\n\n// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug\n// which results in calls to destructors being emitted for each temporary,\n// without a matching initialization. In practice, this can result in something\n// like `std::string::~string` being called on an uninitialized value.\n//\n// For example, this code will likely segfault under IBM XL:\n// ```\n// REQUIRE(std::string(\"12\") + \"34\" == \"1234\")\n// ```\n//\n// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented.\n#  if !defined(__ibmxl__) && !defined(__CUDACC__)\n#    define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */\n#  endif\n\n#    define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wexit-time-destructors\\\"\" ) \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wglobal-constructors\\\"\")\n\n#    define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wparentheses\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wunused-variable\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wgnu-zero-variadic-macro-arguments\\\"\" )\n\n#    define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n         _Pragma( \"clang diagnostic ignored \\\"-Wunused-template\\\"\" )\n\n#endif // __clang__\n\n////////////////////////////////////////////////////////////////////////////////\n// Assume that non-Windows platforms support posix signals by default\n#if !defined(CATCH_PLATFORM_WINDOWS)\n    #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// We know some environments not to support full POSIX signals\n#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__)\n    #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS\n#endif\n\n#ifdef __OS400__\n#       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS\n#       define CATCH_CONFIG_COLOUR_NONE\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Android somehow still does not support std::to_string\n#if defined(__ANDROID__)\n#    define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING\n#    define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Not all Windows environments support SEH properly\n#if defined(__MINGW32__)\n#    define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// PS4\n#if defined(__ORBIS__)\n#    define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// Cygwin\n#ifdef __CYGWIN__\n\n// Required for some versions of Cygwin to declare gettimeofday\n// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin\n#   define _BSD_SOURCE\n// some versions of cygwin (most) do not support std::to_string. Use the libstd check.\n// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813\n# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \\\n           && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF))\n\n#    define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING\n\n# endif\n#endif // __CYGWIN__\n\n////////////////////////////////////////////////////////////////////////////////\n// Visual C++\n#if defined(_MSC_VER)\n\n// Universal Windows platform does not support SEH\n// Or console colours (or console at all...)\n#  if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)\n#    define CATCH_CONFIG_COLOUR_NONE\n#  else\n#    define CATCH_INTERNAL_CONFIG_WINDOWS_SEH\n#  endif\n\n#  if !defined(__clang__) // Handle Clang masquerading for msvc\n\n// MSVC traditional preprocessor needs some workaround for __VA_ARGS__\n// _MSVC_TRADITIONAL == 0 means new conformant preprocessor\n// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor\n#    if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL)\n#      define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#    endif // MSVC_TRADITIONAL\n\n// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop`\n#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) )\n#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  __pragma( warning(pop) )\n#  endif // __clang__\n\n#endif // _MSC_VER\n\n#if defined(_REENTRANT) || defined(_MSC_VER)\n// Enable async processing, as -pthread is specified or no additional linking is required\n# define CATCH_INTERNAL_CONFIG_USE_ASYNC\n#endif // _MSC_VER\n\n////////////////////////////////////////////////////////////////////////////////\n// Check if we are compiled with -fno-exceptions or equivalent\n#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)\n#  define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n// DJGPP\n#ifdef __DJGPP__\n#  define CATCH_INTERNAL_CONFIG_NO_WCHAR\n#endif // __DJGPP__\n\n////////////////////////////////////////////////////////////////////////////////\n// Embarcadero C++Build\n#if defined(__BORLANDC__)\n    #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n\n// Use of __COUNTER__ is suppressed during code analysis in\n// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly\n// handled by it.\n// Otherwise all supported compilers support COUNTER macro,\n// but user still might want to turn it off\n#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L )\n    #define CATCH_INTERNAL_CONFIG_COUNTER\n#endif\n\n////////////////////////////////////////////////////////////////////////////////\n\n// RTX is a special version of Windows that is real time.\n// This means that it is detected as Windows, but does not provide\n// the same set of capabilities as real Windows does.\n#if defined(UNDER_RTSS) || defined(RTX64_BUILD)\n    #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH\n    #define CATCH_INTERNAL_CONFIG_NO_ASYNC\n    #define CATCH_CONFIG_COLOUR_NONE\n#endif\n\n#if !defined(_GLIBCXX_USE_C99_MATH_TR1)\n#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER\n#endif\n\n// Various stdlib support checks that require __has_include\n#if defined(__has_include)\n  // Check if string_view is available and usable\n  #if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER)\n  #    define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW\n  #endif\n\n  // Check if optional is available and usable\n  #  if __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER)\n  #    define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL\n  #  endif // __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER)\n\n  // Check if byte is available and usable\n  #  if __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER)\n  #    include <cstddef>\n  #    if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0)\n  #      define CATCH_INTERNAL_CONFIG_CPP17_BYTE\n  #    endif\n  #  endif // __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER)\n\n  // Check if variant is available and usable\n  #  if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)\n  #    if defined(__clang__) && (__clang_major__ < 8)\n         // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852\n         // fix should be in clang 8, workaround in libstdc++ 8.2\n  #      include <ciso646>\n  #      if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)\n  #        define CATCH_CONFIG_NO_CPP17_VARIANT\n  #      else\n  #        define CATCH_INTERNAL_CONFIG_CPP17_VARIANT\n  #      endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)\n  #    else\n  #      define CATCH_INTERNAL_CONFIG_CPP17_VARIANT\n  #    endif // defined(__clang__) && (__clang_major__ < 8)\n  #  endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER)\n#endif // defined(__has_include)\n\n#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER)\n#   define CATCH_CONFIG_COUNTER\n#endif\n#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH)\n#   define CATCH_CONFIG_WINDOWS_SEH\n#endif\n// This is set by default, because we assume that unix compilers are posix-signal-compatible by default.\n#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)\n#   define CATCH_CONFIG_POSIX_SIGNALS\n#endif\n// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions.\n#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR)\n#   define CATCH_CONFIG_WCHAR\n#endif\n\n#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING)\n#    define CATCH_CONFIG_CPP11_TO_STRING\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL)\n#  define CATCH_CONFIG_CPP17_OPTIONAL\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW)\n#  define CATCH_CONFIG_CPP17_STRING_VIEW\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT)\n#  define CATCH_CONFIG_CPP17_VARIANT\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE)\n#  define CATCH_CONFIG_CPP17_BYTE\n#endif\n\n#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)\n#  define CATCH_INTERNAL_CONFIG_NEW_CAPTURE\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE)\n#  define CATCH_CONFIG_NEW_CAPTURE\n#endif\n\n#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n#  define CATCH_CONFIG_DISABLE_EXCEPTIONS\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN)\n#  define CATCH_CONFIG_POLYFILL_ISNAN\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC)  && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC)\n#  define CATCH_CONFIG_USE_ASYNC\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE)\n#  define CATCH_CONFIG_ANDROID_LOGWRITE\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)\n#  define CATCH_CONFIG_GLOBAL_NEXTAFTER\n#endif\n\n// Even if we do not think the compiler has that warning, we still have\n// to provide a macro that can be used by the code.\n#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION)\n#   define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION\n#endif\n#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION)\n#   define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS\n#endif\n#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS\n#endif\n\n// The goal of this macro is to avoid evaluation of the arguments, but\n// still have the compiler warn on problems inside...\n#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN)\n#   define CATCH_INTERNAL_IGNORE_BUT_WARN(...)\n#endif\n\n#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10)\n#   undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS\n#elif defined(__clang__) && (__clang_major__ < 5)\n#   undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS\n#endif\n\n#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS)\n#   define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS\n#endif\n\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n#define CATCH_TRY if ((true))\n#define CATCH_CATCH_ALL if ((false))\n#define CATCH_CATCH_ANON(type) if ((false))\n#else\n#define CATCH_TRY try\n#define CATCH_CATCH_ALL catch (...)\n#define CATCH_CATCH_ANON(type) catch (type)\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR)\n#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#endif\n\n// end catch_compiler_capabilities.h\n#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line\n#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )\n#ifdef CATCH_CONFIG_COUNTER\n#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )\n#else\n#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )\n#endif\n\n#include <iosfwd>\n#include <string>\n#include <cstdint>\n\n// We need a dummy global operator<< so we can bring it into Catch namespace later\nstruct Catch_global_namespace_dummy {};\nstd::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy);\n\nnamespace Catch {\n\n    struct CaseSensitive { enum Choice {\n        Yes,\n        No\n    }; };\n\n    class NonCopyable {\n        NonCopyable( NonCopyable const& )              = delete;\n        NonCopyable( NonCopyable && )                  = delete;\n        NonCopyable& operator = ( NonCopyable const& ) = delete;\n        NonCopyable& operator = ( NonCopyable && )     = delete;\n\n    protected:\n        NonCopyable();\n        virtual ~NonCopyable();\n    };\n\n    struct SourceLineInfo {\n\n        SourceLineInfo() = delete;\n        SourceLineInfo( char const* _file, std::size_t _line ) noexcept\n        :   file( _file ),\n            line( _line )\n        {}\n\n        SourceLineInfo( SourceLineInfo const& other )            = default;\n        SourceLineInfo& operator = ( SourceLineInfo const& )     = default;\n        SourceLineInfo( SourceLineInfo&& )              noexcept = default;\n        SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default;\n\n        bool empty() const noexcept { return file[0] == '\\0'; }\n        bool operator == ( SourceLineInfo const& other ) const noexcept;\n        bool operator < ( SourceLineInfo const& other ) const noexcept;\n\n        char const* file;\n        std::size_t line;\n    };\n\n    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info );\n\n    // Bring in operator<< from global namespace into Catch namespace\n    // This is necessary because the overload of operator<< above makes\n    // lookup stop at namespace Catch\n    using ::operator<<;\n\n    // Use this in variadic streaming macros to allow\n    //    >> +StreamEndStop\n    // as well as\n    //    >> stuff +StreamEndStop\n    struct StreamEndStop {\n        std::string operator+() const;\n    };\n    template<typename T>\n    T const& operator + ( T const& value, StreamEndStop ) {\n        return value;\n    }\n}\n\n#define CATCH_INTERNAL_LINEINFO \\\n    ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )\n\n// end catch_common.h\nnamespace Catch {\n\n    struct RegistrarForTagAliases {\n        RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );\n    };\n\n} // end namespace Catch\n\n#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n    namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n// end catch_tag_alias_autoregistrar.h\n// start catch_test_registry.h\n\n// start catch_interfaces_testcase.h\n\n#include <vector>\n\nnamespace Catch {\n\n    class TestSpec;\n\n    struct ITestInvoker {\n        virtual void invoke () const = 0;\n        virtual ~ITestInvoker();\n    };\n\n    class TestCase;\n    struct IConfig;\n\n    struct ITestCaseRegistry {\n        virtual ~ITestCaseRegistry();\n        virtual std::vector<TestCase> const& getAllTests() const = 0;\n        virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0;\n    };\n\n    bool isThrowSafe( TestCase const& testCase, IConfig const& config );\n    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );\n    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );\n    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );\n\n}\n\n// end catch_interfaces_testcase.h\n// start catch_stringref.h\n\n#include <cstddef>\n#include <string>\n#include <iosfwd>\n#include <cassert>\n\nnamespace Catch {\n\n    /// A non-owning string class (similar to the forthcoming std::string_view)\n    /// Note that, because a StringRef may be a substring of another string,\n    /// it may not be null terminated.\n    class StringRef {\n    public:\n        using size_type = std::size_t;\n        using const_iterator = const char*;\n\n    private:\n        static constexpr char const* const s_empty = \"\";\n\n        char const* m_start = s_empty;\n        size_type m_size = 0;\n\n    public: // construction\n        constexpr StringRef() noexcept = default;\n\n        StringRef( char const* rawChars ) noexcept;\n\n        constexpr StringRef( char const* rawChars, size_type size ) noexcept\n        :   m_start( rawChars ),\n            m_size( size )\n        {}\n\n        StringRef( std::string const& stdString ) noexcept\n        :   m_start( stdString.c_str() ),\n            m_size( stdString.size() )\n        {}\n\n        explicit operator std::string() const {\n            return std::string(m_start, m_size);\n        }\n\n    public: // operators\n        auto operator == ( StringRef const& other ) const noexcept -> bool;\n        auto operator != (StringRef const& other) const noexcept -> bool {\n            return !(*this == other);\n        }\n\n        auto operator[] ( size_type index ) const noexcept -> char {\n            assert(index < m_size);\n            return m_start[index];\n        }\n\n    public: // named queries\n        constexpr auto empty() const noexcept -> bool {\n            return m_size == 0;\n        }\n        constexpr auto size() const noexcept -> size_type {\n            return m_size;\n        }\n\n        // Returns the current start pointer. If the StringRef is not\n        // null-terminated, throws std::domain_exception\n        auto c_str() const -> char const*;\n\n    public: // substrings and searches\n        // Returns a substring of [start, start + length).\n        // If start + length > size(), then the substring is [start, size()).\n        // If start > size(), then the substring is empty.\n        auto substr( size_type start, size_type length ) const noexcept -> StringRef;\n\n        // Returns the current start pointer. May not be null-terminated.\n        auto data() const noexcept -> char const*;\n\n        constexpr auto isNullTerminated() const noexcept -> bool {\n            return m_start[m_size] == '\\0';\n        }\n\n    public: // iterators\n        constexpr const_iterator begin() const { return m_start; }\n        constexpr const_iterator end() const { return m_start + m_size; }\n    };\n\n    auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&;\n    auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&;\n\n    constexpr auto operator \"\" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef {\n        return StringRef( rawChars, size );\n    }\n} // namespace Catch\n\nconstexpr auto operator \"\" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef {\n    return Catch::StringRef( rawChars, size );\n}\n\n// end catch_stringref.h\n// start catch_preprocessor.hpp\n\n\n#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__\n#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__)))\n\n#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__\n// MSVC needs more evaluations\n#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__)))\n#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__))\n#else\n#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL5(__VA_ARGS__)\n#endif\n\n#define CATCH_REC_END(...)\n#define CATCH_REC_OUT\n\n#define CATCH_EMPTY()\n#define CATCH_DEFER(id) id CATCH_EMPTY()\n\n#define CATCH_REC_GET_END2() 0, CATCH_REC_END\n#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2\n#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1\n#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT\n#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0)\n#define CATCH_REC_NEXT(test, next)  CATCH_REC_NEXT1(CATCH_REC_GET_END test, next)\n\n#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST2(f, x, peek, ...)   f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )\n\n#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...)   f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )\n\n// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results,\n// and passes userdata as the first parameter to each invocation,\n// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c)\n#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0))\n\n#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))\n\n#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param)\n#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__\n#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__\n#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF\n#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__)\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__\n#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param))\n#else\n// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF\n#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__)\n#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__\n#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1)\n#endif\n\n#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__\n#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name)\n\n#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__)\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())\n#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))\n#else\n#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>()))\n#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)))\n#endif\n\n#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\\\n    CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__)\n\n#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0)\n#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1)\n#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2)\n#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3)\n#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4)\n#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5)\n#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6)\n#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7)\n#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8)\n#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9)\n#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10)\n\n#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N\n\n#define INTERNAL_CATCH_TYPE_GEN\\\n    template<typename...> struct TypeList {};\\\n    template<typename...Ts>\\\n    constexpr auto get_wrapper() noexcept -> TypeList<Ts...> { return {}; }\\\n    template<template<typename...> class...> struct TemplateTypeList{};\\\n    template<template<typename...> class...Cs>\\\n    constexpr auto get_wrapper() noexcept -> TemplateTypeList<Cs...> { return {}; }\\\n    template<typename...>\\\n    struct append;\\\n    template<typename...>\\\n    struct rewrap;\\\n    template<template<typename...> class, typename...>\\\n    struct create;\\\n    template<template<typename...> class, typename>\\\n    struct convert;\\\n    \\\n    template<typename T> \\\n    struct append<T> { using type = T; };\\\n    template< template<typename...> class L1, typename...E1, template<typename...> class L2, typename...E2, typename...Rest>\\\n    struct append<L1<E1...>, L2<E2...>, Rest...> { using type = typename append<L1<E1...,E2...>, Rest...>::type; };\\\n    template< template<typename...> class L1, typename...E1, typename...Rest>\\\n    struct append<L1<E1...>, TypeList<mpl_::na>, Rest...> { using type = L1<E1...>; };\\\n    \\\n    template< template<typename...> class Container, template<typename...> class List, typename...elems>\\\n    struct rewrap<TemplateTypeList<Container>, List<elems...>> { using type = TypeList<Container<elems...>>; };\\\n    template< template<typename...> class Container, template<typename...> class List, class...Elems, typename...Elements>\\\n    struct rewrap<TemplateTypeList<Container>, List<Elems...>, Elements...> { using type = typename append<TypeList<Container<Elems...>>, typename rewrap<TemplateTypeList<Container>, Elements...>::type>::type; };\\\n    \\\n    template<template <typename...> class Final, template< typename...> class...Containers, typename...Types>\\\n    struct create<Final, TemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<TemplateTypeList<Containers>, Types...>::type...>::type; };\\\n    template<template <typename...> class Final, template <typename...> class List, typename...Ts>\\\n    struct convert<Final, List<Ts...>> { using type = typename append<Final<>,TypeList<Ts>...>::type; };\n\n#define INTERNAL_CATCH_NTTP_1(signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)> struct Nttp{};\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \\\n    template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...> struct NttpTemplateTypeList{};\\\n    template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Cs>\\\n    constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \\\n    \\\n    template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> { using type = TypeList<Container<__VA_ARGS__>>; };\\\n    template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature), typename...Elements>\\\n    struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>, Elements...> { using type = typename append<TypeList<Container<__VA_ARGS__>>, typename rewrap<NttpTemplateTypeList<Container>, Elements...>::type>::type; };\\\n    template<template <typename...> class Final, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Containers, typename...Types>\\\n    struct create<Final, NttpTemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<NttpTemplateTypeList<Containers>, Types...>::type...>::type; };\n\n#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    static void TestName()\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    static void TestName()\n\n#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    static void TestName()\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature,...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    static void TestName()\n\n#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\\\n    template<typename Type>\\\n    void reg_test(TypeList<Type>, Catch::NameAndTags nameAndTags)\\\n    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Type>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\\\n    }\n\n#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\\\n    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\\\n    }\n\n#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...)\\\n    template<typename Type>\\\n    void reg_test(TypeList<Type>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\\\n    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestName<Type>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\\\n    }\n\n#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\\\n    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\\\n    }\n\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature)\\\n    template<typename TestType> \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<TestType> { \\\n        void test();\\\n    }\n\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \\\n    struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> { \\\n        void test();\\\n    }\n\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature)\\\n    template<typename TestType> \\\n    void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<TestType>::test()\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \\\n    void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define INTERNAL_CATCH_NTTP_0\n#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__),INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_0)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)\n#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)\n#else\n#define INTERNAL_CATCH_NTTP_0(signature)\n#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)( __VA_ARGS__))\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__))\n#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__))\n#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__))\n#endif\n\n// end catch_preprocessor.hpp\n// start catch_meta.hpp\n\n\n#include <type_traits>\n\nnamespace Catch {\n    template<typename T>\n    struct always_false : std::false_type {};\n\n    template <typename> struct true_given : std::true_type {};\n    struct is_callable_tester {\n        template <typename Fun, typename... Args>\n        true_given<decltype(std::declval<Fun>()(std::declval<Args>()...))> static test(int);\n        template <typename...>\n        std::false_type static test(...);\n    };\n\n    template <typename T>\n    struct is_callable;\n\n    template <typename Fun, typename... Args>\n    struct is_callable<Fun(Args...)> : decltype(is_callable_tester::test<Fun, Args...>(0)) {};\n\n#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703\n    // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is\n    // replaced with std::invoke_result here.\n    template <typename Func, typename... U>\n    using FunctionReturnType = std::remove_reference_t<std::remove_cv_t<std::invoke_result_t<Func, U...>>>;\n#else\n    // Keep ::type here because we still support C++11\n    template <typename Func, typename... U>\n    using FunctionReturnType = typename std::remove_reference<typename std::remove_cv<typename std::result_of<Func(U...)>::type>::type>::type;\n#endif\n\n} // namespace Catch\n\nnamespace mpl_{\n    struct na;\n}\n\n// end catch_meta.hpp\nnamespace Catch {\n\ntemplate<typename C>\nclass TestInvokerAsMethod : public ITestInvoker {\n    void (C::*m_testAsMethod)();\npublic:\n    TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {}\n\n    void invoke() const override {\n        C obj;\n        (obj.*m_testAsMethod)();\n    }\n};\n\nauto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*;\n\ntemplate<typename C>\nauto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* {\n    return new(std::nothrow) TestInvokerAsMethod<C>( testAsMethod );\n}\n\nstruct NameAndTags {\n    NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept;\n    StringRef name;\n    StringRef tags;\n};\n\nstruct AutoReg : NonCopyable {\n    AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept;\n    ~AutoReg();\n};\n\n} // end namespace Catch\n\n#if defined(CATCH_CONFIG_DISABLE)\n    #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \\\n        static void TestName()\n    #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \\\n        namespace{                        \\\n            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \\\n                void test();              \\\n            };                            \\\n        }                                 \\\n        void TestName::test()\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( TestName, TestFunc, Name, Tags, Signature, ... )  \\\n        INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... )    \\\n        namespace{                                                                                  \\\n            namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                      \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\\\n        }                                                                                           \\\n        }                                                                                           \\\n        INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n\n    #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ )\n    #else\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \\\n            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) )\n    #endif\n\n    #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ )\n    #else\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \\\n            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) )\n    #endif\n\n    #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ )\n    #else\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \\\n            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) )\n    #endif\n\n    #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ )\n    #else\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \\\n            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )\n    #endif\n#endif\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \\\n        static void TestName(); \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        static void TestName()\n    #define INTERNAL_CATCH_TESTCASE( ... ) \\\n        INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), __VA_ARGS__ )\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, \"&\" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        namespace{ \\\n            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \\\n                void test(); \\\n            }; \\\n            Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \\\n        } \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        void TestName::test()\n    #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \\\n        INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), ClassName, __VA_ARGS__ )\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n    ///////////////////////////////////////////////////////////////////////////////\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, Signature, ... )\\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        INTERNAL_CATCH_DECLARE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature));\\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\\\n            INTERNAL_CATCH_TYPE_GEN\\\n            INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            INTERNAL_CATCH_NTTP_REG_GEN(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            template<typename...Types> \\\n            struct TestName{\\\n                TestName(){\\\n                    int index = 0;                                    \\\n                    constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\\\n                    using expander = int[];\\\n                    (void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \\\n                }\\\n            };\\\n            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\\\n            TestName<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\\\n            return 0;\\\n        }();\\\n        }\\\n        }\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \\\n        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename TestType, __VA_ARGS__ ) )\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \\\n        INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                      \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                      \\\n        CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS                \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS              \\\n        template<typename TestType> static void TestFuncName();       \\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                     \\\n            INTERNAL_CATCH_TYPE_GEN                                                  \\\n            INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))         \\\n            template<typename... Types>                               \\\n            struct TestName {                                         \\\n                void reg_tests() {                                          \\\n                    int index = 0;                                    \\\n                    using expander = int[];                           \\\n                    constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\\\n                    constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\\\n                    constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\\\n                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index / num_types]) + \"<\" + std::string(types_list[index % num_types]) + \">\", Tags } ), index++)... };/* NOLINT */\\\n                }                                                     \\\n            };                                                        \\\n            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \\\n                using TestInit = typename create<TestName, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \\\n                TestInit t;                                           \\\n                t.reg_tests();                                        \\\n                return 0;                                             \\\n            }();                                                      \\\n        }                                                             \\\n        }                                                             \\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                       \\\n        template<typename TestType>                                   \\\n        static void TestFuncName()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\\\n        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename T,__VA_ARGS__)\n#else\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, typename T, __VA_ARGS__ ) )\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\\\n        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__)\n#else\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList)\\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        template<typename TestType> static void TestFunc();       \\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\\\n        INTERNAL_CATCH_TYPE_GEN\\\n        template<typename... Types>                               \\\n        struct TestName {                                         \\\n            void reg_tests() {                                          \\\n                int index = 0;                                    \\\n                using expander = int[];                           \\\n                (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name \" - \" + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + \" - \" + std::to_string(index), Tags } ), index++)... };/* NOLINT */\\\n            }                                                     \\\n        };\\\n        static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \\\n                using TestInit = typename convert<TestName, TmplList>::type; \\\n                TestInit t;                                           \\\n                t.reg_tests();                                        \\\n                return 0;                                             \\\n            }();                                                      \\\n        }}\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                       \\\n        template<typename TestType>                                   \\\n        static void TestFunc()\n\n    #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \\\n        INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, TmplList )\n\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \\\n            INTERNAL_CATCH_TYPE_GEN\\\n            INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\\\n            INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            template<typename...Types> \\\n            struct TestNameClass{\\\n                TestNameClass(){\\\n                    int index = 0;                                    \\\n                    constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\\\n                    using expander = int[];\\\n                    (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \\\n                }\\\n            };\\\n            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\\\n                TestNameClass<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\\\n                return 0;\\\n        }();\\\n        }\\\n        }\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \\\n        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) )\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \\\n        INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        template<typename TestType> \\\n            struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \\\n                void test();\\\n            };\\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) {\\\n            INTERNAL_CATCH_TYPE_GEN                  \\\n            INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\\\n            template<typename...Types>\\\n            struct TestNameClass{\\\n                void reg_tests(){\\\n                    int index = 0;\\\n                    using expander = int[];\\\n                    constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\\\n                    constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\\\n                    constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\\\n                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index / num_types]) + \"<\" + std::string(types_list[index % num_types]) + \">\", Tags } ), index++)... };/* NOLINT */ \\\n                }\\\n            };\\\n            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\\\n                using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;\\\n                TestInit t;\\\n                t.reg_tests();\\\n                return 0;\\\n            }(); \\\n        }\\\n        }\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        template<typename TestType> \\\n        void TestName<TestType>::test()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\\\n        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, typename T, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) )\n#endif\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\\\n        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \\\n        CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n        CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n        CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \\\n        template<typename TestType> \\\n        struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \\\n            void test();\\\n        };\\\n        namespace {\\\n        namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \\\n            INTERNAL_CATCH_TYPE_GEN\\\n            template<typename...Types>\\\n            struct TestNameClass{\\\n                void reg_tests(){\\\n                    int index = 0;\\\n                    using expander = int[];\\\n                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name \" - \" + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + \" - \" + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \\\n                }\\\n            };\\\n            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\\\n                using TestInit = typename convert<TestNameClass, TmplList>::type;\\\n                TestInit t;\\\n                t.reg_tests();\\\n                return 0;\\\n            }(); \\\n        }}\\\n        CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        template<typename TestType> \\\n        void TestName<TestType>::test()\n\n#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \\\n        INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, TmplList )\n\n// end catch_test_registry.h\n// start catch_capture.hpp\n\n// start catch_assertionhandler.h\n\n// start catch_assertioninfo.h\n\n// start catch_result_type.h\n\nnamespace Catch {\n\n    // ResultWas::OfType enum\n    struct ResultWas { enum OfType {\n        Unknown = -1,\n        Ok = 0,\n        Info = 1,\n        Warning = 2,\n\n        FailureBit = 0x10,\n\n        ExpressionFailed = FailureBit | 1,\n        ExplicitFailure = FailureBit | 2,\n\n        Exception = 0x100 | FailureBit,\n\n        ThrewException = Exception | 1,\n        DidntThrowException = Exception | 2,\n\n        FatalErrorCondition = 0x200 | FailureBit\n\n    }; };\n\n    bool isOk( ResultWas::OfType resultType );\n    bool isJustInfo( int flags );\n\n    // ResultDisposition::Flags enum\n    struct ResultDisposition { enum Flags {\n        Normal = 0x01,\n\n        ContinueOnFailure = 0x02,   // Failures fail test, but execution continues\n        FalseTest = 0x04,           // Prefix expression with !\n        SuppressFail = 0x08         // Failures are reported but do not fail the test\n    }; };\n\n    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs );\n\n    bool shouldContinueOnFailure( int flags );\n    inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; }\n    bool shouldSuppressFailure( int flags );\n\n} // end namespace Catch\n\n// end catch_result_type.h\nnamespace Catch {\n\n    struct AssertionInfo\n    {\n        StringRef macroName;\n        SourceLineInfo lineInfo;\n        StringRef capturedExpression;\n        ResultDisposition::Flags resultDisposition;\n\n        // We want to delete this constructor but a compiler bug in 4.8 means\n        // the struct is then treated as non-aggregate\n        //AssertionInfo() = delete;\n    };\n\n} // end namespace Catch\n\n// end catch_assertioninfo.h\n// start catch_decomposer.h\n\n// start catch_tostring.h\n\n#include <vector>\n#include <cstddef>\n#include <type_traits>\n#include <string>\n// start catch_stream.h\n\n#include <iosfwd>\n#include <cstddef>\n#include <ostream>\n\nnamespace Catch {\n\n    std::ostream& cout();\n    std::ostream& cerr();\n    std::ostream& clog();\n\n    class StringRef;\n\n    struct IStream {\n        virtual ~IStream();\n        virtual std::ostream& stream() const = 0;\n    };\n\n    auto makeStream( StringRef const &filename ) -> IStream const*;\n\n    class ReusableStringStream : NonCopyable {\n        std::size_t m_index;\n        std::ostream* m_oss;\n    public:\n        ReusableStringStream();\n        ~ReusableStringStream();\n\n        auto str() const -> std::string;\n\n        template<typename T>\n        auto operator << ( T const& value ) -> ReusableStringStream& {\n            *m_oss << value;\n            return *this;\n        }\n        auto get() -> std::ostream& { return *m_oss; }\n    };\n}\n\n// end catch_stream.h\n// start catch_interfaces_enum_values_registry.h\n\n#include <vector>\n\nnamespace Catch {\n\n    namespace Detail {\n        struct EnumInfo {\n            StringRef m_name;\n            std::vector<std::pair<int, StringRef>> m_values;\n\n            ~EnumInfo();\n\n            StringRef lookup( int value ) const;\n        };\n    } // namespace Detail\n\n    struct IMutableEnumValuesRegistry {\n        virtual ~IMutableEnumValuesRegistry();\n\n        virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values ) = 0;\n\n        template<typename E>\n        Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list<E> values ) {\n            static_assert(sizeof(int) >= sizeof(E), \"Cannot serialize enum to int\");\n            std::vector<int> intValues;\n            intValues.reserve( values.size() );\n            for( auto enumValue : values )\n                intValues.push_back( static_cast<int>( enumValue ) );\n            return registerEnum( enumName, allEnums, intValues );\n        }\n    };\n\n} // Catch\n\n// end catch_interfaces_enum_values_registry.h\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\n#include <string_view>\n#endif\n\n#ifdef __OBJC__\n// start catch_objc_arc.hpp\n\n#import <Foundation/Foundation.h>\n\n#ifdef __has_feature\n#define CATCH_ARC_ENABLED __has_feature(objc_arc)\n#else\n#define CATCH_ARC_ENABLED 0\n#endif\n\nvoid arcSafeRelease( NSObject* obj );\nid performOptionalSelector( id obj, SEL sel );\n\n#if !CATCH_ARC_ENABLED\ninline void arcSafeRelease( NSObject* obj ) {\n    [obj release];\n}\ninline id performOptionalSelector( id obj, SEL sel ) {\n    if( [obj respondsToSelector: sel] )\n        return [obj performSelector: sel];\n    return nil;\n}\n#define CATCH_UNSAFE_UNRETAINED\n#define CATCH_ARC_STRONG\n#else\ninline void arcSafeRelease( NSObject* ){}\ninline id performOptionalSelector( id obj, SEL sel ) {\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Warc-performSelector-leaks\"\n#endif\n    if( [obj respondsToSelector: sel] )\n        return [obj performSelector: sel];\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n    return nil;\n}\n#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained\n#define CATCH_ARC_STRONG __strong\n#endif\n\n// end catch_objc_arc.hpp\n#endif\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless\n#endif\n\nnamespace Catch {\n    namespace Detail {\n\n        extern const std::string unprintableString;\n\n        std::string rawMemoryToString( const void *object, std::size_t size );\n\n        template<typename T>\n        std::string rawMemoryToString( const T& object ) {\n          return rawMemoryToString( &object, sizeof(object) );\n        }\n\n        template<typename T>\n        class IsStreamInsertable {\n            template<typename Stream, typename U>\n            static auto test(int)\n                -> decltype(std::declval<Stream&>() << std::declval<U>(), std::true_type());\n\n            template<typename, typename>\n            static auto test(...)->std::false_type;\n\n        public:\n            static const bool value = decltype(test<std::ostream, const T&>(0))::value;\n        };\n\n        template<typename E>\n        std::string convertUnknownEnumToString( E e );\n\n        template<typename T>\n        typename std::enable_if<\n            !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value,\n        std::string>::type convertUnstreamable( T const& ) {\n            return Detail::unprintableString;\n        }\n        template<typename T>\n        typename std::enable_if<\n            !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value,\n         std::string>::type convertUnstreamable(T const& ex) {\n            return ex.what();\n        }\n\n        template<typename T>\n        typename std::enable_if<\n            std::is_enum<T>::value\n        , std::string>::type convertUnstreamable( T const& value ) {\n            return convertUnknownEnumToString( value );\n        }\n\n#if defined(_MANAGED)\n        //! Convert a CLR string to a utf8 std::string\n        template<typename T>\n        std::string clrReferenceToString( T^ ref ) {\n            if (ref == nullptr)\n                return std::string(\"null\");\n            auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString());\n            cli::pin_ptr<System::Byte> p = &bytes[0];\n            return std::string(reinterpret_cast<char const *>(p), bytes->Length);\n        }\n#endif\n\n    } // namespace Detail\n\n    // If we decide for C++14, change these to enable_if_ts\n    template <typename T, typename = void>\n    struct StringMaker {\n        template <typename Fake = T>\n        static\n        typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type\n            convert(const Fake& value) {\n                ReusableStringStream rss;\n                // NB: call using the function-like syntax to avoid ambiguity with\n                // user-defined templated operator<< under clang.\n                rss.operator<<(value);\n                return rss.str();\n        }\n\n        template <typename Fake = T>\n        static\n        typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type\n            convert( const Fake& value ) {\n#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)\n            return Detail::convertUnstreamable(value);\n#else\n            return CATCH_CONFIG_FALLBACK_STRINGIFIER(value);\n#endif\n        }\n    };\n\n    namespace Detail {\n\n        // This function dispatches all stringification requests inside of Catch.\n        // Should be preferably called fully qualified, like ::Catch::Detail::stringify\n        template <typename T>\n        std::string stringify(const T& e) {\n            return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e);\n        }\n\n        template<typename E>\n        std::string convertUnknownEnumToString( E e ) {\n            return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e));\n        }\n\n#if defined(_MANAGED)\n        template <typename T>\n        std::string stringify( T^ e ) {\n            return ::Catch::StringMaker<T^>::convert(e);\n        }\n#endif\n\n    } // namespace Detail\n\n    // Some predefined specializations\n\n    template<>\n    struct StringMaker<std::string> {\n        static std::string convert(const std::string& str);\n    };\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\n    template<>\n    struct StringMaker<std::string_view> {\n        static std::string convert(std::string_view str);\n    };\n#endif\n\n    template<>\n    struct StringMaker<char const *> {\n        static std::string convert(char const * str);\n    };\n    template<>\n    struct StringMaker<char *> {\n        static std::string convert(char * str);\n    };\n\n#ifdef CATCH_CONFIG_WCHAR\n    template<>\n    struct StringMaker<std::wstring> {\n        static std::string convert(const std::wstring& wstr);\n    };\n\n# ifdef CATCH_CONFIG_CPP17_STRING_VIEW\n    template<>\n    struct StringMaker<std::wstring_view> {\n        static std::string convert(std::wstring_view str);\n    };\n# endif\n\n    template<>\n    struct StringMaker<wchar_t const *> {\n        static std::string convert(wchar_t const * str);\n    };\n    template<>\n    struct StringMaker<wchar_t *> {\n        static std::string convert(wchar_t * str);\n    };\n#endif\n\n    // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer,\n    //      while keeping string semantics?\n    template<int SZ>\n    struct StringMaker<char[SZ]> {\n        static std::string convert(char const* str) {\n            return ::Catch::Detail::stringify(std::string{ str });\n        }\n    };\n    template<int SZ>\n    struct StringMaker<signed char[SZ]> {\n        static std::string convert(signed char const* str) {\n            return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) });\n        }\n    };\n    template<int SZ>\n    struct StringMaker<unsigned char[SZ]> {\n        static std::string convert(unsigned char const* str) {\n            return ::Catch::Detail::stringify(std::string{ reinterpret_cast<char const *>(str) });\n        }\n    };\n\n#if defined(CATCH_CONFIG_CPP17_BYTE)\n    template<>\n    struct StringMaker<std::byte> {\n        static std::string convert(std::byte value);\n    };\n#endif // defined(CATCH_CONFIG_CPP17_BYTE)\n    template<>\n    struct StringMaker<int> {\n        static std::string convert(int value);\n    };\n    template<>\n    struct StringMaker<long> {\n        static std::string convert(long value);\n    };\n    template<>\n    struct StringMaker<long long> {\n        static std::string convert(long long value);\n    };\n    template<>\n    struct StringMaker<unsigned int> {\n        static std::string convert(unsigned int value);\n    };\n    template<>\n    struct StringMaker<unsigned long> {\n        static std::string convert(unsigned long value);\n    };\n    template<>\n    struct StringMaker<unsigned long long> {\n        static std::string convert(unsigned long long value);\n    };\n\n    template<>\n    struct StringMaker<bool> {\n        static std::string convert(bool b);\n    };\n\n    template<>\n    struct StringMaker<char> {\n        static std::string convert(char c);\n    };\n    template<>\n    struct StringMaker<signed char> {\n        static std::string convert(signed char c);\n    };\n    template<>\n    struct StringMaker<unsigned char> {\n        static std::string convert(unsigned char c);\n    };\n\n    template<>\n    struct StringMaker<std::nullptr_t> {\n        static std::string convert(std::nullptr_t);\n    };\n\n    template<>\n    struct StringMaker<float> {\n        static std::string convert(float value);\n        static int precision;\n    };\n\n    template<>\n    struct StringMaker<double> {\n        static std::string convert(double value);\n        static int precision;\n    };\n\n    template <typename T>\n    struct StringMaker<T*> {\n        template <typename U>\n        static std::string convert(U* p) {\n            if (p) {\n                return ::Catch::Detail::rawMemoryToString(p);\n            } else {\n                return \"nullptr\";\n            }\n        }\n    };\n\n    template <typename R, typename C>\n    struct StringMaker<R C::*> {\n        static std::string convert(R C::* p) {\n            if (p) {\n                return ::Catch::Detail::rawMemoryToString(p);\n            } else {\n                return \"nullptr\";\n            }\n        }\n    };\n\n#if defined(_MANAGED)\n    template <typename T>\n    struct StringMaker<T^> {\n        static std::string convert( T^ ref ) {\n            return ::Catch::Detail::clrReferenceToString(ref);\n        }\n    };\n#endif\n\n    namespace Detail {\n        template<typename InputIterator, typename Sentinel = InputIterator>\n        std::string rangeToString(InputIterator first, Sentinel last) {\n            ReusableStringStream rss;\n            rss << \"{ \";\n            if (first != last) {\n                rss << ::Catch::Detail::stringify(*first);\n                for (++first; first != last; ++first)\n                    rss << \", \" << ::Catch::Detail::stringify(*first);\n            }\n            rss << \" }\";\n            return rss.str();\n        }\n    }\n\n#ifdef __OBJC__\n    template<>\n    struct StringMaker<NSString*> {\n        static std::string convert(NSString * nsstring) {\n            if (!nsstring)\n                return \"nil\";\n            return std::string(\"@\") + [nsstring UTF8String];\n        }\n    };\n    template<>\n    struct StringMaker<NSObject*> {\n        static std::string convert(NSObject* nsObject) {\n            return ::Catch::Detail::stringify([nsObject description]);\n        }\n\n    };\n    namespace Detail {\n        inline std::string stringify( NSString* nsstring ) {\n            return StringMaker<NSString*>::convert( nsstring );\n        }\n\n    } // namespace Detail\n#endif // __OBJC__\n\n} // namespace Catch\n\n//////////////////////////////////////////////////////\n// Separate std-lib types stringification, so it can be selectively enabled\n// This means that we do not bring in\n\n#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS)\n#  define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER\n#  define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER\n#  define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER\n#  define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER\n#  define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER\n#endif\n\n// Separate std::pair specialization\n#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER)\n#include <utility>\nnamespace Catch {\n    template<typename T1, typename T2>\n    struct StringMaker<std::pair<T1, T2> > {\n        static std::string convert(const std::pair<T1, T2>& pair) {\n            ReusableStringStream rss;\n            rss << \"{ \"\n                << ::Catch::Detail::stringify(pair.first)\n                << \", \"\n                << ::Catch::Detail::stringify(pair.second)\n                << \" }\";\n            return rss.str();\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER\n\n#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL)\n#include <optional>\nnamespace Catch {\n    template<typename T>\n    struct StringMaker<std::optional<T> > {\n        static std::string convert(const std::optional<T>& optional) {\n            ReusableStringStream rss;\n            if (optional.has_value()) {\n                rss << ::Catch::Detail::stringify(*optional);\n            } else {\n                rss << \"{ }\";\n            }\n            return rss.str();\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER\n\n// Separate std::tuple specialization\n#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)\n#include <tuple>\nnamespace Catch {\n    namespace Detail {\n        template<\n            typename Tuple,\n            std::size_t N = 0,\n            bool = (N < std::tuple_size<Tuple>::value)\n            >\n            struct TupleElementPrinter {\n            static void print(const Tuple& tuple, std::ostream& os) {\n                os << (N ? \", \" : \" \")\n                    << ::Catch::Detail::stringify(std::get<N>(tuple));\n                TupleElementPrinter<Tuple, N + 1>::print(tuple, os);\n            }\n        };\n\n        template<\n            typename Tuple,\n            std::size_t N\n        >\n            struct TupleElementPrinter<Tuple, N, false> {\n            static void print(const Tuple&, std::ostream&) {}\n        };\n\n    }\n\n    template<typename ...Types>\n    struct StringMaker<std::tuple<Types...>> {\n        static std::string convert(const std::tuple<Types...>& tuple) {\n            ReusableStringStream rss;\n            rss << '{';\n            Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get());\n            rss << \" }\";\n            return rss.str();\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER\n\n#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT)\n#include <variant>\nnamespace Catch {\n    template<>\n    struct StringMaker<std::monostate> {\n        static std::string convert(const std::monostate&) {\n            return \"{ }\";\n        }\n    };\n\n    template<typename... Elements>\n    struct StringMaker<std::variant<Elements...>> {\n        static std::string convert(const std::variant<Elements...>& variant) {\n            if (variant.valueless_by_exception()) {\n                return \"{valueless variant}\";\n            } else {\n                return std::visit(\n                    [](const auto& value) {\n                        return ::Catch::Detail::stringify(value);\n                    },\n                    variant\n                );\n            }\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER\n\nnamespace Catch {\n    // Import begin/ end from std here\n    using std::begin;\n    using std::end;\n\n    namespace detail {\n        template <typename...>\n        struct void_type {\n            using type = void;\n        };\n\n        template <typename T, typename = void>\n        struct is_range_impl : std::false_type {\n        };\n\n        template <typename T>\n        struct is_range_impl<T, typename void_type<decltype(begin(std::declval<T>()))>::type> : std::true_type {\n        };\n    } // namespace detail\n\n    template <typename T>\n    struct is_range : detail::is_range_impl<T> {\n    };\n\n#if defined(_MANAGED) // Managed types are never ranges\n    template <typename T>\n    struct is_range<T^> {\n        static const bool value = false;\n    };\n#endif\n\n    template<typename Range>\n    std::string rangeToString( Range const& range ) {\n        return ::Catch::Detail::rangeToString( begin( range ), end( range ) );\n    }\n\n    // Handle vector<bool> specially\n    template<typename Allocator>\n    std::string rangeToString( std::vector<bool, Allocator> const& v ) {\n        ReusableStringStream rss;\n        rss << \"{ \";\n        bool first = true;\n        for( bool b : v ) {\n            if( first )\n                first = false;\n            else\n                rss << \", \";\n            rss << ::Catch::Detail::stringify( b );\n        }\n        rss << \" }\";\n        return rss.str();\n    }\n\n    template<typename R>\n    struct StringMaker<R, typename std::enable_if<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>::type> {\n        static std::string convert( R const& range ) {\n            return rangeToString( range );\n        }\n    };\n\n    template <typename T, int SZ>\n    struct StringMaker<T[SZ]> {\n        static std::string convert(T const(&arr)[SZ]) {\n            return rangeToString(arr);\n        }\n    };\n\n} // namespace Catch\n\n// Separate std::chrono::duration specialization\n#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)\n#include <ctime>\n#include <ratio>\n#include <chrono>\n\nnamespace Catch {\n\ntemplate <class Ratio>\nstruct ratio_string {\n    static std::string symbol();\n};\n\ntemplate <class Ratio>\nstd::string ratio_string<Ratio>::symbol() {\n    Catch::ReusableStringStream rss;\n    rss << '[' << Ratio::num << '/'\n        << Ratio::den << ']';\n    return rss.str();\n}\ntemplate <>\nstruct ratio_string<std::atto> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::femto> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::pico> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::nano> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::micro> {\n    static std::string symbol();\n};\ntemplate <>\nstruct ratio_string<std::milli> {\n    static std::string symbol();\n};\n\n    ////////////\n    // std::chrono::duration specializations\n    template<typename Value, typename Ratio>\n    struct StringMaker<std::chrono::duration<Value, Ratio>> {\n        static std::string convert(std::chrono::duration<Value, Ratio> const& duration) {\n            ReusableStringStream rss;\n            rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's';\n            return rss.str();\n        }\n    };\n    template<typename Value>\n    struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> {\n        static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) {\n            ReusableStringStream rss;\n            rss << duration.count() << \" s\";\n            return rss.str();\n        }\n    };\n    template<typename Value>\n    struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> {\n        static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) {\n            ReusableStringStream rss;\n            rss << duration.count() << \" m\";\n            return rss.str();\n        }\n    };\n    template<typename Value>\n    struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> {\n        static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) {\n            ReusableStringStream rss;\n            rss << duration.count() << \" h\";\n            return rss.str();\n        }\n    };\n\n    ////////////\n    // std::chrono::time_point specialization\n    // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock>\n    template<typename Clock, typename Duration>\n    struct StringMaker<std::chrono::time_point<Clock, Duration>> {\n        static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) {\n            return ::Catch::Detail::stringify(time_point.time_since_epoch()) + \" since epoch\";\n        }\n    };\n    // std::chrono::time_point<system_clock> specialization\n    template<typename Duration>\n    struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {\n        static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) {\n            auto converted = std::chrono::system_clock::to_time_t(time_point);\n\n#ifdef _MSC_VER\n            std::tm timeInfo = {};\n            gmtime_s(&timeInfo, &converted);\n#else\n            std::tm* timeInfo = std::gmtime(&converted);\n#endif\n\n            auto const timeStampSize = sizeof(\"2017-01-16T17:06:45Z\");\n            char timeStamp[timeStampSize];\n            const char * const fmt = \"%Y-%m-%dT%H:%M:%SZ\";\n\n#ifdef _MSC_VER\n            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);\n#else\n            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);\n#endif\n            return std::string(timeStamp);\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER\n\n#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \\\nnamespace Catch { \\\n    template<> struct StringMaker<enumName> { \\\n        static std::string convert( enumName value ) { \\\n            static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \\\n            return static_cast<std::string>(enumInfo.lookup( static_cast<int>( value ) )); \\\n        } \\\n    }; \\\n}\n\n#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ )\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n// end catch_tostring.h\n#include <iosfwd>\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable:4389) // '==' : signed/unsigned mismatch\n#pragma warning(disable:4018) // more \"signed/unsigned mismatch\"\n#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform)\n#pragma warning(disable:4180) // qualifier applied to function type has no meaning\n#pragma warning(disable:4800) // Forcing result to true or false\n#endif\n\nnamespace Catch {\n\n    struct ITransientExpression {\n        auto isBinaryExpression() const -> bool { return m_isBinaryExpression; }\n        auto getResult() const -> bool { return m_result; }\n        virtual void streamReconstructedExpression( std::ostream &os ) const = 0;\n\n        ITransientExpression( bool isBinaryExpression, bool result )\n        :   m_isBinaryExpression( isBinaryExpression ),\n            m_result( result )\n        {}\n\n        // We don't actually need a virtual destructor, but many static analysers\n        // complain if it's not here :-(\n        virtual ~ITransientExpression();\n\n        bool m_isBinaryExpression;\n        bool m_result;\n\n    };\n\n    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );\n\n    template<typename LhsT, typename RhsT>\n    class BinaryExpr  : public ITransientExpression {\n        LhsT m_lhs;\n        StringRef m_op;\n        RhsT m_rhs;\n\n        void streamReconstructedExpression( std::ostream &os ) const override {\n            formatReconstructedExpression\n                    ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) );\n        }\n\n    public:\n        BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs )\n        :   ITransientExpression{ true, comparisonResult },\n            m_lhs( lhs ),\n            m_op( op ),\n            m_rhs( rhs )\n        {}\n\n        template<typename T>\n        auto operator && ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator || ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator == ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator != ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator > ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator < ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator >= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename T>\n        auto operator <= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<T>::value,\n            \"chained comparisons are not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n    };\n\n    template<typename LhsT>\n    class UnaryExpr : public ITransientExpression {\n        LhsT m_lhs;\n\n        void streamReconstructedExpression( std::ostream &os ) const override {\n            os << Catch::Detail::stringify( m_lhs );\n        }\n\n    public:\n        explicit UnaryExpr( LhsT lhs )\n        :   ITransientExpression{ false, static_cast<bool>(lhs) },\n            m_lhs( lhs )\n        {}\n    };\n\n    // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int)\n    template<typename LhsT, typename RhsT>\n    auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast<bool>(lhs == rhs); }\n    template<typename T>\n    auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); }\n    template<typename T>\n    auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); }\n    template<typename T>\n    auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; }\n    template<typename T>\n    auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; }\n\n    template<typename LhsT, typename RhsT>\n    auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast<bool>(lhs != rhs); }\n    template<typename T>\n    auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); }\n    template<typename T>\n    auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); }\n    template<typename T>\n    auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; }\n    template<typename T>\n    auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; }\n\n    template<typename LhsT>\n    class ExprLhs {\n        LhsT m_lhs;\n    public:\n        explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {}\n\n        template<typename RhsT>\n        auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {\n            return { compareEqual( m_lhs, rhs ), m_lhs, \"==\", rhs };\n        }\n        auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const {\n            return { m_lhs == rhs, m_lhs, \"==\", rhs };\n        }\n\n        template<typename RhsT>\n        auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {\n            return { compareNotEqual( m_lhs, rhs ), m_lhs, \"!=\", rhs };\n        }\n        auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const {\n            return { m_lhs != rhs, m_lhs, \"!=\", rhs };\n        }\n\n        template<typename RhsT>\n        auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {\n            return { static_cast<bool>(m_lhs > rhs), m_lhs, \">\", rhs };\n        }\n        template<typename RhsT>\n        auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {\n            return { static_cast<bool>(m_lhs < rhs), m_lhs, \"<\", rhs };\n        }\n        template<typename RhsT>\n        auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {\n            return { static_cast<bool>(m_lhs >= rhs), m_lhs, \">=\", rhs };\n        }\n        template<typename RhsT>\n        auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {\n            return { static_cast<bool>(m_lhs <= rhs), m_lhs, \"<=\", rhs };\n        }\n        template <typename RhsT>\n        auto operator | (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const {\n            return { static_cast<bool>(m_lhs | rhs), m_lhs, \"|\", rhs };\n        }\n        template <typename RhsT>\n        auto operator & (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const {\n            return { static_cast<bool>(m_lhs & rhs), m_lhs, \"&\", rhs };\n        }\n        template <typename RhsT>\n        auto operator ^ (RhsT const& rhs) -> BinaryExpr<LhsT, RhsT const&> const {\n            return { static_cast<bool>(m_lhs ^ rhs), m_lhs, \"^\", rhs };\n        }\n\n        template<typename RhsT>\n        auto operator && ( RhsT const& ) -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<RhsT>::value,\n            \"operator&& is not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        template<typename RhsT>\n        auto operator || ( RhsT const& ) -> BinaryExpr<LhsT, RhsT const&> const {\n            static_assert(always_false<RhsT>::value,\n            \"operator|| is not supported inside assertions, \"\n            \"wrap the expression inside parentheses, or decompose it\");\n        }\n\n        auto makeUnaryExpr() const -> UnaryExpr<LhsT> {\n            return UnaryExpr<LhsT>{ m_lhs };\n        }\n    };\n\n    void handleExpression( ITransientExpression const& expr );\n\n    template<typename T>\n    void handleExpression( ExprLhs<T> const& expr ) {\n        handleExpression( expr.makeUnaryExpr() );\n    }\n\n    struct Decomposer {\n        template<typename T>\n        auto operator <= ( T const& lhs ) -> ExprLhs<T const&> {\n            return ExprLhs<T const&>{ lhs };\n        }\n\n        auto operator <=( bool value ) -> ExprLhs<bool> {\n            return ExprLhs<bool>{ value };\n        }\n    };\n\n} // end namespace Catch\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n// end catch_decomposer.h\n// start catch_interfaces_capture.h\n\n#include <string>\n#include <chrono>\n\nnamespace Catch {\n\n    class AssertionResult;\n    struct AssertionInfo;\n    struct SectionInfo;\n    struct SectionEndInfo;\n    struct MessageInfo;\n    struct MessageBuilder;\n    struct Counts;\n    struct AssertionReaction;\n    struct SourceLineInfo;\n\n    struct ITransientExpression;\n    struct IGeneratorTracker;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    struct BenchmarkInfo;\n    template <typename Duration = std::chrono::duration<double, std::nano>>\n    struct BenchmarkStats;\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    struct IResultCapture {\n\n        virtual ~IResultCapture();\n\n        virtual bool sectionStarted(    SectionInfo const& sectionInfo,\n                                        Counts& assertions ) = 0;\n        virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0;\n        virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0;\n\n        virtual auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& = 0;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n        virtual void benchmarkPreparing( std::string const& name ) = 0;\n        virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0;\n        virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0;\n        virtual void benchmarkFailed( std::string const& error ) = 0;\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n        virtual void pushScopedMessage( MessageInfo const& message ) = 0;\n        virtual void popScopedMessage( MessageInfo const& message ) = 0;\n\n        virtual void emplaceUnscopedMessage( MessageBuilder const& builder ) = 0;\n\n        virtual void handleFatalErrorCondition( StringRef message ) = 0;\n\n        virtual void handleExpr\n                (   AssertionInfo const& info,\n                    ITransientExpression const& expr,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleMessage\n                (   AssertionInfo const& info,\n                    ResultWas::OfType resultType,\n                    StringRef const& message,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleUnexpectedExceptionNotThrown\n                (   AssertionInfo const& info,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleUnexpectedInflightException\n                (   AssertionInfo const& info,\n                    std::string const& message,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleIncomplete\n                (   AssertionInfo const& info ) = 0;\n        virtual void handleNonExpr\n                (   AssertionInfo const &info,\n                    ResultWas::OfType resultType,\n                    AssertionReaction &reaction ) = 0;\n\n        virtual bool lastAssertionPassed() = 0;\n        virtual void assertionPassed() = 0;\n\n        // Deprecated, do not use:\n        virtual std::string getCurrentTestName() const = 0;\n        virtual const AssertionResult* getLastResult() const = 0;\n        virtual void exceptionEarlyReported() = 0;\n    };\n\n    IResultCapture& getResultCapture();\n}\n\n// end catch_interfaces_capture.h\nnamespace Catch {\n\n    struct TestFailureException{};\n    struct AssertionResultData;\n    struct IResultCapture;\n    class RunContext;\n\n    class LazyExpression {\n        friend class AssertionHandler;\n        friend struct AssertionStats;\n        friend class RunContext;\n\n        ITransientExpression const* m_transientExpression = nullptr;\n        bool m_isNegated;\n    public:\n        LazyExpression( bool isNegated );\n        LazyExpression( LazyExpression const& other );\n        LazyExpression& operator = ( LazyExpression const& ) = delete;\n\n        explicit operator bool() const;\n\n        friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&;\n    };\n\n    struct AssertionReaction {\n        bool shouldDebugBreak = false;\n        bool shouldThrow = false;\n    };\n\n    class AssertionHandler {\n        AssertionInfo m_assertionInfo;\n        AssertionReaction m_reaction;\n        bool m_completed = false;\n        IResultCapture& m_resultCapture;\n\n    public:\n        AssertionHandler\n            (   StringRef const& macroName,\n                SourceLineInfo const& lineInfo,\n                StringRef capturedExpression,\n                ResultDisposition::Flags resultDisposition );\n        ~AssertionHandler() {\n            if ( !m_completed ) {\n                m_resultCapture.handleIncomplete( m_assertionInfo );\n            }\n        }\n\n        template<typename T>\n        void handleExpr( ExprLhs<T> const& expr ) {\n            handleExpr( expr.makeUnaryExpr() );\n        }\n        void handleExpr( ITransientExpression const& expr );\n\n        void handleMessage(ResultWas::OfType resultType, StringRef const& message);\n\n        void handleExceptionThrownAsExpected();\n        void handleUnexpectedExceptionNotThrown();\n        void handleExceptionNotThrownAsExpected();\n        void handleThrowingCallSkipped();\n        void handleUnexpectedInflightException();\n\n        void complete();\n        void setCompleted();\n\n        // query\n        auto allowThrows() const -> bool;\n    };\n\n    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString );\n\n} // namespace Catch\n\n// end catch_assertionhandler.h\n// start catch_message.h\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\n\n    struct MessageInfo {\n        MessageInfo(    StringRef const& _macroName,\n                        SourceLineInfo const& _lineInfo,\n                        ResultWas::OfType _type );\n\n        StringRef macroName;\n        std::string message;\n        SourceLineInfo lineInfo;\n        ResultWas::OfType type;\n        unsigned int sequence;\n\n        bool operator == ( MessageInfo const& other ) const;\n        bool operator < ( MessageInfo const& other ) const;\n    private:\n        static unsigned int globalCount;\n    };\n\n    struct MessageStream {\n\n        template<typename T>\n        MessageStream& operator << ( T const& value ) {\n            m_stream << value;\n            return *this;\n        }\n\n        ReusableStringStream m_stream;\n    };\n\n    struct MessageBuilder : MessageStream {\n        MessageBuilder( StringRef const& macroName,\n                        SourceLineInfo const& lineInfo,\n                        ResultWas::OfType type );\n\n        template<typename T>\n        MessageBuilder& operator << ( T const& value ) {\n            m_stream << value;\n            return *this;\n        }\n\n        MessageInfo m_info;\n    };\n\n    class ScopedMessage {\n    public:\n        explicit ScopedMessage( MessageBuilder const& builder );\n        ScopedMessage( ScopedMessage& duplicate ) = delete;\n        ScopedMessage( ScopedMessage&& old );\n        ~ScopedMessage();\n\n        MessageInfo m_info;\n        bool m_moved;\n    };\n\n    class Capturer {\n        std::vector<MessageInfo> m_messages;\n        IResultCapture& m_resultCapture = getResultCapture();\n        size_t m_captured = 0;\n    public:\n        Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names );\n        ~Capturer();\n\n        void captureValue( size_t index, std::string const& value );\n\n        template<typename T>\n        void captureValues( size_t index, T const& value ) {\n            captureValue( index, Catch::Detail::stringify( value ) );\n        }\n\n        template<typename T, typename... Ts>\n        void captureValues( size_t index, T const& value, Ts const&... values ) {\n            captureValue( index, Catch::Detail::stringify(value) );\n            captureValues( index+1, values... );\n        }\n    };\n\n} // end namespace Catch\n\n// end catch_message.h\n#if !defined(CATCH_CONFIG_DISABLE)\n\n#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION)\n  #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__\n#else\n  #define CATCH_INTERNAL_STRINGIFY(...) \"Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION\"\n#endif\n\n#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n\n///////////////////////////////////////////////////////////////////////////////\n// Another way to speed-up compilation is to omit local try-catch for REQUIRE*\n// macros.\n#define INTERNAL_CATCH_TRY\n#define INTERNAL_CATCH_CATCH( capturer )\n\n#else // CATCH_CONFIG_FAST_COMPILE\n\n#define INTERNAL_CATCH_TRY try\n#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); }\n\n#endif\n\n#define INTERNAL_CATCH_REACT( handler ) handler.complete();\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \\\n    do { \\\n        CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \\\n        INTERNAL_CATCH_TRY { \\\n            CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n            CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \\\n            catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \\\n            CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n        } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \\\n        INTERNAL_CATCH_REACT( catchAssertionHandler ) \\\n    } while( (void)0, (false) && static_cast<bool>( !!(__VA_ARGS__) ) )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \\\n    INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \\\n    if( Catch::getResultCapture().lastAssertionPassed() )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \\\n    INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \\\n    if( !Catch::getResultCapture().lastAssertionPassed() )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \\\n        try { \\\n            static_cast<void>(__VA_ARGS__); \\\n            catchAssertionHandler.handleExceptionNotThrownAsExpected(); \\\n        } \\\n        catch( ... ) { \\\n            catchAssertionHandler.handleUnexpectedInflightException(); \\\n        } \\\n        INTERNAL_CATCH_REACT( catchAssertionHandler ) \\\n    } while( false )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                static_cast<void>(__VA_ARGS__); \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( ... ) { \\\n                catchAssertionHandler.handleExceptionThrownAsExpected(); \\\n            } \\\n        else \\\n            catchAssertionHandler.handleThrowingCallSkipped(); \\\n        INTERNAL_CATCH_REACT( catchAssertionHandler ) \\\n    } while( false )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) \", \" CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                static_cast<void>(expr); \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( exceptionType const& ) { \\\n                catchAssertionHandler.handleExceptionThrownAsExpected(); \\\n            } \\\n            catch( ... ) { \\\n                catchAssertionHandler.handleUnexpectedInflightException(); \\\n            } \\\n        else \\\n            catchAssertionHandler.handleThrowingCallSkipped(); \\\n        INTERNAL_CATCH_REACT( catchAssertionHandler ) \\\n    } while( false )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \\\n        catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \\\n        INTERNAL_CATCH_REACT( catchAssertionHandler ) \\\n    } while( false )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \\\n    auto varName = Catch::Capturer( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info, #__VA_ARGS__ ); \\\n    varName.captureValues( 0, __VA_ARGS__ )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_INFO( macroName, log ) \\\n    Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log );\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \\\n    Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log )\n\n///////////////////////////////////////////////////////////////////////////////\n// Although this is matcher-based, it can be used with just a string\n#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) \", \" CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                static_cast<void>(__VA_ARGS__); \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( ... ) { \\\n                Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher##_catch_sr ); \\\n            } \\\n        else \\\n            catchAssertionHandler.handleThrowingCallSkipped(); \\\n        INTERNAL_CATCH_REACT( catchAssertionHandler ) \\\n    } while( false )\n\n#endif // CATCH_CONFIG_DISABLE\n\n// end catch_capture.hpp\n// start catch_section.h\n\n// start catch_section_info.h\n\n// start catch_totals.h\n\n#include <cstddef>\n\nnamespace Catch {\n\n    struct Counts {\n        Counts operator - ( Counts const& other ) const;\n        Counts& operator += ( Counts const& other );\n\n        std::size_t total() const;\n        bool allPassed() const;\n        bool allOk() const;\n\n        std::size_t passed = 0;\n        std::size_t failed = 0;\n        std::size_t failedButOk = 0;\n    };\n\n    struct Totals {\n\n        Totals operator - ( Totals const& other ) const;\n        Totals& operator += ( Totals const& other );\n\n        Totals delta( Totals const& prevTotals ) const;\n\n        int error = 0;\n        Counts assertions;\n        Counts testCases;\n    };\n}\n\n// end catch_totals.h\n#include <string>\n\nnamespace Catch {\n\n    struct SectionInfo {\n        SectionInfo\n            (   SourceLineInfo const& _lineInfo,\n                std::string const& _name );\n\n        // Deprecated\n        SectionInfo\n            (   SourceLineInfo const& _lineInfo,\n                std::string const& _name,\n                std::string const& ) : SectionInfo( _lineInfo, _name ) {}\n\n        std::string name;\n        std::string description; // !Deprecated: this will always be empty\n        SourceLineInfo lineInfo;\n    };\n\n    struct SectionEndInfo {\n        SectionInfo sectionInfo;\n        Counts prevAssertions;\n        double durationInSeconds;\n    };\n\n} // end namespace Catch\n\n// end catch_section_info.h\n// start catch_timer.h\n\n#include <cstdint>\n\nnamespace Catch {\n\n    auto getCurrentNanosecondsSinceEpoch() -> uint64_t;\n    auto getEstimatedClockResolution() -> uint64_t;\n\n    class Timer {\n        uint64_t m_nanoseconds = 0;\n    public:\n        void start();\n        auto getElapsedNanoseconds() const -> uint64_t;\n        auto getElapsedMicroseconds() const -> uint64_t;\n        auto getElapsedMilliseconds() const -> unsigned int;\n        auto getElapsedSeconds() const -> double;\n    };\n\n} // namespace Catch\n\n// end catch_timer.h\n#include <string>\n\nnamespace Catch {\n\n    class Section : NonCopyable {\n    public:\n        Section( SectionInfo const& info );\n        ~Section();\n\n        // This indicates whether the section should be executed or not\n        explicit operator bool() const;\n\n    private:\n        SectionInfo m_info;\n\n        std::string m_name;\n        Counts m_assertions;\n        bool m_sectionIncluded;\n        Timer m_timer;\n    };\n\n} // end namespace Catch\n\n#define INTERNAL_CATCH_SECTION( ... ) \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \\\n    if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n    CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \\\n    if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (Catch::ReusableStringStream() << __VA_ARGS__).str() ) ) \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n// end catch_section.h\n// start catch_interfaces_exception.h\n\n// start catch_interfaces_registry_hub.h\n\n#include <string>\n#include <memory>\n\nnamespace Catch {\n\n    class TestCase;\n    struct ITestCaseRegistry;\n    struct IExceptionTranslatorRegistry;\n    struct IExceptionTranslator;\n    struct IReporterRegistry;\n    struct IReporterFactory;\n    struct ITagAliasRegistry;\n    struct IMutableEnumValuesRegistry;\n\n    class StartupExceptionRegistry;\n\n    using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;\n\n    struct IRegistryHub {\n        virtual ~IRegistryHub();\n\n        virtual IReporterRegistry const& getReporterRegistry() const = 0;\n        virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;\n        virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0;\n        virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0;\n\n        virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0;\n    };\n\n    struct IMutableRegistryHub {\n        virtual ~IMutableRegistryHub();\n        virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0;\n        virtual void registerListener( IReporterFactoryPtr const& factory ) = 0;\n        virtual void registerTest( TestCase const& testInfo ) = 0;\n        virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;\n        virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;\n        virtual void registerStartupException() noexcept = 0;\n        virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0;\n    };\n\n    IRegistryHub const& getRegistryHub();\n    IMutableRegistryHub& getMutableRegistryHub();\n    void cleanUp();\n    std::string translateActiveException();\n\n}\n\n// end catch_interfaces_registry_hub.h\n#if defined(CATCH_CONFIG_DISABLE)\n    #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \\\n        static std::string translatorName( signature )\n#endif\n\n#include <exception>\n#include <string>\n#include <vector>\n\nnamespace Catch {\n    using exceptionTranslateFunction = std::string(*)();\n\n    struct IExceptionTranslator;\n    using ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>;\n\n    struct IExceptionTranslator {\n        virtual ~IExceptionTranslator();\n        virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0;\n    };\n\n    struct IExceptionTranslatorRegistry {\n        virtual ~IExceptionTranslatorRegistry();\n\n        virtual std::string translateActiveException() const = 0;\n    };\n\n    class ExceptionTranslatorRegistrar {\n        template<typename T>\n        class ExceptionTranslator : public IExceptionTranslator {\n        public:\n\n            ExceptionTranslator( std::string(*translateFunction)( T& ) )\n            : m_translateFunction( translateFunction )\n            {}\n\n            std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override {\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n                return \"\";\n#else\n                try {\n                    if( it == itEnd )\n                        std::rethrow_exception(std::current_exception());\n                    else\n                        return (*it)->translate( it+1, itEnd );\n                }\n                catch( T& ex ) {\n                    return m_translateFunction( ex );\n                }\n#endif\n            }\n\n        protected:\n            std::string(*m_translateFunction)( T& );\n        };\n\n    public:\n        template<typename T>\n        ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) {\n            getMutableRegistryHub().registerTranslator\n                ( new ExceptionTranslator<T>( translateFunction ) );\n        }\n    };\n}\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \\\n    static std::string translatorName( signature ); \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \\\n    namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n    static std::string translatorName( signature )\n\n#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )\n\n// end catch_interfaces_exception.h\n// start catch_approx.h\n\n#include <type_traits>\n\nnamespace Catch {\nnamespace Detail {\n\n    class Approx {\n    private:\n        bool equalityComparisonImpl(double other) const;\n        // Validates the new margin (margin >= 0)\n        // out-of-line to avoid including stdexcept in the header\n        void setMargin(double margin);\n        // Validates the new epsilon (0 < epsilon < 1)\n        // out-of-line to avoid including stdexcept in the header\n        void setEpsilon(double epsilon);\n\n    public:\n        explicit Approx ( double value );\n\n        static Approx custom();\n\n        Approx operator-() const;\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        Approx operator()( T const& value ) const {\n            Approx approx( static_cast<double>(value) );\n            approx.m_epsilon = m_epsilon;\n            approx.m_margin = m_margin;\n            approx.m_scale = m_scale;\n            return approx;\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        explicit Approx( T const& value ): Approx(static_cast<double>(value))\n        {}\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        friend bool operator == ( const T& lhs, Approx const& rhs ) {\n            auto lhs_v = static_cast<double>(lhs);\n            return rhs.equalityComparisonImpl(lhs_v);\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        friend bool operator == ( Approx const& lhs, const T& rhs ) {\n            return operator==( rhs, lhs );\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        friend bool operator != ( T const& lhs, Approx const& rhs ) {\n            return !operator==( lhs, rhs );\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        friend bool operator != ( Approx const& lhs, T const& rhs ) {\n            return !operator==( rhs, lhs );\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        friend bool operator <= ( T const& lhs, Approx const& rhs ) {\n            return static_cast<double>(lhs) < rhs.m_value || lhs == rhs;\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        friend bool operator <= ( Approx const& lhs, T const& rhs ) {\n            return lhs.m_value < static_cast<double>(rhs) || lhs == rhs;\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        friend bool operator >= ( T const& lhs, Approx const& rhs ) {\n            return static_cast<double>(lhs) > rhs.m_value || lhs == rhs;\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        friend bool operator >= ( Approx const& lhs, T const& rhs ) {\n            return lhs.m_value > static_cast<double>(rhs) || lhs == rhs;\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        Approx& epsilon( T const& newEpsilon ) {\n            double epsilonAsDouble = static_cast<double>(newEpsilon);\n            setEpsilon(epsilonAsDouble);\n            return *this;\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        Approx& margin( T const& newMargin ) {\n            double marginAsDouble = static_cast<double>(newMargin);\n            setMargin(marginAsDouble);\n            return *this;\n        }\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        Approx& scale( T const& newScale ) {\n            m_scale = static_cast<double>(newScale);\n            return *this;\n        }\n\n        std::string toString() const;\n\n    private:\n        double m_epsilon;\n        double m_margin;\n        double m_scale;\n        double m_value;\n    };\n} // end namespace Detail\n\nnamespace literals {\n    Detail::Approx operator \"\" _a(long double val);\n    Detail::Approx operator \"\" _a(unsigned long long val);\n} // end namespace literals\n\ntemplate<>\nstruct StringMaker<Catch::Detail::Approx> {\n    static std::string convert(Catch::Detail::Approx const& value);\n};\n\n} // end namespace Catch\n\n// end catch_approx.h\n// start catch_string_manip.h\n\n#include <string>\n#include <iosfwd>\n#include <vector>\n\nnamespace Catch {\n\n    bool startsWith( std::string const& s, std::string const& prefix );\n    bool startsWith( std::string const& s, char prefix );\n    bool endsWith( std::string const& s, std::string const& suffix );\n    bool endsWith( std::string const& s, char suffix );\n    bool contains( std::string const& s, std::string const& infix );\n    void toLowerInPlace( std::string& s );\n    std::string toLower( std::string const& s );\n    //! Returns a new string without whitespace at the start/end\n    std::string trim( std::string const& str );\n    //! Returns a substring of the original ref without whitespace. Beware lifetimes!\n    StringRef trim(StringRef ref);\n\n    // !!! Be aware, returns refs into original string - make sure original string outlives them\n    std::vector<StringRef> splitStringRef( StringRef str, char delimiter );\n    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );\n\n    struct pluralise {\n        pluralise( std::size_t count, std::string const& label );\n\n        friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser );\n\n        std::size_t m_count;\n        std::string m_label;\n    };\n}\n\n// end catch_string_manip.h\n#ifndef CATCH_CONFIG_DISABLE_MATCHERS\n// start catch_capture_matchers.h\n\n// start catch_matchers.h\n\n#include <string>\n#include <vector>\n\nnamespace Catch {\nnamespace Matchers {\n    namespace Impl {\n\n        template<typename ArgT> struct MatchAllOf;\n        template<typename ArgT> struct MatchAnyOf;\n        template<typename ArgT> struct MatchNotOf;\n\n        class MatcherUntypedBase {\n        public:\n            MatcherUntypedBase() = default;\n            MatcherUntypedBase ( MatcherUntypedBase const& ) = default;\n            MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete;\n            std::string toString() const;\n\n        protected:\n            virtual ~MatcherUntypedBase();\n            virtual std::string describe() const = 0;\n            mutable std::string m_cachedToString;\n        };\n\n#ifdef __clang__\n#    pragma clang diagnostic push\n#    pragma clang diagnostic ignored \"-Wnon-virtual-dtor\"\n#endif\n\n        template<typename ObjectT>\n        struct MatcherMethod {\n            virtual bool match( ObjectT const& arg ) const = 0;\n        };\n\n#if defined(__OBJC__)\n        // Hack to fix Catch GH issue #1661. Could use id for generic Object support.\n        // use of const for Object pointers is very uncommon and under ARC it causes some kind of signature mismatch that breaks compilation\n        template<>\n        struct MatcherMethod<NSString*> {\n            virtual bool match( NSString* arg ) const = 0;\n        };\n#endif\n\n#ifdef __clang__\n#    pragma clang diagnostic pop\n#endif\n\n        template<typename T>\n        struct MatcherBase : MatcherUntypedBase, MatcherMethod<T> {\n\n            MatchAllOf<T> operator && ( MatcherBase const& other ) const;\n            MatchAnyOf<T> operator || ( MatcherBase const& other ) const;\n            MatchNotOf<T> operator ! () const;\n        };\n\n        template<typename ArgT>\n        struct MatchAllOf : MatcherBase<ArgT> {\n            bool match( ArgT const& arg ) const override {\n                for( auto matcher : m_matchers ) {\n                    if (!matcher->match(arg))\n                        return false;\n                }\n                return true;\n            }\n            std::string describe() const override {\n                std::string description;\n                description.reserve( 4 + m_matchers.size()*32 );\n                description += \"( \";\n                bool first = true;\n                for( auto matcher : m_matchers ) {\n                    if( first )\n                        first = false;\n                    else\n                        description += \" and \";\n                    description += matcher->toString();\n                }\n                description += \" )\";\n                return description;\n            }\n\n            MatchAllOf<ArgT> operator && ( MatcherBase<ArgT> const& other ) {\n                auto copy(*this);\n                copy.m_matchers.push_back( &other );\n                return copy;\n            }\n\n            std::vector<MatcherBase<ArgT> const*> m_matchers;\n        };\n        template<typename ArgT>\n        struct MatchAnyOf : MatcherBase<ArgT> {\n\n            bool match( ArgT const& arg ) const override {\n                for( auto matcher : m_matchers ) {\n                    if (matcher->match(arg))\n                        return true;\n                }\n                return false;\n            }\n            std::string describe() const override {\n                std::string description;\n                description.reserve( 4 + m_matchers.size()*32 );\n                description += \"( \";\n                bool first = true;\n                for( auto matcher : m_matchers ) {\n                    if( first )\n                        first = false;\n                    else\n                        description += \" or \";\n                    description += matcher->toString();\n                }\n                description += \" )\";\n                return description;\n            }\n\n            MatchAnyOf<ArgT> operator || ( MatcherBase<ArgT> const& other ) {\n                auto copy(*this);\n                copy.m_matchers.push_back( &other );\n                return copy;\n            }\n\n            std::vector<MatcherBase<ArgT> const*> m_matchers;\n        };\n\n        template<typename ArgT>\n        struct MatchNotOf : MatcherBase<ArgT> {\n\n            MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {}\n\n            bool match( ArgT const& arg ) const override {\n                return !m_underlyingMatcher.match( arg );\n            }\n\n            std::string describe() const override {\n                return \"not \" + m_underlyingMatcher.toString();\n            }\n            MatcherBase<ArgT> const& m_underlyingMatcher;\n        };\n\n        template<typename T>\n        MatchAllOf<T> MatcherBase<T>::operator && ( MatcherBase const& other ) const {\n            return MatchAllOf<T>() && *this && other;\n        }\n        template<typename T>\n        MatchAnyOf<T> MatcherBase<T>::operator || ( MatcherBase const& other ) const {\n            return MatchAnyOf<T>() || *this || other;\n        }\n        template<typename T>\n        MatchNotOf<T> MatcherBase<T>::operator ! () const {\n            return MatchNotOf<T>( *this );\n        }\n\n    } // namespace Impl\n\n} // namespace Matchers\n\nusing namespace Matchers;\nusing Matchers::Impl::MatcherBase;\n\n} // namespace Catch\n\n// end catch_matchers.h\n// start catch_matchers_exception.hpp\n\nnamespace Catch {\nnamespace Matchers {\nnamespace Exception {\n\nclass ExceptionMessageMatcher : public MatcherBase<std::exception> {\n    std::string m_message;\npublic:\n\n    ExceptionMessageMatcher(std::string const& message):\n        m_message(message)\n    {}\n\n    bool match(std::exception const& ex) const override;\n\n    std::string describe() const override;\n};\n\n} // namespace Exception\n\nException::ExceptionMessageMatcher Message(std::string const& message);\n\n} // namespace Matchers\n} // namespace Catch\n\n// end catch_matchers_exception.hpp\n// start catch_matchers_floating.h\n\nnamespace Catch {\nnamespace Matchers {\n\n    namespace Floating {\n\n        enum class FloatingPointKind : uint8_t;\n\n        struct WithinAbsMatcher : MatcherBase<double> {\n            WithinAbsMatcher(double target, double margin);\n            bool match(double const& matchee) const override;\n            std::string describe() const override;\n        private:\n            double m_target;\n            double m_margin;\n        };\n\n        struct WithinUlpsMatcher : MatcherBase<double> {\n            WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType);\n            bool match(double const& matchee) const override;\n            std::string describe() const override;\n        private:\n            double m_target;\n            uint64_t m_ulps;\n            FloatingPointKind m_type;\n        };\n\n        // Given IEEE-754 format for floats and doubles, we can assume\n        // that float -> double promotion is lossless. Given this, we can\n        // assume that if we do the standard relative comparison of\n        // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get\n        // the same result if we do this for floats, as if we do this for\n        // doubles that were promoted from floats.\n        struct WithinRelMatcher : MatcherBase<double> {\n            WithinRelMatcher(double target, double epsilon);\n            bool match(double const& matchee) const override;\n            std::string describe() const override;\n        private:\n            double m_target;\n            double m_epsilon;\n        };\n\n    } // namespace Floating\n\n    // The following functions create the actual matcher objects.\n    // This allows the types to be inferred\n    Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff);\n    Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff);\n    Floating::WithinAbsMatcher WithinAbs(double target, double margin);\n    Floating::WithinRelMatcher WithinRel(double target, double eps);\n    // defaults epsilon to 100*numeric_limits<double>::epsilon()\n    Floating::WithinRelMatcher WithinRel(double target);\n    Floating::WithinRelMatcher WithinRel(float target, float eps);\n    // defaults epsilon to 100*numeric_limits<float>::epsilon()\n    Floating::WithinRelMatcher WithinRel(float target);\n\n} // namespace Matchers\n} // namespace Catch\n\n// end catch_matchers_floating.h\n// start catch_matchers_generic.hpp\n\n#include <functional>\n#include <string>\n\nnamespace Catch {\nnamespace Matchers {\nnamespace Generic {\n\nnamespace Detail {\n    std::string finalizeDescription(const std::string& desc);\n}\n\ntemplate <typename T>\nclass PredicateMatcher : public MatcherBase<T> {\n    std::function<bool(T const&)> m_predicate;\n    std::string m_description;\npublic:\n\n    PredicateMatcher(std::function<bool(T const&)> const& elem, std::string const& descr)\n        :m_predicate(std::move(elem)),\n        m_description(Detail::finalizeDescription(descr))\n    {}\n\n    bool match( T const& item ) const override {\n        return m_predicate(item);\n    }\n\n    std::string describe() const override {\n        return m_description;\n    }\n};\n\n} // namespace Generic\n\n    // The following functions create the actual matcher objects.\n    // The user has to explicitly specify type to the function, because\n    // inferring std::function<bool(T const&)> is hard (but possible) and\n    // requires a lot of TMP.\n    template<typename T>\n    Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, std::string const& description = \"\") {\n        return Generic::PredicateMatcher<T>(predicate, description);\n    }\n\n} // namespace Matchers\n} // namespace Catch\n\n// end catch_matchers_generic.hpp\n// start catch_matchers_string.h\n\n#include <string>\n\nnamespace Catch {\nnamespace Matchers {\n\n    namespace StdString {\n\n        struct CasedString\n        {\n            CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity );\n            std::string adjustString( std::string const& str ) const;\n            std::string caseSensitivitySuffix() const;\n\n            CaseSensitive::Choice m_caseSensitivity;\n            std::string m_str;\n        };\n\n        struct StringMatcherBase : MatcherBase<std::string> {\n            StringMatcherBase( std::string const& operation, CasedString const& comparator );\n            std::string describe() const override;\n\n            CasedString m_comparator;\n            std::string m_operation;\n        };\n\n        struct EqualsMatcher : StringMatcherBase {\n            EqualsMatcher( CasedString const& comparator );\n            bool match( std::string const& source ) const override;\n        };\n        struct ContainsMatcher : StringMatcherBase {\n            ContainsMatcher( CasedString const& comparator );\n            bool match( std::string const& source ) const override;\n        };\n        struct StartsWithMatcher : StringMatcherBase {\n            StartsWithMatcher( CasedString const& comparator );\n            bool match( std::string const& source ) const override;\n        };\n        struct EndsWithMatcher : StringMatcherBase {\n            EndsWithMatcher( CasedString const& comparator );\n            bool match( std::string const& source ) const override;\n        };\n\n        struct RegexMatcher : MatcherBase<std::string> {\n            RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity );\n            bool match( std::string const& matchee ) const override;\n            std::string describe() const override;\n\n        private:\n            std::string m_regex;\n            CaseSensitive::Choice m_caseSensitivity;\n        };\n\n    } // namespace StdString\n\n    // The following functions create the actual matcher objects.\n    // This allows the types to be inferred\n\n    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );\n    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );\n    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );\n    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );\n    StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );\n\n} // namespace Matchers\n} // namespace Catch\n\n// end catch_matchers_string.h\n// start catch_matchers_vector.h\n\n#include <algorithm>\n\nnamespace Catch {\nnamespace Matchers {\n\n    namespace Vector {\n        template<typename T, typename Alloc>\n        struct ContainsElementMatcher : MatcherBase<std::vector<T, Alloc>> {\n\n            ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {}\n\n            bool match(std::vector<T, Alloc> const &v) const override {\n                for (auto const& el : v) {\n                    if (el == m_comparator) {\n                        return true;\n                    }\n                }\n                return false;\n            }\n\n            std::string describe() const override {\n                return \"Contains: \" + ::Catch::Detail::stringify( m_comparator );\n            }\n\n            T const& m_comparator;\n        };\n\n        template<typename T, typename AllocComp, typename AllocMatch>\n        struct ContainsMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n\n            ContainsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator( comparator ) {}\n\n            bool match(std::vector<T, AllocMatch> const &v) const override {\n                // !TBD: see note in EqualsMatcher\n                if (m_comparator.size() > v.size())\n                    return false;\n                for (auto const& comparator : m_comparator) {\n                    auto present = false;\n                    for (const auto& el : v) {\n                        if (el == comparator) {\n                            present = true;\n                            break;\n                        }\n                    }\n                    if (!present) {\n                        return false;\n                    }\n                }\n                return true;\n            }\n            std::string describe() const override {\n                return \"Contains: \" + ::Catch::Detail::stringify( m_comparator );\n            }\n\n            std::vector<T, AllocComp> const& m_comparator;\n        };\n\n        template<typename T, typename AllocComp, typename AllocMatch>\n        struct EqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n\n            EqualsMatcher(std::vector<T, AllocComp> const &comparator) : m_comparator( comparator ) {}\n\n            bool match(std::vector<T, AllocMatch> const &v) const override {\n                // !TBD: This currently works if all elements can be compared using !=\n                // - a more general approach would be via a compare template that defaults\n                // to using !=. but could be specialised for, e.g. std::vector<T, Alloc> etc\n                // - then just call that directly\n                if (m_comparator.size() != v.size())\n                    return false;\n                for (std::size_t i = 0; i < v.size(); ++i)\n                    if (m_comparator[i] != v[i])\n                        return false;\n                return true;\n            }\n            std::string describe() const override {\n                return \"Equals: \" + ::Catch::Detail::stringify( m_comparator );\n            }\n            std::vector<T, AllocComp> const& m_comparator;\n        };\n\n        template<typename T, typename AllocComp, typename AllocMatch>\n        struct ApproxMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n\n            ApproxMatcher(std::vector<T, AllocComp> const& comparator) : m_comparator( comparator ) {}\n\n            bool match(std::vector<T, AllocMatch> const &v) const override {\n                if (m_comparator.size() != v.size())\n                    return false;\n                for (std::size_t i = 0; i < v.size(); ++i)\n                    if (m_comparator[i] != approx(v[i]))\n                        return false;\n                return true;\n            }\n            std::string describe() const override {\n                return \"is approx: \" + ::Catch::Detail::stringify( m_comparator );\n            }\n            template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n            ApproxMatcher& epsilon( T const& newEpsilon ) {\n                approx.epsilon(newEpsilon);\n                return *this;\n            }\n            template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n            ApproxMatcher& margin( T const& newMargin ) {\n                approx.margin(newMargin);\n                return *this;\n            }\n            template <typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n            ApproxMatcher& scale( T const& newScale ) {\n                approx.scale(newScale);\n                return *this;\n            }\n\n            std::vector<T, AllocComp> const& m_comparator;\n            mutable Catch::Detail::Approx approx = Catch::Detail::Approx::custom();\n        };\n\n        template<typename T, typename AllocComp, typename AllocMatch>\n        struct UnorderedEqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n            UnorderedEqualsMatcher(std::vector<T, AllocComp> const& target) : m_target(target) {}\n            bool match(std::vector<T, AllocMatch> const& vec) const override {\n                if (m_target.size() != vec.size()) {\n                    return false;\n                }\n                return std::is_permutation(m_target.begin(), m_target.end(), vec.begin());\n            }\n\n            std::string describe() const override {\n                return \"UnorderedEquals: \" + ::Catch::Detail::stringify(m_target);\n            }\n        private:\n            std::vector<T, AllocComp> const& m_target;\n        };\n\n    } // namespace Vector\n\n    // The following functions create the actual matcher objects.\n    // This allows the types to be inferred\n\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    Vector::ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) {\n        return Vector::ContainsMatcher<T, AllocComp, AllocMatch>( comparator );\n    }\n\n    template<typename T, typename Alloc = std::allocator<T>>\n    Vector::ContainsElementMatcher<T, Alloc> VectorContains( T const& comparator ) {\n        return Vector::ContainsElementMatcher<T, Alloc>( comparator );\n    }\n\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    Vector::EqualsMatcher<T, AllocComp, AllocMatch> Equals( std::vector<T, AllocComp> const& comparator ) {\n        return Vector::EqualsMatcher<T, AllocComp, AllocMatch>( comparator );\n    }\n\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    Vector::ApproxMatcher<T, AllocComp, AllocMatch> Approx( std::vector<T, AllocComp> const& comparator ) {\n        return Vector::ApproxMatcher<T, AllocComp, AllocMatch>( comparator );\n    }\n\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(std::vector<T, AllocComp> const& target) {\n        return Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch>( target );\n    }\n\n} // namespace Matchers\n} // namespace Catch\n\n// end catch_matchers_vector.h\nnamespace Catch {\n\n    template<typename ArgT, typename MatcherT>\n    class MatchExpr : public ITransientExpression {\n        ArgT const& m_arg;\n        MatcherT m_matcher;\n        StringRef m_matcherString;\n    public:\n        MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString )\n        :   ITransientExpression{ true, matcher.match( arg ) },\n            m_arg( arg ),\n            m_matcher( matcher ),\n            m_matcherString( matcherString )\n        {}\n\n        void streamReconstructedExpression( std::ostream &os ) const override {\n            auto matcherAsString = m_matcher.toString();\n            os << Catch::Detail::stringify( m_arg ) << ' ';\n            if( matcherAsString == Detail::unprintableString )\n                os << m_matcherString;\n            else\n                os << matcherAsString;\n        }\n    };\n\n    using StringMatcher = Matchers::Impl::MatcherBase<std::string>;\n\n    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString  );\n\n    template<typename ArgT, typename MatcherT>\n    auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString  ) -> MatchExpr<ArgT, MatcherT> {\n        return MatchExpr<ArgT, MatcherT>( arg, matcher, matcherString );\n    }\n\n} // namespace Catch\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) \", \" CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \\\n        INTERNAL_CATCH_TRY { \\\n            catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \\\n        } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \\\n        INTERNAL_CATCH_REACT( catchAssertionHandler ) \\\n    } while( false )\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \\\n    do { \\\n        Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) \", \" CATCH_INTERNAL_STRINGIFY(exceptionType) \", \" CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                static_cast<void>(__VA_ARGS__ ); \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( exceptionType const& ex ) { \\\n                catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \\\n            } \\\n            catch( ... ) { \\\n                catchAssertionHandler.handleUnexpectedInflightException(); \\\n            } \\\n        else \\\n            catchAssertionHandler.handleThrowingCallSkipped(); \\\n        INTERNAL_CATCH_REACT( catchAssertionHandler ) \\\n    } while( false )\n\n// end catch_capture_matchers.h\n#endif\n// start catch_generators.hpp\n\n// start catch_interfaces_generatortracker.h\n\n\n#include <memory>\n\nnamespace Catch {\n\n    namespace Generators {\n        class GeneratorUntypedBase {\n        public:\n            GeneratorUntypedBase() = default;\n            virtual ~GeneratorUntypedBase();\n            // Attempts to move the generator to the next element\n             //\n             // Returns true iff the move succeeded (and a valid element\n             // can be retrieved).\n            virtual bool next() = 0;\n        };\n        using GeneratorBasePtr = std::unique_ptr<GeneratorUntypedBase>;\n\n    } // namespace Generators\n\n    struct IGeneratorTracker {\n        virtual ~IGeneratorTracker();\n        virtual auto hasGenerator() const -> bool = 0;\n        virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0;\n        virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0;\n    };\n\n} // namespace Catch\n\n// end catch_interfaces_generatortracker.h\n// start catch_enforce.h\n\n#include <exception>\n\nnamespace Catch {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    template <typename Ex>\n    [[noreturn]]\n    void throw_exception(Ex const& e) {\n        throw e;\n    }\n#else // ^^ Exceptions are enabled //  Exceptions are disabled vv\n    [[noreturn]]\n    void throw_exception(std::exception const& e);\n#endif\n\n    [[noreturn]]\n    void throw_logic_error(std::string const& msg);\n    [[noreturn]]\n    void throw_domain_error(std::string const& msg);\n    [[noreturn]]\n    void throw_runtime_error(std::string const& msg);\n\n} // namespace Catch;\n\n#define CATCH_MAKE_MSG(...) \\\n    (Catch::ReusableStringStream() << __VA_ARGS__).str()\n\n#define CATCH_INTERNAL_ERROR(...) \\\n    Catch::throw_logic_error(CATCH_MAKE_MSG( CATCH_INTERNAL_LINEINFO << \": Internal Catch2 error: \" << __VA_ARGS__))\n\n#define CATCH_ERROR(...) \\\n    Catch::throw_domain_error(CATCH_MAKE_MSG( __VA_ARGS__ ))\n\n#define CATCH_RUNTIME_ERROR(...) \\\n    Catch::throw_runtime_error(CATCH_MAKE_MSG( __VA_ARGS__ ))\n\n#define CATCH_ENFORCE( condition, ... ) \\\n    do{ if( !(condition) ) CATCH_ERROR( __VA_ARGS__ ); } while(false)\n\n// end catch_enforce.h\n#include <memory>\n#include <vector>\n#include <cassert>\n\n#include <utility>\n#include <exception>\n\nnamespace Catch {\n\nclass GeneratorException : public std::exception {\n    const char* const m_msg = \"\";\n\npublic:\n    GeneratorException(const char* msg):\n        m_msg(msg)\n    {}\n\n    const char* what() const noexcept override final;\n};\n\nnamespace Generators {\n\n    // !TBD move this into its own location?\n    namespace pf{\n        template<typename T, typename... Args>\n        std::unique_ptr<T> make_unique( Args&&... args ) {\n            return std::unique_ptr<T>(new T(std::forward<Args>(args)...));\n        }\n    }\n\n    template<typename T>\n    struct IGenerator : GeneratorUntypedBase {\n        virtual ~IGenerator() = default;\n\n        // Returns the current element of the generator\n        //\n        // \\Precondition The generator is either freshly constructed,\n        // or the last call to `next()` returned true\n        virtual T const& get() const = 0;\n        using type = T;\n    };\n\n    template<typename T>\n    class SingleValueGenerator final : public IGenerator<T> {\n        T m_value;\n    public:\n        SingleValueGenerator(T&& value) : m_value(std::move(value)) {}\n\n        T const& get() const override {\n            return m_value;\n        }\n        bool next() override {\n            return false;\n        }\n    };\n\n    template<typename T>\n    class FixedValuesGenerator final : public IGenerator<T> {\n        static_assert(!std::is_same<T, bool>::value,\n            \"FixedValuesGenerator does not support bools because of std::vector<bool>\"\n            \"specialization, use SingleValue Generator instead.\");\n        std::vector<T> m_values;\n        size_t m_idx = 0;\n    public:\n        FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {}\n\n        T const& get() const override {\n            return m_values[m_idx];\n        }\n        bool next() override {\n            ++m_idx;\n            return m_idx < m_values.size();\n        }\n    };\n\n    template <typename T>\n    class GeneratorWrapper final {\n        std::unique_ptr<IGenerator<T>> m_generator;\n    public:\n        GeneratorWrapper(std::unique_ptr<IGenerator<T>> generator):\n            m_generator(std::move(generator))\n        {}\n        T const& get() const {\n            return m_generator->get();\n        }\n        bool next() {\n            return m_generator->next();\n        }\n    };\n\n    template <typename T>\n    GeneratorWrapper<T> value(T&& value) {\n        return GeneratorWrapper<T>(pf::make_unique<SingleValueGenerator<T>>(std::forward<T>(value)));\n    }\n    template <typename T>\n    GeneratorWrapper<T> values(std::initializer_list<T> values) {\n        return GeneratorWrapper<T>(pf::make_unique<FixedValuesGenerator<T>>(values));\n    }\n\n    template<typename T>\n    class Generators : public IGenerator<T> {\n        std::vector<GeneratorWrapper<T>> m_generators;\n        size_t m_current = 0;\n\n        void populate(GeneratorWrapper<T>&& generator) {\n            m_generators.emplace_back(std::move(generator));\n        }\n        void populate(T&& val) {\n            m_generators.emplace_back(value(std::forward<T>(val)));\n        }\n        template<typename U>\n        void populate(U&& val) {\n            populate(T(std::forward<U>(val)));\n        }\n        template<typename U, typename... Gs>\n        void populate(U&& valueOrGenerator, Gs &&... moreGenerators) {\n            populate(std::forward<U>(valueOrGenerator));\n            populate(std::forward<Gs>(moreGenerators)...);\n        }\n\n    public:\n        template <typename... Gs>\n        Generators(Gs &&... moreGenerators) {\n            m_generators.reserve(sizeof...(Gs));\n            populate(std::forward<Gs>(moreGenerators)...);\n        }\n\n        T const& get() const override {\n            return m_generators[m_current].get();\n        }\n\n        bool next() override {\n            if (m_current >= m_generators.size()) {\n                return false;\n            }\n            const bool current_status = m_generators[m_current].next();\n            if (!current_status) {\n                ++m_current;\n            }\n            return m_current < m_generators.size();\n        }\n    };\n\n    template<typename... Ts>\n    GeneratorWrapper<std::tuple<Ts...>> table( std::initializer_list<std::tuple<typename std::decay<Ts>::type...>> tuples ) {\n        return values<std::tuple<Ts...>>( tuples );\n    }\n\n    // Tag type to signal that a generator sequence should convert arguments to a specific type\n    template <typename T>\n    struct as {};\n\n    template<typename T, typename... Gs>\n    auto makeGenerators( GeneratorWrapper<T>&& generator, Gs &&... moreGenerators ) -> Generators<T> {\n        return Generators<T>(std::move(generator), std::forward<Gs>(moreGenerators)...);\n    }\n    template<typename T>\n    auto makeGenerators( GeneratorWrapper<T>&& generator ) -> Generators<T> {\n        return Generators<T>(std::move(generator));\n    }\n    template<typename T, typename... Gs>\n    auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<T> {\n        return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... );\n    }\n    template<typename T, typename U, typename... Gs>\n    auto makeGenerators( as<T>, U&& val, Gs &&... moreGenerators ) -> Generators<T> {\n        return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... );\n    }\n\n    auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker&;\n\n    template<typename L>\n    // Note: The type after -> is weird, because VS2015 cannot parse\n    //       the expression used in the typedef inside, when it is in\n    //       return type. Yeah.\n    auto generate( StringRef generatorName, SourceLineInfo const& lineInfo, L const& generatorExpression ) -> decltype(std::declval<decltype(generatorExpression())>().get()) {\n        using UnderlyingType = typename decltype(generatorExpression())::type;\n\n        IGeneratorTracker& tracker = acquireGeneratorTracker( generatorName, lineInfo );\n        if (!tracker.hasGenerator()) {\n            tracker.setGenerator(pf::make_unique<Generators<UnderlyingType>>(generatorExpression()));\n        }\n\n        auto const& generator = static_cast<IGenerator<UnderlyingType> const&>( *tracker.getGenerator() );\n        return generator.get();\n    }\n\n} // namespace Generators\n} // namespace Catch\n\n#define GENERATE( ... ) \\\n    Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \\\n                                 CATCH_INTERNAL_LINEINFO, \\\n                                 [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace)\n#define GENERATE_COPY( ... ) \\\n    Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \\\n                                 CATCH_INTERNAL_LINEINFO, \\\n                                 [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace)\n#define GENERATE_REF( ... ) \\\n    Catch::Generators::generate( INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \\\n                                 CATCH_INTERNAL_LINEINFO, \\\n                                 [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace)\n\n// end catch_generators.hpp\n// start catch_generators_generic.hpp\n\nnamespace Catch {\nnamespace Generators {\n\n    template <typename T>\n    class TakeGenerator : public IGenerator<T> {\n        GeneratorWrapper<T> m_generator;\n        size_t m_returned = 0;\n        size_t m_target;\n    public:\n        TakeGenerator(size_t target, GeneratorWrapper<T>&& generator):\n            m_generator(std::move(generator)),\n            m_target(target)\n        {\n            assert(target != 0 && \"Empty generators are not allowed\");\n        }\n        T const& get() const override {\n            return m_generator.get();\n        }\n        bool next() override {\n            ++m_returned;\n            if (m_returned >= m_target) {\n                return false;\n            }\n\n            const auto success = m_generator.next();\n            // If the underlying generator does not contain enough values\n            // then we cut short as well\n            if (!success) {\n                m_returned = m_target;\n            }\n            return success;\n        }\n    };\n\n    template <typename T>\n    GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<T>(pf::make_unique<TakeGenerator<T>>(target, std::move(generator)));\n    }\n\n    template <typename T, typename Predicate>\n    class FilterGenerator : public IGenerator<T> {\n        GeneratorWrapper<T> m_generator;\n        Predicate m_predicate;\n    public:\n        template <typename P = Predicate>\n        FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator):\n            m_generator(std::move(generator)),\n            m_predicate(std::forward<P>(pred))\n        {\n            if (!m_predicate(m_generator.get())) {\n                // It might happen that there are no values that pass the\n                // filter. In that case we throw an exception.\n                auto has_initial_value = nextImpl();\n                if (!has_initial_value) {\n                    Catch::throw_exception(GeneratorException(\"No valid value found in filtered generator\"));\n                }\n            }\n        }\n\n        T const& get() const override {\n            return m_generator.get();\n        }\n\n        bool next() override {\n            return nextImpl();\n        }\n\n    private:\n        bool nextImpl() {\n            bool success = m_generator.next();\n            if (!success) {\n                return false;\n            }\n            while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true);\n            return success;\n        }\n    };\n\n    template <typename T, typename Predicate>\n    GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<T>(std::unique_ptr<IGenerator<T>>(pf::make_unique<FilterGenerator<T, Predicate>>(std::forward<Predicate>(pred), std::move(generator))));\n    }\n\n    template <typename T>\n    class RepeatGenerator : public IGenerator<T> {\n        static_assert(!std::is_same<T, bool>::value,\n            \"RepeatGenerator currently does not support bools\"\n            \"because of std::vector<bool> specialization\");\n        GeneratorWrapper<T> m_generator;\n        mutable std::vector<T> m_returned;\n        size_t m_target_repeats;\n        size_t m_current_repeat = 0;\n        size_t m_repeat_index = 0;\n    public:\n        RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator):\n            m_generator(std::move(generator)),\n            m_target_repeats(repeats)\n        {\n            assert(m_target_repeats > 0 && \"Repeat generator must repeat at least once\");\n        }\n\n        T const& get() const override {\n            if (m_current_repeat == 0) {\n                m_returned.push_back(m_generator.get());\n                return m_returned.back();\n            }\n            return m_returned[m_repeat_index];\n        }\n\n        bool next() override {\n            // There are 2 basic cases:\n            // 1) We are still reading the generator\n            // 2) We are reading our own cache\n\n            // In the first case, we need to poke the underlying generator.\n            // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache\n            if (m_current_repeat == 0) {\n                const auto success = m_generator.next();\n                if (!success) {\n                    ++m_current_repeat;\n                }\n                return m_current_repeat < m_target_repeats;\n            }\n\n            // In the second case, we need to move indices forward and check that we haven't run up against the end\n            ++m_repeat_index;\n            if (m_repeat_index == m_returned.size()) {\n                m_repeat_index = 0;\n                ++m_current_repeat;\n            }\n            return m_current_repeat < m_target_repeats;\n        }\n    };\n\n    template <typename T>\n    GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<T>(pf::make_unique<RepeatGenerator<T>>(repeats, std::move(generator)));\n    }\n\n    template <typename T, typename U, typename Func>\n    class MapGenerator : public IGenerator<T> {\n        // TBD: provide static assert for mapping function, for friendly error message\n        GeneratorWrapper<U> m_generator;\n        Func m_function;\n        // To avoid returning dangling reference, we have to save the values\n        T m_cache;\n    public:\n        template <typename F2 = Func>\n        MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) :\n            m_generator(std::move(generator)),\n            m_function(std::forward<F2>(function)),\n            m_cache(m_function(m_generator.get()))\n        {}\n\n        T const& get() const override {\n            return m_cache;\n        }\n        bool next() override {\n            const auto success = m_generator.next();\n            if (success) {\n                m_cache = m_function(m_generator.get());\n            }\n            return success;\n        }\n    };\n\n    template <typename Func, typename U, typename T = FunctionReturnType<Func, U>>\n    GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) {\n        return GeneratorWrapper<T>(\n            pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator))\n        );\n    }\n\n    template <typename T, typename U, typename Func>\n    GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) {\n        return GeneratorWrapper<T>(\n            pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator))\n        );\n    }\n\n    template <typename T>\n    class ChunkGenerator final : public IGenerator<std::vector<T>> {\n        std::vector<T> m_chunk;\n        size_t m_chunk_size;\n        GeneratorWrapper<T> m_generator;\n        bool m_used_up = false;\n    public:\n        ChunkGenerator(size_t size, GeneratorWrapper<T> generator) :\n            m_chunk_size(size), m_generator(std::move(generator))\n        {\n            m_chunk.reserve(m_chunk_size);\n            if (m_chunk_size != 0) {\n                m_chunk.push_back(m_generator.get());\n                for (size_t i = 1; i < m_chunk_size; ++i) {\n                    if (!m_generator.next()) {\n                        Catch::throw_exception(GeneratorException(\"Not enough values to initialize the first chunk\"));\n                    }\n                    m_chunk.push_back(m_generator.get());\n                }\n            }\n        }\n        std::vector<T> const& get() const override {\n            return m_chunk;\n        }\n        bool next() override {\n            m_chunk.clear();\n            for (size_t idx = 0; idx < m_chunk_size; ++idx) {\n                if (!m_generator.next()) {\n                    return false;\n                }\n                m_chunk.push_back(m_generator.get());\n            }\n            return true;\n        }\n    };\n\n    template <typename T>\n    GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<std::vector<T>>(\n            pf::make_unique<ChunkGenerator<T>>(size, std::move(generator))\n        );\n    }\n\n} // namespace Generators\n} // namespace Catch\n\n// end catch_generators_generic.hpp\n// start catch_generators_specific.hpp\n\n// start catch_context.h\n\n#include <memory>\n\nnamespace Catch {\n\n    struct IResultCapture;\n    struct IRunner;\n    struct IConfig;\n    struct IMutableContext;\n\n    using IConfigPtr = std::shared_ptr<IConfig const>;\n\n    struct IContext\n    {\n        virtual ~IContext();\n\n        virtual IResultCapture* getResultCapture() = 0;\n        virtual IRunner* getRunner() = 0;\n        virtual IConfigPtr const& getConfig() const = 0;\n    };\n\n    struct IMutableContext : IContext\n    {\n        virtual ~IMutableContext();\n        virtual void setResultCapture( IResultCapture* resultCapture ) = 0;\n        virtual void setRunner( IRunner* runner ) = 0;\n        virtual void setConfig( IConfigPtr const& config ) = 0;\n\n    private:\n        static IMutableContext *currentContext;\n        friend IMutableContext& getCurrentMutableContext();\n        friend void cleanUpContext();\n        static void createContext();\n    };\n\n    inline IMutableContext& getCurrentMutableContext()\n    {\n        if( !IMutableContext::currentContext )\n            IMutableContext::createContext();\n        // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)\n        return *IMutableContext::currentContext;\n    }\n\n    inline IContext& getCurrentContext()\n    {\n        return getCurrentMutableContext();\n    }\n\n    void cleanUpContext();\n\n    class SimplePcg32;\n    SimplePcg32& rng();\n}\n\n// end catch_context.h\n// start catch_interfaces_config.h\n\n// start catch_option.hpp\n\nnamespace Catch {\n\n    // An optional type\n    template<typename T>\n    class Option {\n    public:\n        Option() : nullableValue( nullptr ) {}\n        Option( T const& _value )\n        : nullableValue( new( storage ) T( _value ) )\n        {}\n        Option( Option const& _other )\n        : nullableValue( _other ? new( storage ) T( *_other ) : nullptr )\n        {}\n\n        ~Option() {\n            reset();\n        }\n\n        Option& operator= ( Option const& _other ) {\n            if( &_other != this ) {\n                reset();\n                if( _other )\n                    nullableValue = new( storage ) T( *_other );\n            }\n            return *this;\n        }\n        Option& operator = ( T const& _value ) {\n            reset();\n            nullableValue = new( storage ) T( _value );\n            return *this;\n        }\n\n        void reset() {\n            if( nullableValue )\n                nullableValue->~T();\n            nullableValue = nullptr;\n        }\n\n        T& operator*() { return *nullableValue; }\n        T const& operator*() const { return *nullableValue; }\n        T* operator->() { return nullableValue; }\n        const T* operator->() const { return nullableValue; }\n\n        T valueOr( T const& defaultValue ) const {\n            return nullableValue ? *nullableValue : defaultValue;\n        }\n\n        bool some() const { return nullableValue != nullptr; }\n        bool none() const { return nullableValue == nullptr; }\n\n        bool operator !() const { return nullableValue == nullptr; }\n        explicit operator bool() const {\n            return some();\n        }\n\n    private:\n        T *nullableValue;\n        alignas(alignof(T)) char storage[sizeof(T)];\n    };\n\n} // end namespace Catch\n\n// end catch_option.hpp\n#include <chrono>\n#include <iosfwd>\n#include <string>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\n\n    enum class Verbosity {\n        Quiet = 0,\n        Normal,\n        High\n    };\n\n    struct WarnAbout { enum What {\n        Nothing = 0x00,\n        NoAssertions = 0x01,\n        NoTests = 0x02\n    }; };\n\n    struct ShowDurations { enum OrNot {\n        DefaultForReporter,\n        Always,\n        Never\n    }; };\n    struct RunTests { enum InWhatOrder {\n        InDeclarationOrder,\n        InLexicographicalOrder,\n        InRandomOrder\n    }; };\n    struct UseColour { enum YesOrNo {\n        Auto,\n        Yes,\n        No\n    }; };\n    struct WaitForKeypress { enum When {\n        Never,\n        BeforeStart = 1,\n        BeforeExit = 2,\n        BeforeStartAndExit = BeforeStart | BeforeExit\n    }; };\n\n    class TestSpec;\n\n    struct IConfig : NonCopyable {\n\n        virtual ~IConfig();\n\n        virtual bool allowThrows() const = 0;\n        virtual std::ostream& stream() const = 0;\n        virtual std::string name() const = 0;\n        virtual bool includeSuccessfulResults() const = 0;\n        virtual bool shouldDebugBreak() const = 0;\n        virtual bool warnAboutMissingAssertions() const = 0;\n        virtual bool warnAboutNoTests() const = 0;\n        virtual int abortAfter() const = 0;\n        virtual bool showInvisibles() const = 0;\n        virtual ShowDurations::OrNot showDurations() const = 0;\n        virtual double minDuration() const = 0;\n        virtual TestSpec const& testSpec() const = 0;\n        virtual bool hasTestFilters() const = 0;\n        virtual std::vector<std::string> const& getTestsOrTags() const = 0;\n        virtual RunTests::InWhatOrder runOrder() const = 0;\n        virtual unsigned int rngSeed() const = 0;\n        virtual UseColour::YesOrNo useColour() const = 0;\n        virtual std::vector<std::string> const& getSectionsToRun() const = 0;\n        virtual Verbosity verbosity() const = 0;\n\n        virtual bool benchmarkNoAnalysis() const = 0;\n        virtual int benchmarkSamples() const = 0;\n        virtual double benchmarkConfidenceInterval() const = 0;\n        virtual unsigned int benchmarkResamples() const = 0;\n        virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0;\n    };\n\n    using IConfigPtr = std::shared_ptr<IConfig const>;\n}\n\n// end catch_interfaces_config.h\n// start catch_random_number_generator.h\n\n#include <cstdint>\n\nnamespace Catch {\n\n    // This is a simple implementation of C++11 Uniform Random Number\n    // Generator. It does not provide all operators, because Catch2\n    // does not use it, but it should behave as expected inside stdlib's\n    // distributions.\n    // The implementation is based on the PCG family (http://pcg-random.org)\n    class SimplePcg32 {\n        using state_type = std::uint64_t;\n    public:\n        using result_type = std::uint32_t;\n        static constexpr result_type (min)() {\n            return 0;\n        }\n        static constexpr result_type (max)() {\n            return static_cast<result_type>(-1);\n        }\n\n        // Provide some default initial state for the default constructor\n        SimplePcg32():SimplePcg32(0xed743cc4U) {}\n\n        explicit SimplePcg32(result_type seed_);\n\n        void seed(result_type seed_);\n        void discard(uint64_t skip);\n\n        result_type operator()();\n\n    private:\n        friend bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs);\n        friend bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs);\n\n        // In theory we also need operator<< and operator>>\n        // In practice we do not use them, so we will skip them for now\n\n        std::uint64_t m_state;\n        // This part of the state determines which \"stream\" of the numbers\n        // is chosen -- we take it as a constant for Catch2, so we only\n        // need to deal with seeding the main state.\n        // Picked by reading 8 bytes from `/dev/random` :-)\n        static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL;\n    };\n\n} // end namespace Catch\n\n// end catch_random_number_generator.h\n#include <random>\n\nnamespace Catch {\nnamespace Generators {\n\ntemplate <typename Float>\nclass RandomFloatingGenerator final : public IGenerator<Float> {\n    Catch::SimplePcg32& m_rng;\n    std::uniform_real_distribution<Float> m_dist;\n    Float m_current_number;\npublic:\n\n    RandomFloatingGenerator(Float a, Float b):\n        m_rng(rng()),\n        m_dist(a, b) {\n        static_cast<void>(next());\n    }\n\n    Float const& get() const override {\n        return m_current_number;\n    }\n    bool next() override {\n        m_current_number = m_dist(m_rng);\n        return true;\n    }\n};\n\ntemplate <typename Integer>\nclass RandomIntegerGenerator final : public IGenerator<Integer> {\n    Catch::SimplePcg32& m_rng;\n    std::uniform_int_distribution<Integer> m_dist;\n    Integer m_current_number;\npublic:\n\n    RandomIntegerGenerator(Integer a, Integer b):\n        m_rng(rng()),\n        m_dist(a, b) {\n        static_cast<void>(next());\n    }\n\n    Integer const& get() const override {\n        return m_current_number;\n    }\n    bool next() override {\n        m_current_number = m_dist(m_rng);\n        return true;\n    }\n};\n\n// TODO: Ideally this would be also constrained against the various char types,\n//       but I don't expect users to run into that in practice.\ntemplate <typename T>\ntypename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value,\nGeneratorWrapper<T>>::type\nrandom(T a, T b) {\n    return GeneratorWrapper<T>(\n        pf::make_unique<RandomIntegerGenerator<T>>(a, b)\n    );\n}\n\ntemplate <typename T>\ntypename std::enable_if<std::is_floating_point<T>::value,\nGeneratorWrapper<T>>::type\nrandom(T a, T b) {\n    return GeneratorWrapper<T>(\n        pf::make_unique<RandomFloatingGenerator<T>>(a, b)\n    );\n}\n\ntemplate <typename T>\nclass RangeGenerator final : public IGenerator<T> {\n    T m_current;\n    T m_end;\n    T m_step;\n    bool m_positive;\n\npublic:\n    RangeGenerator(T const& start, T const& end, T const& step):\n        m_current(start),\n        m_end(end),\n        m_step(step),\n        m_positive(m_step > T(0))\n    {\n        assert(m_current != m_end && \"Range start and end cannot be equal\");\n        assert(m_step != T(0) && \"Step size cannot be zero\");\n        assert(((m_positive && m_current <= m_end) || (!m_positive && m_current >= m_end)) && \"Step moves away from end\");\n    }\n\n    RangeGenerator(T const& start, T const& end):\n        RangeGenerator(start, end, (start < end) ? T(1) : T(-1))\n    {}\n\n    T const& get() const override {\n        return m_current;\n    }\n\n    bool next() override {\n        m_current += m_step;\n        return (m_positive) ? (m_current < m_end) : (m_current > m_end);\n    }\n};\n\ntemplate <typename T>\nGeneratorWrapper<T> range(T const& start, T const& end, T const& step) {\n    static_assert(std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, \"Type must be numeric\");\n    return GeneratorWrapper<T>(pf::make_unique<RangeGenerator<T>>(start, end, step));\n}\n\ntemplate <typename T>\nGeneratorWrapper<T> range(T const& start, T const& end) {\n    static_assert(std::is_integral<T>::value && !std::is_same<T, bool>::value, \"Type must be an integer\");\n    return GeneratorWrapper<T>(pf::make_unique<RangeGenerator<T>>(start, end));\n}\n\ntemplate <typename T>\nclass IteratorGenerator final : public IGenerator<T> {\n    static_assert(!std::is_same<T, bool>::value,\n        \"IteratorGenerator currently does not support bools\"\n        \"because of std::vector<bool> specialization\");\n\n    std::vector<T> m_elems;\n    size_t m_current = 0;\npublic:\n    template <typename InputIterator, typename InputSentinel>\n    IteratorGenerator(InputIterator first, InputSentinel last):m_elems(first, last) {\n        if (m_elems.empty()) {\n            Catch::throw_exception(GeneratorException(\"IteratorGenerator received no valid values\"));\n        }\n    }\n\n    T const& get() const override {\n        return m_elems[m_current];\n    }\n\n    bool next() override {\n        ++m_current;\n        return m_current != m_elems.size();\n    }\n};\n\ntemplate <typename InputIterator,\n          typename InputSentinel,\n          typename ResultType = typename std::iterator_traits<InputIterator>::value_type>\nGeneratorWrapper<ResultType> from_range(InputIterator from, InputSentinel to) {\n    return GeneratorWrapper<ResultType>(pf::make_unique<IteratorGenerator<ResultType>>(from, to));\n}\n\ntemplate <typename Container,\n          typename ResultType = typename Container::value_type>\nGeneratorWrapper<ResultType> from_range(Container const& cnt) {\n    return GeneratorWrapper<ResultType>(pf::make_unique<IteratorGenerator<ResultType>>(cnt.begin(), cnt.end()));\n}\n\n} // namespace Generators\n} // namespace Catch\n\n// end catch_generators_specific.hpp\n\n// These files are included here so the single_include script doesn't put them\n// in the conditionally compiled sections\n// start catch_test_case_info.h\n\n#include <string>\n#include <vector>\n#include <memory>\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\nnamespace Catch {\n\n    struct ITestInvoker;\n\n    struct TestCaseInfo {\n        enum SpecialProperties{\n            None = 0,\n            IsHidden = 1 << 1,\n            ShouldFail = 1 << 2,\n            MayFail = 1 << 3,\n            Throws = 1 << 4,\n            NonPortable = 1 << 5,\n            Benchmark = 1 << 6\n        };\n\n        TestCaseInfo(   std::string const& _name,\n                        std::string const& _className,\n                        std::string const& _description,\n                        std::vector<std::string> const& _tags,\n                        SourceLineInfo const& _lineInfo );\n\n        friend void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags );\n\n        bool isHidden() const;\n        bool throws() const;\n        bool okToFail() const;\n        bool expectedToFail() const;\n\n        std::string tagsAsString() const;\n\n        std::string name;\n        std::string className;\n        std::string description;\n        std::vector<std::string> tags;\n        std::vector<std::string> lcaseTags;\n        SourceLineInfo lineInfo;\n        SpecialProperties properties;\n    };\n\n    class TestCase : public TestCaseInfo {\n    public:\n\n        TestCase( ITestInvoker* testCase, TestCaseInfo&& info );\n\n        TestCase withName( std::string const& _newName ) const;\n\n        void invoke() const;\n\n        TestCaseInfo const& getTestCaseInfo() const;\n\n        bool operator == ( TestCase const& other ) const;\n        bool operator < ( TestCase const& other ) const;\n\n    private:\n        std::shared_ptr<ITestInvoker> test;\n    };\n\n    TestCase makeTestCase(  ITestInvoker* testCase,\n                            std::string const& className,\n                            NameAndTags const& nameAndTags,\n                            SourceLineInfo const& lineInfo );\n}\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// end catch_test_case_info.h\n// start catch_interfaces_runner.h\n\nnamespace Catch {\n\n    struct IRunner {\n        virtual ~IRunner();\n        virtual bool aborting() const = 0;\n    };\n}\n\n// end catch_interfaces_runner.h\n\n#ifdef __OBJC__\n// start catch_objc.hpp\n\n#import <objc/runtime.h>\n\n#include <string>\n\n// NB. Any general catch headers included here must be included\n// in catch.hpp first to make sure they are included by the single\n// header for non obj-usage\n\n///////////////////////////////////////////////////////////////////////////////\n// This protocol is really only here for (self) documenting purposes, since\n// all its methods are optional.\n@protocol OcFixture\n\n@optional\n\n-(void) setUp;\n-(void) tearDown;\n\n@end\n\nnamespace Catch {\n\n    class OcMethod : public ITestInvoker {\n\n    public:\n        OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {}\n\n        virtual void invoke() const {\n            id obj = [[m_cls alloc] init];\n\n            performOptionalSelector( obj, @selector(setUp)  );\n            performOptionalSelector( obj, m_sel );\n            performOptionalSelector( obj, @selector(tearDown)  );\n\n            arcSafeRelease( obj );\n        }\n    private:\n        virtual ~OcMethod() {}\n\n        Class m_cls;\n        SEL m_sel;\n    };\n\n    namespace Detail{\n\n        inline std::string getAnnotation(   Class cls,\n                                            std::string const& annotationName,\n                                            std::string const& testCaseName ) {\n            NSString* selStr = [[NSString alloc] initWithFormat:@\"Catch_%s_%s\", annotationName.c_str(), testCaseName.c_str()];\n            SEL sel = NSSelectorFromString( selStr );\n            arcSafeRelease( selStr );\n            id value = performOptionalSelector( cls, sel );\n            if( value )\n                return [(NSString*)value UTF8String];\n            return \"\";\n        }\n    }\n\n    inline std::size_t registerTestMethods() {\n        std::size_t noTestMethods = 0;\n        int noClasses = objc_getClassList( nullptr, 0 );\n\n        Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses);\n        objc_getClassList( classes, noClasses );\n\n        for( int c = 0; c < noClasses; c++ ) {\n            Class cls = classes[c];\n            {\n                u_int count;\n                Method* methods = class_copyMethodList( cls, &count );\n                for( u_int m = 0; m < count ; m++ ) {\n                    SEL selector = method_getName(methods[m]);\n                    std::string methodName = sel_getName(selector);\n                    if( startsWith( methodName, \"Catch_TestCase_\" ) ) {\n                        std::string testCaseName = methodName.substr( 15 );\n                        std::string name = Detail::getAnnotation( cls, \"Name\", testCaseName );\n                        std::string desc = Detail::getAnnotation( cls, \"Description\", testCaseName );\n                        const char* className = class_getName( cls );\n\n                        getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, NameAndTags( name.c_str(), desc.c_str() ), SourceLineInfo(\"\",0) ) );\n                        noTestMethods++;\n                    }\n                }\n                free(methods);\n            }\n        }\n        return noTestMethods;\n    }\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n\n    namespace Matchers {\n        namespace Impl {\n        namespace NSStringMatchers {\n\n            struct StringHolder : MatcherBase<NSString*>{\n                StringHolder( NSString* substr ) : m_substr( [substr copy] ){}\n                StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}\n                StringHolder() {\n                    arcSafeRelease( m_substr );\n                }\n\n                bool match( NSString* str ) const override {\n                    return false;\n                }\n\n                NSString* CATCH_ARC_STRONG m_substr;\n            };\n\n            struct Equals : StringHolder {\n                Equals( NSString* substr ) : StringHolder( substr ){}\n\n                bool match( NSString* str ) const override {\n                    return  (str != nil || m_substr == nil ) &&\n                            [str isEqualToString:m_substr];\n                }\n\n                std::string describe() const override {\n                    return \"equals string: \" + Catch::Detail::stringify( m_substr );\n                }\n            };\n\n            struct Contains : StringHolder {\n                Contains( NSString* substr ) : StringHolder( substr ){}\n\n                bool match( NSString* str ) const override {\n                    return  (str != nil || m_substr == nil ) &&\n                            [str rangeOfString:m_substr].location != NSNotFound;\n                }\n\n                std::string describe() const override {\n                    return \"contains string: \" + Catch::Detail::stringify( m_substr );\n                }\n            };\n\n            struct StartsWith : StringHolder {\n                StartsWith( NSString* substr ) : StringHolder( substr ){}\n\n                bool match( NSString* str ) const override {\n                    return  (str != nil || m_substr == nil ) &&\n                            [str rangeOfString:m_substr].location == 0;\n                }\n\n                std::string describe() const override {\n                    return \"starts with: \" + Catch::Detail::stringify( m_substr );\n                }\n            };\n            struct EndsWith : StringHolder {\n                EndsWith( NSString* substr ) : StringHolder( substr ){}\n\n                bool match( NSString* str ) const override {\n                    return  (str != nil || m_substr == nil ) &&\n                            [str rangeOfString:m_substr].location == [str length] - [m_substr length];\n                }\n\n                std::string describe() const override {\n                    return \"ends with: \" + Catch::Detail::stringify( m_substr );\n                }\n            };\n\n        } // namespace NSStringMatchers\n        } // namespace Impl\n\n        inline Impl::NSStringMatchers::Equals\n            Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); }\n\n        inline Impl::NSStringMatchers::Contains\n            Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); }\n\n        inline Impl::NSStringMatchers::StartsWith\n            StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); }\n\n        inline Impl::NSStringMatchers::EndsWith\n            EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); }\n\n    } // namespace Matchers\n\n    using namespace Matchers;\n\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n\n} // namespace Catch\n\n///////////////////////////////////////////////////////////////////////////////\n#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix\n#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \\\n+(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \\\n{ \\\nreturn @ name; \\\n} \\\n+(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \\\n{ \\\nreturn @ desc; \\\n} \\\n-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix )\n\n#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ )\n\n// end catch_objc.hpp\n#endif\n\n// Benchmarking needs the externally-facing parts of reporters to work\n#if defined(CATCH_CONFIG_EXTERNAL_INTERFACES) || defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n// start catch_external_interfaces.h\n\n// start catch_reporter_bases.hpp\n\n// start catch_interfaces_reporter.h\n\n// start catch_config.hpp\n\n// start catch_test_spec_parser.h\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\n// start catch_test_spec.h\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\n// start catch_wildcard_pattern.h\n\nnamespace Catch\n{\n    class WildcardPattern {\n        enum WildcardPosition {\n            NoWildcard = 0,\n            WildcardAtStart = 1,\n            WildcardAtEnd = 2,\n            WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd\n        };\n\n    public:\n\n        WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity );\n        virtual ~WildcardPattern() = default;\n        virtual bool matches( std::string const& str ) const;\n\n    private:\n        std::string normaliseString( std::string const& str ) const;\n        CaseSensitive::Choice m_caseSensitivity;\n        WildcardPosition m_wildcard = NoWildcard;\n        std::string m_pattern;\n    };\n}\n\n// end catch_wildcard_pattern.h\n#include <string>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\n\n    struct IConfig;\n\n    class TestSpec {\n        class Pattern {\n        public:\n            explicit Pattern( std::string const& name );\n            virtual ~Pattern();\n            virtual bool matches( TestCaseInfo const& testCase ) const = 0;\n            std::string const& name() const;\n        private:\n            std::string const m_name;\n        };\n        using PatternPtr = std::shared_ptr<Pattern>;\n\n        class NamePattern : public Pattern {\n        public:\n            explicit NamePattern( std::string const& name, std::string const& filterString );\n            bool matches( TestCaseInfo const& testCase ) const override;\n        private:\n            WildcardPattern m_wildcardPattern;\n        };\n\n        class TagPattern : public Pattern {\n        public:\n            explicit TagPattern( std::string const& tag, std::string const& filterString );\n            bool matches( TestCaseInfo const& testCase ) const override;\n        private:\n            std::string m_tag;\n        };\n\n        class ExcludedPattern : public Pattern {\n        public:\n            explicit ExcludedPattern( PatternPtr const& underlyingPattern );\n            bool matches( TestCaseInfo const& testCase ) const override;\n        private:\n            PatternPtr m_underlyingPattern;\n        };\n\n        struct Filter {\n            std::vector<PatternPtr> m_patterns;\n\n            bool matches( TestCaseInfo const& testCase ) const;\n            std::string name() const;\n        };\n\n    public:\n        struct FilterMatch {\n            std::string name;\n            std::vector<TestCase const*> tests;\n        };\n        using Matches = std::vector<FilterMatch>;\n        using vectorStrings = std::vector<std::string>;\n\n        bool hasFilters() const;\n        bool matches( TestCaseInfo const& testCase ) const;\n        Matches matchesByFilter( std::vector<TestCase> const& testCases, IConfig const& config ) const;\n        const vectorStrings & getInvalidArgs() const;\n\n    private:\n        std::vector<Filter> m_filters;\n        std::vector<std::string> m_invalidArgs;\n        friend class TestSpecParser;\n    };\n}\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// end catch_test_spec.h\n// start catch_interfaces_tag_alias_registry.h\n\n#include <string>\n\nnamespace Catch {\n\n    struct TagAlias;\n\n    struct ITagAliasRegistry {\n        virtual ~ITagAliasRegistry();\n        // Nullptr if not present\n        virtual TagAlias const* find( std::string const& alias ) const = 0;\n        virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0;\n\n        static ITagAliasRegistry const& get();\n    };\n\n} // end namespace Catch\n\n// end catch_interfaces_tag_alias_registry.h\nnamespace Catch {\n\n    class TestSpecParser {\n        enum Mode{ None, Name, QuotedName, Tag, EscapedName };\n        Mode m_mode = None;\n        Mode lastMode = None;\n        bool m_exclusion = false;\n        std::size_t m_pos = 0;\n        std::size_t m_realPatternPos = 0;\n        std::string m_arg;\n        std::string m_substring;\n        std::string m_patternName;\n        std::vector<std::size_t> m_escapeChars;\n        TestSpec::Filter m_currentFilter;\n        TestSpec m_testSpec;\n        ITagAliasRegistry const* m_tagAliases = nullptr;\n\n    public:\n        TestSpecParser( ITagAliasRegistry const& tagAliases );\n\n        TestSpecParser& parse( std::string const& arg );\n        TestSpec testSpec();\n\n    private:\n        bool visitChar( char c );\n        void startNewMode( Mode mode );\n        bool processNoneChar( char c );\n        void processNameChar( char c );\n        bool processOtherChar( char c );\n        void endMode();\n        void escape();\n        bool isControlChar( char c ) const;\n        void saveLastMode();\n        void revertBackToLastMode();\n        void addFilter();\n        bool separate();\n\n        // Handles common preprocessing of the pattern for name/tag patterns\n        std::string preprocessPattern();\n        // Adds the current pattern as a test name\n        void addNamePattern();\n        // Adds the current pattern as a tag\n        void addTagPattern();\n\n        inline void addCharToPattern(char c) {\n            m_substring += c;\n            m_patternName += c;\n            m_realPatternPos++;\n        }\n\n    };\n    TestSpec parseTestSpec( std::string const& arg );\n\n} // namespace Catch\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// end catch_test_spec_parser.h\n// Libstdc++ doesn't like incomplete classes for unique_ptr\n\n#include <memory>\n#include <vector>\n#include <string>\n\n#ifndef CATCH_CONFIG_CONSOLE_WIDTH\n#define CATCH_CONFIG_CONSOLE_WIDTH 80\n#endif\n\nnamespace Catch {\n\n    struct IStream;\n\n    struct ConfigData {\n        bool listTests = false;\n        bool listTags = false;\n        bool listReporters = false;\n        bool listTestNamesOnly = false;\n\n        bool showSuccessfulTests = false;\n        bool shouldDebugBreak = false;\n        bool noThrow = false;\n        bool showHelp = false;\n        bool showInvisibles = false;\n        bool filenamesAsTags = false;\n        bool libIdentify = false;\n\n        int abortAfter = -1;\n        unsigned int rngSeed = 0;\n\n        bool benchmarkNoAnalysis = false;\n        unsigned int benchmarkSamples = 100;\n        double benchmarkConfidenceInterval = 0.95;\n        unsigned int benchmarkResamples = 100000;\n        std::chrono::milliseconds::rep benchmarkWarmupTime = 100;\n\n        Verbosity verbosity = Verbosity::Normal;\n        WarnAbout::What warnings = WarnAbout::Nothing;\n        ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter;\n        double minDuration = -1;\n        RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder;\n        UseColour::YesOrNo useColour = UseColour::Auto;\n        WaitForKeypress::When waitForKeypress = WaitForKeypress::Never;\n\n        std::string outputFilename;\n        std::string name;\n        std::string processName;\n#ifndef CATCH_CONFIG_DEFAULT_REPORTER\n#define CATCH_CONFIG_DEFAULT_REPORTER \"console\"\n#endif\n        std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER;\n#undef CATCH_CONFIG_DEFAULT_REPORTER\n\n        std::vector<std::string> testsOrTags;\n        std::vector<std::string> sectionsToRun;\n    };\n\n    class Config : public IConfig {\n    public:\n\n        Config() = default;\n        Config( ConfigData const& data );\n        virtual ~Config() = default;\n\n        std::string const& getFilename() const;\n\n        bool listTests() const;\n        bool listTestNamesOnly() const;\n        bool listTags() const;\n        bool listReporters() const;\n\n        std::string getProcessName() const;\n        std::string const& getReporterName() const;\n\n        std::vector<std::string> const& getTestsOrTags() const override;\n        std::vector<std::string> const& getSectionsToRun() const override;\n\n        TestSpec const& testSpec() const override;\n        bool hasTestFilters() const override;\n\n        bool showHelp() const;\n\n        // IConfig interface\n        bool allowThrows() const override;\n        std::ostream& stream() const override;\n        std::string name() const override;\n        bool includeSuccessfulResults() const override;\n        bool warnAboutMissingAssertions() const override;\n        bool warnAboutNoTests() const override;\n        ShowDurations::OrNot showDurations() const override;\n        double minDuration() const override;\n        RunTests::InWhatOrder runOrder() const override;\n        unsigned int rngSeed() const override;\n        UseColour::YesOrNo useColour() const override;\n        bool shouldDebugBreak() const override;\n        int abortAfter() const override;\n        bool showInvisibles() const override;\n        Verbosity verbosity() const override;\n        bool benchmarkNoAnalysis() const override;\n        int benchmarkSamples() const override;\n        double benchmarkConfidenceInterval() const override;\n        unsigned int benchmarkResamples() const override;\n        std::chrono::milliseconds benchmarkWarmupTime() const override;\n\n    private:\n\n        IStream const* openStream();\n        ConfigData m_data;\n\n        std::unique_ptr<IStream const> m_stream;\n        TestSpec m_testSpec;\n        bool m_hasTestFilters = false;\n    };\n\n} // end namespace Catch\n\n// end catch_config.hpp\n// start catch_assertionresult.h\n\n#include <string>\n\nnamespace Catch {\n\n    struct AssertionResultData\n    {\n        AssertionResultData() = delete;\n\n        AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression );\n\n        std::string message;\n        mutable std::string reconstructedExpression;\n        LazyExpression lazyExpression;\n        ResultWas::OfType resultType;\n\n        std::string reconstructExpression() const;\n    };\n\n    class AssertionResult {\n    public:\n        AssertionResult() = delete;\n        AssertionResult( AssertionInfo const& info, AssertionResultData const& data );\n\n        bool isOk() const;\n        bool succeeded() const;\n        ResultWas::OfType getResultType() const;\n        bool hasExpression() const;\n        bool hasMessage() const;\n        std::string getExpression() const;\n        std::string getExpressionInMacro() const;\n        bool hasExpandedExpression() const;\n        std::string getExpandedExpression() const;\n        std::string getMessage() const;\n        SourceLineInfo getSourceInfo() const;\n        StringRef getTestMacroName() const;\n\n    //protected:\n        AssertionInfo m_info;\n        AssertionResultData m_resultData;\n    };\n\n} // end namespace Catch\n\n// end catch_assertionresult.h\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n// start catch_estimate.hpp\n\n // Statistics estimates\n\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Duration>\n        struct Estimate {\n            Duration point;\n            Duration lower_bound;\n            Duration upper_bound;\n            double confidence_interval;\n\n            template <typename Duration2>\n            operator Estimate<Duration2>() const {\n                return { point, lower_bound, upper_bound, confidence_interval };\n            }\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_estimate.hpp\n// start catch_outlier_classification.hpp\n\n// Outlier information\n\nnamespace Catch {\n    namespace Benchmark {\n        struct OutlierClassification {\n            int samples_seen = 0;\n            int low_severe = 0;     // more than 3 times IQR below Q1\n            int low_mild = 0;       // 1.5 to 3 times IQR below Q1\n            int high_mild = 0;      // 1.5 to 3 times IQR above Q3\n            int high_severe = 0;    // more than 3 times IQR above Q3\n\n            int total() const {\n                return low_severe + low_mild + high_mild + high_severe;\n            }\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_outlier_classification.hpp\n\n#include <iterator>\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n#include <string>\n#include <iosfwd>\n#include <map>\n#include <set>\n#include <memory>\n#include <algorithm>\n\nnamespace Catch {\n\n    struct ReporterConfig {\n        explicit ReporterConfig( IConfigPtr const& _fullConfig );\n\n        ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream );\n\n        std::ostream& stream() const;\n        IConfigPtr fullConfig() const;\n\n    private:\n        std::ostream* m_stream;\n        IConfigPtr m_fullConfig;\n    };\n\n    struct ReporterPreferences {\n        bool shouldRedirectStdOut = false;\n        bool shouldReportAllAssertions = false;\n    };\n\n    template<typename T>\n    struct LazyStat : Option<T> {\n        LazyStat& operator=( T const& _value ) {\n            Option<T>::operator=( _value );\n            used = false;\n            return *this;\n        }\n        void reset() {\n            Option<T>::reset();\n            used = false;\n        }\n        bool used = false;\n    };\n\n    struct TestRunInfo {\n        TestRunInfo( std::string const& _name );\n        std::string name;\n    };\n    struct GroupInfo {\n        GroupInfo(  std::string const& _name,\n                    std::size_t _groupIndex,\n                    std::size_t _groupsCount );\n\n        std::string name;\n        std::size_t groupIndex;\n        std::size_t groupsCounts;\n    };\n\n    struct AssertionStats {\n        AssertionStats( AssertionResult const& _assertionResult,\n                        std::vector<MessageInfo> const& _infoMessages,\n                        Totals const& _totals );\n\n        AssertionStats( AssertionStats const& )              = default;\n        AssertionStats( AssertionStats && )                  = default;\n        AssertionStats& operator = ( AssertionStats const& ) = delete;\n        AssertionStats& operator = ( AssertionStats && )     = delete;\n        virtual ~AssertionStats();\n\n        AssertionResult assertionResult;\n        std::vector<MessageInfo> infoMessages;\n        Totals totals;\n    };\n\n    struct SectionStats {\n        SectionStats(   SectionInfo const& _sectionInfo,\n                        Counts const& _assertions,\n                        double _durationInSeconds,\n                        bool _missingAssertions );\n        SectionStats( SectionStats const& )              = default;\n        SectionStats( SectionStats && )                  = default;\n        SectionStats& operator = ( SectionStats const& ) = default;\n        SectionStats& operator = ( SectionStats && )     = default;\n        virtual ~SectionStats();\n\n        SectionInfo sectionInfo;\n        Counts assertions;\n        double durationInSeconds;\n        bool missingAssertions;\n    };\n\n    struct TestCaseStats {\n        TestCaseStats(  TestCaseInfo const& _testInfo,\n                        Totals const& _totals,\n                        std::string const& _stdOut,\n                        std::string const& _stdErr,\n                        bool _aborting );\n\n        TestCaseStats( TestCaseStats const& )              = default;\n        TestCaseStats( TestCaseStats && )                  = default;\n        TestCaseStats& operator = ( TestCaseStats const& ) = default;\n        TestCaseStats& operator = ( TestCaseStats && )     = default;\n        virtual ~TestCaseStats();\n\n        TestCaseInfo testInfo;\n        Totals totals;\n        std::string stdOut;\n        std::string stdErr;\n        bool aborting;\n    };\n\n    struct TestGroupStats {\n        TestGroupStats( GroupInfo const& _groupInfo,\n                        Totals const& _totals,\n                        bool _aborting );\n        TestGroupStats( GroupInfo const& _groupInfo );\n\n        TestGroupStats( TestGroupStats const& )              = default;\n        TestGroupStats( TestGroupStats && )                  = default;\n        TestGroupStats& operator = ( TestGroupStats const& ) = default;\n        TestGroupStats& operator = ( TestGroupStats && )     = default;\n        virtual ~TestGroupStats();\n\n        GroupInfo groupInfo;\n        Totals totals;\n        bool aborting;\n    };\n\n    struct TestRunStats {\n        TestRunStats(   TestRunInfo const& _runInfo,\n                        Totals const& _totals,\n                        bool _aborting );\n\n        TestRunStats( TestRunStats const& )              = default;\n        TestRunStats( TestRunStats && )                  = default;\n        TestRunStats& operator = ( TestRunStats const& ) = default;\n        TestRunStats& operator = ( TestRunStats && )     = default;\n        virtual ~TestRunStats();\n\n        TestRunInfo runInfo;\n        Totals totals;\n        bool aborting;\n    };\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    struct BenchmarkInfo {\n        std::string name;\n        double estimatedDuration;\n        int iterations;\n        int samples;\n        unsigned int resamples;\n        double clockResolution;\n        double clockCost;\n    };\n\n    template <class Duration>\n    struct BenchmarkStats {\n        BenchmarkInfo info;\n\n        std::vector<Duration> samples;\n        Benchmark::Estimate<Duration> mean;\n        Benchmark::Estimate<Duration> standardDeviation;\n        Benchmark::OutlierClassification outliers;\n        double outlierVariance;\n\n        template <typename Duration2>\n        operator BenchmarkStats<Duration2>() const {\n            std::vector<Duration2> samples2;\n            samples2.reserve(samples.size());\n            std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); });\n            return {\n                info,\n                std::move(samples2),\n                mean,\n                standardDeviation,\n                outliers,\n                outlierVariance,\n            };\n        }\n    };\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    struct IStreamingReporter {\n        virtual ~IStreamingReporter() = default;\n\n        // Implementing class must also provide the following static methods:\n        // static std::string getDescription();\n        // static std::set<Verbosity> getSupportedVerbosities()\n\n        virtual ReporterPreferences getPreferences() const = 0;\n\n        virtual void noMatchingTestCases( std::string const& spec ) = 0;\n\n        virtual void reportInvalidArguments(std::string const&) {}\n\n        virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;\n        virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0;\n\n        virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0;\n        virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n        virtual void benchmarkPreparing( std::string const& ) {}\n        virtual void benchmarkStarting( BenchmarkInfo const& ) {}\n        virtual void benchmarkEnded( BenchmarkStats<> const& ) {}\n        virtual void benchmarkFailed( std::string const& ) {}\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n        virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;\n\n        // The return value indicates if the messages buffer should be cleared:\n        virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;\n\n        virtual void sectionEnded( SectionStats const& sectionStats ) = 0;\n        virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;\n        virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;\n        virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;\n\n        virtual void skipTest( TestCaseInfo const& testInfo ) = 0;\n\n        // Default empty implementation provided\n        virtual void fatalErrorEncountered( StringRef name );\n\n        virtual bool isMulti() const;\n    };\n    using IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>;\n\n    struct IReporterFactory {\n        virtual ~IReporterFactory();\n        virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0;\n        virtual std::string getDescription() const = 0;\n    };\n    using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;\n\n    struct IReporterRegistry {\n        using FactoryMap = std::map<std::string, IReporterFactoryPtr>;\n        using Listeners = std::vector<IReporterFactoryPtr>;\n\n        virtual ~IReporterRegistry();\n        virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0;\n        virtual FactoryMap const& getFactories() const = 0;\n        virtual Listeners const& getListeners() const = 0;\n    };\n\n} // end namespace Catch\n\n// end catch_interfaces_reporter.h\n#include <algorithm>\n#include <cstring>\n#include <cfloat>\n#include <cstdio>\n#include <cassert>\n#include <memory>\n#include <ostream>\n\nnamespace Catch {\n    void prepareExpandedExpression(AssertionResult& result);\n\n    // Returns double formatted as %.3f (format expected on output)\n    std::string getFormattedDuration( double duration );\n\n    //! Should the reporter show\n    bool shouldShowDuration( IConfig const& config, double duration );\n\n    std::string serializeFilters( std::vector<std::string> const& container );\n\n    template<typename DerivedT>\n    struct StreamingReporterBase : IStreamingReporter {\n\n        StreamingReporterBase( ReporterConfig const& _config )\n        :   m_config( _config.fullConfig() ),\n            stream( _config.stream() )\n        {\n            m_reporterPrefs.shouldRedirectStdOut = false;\n            if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) )\n                CATCH_ERROR( \"Verbosity level not supported by this reporter\" );\n        }\n\n        ReporterPreferences getPreferences() const override {\n            return m_reporterPrefs;\n        }\n\n        static std::set<Verbosity> getSupportedVerbosities() {\n            return { Verbosity::Normal };\n        }\n\n        ~StreamingReporterBase() override = default;\n\n        void noMatchingTestCases(std::string const&) override {}\n\n        void reportInvalidArguments(std::string const&) override {}\n\n        void testRunStarting(TestRunInfo const& _testRunInfo) override {\n            currentTestRunInfo = _testRunInfo;\n        }\n\n        void testGroupStarting(GroupInfo const& _groupInfo) override {\n            currentGroupInfo = _groupInfo;\n        }\n\n        void testCaseStarting(TestCaseInfo const& _testInfo) override  {\n            currentTestCaseInfo = _testInfo;\n        }\n        void sectionStarting(SectionInfo const& _sectionInfo) override {\n            m_sectionStack.push_back(_sectionInfo);\n        }\n\n        void sectionEnded(SectionStats const& /* _sectionStats */) override {\n            m_sectionStack.pop_back();\n        }\n        void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override {\n            currentTestCaseInfo.reset();\n        }\n        void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override {\n            currentGroupInfo.reset();\n        }\n        void testRunEnded(TestRunStats const& /* _testRunStats */) override {\n            currentTestCaseInfo.reset();\n            currentGroupInfo.reset();\n            currentTestRunInfo.reset();\n        }\n\n        void skipTest(TestCaseInfo const&) override {\n            // Don't do anything with this by default.\n            // It can optionally be overridden in the derived class.\n        }\n\n        IConfigPtr m_config;\n        std::ostream& stream;\n\n        LazyStat<TestRunInfo> currentTestRunInfo;\n        LazyStat<GroupInfo> currentGroupInfo;\n        LazyStat<TestCaseInfo> currentTestCaseInfo;\n\n        std::vector<SectionInfo> m_sectionStack;\n        ReporterPreferences m_reporterPrefs;\n    };\n\n    template<typename DerivedT>\n    struct CumulativeReporterBase : IStreamingReporter {\n        template<typename T, typename ChildNodeT>\n        struct Node {\n            explicit Node( T const& _value ) : value( _value ) {}\n            virtual ~Node() {}\n\n            using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>;\n            T value;\n            ChildNodes children;\n        };\n        struct SectionNode {\n            explicit SectionNode(SectionStats const& _stats) : stats(_stats) {}\n            virtual ~SectionNode() = default;\n\n            bool operator == (SectionNode const& other) const {\n                return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;\n            }\n            bool operator == (std::shared_ptr<SectionNode> const& other) const {\n                return operator==(*other);\n            }\n\n            SectionStats stats;\n            using ChildSections = std::vector<std::shared_ptr<SectionNode>>;\n            using Assertions = std::vector<AssertionStats>;\n            ChildSections childSections;\n            Assertions assertions;\n            std::string stdOut;\n            std::string stdErr;\n        };\n\n        struct BySectionInfo {\n            BySectionInfo( SectionInfo const& other ) : m_other( other ) {}\n            BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}\n            bool operator() (std::shared_ptr<SectionNode> const& node) const {\n                return ((node->stats.sectionInfo.name == m_other.name) &&\n                        (node->stats.sectionInfo.lineInfo == m_other.lineInfo));\n            }\n            void operator=(BySectionInfo const&) = delete;\n\n        private:\n            SectionInfo const& m_other;\n        };\n\n        using TestCaseNode = Node<TestCaseStats, SectionNode>;\n        using TestGroupNode = Node<TestGroupStats, TestCaseNode>;\n        using TestRunNode = Node<TestRunStats, TestGroupNode>;\n\n        CumulativeReporterBase( ReporterConfig const& _config )\n        :   m_config( _config.fullConfig() ),\n            stream( _config.stream() )\n        {\n            m_reporterPrefs.shouldRedirectStdOut = false;\n            if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) )\n                CATCH_ERROR( \"Verbosity level not supported by this reporter\" );\n        }\n        ~CumulativeReporterBase() override = default;\n\n        ReporterPreferences getPreferences() const override {\n            return m_reporterPrefs;\n        }\n\n        static std::set<Verbosity> getSupportedVerbosities() {\n            return { Verbosity::Normal };\n        }\n\n        void testRunStarting( TestRunInfo const& ) override {}\n        void testGroupStarting( GroupInfo const& ) override {}\n\n        void testCaseStarting( TestCaseInfo const& ) override {}\n\n        void sectionStarting( SectionInfo const& sectionInfo ) override {\n            SectionStats incompleteStats( sectionInfo, Counts(), 0, false );\n            std::shared_ptr<SectionNode> node;\n            if( m_sectionStack.empty() ) {\n                if( !m_rootSection )\n                    m_rootSection = std::make_shared<SectionNode>( incompleteStats );\n                node = m_rootSection;\n            }\n            else {\n                SectionNode& parentNode = *m_sectionStack.back();\n                auto it =\n                    std::find_if(   parentNode.childSections.begin(),\n                                    parentNode.childSections.end(),\n                                    BySectionInfo( sectionInfo ) );\n                if( it == parentNode.childSections.end() ) {\n                    node = std::make_shared<SectionNode>( incompleteStats );\n                    parentNode.childSections.push_back( node );\n                }\n                else\n                    node = *it;\n            }\n            m_sectionStack.push_back( node );\n            m_deepestSection = std::move(node);\n        }\n\n        void assertionStarting(AssertionInfo const&) override {}\n\n        bool assertionEnded(AssertionStats const& assertionStats) override {\n            assert(!m_sectionStack.empty());\n            // AssertionResult holds a pointer to a temporary DecomposedExpression,\n            // which getExpandedExpression() calls to build the expression string.\n            // Our section stack copy of the assertionResult will likely outlive the\n            // temporary, so it must be expanded or discarded now to avoid calling\n            // a destroyed object later.\n            prepareExpandedExpression(const_cast<AssertionResult&>( assertionStats.assertionResult ) );\n            SectionNode& sectionNode = *m_sectionStack.back();\n            sectionNode.assertions.push_back(assertionStats);\n            return true;\n        }\n        void sectionEnded(SectionStats const& sectionStats) override {\n            assert(!m_sectionStack.empty());\n            SectionNode& node = *m_sectionStack.back();\n            node.stats = sectionStats;\n            m_sectionStack.pop_back();\n        }\n        void testCaseEnded(TestCaseStats const& testCaseStats) override {\n            auto node = std::make_shared<TestCaseNode>(testCaseStats);\n            assert(m_sectionStack.size() == 0);\n            node->children.push_back(m_rootSection);\n            m_testCases.push_back(node);\n            m_rootSection.reset();\n\n            assert(m_deepestSection);\n            m_deepestSection->stdOut = testCaseStats.stdOut;\n            m_deepestSection->stdErr = testCaseStats.stdErr;\n        }\n        void testGroupEnded(TestGroupStats const& testGroupStats) override {\n            auto node = std::make_shared<TestGroupNode>(testGroupStats);\n            node->children.swap(m_testCases);\n            m_testGroups.push_back(node);\n        }\n        void testRunEnded(TestRunStats const& testRunStats) override {\n            auto node = std::make_shared<TestRunNode>(testRunStats);\n            node->children.swap(m_testGroups);\n            m_testRuns.push_back(node);\n            testRunEndedCumulative();\n        }\n        virtual void testRunEndedCumulative() = 0;\n\n        void skipTest(TestCaseInfo const&) override {}\n\n        IConfigPtr m_config;\n        std::ostream& stream;\n        std::vector<AssertionStats> m_assertions;\n        std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections;\n        std::vector<std::shared_ptr<TestCaseNode>> m_testCases;\n        std::vector<std::shared_ptr<TestGroupNode>> m_testGroups;\n\n        std::vector<std::shared_ptr<TestRunNode>> m_testRuns;\n\n        std::shared_ptr<SectionNode> m_rootSection;\n        std::shared_ptr<SectionNode> m_deepestSection;\n        std::vector<std::shared_ptr<SectionNode>> m_sectionStack;\n        ReporterPreferences m_reporterPrefs;\n    };\n\n    template<char C>\n    char const* getLineOfChars() {\n        static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};\n        if( !*line ) {\n            std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );\n            line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;\n        }\n        return line;\n    }\n\n    struct TestEventListenerBase : StreamingReporterBase<TestEventListenerBase> {\n        TestEventListenerBase( ReporterConfig const& _config );\n\n        static std::set<Verbosity> getSupportedVerbosities();\n\n        void assertionStarting(AssertionInfo const&) override;\n        bool assertionEnded(AssertionStats const&) override;\n    };\n\n} // end namespace Catch\n\n// end catch_reporter_bases.hpp\n// start catch_console_colour.h\n\nnamespace Catch {\n\n    struct Colour {\n        enum Code {\n            None = 0,\n\n            White,\n            Red,\n            Green,\n            Blue,\n            Cyan,\n            Yellow,\n            Grey,\n\n            Bright = 0x10,\n\n            BrightRed = Bright | Red,\n            BrightGreen = Bright | Green,\n            LightGrey = Bright | Grey,\n            BrightWhite = Bright | White,\n            BrightYellow = Bright | Yellow,\n\n            // By intention\n            FileName = LightGrey,\n            Warning = BrightYellow,\n            ResultError = BrightRed,\n            ResultSuccess = BrightGreen,\n            ResultExpectedFailure = Warning,\n\n            Error = BrightRed,\n            Success = Green,\n\n            OriginalExpression = Cyan,\n            ReconstructedExpression = BrightYellow,\n\n            SecondaryText = LightGrey,\n            Headers = White\n        };\n\n        // Use constructed object for RAII guard\n        Colour( Code _colourCode );\n        Colour( Colour&& other ) noexcept;\n        Colour& operator=( Colour&& other ) noexcept;\n        ~Colour();\n\n        // Use static method for one-shot changes\n        static void use( Code _colourCode );\n\n    private:\n        bool m_moved = false;\n    };\n\n    std::ostream& operator << ( std::ostream& os, Colour const& );\n\n} // end namespace Catch\n\n// end catch_console_colour.h\n// start catch_reporter_registrars.hpp\n\n\nnamespace Catch {\n\n    template<typename T>\n    class ReporterRegistrar {\n\n        class ReporterFactory : public IReporterFactory {\n\n            IStreamingReporterPtr create( ReporterConfig const& config ) const override {\n                return std::unique_ptr<T>( new T( config ) );\n            }\n\n            std::string getDescription() const override {\n                return T::getDescription();\n            }\n        };\n\n    public:\n\n        explicit ReporterRegistrar( std::string const& name ) {\n            getMutableRegistryHub().registerReporter( name, std::make_shared<ReporterFactory>() );\n        }\n    };\n\n    template<typename T>\n    class ListenerRegistrar {\n\n        class ListenerFactory : public IReporterFactory {\n\n            IStreamingReporterPtr create( ReporterConfig const& config ) const override {\n                return std::unique_ptr<T>( new T( config ) );\n            }\n            std::string getDescription() const override {\n                return std::string();\n            }\n        };\n\n    public:\n\n        ListenerRegistrar() {\n            getMutableRegistryHub().registerListener( std::make_shared<ListenerFactory>() );\n        }\n    };\n}\n\n#if !defined(CATCH_CONFIG_DISABLE)\n\n#define CATCH_REGISTER_REPORTER( name, reporterType ) \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION         \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS          \\\n    namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n#define CATCH_REGISTER_LISTENER( listenerType ) \\\n    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION   \\\n    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS    \\\n    namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n#else // CATCH_CONFIG_DISABLE\n\n#define CATCH_REGISTER_REPORTER(name, reporterType)\n#define CATCH_REGISTER_LISTENER(listenerType)\n\n#endif // CATCH_CONFIG_DISABLE\n\n// end catch_reporter_registrars.hpp\n// Allow users to base their work off existing reporters\n// start catch_reporter_compact.h\n\nnamespace Catch {\n\n    struct CompactReporter : StreamingReporterBase<CompactReporter> {\n\n        using StreamingReporterBase::StreamingReporterBase;\n\n        ~CompactReporter() override;\n\n        static std::string getDescription();\n\n        void noMatchingTestCases(std::string const& spec) override;\n\n        void assertionStarting(AssertionInfo const&) override;\n\n        bool assertionEnded(AssertionStats const& _assertionStats) override;\n\n        void sectionEnded(SectionStats const& _sectionStats) override;\n\n        void testRunEnded(TestRunStats const& _testRunStats) override;\n\n    };\n\n} // end namespace Catch\n\n// end catch_reporter_compact.h\n// start catch_reporter_console.h\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch\n                              // Note that 4062 (not all labels are handled\n                              // and default is missing) is enabled\n#endif\n\nnamespace Catch {\n    // Fwd decls\n    struct SummaryColumn;\n    class TablePrinter;\n\n    struct ConsoleReporter : StreamingReporterBase<ConsoleReporter> {\n        std::unique_ptr<TablePrinter> m_tablePrinter;\n\n        ConsoleReporter(ReporterConfig const& config);\n        ~ConsoleReporter() override;\n        static std::string getDescription();\n\n        void noMatchingTestCases(std::string const& spec) override;\n\n        void reportInvalidArguments(std::string const&arg) override;\n\n        void assertionStarting(AssertionInfo const&) override;\n\n        bool assertionEnded(AssertionStats const& _assertionStats) override;\n\n        void sectionStarting(SectionInfo const& _sectionInfo) override;\n        void sectionEnded(SectionStats const& _sectionStats) override;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n        void benchmarkPreparing(std::string const& name) override;\n        void benchmarkStarting(BenchmarkInfo const& info) override;\n        void benchmarkEnded(BenchmarkStats<> const& stats) override;\n        void benchmarkFailed(std::string const& error) override;\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n        void testCaseEnded(TestCaseStats const& _testCaseStats) override;\n        void testGroupEnded(TestGroupStats const& _testGroupStats) override;\n        void testRunEnded(TestRunStats const& _testRunStats) override;\n        void testRunStarting(TestRunInfo const& _testRunInfo) override;\n    private:\n\n        void lazyPrint();\n\n        void lazyPrintWithoutClosingBenchmarkTable();\n        void lazyPrintRunInfo();\n        void lazyPrintGroupInfo();\n        void printTestCaseAndSectionHeader();\n\n        void printClosedHeader(std::string const& _name);\n        void printOpenHeader(std::string const& _name);\n\n        // if string has a : in first line will set indent to follow it on\n        // subsequent lines\n        void printHeaderString(std::string const& _string, std::size_t indent = 0);\n\n        void printTotals(Totals const& totals);\n        void printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row);\n\n        void printTotalsDivider(Totals const& totals);\n        void printSummaryDivider();\n        void printTestFilters();\n\n    private:\n        bool m_headerPrinted = false;\n    };\n\n} // end namespace Catch\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\n// end catch_reporter_console.h\n// start catch_reporter_junit.h\n\n// start catch_xmlwriter.h\n\n#include <vector>\n\nnamespace Catch {\n    enum class XmlFormatting {\n        None = 0x00,\n        Indent = 0x01,\n        Newline = 0x02,\n    };\n\n    XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs);\n    XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs);\n\n    class XmlEncode {\n    public:\n        enum ForWhat { ForTextNodes, ForAttributes };\n\n        XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes );\n\n        void encodeTo( std::ostream& os ) const;\n\n        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode );\n\n    private:\n        std::string m_str;\n        ForWhat m_forWhat;\n    };\n\n    class XmlWriter {\n    public:\n\n        class ScopedElement {\n        public:\n            ScopedElement( XmlWriter* writer, XmlFormatting fmt );\n\n            ScopedElement( ScopedElement&& other ) noexcept;\n            ScopedElement& operator=( ScopedElement&& other ) noexcept;\n\n            ~ScopedElement();\n\n            ScopedElement& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent );\n\n            template<typename T>\n            ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {\n                m_writer->writeAttribute( name, attribute );\n                return *this;\n            }\n\n        private:\n            mutable XmlWriter* m_writer = nullptr;\n            XmlFormatting m_fmt;\n        };\n\n        XmlWriter( std::ostream& os = Catch::cout() );\n        ~XmlWriter();\n\n        XmlWriter( XmlWriter const& ) = delete;\n        XmlWriter& operator=( XmlWriter const& ) = delete;\n\n        XmlWriter& startElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        ScopedElement scopedElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        XmlWriter& endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        XmlWriter& writeAttribute( std::string const& name, std::string const& attribute );\n\n        XmlWriter& writeAttribute( std::string const& name, bool attribute );\n\n        template<typename T>\n        XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {\n            ReusableStringStream rss;\n            rss << attribute;\n            return writeAttribute( name, rss.str() );\n        }\n\n        XmlWriter& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        XmlWriter& writeComment(std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        void writeStylesheetRef( std::string const& url );\n\n        XmlWriter& writeBlankLine();\n\n        void ensureTagClosed();\n\n    private:\n\n        void applyFormatting(XmlFormatting fmt);\n\n        void writeDeclaration();\n\n        void newlineIfNecessary();\n\n        bool m_tagIsOpen = false;\n        bool m_needsNewline = false;\n        std::vector<std::string> m_tags;\n        std::string m_indent;\n        std::ostream& m_os;\n    };\n\n}\n\n// end catch_xmlwriter.h\nnamespace Catch {\n\n    class JunitReporter : public CumulativeReporterBase<JunitReporter> {\n    public:\n        JunitReporter(ReporterConfig const& _config);\n\n        ~JunitReporter() override;\n\n        static std::string getDescription();\n\n        void noMatchingTestCases(std::string const& /*spec*/) override;\n\n        void testRunStarting(TestRunInfo const& runInfo) override;\n\n        void testGroupStarting(GroupInfo const& groupInfo) override;\n\n        void testCaseStarting(TestCaseInfo const& testCaseInfo) override;\n        bool assertionEnded(AssertionStats const& assertionStats) override;\n\n        void testCaseEnded(TestCaseStats const& testCaseStats) override;\n\n        void testGroupEnded(TestGroupStats const& testGroupStats) override;\n\n        void testRunEndedCumulative() override;\n\n        void writeGroup(TestGroupNode const& groupNode, double suiteTime);\n\n        void writeTestCase(TestCaseNode const& testCaseNode);\n\n        void writeSection( std::string const& className,\n                           std::string const& rootName,\n                           SectionNode const& sectionNode,\n                           bool testOkToFail );\n\n        void writeAssertions(SectionNode const& sectionNode);\n        void writeAssertion(AssertionStats const& stats);\n\n        XmlWriter xml;\n        Timer suiteTimer;\n        std::string stdOutForSuite;\n        std::string stdErrForSuite;\n        unsigned int unexpectedExceptions = 0;\n        bool m_okToFail = false;\n    };\n\n} // end namespace Catch\n\n// end catch_reporter_junit.h\n// start catch_reporter_xml.h\n\nnamespace Catch {\n    class XmlReporter : public StreamingReporterBase<XmlReporter> {\n    public:\n        XmlReporter(ReporterConfig const& _config);\n\n        ~XmlReporter() override;\n\n        static std::string getDescription();\n\n        virtual std::string getStylesheetRef() const;\n\n        void writeSourceInfo(SourceLineInfo const& sourceInfo);\n\n    public: // StreamingReporterBase\n\n        void noMatchingTestCases(std::string const& s) override;\n\n        void testRunStarting(TestRunInfo const& testInfo) override;\n\n        void testGroupStarting(GroupInfo const& groupInfo) override;\n\n        void testCaseStarting(TestCaseInfo const& testInfo) override;\n\n        void sectionStarting(SectionInfo const& sectionInfo) override;\n\n        void assertionStarting(AssertionInfo const&) override;\n\n        bool assertionEnded(AssertionStats const& assertionStats) override;\n\n        void sectionEnded(SectionStats const& sectionStats) override;\n\n        void testCaseEnded(TestCaseStats const& testCaseStats) override;\n\n        void testGroupEnded(TestGroupStats const& testGroupStats) override;\n\n        void testRunEnded(TestRunStats const& testRunStats) override;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n        void benchmarkPreparing(std::string const& name) override;\n        void benchmarkStarting(BenchmarkInfo const&) override;\n        void benchmarkEnded(BenchmarkStats<> const&) override;\n        void benchmarkFailed(std::string const&) override;\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    private:\n        Timer m_testCaseTimer;\n        XmlWriter m_xml;\n        int m_sectionDepth = 0;\n    };\n\n} // end namespace Catch\n\n// end catch_reporter_xml.h\n\n// end catch_external_interfaces.h\n#endif\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n// start catch_benchmarking_all.hpp\n\n// A proxy header that includes all of the benchmarking headers to allow\n// concise include of the benchmarking features. You should prefer the\n// individual includes in standard use.\n\n// start catch_benchmark.hpp\n\n // Benchmark\n\n// start catch_chronometer.hpp\n\n// User-facing chronometer\n\n\n// start catch_clock.hpp\n\n// Clocks\n\n\n#include <chrono>\n#include <ratio>\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Clock>\n        using ClockDuration = typename Clock::duration;\n        template <typename Clock>\n        using FloatDuration = std::chrono::duration<double, typename Clock::period>;\n\n        template <typename Clock>\n        using TimePoint = typename Clock::time_point;\n\n        using default_clock = std::chrono::steady_clock;\n\n        template <typename Clock>\n        struct now {\n            TimePoint<Clock> operator()() const {\n                return Clock::now();\n            }\n        };\n\n        using fp_seconds = std::chrono::duration<double, std::ratio<1>>;\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_clock.hpp\n// start catch_optimizer.hpp\n\n // Hinting the optimizer\n\n\n#if defined(_MSC_VER)\n#   include <atomic> // atomic_thread_fence\n#endif\n\nnamespace Catch {\n    namespace Benchmark {\n#if defined(__GNUC__) || defined(__clang__)\n        template <typename T>\n        inline void keep_memory(T* p) {\n            asm volatile(\"\" : : \"g\"(p) : \"memory\");\n        }\n        inline void keep_memory() {\n            asm volatile(\"\" : : : \"memory\");\n        }\n\n        namespace Detail {\n            inline void optimizer_barrier() { keep_memory(); }\n        } // namespace Detail\n#elif defined(_MSC_VER)\n\n#pragma optimize(\"\", off)\n        template <typename T>\n        inline void keep_memory(T* p) {\n            // thanks @milleniumbug\n            *reinterpret_cast<char volatile*>(p) = *reinterpret_cast<char const volatile*>(p);\n        }\n        // TODO equivalent keep_memory()\n#pragma optimize(\"\", on)\n\n        namespace Detail {\n            inline void optimizer_barrier() {\n                std::atomic_thread_fence(std::memory_order_seq_cst);\n            }\n        } // namespace Detail\n\n#endif\n\n        template <typename T>\n        inline void deoptimize_value(T&& x) {\n            keep_memory(&x);\n        }\n\n        template <typename Fn, typename... Args>\n        inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> typename std::enable_if<!std::is_same<void, decltype(fn(args...))>::value>::type {\n            deoptimize_value(std::forward<Fn>(fn) (std::forward<Args...>(args...)));\n        }\n\n        template <typename Fn, typename... Args>\n        inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> typename std::enable_if<std::is_same<void, decltype(fn(args...))>::value>::type {\n            std::forward<Fn>(fn) (std::forward<Args...>(args...));\n        }\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_optimizer.hpp\n// start catch_complete_invoke.hpp\n\n// Invoke with a special case for void\n\n\n#include <type_traits>\n#include <utility>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename T>\n            struct CompleteType { using type = T; };\n            template <>\n            struct CompleteType<void> { struct type {}; };\n\n            template <typename T>\n            using CompleteType_t = typename CompleteType<T>::type;\n\n            template <typename Result>\n            struct CompleteInvoker {\n                template <typename Fun, typename... Args>\n                static Result invoke(Fun&& fun, Args&&... args) {\n                    return std::forward<Fun>(fun)(std::forward<Args>(args)...);\n                }\n            };\n            template <>\n            struct CompleteInvoker<void> {\n                template <typename Fun, typename... Args>\n                static CompleteType_t<void> invoke(Fun&& fun, Args&&... args) {\n                    std::forward<Fun>(fun)(std::forward<Args>(args)...);\n                    return {};\n                }\n            };\n\n            // invoke and not return void :(\n            template <typename Fun, typename... Args>\n            CompleteType_t<FunctionReturnType<Fun, Args...>> complete_invoke(Fun&& fun, Args&&... args) {\n                return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(std::forward<Fun>(fun), std::forward<Args>(args)...);\n            }\n\n            const std::string benchmarkErrorMsg = \"a benchmark failed to run successfully\";\n        } // namespace Detail\n\n        template <typename Fun>\n        Detail::CompleteType_t<FunctionReturnType<Fun>> user_code(Fun&& fun) {\n            CATCH_TRY{\n                return Detail::complete_invoke(std::forward<Fun>(fun));\n            } CATCH_CATCH_ALL{\n                getResultCapture().benchmarkFailed(translateActiveException());\n                CATCH_RUNTIME_ERROR(Detail::benchmarkErrorMsg);\n            }\n        }\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_complete_invoke.hpp\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            struct ChronometerConcept {\n                virtual void start() = 0;\n                virtual void finish() = 0;\n                virtual ~ChronometerConcept() = default;\n            };\n            template <typename Clock>\n            struct ChronometerModel final : public ChronometerConcept {\n                void start() override { started = Clock::now(); }\n                void finish() override { finished = Clock::now(); }\n\n                ClockDuration<Clock> elapsed() const { return finished - started; }\n\n                TimePoint<Clock> started;\n                TimePoint<Clock> finished;\n            };\n        } // namespace Detail\n\n        struct Chronometer {\n        public:\n            template <typename Fun>\n            void measure(Fun&& fun) { measure(std::forward<Fun>(fun), is_callable<Fun(int)>()); }\n\n            int runs() const { return k; }\n\n            Chronometer(Detail::ChronometerConcept& meter, int k)\n                : impl(&meter)\n                , k(k) {}\n\n        private:\n            template <typename Fun>\n            void measure(Fun&& fun, std::false_type) {\n                measure([&fun](int) { return fun(); }, std::true_type());\n            }\n\n            template <typename Fun>\n            void measure(Fun&& fun, std::true_type) {\n                Detail::optimizer_barrier();\n                impl->start();\n                for (int i = 0; i < k; ++i) invoke_deoptimized(fun, i);\n                impl->finish();\n                Detail::optimizer_barrier();\n            }\n\n            Detail::ChronometerConcept* impl;\n            int k;\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_chronometer.hpp\n// start catch_environment.hpp\n\n// Environment information\n\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Duration>\n        struct EnvironmentEstimate {\n            Duration mean;\n            OutlierClassification outliers;\n\n            template <typename Duration2>\n            operator EnvironmentEstimate<Duration2>() const {\n                return { mean, outliers };\n            }\n        };\n        template <typename Clock>\n        struct Environment {\n            using clock_type = Clock;\n            EnvironmentEstimate<FloatDuration<Clock>> clock_resolution;\n            EnvironmentEstimate<FloatDuration<Clock>> clock_cost;\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_environment.hpp\n// start catch_execution_plan.hpp\n\n // Execution plan\n\n\n// start catch_benchmark_function.hpp\n\n // Dumb std::function implementation for consistent call overhead\n\n\n#include <cassert>\n#include <type_traits>\n#include <utility>\n#include <memory>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename T>\n            using Decay = typename std::decay<T>::type;\n            template <typename T, typename U>\n            struct is_related\n                : std::is_same<Decay<T>, Decay<U>> {};\n\n            /// We need to reinvent std::function because every piece of code that might add overhead\n            /// in a measurement context needs to have consistent performance characteristics so that we\n            /// can account for it in the measurement.\n            /// Implementations of std::function with optimizations that aren't always applicable, like\n            /// small buffer optimizations, are not uncommon.\n            /// This is effectively an implementation of std::function without any such optimizations;\n            /// it may be slow, but it is consistently slow.\n            struct BenchmarkFunction {\n            private:\n                struct callable {\n                    virtual void call(Chronometer meter) const = 0;\n                    virtual callable* clone() const = 0;\n                    virtual ~callable() = default;\n                };\n                template <typename Fun>\n                struct model : public callable {\n                    model(Fun&& fun) : fun(std::move(fun)) {}\n                    model(Fun const& fun) : fun(fun) {}\n\n                    model<Fun>* clone() const override { return new model<Fun>(*this); }\n\n                    void call(Chronometer meter) const override {\n                        call(meter, is_callable<Fun(Chronometer)>());\n                    }\n                    void call(Chronometer meter, std::true_type) const {\n                        fun(meter);\n                    }\n                    void call(Chronometer meter, std::false_type) const {\n                        meter.measure(fun);\n                    }\n\n                    Fun fun;\n                };\n\n                struct do_nothing { void operator()() const {} };\n\n                template <typename T>\n                BenchmarkFunction(model<T>* c) : f(c) {}\n\n            public:\n                BenchmarkFunction()\n                    : f(new model<do_nothing>{ {} }) {}\n\n                template <typename Fun,\n                    typename std::enable_if<!is_related<Fun, BenchmarkFunction>::value, int>::type = 0>\n                    BenchmarkFunction(Fun&& fun)\n                    : f(new model<typename std::decay<Fun>::type>(std::forward<Fun>(fun))) {}\n\n                BenchmarkFunction(BenchmarkFunction&& that)\n                    : f(std::move(that.f)) {}\n\n                BenchmarkFunction(BenchmarkFunction const& that)\n                    : f(that.f->clone()) {}\n\n                BenchmarkFunction& operator=(BenchmarkFunction&& that) {\n                    f = std::move(that.f);\n                    return *this;\n                }\n\n                BenchmarkFunction& operator=(BenchmarkFunction const& that) {\n                    f.reset(that.f->clone());\n                    return *this;\n                }\n\n                void operator()(Chronometer meter) const { f->call(meter); }\n\n            private:\n                std::unique_ptr<callable> f;\n            };\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_benchmark_function.hpp\n// start catch_repeat.hpp\n\n// repeat algorithm\n\n\n#include <type_traits>\n#include <utility>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Fun>\n            struct repeater {\n                void operator()(int k) const {\n                    for (int i = 0; i < k; ++i) {\n                        fun();\n                    }\n                }\n                Fun fun;\n            };\n            template <typename Fun>\n            repeater<typename std::decay<Fun>::type> repeat(Fun&& fun) {\n                return { std::forward<Fun>(fun) };\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_repeat.hpp\n// start catch_run_for_at_least.hpp\n\n// Run a function for a minimum amount of time\n\n\n// start catch_measure.hpp\n\n// Measure\n\n\n// start catch_timing.hpp\n\n// Timing\n\n\n#include <tuple>\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Duration, typename Result>\n        struct Timing {\n            Duration elapsed;\n            Result result;\n            int iterations;\n        };\n        template <typename Clock, typename Func, typename... Args>\n        using TimingOf = Timing<ClockDuration<Clock>, Detail::CompleteType_t<FunctionReturnType<Func, Args...>>>;\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_timing.hpp\n#include <utility>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Clock, typename Fun, typename... Args>\n            TimingOf<Clock, Fun, Args...> measure(Fun&& fun, Args&&... args) {\n                auto start = Clock::now();\n                auto&& r = Detail::complete_invoke(fun, std::forward<Args>(args)...);\n                auto end = Clock::now();\n                auto delta = end - start;\n                return { delta, std::forward<decltype(r)>(r), 1 };\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_measure.hpp\n#include <utility>\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Clock, typename Fun>\n            TimingOf<Clock, Fun, int> measure_one(Fun&& fun, int iters, std::false_type) {\n                return Detail::measure<Clock>(fun, iters);\n            }\n            template <typename Clock, typename Fun>\n            TimingOf<Clock, Fun, Chronometer> measure_one(Fun&& fun, int iters, std::true_type) {\n                Detail::ChronometerModel<Clock> meter;\n                auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters));\n\n                return { meter.elapsed(), std::move(result), iters };\n            }\n\n            template <typename Clock, typename Fun>\n            using run_for_at_least_argument_t = typename std::conditional<is_callable<Fun(Chronometer)>::value, Chronometer, int>::type;\n\n            struct optimized_away_error : std::exception {\n                const char* what() const noexcept override {\n                    return \"could not measure benchmark, maybe it was optimized away\";\n                }\n            };\n\n            template <typename Clock, typename Fun>\n            TimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>> run_for_at_least(ClockDuration<Clock> how_long, int seed, Fun&& fun) {\n                auto iters = seed;\n                while (iters < (1 << 30)) {\n                    auto&& Timing = measure_one<Clock>(fun, iters, is_callable<Fun(Chronometer)>());\n\n                    if (Timing.elapsed >= how_long) {\n                        return { Timing.elapsed, std::move(Timing.result), iters };\n                    }\n                    iters *= 2;\n                }\n                Catch::throw_exception(optimized_away_error{});\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_run_for_at_least.hpp\n#include <algorithm>\n#include <iterator>\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Duration>\n        struct ExecutionPlan {\n            int iterations_per_sample;\n            Duration estimated_duration;\n            Detail::BenchmarkFunction benchmark;\n            Duration warmup_time;\n            int warmup_iterations;\n\n            template <typename Duration2>\n            operator ExecutionPlan<Duration2>() const {\n                return { iterations_per_sample, estimated_duration, benchmark, warmup_time, warmup_iterations };\n            }\n\n            template <typename Clock>\n            std::vector<FloatDuration<Clock>> run(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const {\n                // warmup a bit\n                Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_iterations, Detail::repeat(now<Clock>{}));\n\n                std::vector<FloatDuration<Clock>> times;\n                times.reserve(cfg.benchmarkSamples());\n                std::generate_n(std::back_inserter(times), cfg.benchmarkSamples(), [this, env] {\n                    Detail::ChronometerModel<Clock> model;\n                    this->benchmark(Chronometer(model, iterations_per_sample));\n                    auto sample_time = model.elapsed() - env.clock_cost.mean;\n                    if (sample_time < FloatDuration<Clock>::zero()) sample_time = FloatDuration<Clock>::zero();\n                    return sample_time / iterations_per_sample;\n                });\n                return times;\n            }\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_execution_plan.hpp\n// start catch_estimate_clock.hpp\n\n // Environment measurement\n\n\n// start catch_stats.hpp\n\n// Statistical analysis tools\n\n\n#include <algorithm>\n#include <functional>\n#include <vector>\n#include <iterator>\n#include <numeric>\n#include <tuple>\n#include <cmath>\n#include <utility>\n#include <cstddef>\n#include <random>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            using sample = std::vector<double>;\n\n            double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last);\n\n            template <typename Iterator>\n            OutlierClassification classify_outliers(Iterator first, Iterator last) {\n                std::vector<double> copy(first, last);\n\n                auto q1 = weighted_average_quantile(1, 4, copy.begin(), copy.end());\n                auto q3 = weighted_average_quantile(3, 4, copy.begin(), copy.end());\n                auto iqr = q3 - q1;\n                auto los = q1 - (iqr * 3.);\n                auto lom = q1 - (iqr * 1.5);\n                auto him = q3 + (iqr * 1.5);\n                auto his = q3 + (iqr * 3.);\n\n                OutlierClassification o;\n                for (; first != last; ++first) {\n                    auto&& t = *first;\n                    if (t < los) ++o.low_severe;\n                    else if (t < lom) ++o.low_mild;\n                    else if (t > his) ++o.high_severe;\n                    else if (t > him) ++o.high_mild;\n                    ++o.samples_seen;\n                }\n                return o;\n            }\n\n            template <typename Iterator>\n            double mean(Iterator first, Iterator last) {\n                auto count = last - first;\n                double sum = std::accumulate(first, last, 0.);\n                return sum / count;\n            }\n\n            template <typename URng, typename Iterator, typename Estimator>\n            sample resample(URng& rng, int resamples, Iterator first, Iterator last, Estimator& estimator) {\n                auto n = last - first;\n                std::uniform_int_distribution<decltype(n)> dist(0, n - 1);\n\n                sample out;\n                out.reserve(resamples);\n                std::generate_n(std::back_inserter(out), resamples, [n, first, &estimator, &dist, &rng] {\n                    std::vector<double> resampled;\n                    resampled.reserve(n);\n                    std::generate_n(std::back_inserter(resampled), n, [first, &dist, &rng] { return first[dist(rng)]; });\n                    return estimator(resampled.begin(), resampled.end());\n                });\n                std::sort(out.begin(), out.end());\n                return out;\n            }\n\n            template <typename Estimator, typename Iterator>\n            sample jackknife(Estimator&& estimator, Iterator first, Iterator last) {\n                auto n = last - first;\n                auto second = std::next(first);\n                sample results;\n                results.reserve(n);\n\n                for (auto it = first; it != last; ++it) {\n                    std::iter_swap(it, first);\n                    results.push_back(estimator(second, last));\n                }\n\n                return results;\n            }\n\n            inline double normal_cdf(double x) {\n                return std::erfc(-x / std::sqrt(2.0)) / 2.0;\n            }\n\n            double erfc_inv(double x);\n\n            double normal_quantile(double p);\n\n            template <typename Iterator, typename Estimator>\n            Estimate<double> bootstrap(double confidence_level, Iterator first, Iterator last, sample const& resample, Estimator&& estimator) {\n                auto n_samples = last - first;\n\n                double point = estimator(first, last);\n                // Degenerate case with a single sample\n                if (n_samples == 1) return { point, point, point, confidence_level };\n\n                sample jack = jackknife(estimator, first, last);\n                double jack_mean = mean(jack.begin(), jack.end());\n                double sum_squares, sum_cubes;\n                std::tie(sum_squares, sum_cubes) = std::accumulate(jack.begin(), jack.end(), std::make_pair(0., 0.), [jack_mean](std::pair<double, double> sqcb, double x) -> std::pair<double, double> {\n                    auto d = jack_mean - x;\n                    auto d2 = d * d;\n                    auto d3 = d2 * d;\n                    return { sqcb.first + d2, sqcb.second + d3 };\n                });\n\n                double accel = sum_cubes / (6 * std::pow(sum_squares, 1.5));\n                int n = static_cast<int>(resample.size());\n                double prob_n = std::count_if(resample.begin(), resample.end(), [point](double x) { return x < point; }) / (double)n;\n                // degenerate case with uniform samples\n                if (prob_n == 0) return { point, point, point, confidence_level };\n\n                double bias = normal_quantile(prob_n);\n                double z1 = normal_quantile((1. - confidence_level) / 2.);\n\n                auto cumn = [n](double x) -> int {\n                    return std::lround(normal_cdf(x) * n); };\n                auto a = [bias, accel](double b) { return bias + b / (1. - accel * b); };\n                double b1 = bias + z1;\n                double b2 = bias - z1;\n                double a1 = a(b1);\n                double a2 = a(b2);\n                auto lo = (std::max)(cumn(a1), 0);\n                auto hi = (std::min)(cumn(a2), n - 1);\n\n                return { point, resample[lo], resample[hi], confidence_level };\n            }\n\n            double outlier_variance(Estimate<double> mean, Estimate<double> stddev, int n);\n\n            struct bootstrap_analysis {\n                Estimate<double> mean;\n                Estimate<double> standard_deviation;\n                double outlier_variance;\n            };\n\n            bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last);\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_stats.hpp\n#include <algorithm>\n#include <iterator>\n#include <tuple>\n#include <vector>\n#include <cmath>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Clock>\n            std::vector<double> resolution(int k) {\n                std::vector<TimePoint<Clock>> times;\n                times.reserve(k + 1);\n                std::generate_n(std::back_inserter(times), k + 1, now<Clock>{});\n\n                std::vector<double> deltas;\n                deltas.reserve(k);\n                std::transform(std::next(times.begin()), times.end(), times.begin(),\n                    std::back_inserter(deltas),\n                    [](TimePoint<Clock> a, TimePoint<Clock> b) { return static_cast<double>((a - b).count()); });\n\n                return deltas;\n            }\n\n            const auto warmup_iterations = 10000;\n            const auto warmup_time = std::chrono::milliseconds(100);\n            const auto minimum_ticks = 1000;\n            const auto warmup_seed = 10000;\n            const auto clock_resolution_estimation_time = std::chrono::milliseconds(500);\n            const auto clock_cost_estimation_time_limit = std::chrono::seconds(1);\n            const auto clock_cost_estimation_tick_limit = 100000;\n            const auto clock_cost_estimation_time = std::chrono::milliseconds(10);\n            const auto clock_cost_estimation_iterations = 10000;\n\n            template <typename Clock>\n            int warmup() {\n                return run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_seed, &resolution<Clock>)\n                    .iterations;\n            }\n            template <typename Clock>\n            EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_resolution(int iterations) {\n                auto r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_resolution_estimation_time), iterations, &resolution<Clock>)\n                    .result;\n                return {\n                    FloatDuration<Clock>(mean(r.begin(), r.end())),\n                    classify_outliers(r.begin(), r.end()),\n                };\n            }\n            template <typename Clock>\n            EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) {\n                auto time_limit = (std::min)(\n                    resolution * clock_cost_estimation_tick_limit,\n                    FloatDuration<Clock>(clock_cost_estimation_time_limit));\n                auto time_clock = [](int k) {\n                    return Detail::measure<Clock>([k] {\n                        for (int i = 0; i < k; ++i) {\n                            volatile auto ignored = Clock::now();\n                            (void)ignored;\n                        }\n                    }).elapsed;\n                };\n                time_clock(1);\n                int iters = clock_cost_estimation_iterations;\n                auto&& r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_cost_estimation_time), iters, time_clock);\n                std::vector<double> times;\n                int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed));\n                times.reserve(nsamples);\n                std::generate_n(std::back_inserter(times), nsamples, [time_clock, &r] {\n                    return static_cast<double>((time_clock(r.iterations) / r.iterations).count());\n                });\n                return {\n                    FloatDuration<Clock>(mean(times.begin(), times.end())),\n                    classify_outliers(times.begin(), times.end()),\n                };\n            }\n\n            template <typename Clock>\n            Environment<FloatDuration<Clock>> measure_environment() {\n                static Environment<FloatDuration<Clock>>* env = nullptr;\n                if (env) {\n                    return *env;\n                }\n\n                auto iters = Detail::warmup<Clock>();\n                auto resolution = Detail::estimate_clock_resolution<Clock>(iters);\n                auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean);\n\n                env = new Environment<FloatDuration<Clock>>{ resolution, cost };\n                return *env;\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_estimate_clock.hpp\n// start catch_analyse.hpp\n\n // Run and analyse one benchmark\n\n\n// start catch_sample_analysis.hpp\n\n// Benchmark results\n\n\n#include <algorithm>\n#include <vector>\n#include <string>\n#include <iterator>\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Duration>\n        struct SampleAnalysis {\n            std::vector<Duration> samples;\n            Estimate<Duration> mean;\n            Estimate<Duration> standard_deviation;\n            OutlierClassification outliers;\n            double outlier_variance;\n\n            template <typename Duration2>\n            operator SampleAnalysis<Duration2>() const {\n                std::vector<Duration2> samples2;\n                samples2.reserve(samples.size());\n                std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](Duration d) { return Duration2(d); });\n                return {\n                    std::move(samples2),\n                    mean,\n                    standard_deviation,\n                    outliers,\n                    outlier_variance,\n                };\n            }\n        };\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_sample_analysis.hpp\n#include <algorithm>\n#include <iterator>\n#include <vector>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Duration, typename Iterator>\n            SampleAnalysis<Duration> analyse(const IConfig &cfg, Environment<Duration>, Iterator first, Iterator last) {\n                if (!cfg.benchmarkNoAnalysis()) {\n                    std::vector<double> samples;\n                    samples.reserve(last - first);\n                    std::transform(first, last, std::back_inserter(samples), [](Duration d) { return d.count(); });\n\n                    auto analysis = Catch::Benchmark::Detail::analyse_samples(cfg.benchmarkConfidenceInterval(), cfg.benchmarkResamples(), samples.begin(), samples.end());\n                    auto outliers = Catch::Benchmark::Detail::classify_outliers(samples.begin(), samples.end());\n\n                    auto wrap_estimate = [](Estimate<double> e) {\n                        return Estimate<Duration> {\n                            Duration(e.point),\n                                Duration(e.lower_bound),\n                                Duration(e.upper_bound),\n                                e.confidence_interval,\n                        };\n                    };\n                    std::vector<Duration> samples2;\n                    samples2.reserve(samples.size());\n                    std::transform(samples.begin(), samples.end(), std::back_inserter(samples2), [](double d) { return Duration(d); });\n                    return {\n                        std::move(samples2),\n                        wrap_estimate(analysis.mean),\n                        wrap_estimate(analysis.standard_deviation),\n                        outliers,\n                        analysis.outlier_variance,\n                    };\n                } else {\n                    std::vector<Duration> samples;\n                    samples.reserve(last - first);\n\n                    Duration mean = Duration(0);\n                    int i = 0;\n                    for (auto it = first; it < last; ++it, ++i) {\n                        samples.push_back(Duration(*it));\n                        mean += Duration(*it);\n                    }\n                    mean /= i;\n\n                    return {\n                        std::move(samples),\n                        Estimate<Duration>{mean, mean, mean, 0.0},\n                        Estimate<Duration>{Duration(0), Duration(0), Duration(0), 0.0},\n                        OutlierClassification{},\n                        0.0\n                    };\n                }\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_analyse.hpp\n#include <algorithm>\n#include <functional>\n#include <string>\n#include <vector>\n#include <cmath>\n\nnamespace Catch {\n    namespace Benchmark {\n        struct Benchmark {\n            Benchmark(std::string &&name)\n                : name(std::move(name)) {}\n\n            template <class FUN>\n            Benchmark(std::string &&name, FUN &&func)\n                : fun(std::move(func)), name(std::move(name)) {}\n\n            template <typename Clock>\n            ExecutionPlan<FloatDuration<Clock>> prepare(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const {\n                auto min_time = env.clock_resolution.mean * Detail::minimum_ticks;\n                auto run_time = std::max(min_time, std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime()));\n                auto&& test = Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(run_time), 1, fun);\n                int new_iters = static_cast<int>(std::ceil(min_time * test.iterations / test.elapsed));\n                return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast<FloatDuration<Clock>>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations };\n            }\n\n            template <typename Clock = default_clock>\n            void run() {\n                IConfigPtr cfg = getCurrentContext().getConfig();\n\n                auto env = Detail::measure_environment<Clock>();\n\n                getResultCapture().benchmarkPreparing(name);\n                CATCH_TRY{\n                    auto plan = user_code([&] {\n                        return prepare<Clock>(*cfg, env);\n                    });\n\n                    BenchmarkInfo info {\n                        name,\n                        plan.estimated_duration.count(),\n                        plan.iterations_per_sample,\n                        cfg->benchmarkSamples(),\n                        cfg->benchmarkResamples(),\n                        env.clock_resolution.mean.count(),\n                        env.clock_cost.mean.count()\n                    };\n\n                    getResultCapture().benchmarkStarting(info);\n\n                    auto samples = user_code([&] {\n                        return plan.template run<Clock>(*cfg, env);\n                    });\n\n                    auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end());\n                    BenchmarkStats<FloatDuration<Clock>> stats{ info, analysis.samples, analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance };\n                    getResultCapture().benchmarkEnded(stats);\n\n                } CATCH_CATCH_ALL{\n                    if (translateActiveException() != Detail::benchmarkErrorMsg) // benchmark errors have been reported, otherwise rethrow.\n                        std::rethrow_exception(std::current_exception());\n                }\n            }\n\n            // sets lambda to be used in fun *and* executes benchmark!\n            template <typename Fun,\n                typename std::enable_if<!Detail::is_related<Fun, Benchmark>::value, int>::type = 0>\n                Benchmark & operator=(Fun func) {\n                fun = Detail::BenchmarkFunction(func);\n                run();\n                return *this;\n            }\n\n            explicit operator bool() {\n                return true;\n            }\n\n        private:\n            Detail::BenchmarkFunction fun;\n            std::string name;\n        };\n    }\n} // namespace Catch\n\n#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1\n#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2\n\n#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\\\n    if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \\\n        BenchmarkName = [&](int benchmarkIndex)\n\n#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\\\n    if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \\\n        BenchmarkName = [&]\n\n// end catch_benchmark.hpp\n// start catch_constructor.hpp\n\n// Constructor and destructor helpers\n\n\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename T, bool Destruct>\n            struct ObjectStorage\n            {\n                using TStorage = typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type;\n\n                ObjectStorage() : data() {}\n\n                ObjectStorage(const ObjectStorage& other)\n                {\n                    new(&data) T(other.stored_object());\n                }\n\n                ObjectStorage(ObjectStorage&& other)\n                {\n                    new(&data) T(std::move(other.stored_object()));\n                }\n\n                ~ObjectStorage() { destruct_on_exit<T>(); }\n\n                template <typename... Args>\n                void construct(Args&&... args)\n                {\n                    new (&data) T(std::forward<Args>(args)...);\n                }\n\n                template <bool AllowManualDestruction = !Destruct>\n                typename std::enable_if<AllowManualDestruction>::type destruct()\n                {\n                    stored_object().~T();\n                }\n\n            private:\n                // If this is a constructor benchmark, destruct the underlying object\n                template <typename U>\n                void destruct_on_exit(typename std::enable_if<Destruct, U>::type* = 0) { destruct<true>(); }\n                // Otherwise, don't\n                template <typename U>\n                void destruct_on_exit(typename std::enable_if<!Destruct, U>::type* = 0) { }\n\n                T& stored_object() {\n                    return *static_cast<T*>(static_cast<void*>(&data));\n                }\n\n                T const& stored_object() const {\n                    return *static_cast<T*>(static_cast<void*>(&data));\n                }\n\n                TStorage data;\n            };\n        }\n\n        template <typename T>\n        using storage_for = Detail::ObjectStorage<T, true>;\n\n        template <typename T>\n        using destructable_object = Detail::ObjectStorage<T, false>;\n    }\n}\n\n// end catch_constructor.hpp\n// end catch_benchmarking_all.hpp\n#endif\n\n#endif // ! CATCH_CONFIG_IMPL_ONLY\n\n#ifdef CATCH_IMPL\n// start catch_impl.hpp\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wweak-vtables\"\n#endif\n\n// Keep these here for external reporters\n// start catch_test_case_tracker.h\n\n#include <string>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\nnamespace TestCaseTracking {\n\n    struct NameAndLocation {\n        std::string name;\n        SourceLineInfo location;\n\n        NameAndLocation( std::string const& _name, SourceLineInfo const& _location );\n        friend bool operator==(NameAndLocation const& lhs, NameAndLocation const& rhs) {\n            return lhs.name == rhs.name\n                && lhs.location == rhs.location;\n        }\n    };\n\n    class ITracker;\n\n    using ITrackerPtr = std::shared_ptr<ITracker>;\n\n    class  ITracker {\n        NameAndLocation m_nameAndLocation;\n\n    public:\n        ITracker(NameAndLocation const& nameAndLoc) :\n            m_nameAndLocation(nameAndLoc)\n        {}\n\n        // static queries\n        NameAndLocation const& nameAndLocation() const {\n            return m_nameAndLocation;\n        }\n\n        virtual ~ITracker();\n\n        // dynamic queries\n        virtual bool isComplete() const = 0; // Successfully completed or failed\n        virtual bool isSuccessfullyCompleted() const = 0;\n        virtual bool isOpen() const = 0; // Started but not complete\n        virtual bool hasChildren() const = 0;\n        virtual bool hasStarted() const = 0;\n\n        virtual ITracker& parent() = 0;\n\n        // actions\n        virtual void close() = 0; // Successfully complete\n        virtual void fail() = 0;\n        virtual void markAsNeedingAnotherRun() = 0;\n\n        virtual void addChild( ITrackerPtr const& child ) = 0;\n        virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0;\n        virtual void openChild() = 0;\n\n        // Debug/ checking\n        virtual bool isSectionTracker() const = 0;\n        virtual bool isGeneratorTracker() const = 0;\n    };\n\n    class TrackerContext {\n\n        enum RunState {\n            NotStarted,\n            Executing,\n            CompletedCycle\n        };\n\n        ITrackerPtr m_rootTracker;\n        ITracker* m_currentTracker = nullptr;\n        RunState m_runState = NotStarted;\n\n    public:\n\n        ITracker& startRun();\n        void endRun();\n\n        void startCycle();\n        void completeCycle();\n\n        bool completedCycle() const;\n        ITracker& currentTracker();\n        void setCurrentTracker( ITracker* tracker );\n    };\n\n    class TrackerBase : public ITracker {\n    protected:\n        enum CycleState {\n            NotStarted,\n            Executing,\n            ExecutingChildren,\n            NeedsAnotherRun,\n            CompletedSuccessfully,\n            Failed\n        };\n\n        using Children = std::vector<ITrackerPtr>;\n        TrackerContext& m_ctx;\n        ITracker* m_parent;\n        Children m_children;\n        CycleState m_runState = NotStarted;\n\n    public:\n        TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent );\n\n        bool isComplete() const override;\n        bool isSuccessfullyCompleted() const override;\n        bool isOpen() const override;\n        bool hasChildren() const override;\n        bool hasStarted() const override {\n            return m_runState != NotStarted;\n        }\n\n        void addChild( ITrackerPtr const& child ) override;\n\n        ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override;\n        ITracker& parent() override;\n\n        void openChild() override;\n\n        bool isSectionTracker() const override;\n        bool isGeneratorTracker() const override;\n\n        void open();\n\n        void close() override;\n        void fail() override;\n        void markAsNeedingAnotherRun() override;\n\n    private:\n        void moveToParent();\n        void moveToThis();\n    };\n\n    class SectionTracker : public TrackerBase {\n        std::vector<std::string> m_filters;\n        std::string m_trimmed_name;\n    public:\n        SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent );\n\n        bool isSectionTracker() const override;\n\n        bool isComplete() const override;\n\n        static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation );\n\n        void tryOpen();\n\n        void addInitialFilters( std::vector<std::string> const& filters );\n        void addNextFilters( std::vector<std::string> const& filters );\n        //! Returns filters active in this tracker\n        std::vector<std::string> const& getFilters() const;\n        //! Returns whitespace-trimmed name of the tracked section\n        std::string const& trimmedName() const;\n    };\n\n} // namespace TestCaseTracking\n\nusing TestCaseTracking::ITracker;\nusing TestCaseTracking::TrackerContext;\nusing TestCaseTracking::SectionTracker;\n\n} // namespace Catch\n\n// end catch_test_case_tracker.h\n\n// start catch_leak_detector.h\n\nnamespace Catch {\n\n    struct LeakDetector {\n        LeakDetector();\n        ~LeakDetector();\n    };\n\n}\n// end catch_leak_detector.h\n// Cpp files will be included in the single-header file here\n// start catch_stats.cpp\n\n// Statistical analysis tools\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n\n#include <cassert>\n#include <random>\n\n#if defined(CATCH_CONFIG_USE_ASYNC)\n#include <future>\n#endif\n\nnamespace {\n    double erf_inv(double x) {\n        // Code accompanying the article \"Approximating the erfinv function\" in GPU Computing Gems, Volume 2\n        double w, p;\n\n        w = -log((1.0 - x) * (1.0 + x));\n\n        if (w < 6.250000) {\n            w = w - 3.125000;\n            p = -3.6444120640178196996e-21;\n            p = -1.685059138182016589e-19 + p * w;\n            p = 1.2858480715256400167e-18 + p * w;\n            p = 1.115787767802518096e-17 + p * w;\n            p = -1.333171662854620906e-16 + p * w;\n            p = 2.0972767875968561637e-17 + p * w;\n            p = 6.6376381343583238325e-15 + p * w;\n            p = -4.0545662729752068639e-14 + p * w;\n            p = -8.1519341976054721522e-14 + p * w;\n            p = 2.6335093153082322977e-12 + p * w;\n            p = -1.2975133253453532498e-11 + p * w;\n            p = -5.4154120542946279317e-11 + p * w;\n            p = 1.051212273321532285e-09 + p * w;\n            p = -4.1126339803469836976e-09 + p * w;\n            p = -2.9070369957882005086e-08 + p * w;\n            p = 4.2347877827932403518e-07 + p * w;\n            p = -1.3654692000834678645e-06 + p * w;\n            p = -1.3882523362786468719e-05 + p * w;\n            p = 0.0001867342080340571352 + p * w;\n            p = -0.00074070253416626697512 + p * w;\n            p = -0.0060336708714301490533 + p * w;\n            p = 0.24015818242558961693 + p * w;\n            p = 1.6536545626831027356 + p * w;\n        } else if (w < 16.000000) {\n            w = sqrt(w) - 3.250000;\n            p = 2.2137376921775787049e-09;\n            p = 9.0756561938885390979e-08 + p * w;\n            p = -2.7517406297064545428e-07 + p * w;\n            p = 1.8239629214389227755e-08 + p * w;\n            p = 1.5027403968909827627e-06 + p * w;\n            p = -4.013867526981545969e-06 + p * w;\n            p = 2.9234449089955446044e-06 + p * w;\n            p = 1.2475304481671778723e-05 + p * w;\n            p = -4.7318229009055733981e-05 + p * w;\n            p = 6.8284851459573175448e-05 + p * w;\n            p = 2.4031110387097893999e-05 + p * w;\n            p = -0.0003550375203628474796 + p * w;\n            p = 0.00095328937973738049703 + p * w;\n            p = -0.0016882755560235047313 + p * w;\n            p = 0.0024914420961078508066 + p * w;\n            p = -0.0037512085075692412107 + p * w;\n            p = 0.005370914553590063617 + p * w;\n            p = 1.0052589676941592334 + p * w;\n            p = 3.0838856104922207635 + p * w;\n        } else {\n            w = sqrt(w) - 5.000000;\n            p = -2.7109920616438573243e-11;\n            p = -2.5556418169965252055e-10 + p * w;\n            p = 1.5076572693500548083e-09 + p * w;\n            p = -3.7894654401267369937e-09 + p * w;\n            p = 7.6157012080783393804e-09 + p * w;\n            p = -1.4960026627149240478e-08 + p * w;\n            p = 2.9147953450901080826e-08 + p * w;\n            p = -6.7711997758452339498e-08 + p * w;\n            p = 2.2900482228026654717e-07 + p * w;\n            p = -9.9298272942317002539e-07 + p * w;\n            p = 4.5260625972231537039e-06 + p * w;\n            p = -1.9681778105531670567e-05 + p * w;\n            p = 7.5995277030017761139e-05 + p * w;\n            p = -0.00021503011930044477347 + p * w;\n            p = -0.00013871931833623122026 + p * w;\n            p = 1.0103004648645343977 + p * w;\n            p = 4.8499064014085844221 + p * w;\n        }\n        return p * x;\n    }\n\n    double standard_deviation(std::vector<double>::iterator first, std::vector<double>::iterator last) {\n        auto m = Catch::Benchmark::Detail::mean(first, last);\n        double variance = std::accumulate(first, last, 0., [m](double a, double b) {\n            double diff = b - m;\n            return a + diff * diff;\n            }) / (last - first);\n            return std::sqrt(variance);\n    }\n\n}\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n\n            double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last) {\n                auto count = last - first;\n                double idx = (count - 1) * k / static_cast<double>(q);\n                int j = static_cast<int>(idx);\n                double g = idx - j;\n                std::nth_element(first, first + j, last);\n                auto xj = first[j];\n                if (g == 0) return xj;\n\n                auto xj1 = *std::min_element(first + (j + 1), last);\n                return xj + g * (xj1 - xj);\n            }\n\n            double erfc_inv(double x) {\n                return erf_inv(1.0 - x);\n            }\n\n            double normal_quantile(double p) {\n                static const double ROOT_TWO = std::sqrt(2.0);\n\n                double result = 0.0;\n                assert(p >= 0 && p <= 1);\n                if (p < 0 || p > 1) {\n                    return result;\n                }\n\n                result = -erfc_inv(2.0 * p);\n                // result *= normal distribution standard deviation (1.0) * sqrt(2)\n                result *= /*sd * */ ROOT_TWO;\n                // result += normal disttribution mean (0)\n                return result;\n            }\n\n            double outlier_variance(Estimate<double> mean, Estimate<double> stddev, int n) {\n                double sb = stddev.point;\n                double mn = mean.point / n;\n                double mg_min = mn / 2.;\n                double sg = (std::min)(mg_min / 4., sb / std::sqrt(n));\n                double sg2 = sg * sg;\n                double sb2 = sb * sb;\n\n                auto c_max = [n, mn, sb2, sg2](double x) -> double {\n                    double k = mn - x;\n                    double d = k * k;\n                    double nd = n * d;\n                    double k0 = -n * nd;\n                    double k1 = sb2 - n * sg2 + nd;\n                    double det = k1 * k1 - 4 * sg2 * k0;\n                    return (int)(-2. * k0 / (k1 + std::sqrt(det)));\n                };\n\n                auto var_out = [n, sb2, sg2](double c) {\n                    double nc = n - c;\n                    return (nc / n) * (sb2 - nc * sg2);\n                };\n\n                return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2;\n            }\n\n            bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, std::vector<double>::iterator last) {\n                CATCH_INTERNAL_START_WARNINGS_SUPPRESSION\n                CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS\n                static std::random_device entropy;\n                CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION\n\n                auto n = static_cast<int>(last - first); // seriously, one can't use integral types without hell in C++\n\n                auto mean = &Detail::mean<std::vector<double>::iterator>;\n                auto stddev = &standard_deviation;\n\n#if defined(CATCH_CONFIG_USE_ASYNC)\n                auto Estimate = [=](double(*f)(std::vector<double>::iterator, std::vector<double>::iterator)) {\n                    auto seed = entropy();\n                    return std::async(std::launch::async, [=] {\n                        std::mt19937 rng(seed);\n                        auto resampled = resample(rng, n_resamples, first, last, f);\n                        return bootstrap(confidence_level, first, last, resampled, f);\n                    });\n                };\n\n                auto mean_future = Estimate(mean);\n                auto stddev_future = Estimate(stddev);\n\n                auto mean_estimate = mean_future.get();\n                auto stddev_estimate = stddev_future.get();\n#else\n                auto Estimate = [=](double(*f)(std::vector<double>::iterator, std::vector<double>::iterator)) {\n                    auto seed = entropy();\n                    std::mt19937 rng(seed);\n                    auto resampled = resample(rng, n_resamples, first, last, f);\n                    return bootstrap(confidence_level, first, last, resampled, f);\n                };\n\n                auto mean_estimate = Estimate(mean);\n                auto stddev_estimate = Estimate(stddev);\n#endif // CATCH_USE_ASYNC\n\n                double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n);\n\n                return { mean_estimate, stddev_estimate, outlier_variance };\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n// end catch_stats.cpp\n// start catch_approx.cpp\n\n#include <cmath>\n#include <limits>\n\nnamespace {\n\n// Performs equivalent check of std::fabs(lhs - rhs) <= margin\n// But without the subtraction to allow for INFINITY in comparison\nbool marginComparison(double lhs, double rhs, double margin) {\n    return (lhs + margin >= rhs) && (rhs + margin >= lhs);\n}\n\n}\n\nnamespace Catch {\nnamespace Detail {\n\n    Approx::Approx ( double value )\n    :   m_epsilon( std::numeric_limits<float>::epsilon()*100 ),\n        m_margin( 0.0 ),\n        m_scale( 0.0 ),\n        m_value( value )\n    {}\n\n    Approx Approx::custom() {\n        return Approx( 0 );\n    }\n\n    Approx Approx::operator-() const {\n        auto temp(*this);\n        temp.m_value = -temp.m_value;\n        return temp;\n    }\n\n    std::string Approx::toString() const {\n        ReusableStringStream rss;\n        rss << \"Approx( \" << ::Catch::Detail::stringify( m_value ) << \" )\";\n        return rss.str();\n    }\n\n    bool Approx::equalityComparisonImpl(const double other) const {\n        // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value\n        // Thanks to Richard Harris for his help refining the scaled margin value\n        return marginComparison(m_value, other, m_margin)\n            || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value)));\n    }\n\n    void Approx::setMargin(double newMargin) {\n        CATCH_ENFORCE(newMargin >= 0,\n            \"Invalid Approx::margin: \" << newMargin << '.'\n            << \" Approx::Margin has to be non-negative.\");\n        m_margin = newMargin;\n    }\n\n    void Approx::setEpsilon(double newEpsilon) {\n        CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0,\n            \"Invalid Approx::epsilon: \" << newEpsilon << '.'\n            << \" Approx::epsilon has to be in [0, 1]\");\n        m_epsilon = newEpsilon;\n    }\n\n} // end namespace Detail\n\nnamespace literals {\n    Detail::Approx operator \"\" _a(long double val) {\n        return Detail::Approx(val);\n    }\n    Detail::Approx operator \"\" _a(unsigned long long val) {\n        return Detail::Approx(val);\n    }\n} // end namespace literals\n\nstd::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const& value) {\n    return value.toString();\n}\n\n} // end namespace Catch\n// end catch_approx.cpp\n// start catch_assertionhandler.cpp\n\n// start catch_debugger.h\n\nnamespace Catch {\n    bool isDebuggerActive();\n}\n\n#ifdef CATCH_PLATFORM_MAC\n\n    #if defined(__i386__) || defined(__x86_64__)\n        #define CATCH_TRAP() __asm__(\"int $3\\n\" : : ) /* NOLINT */\n    #elif defined(__aarch64__)\n        #define CATCH_TRAP()  __asm__(\".inst 0xd4200000\")\n    #endif\n\n#elif defined(CATCH_PLATFORM_IPHONE)\n\n    // use inline assembler\n    #if defined(__i386__) || defined(__x86_64__)\n        #define CATCH_TRAP()  __asm__(\"int $3\")\n    #elif defined(__aarch64__)\n        #define CATCH_TRAP()  __asm__(\".inst 0xd4200000\")\n    #elif defined(__arm__) && !defined(__thumb__)\n        #define CATCH_TRAP()  __asm__(\".inst 0xe7f001f0\")\n    #elif defined(__arm__) &&  defined(__thumb__)\n        #define CATCH_TRAP()  __asm__(\".inst 0xde01\")\n    #endif\n\n#elif defined(CATCH_PLATFORM_LINUX)\n    // If we can use inline assembler, do it because this allows us to break\n    // directly at the location of the failing check instead of breaking inside\n    // raise() called from it, i.e. one stack frame below.\n    #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))\n        #define CATCH_TRAP() asm volatile (\"int $3\") /* NOLINT */\n    #else // Fall back to the generic way.\n        #include <signal.h>\n\n        #define CATCH_TRAP() raise(SIGTRAP)\n    #endif\n#elif defined(_MSC_VER)\n    #define CATCH_TRAP() __debugbreak()\n#elif defined(__MINGW32__)\n    extern \"C\" __declspec(dllimport) void __stdcall DebugBreak();\n    #define CATCH_TRAP() DebugBreak()\n#endif\n\n#ifndef CATCH_BREAK_INTO_DEBUGGER\n    #ifdef CATCH_TRAP\n        #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }()\n    #else\n        #define CATCH_BREAK_INTO_DEBUGGER() []{}()\n    #endif\n#endif\n\n// end catch_debugger.h\n// start catch_run_context.h\n\n// start catch_fatal_condition.h\n\n#include <cassert>\n\nnamespace Catch {\n\n    // Wrapper for platform-specific fatal error (signals/SEH) handlers\n    //\n    // Tries to be cooperative with other handlers, and not step over\n    // other handlers. This means that unknown structured exceptions\n    // are passed on, previous signal handlers are called, and so on.\n    //\n    // Can only be instantiated once, and assumes that once a signal\n    // is caught, the binary will end up terminating. Thus, there\n    class FatalConditionHandler {\n        bool m_started = false;\n\n        // Install/disengage implementation for specific platform.\n        // Should be if-defed to work on current platform, can assume\n        // engage-disengage 1:1 pairing.\n        void engage_platform();\n        void disengage_platform();\n    public:\n        // Should also have platform-specific implementations as needed\n        FatalConditionHandler();\n        ~FatalConditionHandler();\n\n        void engage() {\n            assert(!m_started && \"Handler cannot be installed twice.\");\n            m_started = true;\n            engage_platform();\n        }\n\n        void disengage() {\n            assert(m_started && \"Handler cannot be uninstalled without being installed first\");\n            m_started = false;\n            disengage_platform();\n        }\n    };\n\n    //! Simple RAII guard for (dis)engaging the FatalConditionHandler\n    class FatalConditionHandlerGuard {\n        FatalConditionHandler* m_handler;\n    public:\n        FatalConditionHandlerGuard(FatalConditionHandler* handler):\n            m_handler(handler) {\n            m_handler->engage();\n        }\n        ~FatalConditionHandlerGuard() {\n            m_handler->disengage();\n        }\n    };\n\n} // end namespace Catch\n\n// end catch_fatal_condition.h\n#include <string>\n\nnamespace Catch {\n\n    struct IMutableContext;\n\n    ///////////////////////////////////////////////////////////////////////////\n\n    class RunContext : public IResultCapture, public IRunner {\n\n    public:\n        RunContext( RunContext const& ) = delete;\n        RunContext& operator =( RunContext const& ) = delete;\n\n        explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter );\n\n        ~RunContext() override;\n\n        void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount );\n        void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount );\n\n        Totals runTest(TestCase const& testCase);\n\n        IConfigPtr config() const;\n        IStreamingReporter& reporter() const;\n\n    public: // IResultCapture\n\n        // Assertion handlers\n        void handleExpr\n                (   AssertionInfo const& info,\n                    ITransientExpression const& expr,\n                    AssertionReaction& reaction ) override;\n        void handleMessage\n                (   AssertionInfo const& info,\n                    ResultWas::OfType resultType,\n                    StringRef const& message,\n                    AssertionReaction& reaction ) override;\n        void handleUnexpectedExceptionNotThrown\n                (   AssertionInfo const& info,\n                    AssertionReaction& reaction ) override;\n        void handleUnexpectedInflightException\n                (   AssertionInfo const& info,\n                    std::string const& message,\n                    AssertionReaction& reaction ) override;\n        void handleIncomplete\n                (   AssertionInfo const& info ) override;\n        void handleNonExpr\n                (   AssertionInfo const &info,\n                    ResultWas::OfType resultType,\n                    AssertionReaction &reaction ) override;\n\n        bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override;\n\n        void sectionEnded( SectionEndInfo const& endInfo ) override;\n        void sectionEndedEarly( SectionEndInfo const& endInfo ) override;\n\n        auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& override;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n        void benchmarkPreparing( std::string const& name ) override;\n        void benchmarkStarting( BenchmarkInfo const& info ) override;\n        void benchmarkEnded( BenchmarkStats<> const& stats ) override;\n        void benchmarkFailed( std::string const& error ) override;\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n        void pushScopedMessage( MessageInfo const& message ) override;\n        void popScopedMessage( MessageInfo const& message ) override;\n\n        void emplaceUnscopedMessage( MessageBuilder const& builder ) override;\n\n        std::string getCurrentTestName() const override;\n\n        const AssertionResult* getLastResult() const override;\n\n        void exceptionEarlyReported() override;\n\n        void handleFatalErrorCondition( StringRef message ) override;\n\n        bool lastAssertionPassed() override;\n\n        void assertionPassed() override;\n\n    public:\n        // !TBD We need to do this another way!\n        bool aborting() const final;\n\n    private:\n\n        void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr );\n        void invokeActiveTestCase();\n\n        void resetAssertionInfo();\n        bool testForMissingAssertions( Counts& assertions );\n\n        void assertionEnded( AssertionResult const& result );\n        void reportExpr\n                (   AssertionInfo const &info,\n                    ResultWas::OfType resultType,\n                    ITransientExpression const *expr,\n                    bool negated );\n\n        void populateReaction( AssertionReaction& reaction );\n\n    private:\n\n        void handleUnfinishedSections();\n\n        TestRunInfo m_runInfo;\n        IMutableContext& m_context;\n        TestCase const* m_activeTestCase = nullptr;\n        ITracker* m_testCaseTracker = nullptr;\n        Option<AssertionResult> m_lastResult;\n\n        IConfigPtr m_config;\n        Totals m_totals;\n        IStreamingReporterPtr m_reporter;\n        std::vector<MessageInfo> m_messages;\n        std::vector<ScopedMessage> m_messageScopes; /* Keeps owners of so-called unscoped messages. */\n        AssertionInfo m_lastAssertionInfo;\n        std::vector<SectionEndInfo> m_unfinishedSections;\n        std::vector<ITracker*> m_activeSections;\n        TrackerContext m_trackerContext;\n        FatalConditionHandler m_fatalConditionhandler;\n        bool m_lastAssertionPassed = false;\n        bool m_shouldReportUnexpected = true;\n        bool m_includeSuccessfulResults;\n    };\n\n    void seedRng(IConfig const& config);\n    unsigned int rngSeed();\n} // end namespace Catch\n\n// end catch_run_context.h\nnamespace Catch {\n\n    namespace {\n        auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& {\n            expr.streamReconstructedExpression( os );\n            return os;\n        }\n    }\n\n    LazyExpression::LazyExpression( bool isNegated )\n    :   m_isNegated( isNegated )\n    {}\n\n    LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {}\n\n    LazyExpression::operator bool() const {\n        return m_transientExpression != nullptr;\n    }\n\n    auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& {\n        if( lazyExpr.m_isNegated )\n            os << \"!\";\n\n        if( lazyExpr ) {\n            if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() )\n                os << \"(\" << *lazyExpr.m_transientExpression << \")\";\n            else\n                os << *lazyExpr.m_transientExpression;\n        }\n        else {\n            os << \"{** error - unchecked empty expression requested **}\";\n        }\n        return os;\n    }\n\n    AssertionHandler::AssertionHandler\n        (   StringRef const& macroName,\n            SourceLineInfo const& lineInfo,\n            StringRef capturedExpression,\n            ResultDisposition::Flags resultDisposition )\n    :   m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition },\n        m_resultCapture( getResultCapture() )\n    {}\n\n    void AssertionHandler::handleExpr( ITransientExpression const& expr ) {\n        m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction );\n    }\n    void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) {\n        m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction );\n    }\n\n    auto AssertionHandler::allowThrows() const -> bool {\n        return getCurrentContext().getConfig()->allowThrows();\n    }\n\n    void AssertionHandler::complete() {\n        setCompleted();\n        if( m_reaction.shouldDebugBreak ) {\n\n            // If you find your debugger stopping you here then go one level up on the\n            // call-stack for the code that caused it (typically a failed assertion)\n\n            // (To go back to the test and change execution, jump over the throw, next)\n            CATCH_BREAK_INTO_DEBUGGER();\n        }\n        if (m_reaction.shouldThrow) {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n            throw Catch::TestFailureException();\n#else\n            CATCH_ERROR( \"Test failure requires aborting test!\" );\n#endif\n        }\n    }\n    void AssertionHandler::setCompleted() {\n        m_completed = true;\n    }\n\n    void AssertionHandler::handleUnexpectedInflightException() {\n        m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction );\n    }\n\n    void AssertionHandler::handleExceptionThrownAsExpected() {\n        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n    }\n    void AssertionHandler::handleExceptionNotThrownAsExpected() {\n        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n    }\n\n    void AssertionHandler::handleUnexpectedExceptionNotThrown() {\n        m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction );\n    }\n\n    void AssertionHandler::handleThrowingCallSkipped() {\n        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n    }\n\n    // This is the overload that takes a string and infers the Equals matcher from it\n    // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp\n    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef const& matcherString  ) {\n        handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString );\n    }\n\n} // namespace Catch\n// end catch_assertionhandler.cpp\n// start catch_assertionresult.cpp\n\nnamespace Catch {\n    AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression):\n        lazyExpression(_lazyExpression),\n        resultType(_resultType) {}\n\n    std::string AssertionResultData::reconstructExpression() const {\n\n        if( reconstructedExpression.empty() ) {\n            if( lazyExpression ) {\n                ReusableStringStream rss;\n                rss << lazyExpression;\n                reconstructedExpression = rss.str();\n            }\n        }\n        return reconstructedExpression;\n    }\n\n    AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data )\n    :   m_info( info ),\n        m_resultData( data )\n    {}\n\n    // Result was a success\n    bool AssertionResult::succeeded() const {\n        return Catch::isOk( m_resultData.resultType );\n    }\n\n    // Result was a success, or failure is suppressed\n    bool AssertionResult::isOk() const {\n        return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );\n    }\n\n    ResultWas::OfType AssertionResult::getResultType() const {\n        return m_resultData.resultType;\n    }\n\n    bool AssertionResult::hasExpression() const {\n        return !m_info.capturedExpression.empty();\n    }\n\n    bool AssertionResult::hasMessage() const {\n        return !m_resultData.message.empty();\n    }\n\n    std::string AssertionResult::getExpression() const {\n        // Possibly overallocating by 3 characters should be basically free\n        std::string expr; expr.reserve(m_info.capturedExpression.size() + 3);\n        if (isFalseTest(m_info.resultDisposition)) {\n            expr += \"!(\";\n        }\n        expr += m_info.capturedExpression;\n        if (isFalseTest(m_info.resultDisposition)) {\n            expr += ')';\n        }\n        return expr;\n    }\n\n    std::string AssertionResult::getExpressionInMacro() const {\n        std::string expr;\n        if( m_info.macroName.empty() )\n            expr = static_cast<std::string>(m_info.capturedExpression);\n        else {\n            expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 );\n            expr += m_info.macroName;\n            expr += \"( \";\n            expr += m_info.capturedExpression;\n            expr += \" )\";\n        }\n        return expr;\n    }\n\n    bool AssertionResult::hasExpandedExpression() const {\n        return hasExpression() && getExpandedExpression() != getExpression();\n    }\n\n    std::string AssertionResult::getExpandedExpression() const {\n        std::string expr = m_resultData.reconstructExpression();\n        return expr.empty()\n                ? getExpression()\n                : expr;\n    }\n\n    std::string AssertionResult::getMessage() const {\n        return m_resultData.message;\n    }\n    SourceLineInfo AssertionResult::getSourceInfo() const {\n        return m_info.lineInfo;\n    }\n\n    StringRef AssertionResult::getTestMacroName() const {\n        return m_info.macroName;\n    }\n\n} // end namespace Catch\n// end catch_assertionresult.cpp\n// start catch_capture_matchers.cpp\n\nnamespace Catch {\n\n    using StringMatcher = Matchers::Impl::MatcherBase<std::string>;\n\n    // This is the general overload that takes a any string matcher\n    // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers\n    // the Equals matcher (so the header does not mention matchers)\n    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString  ) {\n        std::string exceptionMessage = Catch::translateActiveException();\n        MatchExpr<std::string, StringMatcher const&> expr( exceptionMessage, matcher, matcherString );\n        handler.handleExpr( expr );\n    }\n\n} // namespace Catch\n// end catch_capture_matchers.cpp\n// start catch_commandline.cpp\n\n// start catch_commandline.h\n\n// start catch_clara.h\n\n// Use Catch's value for console width (store Clara's off to the side, if present)\n#ifdef CLARA_CONFIG_CONSOLE_WIDTH\n#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#endif\n#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wweak-vtables\"\n#pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#pragma clang diagnostic ignored \"-Wshadow\"\n#endif\n\n// start clara.hpp\n// Copyright 2017 Two Blue Cubes Ltd. All rights reserved.\n//\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n//\n// See https://github.com/philsquared/Clara for more details\n\n// Clara v1.1.5\n\n\n#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH\n#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80\n#endif\n\n#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH\n#endif\n\n#ifndef CLARA_CONFIG_OPTIONAL_TYPE\n#ifdef __has_include\n#if __has_include(<optional>) && __cplusplus >= 201703L\n#include <optional>\n#define CLARA_CONFIG_OPTIONAL_TYPE std::optional\n#endif\n#endif\n#endif\n\n// ----------- #included from clara_textflow.hpp -----------\n\n// TextFlowCpp\n//\n// A single-header library for wrapping and laying out basic text, by Phil Nash\n//\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n//\n// This project is hosted at https://github.com/philsquared/textflowcpp\n\n\n#include <cassert>\n#include <ostream>\n#include <sstream>\n#include <vector>\n\n#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH\n#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80\n#endif\n\nnamespace Catch {\nnamespace clara {\nnamespace TextFlow {\n\ninline auto isWhitespace(char c) -> bool {\n\tstatic std::string chars = \" \\t\\n\\r\";\n\treturn chars.find(c) != std::string::npos;\n}\ninline auto isBreakableBefore(char c) -> bool {\n\tstatic std::string chars = \"[({<|\";\n\treturn chars.find(c) != std::string::npos;\n}\ninline auto isBreakableAfter(char c) -> bool {\n\tstatic std::string chars = \"])}>.,:;*+-=&/\\\\\";\n\treturn chars.find(c) != std::string::npos;\n}\n\nclass Columns;\n\nclass Column {\n\tstd::vector<std::string> m_strings;\n\tsize_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;\n\tsize_t m_indent = 0;\n\tsize_t m_initialIndent = std::string::npos;\n\npublic:\n\tclass iterator {\n\t\tfriend Column;\n\n\t\tColumn const& m_column;\n\t\tsize_t m_stringIndex = 0;\n\t\tsize_t m_pos = 0;\n\n\t\tsize_t m_len = 0;\n\t\tsize_t m_end = 0;\n\t\tbool m_suffix = false;\n\n\t\titerator(Column const& column, size_t stringIndex)\n\t\t\t: m_column(column),\n\t\t\tm_stringIndex(stringIndex) {}\n\n\t\tauto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; }\n\n\t\tauto isBoundary(size_t at) const -> bool {\n\t\t\tassert(at > 0);\n\t\t\tassert(at <= line().size());\n\n\t\t\treturn at == line().size() ||\n\t\t\t\t(isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) ||\n\t\t\t\tisBreakableBefore(line()[at]) ||\n\t\t\t\tisBreakableAfter(line()[at - 1]);\n\t\t}\n\n\t\tvoid calcLength() {\n\t\t\tassert(m_stringIndex < m_column.m_strings.size());\n\n\t\t\tm_suffix = false;\n\t\t\tauto width = m_column.m_width - indent();\n\t\t\tm_end = m_pos;\n\t\t\tif (line()[m_pos] == '\\n') {\n\t\t\t\t++m_end;\n\t\t\t}\n\t\t\twhile (m_end < line().size() && line()[m_end] != '\\n')\n\t\t\t\t++m_end;\n\n\t\t\tif (m_end < m_pos + width) {\n\t\t\t\tm_len = m_end - m_pos;\n\t\t\t} else {\n\t\t\t\tsize_t len = width;\n\t\t\t\twhile (len > 0 && !isBoundary(m_pos + len))\n\t\t\t\t\t--len;\n\t\t\t\twhile (len > 0 && isWhitespace(line()[m_pos + len - 1]))\n\t\t\t\t\t--len;\n\n\t\t\t\tif (len > 0) {\n\t\t\t\t\tm_len = len;\n\t\t\t\t} else {\n\t\t\t\t\tm_suffix = true;\n\t\t\t\t\tm_len = width - 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tauto indent() const -> size_t {\n\t\t\tauto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos;\n\t\t\treturn initial == std::string::npos ? m_column.m_indent : initial;\n\t\t}\n\n\t\tauto addIndentAndSuffix(std::string const &plain) const -> std::string {\n\t\t\treturn std::string(indent(), ' ') + (m_suffix ? plain + \"-\" : plain);\n\t\t}\n\n\tpublic:\n\t\tusing difference_type = std::ptrdiff_t;\n\t\tusing value_type = std::string;\n\t\tusing pointer = value_type * ;\n\t\tusing reference = value_type & ;\n\t\tusing iterator_category = std::forward_iterator_tag;\n\n\t\texplicit iterator(Column const& column) : m_column(column) {\n\t\t\tassert(m_column.m_width > m_column.m_indent);\n\t\t\tassert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent);\n\t\t\tcalcLength();\n\t\t\tif (m_len == 0)\n\t\t\t\tm_stringIndex++; // Empty string\n\t\t}\n\n\t\tauto operator *() const -> std::string {\n\t\t\tassert(m_stringIndex < m_column.m_strings.size());\n\t\t\tassert(m_pos <= m_end);\n\t\t\treturn addIndentAndSuffix(line().substr(m_pos, m_len));\n\t\t}\n\n\t\tauto operator ++() -> iterator& {\n\t\t\tm_pos += m_len;\n\t\t\tif (m_pos < line().size() && line()[m_pos] == '\\n')\n\t\t\t\tm_pos += 1;\n\t\t\telse\n\t\t\t\twhile (m_pos < line().size() && isWhitespace(line()[m_pos]))\n\t\t\t\t\t++m_pos;\n\n\t\t\tif (m_pos == line().size()) {\n\t\t\t\tm_pos = 0;\n\t\t\t\t++m_stringIndex;\n\t\t\t}\n\t\t\tif (m_stringIndex < m_column.m_strings.size())\n\t\t\t\tcalcLength();\n\t\t\treturn *this;\n\t\t}\n\t\tauto operator ++(int) -> iterator {\n\t\t\titerator prev(*this);\n\t\t\toperator++();\n\t\t\treturn prev;\n\t\t}\n\n\t\tauto operator ==(iterator const& other) const -> bool {\n\t\t\treturn\n\t\t\t\tm_pos == other.m_pos &&\n\t\t\t\tm_stringIndex == other.m_stringIndex &&\n\t\t\t\t&m_column == &other.m_column;\n\t\t}\n\t\tauto operator !=(iterator const& other) const -> bool {\n\t\t\treturn !operator==(other);\n\t\t}\n\t};\n\tusing const_iterator = iterator;\n\n\texplicit Column(std::string const& text) { m_strings.push_back(text); }\n\n\tauto width(size_t newWidth) -> Column& {\n\t\tassert(newWidth > 0);\n\t\tm_width = newWidth;\n\t\treturn *this;\n\t}\n\tauto indent(size_t newIndent) -> Column& {\n\t\tm_indent = newIndent;\n\t\treturn *this;\n\t}\n\tauto initialIndent(size_t newIndent) -> Column& {\n\t\tm_initialIndent = newIndent;\n\t\treturn *this;\n\t}\n\n\tauto width() const -> size_t { return m_width; }\n\tauto begin() const -> iterator { return iterator(*this); }\n\tauto end() const -> iterator { return { *this, m_strings.size() }; }\n\n\tinline friend std::ostream& operator << (std::ostream& os, Column const& col) {\n\t\tbool first = true;\n\t\tfor (auto line : col) {\n\t\t\tif (first)\n\t\t\t\tfirst = false;\n\t\t\telse\n\t\t\t\tos << \"\\n\";\n\t\t\tos << line;\n\t\t}\n\t\treturn os;\n\t}\n\n\tauto operator + (Column const& other)->Columns;\n\n\tauto toString() const -> std::string {\n\t\tstd::ostringstream oss;\n\t\toss << *this;\n\t\treturn oss.str();\n\t}\n};\n\nclass Spacer : public Column {\n\npublic:\n\texplicit Spacer(size_t spaceWidth) : Column(\"\") {\n\t\twidth(spaceWidth);\n\t}\n};\n\nclass Columns {\n\tstd::vector<Column> m_columns;\n\npublic:\n\n\tclass iterator {\n\t\tfriend Columns;\n\t\tstruct EndTag {};\n\n\t\tstd::vector<Column> const& m_columns;\n\t\tstd::vector<Column::iterator> m_iterators;\n\t\tsize_t m_activeIterators;\n\n\t\titerator(Columns const& columns, EndTag)\n\t\t\t: m_columns(columns.m_columns),\n\t\t\tm_activeIterators(0) {\n\t\t\tm_iterators.reserve(m_columns.size());\n\n\t\t\tfor (auto const& col : m_columns)\n\t\t\t\tm_iterators.push_back(col.end());\n\t\t}\n\n\tpublic:\n\t\tusing difference_type = std::ptrdiff_t;\n\t\tusing value_type = std::string;\n\t\tusing pointer = value_type * ;\n\t\tusing reference = value_type & ;\n\t\tusing iterator_category = std::forward_iterator_tag;\n\n\t\texplicit iterator(Columns const& columns)\n\t\t\t: m_columns(columns.m_columns),\n\t\t\tm_activeIterators(m_columns.size()) {\n\t\t\tm_iterators.reserve(m_columns.size());\n\n\t\t\tfor (auto const& col : m_columns)\n\t\t\t\tm_iterators.push_back(col.begin());\n\t\t}\n\n\t\tauto operator ==(iterator const& other) const -> bool {\n\t\t\treturn m_iterators == other.m_iterators;\n\t\t}\n\t\tauto operator !=(iterator const& other) const -> bool {\n\t\t\treturn m_iterators != other.m_iterators;\n\t\t}\n\t\tauto operator *() const -> std::string {\n\t\t\tstd::string row, padding;\n\n\t\t\tfor (size_t i = 0; i < m_columns.size(); ++i) {\n\t\t\t\tauto width = m_columns[i].width();\n\t\t\t\tif (m_iterators[i] != m_columns[i].end()) {\n\t\t\t\t\tstd::string col = *m_iterators[i];\n\t\t\t\t\trow += padding + col;\n\t\t\t\t\tif (col.size() < width)\n\t\t\t\t\t\tpadding = std::string(width - col.size(), ' ');\n\t\t\t\t\telse\n\t\t\t\t\t\tpadding = \"\";\n\t\t\t\t} else {\n\t\t\t\t\tpadding += std::string(width, ' ');\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn row;\n\t\t}\n\t\tauto operator ++() -> iterator& {\n\t\t\tfor (size_t i = 0; i < m_columns.size(); ++i) {\n\t\t\t\tif (m_iterators[i] != m_columns[i].end())\n\t\t\t\t\t++m_iterators[i];\n\t\t\t}\n\t\t\treturn *this;\n\t\t}\n\t\tauto operator ++(int) -> iterator {\n\t\t\titerator prev(*this);\n\t\t\toperator++();\n\t\t\treturn prev;\n\t\t}\n\t};\n\tusing const_iterator = iterator;\n\n\tauto begin() const -> iterator { return iterator(*this); }\n\tauto end() const -> iterator { return { *this, iterator::EndTag() }; }\n\n\tauto operator += (Column const& col) -> Columns& {\n\t\tm_columns.push_back(col);\n\t\treturn *this;\n\t}\n\tauto operator + (Column const& col) -> Columns {\n\t\tColumns combined = *this;\n\t\tcombined += col;\n\t\treturn combined;\n\t}\n\n\tinline friend std::ostream& operator << (std::ostream& os, Columns const& cols) {\n\n\t\tbool first = true;\n\t\tfor (auto line : cols) {\n\t\t\tif (first)\n\t\t\t\tfirst = false;\n\t\t\telse\n\t\t\t\tos << \"\\n\";\n\t\t\tos << line;\n\t\t}\n\t\treturn os;\n\t}\n\n\tauto toString() const -> std::string {\n\t\tstd::ostringstream oss;\n\t\toss << *this;\n\t\treturn oss.str();\n\t}\n};\n\ninline auto Column::operator + (Column const& other) -> Columns {\n\tColumns cols;\n\tcols += *this;\n\tcols += other;\n\treturn cols;\n}\n}\n\n}\n}\n\n// ----------- end of #include from clara_textflow.hpp -----------\n// ........... back in clara.hpp\n\n#include <cctype>\n#include <string>\n#include <memory>\n#include <set>\n#include <algorithm>\n\n#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )\n#define CATCH_PLATFORM_WINDOWS\n#endif\n\nnamespace Catch { namespace clara {\nnamespace detail {\n\n    // Traits for extracting arg and return type of lambdas (for single argument lambdas)\n    template<typename L>\n    struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {};\n\n    template<typename ClassT, typename ReturnT, typename... Args>\n    struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> {\n        static const bool isValid = false;\n    };\n\n    template<typename ClassT, typename ReturnT, typename ArgT>\n    struct UnaryLambdaTraits<ReturnT( ClassT::* )( ArgT ) const> {\n        static const bool isValid = true;\n        using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type;\n        using ReturnType = ReturnT;\n    };\n\n    class TokenStream;\n\n    // Transport for raw args (copied from main args, or supplied via init list for testing)\n    class Args {\n        friend TokenStream;\n        std::string m_exeName;\n        std::vector<std::string> m_args;\n\n    public:\n        Args( int argc, char const* const* argv )\n            : m_exeName(argv[0]),\n              m_args(argv + 1, argv + argc) {}\n\n        Args( std::initializer_list<std::string> args )\n        :   m_exeName( *args.begin() ),\n            m_args( args.begin()+1, args.end() )\n        {}\n\n        auto exeName() const -> std::string {\n            return m_exeName;\n        }\n    };\n\n    // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string\n    // may encode an option + its argument if the : or = form is used\n    enum class TokenType {\n        Option, Argument\n    };\n    struct Token {\n        TokenType type;\n        std::string token;\n    };\n\n    inline auto isOptPrefix( char c ) -> bool {\n        return c == '-'\n#ifdef CATCH_PLATFORM_WINDOWS\n            || c == '/'\n#endif\n        ;\n    }\n\n    // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled\n    class TokenStream {\n        using Iterator = std::vector<std::string>::const_iterator;\n        Iterator it;\n        Iterator itEnd;\n        std::vector<Token> m_tokenBuffer;\n\n        void loadBuffer() {\n            m_tokenBuffer.resize( 0 );\n\n            // Skip any empty strings\n            while( it != itEnd && it->empty() )\n                ++it;\n\n            if( it != itEnd ) {\n                auto const &next = *it;\n                if( isOptPrefix( next[0] ) ) {\n                    auto delimiterPos = next.find_first_of( \" :=\" );\n                    if( delimiterPos != std::string::npos ) {\n                        m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } );\n                        m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } );\n                    } else {\n                        if( next[1] != '-' && next.size() > 2 ) {\n                            std::string opt = \"- \";\n                            for( size_t i = 1; i < next.size(); ++i ) {\n                                opt[1] = next[i];\n                                m_tokenBuffer.push_back( { TokenType::Option, opt } );\n                            }\n                        } else {\n                            m_tokenBuffer.push_back( { TokenType::Option, next } );\n                        }\n                    }\n                } else {\n                    m_tokenBuffer.push_back( { TokenType::Argument, next } );\n                }\n            }\n        }\n\n    public:\n        explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {}\n\n        TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) {\n            loadBuffer();\n        }\n\n        explicit operator bool() const {\n            return !m_tokenBuffer.empty() || it != itEnd;\n        }\n\n        auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); }\n\n        auto operator*() const -> Token {\n            assert( !m_tokenBuffer.empty() );\n            return m_tokenBuffer.front();\n        }\n\n        auto operator->() const -> Token const * {\n            assert( !m_tokenBuffer.empty() );\n            return &m_tokenBuffer.front();\n        }\n\n        auto operator++() -> TokenStream & {\n            if( m_tokenBuffer.size() >= 2 ) {\n                m_tokenBuffer.erase( m_tokenBuffer.begin() );\n            } else {\n                if( it != itEnd )\n                    ++it;\n                loadBuffer();\n            }\n            return *this;\n        }\n    };\n\n    class ResultBase {\n    public:\n        enum Type {\n            Ok, LogicError, RuntimeError\n        };\n\n    protected:\n        ResultBase( Type type ) : m_type( type ) {}\n        virtual ~ResultBase() = default;\n\n        virtual void enforceOk() const = 0;\n\n        Type m_type;\n    };\n\n    template<typename T>\n    class ResultValueBase : public ResultBase {\n    public:\n        auto value() const -> T const & {\n            enforceOk();\n            return m_value;\n        }\n\n    protected:\n        ResultValueBase( Type type ) : ResultBase( type ) {}\n\n        ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) {\n            if( m_type == ResultBase::Ok )\n                new( &m_value ) T( other.m_value );\n        }\n\n        ResultValueBase( Type, T const &value ) : ResultBase( Ok ) {\n            new( &m_value ) T( value );\n        }\n\n        auto operator=( ResultValueBase const &other ) -> ResultValueBase & {\n            if( m_type == ResultBase::Ok )\n                m_value.~T();\n            ResultBase::operator=(other);\n            if( m_type == ResultBase::Ok )\n                new( &m_value ) T( other.m_value );\n            return *this;\n        }\n\n        ~ResultValueBase() override {\n            if( m_type == Ok )\n                m_value.~T();\n        }\n\n        union {\n            T m_value;\n        };\n    };\n\n    template<>\n    class ResultValueBase<void> : public ResultBase {\n    protected:\n        using ResultBase::ResultBase;\n    };\n\n    template<typename T = void>\n    class BasicResult : public ResultValueBase<T> {\n    public:\n        template<typename U>\n        explicit BasicResult( BasicResult<U> const &other )\n        :   ResultValueBase<T>( other.type() ),\n            m_errorMessage( other.errorMessage() )\n        {\n            assert( type() != ResultBase::Ok );\n        }\n\n        template<typename U>\n        static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; }\n        static auto ok() -> BasicResult { return { ResultBase::Ok }; }\n        static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; }\n        static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; }\n\n        explicit operator bool() const { return m_type == ResultBase::Ok; }\n        auto type() const -> ResultBase::Type { return m_type; }\n        auto errorMessage() const -> std::string { return m_errorMessage; }\n\n    protected:\n        void enforceOk() const override {\n\n            // Errors shouldn't reach this point, but if they do\n            // the actual error message will be in m_errorMessage\n            assert( m_type != ResultBase::LogicError );\n            assert( m_type != ResultBase::RuntimeError );\n            if( m_type != ResultBase::Ok )\n                std::abort();\n        }\n\n        std::string m_errorMessage; // Only populated if resultType is an error\n\n        BasicResult( ResultBase::Type type, std::string const &message )\n        :   ResultValueBase<T>(type),\n            m_errorMessage(message)\n        {\n            assert( m_type != ResultBase::Ok );\n        }\n\n        using ResultValueBase<T>::ResultValueBase;\n        using ResultBase::m_type;\n    };\n\n    enum class ParseResultType {\n        Matched, NoMatch, ShortCircuitAll, ShortCircuitSame\n    };\n\n    class ParseState {\n    public:\n\n        ParseState( ParseResultType type, TokenStream const &remainingTokens )\n        : m_type(type),\n          m_remainingTokens( remainingTokens )\n        {}\n\n        auto type() const -> ParseResultType { return m_type; }\n        auto remainingTokens() const -> TokenStream { return m_remainingTokens; }\n\n    private:\n        ParseResultType m_type;\n        TokenStream m_remainingTokens;\n    };\n\n    using Result = BasicResult<void>;\n    using ParserResult = BasicResult<ParseResultType>;\n    using InternalParseResult = BasicResult<ParseState>;\n\n    struct HelpColumns {\n        std::string left;\n        std::string right;\n    };\n\n    template<typename T>\n    inline auto convertInto( std::string const &source, T& target ) -> ParserResult {\n        std::stringstream ss;\n        ss << source;\n        ss >> target;\n        if( ss.fail() )\n            return ParserResult::runtimeError( \"Unable to convert '\" + source + \"' to destination type\" );\n        else\n            return ParserResult::ok( ParseResultType::Matched );\n    }\n    inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult {\n        target = source;\n        return ParserResult::ok( ParseResultType::Matched );\n    }\n    inline auto convertInto( std::string const &source, bool &target ) -> ParserResult {\n        std::string srcLC = source;\n        std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( unsigned char c ) { return static_cast<char>( std::tolower(c) ); } );\n        if (srcLC == \"y\" || srcLC == \"1\" || srcLC == \"true\" || srcLC == \"yes\" || srcLC == \"on\")\n            target = true;\n        else if (srcLC == \"n\" || srcLC == \"0\" || srcLC == \"false\" || srcLC == \"no\" || srcLC == \"off\")\n            target = false;\n        else\n            return ParserResult::runtimeError( \"Expected a boolean value but did not recognise: '\" + source + \"'\" );\n        return ParserResult::ok( ParseResultType::Matched );\n    }\n#ifdef CLARA_CONFIG_OPTIONAL_TYPE\n    template<typename T>\n    inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T>& target ) -> ParserResult {\n        T temp;\n        auto result = convertInto( source, temp );\n        if( result )\n            target = std::move(temp);\n        return result;\n    }\n#endif // CLARA_CONFIG_OPTIONAL_TYPE\n\n    struct NonCopyable {\n        NonCopyable() = default;\n        NonCopyable( NonCopyable const & ) = delete;\n        NonCopyable( NonCopyable && ) = delete;\n        NonCopyable &operator=( NonCopyable const & ) = delete;\n        NonCopyable &operator=( NonCopyable && ) = delete;\n    };\n\n    struct BoundRef : NonCopyable {\n        virtual ~BoundRef() = default;\n        virtual auto isContainer() const -> bool { return false; }\n        virtual auto isFlag() const -> bool { return false; }\n    };\n    struct BoundValueRefBase : BoundRef {\n        virtual auto setValue( std::string const &arg ) -> ParserResult = 0;\n    };\n    struct BoundFlagRefBase : BoundRef {\n        virtual auto setFlag( bool flag ) -> ParserResult = 0;\n        virtual auto isFlag() const -> bool { return true; }\n    };\n\n    template<typename T>\n    struct BoundValueRef : BoundValueRefBase {\n        T &m_ref;\n\n        explicit BoundValueRef( T &ref ) : m_ref( ref ) {}\n\n        auto setValue( std::string const &arg ) -> ParserResult override {\n            return convertInto( arg, m_ref );\n        }\n    };\n\n    template<typename T>\n    struct BoundValueRef<std::vector<T>> : BoundValueRefBase {\n        std::vector<T> &m_ref;\n\n        explicit BoundValueRef( std::vector<T> &ref ) : m_ref( ref ) {}\n\n        auto isContainer() const -> bool override { return true; }\n\n        auto setValue( std::string const &arg ) -> ParserResult override {\n            T temp;\n            auto result = convertInto( arg, temp );\n            if( result )\n                m_ref.push_back( temp );\n            return result;\n        }\n    };\n\n    struct BoundFlagRef : BoundFlagRefBase {\n        bool &m_ref;\n\n        explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {}\n\n        auto setFlag( bool flag ) -> ParserResult override {\n            m_ref = flag;\n            return ParserResult::ok( ParseResultType::Matched );\n        }\n    };\n\n    template<typename ReturnType>\n    struct LambdaInvoker {\n        static_assert( std::is_same<ReturnType, ParserResult>::value, \"Lambda must return void or clara::ParserResult\" );\n\n        template<typename L, typename ArgType>\n        static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {\n            return lambda( arg );\n        }\n    };\n\n    template<>\n    struct LambdaInvoker<void> {\n        template<typename L, typename ArgType>\n        static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {\n            lambda( arg );\n            return ParserResult::ok( ParseResultType::Matched );\n        }\n    };\n\n    template<typename ArgType, typename L>\n    inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult {\n        ArgType temp{};\n        auto result = convertInto( arg, temp );\n        return !result\n           ? result\n           : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp );\n    }\n\n    template<typename L>\n    struct BoundLambda : BoundValueRefBase {\n        L m_lambda;\n\n        static_assert( UnaryLambdaTraits<L>::isValid, \"Supplied lambda must take exactly one argument\" );\n        explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {}\n\n        auto setValue( std::string const &arg ) -> ParserResult override {\n            return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg );\n        }\n    };\n\n    template<typename L>\n    struct BoundFlagLambda : BoundFlagRefBase {\n        L m_lambda;\n\n        static_assert( UnaryLambdaTraits<L>::isValid, \"Supplied lambda must take exactly one argument\" );\n        static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, \"flags must be boolean\" );\n\n        explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {}\n\n        auto setFlag( bool flag ) -> ParserResult override {\n            return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag );\n        }\n    };\n\n    enum class Optionality { Optional, Required };\n\n    struct Parser;\n\n    class ParserBase {\n    public:\n        virtual ~ParserBase() = default;\n        virtual auto validate() const -> Result { return Result::ok(); }\n        virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult  = 0;\n        virtual auto cardinality() const -> size_t { return 1; }\n\n        auto parse( Args const &args ) const -> InternalParseResult {\n            return parse( args.exeName(), TokenStream( args ) );\n        }\n    };\n\n    template<typename DerivedT>\n    class ComposableParserImpl : public ParserBase {\n    public:\n        template<typename T>\n        auto operator|( T const &other ) const -> Parser;\n\n\t\ttemplate<typename T>\n        auto operator+( T const &other ) const -> Parser;\n    };\n\n    // Common code and state for Args and Opts\n    template<typename DerivedT>\n    class ParserRefImpl : public ComposableParserImpl<DerivedT> {\n    protected:\n        Optionality m_optionality = Optionality::Optional;\n        std::shared_ptr<BoundRef> m_ref;\n        std::string m_hint;\n        std::string m_description;\n\n        explicit ParserRefImpl( std::shared_ptr<BoundRef> const &ref ) : m_ref( ref ) {}\n\n    public:\n        template<typename T>\n        ParserRefImpl( T &ref, std::string const &hint )\n        :   m_ref( std::make_shared<BoundValueRef<T>>( ref ) ),\n            m_hint( hint )\n        {}\n\n        template<typename LambdaT>\n        ParserRefImpl( LambdaT const &ref, std::string const &hint )\n        :   m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ),\n            m_hint(hint)\n        {}\n\n        auto operator()( std::string const &description ) -> DerivedT & {\n            m_description = description;\n            return static_cast<DerivedT &>( *this );\n        }\n\n        auto optional() -> DerivedT & {\n            m_optionality = Optionality::Optional;\n            return static_cast<DerivedT &>( *this );\n        };\n\n        auto required() -> DerivedT & {\n            m_optionality = Optionality::Required;\n            return static_cast<DerivedT &>( *this );\n        };\n\n        auto isOptional() const -> bool {\n            return m_optionality == Optionality::Optional;\n        }\n\n        auto cardinality() const -> size_t override {\n            if( m_ref->isContainer() )\n                return 0;\n            else\n                return 1;\n        }\n\n        auto hint() const -> std::string { return m_hint; }\n    };\n\n    class ExeName : public ComposableParserImpl<ExeName> {\n        std::shared_ptr<std::string> m_name;\n        std::shared_ptr<BoundValueRefBase> m_ref;\n\n        template<typename LambdaT>\n        static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundValueRefBase> {\n            return std::make_shared<BoundLambda<LambdaT>>( lambda) ;\n        }\n\n    public:\n        ExeName() : m_name( std::make_shared<std::string>( \"<executable>\" ) ) {}\n\n        explicit ExeName( std::string &ref ) : ExeName() {\n            m_ref = std::make_shared<BoundValueRef<std::string>>( ref );\n        }\n\n        template<typename LambdaT>\n        explicit ExeName( LambdaT const& lambda ) : ExeName() {\n            m_ref = std::make_shared<BoundLambda<LambdaT>>( lambda );\n        }\n\n        // The exe name is not parsed out of the normal tokens, but is handled specially\n        auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {\n            return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );\n        }\n\n        auto name() const -> std::string { return *m_name; }\n        auto set( std::string const& newName ) -> ParserResult {\n\n            auto lastSlash = newName.find_last_of( \"\\\\/\" );\n            auto filename = ( lastSlash == std::string::npos )\n                    ? newName\n                    : newName.substr( lastSlash+1 );\n\n            *m_name = filename;\n            if( m_ref )\n                return m_ref->setValue( filename );\n            else\n                return ParserResult::ok( ParseResultType::Matched );\n        }\n    };\n\n    class Arg : public ParserRefImpl<Arg> {\n    public:\n        using ParserRefImpl::ParserRefImpl;\n\n        auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override {\n            auto validationResult = validate();\n            if( !validationResult )\n                return InternalParseResult( validationResult );\n\n            auto remainingTokens = tokens;\n            auto const &token = *remainingTokens;\n            if( token.type != TokenType::Argument )\n                return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );\n\n            assert( !m_ref->isFlag() );\n            auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() );\n\n            auto result = valueRef->setValue( remainingTokens->token );\n            if( !result )\n                return InternalParseResult( result );\n            else\n                return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );\n        }\n    };\n\n    inline auto normaliseOpt( std::string const &optName ) -> std::string {\n#ifdef CATCH_PLATFORM_WINDOWS\n        if( optName[0] == '/' )\n            return \"-\" + optName.substr( 1 );\n        else\n#endif\n            return optName;\n    }\n\n    class Opt : public ParserRefImpl<Opt> {\n    protected:\n        std::vector<std::string> m_optNames;\n\n    public:\n        template<typename LambdaT>\n        explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {}\n\n        explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared<BoundFlagRef>( ref ) ) {}\n\n        template<typename LambdaT>\n        Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}\n\n        template<typename T>\n        Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}\n\n        auto operator[]( std::string const &optName ) -> Opt & {\n            m_optNames.push_back( optName );\n            return *this;\n        }\n\n        auto getHelpColumns() const -> std::vector<HelpColumns> {\n            std::ostringstream oss;\n            bool first = true;\n            for( auto const &opt : m_optNames ) {\n                if (first)\n                    first = false;\n                else\n                    oss << \", \";\n                oss << opt;\n            }\n            if( !m_hint.empty() )\n                oss << \" <\" << m_hint << \">\";\n            return { { oss.str(), m_description } };\n        }\n\n        auto isMatch( std::string const &optToken ) const -> bool {\n            auto normalisedToken = normaliseOpt( optToken );\n            for( auto const &name : m_optNames ) {\n                if( normaliseOpt( name ) == normalisedToken )\n                    return true;\n            }\n            return false;\n        }\n\n        using ParserBase::parse;\n\n        auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {\n            auto validationResult = validate();\n            if( !validationResult )\n                return InternalParseResult( validationResult );\n\n            auto remainingTokens = tokens;\n            if( remainingTokens && remainingTokens->type == TokenType::Option ) {\n                auto const &token = *remainingTokens;\n                if( isMatch(token.token ) ) {\n                    if( m_ref->isFlag() ) {\n                        auto flagRef = static_cast<detail::BoundFlagRefBase*>( m_ref.get() );\n                        auto result = flagRef->setFlag( true );\n                        if( !result )\n                            return InternalParseResult( result );\n                        if( result.value() == ParseResultType::ShortCircuitAll )\n                            return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );\n                    } else {\n                        auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() );\n                        ++remainingTokens;\n                        if( !remainingTokens )\n                            return InternalParseResult::runtimeError( \"Expected argument following \" + token.token );\n                        auto const &argToken = *remainingTokens;\n                        if( argToken.type != TokenType::Argument )\n                            return InternalParseResult::runtimeError( \"Expected argument following \" + token.token );\n                        auto result = valueRef->setValue( argToken.token );\n                        if( !result )\n                            return InternalParseResult( result );\n                        if( result.value() == ParseResultType::ShortCircuitAll )\n                            return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );\n                    }\n                    return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );\n                }\n            }\n            return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );\n        }\n\n        auto validate() const -> Result override {\n            if( m_optNames.empty() )\n                return Result::logicError( \"No options supplied to Opt\" );\n            for( auto const &name : m_optNames ) {\n                if( name.empty() )\n                    return Result::logicError( \"Option name cannot be empty\" );\n#ifdef CATCH_PLATFORM_WINDOWS\n                if( name[0] != '-' && name[0] != '/' )\n                    return Result::logicError( \"Option name must begin with '-' or '/'\" );\n#else\n                if( name[0] != '-' )\n                    return Result::logicError( \"Option name must begin with '-'\" );\n#endif\n            }\n            return ParserRefImpl::validate();\n        }\n    };\n\n    struct Help : Opt {\n        Help( bool &showHelpFlag )\n        :   Opt([&]( bool flag ) {\n                showHelpFlag = flag;\n                return ParserResult::ok( ParseResultType::ShortCircuitAll );\n            })\n        {\n            static_cast<Opt &>( *this )\n                    (\"display usage information\")\n                    [\"-?\"][\"-h\"][\"--help\"]\n                    .optional();\n        }\n    };\n\n    struct Parser : ParserBase {\n\n        mutable ExeName m_exeName;\n        std::vector<Opt> m_options;\n        std::vector<Arg> m_args;\n\n        auto operator|=( ExeName const &exeName ) -> Parser & {\n            m_exeName = exeName;\n            return *this;\n        }\n\n        auto operator|=( Arg const &arg ) -> Parser & {\n            m_args.push_back(arg);\n            return *this;\n        }\n\n        auto operator|=( Opt const &opt ) -> Parser & {\n            m_options.push_back(opt);\n            return *this;\n        }\n\n        auto operator|=( Parser const &other ) -> Parser & {\n            m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end());\n            m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end());\n            return *this;\n        }\n\n        template<typename T>\n        auto operator|( T const &other ) const -> Parser {\n            return Parser( *this ) |= other;\n        }\n\n        // Forward deprecated interface with '+' instead of '|'\n        template<typename T>\n        auto operator+=( T const &other ) -> Parser & { return operator|=( other ); }\n        template<typename T>\n        auto operator+( T const &other ) const -> Parser { return operator|( other ); }\n\n        auto getHelpColumns() const -> std::vector<HelpColumns> {\n            std::vector<HelpColumns> cols;\n            for (auto const &o : m_options) {\n                auto childCols = o.getHelpColumns();\n                cols.insert( cols.end(), childCols.begin(), childCols.end() );\n            }\n            return cols;\n        }\n\n        void writeToStream( std::ostream &os ) const {\n            if (!m_exeName.name().empty()) {\n                os << \"usage:\\n\" << \"  \" << m_exeName.name() << \" \";\n                bool required = true, first = true;\n                for( auto const &arg : m_args ) {\n                    if (first)\n                        first = false;\n                    else\n                        os << \" \";\n                    if( arg.isOptional() && required ) {\n                        os << \"[\";\n                        required = false;\n                    }\n                    os << \"<\" << arg.hint() << \">\";\n                    if( arg.cardinality() == 0 )\n                        os << \" ... \";\n                }\n                if( !required )\n                    os << \"]\";\n                if( !m_options.empty() )\n                    os << \" options\";\n                os << \"\\n\\nwhere options are:\" << std::endl;\n            }\n\n            auto rows = getHelpColumns();\n            size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH;\n            size_t optWidth = 0;\n            for( auto const &cols : rows )\n                optWidth = (std::max)(optWidth, cols.left.size() + 2);\n\n            optWidth = (std::min)(optWidth, consoleWidth/2);\n\n            for( auto const &cols : rows ) {\n                auto row =\n                        TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) +\n                        TextFlow::Spacer(4) +\n                        TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth );\n                os << row << std::endl;\n            }\n        }\n\n        friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& {\n            parser.writeToStream( os );\n            return os;\n        }\n\n        auto validate() const -> Result override {\n            for( auto const &opt : m_options ) {\n                auto result = opt.validate();\n                if( !result )\n                    return result;\n            }\n            for( auto const &arg : m_args ) {\n                auto result = arg.validate();\n                if( !result )\n                    return result;\n            }\n            return Result::ok();\n        }\n\n        using ParserBase::parse;\n\n        auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override {\n\n            struct ParserInfo {\n                ParserBase const* parser = nullptr;\n                size_t count = 0;\n            };\n            const size_t totalParsers = m_options.size() + m_args.size();\n            assert( totalParsers < 512 );\n            // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do\n            ParserInfo parseInfos[512];\n\n            {\n                size_t i = 0;\n                for (auto const &opt : m_options) parseInfos[i++].parser = &opt;\n                for (auto const &arg : m_args) parseInfos[i++].parser = &arg;\n            }\n\n            m_exeName.set( exeName );\n\n            auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );\n            while( result.value().remainingTokens() ) {\n                bool tokenParsed = false;\n\n                for( size_t i = 0; i < totalParsers; ++i ) {\n                    auto&  parseInfo = parseInfos[i];\n                    if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) {\n                        result = parseInfo.parser->parse(exeName, result.value().remainingTokens());\n                        if (!result)\n                            return result;\n                        if (result.value().type() != ParseResultType::NoMatch) {\n                            tokenParsed = true;\n                            ++parseInfo.count;\n                            break;\n                        }\n                    }\n                }\n\n                if( result.value().type() == ParseResultType::ShortCircuitAll )\n                    return result;\n                if( !tokenParsed )\n                    return InternalParseResult::runtimeError( \"Unrecognised token: \" + result.value().remainingTokens()->token );\n            }\n            // !TBD Check missing required options\n            return result;\n        }\n    };\n\n    template<typename DerivedT>\n    template<typename T>\n    auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser {\n        return Parser() | static_cast<DerivedT const &>( *this ) | other;\n    }\n} // namespace detail\n\n// A Combined parser\nusing detail::Parser;\n\n// A parser for options\nusing detail::Opt;\n\n// A parser for arguments\nusing detail::Arg;\n\n// Wrapper for argc, argv from main()\nusing detail::Args;\n\n// Specifies the name of the executable\nusing detail::ExeName;\n\n// Convenience wrapper for option parser that specifies the help option\nusing detail::Help;\n\n// enum of result types from a parse\nusing detail::ParseResultType;\n\n// Result type for parser operation\nusing detail::ParserResult;\n\n}} // namespace Catch::clara\n\n// end clara.hpp\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// Restore Clara's value for console width, if present\n#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH\n#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH\n#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH\n#endif\n\n// end catch_clara.h\nnamespace Catch {\n\n    clara::Parser makeCommandLineParser( ConfigData& config );\n\n} // end namespace Catch\n\n// end catch_commandline.h\n#include <fstream>\n#include <ctime>\n\nnamespace Catch {\n\n    clara::Parser makeCommandLineParser( ConfigData& config ) {\n\n        using namespace clara;\n\n        auto const setWarning = [&]( std::string const& warning ) {\n                auto warningSet = [&]() {\n                    if( warning == \"NoAssertions\" )\n                        return WarnAbout::NoAssertions;\n\n                    if ( warning == \"NoTests\" )\n                        return WarnAbout::NoTests;\n\n                    return WarnAbout::Nothing;\n                }();\n\n                if (warningSet == WarnAbout::Nothing)\n                    return ParserResult::runtimeError( \"Unrecognised warning: '\" + warning + \"'\" );\n                config.warnings = static_cast<WarnAbout::What>( config.warnings | warningSet );\n                return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const loadTestNamesFromFile = [&]( std::string const& filename ) {\n                std::ifstream f( filename.c_str() );\n                if( !f.is_open() )\n                    return ParserResult::runtimeError( \"Unable to load input file: '\" + filename + \"'\" );\n\n                std::string line;\n                while( std::getline( f, line ) ) {\n                    line = trim(line);\n                    if( !line.empty() && !startsWith( line, '#' ) ) {\n                        if( !startsWith( line, '\"' ) )\n                            line = '\"' + line + '\"';\n                        config.testsOrTags.push_back( line );\n                        config.testsOrTags.emplace_back( \",\" );\n                    }\n                }\n                //Remove comma in the end\n                if(!config.testsOrTags.empty())\n                    config.testsOrTags.erase( config.testsOrTags.end()-1 );\n\n                return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const setTestOrder = [&]( std::string const& order ) {\n                if( startsWith( \"declared\", order ) )\n                    config.runOrder = RunTests::InDeclarationOrder;\n                else if( startsWith( \"lexical\", order ) )\n                    config.runOrder = RunTests::InLexicographicalOrder;\n                else if( startsWith( \"random\", order ) )\n                    config.runOrder = RunTests::InRandomOrder;\n                else\n                    return clara::ParserResult::runtimeError( \"Unrecognised ordering: '\" + order + \"'\" );\n                return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const setRngSeed = [&]( std::string const& seed ) {\n                if( seed != \"time\" )\n                    return clara::detail::convertInto( seed, config.rngSeed );\n                config.rngSeed = static_cast<unsigned int>( std::time(nullptr) );\n                return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const setColourUsage = [&]( std::string const& useColour ) {\n                    auto mode = toLower( useColour );\n\n                    if( mode == \"yes\" )\n                        config.useColour = UseColour::Yes;\n                    else if( mode == \"no\" )\n                        config.useColour = UseColour::No;\n                    else if( mode == \"auto\" )\n                        config.useColour = UseColour::Auto;\n                    else\n                        return ParserResult::runtimeError( \"colour mode must be one of: auto, yes or no. '\" + useColour + \"' not recognised\" );\n                return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const setWaitForKeypress = [&]( std::string const& keypress ) {\n                auto keypressLc = toLower( keypress );\n                if (keypressLc == \"never\")\n                    config.waitForKeypress = WaitForKeypress::Never;\n                else if( keypressLc == \"start\" )\n                    config.waitForKeypress = WaitForKeypress::BeforeStart;\n                else if( keypressLc == \"exit\" )\n                    config.waitForKeypress = WaitForKeypress::BeforeExit;\n                else if( keypressLc == \"both\" )\n                    config.waitForKeypress = WaitForKeypress::BeforeStartAndExit;\n                else\n                    return ParserResult::runtimeError( \"keypress argument must be one of: never, start, exit or both. '\" + keypress + \"' not recognised\" );\n            return ParserResult::ok( ParseResultType::Matched );\n            };\n        auto const setVerbosity = [&]( std::string const& verbosity ) {\n            auto lcVerbosity = toLower( verbosity );\n            if( lcVerbosity == \"quiet\" )\n                config.verbosity = Verbosity::Quiet;\n            else if( lcVerbosity == \"normal\" )\n                config.verbosity = Verbosity::Normal;\n            else if( lcVerbosity == \"high\" )\n                config.verbosity = Verbosity::High;\n            else\n                return ParserResult::runtimeError( \"Unrecognised verbosity, '\" + verbosity + \"'\" );\n            return ParserResult::ok( ParseResultType::Matched );\n        };\n        auto const setReporter = [&]( std::string const& reporter ) {\n            IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();\n\n            auto lcReporter = toLower( reporter );\n            auto result = factories.find( lcReporter );\n\n            if( factories.end() != result )\n                config.reporterName = lcReporter;\n            else\n                return ParserResult::runtimeError( \"Unrecognized reporter, '\" + reporter + \"'. Check available with --list-reporters\" );\n            return ParserResult::ok( ParseResultType::Matched );\n        };\n\n        auto cli\n            = ExeName( config.processName )\n            | Help( config.showHelp )\n            | Opt( config.listTests )\n                [\"-l\"][\"--list-tests\"]\n                ( \"list all/matching test cases\" )\n            | Opt( config.listTags )\n                [\"-t\"][\"--list-tags\"]\n                ( \"list all/matching tags\" )\n            | Opt( config.showSuccessfulTests )\n                [\"-s\"][\"--success\"]\n                ( \"include successful tests in output\" )\n            | Opt( config.shouldDebugBreak )\n                [\"-b\"][\"--break\"]\n                ( \"break into debugger on failure\" )\n            | Opt( config.noThrow )\n                [\"-e\"][\"--nothrow\"]\n                ( \"skip exception tests\" )\n            | Opt( config.showInvisibles )\n                [\"-i\"][\"--invisibles\"]\n                ( \"show invisibles (tabs, newlines)\" )\n            | Opt( config.outputFilename, \"filename\" )\n                [\"-o\"][\"--out\"]\n                ( \"output filename\" )\n            | Opt( setReporter, \"name\" )\n                [\"-r\"][\"--reporter\"]\n                ( \"reporter to use (defaults to console)\" )\n            | Opt( config.name, \"name\" )\n                [\"-n\"][\"--name\"]\n                ( \"suite name\" )\n            | Opt( [&]( bool ){ config.abortAfter = 1; } )\n                [\"-a\"][\"--abort\"]\n                ( \"abort at first failure\" )\n            | Opt( [&]( int x ){ config.abortAfter = x; }, \"no. failures\" )\n                [\"-x\"][\"--abortx\"]\n                ( \"abort after x failures\" )\n            | Opt( setWarning, \"warning name\" )\n                [\"-w\"][\"--warn\"]\n                ( \"enable warnings\" )\n            | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, \"yes|no\" )\n                [\"-d\"][\"--durations\"]\n                ( \"show test durations\" )\n            | Opt( config.minDuration, \"seconds\" )\n                [\"-D\"][\"--min-duration\"]\n                ( \"show test durations for tests taking at least the given number of seconds\" )\n            | Opt( loadTestNamesFromFile, \"filename\" )\n                [\"-f\"][\"--input-file\"]\n                ( \"load test names to run from a file\" )\n            | Opt( config.filenamesAsTags )\n                [\"-#\"][\"--filenames-as-tags\"]\n                ( \"adds a tag for the filename\" )\n            | Opt( config.sectionsToRun, \"section name\" )\n                [\"-c\"][\"--section\"]\n                ( \"specify section to run\" )\n            | Opt( setVerbosity, \"quiet|normal|high\" )\n                [\"-v\"][\"--verbosity\"]\n                ( \"set output verbosity\" )\n            | Opt( config.listTestNamesOnly )\n                [\"--list-test-names-only\"]\n                ( \"list all/matching test cases names only\" )\n            | Opt( config.listReporters )\n                [\"--list-reporters\"]\n                ( \"list all reporters\" )\n            | Opt( setTestOrder, \"decl|lex|rand\" )\n                [\"--order\"]\n                ( \"test case order (defaults to decl)\" )\n            | Opt( setRngSeed, \"'time'|number\" )\n                [\"--rng-seed\"]\n                ( \"set a specific seed for random numbers\" )\n            | Opt( setColourUsage, \"yes|no\" )\n                [\"--use-colour\"]\n                ( \"should output be colourised\" )\n            | Opt( config.libIdentify )\n                [\"--libidentify\"]\n                ( \"report name and version according to libidentify standard\" )\n            | Opt( setWaitForKeypress, \"never|start|exit|both\" )\n                [\"--wait-for-keypress\"]\n                ( \"waits for a keypress before exiting\" )\n            | Opt( config.benchmarkSamples, \"samples\" )\n                [\"--benchmark-samples\"]\n                ( \"number of samples to collect (default: 100)\" )\n            | Opt( config.benchmarkResamples, \"resamples\" )\n                [\"--benchmark-resamples\"]\n                ( \"number of resamples for the bootstrap (default: 100000)\" )\n            | Opt( config.benchmarkConfidenceInterval, \"confidence interval\" )\n                [\"--benchmark-confidence-interval\"]\n                ( \"confidence interval for the bootstrap (between 0 and 1, default: 0.95)\" )\n            | Opt( config.benchmarkNoAnalysis )\n                [\"--benchmark-no-analysis\"]\n                ( \"perform only measurements; do not perform any analysis\" )\n            | Opt( config.benchmarkWarmupTime, \"benchmarkWarmupTime\" )\n                [\"--benchmark-warmup-time\"]\n                ( \"amount of time in milliseconds spent on warming up each test (default: 100)\" )\n            | Arg( config.testsOrTags, \"test name|pattern|tags\" )\n                ( \"which test or tests to use\" );\n\n        return cli;\n    }\n\n} // end namespace Catch\n// end catch_commandline.cpp\n// start catch_common.cpp\n\n#include <cstring>\n#include <ostream>\n\nnamespace Catch {\n\n    bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept {\n        return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0);\n    }\n    bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept {\n        // We can assume that the same file will usually have the same pointer.\n        // Thus, if the pointers are the same, there is no point in calling the strcmp\n        return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0));\n    }\n\n    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {\n#ifndef __GNUG__\n        os << info.file << '(' << info.line << ')';\n#else\n        os << info.file << ':' << info.line;\n#endif\n        return os;\n    }\n\n    std::string StreamEndStop::operator+() const {\n        return std::string();\n    }\n\n    NonCopyable::NonCopyable() = default;\n    NonCopyable::~NonCopyable() = default;\n\n}\n// end catch_common.cpp\n// start catch_config.cpp\n\nnamespace Catch {\n\n    Config::Config( ConfigData const& data )\n    :   m_data( data ),\n        m_stream( openStream() )\n    {\n        // We need to trim filter specs to avoid trouble with superfluous\n        // whitespace (esp. important for bdd macros, as those are manually\n        // aligned with whitespace).\n\n        for (auto& elem : m_data.testsOrTags) {\n            elem = trim(elem);\n        }\n        for (auto& elem : m_data.sectionsToRun) {\n            elem = trim(elem);\n        }\n\n        TestSpecParser parser(ITagAliasRegistry::get());\n        if (!m_data.testsOrTags.empty()) {\n            m_hasTestFilters = true;\n            for (auto const& testOrTags : m_data.testsOrTags) {\n                parser.parse(testOrTags);\n            }\n        }\n        m_testSpec = parser.testSpec();\n    }\n\n    std::string const& Config::getFilename() const {\n        return m_data.outputFilename ;\n    }\n\n    bool Config::listTests() const          { return m_data.listTests; }\n    bool Config::listTestNamesOnly() const  { return m_data.listTestNamesOnly; }\n    bool Config::listTags() const           { return m_data.listTags; }\n    bool Config::listReporters() const      { return m_data.listReporters; }\n\n    std::string Config::getProcessName() const { return m_data.processName; }\n    std::string const& Config::getReporterName() const { return m_data.reporterName; }\n\n    std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }\n    std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }\n\n    TestSpec const& Config::testSpec() const { return m_testSpec; }\n    bool Config::hasTestFilters() const { return m_hasTestFilters; }\n\n    bool Config::showHelp() const { return m_data.showHelp; }\n\n    // IConfig interface\n    bool Config::allowThrows() const                   { return !m_data.noThrow; }\n    std::ostream& Config::stream() const               { return m_stream->stream(); }\n    std::string Config::name() const                   { return m_data.name.empty() ? m_data.processName : m_data.name; }\n    bool Config::includeSuccessfulResults() const      { return m_data.showSuccessfulTests; }\n    bool Config::warnAboutMissingAssertions() const    { return !!(m_data.warnings & WarnAbout::NoAssertions); }\n    bool Config::warnAboutNoTests() const              { return !!(m_data.warnings & WarnAbout::NoTests); }\n    ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; }\n    double Config::minDuration() const                 { return m_data.minDuration; }\n    RunTests::InWhatOrder Config::runOrder() const     { return m_data.runOrder; }\n    unsigned int Config::rngSeed() const               { return m_data.rngSeed; }\n    UseColour::YesOrNo Config::useColour() const       { return m_data.useColour; }\n    bool Config::shouldDebugBreak() const              { return m_data.shouldDebugBreak; }\n    int Config::abortAfter() const                     { return m_data.abortAfter; }\n    bool Config::showInvisibles() const                { return m_data.showInvisibles; }\n    Verbosity Config::verbosity() const                { return m_data.verbosity; }\n\n    bool Config::benchmarkNoAnalysis() const                      { return m_data.benchmarkNoAnalysis; }\n    int Config::benchmarkSamples() const                          { return m_data.benchmarkSamples; }\n    double Config::benchmarkConfidenceInterval() const            { return m_data.benchmarkConfidenceInterval; }\n    unsigned int Config::benchmarkResamples() const               { return m_data.benchmarkResamples; }\n    std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); }\n\n    IStream const* Config::openStream() {\n        return Catch::makeStream(m_data.outputFilename);\n    }\n\n} // end namespace Catch\n// end catch_config.cpp\n// start catch_console_colour.cpp\n\n#if defined(__clang__)\n#    pragma clang diagnostic push\n#    pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#endif\n\n// start catch_errno_guard.h\n\nnamespace Catch {\n\n    class ErrnoGuard {\n    public:\n        ErrnoGuard();\n        ~ErrnoGuard();\n    private:\n        int m_oldErrno;\n    };\n\n}\n\n// end catch_errno_guard.h\n// start catch_windows_h_proxy.h\n\n\n#if defined(CATCH_PLATFORM_WINDOWS)\n\n#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX)\n#  define CATCH_DEFINED_NOMINMAX\n#  define NOMINMAX\n#endif\n#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN)\n#  define CATCH_DEFINED_WIN32_LEAN_AND_MEAN\n#  define WIN32_LEAN_AND_MEAN\n#endif\n\n#ifdef __AFXDLL\n#include <AfxWin.h>\n#else\n#include <windows.h>\n#endif\n\n#ifdef CATCH_DEFINED_NOMINMAX\n#  undef NOMINMAX\n#endif\n#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN\n#  undef WIN32_LEAN_AND_MEAN\n#endif\n\n#endif // defined(CATCH_PLATFORM_WINDOWS)\n\n// end catch_windows_h_proxy.h\n#include <sstream>\n\nnamespace Catch {\n    namespace {\n\n        struct IColourImpl {\n            virtual ~IColourImpl() = default;\n            virtual void use( Colour::Code _colourCode ) = 0;\n        };\n\n        struct NoColourImpl : IColourImpl {\n            void use( Colour::Code ) override {}\n\n            static IColourImpl* instance() {\n                static NoColourImpl s_instance;\n                return &s_instance;\n            }\n        };\n\n    } // anon namespace\n} // namespace Catch\n\n#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )\n#   ifdef CATCH_PLATFORM_WINDOWS\n#       define CATCH_CONFIG_COLOUR_WINDOWS\n#   else\n#       define CATCH_CONFIG_COLOUR_ANSI\n#   endif\n#endif\n\n#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////\n\nnamespace Catch {\nnamespace {\n\n    class Win32ColourImpl : public IColourImpl {\n    public:\n        Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )\n        {\n            CONSOLE_SCREEN_BUFFER_INFO csbiInfo;\n            GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo );\n            originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );\n            originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );\n        }\n\n        void use( Colour::Code _colourCode ) override {\n            switch( _colourCode ) {\n                case Colour::None:      return setTextAttribute( originalForegroundAttributes );\n                case Colour::White:     return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );\n                case Colour::Red:       return setTextAttribute( FOREGROUND_RED );\n                case Colour::Green:     return setTextAttribute( FOREGROUND_GREEN );\n                case Colour::Blue:      return setTextAttribute( FOREGROUND_BLUE );\n                case Colour::Cyan:      return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );\n                case Colour::Yellow:    return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );\n                case Colour::Grey:      return setTextAttribute( 0 );\n\n                case Colour::LightGrey:     return setTextAttribute( FOREGROUND_INTENSITY );\n                case Colour::BrightRed:     return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );\n                case Colour::BrightGreen:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );\n                case Colour::BrightWhite:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );\n                case Colour::BrightYellow:  return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN );\n\n                case Colour::Bright: CATCH_INTERNAL_ERROR( \"not a colour\" );\n\n                default:\n                    CATCH_ERROR( \"Unknown colour requested\" );\n            }\n        }\n\n    private:\n        void setTextAttribute( WORD _textAttribute ) {\n            SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes );\n        }\n        HANDLE stdoutHandle;\n        WORD originalForegroundAttributes;\n        WORD originalBackgroundAttributes;\n    };\n\n    IColourImpl* platformColourInstance() {\n        static Win32ColourImpl s_instance;\n\n        IConfigPtr config = getCurrentContext().getConfig();\n        UseColour::YesOrNo colourMode = config\n            ? config->useColour()\n            : UseColour::Auto;\n        if( colourMode == UseColour::Auto )\n            colourMode = UseColour::Yes;\n        return colourMode == UseColour::Yes\n            ? &s_instance\n            : NoColourImpl::instance();\n    }\n\n} // end anon namespace\n} // end namespace Catch\n\n#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////\n\n#include <unistd.h>\n\nnamespace Catch {\nnamespace {\n\n    // use POSIX/ ANSI console terminal codes\n    // Thanks to Adam Strzelecki for original contribution\n    // (http://github.com/nanoant)\n    // https://github.com/philsquared/Catch/pull/131\n    class PosixColourImpl : public IColourImpl {\n    public:\n        void use( Colour::Code _colourCode ) override {\n            switch( _colourCode ) {\n                case Colour::None:\n                case Colour::White:     return setColour( \"[0m\" );\n                case Colour::Red:       return setColour( \"[0;31m\" );\n                case Colour::Green:     return setColour( \"[0;32m\" );\n                case Colour::Blue:      return setColour( \"[0;34m\" );\n                case Colour::Cyan:      return setColour( \"[0;36m\" );\n                case Colour::Yellow:    return setColour( \"[0;33m\" );\n                case Colour::Grey:      return setColour( \"[1;30m\" );\n\n                case Colour::LightGrey:     return setColour( \"[0;37m\" );\n                case Colour::BrightRed:     return setColour( \"[1;31m\" );\n                case Colour::BrightGreen:   return setColour( \"[1;32m\" );\n                case Colour::BrightWhite:   return setColour( \"[1;37m\" );\n                case Colour::BrightYellow:  return setColour( \"[1;33m\" );\n\n                case Colour::Bright: CATCH_INTERNAL_ERROR( \"not a colour\" );\n                default: CATCH_INTERNAL_ERROR( \"Unknown colour requested\" );\n            }\n        }\n        static IColourImpl* instance() {\n            static PosixColourImpl s_instance;\n            return &s_instance;\n        }\n\n    private:\n        void setColour( const char* _escapeCode ) {\n            getCurrentContext().getConfig()->stream()\n                << '\\033' << _escapeCode;\n        }\n    };\n\n    bool useColourOnPlatform() {\n        return\n#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE)\n            !isDebuggerActive() &&\n#endif\n#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__))\n            isatty(STDOUT_FILENO)\n#else\n            false\n#endif\n            ;\n    }\n    IColourImpl* platformColourInstance() {\n        ErrnoGuard guard;\n        IConfigPtr config = getCurrentContext().getConfig();\n        UseColour::YesOrNo colourMode = config\n            ? config->useColour()\n            : UseColour::Auto;\n        if( colourMode == UseColour::Auto )\n            colourMode = useColourOnPlatform()\n                ? UseColour::Yes\n                : UseColour::No;\n        return colourMode == UseColour::Yes\n            ? PosixColourImpl::instance()\n            : NoColourImpl::instance();\n    }\n\n} // end anon namespace\n} // end namespace Catch\n\n#else  // not Windows or ANSI ///////////////////////////////////////////////\n\nnamespace Catch {\n\n    static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }\n\n} // end namespace Catch\n\n#endif // Windows/ ANSI/ None\n\nnamespace Catch {\n\n    Colour::Colour( Code _colourCode ) { use( _colourCode ); }\n    Colour::Colour( Colour&& other ) noexcept {\n        m_moved = other.m_moved;\n        other.m_moved = true;\n    }\n    Colour& Colour::operator=( Colour&& other ) noexcept {\n        m_moved = other.m_moved;\n        other.m_moved  = true;\n        return *this;\n    }\n\n    Colour::~Colour(){ if( !m_moved ) use( None ); }\n\n    void Colour::use( Code _colourCode ) {\n        static IColourImpl* impl = platformColourInstance();\n        // Strictly speaking, this cannot possibly happen.\n        // However, under some conditions it does happen (see #1626),\n        // and this change is small enough that we can let practicality\n        // triumph over purity in this case.\n        if (impl != nullptr) {\n            impl->use( _colourCode );\n        }\n    }\n\n    std::ostream& operator << ( std::ostream& os, Colour const& ) {\n        return os;\n    }\n\n} // end namespace Catch\n\n#if defined(__clang__)\n#    pragma clang diagnostic pop\n#endif\n\n// end catch_console_colour.cpp\n// start catch_context.cpp\n\nnamespace Catch {\n\n    class Context : public IMutableContext, NonCopyable {\n\n    public: // IContext\n        IResultCapture* getResultCapture() override {\n            return m_resultCapture;\n        }\n        IRunner* getRunner() override {\n            return m_runner;\n        }\n\n        IConfigPtr const& getConfig() const override {\n            return m_config;\n        }\n\n        ~Context() override;\n\n    public: // IMutableContext\n        void setResultCapture( IResultCapture* resultCapture ) override {\n            m_resultCapture = resultCapture;\n        }\n        void setRunner( IRunner* runner ) override {\n            m_runner = runner;\n        }\n        void setConfig( IConfigPtr const& config ) override {\n            m_config = config;\n        }\n\n        friend IMutableContext& getCurrentMutableContext();\n\n    private:\n        IConfigPtr m_config;\n        IRunner* m_runner = nullptr;\n        IResultCapture* m_resultCapture = nullptr;\n    };\n\n    IMutableContext *IMutableContext::currentContext = nullptr;\n\n    void IMutableContext::createContext()\n    {\n        currentContext = new Context();\n    }\n\n    void cleanUpContext() {\n        delete IMutableContext::currentContext;\n        IMutableContext::currentContext = nullptr;\n    }\n    IContext::~IContext() = default;\n    IMutableContext::~IMutableContext() = default;\n    Context::~Context() = default;\n\n    SimplePcg32& rng() {\n        static SimplePcg32 s_rng;\n        return s_rng;\n    }\n\n}\n// end catch_context.cpp\n// start catch_debug_console.cpp\n\n// start catch_debug_console.h\n\n#include <string>\n\nnamespace Catch {\n    void writeToDebugConsole( std::string const& text );\n}\n\n// end catch_debug_console.h\n#if defined(CATCH_CONFIG_ANDROID_LOGWRITE)\n#include <android/log.h>\n\n    namespace Catch {\n        void writeToDebugConsole( std::string const& text ) {\n            __android_log_write( ANDROID_LOG_DEBUG, \"Catch\", text.c_str() );\n        }\n    }\n\n#elif defined(CATCH_PLATFORM_WINDOWS)\n\n    namespace Catch {\n        void writeToDebugConsole( std::string const& text ) {\n            ::OutputDebugStringA( text.c_str() );\n        }\n    }\n\n#else\n\n    namespace Catch {\n        void writeToDebugConsole( std::string const& text ) {\n            // !TBD: Need a version for Mac/ XCode and other IDEs\n            Catch::cout() << text;\n        }\n    }\n\n#endif // Platform\n// end catch_debug_console.cpp\n// start catch_debugger.cpp\n\n#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE)\n\n#  include <cassert>\n#  include <sys/types.h>\n#  include <unistd.h>\n#  include <cstddef>\n#  include <ostream>\n\n#ifdef __apple_build_version__\n    // These headers will only compile with AppleClang (XCode)\n    // For other compilers (Clang, GCC, ... ) we need to exclude them\n#  include <sys/sysctl.h>\n#endif\n\n    namespace Catch {\n        #ifdef __apple_build_version__\n        // The following function is taken directly from the following technical note:\n        // https://developer.apple.com/library/archive/qa/qa1361/_index.html\n\n        // Returns true if the current process is being debugged (either\n        // running under the debugger or has a debugger attached post facto).\n        bool isDebuggerActive(){\n            int                 mib[4];\n            struct kinfo_proc   info;\n            std::size_t         size;\n\n            // Initialize the flags so that, if sysctl fails for some bizarre\n            // reason, we get a predictable result.\n\n            info.kp_proc.p_flag = 0;\n\n            // Initialize mib, which tells sysctl the info we want, in this case\n            // we're looking for information about a specific process ID.\n\n            mib[0] = CTL_KERN;\n            mib[1] = KERN_PROC;\n            mib[2] = KERN_PROC_PID;\n            mib[3] = getpid();\n\n            // Call sysctl.\n\n            size = sizeof(info);\n            if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) {\n                Catch::cerr() << \"\\n** Call to sysctl failed - unable to determine if debugger is active **\\n\" << std::endl;\n                return false;\n            }\n\n            // We're being debugged if the P_TRACED flag is set.\n\n            return ( (info.kp_proc.p_flag & P_TRACED) != 0 );\n        }\n        #else\n        bool isDebuggerActive() {\n            // We need to find another way to determine this for non-appleclang compilers on macOS\n            return false;\n        }\n        #endif\n    } // namespace Catch\n\n#elif defined(CATCH_PLATFORM_LINUX)\n    #include <fstream>\n    #include <string>\n\n    namespace Catch{\n        // The standard POSIX way of detecting a debugger is to attempt to\n        // ptrace() the process, but this needs to be done from a child and not\n        // this process itself to still allow attaching to this process later\n        // if wanted, so is rather heavy. Under Linux we have the PID of the\n        // \"debugger\" (which doesn't need to be gdb, of course, it could also\n        // be strace, for example) in /proc/$PID/status, so just get it from\n        // there instead.\n        bool isDebuggerActive(){\n            // Libstdc++ has a bug, where std::ifstream sets errno to 0\n            // This way our users can properly assert over errno values\n            ErrnoGuard guard;\n            std::ifstream in(\"/proc/self/status\");\n            for( std::string line; std::getline(in, line); ) {\n                static const int PREFIX_LEN = 11;\n                if( line.compare(0, PREFIX_LEN, \"TracerPid:\\t\") == 0 ) {\n                    // We're traced if the PID is not 0 and no other PID starts\n                    // with 0 digit, so it's enough to check for just a single\n                    // character.\n                    return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';\n                }\n            }\n\n            return false;\n        }\n    } // namespace Catch\n#elif defined(_MSC_VER)\n    extern \"C\" __declspec(dllimport) int __stdcall IsDebuggerPresent();\n    namespace Catch {\n        bool isDebuggerActive() {\n            return IsDebuggerPresent() != 0;\n        }\n    }\n#elif defined(__MINGW32__)\n    extern \"C\" __declspec(dllimport) int __stdcall IsDebuggerPresent();\n    namespace Catch {\n        bool isDebuggerActive() {\n            return IsDebuggerPresent() != 0;\n        }\n    }\n#else\n    namespace Catch {\n       bool isDebuggerActive() { return false; }\n    }\n#endif // Platform\n// end catch_debugger.cpp\n// start catch_decomposer.cpp\n\nnamespace Catch {\n\n    ITransientExpression::~ITransientExpression() = default;\n\n    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) {\n        if( lhs.size() + rhs.size() < 40 &&\n                lhs.find('\\n') == std::string::npos &&\n                rhs.find('\\n') == std::string::npos )\n            os << lhs << \" \" << op << \" \" << rhs;\n        else\n            os << lhs << \"\\n\" << op << \"\\n\" << rhs;\n    }\n}\n// end catch_decomposer.cpp\n// start catch_enforce.cpp\n\n#include <stdexcept>\n\nnamespace Catch {\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER)\n    [[noreturn]]\n    void throw_exception(std::exception const& e) {\n        Catch::cerr() << \"Catch will terminate because it needed to throw an exception.\\n\"\n                      << \"The message was: \" << e.what() << '\\n';\n        std::terminate();\n    }\n#endif\n\n    [[noreturn]]\n    void throw_logic_error(std::string const& msg) {\n        throw_exception(std::logic_error(msg));\n    }\n\n    [[noreturn]]\n    void throw_domain_error(std::string const& msg) {\n        throw_exception(std::domain_error(msg));\n    }\n\n    [[noreturn]]\n    void throw_runtime_error(std::string const& msg) {\n        throw_exception(std::runtime_error(msg));\n    }\n\n} // namespace Catch;\n// end catch_enforce.cpp\n// start catch_enum_values_registry.cpp\n// start catch_enum_values_registry.h\n\n#include <vector>\n#include <memory>\n\nnamespace Catch {\n\n    namespace Detail {\n\n        std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values );\n\n        class EnumValuesRegistry : public IMutableEnumValuesRegistry {\n\n            std::vector<std::unique_ptr<EnumInfo>> m_enumInfos;\n\n            EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values) override;\n        };\n\n        std::vector<StringRef> parseEnums( StringRef enums );\n\n    } // Detail\n\n} // Catch\n\n// end catch_enum_values_registry.h\n\n#include <map>\n#include <cassert>\n\nnamespace Catch {\n\n    IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {}\n\n    namespace Detail {\n\n        namespace {\n            // Extracts the actual name part of an enum instance\n            // In other words, it returns the Blue part of Bikeshed::Colour::Blue\n            StringRef extractInstanceName(StringRef enumInstance) {\n                // Find last occurrence of \":\"\n                size_t name_start = enumInstance.size();\n                while (name_start > 0 && enumInstance[name_start - 1] != ':') {\n                    --name_start;\n                }\n                return enumInstance.substr(name_start, enumInstance.size() - name_start);\n            }\n        }\n\n        std::vector<StringRef> parseEnums( StringRef enums ) {\n            auto enumValues = splitStringRef( enums, ',' );\n            std::vector<StringRef> parsed;\n            parsed.reserve( enumValues.size() );\n            for( auto const& enumValue : enumValues ) {\n                parsed.push_back(trim(extractInstanceName(enumValue)));\n            }\n            return parsed;\n        }\n\n        EnumInfo::~EnumInfo() {}\n\n        StringRef EnumInfo::lookup( int value ) const {\n            for( auto const& valueToName : m_values ) {\n                if( valueToName.first == value )\n                    return valueToName.second;\n            }\n            return \"{** unexpected enum value **}\"_sr;\n        }\n\n        std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {\n            std::unique_ptr<EnumInfo> enumInfo( new EnumInfo );\n            enumInfo->m_name = enumName;\n            enumInfo->m_values.reserve( values.size() );\n\n            const auto valueNames = Catch::Detail::parseEnums( allValueNames );\n            assert( valueNames.size() == values.size() );\n            std::size_t i = 0;\n            for( auto value : values )\n                enumInfo->m_values.emplace_back(value, valueNames[i++]);\n\n            return enumInfo;\n        }\n\n        EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {\n            m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values));\n            return *m_enumInfos.back();\n        }\n\n    } // Detail\n} // Catch\n\n// end catch_enum_values_registry.cpp\n// start catch_errno_guard.cpp\n\n#include <cerrno>\n\nnamespace Catch {\n        ErrnoGuard::ErrnoGuard():m_oldErrno(errno){}\n        ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; }\n}\n// end catch_errno_guard.cpp\n// start catch_exception_translator_registry.cpp\n\n// start catch_exception_translator_registry.h\n\n#include <vector>\n#include <string>\n#include <memory>\n\nnamespace Catch {\n\n    class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {\n    public:\n        ~ExceptionTranslatorRegistry();\n        virtual void registerTranslator( const IExceptionTranslator* translator );\n        std::string translateActiveException() const override;\n        std::string tryTranslators() const;\n\n    private:\n        std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators;\n    };\n}\n\n// end catch_exception_translator_registry.h\n#ifdef __OBJC__\n#import \"Foundation/Foundation.h\"\n#endif\n\nnamespace Catch {\n\n    ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() {\n    }\n\n    void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) {\n        m_translators.push_back( std::unique_ptr<const IExceptionTranslator>( translator ) );\n    }\n\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    std::string ExceptionTranslatorRegistry::translateActiveException() const {\n        try {\n#ifdef __OBJC__\n            // In Objective-C try objective-c exceptions first\n            @try {\n                return tryTranslators();\n            }\n            @catch (NSException *exception) {\n                return Catch::Detail::stringify( [exception description] );\n            }\n#else\n            // Compiling a mixed mode project with MSVC means that CLR\n            // exceptions will be caught in (...) as well. However, these\n            // do not fill-in std::current_exception and thus lead to crash\n            // when attempting rethrow.\n            // /EHa switch also causes structured exceptions to be caught\n            // here, but they fill-in current_exception properly, so\n            // at worst the output should be a little weird, instead of\n            // causing a crash.\n            if (std::current_exception() == nullptr) {\n                return \"Non C++ exception. Possibly a CLR exception.\";\n            }\n            return tryTranslators();\n#endif\n        }\n        catch( TestFailureException& ) {\n            std::rethrow_exception(std::current_exception());\n        }\n        catch( std::exception& ex ) {\n            return ex.what();\n        }\n        catch( std::string& msg ) {\n            return msg;\n        }\n        catch( const char* msg ) {\n            return msg;\n        }\n        catch(...) {\n            return \"Unknown exception\";\n        }\n    }\n\n    std::string ExceptionTranslatorRegistry::tryTranslators() const {\n        if (m_translators.empty()) {\n            std::rethrow_exception(std::current_exception());\n        } else {\n            return m_translators[0]->translate(m_translators.begin() + 1, m_translators.end());\n        }\n    }\n\n#else // ^^ Exceptions are enabled // Exceptions are disabled vv\n    std::string ExceptionTranslatorRegistry::translateActiveException() const {\n        CATCH_INTERNAL_ERROR(\"Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n    }\n\n    std::string ExceptionTranslatorRegistry::tryTranslators() const {\n        CATCH_INTERNAL_ERROR(\"Attempted to use exception translators under CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n    }\n#endif\n\n}\n// end catch_exception_translator_registry.cpp\n// start catch_fatal_condition.cpp\n\n#include <algorithm>\n\n#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS )\n\nnamespace Catch {\n\n    // If neither SEH nor signal handling is required, the handler impls\n    // do not have to do anything, and can be empty.\n    void FatalConditionHandler::engage_platform() {}\n    void FatalConditionHandler::disengage_platform() {}\n    FatalConditionHandler::FatalConditionHandler() = default;\n    FatalConditionHandler::~FatalConditionHandler() = default;\n\n} // end namespace Catch\n\n#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS\n\n#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS )\n#error \"Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time\"\n#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS\n\n#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )\n\nnamespace {\n    //! Signals fatal error message to the run context\n    void reportFatal( char const * const message ) {\n        Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );\n    }\n\n    //! Minimal size Catch2 needs for its own fatal error handling.\n    //! Picked anecdotally, so it might not be sufficient on all\n    //! platforms, and for all configurations.\n    constexpr std::size_t minStackSizeForErrors = 32 * 1024;\n} // end unnamed namespace\n\n#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS\n\n#if defined( CATCH_CONFIG_WINDOWS_SEH )\n\nnamespace Catch {\n\n    struct SignalDefs { DWORD id; const char* name; };\n\n    // There is no 1-1 mapping between signals and windows exceptions.\n    // Windows can easily distinguish between SO and SigSegV,\n    // but SigInt, SigTerm, etc are handled differently.\n    static SignalDefs signalDefs[] = {\n        { static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION),  \"SIGILL - Illegal instruction signal\" },\n        { static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), \"SIGSEGV - Stack overflow\" },\n        { static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION), \"SIGSEGV - Segmentation violation signal\" },\n        { static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), \"Divide by zero error\" },\n    };\n\n    static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {\n        for (auto const& def : signalDefs) {\n            if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {\n                reportFatal(def.name);\n            }\n        }\n        // If its not an exception we care about, pass it along.\n        // This stops us from eating debugger breaks etc.\n        return EXCEPTION_CONTINUE_SEARCH;\n    }\n\n    // Since we do not support multiple instantiations, we put these\n    // into global variables and rely on cleaning them up in outlined\n    // constructors/destructors\n    static PVOID exceptionHandlerHandle = nullptr;\n\n    // For MSVC, we reserve part of the stack memory for handling\n    // memory overflow structured exception.\n    FatalConditionHandler::FatalConditionHandler() {\n        ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors);\n        if (!SetThreadStackGuarantee(&guaranteeSize)) {\n            // We do not want to fully error out, because needing\n            // the stack reserve should be rare enough anyway.\n            Catch::cerr()\n                << \"Failed to reserve piece of stack.\"\n                << \" Stack overflows will not be reported successfully.\";\n        }\n    }\n\n    // We do not attempt to unset the stack guarantee, because\n    // Windows does not support lowering the stack size guarantee.\n    FatalConditionHandler::~FatalConditionHandler() = default;\n\n    void FatalConditionHandler::engage_platform() {\n        // Register as first handler in current chain\n        exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);\n        if (!exceptionHandlerHandle) {\n            CATCH_RUNTIME_ERROR(\"Could not register vectored exception handler\");\n        }\n    }\n\n    void FatalConditionHandler::disengage_platform() {\n        if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) {\n            CATCH_RUNTIME_ERROR(\"Could not unregister vectored exception handler\");\n        }\n        exceptionHandlerHandle = nullptr;\n    }\n\n} // end namespace Catch\n\n#endif // CATCH_CONFIG_WINDOWS_SEH\n\n#if defined( CATCH_CONFIG_POSIX_SIGNALS )\n\n#include <signal.h>\n\nnamespace Catch {\n\n    struct SignalDefs {\n        int id;\n        const char* name;\n    };\n\n    static SignalDefs signalDefs[] = {\n        { SIGINT,  \"SIGINT - Terminal interrupt signal\" },\n        { SIGILL,  \"SIGILL - Illegal instruction signal\" },\n        { SIGFPE,  \"SIGFPE - Floating point error signal\" },\n        { SIGSEGV, \"SIGSEGV - Segmentation violation signal\" },\n        { SIGTERM, \"SIGTERM - Termination request signal\" },\n        { SIGABRT, \"SIGABRT - Abort (abnormal termination) signal\" }\n    };\n\n// Older GCCs trigger -Wmissing-field-initializers for T foo = {}\n// which is zero initialization, but not explicit. We want to avoid\n// that.\n#if defined(__GNUC__)\n#    pragma GCC diagnostic push\n#    pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n#endif\n\n    static char* altStackMem = nullptr;\n    static std::size_t altStackSize = 0;\n    static stack_t oldSigStack{};\n    static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{};\n\n    static void restorePreviousSignalHandlers() {\n        // We set signal handlers back to the previous ones. Hopefully\n        // nobody overwrote them in the meantime, and doesn't expect\n        // their signal handlers to live past ours given that they\n        // installed them after ours..\n        for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {\n            sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);\n        }\n        // Return the old stack\n        sigaltstack(&oldSigStack, nullptr);\n    }\n\n    static void handleSignal( int sig ) {\n        char const * name = \"<unknown signal>\";\n        for (auto const& def : signalDefs) {\n            if (sig == def.id) {\n                name = def.name;\n                break;\n            }\n        }\n        // We need to restore previous signal handlers and let them do\n        // their thing, so that the users can have the debugger break\n        // when a signal is raised, and so on.\n        restorePreviousSignalHandlers();\n        reportFatal( name );\n        raise( sig );\n    }\n\n    FatalConditionHandler::FatalConditionHandler() {\n        assert(!altStackMem && \"Cannot initialize POSIX signal handler when one already exists\");\n        if (altStackSize == 0) {\n            altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors);\n        }\n        altStackMem = new char[altStackSize]();\n    }\n\n    FatalConditionHandler::~FatalConditionHandler() {\n        delete[] altStackMem;\n        // We signal that another instance can be constructed by zeroing\n        // out the pointer.\n        altStackMem = nullptr;\n    }\n\n    void FatalConditionHandler::engage_platform() {\n        stack_t sigStack;\n        sigStack.ss_sp = altStackMem;\n        sigStack.ss_size = altStackSize;\n        sigStack.ss_flags = 0;\n        sigaltstack(&sigStack, &oldSigStack);\n        struct sigaction sa = { };\n\n        sa.sa_handler = handleSignal;\n        sa.sa_flags = SA_ONSTACK;\n        for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) {\n            sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);\n        }\n    }\n\n#if defined(__GNUC__)\n#    pragma GCC diagnostic pop\n#endif\n\n    void FatalConditionHandler::disengage_platform() {\n        restorePreviousSignalHandlers();\n    }\n\n} // end namespace Catch\n\n#endif // CATCH_CONFIG_POSIX_SIGNALS\n// end catch_fatal_condition.cpp\n// start catch_generators.cpp\n\n#include <limits>\n#include <set>\n\nnamespace Catch {\n\nIGeneratorTracker::~IGeneratorTracker() {}\n\nconst char* GeneratorException::what() const noexcept {\n    return m_msg;\n}\n\nnamespace Generators {\n\n    GeneratorUntypedBase::~GeneratorUntypedBase() {}\n\n    auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& {\n        return getResultCapture().acquireGeneratorTracker( generatorName, lineInfo );\n    }\n\n} // namespace Generators\n} // namespace Catch\n// end catch_generators.cpp\n// start catch_interfaces_capture.cpp\n\nnamespace Catch {\n    IResultCapture::~IResultCapture() = default;\n}\n// end catch_interfaces_capture.cpp\n// start catch_interfaces_config.cpp\n\nnamespace Catch {\n    IConfig::~IConfig() = default;\n}\n// end catch_interfaces_config.cpp\n// start catch_interfaces_exception.cpp\n\nnamespace Catch {\n    IExceptionTranslator::~IExceptionTranslator() = default;\n    IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default;\n}\n// end catch_interfaces_exception.cpp\n// start catch_interfaces_registry_hub.cpp\n\nnamespace Catch {\n    IRegistryHub::~IRegistryHub() = default;\n    IMutableRegistryHub::~IMutableRegistryHub() = default;\n}\n// end catch_interfaces_registry_hub.cpp\n// start catch_interfaces_reporter.cpp\n\n// start catch_reporter_listening.h\n\nnamespace Catch {\n\n    class ListeningReporter : public IStreamingReporter {\n        using Reporters = std::vector<IStreamingReporterPtr>;\n        Reporters m_listeners;\n        IStreamingReporterPtr m_reporter = nullptr;\n        ReporterPreferences m_preferences;\n\n    public:\n        ListeningReporter();\n\n        void addListener( IStreamingReporterPtr&& listener );\n        void addReporter( IStreamingReporterPtr&& reporter );\n\n    public: // IStreamingReporter\n\n        ReporterPreferences getPreferences() const override;\n\n        void noMatchingTestCases( std::string const& spec ) override;\n\n        void reportInvalidArguments(std::string const&arg) override;\n\n        static std::set<Verbosity> getSupportedVerbosities();\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n        void benchmarkPreparing(std::string const& name) override;\n        void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override;\n        void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override;\n        void benchmarkFailed(std::string const&) override;\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n        void testRunStarting( TestRunInfo const& testRunInfo ) override;\n        void testGroupStarting( GroupInfo const& groupInfo ) override;\n        void testCaseStarting( TestCaseInfo const& testInfo ) override;\n        void sectionStarting( SectionInfo const& sectionInfo ) override;\n        void assertionStarting( AssertionInfo const& assertionInfo ) override;\n\n        // The return value indicates if the messages buffer should be cleared:\n        bool assertionEnded( AssertionStats const& assertionStats ) override;\n        void sectionEnded( SectionStats const& sectionStats ) override;\n        void testCaseEnded( TestCaseStats const& testCaseStats ) override;\n        void testGroupEnded( TestGroupStats const& testGroupStats ) override;\n        void testRunEnded( TestRunStats const& testRunStats ) override;\n\n        void skipTest( TestCaseInfo const& testInfo ) override;\n        bool isMulti() const override;\n\n    };\n\n} // end namespace Catch\n\n// end catch_reporter_listening.h\nnamespace Catch {\n\n    ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig )\n    :   m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {}\n\n    ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream )\n    :   m_stream( &_stream ), m_fullConfig( _fullConfig ) {}\n\n    std::ostream& ReporterConfig::stream() const { return *m_stream; }\n    IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; }\n\n    TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {}\n\n    GroupInfo::GroupInfo(  std::string const& _name,\n                           std::size_t _groupIndex,\n                           std::size_t _groupsCount )\n    :   name( _name ),\n        groupIndex( _groupIndex ),\n        groupsCounts( _groupsCount )\n    {}\n\n     AssertionStats::AssertionStats( AssertionResult const& _assertionResult,\n                                     std::vector<MessageInfo> const& _infoMessages,\n                                     Totals const& _totals )\n    :   assertionResult( _assertionResult ),\n        infoMessages( _infoMessages ),\n        totals( _totals )\n    {\n        assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression;\n\n        if( assertionResult.hasMessage() ) {\n            // Copy message into messages list.\n            // !TBD This should have been done earlier, somewhere\n            MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() );\n            builder << assertionResult.getMessage();\n            builder.m_info.message = builder.m_stream.str();\n\n            infoMessages.push_back( builder.m_info );\n        }\n    }\n\n     AssertionStats::~AssertionStats() = default;\n\n    SectionStats::SectionStats(  SectionInfo const& _sectionInfo,\n                                 Counts const& _assertions,\n                                 double _durationInSeconds,\n                                 bool _missingAssertions )\n    :   sectionInfo( _sectionInfo ),\n        assertions( _assertions ),\n        durationInSeconds( _durationInSeconds ),\n        missingAssertions( _missingAssertions )\n    {}\n\n    SectionStats::~SectionStats() = default;\n\n    TestCaseStats::TestCaseStats(  TestCaseInfo const& _testInfo,\n                                   Totals const& _totals,\n                                   std::string const& _stdOut,\n                                   std::string const& _stdErr,\n                                   bool _aborting )\n    : testInfo( _testInfo ),\n        totals( _totals ),\n        stdOut( _stdOut ),\n        stdErr( _stdErr ),\n        aborting( _aborting )\n    {}\n\n    TestCaseStats::~TestCaseStats() = default;\n\n    TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo,\n                                    Totals const& _totals,\n                                    bool _aborting )\n    :   groupInfo( _groupInfo ),\n        totals( _totals ),\n        aborting( _aborting )\n    {}\n\n    TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo )\n    :   groupInfo( _groupInfo ),\n        aborting( false )\n    {}\n\n    TestGroupStats::~TestGroupStats() = default;\n\n    TestRunStats::TestRunStats(   TestRunInfo const& _runInfo,\n                    Totals const& _totals,\n                    bool _aborting )\n    :   runInfo( _runInfo ),\n        totals( _totals ),\n        aborting( _aborting )\n    {}\n\n    TestRunStats::~TestRunStats() = default;\n\n    void IStreamingReporter::fatalErrorEncountered( StringRef ) {}\n    bool IStreamingReporter::isMulti() const { return false; }\n\n    IReporterFactory::~IReporterFactory() = default;\n    IReporterRegistry::~IReporterRegistry() = default;\n\n} // end namespace Catch\n// end catch_interfaces_reporter.cpp\n// start catch_interfaces_runner.cpp\n\nnamespace Catch {\n    IRunner::~IRunner() = default;\n}\n// end catch_interfaces_runner.cpp\n// start catch_interfaces_testcase.cpp\n\nnamespace Catch {\n    ITestInvoker::~ITestInvoker() = default;\n    ITestCaseRegistry::~ITestCaseRegistry() = default;\n}\n// end catch_interfaces_testcase.cpp\n// start catch_leak_detector.cpp\n\n#ifdef CATCH_CONFIG_WINDOWS_CRTDBG\n#include <crtdbg.h>\n\nnamespace Catch {\n\n    LeakDetector::LeakDetector() {\n        int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);\n        flag |= _CRTDBG_LEAK_CHECK_DF;\n        flag |= _CRTDBG_ALLOC_MEM_DF;\n        _CrtSetDbgFlag(flag);\n        _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);\n        _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);\n        // Change this to leaking allocation's number to break there\n        _CrtSetBreakAlloc(-1);\n    }\n}\n\n#else\n\n    Catch::LeakDetector::LeakDetector() {}\n\n#endif\n\nCatch::LeakDetector::~LeakDetector() {\n    Catch::cleanUp();\n}\n// end catch_leak_detector.cpp\n// start catch_list.cpp\n\n// start catch_list.h\n\n#include <set>\n\nnamespace Catch {\n\n    std::size_t listTests( Config const& config );\n\n    std::size_t listTestsNamesOnly( Config const& config );\n\n    struct TagInfo {\n        void add( std::string const& spelling );\n        std::string all() const;\n\n        std::set<std::string> spellings;\n        std::size_t count = 0;\n    };\n\n    std::size_t listTags( Config const& config );\n\n    std::size_t listReporters();\n\n    Option<std::size_t> list( std::shared_ptr<Config> const& config );\n\n} // end namespace Catch\n\n// end catch_list.h\n// start catch_text.h\n\nnamespace Catch {\n    using namespace clara::TextFlow;\n}\n\n// end catch_text.h\n#include <limits>\n#include <algorithm>\n#include <iomanip>\n\nnamespace Catch {\n\n    std::size_t listTests( Config const& config ) {\n        TestSpec const& testSpec = config.testSpec();\n        if( config.hasTestFilters() )\n            Catch::cout() << \"Matching test cases:\\n\";\n        else {\n            Catch::cout() << \"All available test cases:\\n\";\n        }\n\n        auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );\n        for( auto const& testCaseInfo : matchedTestCases ) {\n            Colour::Code colour = testCaseInfo.isHidden()\n                ? Colour::SecondaryText\n                : Colour::None;\n            Colour colourGuard( colour );\n\n            Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << \"\\n\";\n            if( config.verbosity() >= Verbosity::High ) {\n                Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl;\n                std::string description = testCaseInfo.description;\n                if( description.empty() )\n                    description = \"(NO DESCRIPTION)\";\n                Catch::cout() << Column( description ).indent(4) << std::endl;\n            }\n            if( !testCaseInfo.tags.empty() )\n                Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << \"\\n\";\n        }\n\n        if( !config.hasTestFilters() )\n            Catch::cout() << pluralise( matchedTestCases.size(), \"test case\" ) << '\\n' << std::endl;\n        else\n            Catch::cout() << pluralise( matchedTestCases.size(), \"matching test case\" ) << '\\n' << std::endl;\n        return matchedTestCases.size();\n    }\n\n    std::size_t listTestsNamesOnly( Config const& config ) {\n        TestSpec const& testSpec = config.testSpec();\n        std::size_t matchedTests = 0;\n        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );\n        for( auto const& testCaseInfo : matchedTestCases ) {\n            matchedTests++;\n            if( startsWith( testCaseInfo.name, '#' ) )\n               Catch::cout() << '\"' << testCaseInfo.name << '\"';\n            else\n               Catch::cout() << testCaseInfo.name;\n            if ( config.verbosity() >= Verbosity::High )\n                Catch::cout() << \"\\t@\" << testCaseInfo.lineInfo;\n            Catch::cout() << std::endl;\n        }\n        return matchedTests;\n    }\n\n    void TagInfo::add( std::string const& spelling ) {\n        ++count;\n        spellings.insert( spelling );\n    }\n\n    std::string TagInfo::all() const {\n        size_t size = 0;\n        for (auto const& spelling : spellings) {\n            // Add 2 for the brackes\n            size += spelling.size() + 2;\n        }\n\n        std::string out; out.reserve(size);\n        for (auto const& spelling : spellings) {\n            out += '[';\n            out += spelling;\n            out += ']';\n        }\n        return out;\n    }\n\n    std::size_t listTags( Config const& config ) {\n        TestSpec const& testSpec = config.testSpec();\n        if( config.hasTestFilters() )\n            Catch::cout() << \"Tags for matching test cases:\\n\";\n        else {\n            Catch::cout() << \"All available tags:\\n\";\n        }\n\n        std::map<std::string, TagInfo> tagCounts;\n\n        std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );\n        for( auto const& testCase : matchedTestCases ) {\n            for( auto const& tagName : testCase.getTestCaseInfo().tags ) {\n                std::string lcaseTagName = toLower( tagName );\n                auto countIt = tagCounts.find( lcaseTagName );\n                if( countIt == tagCounts.end() )\n                    countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first;\n                countIt->second.add( tagName );\n            }\n        }\n\n        for( auto const& tagCount : tagCounts ) {\n            ReusableStringStream rss;\n            rss << \"  \" << std::setw(2) << tagCount.second.count << \"  \";\n            auto str = rss.str();\n            auto wrapper = Column( tagCount.second.all() )\n                                                    .initialIndent( 0 )\n                                                    .indent( str.size() )\n                                                    .width( CATCH_CONFIG_CONSOLE_WIDTH-10 );\n            Catch::cout() << str << wrapper << '\\n';\n        }\n        Catch::cout() << pluralise( tagCounts.size(), \"tag\" ) << '\\n' << std::endl;\n        return tagCounts.size();\n    }\n\n    std::size_t listReporters() {\n        Catch::cout() << \"Available reporters:\\n\";\n        IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();\n        std::size_t maxNameLen = 0;\n        for( auto const& factoryKvp : factories )\n            maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() );\n\n        for( auto const& factoryKvp : factories ) {\n            Catch::cout()\n                    << Column( factoryKvp.first + \":\" )\n                            .indent(2)\n                            .width( 5+maxNameLen )\n                    +  Column( factoryKvp.second->getDescription() )\n                            .initialIndent(0)\n                            .indent(2)\n                            .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 )\n                    << \"\\n\";\n        }\n        Catch::cout() << std::endl;\n        return factories.size();\n    }\n\n    Option<std::size_t> list( std::shared_ptr<Config> const& config ) {\n        Option<std::size_t> listedCount;\n        getCurrentMutableContext().setConfig( config );\n        if( config->listTests() )\n            listedCount = listedCount.valueOr(0) + listTests( *config );\n        if( config->listTestNamesOnly() )\n            listedCount = listedCount.valueOr(0) + listTestsNamesOnly( *config );\n        if( config->listTags() )\n            listedCount = listedCount.valueOr(0) + listTags( *config );\n        if( config->listReporters() )\n            listedCount = listedCount.valueOr(0) + listReporters();\n        return listedCount;\n    }\n\n} // end namespace Catch\n// end catch_list.cpp\n// start catch_matchers.cpp\n\nnamespace Catch {\nnamespace Matchers {\n    namespace Impl {\n\n        std::string MatcherUntypedBase::toString() const {\n            if( m_cachedToString.empty() )\n                m_cachedToString = describe();\n            return m_cachedToString;\n        }\n\n        MatcherUntypedBase::~MatcherUntypedBase() = default;\n\n    } // namespace Impl\n} // namespace Matchers\n\nusing namespace Matchers;\nusing Matchers::Impl::MatcherBase;\n\n} // namespace Catch\n// end catch_matchers.cpp\n// start catch_matchers_exception.cpp\n\nnamespace Catch {\nnamespace Matchers {\nnamespace Exception {\n\nbool ExceptionMessageMatcher::match(std::exception const& ex) const {\n    return ex.what() == m_message;\n}\n\nstd::string ExceptionMessageMatcher::describe() const {\n    return \"exception message matches \\\"\" + m_message + \"\\\"\";\n}\n\n}\nException::ExceptionMessageMatcher Message(std::string const& message) {\n    return Exception::ExceptionMessageMatcher(message);\n}\n\n// namespace Exception\n} // namespace Matchers\n} // namespace Catch\n// end catch_matchers_exception.cpp\n// start catch_matchers_floating.cpp\n\n// start catch_polyfills.hpp\n\nnamespace Catch {\n    bool isnan(float f);\n    bool isnan(double d);\n}\n\n// end catch_polyfills.hpp\n// start catch_to_string.hpp\n\n#include <string>\n\nnamespace Catch {\n    template <typename T>\n    std::string to_string(T const& t) {\n#if defined(CATCH_CONFIG_CPP11_TO_STRING)\n        return std::to_string(t);\n#else\n        ReusableStringStream rss;\n        rss << t;\n        return rss.str();\n#endif\n    }\n} // end namespace Catch\n\n// end catch_to_string.hpp\n#include <algorithm>\n#include <cmath>\n#include <cstdlib>\n#include <cstdint>\n#include <cstring>\n#include <sstream>\n#include <type_traits>\n#include <iomanip>\n#include <limits>\n\nnamespace Catch {\nnamespace {\n\n    int32_t convert(float f) {\n        static_assert(sizeof(float) == sizeof(int32_t), \"Important ULP matcher assumption violated\");\n        int32_t i;\n        std::memcpy(&i, &f, sizeof(f));\n        return i;\n    }\n\n    int64_t convert(double d) {\n        static_assert(sizeof(double) == sizeof(int64_t), \"Important ULP matcher assumption violated\");\n        int64_t i;\n        std::memcpy(&i, &d, sizeof(d));\n        return i;\n    }\n\n    template <typename FP>\n    bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) {\n        // Comparison with NaN should always be false.\n        // This way we can rule it out before getting into the ugly details\n        if (Catch::isnan(lhs) || Catch::isnan(rhs)) {\n            return false;\n        }\n\n        auto lc = convert(lhs);\n        auto rc = convert(rhs);\n\n        if ((lc < 0) != (rc < 0)) {\n            // Potentially we can have +0 and -0\n            return lhs == rhs;\n        }\n\n        // static cast as a workaround for IBM XLC\n        auto ulpDiff = std::abs(static_cast<FP>(lc - rc));\n        return static_cast<uint64_t>(ulpDiff) <= maxUlpDiff;\n    }\n\n#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)\n\n    float nextafter(float x, float y) {\n        return ::nextafterf(x, y);\n    }\n\n    double nextafter(double x, double y) {\n        return ::nextafter(x, y);\n    }\n\n#endif // ^^^ CATCH_CONFIG_GLOBAL_NEXTAFTER ^^^\n\ntemplate <typename FP>\nFP step(FP start, FP direction, uint64_t steps) {\n    for (uint64_t i = 0; i < steps; ++i) {\n#if defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)\n        start = Catch::nextafter(start, direction);\n#else\n        start = std::nextafter(start, direction);\n#endif\n    }\n    return start;\n}\n\n// Performs equivalent check of std::fabs(lhs - rhs) <= margin\n// But without the subtraction to allow for INFINITY in comparison\nbool marginComparison(double lhs, double rhs, double margin) {\n    return (lhs + margin >= rhs) && (rhs + margin >= lhs);\n}\n\ntemplate <typename FloatingPoint>\nvoid write(std::ostream& out, FloatingPoint num) {\n    out << std::scientific\n        << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1)\n        << num;\n}\n\n} // end anonymous namespace\n\nnamespace Matchers {\nnamespace Floating {\n\n    enum class FloatingPointKind : uint8_t {\n        Float,\n        Double\n    };\n\n    WithinAbsMatcher::WithinAbsMatcher(double target, double margin)\n        :m_target{ target }, m_margin{ margin } {\n        CATCH_ENFORCE(margin >= 0, \"Invalid margin: \" << margin << '.'\n            << \" Margin has to be non-negative.\");\n    }\n\n    // Performs equivalent check of std::fabs(lhs - rhs) <= margin\n    // But without the subtraction to allow for INFINITY in comparison\n    bool WithinAbsMatcher::match(double const& matchee) const {\n        return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);\n    }\n\n    std::string WithinAbsMatcher::describe() const {\n        return \"is within \" + ::Catch::Detail::stringify(m_margin) + \" of \" + ::Catch::Detail::stringify(m_target);\n    }\n\n    WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType)\n        :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {\n        CATCH_ENFORCE(m_type == FloatingPointKind::Double\n                   || m_ulps < (std::numeric_limits<uint32_t>::max)(),\n            \"Provided ULP is impossibly large for a float comparison.\");\n    }\n\n#if defined(__clang__)\n#pragma clang diagnostic push\n// Clang <3.5 reports on the default branch in the switch below\n#pragma clang diagnostic ignored \"-Wunreachable-code\"\n#endif\n\n    bool WithinUlpsMatcher::match(double const& matchee) const {\n        switch (m_type) {\n        case FloatingPointKind::Float:\n            return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps);\n        case FloatingPointKind::Double:\n            return almostEqualUlps<double>(matchee, m_target, m_ulps);\n        default:\n            CATCH_INTERNAL_ERROR( \"Unknown FloatingPointKind value\" );\n        }\n    }\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\n    std::string WithinUlpsMatcher::describe() const {\n        std::stringstream ret;\n\n        ret << \"is within \" << m_ulps << \" ULPs of \";\n\n        if (m_type == FloatingPointKind::Float) {\n            write(ret, static_cast<float>(m_target));\n            ret << 'f';\n        } else {\n            write(ret, m_target);\n        }\n\n        ret << \" ([\";\n        if (m_type == FloatingPointKind::Double) {\n            write(ret, step(m_target, static_cast<double>(-INFINITY), m_ulps));\n            ret << \", \";\n            write(ret, step(m_target, static_cast<double>( INFINITY), m_ulps));\n        } else {\n            // We have to cast INFINITY to float because of MinGW, see #1782\n            write(ret, step(static_cast<float>(m_target), static_cast<float>(-INFINITY), m_ulps));\n            ret << \", \";\n            write(ret, step(static_cast<float>(m_target), static_cast<float>( INFINITY), m_ulps));\n        }\n        ret << \"])\";\n\n        return ret.str();\n    }\n\n    WithinRelMatcher::WithinRelMatcher(double target, double epsilon):\n        m_target(target),\n        m_epsilon(epsilon){\n        CATCH_ENFORCE(m_epsilon >= 0., \"Relative comparison with epsilon <  0 does not make sense.\");\n        CATCH_ENFORCE(m_epsilon  < 1., \"Relative comparison with epsilon >= 1 does not make sense.\");\n    }\n\n    bool WithinRelMatcher::match(double const& matchee) const {\n        const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target));\n        return marginComparison(matchee, m_target,\n                                std::isinf(relMargin)? 0 : relMargin);\n    }\n\n    std::string WithinRelMatcher::describe() const {\n        Catch::ReusableStringStream sstr;\n        sstr << \"and \" << m_target << \" are within \" << m_epsilon * 100. << \"% of each other\";\n        return sstr.str();\n    }\n\n}// namespace Floating\n\nFloating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) {\n    return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double);\n}\n\nFloating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) {\n    return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float);\n}\n\nFloating::WithinAbsMatcher WithinAbs(double target, double margin) {\n    return Floating::WithinAbsMatcher(target, margin);\n}\n\nFloating::WithinRelMatcher WithinRel(double target, double eps) {\n    return Floating::WithinRelMatcher(target, eps);\n}\n\nFloating::WithinRelMatcher WithinRel(double target) {\n    return Floating::WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100);\n}\n\nFloating::WithinRelMatcher WithinRel(float target, float eps) {\n    return Floating::WithinRelMatcher(target, eps);\n}\n\nFloating::WithinRelMatcher WithinRel(float target) {\n    return Floating::WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100);\n}\n\n} // namespace Matchers\n} // namespace Catch\n// end catch_matchers_floating.cpp\n// start catch_matchers_generic.cpp\n\nstd::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) {\n    if (desc.empty()) {\n        return \"matches undescribed predicate\";\n    } else {\n        return \"matches predicate: \\\"\" + desc + '\"';\n    }\n}\n// end catch_matchers_generic.cpp\n// start catch_matchers_string.cpp\n\n#include <regex>\n\nnamespace Catch {\nnamespace Matchers {\n\n    namespace StdString {\n\n        CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity )\n        :   m_caseSensitivity( caseSensitivity ),\n            m_str( adjustString( str ) )\n        {}\n        std::string CasedString::adjustString( std::string const& str ) const {\n            return m_caseSensitivity == CaseSensitive::No\n                   ? toLower( str )\n                   : str;\n        }\n        std::string CasedString::caseSensitivitySuffix() const {\n            return m_caseSensitivity == CaseSensitive::No\n                   ? \" (case insensitive)\"\n                   : std::string();\n        }\n\n        StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator )\n        : m_comparator( comparator ),\n          m_operation( operation ) {\n        }\n\n        std::string StringMatcherBase::describe() const {\n            std::string description;\n            description.reserve(5 + m_operation.size() + m_comparator.m_str.size() +\n                                        m_comparator.caseSensitivitySuffix().size());\n            description += m_operation;\n            description += \": \\\"\";\n            description += m_comparator.m_str;\n            description += \"\\\"\";\n            description += m_comparator.caseSensitivitySuffix();\n            return description;\n        }\n\n        EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( \"equals\", comparator ) {}\n\n        bool EqualsMatcher::match( std::string const& source ) const {\n            return m_comparator.adjustString( source ) == m_comparator.m_str;\n        }\n\n        ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( \"contains\", comparator ) {}\n\n        bool ContainsMatcher::match( std::string const& source ) const {\n            return contains( m_comparator.adjustString( source ), m_comparator.m_str );\n        }\n\n        StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( \"starts with\", comparator ) {}\n\n        bool StartsWithMatcher::match( std::string const& source ) const {\n            return startsWith( m_comparator.adjustString( source ), m_comparator.m_str );\n        }\n\n        EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( \"ends with\", comparator ) {}\n\n        bool EndsWithMatcher::match( std::string const& source ) const {\n            return endsWith( m_comparator.adjustString( source ), m_comparator.m_str );\n        }\n\n        RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {}\n\n        bool RegexMatcher::match(std::string const& matchee) const {\n            auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway\n            if (m_caseSensitivity == CaseSensitive::Choice::No) {\n                flags |= std::regex::icase;\n            }\n            auto reg = std::regex(m_regex, flags);\n            return std::regex_match(matchee, reg);\n        }\n\n        std::string RegexMatcher::describe() const {\n            return \"matches \" + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? \" case sensitively\" : \" case insensitively\");\n        }\n\n    } // namespace StdString\n\n    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) {\n        return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) );\n    }\n    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) {\n        return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) );\n    }\n    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {\n        return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) );\n    }\n    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {\n        return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) );\n    }\n\n    StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) {\n        return StdString::RegexMatcher(regex, caseSensitivity);\n    }\n\n} // namespace Matchers\n} // namespace Catch\n// end catch_matchers_string.cpp\n// start catch_message.cpp\n\n// start catch_uncaught_exceptions.h\n\nnamespace Catch {\n    bool uncaught_exceptions();\n} // end namespace Catch\n\n// end catch_uncaught_exceptions.h\n#include <cassert>\n#include <stack>\n\nnamespace Catch {\n\n    MessageInfo::MessageInfo(   StringRef const& _macroName,\n                                SourceLineInfo const& _lineInfo,\n                                ResultWas::OfType _type )\n    :   macroName( _macroName ),\n        lineInfo( _lineInfo ),\n        type( _type ),\n        sequence( ++globalCount )\n    {}\n\n    bool MessageInfo::operator==( MessageInfo const& other ) const {\n        return sequence == other.sequence;\n    }\n\n    bool MessageInfo::operator<( MessageInfo const& other ) const {\n        return sequence < other.sequence;\n    }\n\n    // This may need protecting if threading support is added\n    unsigned int MessageInfo::globalCount = 0;\n\n    ////////////////////////////////////////////////////////////////////////////\n\n    Catch::MessageBuilder::MessageBuilder( StringRef const& macroName,\n                                           SourceLineInfo const& lineInfo,\n                                           ResultWas::OfType type )\n        :m_info(macroName, lineInfo, type) {}\n\n    ////////////////////////////////////////////////////////////////////////////\n\n    ScopedMessage::ScopedMessage( MessageBuilder const& builder )\n    : m_info( builder.m_info ), m_moved()\n    {\n        m_info.message = builder.m_stream.str();\n        getResultCapture().pushScopedMessage( m_info );\n    }\n\n    ScopedMessage::ScopedMessage( ScopedMessage&& old )\n    : m_info( old.m_info ), m_moved()\n    {\n        old.m_moved = true;\n    }\n\n    ScopedMessage::~ScopedMessage() {\n        if ( !uncaught_exceptions() && !m_moved ){\n            getResultCapture().popScopedMessage(m_info);\n        }\n    }\n\n    Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ) {\n        auto trimmed = [&] (size_t start, size_t end) {\n            while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) {\n                ++start;\n            }\n            while (names[end] == ',' || isspace(static_cast<unsigned char>(names[end]))) {\n                --end;\n            }\n            return names.substr(start, end - start + 1);\n        };\n        auto skipq = [&] (size_t start, char quote) {\n            for (auto i = start + 1; i < names.size() ; ++i) {\n                if (names[i] == quote)\n                    return i;\n                if (names[i] == '\\\\')\n                    ++i;\n            }\n            CATCH_INTERNAL_ERROR(\"CAPTURE parsing encountered unmatched quote\");\n        };\n\n        size_t start = 0;\n        std::stack<char> openings;\n        for (size_t pos = 0; pos < names.size(); ++pos) {\n            char c = names[pos];\n            switch (c) {\n            case '[':\n            case '{':\n            case '(':\n            // It is basically impossible to disambiguate between\n            // comparison and start of template args in this context\n//            case '<':\n                openings.push(c);\n                break;\n            case ']':\n            case '}':\n            case ')':\n//           case '>':\n                openings.pop();\n                break;\n            case '\"':\n            case '\\'':\n                pos = skipq(pos, c);\n                break;\n            case ',':\n                if (start != pos && openings.empty()) {\n                    m_messages.emplace_back(macroName, lineInfo, resultType);\n                    m_messages.back().message = static_cast<std::string>(trimmed(start, pos));\n                    m_messages.back().message += \" := \";\n                    start = pos;\n                }\n            }\n        }\n        assert(openings.empty() && \"Mismatched openings\");\n        m_messages.emplace_back(macroName, lineInfo, resultType);\n        m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1));\n        m_messages.back().message += \" := \";\n    }\n    Capturer::~Capturer() {\n        if ( !uncaught_exceptions() ){\n            assert( m_captured == m_messages.size() );\n            for( size_t i = 0; i < m_captured; ++i  )\n                m_resultCapture.popScopedMessage( m_messages[i] );\n        }\n    }\n\n    void Capturer::captureValue( size_t index, std::string const& value ) {\n        assert( index < m_messages.size() );\n        m_messages[index].message += value;\n        m_resultCapture.pushScopedMessage( m_messages[index] );\n        m_captured++;\n    }\n\n} // end namespace Catch\n// end catch_message.cpp\n// start catch_output_redirect.cpp\n\n// start catch_output_redirect.h\n#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H\n#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H\n\n#include <cstdio>\n#include <iosfwd>\n#include <string>\n\nnamespace Catch {\n\n    class RedirectedStream {\n        std::ostream& m_originalStream;\n        std::ostream& m_redirectionStream;\n        std::streambuf* m_prevBuf;\n\n    public:\n        RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream );\n        ~RedirectedStream();\n    };\n\n    class RedirectedStdOut {\n        ReusableStringStream m_rss;\n        RedirectedStream m_cout;\n    public:\n        RedirectedStdOut();\n        auto str() const -> std::string;\n    };\n\n    // StdErr has two constituent streams in C++, std::cerr and std::clog\n    // This means that we need to redirect 2 streams into 1 to keep proper\n    // order of writes\n    class RedirectedStdErr {\n        ReusableStringStream m_rss;\n        RedirectedStream m_cerr;\n        RedirectedStream m_clog;\n    public:\n        RedirectedStdErr();\n        auto str() const -> std::string;\n    };\n\n    class RedirectedStreams {\n    public:\n        RedirectedStreams(RedirectedStreams const&) = delete;\n        RedirectedStreams& operator=(RedirectedStreams const&) = delete;\n        RedirectedStreams(RedirectedStreams&&) = delete;\n        RedirectedStreams& operator=(RedirectedStreams&&) = delete;\n\n        RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr);\n        ~RedirectedStreams();\n    private:\n        std::string& m_redirectedCout;\n        std::string& m_redirectedCerr;\n        RedirectedStdOut m_redirectedStdOut;\n        RedirectedStdErr m_redirectedStdErr;\n    };\n\n#if defined(CATCH_CONFIG_NEW_CAPTURE)\n\n    // Windows's implementation of std::tmpfile is terrible (it tries\n    // to create a file inside system folder, thus requiring elevated\n    // privileges for the binary), so we have to use tmpnam(_s) and\n    // create the file ourselves there.\n    class TempFile {\n    public:\n        TempFile(TempFile const&) = delete;\n        TempFile& operator=(TempFile const&) = delete;\n        TempFile(TempFile&&) = delete;\n        TempFile& operator=(TempFile&&) = delete;\n\n        TempFile();\n        ~TempFile();\n\n        std::FILE* getFile();\n        std::string getContents();\n\n    private:\n        std::FILE* m_file = nullptr;\n    #if defined(_MSC_VER)\n        char m_buffer[L_tmpnam] = { 0 };\n    #endif\n    };\n\n    class OutputRedirect {\n    public:\n        OutputRedirect(OutputRedirect const&) = delete;\n        OutputRedirect& operator=(OutputRedirect const&) = delete;\n        OutputRedirect(OutputRedirect&&) = delete;\n        OutputRedirect& operator=(OutputRedirect&&) = delete;\n\n        OutputRedirect(std::string& stdout_dest, std::string& stderr_dest);\n        ~OutputRedirect();\n\n    private:\n        int m_originalStdout = -1;\n        int m_originalStderr = -1;\n        TempFile m_stdoutFile;\n        TempFile m_stderrFile;\n        std::string& m_stdoutDest;\n        std::string& m_stderrDest;\n    };\n\n#endif\n\n} // end namespace Catch\n\n#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H\n// end catch_output_redirect.h\n#include <cstdio>\n#include <cstring>\n#include <fstream>\n#include <sstream>\n#include <stdexcept>\n\n#if defined(CATCH_CONFIG_NEW_CAPTURE)\n    #if defined(_MSC_VER)\n    #include <io.h>      //_dup and _dup2\n    #define dup _dup\n    #define dup2 _dup2\n    #define fileno _fileno\n    #else\n    #include <unistd.h>  // dup and dup2\n    #endif\n#endif\n\nnamespace Catch {\n\n    RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream )\n    :   m_originalStream( originalStream ),\n        m_redirectionStream( redirectionStream ),\n        m_prevBuf( m_originalStream.rdbuf() )\n    {\n        m_originalStream.rdbuf( m_redirectionStream.rdbuf() );\n    }\n\n    RedirectedStream::~RedirectedStream() {\n        m_originalStream.rdbuf( m_prevBuf );\n    }\n\n    RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {}\n    auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); }\n\n    RedirectedStdErr::RedirectedStdErr()\n    :   m_cerr( Catch::cerr(), m_rss.get() ),\n        m_clog( Catch::clog(), m_rss.get() )\n    {}\n    auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); }\n\n    RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr)\n    :   m_redirectedCout(redirectedCout),\n        m_redirectedCerr(redirectedCerr)\n    {}\n\n    RedirectedStreams::~RedirectedStreams() {\n        m_redirectedCout += m_redirectedStdOut.str();\n        m_redirectedCerr += m_redirectedStdErr.str();\n    }\n\n#if defined(CATCH_CONFIG_NEW_CAPTURE)\n\n#if defined(_MSC_VER)\n    TempFile::TempFile() {\n        if (tmpnam_s(m_buffer)) {\n            CATCH_RUNTIME_ERROR(\"Could not get a temp filename\");\n        }\n        if (fopen_s(&m_file, m_buffer, \"w+\")) {\n            char buffer[100];\n            if (strerror_s(buffer, errno)) {\n                CATCH_RUNTIME_ERROR(\"Could not translate errno to a string\");\n            }\n            CATCH_RUNTIME_ERROR(\"Could not open the temp file: '\" << m_buffer << \"' because: \" << buffer);\n        }\n    }\n#else\n    TempFile::TempFile() {\n        m_file = std::tmpfile();\n        if (!m_file) {\n            CATCH_RUNTIME_ERROR(\"Could not create a temp file.\");\n        }\n    }\n\n#endif\n\n    TempFile::~TempFile() {\n         // TBD: What to do about errors here?\n         std::fclose(m_file);\n         // We manually create the file on Windows only, on Linux\n         // it will be autodeleted\n#if defined(_MSC_VER)\n         std::remove(m_buffer);\n#endif\n    }\n\n    FILE* TempFile::getFile() {\n        return m_file;\n    }\n\n    std::string TempFile::getContents() {\n        std::stringstream sstr;\n        char buffer[100] = {};\n        std::rewind(m_file);\n        while (std::fgets(buffer, sizeof(buffer), m_file)) {\n            sstr << buffer;\n        }\n        return sstr.str();\n    }\n\n    OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) :\n        m_originalStdout(dup(1)),\n        m_originalStderr(dup(2)),\n        m_stdoutDest(stdout_dest),\n        m_stderrDest(stderr_dest) {\n        dup2(fileno(m_stdoutFile.getFile()), 1);\n        dup2(fileno(m_stderrFile.getFile()), 2);\n    }\n\n    OutputRedirect::~OutputRedirect() {\n        Catch::cout() << std::flush;\n        fflush(stdout);\n        // Since we support overriding these streams, we flush cerr\n        // even though std::cerr is unbuffered\n        Catch::cerr() << std::flush;\n        Catch::clog() << std::flush;\n        fflush(stderr);\n\n        dup2(m_originalStdout, 1);\n        dup2(m_originalStderr, 2);\n\n        m_stdoutDest += m_stdoutFile.getContents();\n        m_stderrDest += m_stderrFile.getContents();\n    }\n\n#endif // CATCH_CONFIG_NEW_CAPTURE\n\n} // namespace Catch\n\n#if defined(CATCH_CONFIG_NEW_CAPTURE)\n    #if defined(_MSC_VER)\n    #undef dup\n    #undef dup2\n    #undef fileno\n    #endif\n#endif\n// end catch_output_redirect.cpp\n// start catch_polyfills.cpp\n\n#include <cmath>\n\nnamespace Catch {\n\n#if !defined(CATCH_CONFIG_POLYFILL_ISNAN)\n    bool isnan(float f) {\n        return std::isnan(f);\n    }\n    bool isnan(double d) {\n        return std::isnan(d);\n    }\n#else\n    // For now we only use this for embarcadero\n    bool isnan(float f) {\n        return std::_isnan(f);\n    }\n    bool isnan(double d) {\n        return std::_isnan(d);\n    }\n#endif\n\n} // end namespace Catch\n// end catch_polyfills.cpp\n// start catch_random_number_generator.cpp\n\nnamespace Catch {\n\nnamespace {\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable:4146) // we negate uint32 during the rotate\n#endif\n        // Safe rotr implementation thanks to John Regehr\n        uint32_t rotate_right(uint32_t val, uint32_t count) {\n            const uint32_t mask = 31;\n            count &= mask;\n            return (val >> count) | (val << (-count & mask));\n        }\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\n}\n\n    SimplePcg32::SimplePcg32(result_type seed_) {\n        seed(seed_);\n    }\n\n    void SimplePcg32::seed(result_type seed_) {\n        m_state = 0;\n        (*this)();\n        m_state += seed_;\n        (*this)();\n    }\n\n    void SimplePcg32::discard(uint64_t skip) {\n        // We could implement this to run in O(log n) steps, but this\n        // should suffice for our use case.\n        for (uint64_t s = 0; s < skip; ++s) {\n            static_cast<void>((*this)());\n        }\n    }\n\n    SimplePcg32::result_type SimplePcg32::operator()() {\n        // prepare the output value\n        const uint32_t xorshifted = static_cast<uint32_t>(((m_state >> 18u) ^ m_state) >> 27u);\n        const auto output = rotate_right(xorshifted, m_state >> 59u);\n\n        // advance state\n        m_state = m_state * 6364136223846793005ULL + s_inc;\n\n        return output;\n    }\n\n    bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) {\n        return lhs.m_state == rhs.m_state;\n    }\n\n    bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) {\n        return lhs.m_state != rhs.m_state;\n    }\n}\n// end catch_random_number_generator.cpp\n// start catch_registry_hub.cpp\n\n// start catch_test_case_registry_impl.h\n\n#include <vector>\n#include <set>\n#include <algorithm>\n#include <ios>\n\nnamespace Catch {\n\n    class TestCase;\n    struct IConfig;\n\n    std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases );\n\n    bool isThrowSafe( TestCase const& testCase, IConfig const& config );\n    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );\n\n    void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions );\n\n    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );\n    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );\n\n    class TestRegistry : public ITestCaseRegistry {\n    public:\n        virtual ~TestRegistry() = default;\n\n        virtual void registerTest( TestCase const& testCase );\n\n        std::vector<TestCase> const& getAllTests() const override;\n        std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const override;\n\n    private:\n        std::vector<TestCase> m_functions;\n        mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder;\n        mutable std::vector<TestCase> m_sortedFunctions;\n        std::size_t m_unnamedCount = 0;\n        std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised\n    };\n\n    ///////////////////////////////////////////////////////////////////////////\n\n    class TestInvokerAsFunction : public ITestInvoker {\n        void(*m_testAsFunction)();\n    public:\n        TestInvokerAsFunction( void(*testAsFunction)() ) noexcept;\n\n        void invoke() const override;\n    };\n\n    std::string extractClassName( StringRef const& classOrQualifiedMethodName );\n\n    ///////////////////////////////////////////////////////////////////////////\n\n} // end namespace Catch\n\n// end catch_test_case_registry_impl.h\n// start catch_reporter_registry.h\n\n#include <map>\n\nnamespace Catch {\n\n    class ReporterRegistry : public IReporterRegistry {\n\n    public:\n\n        ~ReporterRegistry() override;\n\n        IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override;\n\n        void registerReporter( std::string const& name, IReporterFactoryPtr const& factory );\n        void registerListener( IReporterFactoryPtr const& factory );\n\n        FactoryMap const& getFactories() const override;\n        Listeners const& getListeners() const override;\n\n    private:\n        FactoryMap m_factories;\n        Listeners m_listeners;\n    };\n}\n\n// end catch_reporter_registry.h\n// start catch_tag_alias_registry.h\n\n// start catch_tag_alias.h\n\n#include <string>\n\nnamespace Catch {\n\n    struct TagAlias {\n        TagAlias(std::string const& _tag, SourceLineInfo _lineInfo);\n\n        std::string tag;\n        SourceLineInfo lineInfo;\n    };\n\n} // end namespace Catch\n\n// end catch_tag_alias.h\n#include <map>\n\nnamespace Catch {\n\n    class TagAliasRegistry : public ITagAliasRegistry {\n    public:\n        ~TagAliasRegistry() override;\n        TagAlias const* find( std::string const& alias ) const override;\n        std::string expandAliases( std::string const& unexpandedTestSpec ) const override;\n        void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo );\n\n    private:\n        std::map<std::string, TagAlias> m_registry;\n    };\n\n} // end namespace Catch\n\n// end catch_tag_alias_registry.h\n// start catch_startup_exception_registry.h\n\n#include <vector>\n#include <exception>\n\nnamespace Catch {\n\n    class StartupExceptionRegistry {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    public:\n        void add(std::exception_ptr const& exception) noexcept;\n        std::vector<std::exception_ptr> const& getExceptions() const noexcept;\n    private:\n        std::vector<std::exception_ptr> m_exceptions;\n#endif\n    };\n\n} // end namespace Catch\n\n// end catch_startup_exception_registry.h\n// start catch_singletons.hpp\n\nnamespace Catch {\n\n    struct ISingleton {\n        virtual ~ISingleton();\n    };\n\n    void addSingleton( ISingleton* singleton );\n    void cleanupSingletons();\n\n    template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT>\n    class Singleton : SingletonImplT, public ISingleton {\n\n        static auto getInternal() -> Singleton* {\n            static Singleton* s_instance = nullptr;\n            if( !s_instance ) {\n                s_instance = new Singleton;\n                addSingleton( s_instance );\n            }\n            return s_instance;\n        }\n\n    public:\n        static auto get() -> InterfaceT const& {\n            return *getInternal();\n        }\n        static auto getMutable() -> MutableInterfaceT& {\n            return *getInternal();\n        }\n    };\n\n} // namespace Catch\n\n// end catch_singletons.hpp\nnamespace Catch {\n\n    namespace {\n\n        class RegistryHub : public IRegistryHub, public IMutableRegistryHub,\n                            private NonCopyable {\n\n        public: // IRegistryHub\n            RegistryHub() = default;\n            IReporterRegistry const& getReporterRegistry() const override {\n                return m_reporterRegistry;\n            }\n            ITestCaseRegistry const& getTestCaseRegistry() const override {\n                return m_testCaseRegistry;\n            }\n            IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override {\n                return m_exceptionTranslatorRegistry;\n            }\n            ITagAliasRegistry const& getTagAliasRegistry() const override {\n                return m_tagAliasRegistry;\n            }\n            StartupExceptionRegistry const& getStartupExceptionRegistry() const override {\n                return m_exceptionRegistry;\n            }\n\n        public: // IMutableRegistryHub\n            void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override {\n                m_reporterRegistry.registerReporter( name, factory );\n            }\n            void registerListener( IReporterFactoryPtr const& factory ) override {\n                m_reporterRegistry.registerListener( factory );\n            }\n            void registerTest( TestCase const& testInfo ) override {\n                m_testCaseRegistry.registerTest( testInfo );\n            }\n            void registerTranslator( const IExceptionTranslator* translator ) override {\n                m_exceptionTranslatorRegistry.registerTranslator( translator );\n            }\n            void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override {\n                m_tagAliasRegistry.add( alias, tag, lineInfo );\n            }\n            void registerStartupException() noexcept override {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n                m_exceptionRegistry.add(std::current_exception());\n#else\n                CATCH_INTERNAL_ERROR(\"Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n#endif\n            }\n            IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override {\n                return m_enumValuesRegistry;\n            }\n\n        private:\n            TestRegistry m_testCaseRegistry;\n            ReporterRegistry m_reporterRegistry;\n            ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;\n            TagAliasRegistry m_tagAliasRegistry;\n            StartupExceptionRegistry m_exceptionRegistry;\n            Detail::EnumValuesRegistry m_enumValuesRegistry;\n        };\n    }\n\n    using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>;\n\n    IRegistryHub const& getRegistryHub() {\n        return RegistryHubSingleton::get();\n    }\n    IMutableRegistryHub& getMutableRegistryHub() {\n        return RegistryHubSingleton::getMutable();\n    }\n    void cleanUp() {\n        cleanupSingletons();\n        cleanUpContext();\n    }\n    std::string translateActiveException() {\n        return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();\n    }\n\n} // end namespace Catch\n// end catch_registry_hub.cpp\n// start catch_reporter_registry.cpp\n\nnamespace Catch {\n\n    ReporterRegistry::~ReporterRegistry() = default;\n\n    IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const {\n        auto it =  m_factories.find( name );\n        if( it == m_factories.end() )\n            return nullptr;\n        return it->second->create( ReporterConfig( config ) );\n    }\n\n    void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) {\n        m_factories.emplace(name, factory);\n    }\n    void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) {\n        m_listeners.push_back( factory );\n    }\n\n    IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const {\n        return m_factories;\n    }\n    IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const {\n        return m_listeners;\n    }\n\n}\n// end catch_reporter_registry.cpp\n// start catch_result_type.cpp\n\nnamespace Catch {\n\n    bool isOk( ResultWas::OfType resultType ) {\n        return ( resultType & ResultWas::FailureBit ) == 0;\n    }\n    bool isJustInfo( int flags ) {\n        return flags == ResultWas::Info;\n    }\n\n    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {\n        return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) );\n    }\n\n    bool shouldContinueOnFailure( int flags )    { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; }\n    bool shouldSuppressFailure( int flags )      { return ( flags & ResultDisposition::SuppressFail ) != 0; }\n\n} // end namespace Catch\n// end catch_result_type.cpp\n// start catch_run_context.cpp\n\n#include <cassert>\n#include <algorithm>\n#include <sstream>\n\nnamespace Catch {\n\n    namespace Generators {\n        struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker {\n            GeneratorBasePtr m_generator;\n\n            GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )\n            :   TrackerBase( nameAndLocation, ctx, parent )\n            {}\n            ~GeneratorTracker();\n\n            static GeneratorTracker& acquire( TrackerContext& ctx, TestCaseTracking::NameAndLocation const& nameAndLocation ) {\n                std::shared_ptr<GeneratorTracker> tracker;\n\n                ITracker& currentTracker = ctx.currentTracker();\n                // Under specific circumstances, the generator we want\n                // to acquire is also the current tracker. If this is\n                // the case, we have to avoid looking through current\n                // tracker's children, and instead return the current\n                // tracker.\n                // A case where this check is important is e.g.\n                //     for (int i = 0; i < 5; ++i) {\n                //         int n = GENERATE(1, 2);\n                //     }\n                //\n                // without it, the code above creates 5 nested generators.\n                if (currentTracker.nameAndLocation() == nameAndLocation) {\n                    auto thisTracker = currentTracker.parent().findChild(nameAndLocation);\n                    assert(thisTracker);\n                    assert(thisTracker->isGeneratorTracker());\n                    tracker = std::static_pointer_cast<GeneratorTracker>(thisTracker);\n                } else if ( TestCaseTracking::ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {\n                    assert( childTracker );\n                    assert( childTracker->isGeneratorTracker() );\n                    tracker = std::static_pointer_cast<GeneratorTracker>( childTracker );\n                } else {\n                    tracker = std::make_shared<GeneratorTracker>( nameAndLocation, ctx, &currentTracker );\n                    currentTracker.addChild( tracker );\n                }\n\n                if( !tracker->isComplete() ) {\n                    tracker->open();\n                }\n\n                return *tracker;\n            }\n\n            // TrackerBase interface\n            bool isGeneratorTracker() const override { return true; }\n            auto hasGenerator() const -> bool override {\n                return !!m_generator;\n            }\n            void close() override {\n                TrackerBase::close();\n                // If a generator has a child (it is followed by a section)\n                // and none of its children have started, then we must wait\n                // until later to start consuming its values.\n                // This catches cases where `GENERATE` is placed between two\n                // `SECTION`s.\n                // **The check for m_children.empty cannot be removed**.\n                // doing so would break `GENERATE` _not_ followed by `SECTION`s.\n                const bool should_wait_for_child = [&]() {\n                    // No children -> nobody to wait for\n                    if ( m_children.empty() ) {\n                        return false;\n                    }\n                    // If at least one child started executing, don't wait\n                    if ( std::find_if(\n                             m_children.begin(),\n                             m_children.end(),\n                             []( TestCaseTracking::ITrackerPtr tracker ) {\n                                 return tracker->hasStarted();\n                             } ) != m_children.end() ) {\n                        return false;\n                    }\n\n                    // No children have started. We need to check if they _can_\n                    // start, and thus we should wait for them, or they cannot\n                    // start (due to filters), and we shouldn't wait for them\n                    auto* parent = m_parent;\n                    // This is safe: there is always at least one section\n                    // tracker in a test case tracking tree\n                    while ( !parent->isSectionTracker() ) {\n                        parent = &( parent->parent() );\n                    }\n                    assert( parent &&\n                            \"Missing root (test case) level section\" );\n\n                    auto const& parentSection =\n                        static_cast<SectionTracker&>( *parent );\n                    auto const& filters = parentSection.getFilters();\n                    // No filters -> no restrictions on running sections\n                    if ( filters.empty() ) {\n                        return true;\n                    }\n\n                    for ( auto const& child : m_children ) {\n                        if ( child->isSectionTracker() &&\n                             std::find( filters.begin(),\n                                        filters.end(),\n                                        static_cast<SectionTracker&>( *child )\n                                            .trimmedName() ) !=\n                                 filters.end() ) {\n                            return true;\n                        }\n                    }\n                    return false;\n                }();\n\n                // This check is a bit tricky, because m_generator->next()\n                // has a side-effect, where it consumes generator's current\n                // value, but we do not want to invoke the side-effect if\n                // this generator is still waiting for any child to start.\n                if ( should_wait_for_child ||\n                     ( m_runState == CompletedSuccessfully &&\n                       m_generator->next() ) ) {\n                    m_children.clear();\n                    m_runState = Executing;\n                }\n            }\n\n            // IGeneratorTracker interface\n            auto getGenerator() const -> GeneratorBasePtr const& override {\n                return m_generator;\n            }\n            void setGenerator( GeneratorBasePtr&& generator ) override {\n                m_generator = std::move( generator );\n            }\n        };\n        GeneratorTracker::~GeneratorTracker() {}\n    }\n\n    RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter)\n    :   m_runInfo(_config->name()),\n        m_context(getCurrentMutableContext()),\n        m_config(_config),\n        m_reporter(std::move(reporter)),\n        m_lastAssertionInfo{ StringRef(), SourceLineInfo(\"\",0), StringRef(), ResultDisposition::Normal },\n        m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions )\n    {\n        m_context.setRunner(this);\n        m_context.setConfig(m_config);\n        m_context.setResultCapture(this);\n        m_reporter->testRunStarting(m_runInfo);\n    }\n\n    RunContext::~RunContext() {\n        m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));\n    }\n\n    void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) {\n        m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount));\n    }\n\n    void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) {\n        m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting()));\n    }\n\n    Totals RunContext::runTest(TestCase const& testCase) {\n        Totals prevTotals = m_totals;\n\n        std::string redirectedCout;\n        std::string redirectedCerr;\n\n        auto const& testInfo = testCase.getTestCaseInfo();\n\n        m_reporter->testCaseStarting(testInfo);\n\n        m_activeTestCase = &testCase;\n\n        ITracker& rootTracker = m_trackerContext.startRun();\n        assert(rootTracker.isSectionTracker());\n        static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun());\n        do {\n            m_trackerContext.startCycle();\n            m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo));\n            runCurrentTest(redirectedCout, redirectedCerr);\n        } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting());\n\n        Totals deltaTotals = m_totals.delta(prevTotals);\n        if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) {\n            deltaTotals.assertions.failed++;\n            deltaTotals.testCases.passed--;\n            deltaTotals.testCases.failed++;\n        }\n        m_totals.testCases += deltaTotals.testCases;\n        m_reporter->testCaseEnded(TestCaseStats(testInfo,\n                                  deltaTotals,\n                                  redirectedCout,\n                                  redirectedCerr,\n                                  aborting()));\n\n        m_activeTestCase = nullptr;\n        m_testCaseTracker = nullptr;\n\n        return deltaTotals;\n    }\n\n    IConfigPtr RunContext::config() const {\n        return m_config;\n    }\n\n    IStreamingReporter& RunContext::reporter() const {\n        return *m_reporter;\n    }\n\n    void RunContext::assertionEnded(AssertionResult const & result) {\n        if (result.getResultType() == ResultWas::Ok) {\n            m_totals.assertions.passed++;\n            m_lastAssertionPassed = true;\n        } else if (!result.isOk()) {\n            m_lastAssertionPassed = false;\n            if( m_activeTestCase->getTestCaseInfo().okToFail() )\n                m_totals.assertions.failedButOk++;\n            else\n                m_totals.assertions.failed++;\n        }\n        else {\n            m_lastAssertionPassed = true;\n        }\n\n        // We have no use for the return value (whether messages should be cleared), because messages were made scoped\n        // and should be let to clear themselves out.\n        static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals)));\n\n        if (result.getResultType() != ResultWas::Warning)\n            m_messageScopes.clear();\n\n        // Reset working state\n        resetAssertionInfo();\n        m_lastResult = result;\n    }\n    void RunContext::resetAssertionInfo() {\n        m_lastAssertionInfo.macroName = StringRef();\n        m_lastAssertionInfo.capturedExpression = \"{Unknown expression after the reported line}\"_sr;\n    }\n\n    bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) {\n        ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo));\n        if (!sectionTracker.isOpen())\n            return false;\n        m_activeSections.push_back(&sectionTracker);\n\n        m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;\n\n        m_reporter->sectionStarting(sectionInfo);\n\n        assertions = m_totals.assertions;\n\n        return true;\n    }\n    auto RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& {\n        using namespace Generators;\n        GeneratorTracker& tracker = GeneratorTracker::acquire(m_trackerContext,\n                                                              TestCaseTracking::NameAndLocation( static_cast<std::string>(generatorName), lineInfo ) );\n        m_lastAssertionInfo.lineInfo = lineInfo;\n        return tracker;\n    }\n\n    bool RunContext::testForMissingAssertions(Counts& assertions) {\n        if (assertions.total() != 0)\n            return false;\n        if (!m_config->warnAboutMissingAssertions())\n            return false;\n        if (m_trackerContext.currentTracker().hasChildren())\n            return false;\n        m_totals.assertions.failed++;\n        assertions.failed++;\n        return true;\n    }\n\n    void RunContext::sectionEnded(SectionEndInfo const & endInfo) {\n        Counts assertions = m_totals.assertions - endInfo.prevAssertions;\n        bool missingAssertions = testForMissingAssertions(assertions);\n\n        if (!m_activeSections.empty()) {\n            m_activeSections.back()->close();\n            m_activeSections.pop_back();\n        }\n\n        m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions));\n        m_messages.clear();\n        m_messageScopes.clear();\n    }\n\n    void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) {\n        if (m_unfinishedSections.empty())\n            m_activeSections.back()->fail();\n        else\n            m_activeSections.back()->close();\n        m_activeSections.pop_back();\n\n        m_unfinishedSections.push_back(endInfo);\n    }\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    void RunContext::benchmarkPreparing(std::string const& name) {\n        m_reporter->benchmarkPreparing(name);\n    }\n    void RunContext::benchmarkStarting( BenchmarkInfo const& info ) {\n        m_reporter->benchmarkStarting( info );\n    }\n    void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) {\n        m_reporter->benchmarkEnded( stats );\n    }\n    void RunContext::benchmarkFailed(std::string const & error) {\n        m_reporter->benchmarkFailed(error);\n    }\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    void RunContext::pushScopedMessage(MessageInfo const & message) {\n        m_messages.push_back(message);\n    }\n\n    void RunContext::popScopedMessage(MessageInfo const & message) {\n        m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end());\n    }\n\n    void RunContext::emplaceUnscopedMessage( MessageBuilder const& builder ) {\n        m_messageScopes.emplace_back( builder );\n    }\n\n    std::string RunContext::getCurrentTestName() const {\n        return m_activeTestCase\n            ? m_activeTestCase->getTestCaseInfo().name\n            : std::string();\n    }\n\n    const AssertionResult * RunContext::getLastResult() const {\n        return &(*m_lastResult);\n    }\n\n    void RunContext::exceptionEarlyReported() {\n        m_shouldReportUnexpected = false;\n    }\n\n    void RunContext::handleFatalErrorCondition( StringRef message ) {\n        // First notify reporter that bad things happened\n        m_reporter->fatalErrorEncountered(message);\n\n        // Don't rebuild the result -- the stringification itself can cause more fatal errors\n        // Instead, fake a result data.\n        AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } );\n        tempResult.message = static_cast<std::string>(message);\n        AssertionResult result(m_lastAssertionInfo, tempResult);\n\n        assertionEnded(result);\n\n        handleUnfinishedSections();\n\n        // Recreate section for test case (as we will lose the one that was in scope)\n        auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();\n        SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);\n\n        Counts assertions;\n        assertions.failed = 1;\n        SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false);\n        m_reporter->sectionEnded(testCaseSectionStats);\n\n        auto const& testInfo = m_activeTestCase->getTestCaseInfo();\n\n        Totals deltaTotals;\n        deltaTotals.testCases.failed = 1;\n        deltaTotals.assertions.failed = 1;\n        m_reporter->testCaseEnded(TestCaseStats(testInfo,\n                                  deltaTotals,\n                                  std::string(),\n                                  std::string(),\n                                  false));\n        m_totals.testCases.failed++;\n        testGroupEnded(std::string(), m_totals, 1, 1);\n        m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false));\n    }\n\n    bool RunContext::lastAssertionPassed() {\n         return m_lastAssertionPassed;\n    }\n\n    void RunContext::assertionPassed() {\n        m_lastAssertionPassed = true;\n        ++m_totals.assertions.passed;\n        resetAssertionInfo();\n        m_messageScopes.clear();\n    }\n\n    bool RunContext::aborting() const {\n        return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter());\n    }\n\n    void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) {\n        auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo();\n        SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name);\n        m_reporter->sectionStarting(testCaseSection);\n        Counts prevAssertions = m_totals.assertions;\n        double duration = 0;\n        m_shouldReportUnexpected = true;\n        m_lastAssertionInfo = { \"TEST_CASE\"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal };\n\n        seedRng(*m_config);\n\n        Timer timer;\n        CATCH_TRY {\n            if (m_reporter->getPreferences().shouldRedirectStdOut) {\n#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)\n                RedirectedStreams redirectedStreams(redirectedCout, redirectedCerr);\n\n                timer.start();\n                invokeActiveTestCase();\n#else\n                OutputRedirect r(redirectedCout, redirectedCerr);\n                timer.start();\n                invokeActiveTestCase();\n#endif\n            } else {\n                timer.start();\n                invokeActiveTestCase();\n            }\n            duration = timer.getElapsedSeconds();\n        } CATCH_CATCH_ANON (TestFailureException&) {\n            // This just means the test was aborted due to failure\n        } CATCH_CATCH_ALL {\n            // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions\n            // are reported without translation at the point of origin.\n            if( m_shouldReportUnexpected ) {\n                AssertionReaction dummyReaction;\n                handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction );\n            }\n        }\n        Counts assertions = m_totals.assertions - prevAssertions;\n        bool missingAssertions = testForMissingAssertions(assertions);\n\n        m_testCaseTracker->close();\n        handleUnfinishedSections();\n        m_messages.clear();\n        m_messageScopes.clear();\n\n        SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions);\n        m_reporter->sectionEnded(testCaseSectionStats);\n    }\n\n    void RunContext::invokeActiveTestCase() {\n        FatalConditionHandlerGuard _(&m_fatalConditionhandler);\n        m_activeTestCase->invoke();\n    }\n\n    void RunContext::handleUnfinishedSections() {\n        // If sections ended prematurely due to an exception we stored their\n        // infos here so we can tear them down outside the unwind process.\n        for (auto it = m_unfinishedSections.rbegin(),\n             itEnd = m_unfinishedSections.rend();\n             it != itEnd;\n             ++it)\n            sectionEnded(*it);\n        m_unfinishedSections.clear();\n    }\n\n    void RunContext::handleExpr(\n        AssertionInfo const& info,\n        ITransientExpression const& expr,\n        AssertionReaction& reaction\n    ) {\n        m_reporter->assertionStarting( info );\n\n        bool negated = isFalseTest( info.resultDisposition );\n        bool result = expr.getResult() != negated;\n\n        if( result ) {\n            if (!m_includeSuccessfulResults) {\n                assertionPassed();\n            }\n            else {\n                reportExpr(info, ResultWas::Ok, &expr, negated);\n            }\n        }\n        else {\n            reportExpr(info, ResultWas::ExpressionFailed, &expr, negated );\n            populateReaction( reaction );\n        }\n    }\n    void RunContext::reportExpr(\n            AssertionInfo const &info,\n            ResultWas::OfType resultType,\n            ITransientExpression const *expr,\n            bool negated ) {\n\n        m_lastAssertionInfo = info;\n        AssertionResultData data( resultType, LazyExpression( negated ) );\n\n        AssertionResult assertionResult{ info, data };\n        assertionResult.m_resultData.lazyExpression.m_transientExpression = expr;\n\n        assertionEnded( assertionResult );\n    }\n\n    void RunContext::handleMessage(\n            AssertionInfo const& info,\n            ResultWas::OfType resultType,\n            StringRef const& message,\n            AssertionReaction& reaction\n    ) {\n        m_reporter->assertionStarting( info );\n\n        m_lastAssertionInfo = info;\n\n        AssertionResultData data( resultType, LazyExpression( false ) );\n        data.message = static_cast<std::string>(message);\n        AssertionResult assertionResult{ m_lastAssertionInfo, data };\n        assertionEnded( assertionResult );\n        if( !assertionResult.isOk() )\n            populateReaction( reaction );\n    }\n    void RunContext::handleUnexpectedExceptionNotThrown(\n            AssertionInfo const& info,\n            AssertionReaction& reaction\n    ) {\n        handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction);\n    }\n\n    void RunContext::handleUnexpectedInflightException(\n            AssertionInfo const& info,\n            std::string const& message,\n            AssertionReaction& reaction\n    ) {\n        m_lastAssertionInfo = info;\n\n        AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );\n        data.message = message;\n        AssertionResult assertionResult{ info, data };\n        assertionEnded( assertionResult );\n        populateReaction( reaction );\n    }\n\n    void RunContext::populateReaction( AssertionReaction& reaction ) {\n        reaction.shouldDebugBreak = m_config->shouldDebugBreak();\n        reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal);\n    }\n\n    void RunContext::handleIncomplete(\n            AssertionInfo const& info\n    ) {\n        m_lastAssertionInfo = info;\n\n        AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) );\n        data.message = \"Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE\";\n        AssertionResult assertionResult{ info, data };\n        assertionEnded( assertionResult );\n    }\n    void RunContext::handleNonExpr(\n            AssertionInfo const &info,\n            ResultWas::OfType resultType,\n            AssertionReaction &reaction\n    ) {\n        m_lastAssertionInfo = info;\n\n        AssertionResultData data( resultType, LazyExpression( false ) );\n        AssertionResult assertionResult{ info, data };\n        assertionEnded( assertionResult );\n\n        if( !assertionResult.isOk() )\n            populateReaction( reaction );\n    }\n\n    IResultCapture& getResultCapture() {\n        if (auto* capture = getCurrentContext().getResultCapture())\n            return *capture;\n        else\n            CATCH_INTERNAL_ERROR(\"No result capture instance\");\n    }\n\n    void seedRng(IConfig const& config) {\n        if (config.rngSeed() != 0) {\n            std::srand(config.rngSeed());\n            rng().seed(config.rngSeed());\n        }\n    }\n\n    unsigned int rngSeed() {\n        return getCurrentContext().getConfig()->rngSeed();\n    }\n\n}\n// end catch_run_context.cpp\n// start catch_section.cpp\n\nnamespace Catch {\n\n    Section::Section( SectionInfo const& info )\n    :   m_info( info ),\n        m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) )\n    {\n        m_timer.start();\n    }\n\n    Section::~Section() {\n        if( m_sectionIncluded ) {\n            SectionEndInfo endInfo{ m_info, m_assertions, m_timer.getElapsedSeconds() };\n            if( uncaught_exceptions() )\n                getResultCapture().sectionEndedEarly( endInfo );\n            else\n                getResultCapture().sectionEnded( endInfo );\n        }\n    }\n\n    // This indicates whether the section should be executed or not\n    Section::operator bool() const {\n        return m_sectionIncluded;\n    }\n\n} // end namespace Catch\n// end catch_section.cpp\n// start catch_section_info.cpp\n\nnamespace Catch {\n\n    SectionInfo::SectionInfo\n        (   SourceLineInfo const& _lineInfo,\n            std::string const& _name )\n    :   name( _name ),\n        lineInfo( _lineInfo )\n    {}\n\n} // end namespace Catch\n// end catch_section_info.cpp\n// start catch_session.cpp\n\n// start catch_session.h\n\n#include <memory>\n\nnamespace Catch {\n\n    class Session : NonCopyable {\n    public:\n\n        Session();\n        ~Session() override;\n\n        void showHelp() const;\n        void libIdentify();\n\n        int applyCommandLine( int argc, char const * const * argv );\n    #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)\n        int applyCommandLine( int argc, wchar_t const * const * argv );\n    #endif\n\n        void useConfigData( ConfigData const& configData );\n\n        template<typename CharT>\n        int run(int argc, CharT const * const argv[]) {\n            if (m_startupExceptions)\n                return 1;\n            int returnCode = applyCommandLine(argc, argv);\n            if (returnCode == 0)\n                returnCode = run();\n            return returnCode;\n        }\n\n        int run();\n\n        clara::Parser const& cli() const;\n        void cli( clara::Parser const& newParser );\n        ConfigData& configData();\n        Config& config();\n    private:\n        int runInternal();\n\n        clara::Parser m_cli;\n        ConfigData m_configData;\n        std::shared_ptr<Config> m_config;\n        bool m_startupExceptions = false;\n    };\n\n} // end namespace Catch\n\n// end catch_session.h\n// start catch_version.h\n\n#include <iosfwd>\n\nnamespace Catch {\n\n    // Versioning information\n    struct Version {\n        Version( Version const& ) = delete;\n        Version& operator=( Version const& ) = delete;\n        Version(    unsigned int _majorVersion,\n                    unsigned int _minorVersion,\n                    unsigned int _patchNumber,\n                    char const * const _branchName,\n                    unsigned int _buildNumber );\n\n        unsigned int const majorVersion;\n        unsigned int const minorVersion;\n        unsigned int const patchNumber;\n\n        // buildNumber is only used if branchName is not null\n        char const * const branchName;\n        unsigned int const buildNumber;\n\n        friend std::ostream& operator << ( std::ostream& os, Version const& version );\n    };\n\n    Version const& libraryVersion();\n}\n\n// end catch_version.h\n#include <cstdlib>\n#include <iomanip>\n#include <set>\n#include <iterator>\n\nnamespace Catch {\n\n    namespace {\n        const int MaxExitCode = 255;\n\n        IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) {\n            auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config);\n            CATCH_ENFORCE(reporter, \"No reporter registered with name: '\" << reporterName << \"'\");\n\n            return reporter;\n        }\n\n        IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) {\n            if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) {\n                return createReporter(config->getReporterName(), config);\n            }\n\n            // On older platforms, returning std::unique_ptr<ListeningReporter>\n            // when the return type is std::unique_ptr<IStreamingReporter>\n            // doesn't compile without a std::move call. However, this causes\n            // a warning on newer platforms. Thus, we have to work around\n            // it a bit and downcast the pointer manually.\n            auto ret = std::unique_ptr<IStreamingReporter>(new ListeningReporter);\n            auto& multi = static_cast<ListeningReporter&>(*ret);\n            auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners();\n            for (auto const& listener : listeners) {\n                multi.addListener(listener->create(Catch::ReporterConfig(config)));\n            }\n            multi.addReporter(createReporter(config->getReporterName(), config));\n            return ret;\n        }\n\n        class TestGroup {\n        public:\n            explicit TestGroup(std::shared_ptr<Config> const& config)\n            : m_config{config}\n            , m_context{config, makeReporter(config)}\n            {\n                auto const& allTestCases = getAllTestCasesSorted(*m_config);\n                m_matches = m_config->testSpec().matchesByFilter(allTestCases, *m_config);\n                auto const& invalidArgs = m_config->testSpec().getInvalidArgs();\n\n                if (m_matches.empty() && invalidArgs.empty()) {\n                    for (auto const& test : allTestCases)\n                        if (!test.isHidden())\n                            m_tests.emplace(&test);\n                } else {\n                    for (auto const& match : m_matches)\n                        m_tests.insert(match.tests.begin(), match.tests.end());\n                }\n            }\n\n            Totals execute() {\n                auto const& invalidArgs = m_config->testSpec().getInvalidArgs();\n                Totals totals;\n                m_context.testGroupStarting(m_config->name(), 1, 1);\n                for (auto const& testCase : m_tests) {\n                    if (!m_context.aborting())\n                        totals += m_context.runTest(*testCase);\n                    else\n                        m_context.reporter().skipTest(*testCase);\n                }\n\n                for (auto const& match : m_matches) {\n                    if (match.tests.empty()) {\n                        m_context.reporter().noMatchingTestCases(match.name);\n                        totals.error = -1;\n                    }\n                }\n\n                if (!invalidArgs.empty()) {\n                    for (auto const& invalidArg: invalidArgs)\n                         m_context.reporter().reportInvalidArguments(invalidArg);\n                }\n\n                m_context.testGroupEnded(m_config->name(), totals, 1, 1);\n                return totals;\n            }\n\n        private:\n            using Tests = std::set<TestCase const*>;\n\n            std::shared_ptr<Config> m_config;\n            RunContext m_context;\n            Tests m_tests;\n            TestSpec::Matches m_matches;\n        };\n\n        void applyFilenamesAsTags(Catch::IConfig const& config) {\n            auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config));\n            for (auto& testCase : tests) {\n                auto tags = testCase.tags;\n\n                std::string filename = testCase.lineInfo.file;\n                auto lastSlash = filename.find_last_of(\"\\\\/\");\n                if (lastSlash != std::string::npos) {\n                    filename.erase(0, lastSlash);\n                    filename[0] = '#';\n                }\n                else\n                {\n                    filename.insert(0, \"#\");\n                }\n\n                auto lastDot = filename.find_last_of('.');\n                if (lastDot != std::string::npos) {\n                    filename.erase(lastDot);\n                }\n\n                tags.push_back(std::move(filename));\n                setTags(testCase, tags);\n            }\n        }\n\n    } // anon namespace\n\n    Session::Session() {\n        static bool alreadyInstantiated = false;\n        if( alreadyInstantiated ) {\n            CATCH_TRY { CATCH_INTERNAL_ERROR( \"Only one instance of Catch::Session can ever be used\" ); }\n            CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); }\n        }\n\n        // There cannot be exceptions at startup in no-exception mode.\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n        const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions();\n        if ( !exceptions.empty() ) {\n            config();\n            getCurrentMutableContext().setConfig(m_config);\n\n            m_startupExceptions = true;\n            Colour colourGuard( Colour::Red );\n            Catch::cerr() << \"Errors occurred during startup!\" << '\\n';\n            // iterate over all exceptions and notify user\n            for ( const auto& ex_ptr : exceptions ) {\n                try {\n                    std::rethrow_exception(ex_ptr);\n                } catch ( std::exception const& ex ) {\n                    Catch::cerr() << Column( ex.what() ).indent(2) << '\\n';\n                }\n            }\n        }\n#endif\n\n        alreadyInstantiated = true;\n        m_cli = makeCommandLineParser( m_configData );\n    }\n    Session::~Session() {\n        Catch::cleanUp();\n    }\n\n    void Session::showHelp() const {\n        Catch::cout()\n                << \"\\nCatch v\" << libraryVersion() << \"\\n\"\n                << m_cli << std::endl\n                << \"For more detailed usage please see the project docs\\n\" << std::endl;\n    }\n    void Session::libIdentify() {\n        Catch::cout()\n                << std::left << std::setw(16) << \"description: \" << \"A Catch2 test executable\\n\"\n                << std::left << std::setw(16) << \"category: \" << \"testframework\\n\"\n                << std::left << std::setw(16) << \"framework: \" << \"Catch Test\\n\"\n                << std::left << std::setw(16) << \"version: \" << libraryVersion() << std::endl;\n    }\n\n    int Session::applyCommandLine( int argc, char const * const * argv ) {\n        if( m_startupExceptions )\n            return 1;\n\n        auto result = m_cli.parse( clara::Args( argc, argv ) );\n        if( !result ) {\n            config();\n            getCurrentMutableContext().setConfig(m_config);\n            Catch::cerr()\n                << Colour( Colour::Red )\n                << \"\\nError(s) in input:\\n\"\n                << Column( result.errorMessage() ).indent( 2 )\n                << \"\\n\\n\";\n            Catch::cerr() << \"Run with -? for usage\\n\" << std::endl;\n            return MaxExitCode;\n        }\n\n        if( m_configData.showHelp )\n            showHelp();\n        if( m_configData.libIdentify )\n            libIdentify();\n        m_config.reset();\n        return 0;\n    }\n\n#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE)\n    int Session::applyCommandLine( int argc, wchar_t const * const * argv ) {\n\n        char **utf8Argv = new char *[ argc ];\n\n        for ( int i = 0; i < argc; ++i ) {\n            int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr );\n\n            utf8Argv[ i ] = new char[ bufSize ];\n\n            WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr );\n        }\n\n        int returnCode = applyCommandLine( argc, utf8Argv );\n\n        for ( int i = 0; i < argc; ++i )\n            delete [] utf8Argv[ i ];\n\n        delete [] utf8Argv;\n\n        return returnCode;\n    }\n#endif\n\n    void Session::useConfigData( ConfigData const& configData ) {\n        m_configData = configData;\n        m_config.reset();\n    }\n\n    int Session::run() {\n        if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) {\n            Catch::cout() << \"...waiting for enter/ return before starting\" << std::endl;\n            static_cast<void>(std::getchar());\n        }\n        int exitCode = runInternal();\n        if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) {\n            Catch::cout() << \"...waiting for enter/ return before exiting, with code: \" << exitCode << std::endl;\n            static_cast<void>(std::getchar());\n        }\n        return exitCode;\n    }\n\n    clara::Parser const& Session::cli() const {\n        return m_cli;\n    }\n    void Session::cli( clara::Parser const& newParser ) {\n        m_cli = newParser;\n    }\n    ConfigData& Session::configData() {\n        return m_configData;\n    }\n    Config& Session::config() {\n        if( !m_config )\n            m_config = std::make_shared<Config>( m_configData );\n        return *m_config;\n    }\n\n    int Session::runInternal() {\n        if( m_startupExceptions )\n            return 1;\n\n        if (m_configData.showHelp || m_configData.libIdentify) {\n            return 0;\n        }\n\n        CATCH_TRY {\n            config(); // Force config to be constructed\n\n            seedRng( *m_config );\n\n            if( m_configData.filenamesAsTags )\n                applyFilenamesAsTags( *m_config );\n\n            // Handle list request\n            if( Option<std::size_t> listed = list( m_config ) )\n                return static_cast<int>( *listed );\n\n            TestGroup tests { m_config };\n            auto const totals = tests.execute();\n\n            if( m_config->warnAboutNoTests() && totals.error == -1 )\n                return 2;\n\n            // Note that on unices only the lower 8 bits are usually used, clamping\n            // the return value to 255 prevents false negative when some multiple\n            // of 256 tests has failed\n            return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed)));\n        }\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n        catch( std::exception& ex ) {\n            Catch::cerr() << ex.what() << std::endl;\n            return MaxExitCode;\n        }\n#endif\n    }\n\n} // end namespace Catch\n// end catch_session.cpp\n// start catch_singletons.cpp\n\n#include <vector>\n\nnamespace Catch {\n\n    namespace {\n        static auto getSingletons() -> std::vector<ISingleton*>*& {\n            static std::vector<ISingleton*>* g_singletons = nullptr;\n            if( !g_singletons )\n                g_singletons = new std::vector<ISingleton*>();\n            return g_singletons;\n        }\n    }\n\n    ISingleton::~ISingleton() {}\n\n    void addSingleton(ISingleton* singleton ) {\n        getSingletons()->push_back( singleton );\n    }\n    void cleanupSingletons() {\n        auto& singletons = getSingletons();\n        for( auto singleton : *singletons )\n            delete singleton;\n        delete singletons;\n        singletons = nullptr;\n    }\n\n} // namespace Catch\n// end catch_singletons.cpp\n// start catch_startup_exception_registry.cpp\n\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\nnamespace Catch {\nvoid StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept {\n        CATCH_TRY {\n            m_exceptions.push_back(exception);\n        } CATCH_CATCH_ALL {\n            // If we run out of memory during start-up there's really not a lot more we can do about it\n            std::terminate();\n        }\n    }\n\n    std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept {\n        return m_exceptions;\n    }\n\n} // end namespace Catch\n#endif\n// end catch_startup_exception_registry.cpp\n// start catch_stream.cpp\n\n#include <cstdio>\n#include <iostream>\n#include <fstream>\n#include <sstream>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\n\n    Catch::IStream::~IStream() = default;\n\n    namespace Detail { namespace {\n        template<typename WriterF, std::size_t bufferSize=256>\n        class StreamBufImpl : public std::streambuf {\n            char data[bufferSize];\n            WriterF m_writer;\n\n        public:\n            StreamBufImpl() {\n                setp( data, data + sizeof(data) );\n            }\n\n            ~StreamBufImpl() noexcept {\n                StreamBufImpl::sync();\n            }\n\n        private:\n            int overflow( int c ) override {\n                sync();\n\n                if( c != EOF ) {\n                    if( pbase() == epptr() )\n                        m_writer( std::string( 1, static_cast<char>( c ) ) );\n                    else\n                        sputc( static_cast<char>( c ) );\n                }\n                return 0;\n            }\n\n            int sync() override {\n                if( pbase() != pptr() ) {\n                    m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) );\n                    setp( pbase(), epptr() );\n                }\n                return 0;\n            }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        struct OutputDebugWriter {\n\n            void operator()( std::string const&str ) {\n                writeToDebugConsole( str );\n            }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        class FileStream : public IStream {\n            mutable std::ofstream m_ofs;\n        public:\n            FileStream( StringRef filename ) {\n                m_ofs.open( filename.c_str() );\n                CATCH_ENFORCE( !m_ofs.fail(), \"Unable to open file: '\" << filename << \"'\" );\n            }\n            ~FileStream() override = default;\n        public: // IStream\n            std::ostream& stream() const override {\n                return m_ofs;\n            }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        class CoutStream : public IStream {\n            mutable std::ostream m_os;\n        public:\n            // Store the streambuf from cout up-front because\n            // cout may get redirected when running tests\n            CoutStream() : m_os( Catch::cout().rdbuf() ) {}\n            ~CoutStream() override = default;\n\n        public: // IStream\n            std::ostream& stream() const override { return m_os; }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        class DebugOutStream : public IStream {\n            std::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf;\n            mutable std::ostream m_os;\n        public:\n            DebugOutStream()\n            :   m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ),\n                m_os( m_streamBuf.get() )\n            {}\n\n            ~DebugOutStream() override = default;\n\n        public: // IStream\n            std::ostream& stream() const override { return m_os; }\n        };\n\n    }} // namespace anon::detail\n\n    ///////////////////////////////////////////////////////////////////////////\n\n    auto makeStream( StringRef const &filename ) -> IStream const* {\n        if( filename.empty() )\n            return new Detail::CoutStream();\n        else if( filename[0] == '%' ) {\n            if( filename == \"%debug\" )\n                return new Detail::DebugOutStream();\n            else\n                CATCH_ERROR( \"Unrecognised stream: '\" << filename << \"'\" );\n        }\n        else\n            return new Detail::FileStream( filename );\n    }\n\n    // This class encapsulates the idea of a pool of ostringstreams that can be reused.\n    struct StringStreams {\n        std::vector<std::unique_ptr<std::ostringstream>> m_streams;\n        std::vector<std::size_t> m_unused;\n        std::ostringstream m_referenceStream; // Used for copy state/ flags from\n\n        auto add() -> std::size_t {\n            if( m_unused.empty() ) {\n                m_streams.push_back( std::unique_ptr<std::ostringstream>( new std::ostringstream ) );\n                return m_streams.size()-1;\n            }\n            else {\n                auto index = m_unused.back();\n                m_unused.pop_back();\n                return index;\n            }\n        }\n\n        void release( std::size_t index ) {\n            m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state\n            m_unused.push_back(index);\n        }\n    };\n\n    ReusableStringStream::ReusableStringStream()\n    :   m_index( Singleton<StringStreams>::getMutable().add() ),\n        m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() )\n    {}\n\n    ReusableStringStream::~ReusableStringStream() {\n        static_cast<std::ostringstream*>( m_oss )->str(\"\");\n        m_oss->clear();\n        Singleton<StringStreams>::getMutable().release( m_index );\n    }\n\n    auto ReusableStringStream::str() const -> std::string {\n        return static_cast<std::ostringstream*>( m_oss )->str();\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n\n#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions\n    std::ostream& cout() { return std::cout; }\n    std::ostream& cerr() { return std::cerr; }\n    std::ostream& clog() { return std::clog; }\n#endif\n}\n// end catch_stream.cpp\n// start catch_string_manip.cpp\n\n#include <algorithm>\n#include <ostream>\n#include <cstring>\n#include <cctype>\n#include <vector>\n\nnamespace Catch {\n\n    namespace {\n        char toLowerCh(char c) {\n            return static_cast<char>( std::tolower( static_cast<unsigned char>(c) ) );\n        }\n    }\n\n    bool startsWith( std::string const& s, std::string const& prefix ) {\n        return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());\n    }\n    bool startsWith( std::string const& s, char prefix ) {\n        return !s.empty() && s[0] == prefix;\n    }\n    bool endsWith( std::string const& s, std::string const& suffix ) {\n        return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());\n    }\n    bool endsWith( std::string const& s, char suffix ) {\n        return !s.empty() && s[s.size()-1] == suffix;\n    }\n    bool contains( std::string const& s, std::string const& infix ) {\n        return s.find( infix ) != std::string::npos;\n    }\n    void toLowerInPlace( std::string& s ) {\n        std::transform( s.begin(), s.end(), s.begin(), toLowerCh );\n    }\n    std::string toLower( std::string const& s ) {\n        std::string lc = s;\n        toLowerInPlace( lc );\n        return lc;\n    }\n    std::string trim( std::string const& str ) {\n        static char const* whitespaceChars = \"\\n\\r\\t \";\n        std::string::size_type start = str.find_first_not_of( whitespaceChars );\n        std::string::size_type end = str.find_last_not_of( whitespaceChars );\n\n        return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string();\n    }\n\n    StringRef trim(StringRef ref) {\n        const auto is_ws = [](char c) {\n            return c == ' ' || c == '\\t' || c == '\\n' || c == '\\r';\n        };\n        size_t real_begin = 0;\n        while (real_begin < ref.size() && is_ws(ref[real_begin])) { ++real_begin; }\n        size_t real_end = ref.size();\n        while (real_end > real_begin && is_ws(ref[real_end - 1])) { --real_end; }\n\n        return ref.substr(real_begin, real_end - real_begin);\n    }\n\n    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {\n        bool replaced = false;\n        std::size_t i = str.find( replaceThis );\n        while( i != std::string::npos ) {\n            replaced = true;\n            str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() );\n            if( i < str.size()-withThis.size() )\n                i = str.find( replaceThis, i+withThis.size() );\n            else\n                i = std::string::npos;\n        }\n        return replaced;\n    }\n\n    std::vector<StringRef> splitStringRef( StringRef str, char delimiter ) {\n        std::vector<StringRef> subStrings;\n        std::size_t start = 0;\n        for(std::size_t pos = 0; pos < str.size(); ++pos ) {\n            if( str[pos] == delimiter ) {\n                if( pos - start > 1 )\n                    subStrings.push_back( str.substr( start, pos-start ) );\n                start = pos+1;\n            }\n        }\n        if( start < str.size() )\n            subStrings.push_back( str.substr( start, str.size()-start ) );\n        return subStrings;\n    }\n\n    pluralise::pluralise( std::size_t count, std::string const& label )\n    :   m_count( count ),\n        m_label( label )\n    {}\n\n    std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) {\n        os << pluraliser.m_count << ' ' << pluraliser.m_label;\n        if( pluraliser.m_count != 1 )\n            os << 's';\n        return os;\n    }\n\n}\n// end catch_string_manip.cpp\n// start catch_stringref.cpp\n\n#include <algorithm>\n#include <ostream>\n#include <cstring>\n#include <cstdint>\n\nnamespace Catch {\n    StringRef::StringRef( char const* rawChars ) noexcept\n    : StringRef( rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars) ) )\n    {}\n\n    auto StringRef::c_str() const -> char const* {\n        CATCH_ENFORCE(isNullTerminated(), \"Called StringRef::c_str() on a non-null-terminated instance\");\n        return m_start;\n    }\n    auto StringRef::data() const noexcept -> char const* {\n        return m_start;\n    }\n\n    auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef {\n        if (start < m_size) {\n            return StringRef(m_start + start, (std::min)(m_size - start, size));\n        } else {\n            return StringRef();\n        }\n    }\n    auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool {\n        return m_size == other.m_size\n            && (std::memcmp( m_start, other.m_start, m_size ) == 0);\n    }\n\n    auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& {\n        return os.write(str.data(), str.size());\n    }\n\n    auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& {\n        lhs.append(rhs.data(), rhs.size());\n        return lhs;\n    }\n\n} // namespace Catch\n// end catch_stringref.cpp\n// start catch_tag_alias.cpp\n\nnamespace Catch {\n    TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {}\n}\n// end catch_tag_alias.cpp\n// start catch_tag_alias_autoregistrar.cpp\n\nnamespace Catch {\n\n    RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) {\n        CATCH_TRY {\n            getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo);\n        } CATCH_CATCH_ALL {\n            // Do not throw when constructing global objects, instead register the exception to be processed later\n            getMutableRegistryHub().registerStartupException();\n        }\n    }\n\n}\n// end catch_tag_alias_autoregistrar.cpp\n// start catch_tag_alias_registry.cpp\n\n#include <sstream>\n\nnamespace Catch {\n\n    TagAliasRegistry::~TagAliasRegistry() {}\n\n    TagAlias const* TagAliasRegistry::find( std::string const& alias ) const {\n        auto it = m_registry.find( alias );\n        if( it != m_registry.end() )\n            return &(it->second);\n        else\n            return nullptr;\n    }\n\n    std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {\n        std::string expandedTestSpec = unexpandedTestSpec;\n        for( auto const& registryKvp : m_registry ) {\n            std::size_t pos = expandedTestSpec.find( registryKvp.first );\n            if( pos != std::string::npos ) {\n                expandedTestSpec =  expandedTestSpec.substr( 0, pos ) +\n                                    registryKvp.second.tag +\n                                    expandedTestSpec.substr( pos + registryKvp.first.size() );\n            }\n        }\n        return expandedTestSpec;\n    }\n\n    void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) {\n        CATCH_ENFORCE( startsWith(alias, \"[@\") && endsWith(alias, ']'),\n                      \"error: tag alias, '\" << alias << \"' is not of the form [@alias name].\\n\" << lineInfo );\n\n        CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second,\n                      \"error: tag alias, '\" << alias << \"' already registered.\\n\"\n                      << \"\\tFirst seen at: \" << find(alias)->lineInfo << \"\\n\"\n                      << \"\\tRedefined at: \" << lineInfo );\n    }\n\n    ITagAliasRegistry::~ITagAliasRegistry() {}\n\n    ITagAliasRegistry const& ITagAliasRegistry::get() {\n        return getRegistryHub().getTagAliasRegistry();\n    }\n\n} // end namespace Catch\n// end catch_tag_alias_registry.cpp\n// start catch_test_case_info.cpp\n\n#include <cctype>\n#include <exception>\n#include <algorithm>\n#include <sstream>\n\nnamespace Catch {\n\n    namespace {\n        TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {\n            if( startsWith( tag, '.' ) ||\n                tag == \"!hide\" )\n                return TestCaseInfo::IsHidden;\n            else if( tag == \"!throws\" )\n                return TestCaseInfo::Throws;\n            else if( tag == \"!shouldfail\" )\n                return TestCaseInfo::ShouldFail;\n            else if( tag == \"!mayfail\" )\n                return TestCaseInfo::MayFail;\n            else if( tag == \"!nonportable\" )\n                return TestCaseInfo::NonPortable;\n            else if( tag == \"!benchmark\" )\n                return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden );\n            else\n                return TestCaseInfo::None;\n        }\n        bool isReservedTag( std::string const& tag ) {\n            return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) );\n        }\n        void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {\n            CATCH_ENFORCE( !isReservedTag(tag),\n                          \"Tag name: [\" << tag << \"] is not allowed.\\n\"\n                          << \"Tag names starting with non alphanumeric characters are reserved\\n\"\n                          << _lineInfo );\n        }\n    }\n\n    TestCase makeTestCase(  ITestInvoker* _testCase,\n                            std::string const& _className,\n                            NameAndTags const& nameAndTags,\n                            SourceLineInfo const& _lineInfo )\n    {\n        bool isHidden = false;\n\n        // Parse out tags\n        std::vector<std::string> tags;\n        std::string desc, tag;\n        bool inTag = false;\n        for (char c : nameAndTags.tags) {\n            if( !inTag ) {\n                if( c == '[' )\n                    inTag = true;\n                else\n                    desc += c;\n            }\n            else {\n                if( c == ']' ) {\n                    TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );\n                    if( ( prop & TestCaseInfo::IsHidden ) != 0 )\n                        isHidden = true;\n                    else if( prop == TestCaseInfo::None )\n                        enforceNotReservedTag( tag, _lineInfo );\n\n                    // Merged hide tags like `[.approvals]` should be added as\n                    // `[.][approvals]`. The `[.]` is added at later point, so\n                    // we only strip the prefix\n                    if (startsWith(tag, '.') && tag.size() > 1) {\n                        tag.erase(0, 1);\n                    }\n                    tags.push_back( tag );\n                    tag.clear();\n                    inTag = false;\n                }\n                else\n                    tag += c;\n            }\n        }\n        if( isHidden ) {\n            // Add all \"hidden\" tags to make them behave identically\n            tags.insert( tags.end(), { \".\", \"!hide\" } );\n        }\n\n        TestCaseInfo info( static_cast<std::string>(nameAndTags.name), _className, desc, tags, _lineInfo );\n        return TestCase( _testCase, std::move(info) );\n    }\n\n    void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) {\n        std::sort(begin(tags), end(tags));\n        tags.erase(std::unique(begin(tags), end(tags)), end(tags));\n        testCaseInfo.lcaseTags.clear();\n\n        for( auto const& tag : tags ) {\n            std::string lcaseTag = toLower( tag );\n            testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) );\n            testCaseInfo.lcaseTags.push_back( lcaseTag );\n        }\n        testCaseInfo.tags = std::move(tags);\n    }\n\n    TestCaseInfo::TestCaseInfo( std::string const& _name,\n                                std::string const& _className,\n                                std::string const& _description,\n                                std::vector<std::string> const& _tags,\n                                SourceLineInfo const& _lineInfo )\n    :   name( _name ),\n        className( _className ),\n        description( _description ),\n        lineInfo( _lineInfo ),\n        properties( None )\n    {\n        setTags( *this, _tags );\n    }\n\n    bool TestCaseInfo::isHidden() const {\n        return ( properties & IsHidden ) != 0;\n    }\n    bool TestCaseInfo::throws() const {\n        return ( properties & Throws ) != 0;\n    }\n    bool TestCaseInfo::okToFail() const {\n        return ( properties & (ShouldFail | MayFail ) ) != 0;\n    }\n    bool TestCaseInfo::expectedToFail() const {\n        return ( properties & (ShouldFail ) ) != 0;\n    }\n\n    std::string TestCaseInfo::tagsAsString() const {\n        std::string ret;\n        // '[' and ']' per tag\n        std::size_t full_size = 2 * tags.size();\n        for (const auto& tag : tags) {\n            full_size += tag.size();\n        }\n        ret.reserve(full_size);\n        for (const auto& tag : tags) {\n            ret.push_back('[');\n            ret.append(tag);\n            ret.push_back(']');\n        }\n\n        return ret;\n    }\n\n    TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {}\n\n    TestCase TestCase::withName( std::string const& _newName ) const {\n        TestCase other( *this );\n        other.name = _newName;\n        return other;\n    }\n\n    void TestCase::invoke() const {\n        test->invoke();\n    }\n\n    bool TestCase::operator == ( TestCase const& other ) const {\n        return  test.get() == other.test.get() &&\n                name == other.name &&\n                className == other.className;\n    }\n\n    bool TestCase::operator < ( TestCase const& other ) const {\n        return name < other.name;\n    }\n\n    TestCaseInfo const& TestCase::getTestCaseInfo() const\n    {\n        return *this;\n    }\n\n} // end namespace Catch\n// end catch_test_case_info.cpp\n// start catch_test_case_registry_impl.cpp\n\n#include <algorithm>\n#include <sstream>\n\nnamespace Catch {\n\n    namespace {\n        struct TestHasher {\n            using hash_t = uint64_t;\n\n            explicit TestHasher( hash_t hashSuffix ):\n                m_hashSuffix{ hashSuffix } {}\n\n            uint32_t operator()( TestCase const& t ) const {\n                // FNV-1a hash with multiplication fold.\n                const hash_t prime = 1099511628211u;\n                hash_t hash = 14695981039346656037u;\n                for ( const char c : t.name ) {\n                    hash ^= c;\n                    hash *= prime;\n                }\n                hash ^= m_hashSuffix;\n                hash *= prime;\n                const uint32_t low{ static_cast<uint32_t>( hash ) };\n                const uint32_t high{ static_cast<uint32_t>( hash >> 32 ) };\n                return low * high;\n            }\n\n        private:\n            hash_t m_hashSuffix;\n        };\n    } // end unnamed namespace\n\n    std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) {\n        switch( config.runOrder() ) {\n            case RunTests::InDeclarationOrder:\n                // already in declaration order\n                break;\n\n            case RunTests::InLexicographicalOrder: {\n                std::vector<TestCase> sorted = unsortedTestCases;\n                std::sort( sorted.begin(), sorted.end() );\n                return sorted;\n            }\n\n            case RunTests::InRandomOrder: {\n                seedRng( config );\n                TestHasher h{ config.rngSeed() };\n\n                using hashedTest = std::pair<TestHasher::hash_t, TestCase const*>;\n                std::vector<hashedTest> indexed_tests;\n                indexed_tests.reserve( unsortedTestCases.size() );\n\n                for (auto const& testCase : unsortedTestCases) {\n                    indexed_tests.emplace_back(h(testCase), &testCase);\n                }\n\n                std::sort(indexed_tests.begin(), indexed_tests.end(),\n                          [](hashedTest const& lhs, hashedTest const& rhs) {\n                          if (lhs.first == rhs.first) {\n                              return lhs.second->name < rhs.second->name;\n                          }\n                          return lhs.first < rhs.first;\n                });\n\n                std::vector<TestCase> sorted;\n                sorted.reserve( indexed_tests.size() );\n\n                for (auto const& hashed : indexed_tests) {\n                    sorted.emplace_back(*hashed.second);\n                }\n\n                return sorted;\n            }\n        }\n        return unsortedTestCases;\n    }\n\n    bool isThrowSafe( TestCase const& testCase, IConfig const& config ) {\n        return !testCase.throws() || config.allowThrows();\n    }\n\n    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) {\n        return testSpec.matches( testCase ) && isThrowSafe( testCase, config );\n    }\n\n    void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) {\n        std::set<TestCase> seenFunctions;\n        for( auto const& function : functions ) {\n            auto prev = seenFunctions.insert( function );\n            CATCH_ENFORCE( prev.second,\n                    \"error: TEST_CASE( \\\"\" << function.name << \"\\\" ) already defined.\\n\"\n                    << \"\\tFirst seen at \" << prev.first->getTestCaseInfo().lineInfo << \"\\n\"\n                    << \"\\tRedefined at \" << function.getTestCaseInfo().lineInfo );\n        }\n    }\n\n    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) {\n        std::vector<TestCase> filtered;\n        filtered.reserve( testCases.size() );\n        for (auto const& testCase : testCases) {\n            if ((!testSpec.hasFilters() && !testCase.isHidden()) ||\n                (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) {\n                filtered.push_back(testCase);\n            }\n        }\n        return filtered;\n    }\n    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) {\n        return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config );\n    }\n\n    void TestRegistry::registerTest( TestCase const& testCase ) {\n        std::string name = testCase.getTestCaseInfo().name;\n        if( name.empty() ) {\n            ReusableStringStream rss;\n            rss << \"Anonymous test case \" << ++m_unnamedCount;\n            return registerTest( testCase.withName( rss.str() ) );\n        }\n        m_functions.push_back( testCase );\n    }\n\n    std::vector<TestCase> const& TestRegistry::getAllTests() const {\n        return m_functions;\n    }\n    std::vector<TestCase> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const {\n        if( m_sortedFunctions.empty() )\n            enforceNoDuplicateTestCases( m_functions );\n\n        if(  m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) {\n            m_sortedFunctions = sortTests( config, m_functions );\n            m_currentSortOrder = config.runOrder();\n        }\n        return m_sortedFunctions;\n    }\n\n    ///////////////////////////////////////////////////////////////////////////\n    TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {}\n\n    void TestInvokerAsFunction::invoke() const {\n        m_testAsFunction();\n    }\n\n    std::string extractClassName( StringRef const& classOrQualifiedMethodName ) {\n        std::string className(classOrQualifiedMethodName);\n        if( startsWith( className, '&' ) )\n        {\n            std::size_t lastColons = className.rfind( \"::\" );\n            std::size_t penultimateColons = className.rfind( \"::\", lastColons-1 );\n            if( penultimateColons == std::string::npos )\n                penultimateColons = 1;\n            className = className.substr( penultimateColons, lastColons-penultimateColons );\n        }\n        return className;\n    }\n\n} // end namespace Catch\n// end catch_test_case_registry_impl.cpp\n// start catch_test_case_tracker.cpp\n\n#include <algorithm>\n#include <cassert>\n#include <stdexcept>\n#include <memory>\n#include <sstream>\n\n#if defined(__clang__)\n#    pragma clang diagnostic push\n#    pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#endif\n\nnamespace Catch {\nnamespace TestCaseTracking {\n\n    NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location )\n    :   name( _name ),\n        location( _location )\n    {}\n\n    ITracker::~ITracker() = default;\n\n    ITracker& TrackerContext::startRun() {\n        m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( \"{root}\", CATCH_INTERNAL_LINEINFO ), *this, nullptr );\n        m_currentTracker = nullptr;\n        m_runState = Executing;\n        return *m_rootTracker;\n    }\n\n    void TrackerContext::endRun() {\n        m_rootTracker.reset();\n        m_currentTracker = nullptr;\n        m_runState = NotStarted;\n    }\n\n    void TrackerContext::startCycle() {\n        m_currentTracker = m_rootTracker.get();\n        m_runState = Executing;\n    }\n    void TrackerContext::completeCycle() {\n        m_runState = CompletedCycle;\n    }\n\n    bool TrackerContext::completedCycle() const {\n        return m_runState == CompletedCycle;\n    }\n    ITracker& TrackerContext::currentTracker() {\n        return *m_currentTracker;\n    }\n    void TrackerContext::setCurrentTracker( ITracker* tracker ) {\n        m_currentTracker = tracker;\n    }\n\n    TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ):\n        ITracker(nameAndLocation),\n        m_ctx( ctx ),\n        m_parent( parent )\n    {}\n\n    bool TrackerBase::isComplete() const {\n        return m_runState == CompletedSuccessfully || m_runState == Failed;\n    }\n    bool TrackerBase::isSuccessfullyCompleted() const {\n        return m_runState == CompletedSuccessfully;\n    }\n    bool TrackerBase::isOpen() const {\n        return m_runState != NotStarted && !isComplete();\n    }\n    bool TrackerBase::hasChildren() const {\n        return !m_children.empty();\n    }\n\n    void TrackerBase::addChild( ITrackerPtr const& child ) {\n        m_children.push_back( child );\n    }\n\n    ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) {\n        auto it = std::find_if( m_children.begin(), m_children.end(),\n            [&nameAndLocation]( ITrackerPtr const& tracker ){\n                return\n                    tracker->nameAndLocation().location == nameAndLocation.location &&\n                    tracker->nameAndLocation().name == nameAndLocation.name;\n            } );\n        return( it != m_children.end() )\n            ? *it\n            : nullptr;\n    }\n    ITracker& TrackerBase::parent() {\n        assert( m_parent ); // Should always be non-null except for root\n        return *m_parent;\n    }\n\n    void TrackerBase::openChild() {\n        if( m_runState != ExecutingChildren ) {\n            m_runState = ExecutingChildren;\n            if( m_parent )\n                m_parent->openChild();\n        }\n    }\n\n    bool TrackerBase::isSectionTracker() const { return false; }\n    bool TrackerBase::isGeneratorTracker() const { return false; }\n\n    void TrackerBase::open() {\n        m_runState = Executing;\n        moveToThis();\n        if( m_parent )\n            m_parent->openChild();\n    }\n\n    void TrackerBase::close() {\n\n        // Close any still open children (e.g. generators)\n        while( &m_ctx.currentTracker() != this )\n            m_ctx.currentTracker().close();\n\n        switch( m_runState ) {\n            case NeedsAnotherRun:\n                break;\n\n            case Executing:\n                m_runState = CompletedSuccessfully;\n                break;\n            case ExecutingChildren:\n                if( std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const& t){ return t->isComplete(); }) )\n                    m_runState = CompletedSuccessfully;\n                break;\n\n            case NotStarted:\n            case CompletedSuccessfully:\n            case Failed:\n                CATCH_INTERNAL_ERROR( \"Illogical state: \" << m_runState );\n\n            default:\n                CATCH_INTERNAL_ERROR( \"Unknown state: \" << m_runState );\n        }\n        moveToParent();\n        m_ctx.completeCycle();\n    }\n    void TrackerBase::fail() {\n        m_runState = Failed;\n        if( m_parent )\n            m_parent->markAsNeedingAnotherRun();\n        moveToParent();\n        m_ctx.completeCycle();\n    }\n    void TrackerBase::markAsNeedingAnotherRun() {\n        m_runState = NeedsAnotherRun;\n    }\n\n    void TrackerBase::moveToParent() {\n        assert( m_parent );\n        m_ctx.setCurrentTracker( m_parent );\n    }\n    void TrackerBase::moveToThis() {\n        m_ctx.setCurrentTracker( this );\n    }\n\n    SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )\n    :   TrackerBase( nameAndLocation, ctx, parent ),\n        m_trimmed_name(trim(nameAndLocation.name))\n    {\n        if( parent ) {\n            while( !parent->isSectionTracker() )\n                parent = &parent->parent();\n\n            SectionTracker& parentSection = static_cast<SectionTracker&>( *parent );\n            addNextFilters( parentSection.m_filters );\n        }\n    }\n\n    bool SectionTracker::isComplete() const {\n        bool complete = true;\n\n        if (m_filters.empty()\n            || m_filters[0] == \"\"\n            || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) {\n            complete = TrackerBase::isComplete();\n        }\n        return complete;\n    }\n\n    bool SectionTracker::isSectionTracker() const { return true; }\n\n    SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) {\n        std::shared_ptr<SectionTracker> section;\n\n        ITracker& currentTracker = ctx.currentTracker();\n        if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) {\n            assert( childTracker );\n            assert( childTracker->isSectionTracker() );\n            section = std::static_pointer_cast<SectionTracker>( childTracker );\n        }\n        else {\n            section = std::make_shared<SectionTracker>( nameAndLocation, ctx, &currentTracker );\n            currentTracker.addChild( section );\n        }\n        if( !ctx.completedCycle() )\n            section->tryOpen();\n        return *section;\n    }\n\n    void SectionTracker::tryOpen() {\n        if( !isComplete() )\n            open();\n    }\n\n    void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) {\n        if( !filters.empty() ) {\n            m_filters.reserve( m_filters.size() + filters.size() + 2 );\n            m_filters.emplace_back(\"\"); // Root - should never be consulted\n            m_filters.emplace_back(\"\"); // Test Case - not a section filter\n            m_filters.insert( m_filters.end(), filters.begin(), filters.end() );\n        }\n    }\n    void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) {\n        if( filters.size() > 1 )\n            m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() );\n    }\n\n    std::vector<std::string> const& SectionTracker::getFilters() const {\n        return m_filters;\n    }\n\n    std::string const& SectionTracker::trimmedName() const {\n        return m_trimmed_name;\n    }\n\n} // namespace TestCaseTracking\n\nusing TestCaseTracking::ITracker;\nusing TestCaseTracking::TrackerContext;\nusing TestCaseTracking::SectionTracker;\n\n} // namespace Catch\n\n#if defined(__clang__)\n#    pragma clang diagnostic pop\n#endif\n// end catch_test_case_tracker.cpp\n// start catch_test_registry.cpp\n\nnamespace Catch {\n\n    auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* {\n        return new(std::nothrow) TestInvokerAsFunction( testAsFunction );\n    }\n\n    NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {}\n\n    AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept {\n        CATCH_TRY {\n            getMutableRegistryHub()\n                    .registerTest(\n                        makeTestCase(\n                            invoker,\n                            extractClassName( classOrMethod ),\n                            nameAndTags,\n                            lineInfo));\n        } CATCH_CATCH_ALL {\n            // Do not throw when constructing global objects, instead register the exception to be processed later\n            getMutableRegistryHub().registerStartupException();\n        }\n    }\n\n    AutoReg::~AutoReg() = default;\n}\n// end catch_test_registry.cpp\n// start catch_test_spec.cpp\n\n#include <algorithm>\n#include <string>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\n\n    TestSpec::Pattern::Pattern( std::string const& name )\n    : m_name( name )\n    {}\n\n    TestSpec::Pattern::~Pattern() = default;\n\n    std::string const& TestSpec::Pattern::name() const {\n        return m_name;\n    }\n\n    TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString )\n    : Pattern( filterString )\n    , m_wildcardPattern( toLower( name ), CaseSensitive::No )\n    {}\n\n    bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const {\n        return m_wildcardPattern.matches( testCase.name );\n    }\n\n    TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString )\n    : Pattern( filterString )\n    , m_tag( toLower( tag ) )\n    {}\n\n    bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const {\n        return std::find(begin(testCase.lcaseTags),\n                         end(testCase.lcaseTags),\n                         m_tag) != end(testCase.lcaseTags);\n    }\n\n    TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern )\n    : Pattern( underlyingPattern->name() )\n    , m_underlyingPattern( underlyingPattern )\n    {}\n\n    bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const {\n        return !m_underlyingPattern->matches( testCase );\n    }\n\n    bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {\n        return std::all_of( m_patterns.begin(), m_patterns.end(), [&]( PatternPtr const& p ){ return p->matches( testCase ); } );\n    }\n\n    std::string TestSpec::Filter::name() const {\n        std::string name;\n        for( auto const& p : m_patterns )\n            name += p->name();\n        return name;\n    }\n\n    bool TestSpec::hasFilters() const {\n        return !m_filters.empty();\n    }\n\n    bool TestSpec::matches( TestCaseInfo const& testCase ) const {\n        return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } );\n    }\n\n    TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCase> const& testCases, IConfig const& config ) const\n    {\n        Matches matches( m_filters.size() );\n        std::transform( m_filters.begin(), m_filters.end(), matches.begin(), [&]( Filter const& filter ){\n            std::vector<TestCase const*> currentMatches;\n            for( auto const& test : testCases )\n                if( isThrowSafe( test, config ) && filter.matches( test ) )\n                    currentMatches.emplace_back( &test );\n            return FilterMatch{ filter.name(), currentMatches };\n        } );\n        return matches;\n    }\n\n    const TestSpec::vectorStrings& TestSpec::getInvalidArgs() const{\n        return  (m_invalidArgs);\n    }\n\n}\n// end catch_test_spec.cpp\n// start catch_test_spec_parser.cpp\n\nnamespace Catch {\n\n    TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}\n\n    TestSpecParser& TestSpecParser::parse( std::string const& arg ) {\n        m_mode = None;\n        m_exclusion = false;\n        m_arg = m_tagAliases->expandAliases( arg );\n        m_escapeChars.clear();\n        m_substring.reserve(m_arg.size());\n        m_patternName.reserve(m_arg.size());\n        m_realPatternPos = 0;\n\n        for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )\n          //if visitChar fails\n           if( !visitChar( m_arg[m_pos] ) ){\n               m_testSpec.m_invalidArgs.push_back(arg);\n               break;\n           }\n        endMode();\n        return *this;\n    }\n    TestSpec TestSpecParser::testSpec() {\n        addFilter();\n        return m_testSpec;\n    }\n    bool TestSpecParser::visitChar( char c ) {\n        if( (m_mode != EscapedName) && (c == '\\\\') ) {\n            escape();\n            addCharToPattern(c);\n            return true;\n        }else if((m_mode != EscapedName) && (c == ',') )  {\n            return separate();\n        }\n\n        switch( m_mode ) {\n        case None:\n            if( processNoneChar( c ) )\n                return true;\n            break;\n        case Name:\n            processNameChar( c );\n            break;\n        case EscapedName:\n            endMode();\n            addCharToPattern(c);\n            return true;\n        default:\n        case Tag:\n        case QuotedName:\n            if( processOtherChar( c ) )\n                return true;\n            break;\n        }\n\n        m_substring += c;\n        if( !isControlChar( c ) ) {\n            m_patternName += c;\n            m_realPatternPos++;\n        }\n        return true;\n    }\n    // Two of the processing methods return true to signal the caller to return\n    // without adding the given character to the current pattern strings\n    bool TestSpecParser::processNoneChar( char c ) {\n        switch( c ) {\n        case ' ':\n            return true;\n        case '~':\n            m_exclusion = true;\n            return false;\n        case '[':\n            startNewMode( Tag );\n            return false;\n        case '\"':\n            startNewMode( QuotedName );\n            return false;\n        default:\n            startNewMode( Name );\n            return false;\n        }\n    }\n    void TestSpecParser::processNameChar( char c ) {\n        if( c == '[' ) {\n            if( m_substring == \"exclude:\" )\n                m_exclusion = true;\n            else\n                endMode();\n            startNewMode( Tag );\n        }\n    }\n    bool TestSpecParser::processOtherChar( char c ) {\n        if( !isControlChar( c ) )\n            return false;\n        m_substring += c;\n        endMode();\n        return true;\n    }\n    void TestSpecParser::startNewMode( Mode mode ) {\n        m_mode = mode;\n    }\n    void TestSpecParser::endMode() {\n        switch( m_mode ) {\n        case Name:\n        case QuotedName:\n            return addNamePattern();\n        case Tag:\n            return addTagPattern();\n        case EscapedName:\n            revertBackToLastMode();\n            return;\n        case None:\n        default:\n            return startNewMode( None );\n        }\n    }\n    void TestSpecParser::escape() {\n        saveLastMode();\n        m_mode = EscapedName;\n        m_escapeChars.push_back(m_realPatternPos);\n    }\n    bool TestSpecParser::isControlChar( char c ) const {\n        switch( m_mode ) {\n            default:\n                return false;\n            case None:\n                return c == '~';\n            case Name:\n                return c == '[';\n            case EscapedName:\n                return true;\n            case QuotedName:\n                return c == '\"';\n            case Tag:\n                return c == '[' || c == ']';\n        }\n    }\n\n    void TestSpecParser::addFilter() {\n        if( !m_currentFilter.m_patterns.empty() ) {\n            m_testSpec.m_filters.push_back( m_currentFilter );\n            m_currentFilter = TestSpec::Filter();\n        }\n    }\n\n    void TestSpecParser::saveLastMode() {\n      lastMode = m_mode;\n    }\n\n    void TestSpecParser::revertBackToLastMode() {\n      m_mode = lastMode;\n    }\n\n    bool TestSpecParser::separate() {\n      if( (m_mode==QuotedName) || (m_mode==Tag) ){\n         //invalid argument, signal failure to previous scope.\n         m_mode = None;\n         m_pos = m_arg.size();\n         m_substring.clear();\n         m_patternName.clear();\n         m_realPatternPos = 0;\n         return false;\n      }\n      endMode();\n      addFilter();\n      return true; //success\n    }\n\n    std::string TestSpecParser::preprocessPattern() {\n        std::string token = m_patternName;\n        for (std::size_t i = 0; i < m_escapeChars.size(); ++i)\n            token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1);\n        m_escapeChars.clear();\n        if (startsWith(token, \"exclude:\")) {\n            m_exclusion = true;\n            token = token.substr(8);\n        }\n\n        m_patternName.clear();\n        m_realPatternPos = 0;\n\n        return token;\n    }\n\n    void TestSpecParser::addNamePattern() {\n        auto token = preprocessPattern();\n\n        if (!token.empty()) {\n            TestSpec::PatternPtr pattern = std::make_shared<TestSpec::NamePattern>(token, m_substring);\n            if (m_exclusion)\n                pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern);\n            m_currentFilter.m_patterns.push_back(pattern);\n        }\n        m_substring.clear();\n        m_exclusion = false;\n        m_mode = None;\n    }\n\n    void TestSpecParser::addTagPattern() {\n        auto token = preprocessPattern();\n\n        if (!token.empty()) {\n            // If the tag pattern is the \"hide and tag\" shorthand (e.g. [.foo])\n            // we have to create a separate hide tag and shorten the real one\n            if (token.size() > 1 && token[0] == '.') {\n                token.erase(token.begin());\n                TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(\".\", m_substring);\n                if (m_exclusion) {\n                    pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern);\n                }\n                m_currentFilter.m_patterns.push_back(pattern);\n            }\n\n            TestSpec::PatternPtr pattern = std::make_shared<TestSpec::TagPattern>(token, m_substring);\n\n            if (m_exclusion) {\n                pattern = std::make_shared<TestSpec::ExcludedPattern>(pattern);\n            }\n            m_currentFilter.m_patterns.push_back(pattern);\n        }\n        m_substring.clear();\n        m_exclusion = false;\n        m_mode = None;\n    }\n\n    TestSpec parseTestSpec( std::string const& arg ) {\n        return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();\n    }\n\n} // namespace Catch\n// end catch_test_spec_parser.cpp\n// start catch_timer.cpp\n\n#include <chrono>\n\nstatic const uint64_t nanosecondsInSecond = 1000000000;\n\nnamespace Catch {\n\n    auto getCurrentNanosecondsSinceEpoch() -> uint64_t {\n        return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();\n    }\n\n    namespace {\n        auto estimateClockResolution() -> uint64_t {\n            uint64_t sum = 0;\n            static const uint64_t iterations = 1000000;\n\n            auto startTime = getCurrentNanosecondsSinceEpoch();\n\n            for( std::size_t i = 0; i < iterations; ++i ) {\n\n                uint64_t ticks;\n                uint64_t baseTicks = getCurrentNanosecondsSinceEpoch();\n                do {\n                    ticks = getCurrentNanosecondsSinceEpoch();\n                } while( ticks == baseTicks );\n\n                auto delta = ticks - baseTicks;\n                sum += delta;\n\n                // If we have been calibrating for over 3 seconds -- the clock\n                // is terrible and we should move on.\n                // TBD: How to signal that the measured resolution is probably wrong?\n                if (ticks > startTime + 3 * nanosecondsInSecond) {\n                    return sum / ( i + 1u );\n                }\n            }\n\n            // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers\n            // - and potentially do more iterations if there's a high variance.\n            return sum/iterations;\n        }\n    }\n    auto getEstimatedClockResolution() -> uint64_t {\n        static auto s_resolution = estimateClockResolution();\n        return s_resolution;\n    }\n\n    void Timer::start() {\n       m_nanoseconds = getCurrentNanosecondsSinceEpoch();\n    }\n    auto Timer::getElapsedNanoseconds() const -> uint64_t {\n        return getCurrentNanosecondsSinceEpoch() - m_nanoseconds;\n    }\n    auto Timer::getElapsedMicroseconds() const -> uint64_t {\n        return getElapsedNanoseconds()/1000;\n    }\n    auto Timer::getElapsedMilliseconds() const -> unsigned int {\n        return static_cast<unsigned int>(getElapsedMicroseconds()/1000);\n    }\n    auto Timer::getElapsedSeconds() const -> double {\n        return getElapsedMicroseconds()/1000000.0;\n    }\n\n} // namespace Catch\n// end catch_timer.cpp\n// start catch_tostring.cpp\n\n#if defined(__clang__)\n#    pragma clang diagnostic push\n#    pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n#    pragma clang diagnostic ignored \"-Wglobal-constructors\"\n#endif\n\n// Enable specific decls locally\n#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)\n#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER\n#endif\n\n#include <cmath>\n#include <iomanip>\n\nnamespace Catch {\n\nnamespace Detail {\n\n    const std::string unprintableString = \"{?}\";\n\n    namespace {\n        const int hexThreshold = 255;\n\n        struct Endianness {\n            enum Arch { Big, Little };\n\n            static Arch which() {\n                int one = 1;\n                // If the lowest byte we read is non-zero, we can assume\n                // that little endian format is used.\n                auto value = *reinterpret_cast<char*>(&one);\n                return value ? Little : Big;\n            }\n        };\n    }\n\n    std::string rawMemoryToString( const void *object, std::size_t size ) {\n        // Reverse order for little endian architectures\n        int i = 0, end = static_cast<int>( size ), inc = 1;\n        if( Endianness::which() == Endianness::Little ) {\n            i = end-1;\n            end = inc = -1;\n        }\n\n        unsigned char const *bytes = static_cast<unsigned char const *>(object);\n        ReusableStringStream rss;\n        rss << \"0x\" << std::setfill('0') << std::hex;\n        for( ; i != end; i += inc )\n             rss << std::setw(2) << static_cast<unsigned>(bytes[i]);\n       return rss.str();\n    }\n}\n\ntemplate<typename T>\nstd::string fpToString( T value, int precision ) {\n    if (Catch::isnan(value)) {\n        return \"nan\";\n    }\n\n    ReusableStringStream rss;\n    rss << std::setprecision( precision )\n        << std::fixed\n        << value;\n    std::string d = rss.str();\n    std::size_t i = d.find_last_not_of( '0' );\n    if( i != std::string::npos && i != d.size()-1 ) {\n        if( d[i] == '.' )\n            i++;\n        d = d.substr( 0, i+1 );\n    }\n    return d;\n}\n\n//// ======================================================= ////\n//\n//   Out-of-line defs for full specialization of StringMaker\n//\n//// ======================================================= ////\n\nstd::string StringMaker<std::string>::convert(const std::string& str) {\n    if (!getCurrentContext().getConfig()->showInvisibles()) {\n        return '\"' + str + '\"';\n    }\n\n    std::string s(\"\\\"\");\n    for (char c : str) {\n        switch (c) {\n        case '\\n':\n            s.append(\"\\\\n\");\n            break;\n        case '\\t':\n            s.append(\"\\\\t\");\n            break;\n        default:\n            s.push_back(c);\n            break;\n        }\n    }\n    s.append(\"\\\"\");\n    return s;\n}\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\nstd::string StringMaker<std::string_view>::convert(std::string_view str) {\n    return ::Catch::Detail::stringify(std::string{ str });\n}\n#endif\n\nstd::string StringMaker<char const*>::convert(char const* str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::string{ str });\n    } else {\n        return{ \"{null string}\" };\n    }\n}\nstd::string StringMaker<char*>::convert(char* str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::string{ str });\n    } else {\n        return{ \"{null string}\" };\n    }\n}\n\n#ifdef CATCH_CONFIG_WCHAR\nstd::string StringMaker<std::wstring>::convert(const std::wstring& wstr) {\n    std::string s;\n    s.reserve(wstr.size());\n    for (auto c : wstr) {\n        s += (c <= 0xff) ? static_cast<char>(c) : '?';\n    }\n    return ::Catch::Detail::stringify(s);\n}\n\n# ifdef CATCH_CONFIG_CPP17_STRING_VIEW\nstd::string StringMaker<std::wstring_view>::convert(std::wstring_view str) {\n    return StringMaker<std::wstring>::convert(std::wstring(str));\n}\n# endif\n\nstd::string StringMaker<wchar_t const*>::convert(wchar_t const * str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::wstring{ str });\n    } else {\n        return{ \"{null string}\" };\n    }\n}\nstd::string StringMaker<wchar_t *>::convert(wchar_t * str) {\n    if (str) {\n        return ::Catch::Detail::stringify(std::wstring{ str });\n    } else {\n        return{ \"{null string}\" };\n    }\n}\n#endif\n\n#if defined(CATCH_CONFIG_CPP17_BYTE)\n#include <cstddef>\nstd::string StringMaker<std::byte>::convert(std::byte value) {\n    return ::Catch::Detail::stringify(std::to_integer<unsigned long long>(value));\n}\n#endif // defined(CATCH_CONFIG_CPP17_BYTE)\n\nstd::string StringMaker<int>::convert(int value) {\n    return ::Catch::Detail::stringify(static_cast<long long>(value));\n}\nstd::string StringMaker<long>::convert(long value) {\n    return ::Catch::Detail::stringify(static_cast<long long>(value));\n}\nstd::string StringMaker<long long>::convert(long long value) {\n    ReusableStringStream rss;\n    rss << value;\n    if (value > Detail::hexThreshold) {\n        rss << \" (0x\" << std::hex << value << ')';\n    }\n    return rss.str();\n}\n\nstd::string StringMaker<unsigned int>::convert(unsigned int value) {\n    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));\n}\nstd::string StringMaker<unsigned long>::convert(unsigned long value) {\n    return ::Catch::Detail::stringify(static_cast<unsigned long long>(value));\n}\nstd::string StringMaker<unsigned long long>::convert(unsigned long long value) {\n    ReusableStringStream rss;\n    rss << value;\n    if (value > Detail::hexThreshold) {\n        rss << \" (0x\" << std::hex << value << ')';\n    }\n    return rss.str();\n}\n\nstd::string StringMaker<bool>::convert(bool b) {\n    return b ? \"true\" : \"false\";\n}\n\nstd::string StringMaker<signed char>::convert(signed char value) {\n    if (value == '\\r') {\n        return \"'\\\\r'\";\n    } else if (value == '\\f') {\n        return \"'\\\\f'\";\n    } else if (value == '\\n') {\n        return \"'\\\\n'\";\n    } else if (value == '\\t') {\n        return \"'\\\\t'\";\n    } else if ('\\0' <= value && value < ' ') {\n        return ::Catch::Detail::stringify(static_cast<unsigned int>(value));\n    } else {\n        char chstr[] = \"' '\";\n        chstr[1] = value;\n        return chstr;\n    }\n}\nstd::string StringMaker<char>::convert(char c) {\n    return ::Catch::Detail::stringify(static_cast<signed char>(c));\n}\nstd::string StringMaker<unsigned char>::convert(unsigned char c) {\n    return ::Catch::Detail::stringify(static_cast<char>(c));\n}\n\nstd::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) {\n    return \"nullptr\";\n}\n\nint StringMaker<float>::precision = 5;\n\nstd::string StringMaker<float>::convert(float value) {\n    return fpToString(value, precision) + 'f';\n}\n\nint StringMaker<double>::precision = 10;\n\nstd::string StringMaker<double>::convert(double value) {\n    return fpToString(value, precision);\n}\n\nstd::string ratio_string<std::atto>::symbol() { return \"a\"; }\nstd::string ratio_string<std::femto>::symbol() { return \"f\"; }\nstd::string ratio_string<std::pico>::symbol() { return \"p\"; }\nstd::string ratio_string<std::nano>::symbol() { return \"n\"; }\nstd::string ratio_string<std::micro>::symbol() { return \"u\"; }\nstd::string ratio_string<std::milli>::symbol() { return \"m\"; }\n\n} // end namespace Catch\n\n#if defined(__clang__)\n#    pragma clang diagnostic pop\n#endif\n\n// end catch_tostring.cpp\n// start catch_totals.cpp\n\nnamespace Catch {\n\n    Counts Counts::operator - ( Counts const& other ) const {\n        Counts diff;\n        diff.passed = passed - other.passed;\n        diff.failed = failed - other.failed;\n        diff.failedButOk = failedButOk - other.failedButOk;\n        return diff;\n    }\n\n    Counts& Counts::operator += ( Counts const& other ) {\n        passed += other.passed;\n        failed += other.failed;\n        failedButOk += other.failedButOk;\n        return *this;\n    }\n\n    std::size_t Counts::total() const {\n        return passed + failed + failedButOk;\n    }\n    bool Counts::allPassed() const {\n        return failed == 0 && failedButOk == 0;\n    }\n    bool Counts::allOk() const {\n        return failed == 0;\n    }\n\n    Totals Totals::operator - ( Totals const& other ) const {\n        Totals diff;\n        diff.assertions = assertions - other.assertions;\n        diff.testCases = testCases - other.testCases;\n        return diff;\n    }\n\n    Totals& Totals::operator += ( Totals const& other ) {\n        assertions += other.assertions;\n        testCases += other.testCases;\n        return *this;\n    }\n\n    Totals Totals::delta( Totals const& prevTotals ) const {\n        Totals diff = *this - prevTotals;\n        if( diff.assertions.failed > 0 )\n            ++diff.testCases.failed;\n        else if( diff.assertions.failedButOk > 0 )\n            ++diff.testCases.failedButOk;\n        else\n            ++diff.testCases.passed;\n        return diff;\n    }\n\n}\n// end catch_totals.cpp\n// start catch_uncaught_exceptions.cpp\n\n// start catch_config_uncaught_exceptions.hpp\n\n//              Copyright Catch2 Authors\n// Distributed under the Boost Software License, Version 1.0.\n//   (See accompanying file LICENSE_1_0.txt or copy at\n//        https://www.boost.org/LICENSE_1_0.txt)\n\n// SPDX-License-Identifier: BSL-1.0\n\n#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP\n#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP\n\n#if defined(_MSC_VER)\n#  if _MSC_VER >= 1900 // Visual Studio 2015 or newer\n#    define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS\n#  endif\n#endif\n\n#include <exception>\n\n#if defined(__cpp_lib_uncaught_exceptions) \\\n    && !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)\n\n#  define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS\n#endif // __cpp_lib_uncaught_exceptions\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) \\\n    && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) \\\n    && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)\n\n#  define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS\n#endif\n\n#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP\n// end catch_config_uncaught_exceptions.hpp\n#include <exception>\n\nnamespace Catch {\n    bool uncaught_exceptions() {\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n        return false;\n#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS)\n        return std::uncaught_exceptions() > 0;\n#else\n        return std::uncaught_exception();\n#endif\n  }\n} // end namespace Catch\n// end catch_uncaught_exceptions.cpp\n// start catch_version.cpp\n\n#include <ostream>\n\nnamespace Catch {\n\n    Version::Version\n        (   unsigned int _majorVersion,\n            unsigned int _minorVersion,\n            unsigned int _patchNumber,\n            char const * const _branchName,\n            unsigned int _buildNumber )\n    :   majorVersion( _majorVersion ),\n        minorVersion( _minorVersion ),\n        patchNumber( _patchNumber ),\n        branchName( _branchName ),\n        buildNumber( _buildNumber )\n    {}\n\n    std::ostream& operator << ( std::ostream& os, Version const& version ) {\n        os  << version.majorVersion << '.'\n            << version.minorVersion << '.'\n            << version.patchNumber;\n        // branchName is never null -> 0th char is \\0 if it is empty\n        if (version.branchName[0]) {\n            os << '-' << version.branchName\n               << '.' << version.buildNumber;\n        }\n        return os;\n    }\n\n    Version const& libraryVersion() {\n        static Version version( 2, 13, 9, \"\", 0 );\n        return version;\n    }\n\n}\n// end catch_version.cpp\n// start catch_wildcard_pattern.cpp\n\nnamespace Catch {\n\n    WildcardPattern::WildcardPattern( std::string const& pattern,\n                                      CaseSensitive::Choice caseSensitivity )\n    :   m_caseSensitivity( caseSensitivity ),\n        m_pattern( normaliseString( pattern ) )\n    {\n        if( startsWith( m_pattern, '*' ) ) {\n            m_pattern = m_pattern.substr( 1 );\n            m_wildcard = WildcardAtStart;\n        }\n        if( endsWith( m_pattern, '*' ) ) {\n            m_pattern = m_pattern.substr( 0, m_pattern.size()-1 );\n            m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd );\n        }\n    }\n\n    bool WildcardPattern::matches( std::string const& str ) const {\n        switch( m_wildcard ) {\n            case NoWildcard:\n                return m_pattern == normaliseString( str );\n            case WildcardAtStart:\n                return endsWith( normaliseString( str ), m_pattern );\n            case WildcardAtEnd:\n                return startsWith( normaliseString( str ), m_pattern );\n            case WildcardAtBothEnds:\n                return contains( normaliseString( str ), m_pattern );\n            default:\n                CATCH_INTERNAL_ERROR( \"Unknown enum\" );\n        }\n    }\n\n    std::string WildcardPattern::normaliseString( std::string const& str ) const {\n        return trim( m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str );\n    }\n}\n// end catch_wildcard_pattern.cpp\n// start catch_xmlwriter.cpp\n\n#include <iomanip>\n#include <type_traits>\n\nnamespace Catch {\n\nnamespace {\n\n    size_t trailingBytes(unsigned char c) {\n        if ((c & 0xE0) == 0xC0) {\n            return 2;\n        }\n        if ((c & 0xF0) == 0xE0) {\n            return 3;\n        }\n        if ((c & 0xF8) == 0xF0) {\n            return 4;\n        }\n        CATCH_INTERNAL_ERROR(\"Invalid multibyte utf-8 start byte encountered\");\n    }\n\n    uint32_t headerValue(unsigned char c) {\n        if ((c & 0xE0) == 0xC0) {\n            return c & 0x1F;\n        }\n        if ((c & 0xF0) == 0xE0) {\n            return c & 0x0F;\n        }\n        if ((c & 0xF8) == 0xF0) {\n            return c & 0x07;\n        }\n        CATCH_INTERNAL_ERROR(\"Invalid multibyte utf-8 start byte encountered\");\n    }\n\n    void hexEscapeChar(std::ostream& os, unsigned char c) {\n        std::ios_base::fmtflags f(os.flags());\n        os << \"\\\\x\"\n            << std::uppercase << std::hex << std::setfill('0') << std::setw(2)\n            << static_cast<int>(c);\n        os.flags(f);\n    }\n\n    bool shouldNewline(XmlFormatting fmt) {\n        return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Newline));\n    }\n\n    bool shouldIndent(XmlFormatting fmt) {\n        return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Indent));\n    }\n\n} // anonymous namespace\n\n    XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs) {\n        return static_cast<XmlFormatting>(\n            static_cast<std::underlying_type<XmlFormatting>::type>(lhs) |\n            static_cast<std::underlying_type<XmlFormatting>::type>(rhs)\n        );\n    }\n\n    XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs) {\n        return static_cast<XmlFormatting>(\n            static_cast<std::underlying_type<XmlFormatting>::type>(lhs) &\n            static_cast<std::underlying_type<XmlFormatting>::type>(rhs)\n        );\n    }\n\n    XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )\n    :   m_str( str ),\n        m_forWhat( forWhat )\n    {}\n\n    void XmlEncode::encodeTo( std::ostream& os ) const {\n        // Apostrophe escaping not necessary if we always use \" to write attributes\n        // (see: http://www.w3.org/TR/xml/#syntax)\n\n        for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {\n            unsigned char c = m_str[idx];\n            switch (c) {\n            case '<':   os << \"&lt;\"; break;\n            case '&':   os << \"&amp;\"; break;\n\n            case '>':\n                // See: http://www.w3.org/TR/xml/#syntax\n                if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')\n                    os << \"&gt;\";\n                else\n                    os << c;\n                break;\n\n            case '\\\"':\n                if (m_forWhat == ForAttributes)\n                    os << \"&quot;\";\n                else\n                    os << c;\n                break;\n\n            default:\n                // Check for control characters and invalid utf-8\n\n                // Escape control characters in standard ascii\n                // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0\n                if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {\n                    hexEscapeChar(os, c);\n                    break;\n                }\n\n                // Plain ASCII: Write it to stream\n                if (c < 0x7F) {\n                    os << c;\n                    break;\n                }\n\n                // UTF-8 territory\n                // Check if the encoding is valid and if it is not, hex escape bytes.\n                // Important: We do not check the exact decoded values for validity, only the encoding format\n                // First check that this bytes is a valid lead byte:\n                // This means that it is not encoded as 1111 1XXX\n                // Or as 10XX XXXX\n                if (c <  0xC0 ||\n                    c >= 0xF8) {\n                    hexEscapeChar(os, c);\n                    break;\n                }\n\n                auto encBytes = trailingBytes(c);\n                // Are there enough bytes left to avoid accessing out-of-bounds memory?\n                if (idx + encBytes - 1 >= m_str.size()) {\n                    hexEscapeChar(os, c);\n                    break;\n                }\n                // The header is valid, check data\n                // The next encBytes bytes must together be a valid utf-8\n                // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)\n                bool valid = true;\n                uint32_t value = headerValue(c);\n                for (std::size_t n = 1; n < encBytes; ++n) {\n                    unsigned char nc = m_str[idx + n];\n                    valid &= ((nc & 0xC0) == 0x80);\n                    value = (value << 6) | (nc & 0x3F);\n                }\n\n                if (\n                    // Wrong bit pattern of following bytes\n                    (!valid) ||\n                    // Overlong encodings\n                    (value < 0x80) ||\n                    (0x80 <= value && value < 0x800   && encBytes > 2) ||\n                    (0x800 < value && value < 0x10000 && encBytes > 3) ||\n                    // Encoded value out of range\n                    (value >= 0x110000)\n                    ) {\n                    hexEscapeChar(os, c);\n                    break;\n                }\n\n                // If we got here, this is in fact a valid(ish) utf-8 sequence\n                for (std::size_t n = 0; n < encBytes; ++n) {\n                    os << m_str[idx + n];\n                }\n                idx += encBytes - 1;\n                break;\n            }\n        }\n    }\n\n    std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {\n        xmlEncode.encodeTo( os );\n        return os;\n    }\n\n    XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt )\n    :   m_writer( writer ),\n        m_fmt(fmt)\n    {}\n\n    XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept\n    :   m_writer( other.m_writer ),\n        m_fmt(other.m_fmt)\n    {\n        other.m_writer = nullptr;\n        other.m_fmt = XmlFormatting::None;\n    }\n    XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept {\n        if ( m_writer ) {\n            m_writer->endElement();\n        }\n        m_writer = other.m_writer;\n        other.m_writer = nullptr;\n        m_fmt = other.m_fmt;\n        other.m_fmt = XmlFormatting::None;\n        return *this;\n    }\n\n    XmlWriter::ScopedElement::~ScopedElement() {\n        if (m_writer) {\n            m_writer->endElement(m_fmt);\n        }\n    }\n\n    XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, XmlFormatting fmt ) {\n        m_writer->writeText( text, fmt );\n        return *this;\n    }\n\n    XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )\n    {\n        writeDeclaration();\n    }\n\n    XmlWriter::~XmlWriter() {\n        while (!m_tags.empty()) {\n            endElement();\n        }\n        newlineIfNecessary();\n    }\n\n    XmlWriter& XmlWriter::startElement( std::string const& name, XmlFormatting fmt ) {\n        ensureTagClosed();\n        newlineIfNecessary();\n        if (shouldIndent(fmt)) {\n            m_os << m_indent;\n            m_indent += \"  \";\n        }\n        m_os << '<' << name;\n        m_tags.push_back( name );\n        m_tagIsOpen = true;\n        applyFormatting(fmt);\n        return *this;\n    }\n\n    XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) {\n        ScopedElement scoped( this, fmt );\n        startElement( name, fmt );\n        return scoped;\n    }\n\n    XmlWriter& XmlWriter::endElement(XmlFormatting fmt) {\n        m_indent = m_indent.substr(0, m_indent.size() - 2);\n\n        if( m_tagIsOpen ) {\n            m_os << \"/>\";\n            m_tagIsOpen = false;\n        } else {\n            newlineIfNecessary();\n            if (shouldIndent(fmt)) {\n                m_os << m_indent;\n            }\n            m_os << \"</\" << m_tags.back() << \">\";\n        }\n        m_os << std::flush;\n        applyFormatting(fmt);\n        m_tags.pop_back();\n        return *this;\n    }\n\n    XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) {\n        if( !name.empty() && !attribute.empty() )\n            m_os << ' ' << name << \"=\\\"\" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '\"';\n        return *this;\n    }\n\n    XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {\n        m_os << ' ' << name << \"=\\\"\" << ( attribute ? \"true\" : \"false\" ) << '\"';\n        return *this;\n    }\n\n    XmlWriter& XmlWriter::writeText( std::string const& text, XmlFormatting fmt) {\n        if( !text.empty() ){\n            bool tagWasOpen = m_tagIsOpen;\n            ensureTagClosed();\n            if (tagWasOpen && shouldIndent(fmt)) {\n                m_os << m_indent;\n            }\n            m_os << XmlEncode( text );\n            applyFormatting(fmt);\n        }\n        return *this;\n    }\n\n    XmlWriter& XmlWriter::writeComment( std::string const& text, XmlFormatting fmt) {\n        ensureTagClosed();\n        if (shouldIndent(fmt)) {\n            m_os << m_indent;\n        }\n        m_os << \"<!--\" << text << \"-->\";\n        applyFormatting(fmt);\n        return *this;\n    }\n\n    void XmlWriter::writeStylesheetRef( std::string const& url ) {\n        m_os << \"<?xml-stylesheet type=\\\"text/xsl\\\" href=\\\"\" << url << \"\\\"?>\\n\";\n    }\n\n    XmlWriter& XmlWriter::writeBlankLine() {\n        ensureTagClosed();\n        m_os << '\\n';\n        return *this;\n    }\n\n    void XmlWriter::ensureTagClosed() {\n        if( m_tagIsOpen ) {\n            m_os << '>' << std::flush;\n            newlineIfNecessary();\n            m_tagIsOpen = false;\n        }\n    }\n\n    void XmlWriter::applyFormatting(XmlFormatting fmt) {\n        m_needsNewline = shouldNewline(fmt);\n    }\n\n    void XmlWriter::writeDeclaration() {\n        m_os << \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\";\n    }\n\n    void XmlWriter::newlineIfNecessary() {\n        if( m_needsNewline ) {\n            m_os << std::endl;\n            m_needsNewline = false;\n        }\n    }\n}\n// end catch_xmlwriter.cpp\n// start catch_reporter_bases.cpp\n\n#include <cstring>\n#include <cfloat>\n#include <cstdio>\n#include <cassert>\n#include <memory>\n\nnamespace Catch {\n    void prepareExpandedExpression(AssertionResult& result) {\n        result.getExpandedExpression();\n    }\n\n    // Because formatting using c++ streams is stateful, drop down to C is required\n    // Alternatively we could use stringstream, but its performance is... not good.\n    std::string getFormattedDuration( double duration ) {\n        // Max exponent + 1 is required to represent the whole part\n        // + 1 for decimal point\n        // + 3 for the 3 decimal places\n        // + 1 for null terminator\n        const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1;\n        char buffer[maxDoubleSize];\n\n        // Save previous errno, to prevent sprintf from overwriting it\n        ErrnoGuard guard;\n#ifdef _MSC_VER\n        sprintf_s(buffer, \"%.3f\", duration);\n#else\n        std::sprintf(buffer, \"%.3f\", duration);\n#endif\n        return std::string(buffer);\n    }\n\n    bool shouldShowDuration( IConfig const& config, double duration ) {\n        if ( config.showDurations() == ShowDurations::Always ) {\n            return true;\n        }\n        if ( config.showDurations() == ShowDurations::Never ) {\n            return false;\n        }\n        const double min = config.minDuration();\n        return min >= 0 && duration >= min;\n    }\n\n    std::string serializeFilters( std::vector<std::string> const& container ) {\n        ReusableStringStream oss;\n        bool first = true;\n        for (auto&& filter : container)\n        {\n            if (!first)\n                oss << ' ';\n            else\n                first = false;\n\n            oss << filter;\n        }\n        return oss.str();\n    }\n\n    TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config)\n        :StreamingReporterBase(_config) {}\n\n    std::set<Verbosity> TestEventListenerBase::getSupportedVerbosities() {\n        return { Verbosity::Quiet, Verbosity::Normal, Verbosity::High };\n    }\n\n    void TestEventListenerBase::assertionStarting(AssertionInfo const &) {}\n\n    bool TestEventListenerBase::assertionEnded(AssertionStats const &) {\n        return false;\n    }\n\n} // end namespace Catch\n// end catch_reporter_bases.cpp\n// start catch_reporter_compact.cpp\n\nnamespace {\n\n#ifdef CATCH_PLATFORM_MAC\n    const char* failedString() { return \"FAILED\"; }\n    const char* passedString() { return \"PASSED\"; }\n#else\n    const char* failedString() { return \"failed\"; }\n    const char* passedString() { return \"passed\"; }\n#endif\n\n    // Colour::LightGrey\n    Catch::Colour::Code dimColour() { return Catch::Colour::FileName; }\n\n    std::string bothOrAll( std::size_t count ) {\n        return count == 1 ? std::string() :\n               count == 2 ? \"both \" : \"all \" ;\n    }\n\n} // anon namespace\n\nnamespace Catch {\nnamespace {\n// Colour, message variants:\n// - white: No tests ran.\n// -   red: Failed [both/all] N test cases, failed [both/all] M assertions.\n// - white: Passed [both/all] N test cases (no assertions).\n// -   red: Failed N tests cases, failed M assertions.\n// - green: Passed [both/all] N tests cases with M assertions.\nvoid printTotals(std::ostream& out, const Totals& totals) {\n    if (totals.testCases.total() == 0) {\n        out << \"No tests ran.\";\n    } else if (totals.testCases.failed == totals.testCases.total()) {\n        Colour colour(Colour::ResultError);\n        const std::string qualify_assertions_failed =\n            totals.assertions.failed == totals.assertions.total() ?\n            bothOrAll(totals.assertions.failed) : std::string();\n        out <<\n            \"Failed \" << bothOrAll(totals.testCases.failed)\n            << pluralise(totals.testCases.failed, \"test case\") << \", \"\n            \"failed \" << qualify_assertions_failed <<\n            pluralise(totals.assertions.failed, \"assertion\") << '.';\n    } else if (totals.assertions.total() == 0) {\n        out <<\n            \"Passed \" << bothOrAll(totals.testCases.total())\n            << pluralise(totals.testCases.total(), \"test case\")\n            << \" (no assertions).\";\n    } else if (totals.assertions.failed) {\n        Colour colour(Colour::ResultError);\n        out <<\n            \"Failed \" << pluralise(totals.testCases.failed, \"test case\") << \", \"\n            \"failed \" << pluralise(totals.assertions.failed, \"assertion\") << '.';\n    } else {\n        Colour colour(Colour::ResultSuccess);\n        out <<\n            \"Passed \" << bothOrAll(totals.testCases.passed)\n            << pluralise(totals.testCases.passed, \"test case\") <<\n            \" with \" << pluralise(totals.assertions.passed, \"assertion\") << '.';\n    }\n}\n\n// Implementation of CompactReporter formatting\nclass AssertionPrinter {\npublic:\n    AssertionPrinter& operator= (AssertionPrinter const&) = delete;\n    AssertionPrinter(AssertionPrinter const&) = delete;\n    AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages)\n        : stream(_stream)\n        , result(_stats.assertionResult)\n        , messages(_stats.infoMessages)\n        , itMessage(_stats.infoMessages.begin())\n        , printInfoMessages(_printInfoMessages) {}\n\n    void print() {\n        printSourceInfo();\n\n        itMessage = messages.begin();\n\n        switch (result.getResultType()) {\n        case ResultWas::Ok:\n            printResultType(Colour::ResultSuccess, passedString());\n            printOriginalExpression();\n            printReconstructedExpression();\n            if (!result.hasExpression())\n                printRemainingMessages(Colour::None);\n            else\n                printRemainingMessages();\n            break;\n        case ResultWas::ExpressionFailed:\n            if (result.isOk())\n                printResultType(Colour::ResultSuccess, failedString() + std::string(\" - but was ok\"));\n            else\n                printResultType(Colour::Error, failedString());\n            printOriginalExpression();\n            printReconstructedExpression();\n            printRemainingMessages();\n            break;\n        case ResultWas::ThrewException:\n            printResultType(Colour::Error, failedString());\n            printIssue(\"unexpected exception with message:\");\n            printMessage();\n            printExpressionWas();\n            printRemainingMessages();\n            break;\n        case ResultWas::FatalErrorCondition:\n            printResultType(Colour::Error, failedString());\n            printIssue(\"fatal error condition with message:\");\n            printMessage();\n            printExpressionWas();\n            printRemainingMessages();\n            break;\n        case ResultWas::DidntThrowException:\n            printResultType(Colour::Error, failedString());\n            printIssue(\"expected exception, got none\");\n            printExpressionWas();\n            printRemainingMessages();\n            break;\n        case ResultWas::Info:\n            printResultType(Colour::None, \"info\");\n            printMessage();\n            printRemainingMessages();\n            break;\n        case ResultWas::Warning:\n            printResultType(Colour::None, \"warning\");\n            printMessage();\n            printRemainingMessages();\n            break;\n        case ResultWas::ExplicitFailure:\n            printResultType(Colour::Error, failedString());\n            printIssue(\"explicitly\");\n            printRemainingMessages(Colour::None);\n            break;\n            // These cases are here to prevent compiler warnings\n        case ResultWas::Unknown:\n        case ResultWas::FailureBit:\n        case ResultWas::Exception:\n            printResultType(Colour::Error, \"** internal error **\");\n            break;\n        }\n    }\n\nprivate:\n    void printSourceInfo() const {\n        Colour colourGuard(Colour::FileName);\n        stream << result.getSourceInfo() << ':';\n    }\n\n    void printResultType(Colour::Code colour, std::string const& passOrFail) const {\n        if (!passOrFail.empty()) {\n            {\n                Colour colourGuard(colour);\n                stream << ' ' << passOrFail;\n            }\n            stream << ':';\n        }\n    }\n\n    void printIssue(std::string const& issue) const {\n        stream << ' ' << issue;\n    }\n\n    void printExpressionWas() {\n        if (result.hasExpression()) {\n            stream << ';';\n            {\n                Colour colour(dimColour());\n                stream << \" expression was:\";\n            }\n            printOriginalExpression();\n        }\n    }\n\n    void printOriginalExpression() const {\n        if (result.hasExpression()) {\n            stream << ' ' << result.getExpression();\n        }\n    }\n\n    void printReconstructedExpression() const {\n        if (result.hasExpandedExpression()) {\n            {\n                Colour colour(dimColour());\n                stream << \" for: \";\n            }\n            stream << result.getExpandedExpression();\n        }\n    }\n\n    void printMessage() {\n        if (itMessage != messages.end()) {\n            stream << \" '\" << itMessage->message << '\\'';\n            ++itMessage;\n        }\n    }\n\n    void printRemainingMessages(Colour::Code colour = dimColour()) {\n        if (itMessage == messages.end())\n            return;\n\n        const auto itEnd = messages.cend();\n        const auto N = static_cast<std::size_t>(std::distance(itMessage, itEnd));\n\n        {\n            Colour colourGuard(colour);\n            stream << \" with \" << pluralise(N, \"message\") << ':';\n        }\n\n        while (itMessage != itEnd) {\n            // If this assertion is a warning ignore any INFO messages\n            if (printInfoMessages || itMessage->type != ResultWas::Info) {\n                printMessage();\n                if (itMessage != itEnd) {\n                    Colour colourGuard(dimColour());\n                    stream << \" and\";\n                }\n                continue;\n            }\n            ++itMessage;\n        }\n    }\n\nprivate:\n    std::ostream& stream;\n    AssertionResult const& result;\n    std::vector<MessageInfo> messages;\n    std::vector<MessageInfo>::const_iterator itMessage;\n    bool printInfoMessages;\n};\n\n} // anon namespace\n\n        std::string CompactReporter::getDescription() {\n            return \"Reports test results on a single line, suitable for IDEs\";\n        }\n\n        void CompactReporter::noMatchingTestCases( std::string const& spec ) {\n            stream << \"No test cases matched '\" << spec << '\\'' << std::endl;\n        }\n\n        void CompactReporter::assertionStarting( AssertionInfo const& ) {}\n\n        bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) {\n            AssertionResult const& result = _assertionStats.assertionResult;\n\n            bool printInfoMessages = true;\n\n            // Drop out if result was successful and we're not printing those\n            if( !m_config->includeSuccessfulResults() && result.isOk() ) {\n                if( result.getResultType() != ResultWas::Warning )\n                    return false;\n                printInfoMessages = false;\n            }\n\n            AssertionPrinter printer( stream, _assertionStats, printInfoMessages );\n            printer.print();\n\n            stream << std::endl;\n            return true;\n        }\n\n        void CompactReporter::sectionEnded(SectionStats const& _sectionStats) {\n            double dur = _sectionStats.durationInSeconds;\n            if ( shouldShowDuration( *m_config, dur ) ) {\n                stream << getFormattedDuration( dur ) << \" s: \" << _sectionStats.sectionInfo.name << std::endl;\n            }\n        }\n\n        void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) {\n            printTotals( stream, _testRunStats.totals );\n            stream << '\\n' << std::endl;\n            StreamingReporterBase::testRunEnded( _testRunStats );\n        }\n\n        CompactReporter::~CompactReporter() {}\n\n    CATCH_REGISTER_REPORTER( \"compact\", CompactReporter )\n\n} // end namespace Catch\n// end catch_reporter_compact.cpp\n// start catch_reporter_console.cpp\n\n#include <cfloat>\n#include <cstdio>\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch\n // Note that 4062 (not all labels are handled and default is missing) is enabled\n#endif\n\n#if defined(__clang__)\n#  pragma clang diagnostic push\n// For simplicity, benchmarking-only helpers are always enabled\n#  pragma clang diagnostic ignored \"-Wunused-function\"\n#endif\n\nnamespace Catch {\n\nnamespace {\n\n// Formatter impl for ConsoleReporter\nclass ConsoleAssertionPrinter {\npublic:\n    ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete;\n    ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete;\n    ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages)\n        : stream(_stream),\n        stats(_stats),\n        result(_stats.assertionResult),\n        colour(Colour::None),\n        message(result.getMessage()),\n        messages(_stats.infoMessages),\n        printInfoMessages(_printInfoMessages) {\n        switch (result.getResultType()) {\n        case ResultWas::Ok:\n            colour = Colour::Success;\n            passOrFail = \"PASSED\";\n            //if( result.hasMessage() )\n            if (_stats.infoMessages.size() == 1)\n                messageLabel = \"with message\";\n            if (_stats.infoMessages.size() > 1)\n                messageLabel = \"with messages\";\n            break;\n        case ResultWas::ExpressionFailed:\n            if (result.isOk()) {\n                colour = Colour::Success;\n                passOrFail = \"FAILED - but was ok\";\n            } else {\n                colour = Colour::Error;\n                passOrFail = \"FAILED\";\n            }\n            if (_stats.infoMessages.size() == 1)\n                messageLabel = \"with message\";\n            if (_stats.infoMessages.size() > 1)\n                messageLabel = \"with messages\";\n            break;\n        case ResultWas::ThrewException:\n            colour = Colour::Error;\n            passOrFail = \"FAILED\";\n            messageLabel = \"due to unexpected exception with \";\n            if (_stats.infoMessages.size() == 1)\n                messageLabel += \"message\";\n            if (_stats.infoMessages.size() > 1)\n                messageLabel += \"messages\";\n            break;\n        case ResultWas::FatalErrorCondition:\n            colour = Colour::Error;\n            passOrFail = \"FAILED\";\n            messageLabel = \"due to a fatal error condition\";\n            break;\n        case ResultWas::DidntThrowException:\n            colour = Colour::Error;\n            passOrFail = \"FAILED\";\n            messageLabel = \"because no exception was thrown where one was expected\";\n            break;\n        case ResultWas::Info:\n            messageLabel = \"info\";\n            break;\n        case ResultWas::Warning:\n            messageLabel = \"warning\";\n            break;\n        case ResultWas::ExplicitFailure:\n            passOrFail = \"FAILED\";\n            colour = Colour::Error;\n            if (_stats.infoMessages.size() == 1)\n                messageLabel = \"explicitly with message\";\n            if (_stats.infoMessages.size() > 1)\n                messageLabel = \"explicitly with messages\";\n            break;\n            // These cases are here to prevent compiler warnings\n        case ResultWas::Unknown:\n        case ResultWas::FailureBit:\n        case ResultWas::Exception:\n            passOrFail = \"** internal error **\";\n            colour = Colour::Error;\n            break;\n        }\n    }\n\n    void print() const {\n        printSourceInfo();\n        if (stats.totals.assertions.total() > 0) {\n            printResultType();\n            printOriginalExpression();\n            printReconstructedExpression();\n        } else {\n            stream << '\\n';\n        }\n        printMessage();\n    }\n\nprivate:\n    void printResultType() const {\n        if (!passOrFail.empty()) {\n            Colour colourGuard(colour);\n            stream << passOrFail << \":\\n\";\n        }\n    }\n    void printOriginalExpression() const {\n        if (result.hasExpression()) {\n            Colour colourGuard(Colour::OriginalExpression);\n            stream << \"  \";\n            stream << result.getExpressionInMacro();\n            stream << '\\n';\n        }\n    }\n    void printReconstructedExpression() const {\n        if (result.hasExpandedExpression()) {\n            stream << \"with expansion:\\n\";\n            Colour colourGuard(Colour::ReconstructedExpression);\n            stream << Column(result.getExpandedExpression()).indent(2) << '\\n';\n        }\n    }\n    void printMessage() const {\n        if (!messageLabel.empty())\n            stream << messageLabel << ':' << '\\n';\n        for (auto const& msg : messages) {\n            // If this assertion is a warning ignore any INFO messages\n            if (printInfoMessages || msg.type != ResultWas::Info)\n                stream << Column(msg.message).indent(2) << '\\n';\n        }\n    }\n    void printSourceInfo() const {\n        Colour colourGuard(Colour::FileName);\n        stream << result.getSourceInfo() << \": \";\n    }\n\n    std::ostream& stream;\n    AssertionStats const& stats;\n    AssertionResult const& result;\n    Colour::Code colour;\n    std::string passOrFail;\n    std::string messageLabel;\n    std::string message;\n    std::vector<MessageInfo> messages;\n    bool printInfoMessages;\n};\n\nstd::size_t makeRatio(std::size_t number, std::size_t total) {\n    std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0;\n    return (ratio == 0 && number > 0) ? 1 : ratio;\n}\n\nstd::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) {\n    if (i > j && i > k)\n        return i;\n    else if (j > k)\n        return j;\n    else\n        return k;\n}\n\nstruct ColumnInfo {\n    enum Justification { Left, Right };\n    std::string name;\n    int width;\n    Justification justification;\n};\nstruct ColumnBreak {};\nstruct RowBreak {};\n\nclass Duration {\n    enum class Unit {\n        Auto,\n        Nanoseconds,\n        Microseconds,\n        Milliseconds,\n        Seconds,\n        Minutes\n    };\n    static const uint64_t s_nanosecondsInAMicrosecond = 1000;\n    static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond;\n    static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond;\n    static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond;\n\n    double m_inNanoseconds;\n    Unit m_units;\n\npublic:\n    explicit Duration(double inNanoseconds, Unit units = Unit::Auto)\n        : m_inNanoseconds(inNanoseconds),\n        m_units(units) {\n        if (m_units == Unit::Auto) {\n            if (m_inNanoseconds < s_nanosecondsInAMicrosecond)\n                m_units = Unit::Nanoseconds;\n            else if (m_inNanoseconds < s_nanosecondsInAMillisecond)\n                m_units = Unit::Microseconds;\n            else if (m_inNanoseconds < s_nanosecondsInASecond)\n                m_units = Unit::Milliseconds;\n            else if (m_inNanoseconds < s_nanosecondsInAMinute)\n                m_units = Unit::Seconds;\n            else\n                m_units = Unit::Minutes;\n        }\n\n    }\n\n    auto value() const -> double {\n        switch (m_units) {\n        case Unit::Microseconds:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond);\n        case Unit::Milliseconds:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond);\n        case Unit::Seconds:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond);\n        case Unit::Minutes:\n            return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute);\n        default:\n            return m_inNanoseconds;\n        }\n    }\n    auto unitsAsString() const -> std::string {\n        switch (m_units) {\n        case Unit::Nanoseconds:\n            return \"ns\";\n        case Unit::Microseconds:\n            return \"us\";\n        case Unit::Milliseconds:\n            return \"ms\";\n        case Unit::Seconds:\n            return \"s\";\n        case Unit::Minutes:\n            return \"m\";\n        default:\n            return \"** internal error **\";\n        }\n\n    }\n    friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& {\n        return os << duration.value() << ' ' << duration.unitsAsString();\n    }\n};\n} // end anon namespace\n\nclass TablePrinter {\n    std::ostream& m_os;\n    std::vector<ColumnInfo> m_columnInfos;\n    std::ostringstream m_oss;\n    int m_currentColumn = -1;\n    bool m_isOpen = false;\n\npublic:\n    TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos )\n    :   m_os( os ),\n        m_columnInfos( std::move( columnInfos ) ) {}\n\n    auto columnInfos() const -> std::vector<ColumnInfo> const& {\n        return m_columnInfos;\n    }\n\n    void open() {\n        if (!m_isOpen) {\n            m_isOpen = true;\n            *this << RowBreak();\n\n\t\t\tColumns headerCols;\n\t\t\tSpacer spacer(2);\n\t\t\tfor (auto const& info : m_columnInfos) {\n\t\t\t\theaderCols += Column(info.name).width(static_cast<std::size_t>(info.width - 2));\n\t\t\t\theaderCols += spacer;\n\t\t\t}\n\t\t\tm_os << headerCols << '\\n';\n\n            m_os << Catch::getLineOfChars<'-'>() << '\\n';\n        }\n    }\n    void close() {\n        if (m_isOpen) {\n            *this << RowBreak();\n            m_os << std::endl;\n            m_isOpen = false;\n        }\n    }\n\n    template<typename T>\n    friend TablePrinter& operator << (TablePrinter& tp, T const& value) {\n        tp.m_oss << value;\n        return tp;\n    }\n\n    friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) {\n        auto colStr = tp.m_oss.str();\n        const auto strSize = colStr.size();\n        tp.m_oss.str(\"\");\n        tp.open();\n        if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) {\n            tp.m_currentColumn = -1;\n            tp.m_os << '\\n';\n        }\n        tp.m_currentColumn++;\n\n        auto colInfo = tp.m_columnInfos[tp.m_currentColumn];\n        auto padding = (strSize + 1 < static_cast<std::size_t>(colInfo.width))\n            ? std::string(colInfo.width - (strSize + 1), ' ')\n            : std::string();\n        if (colInfo.justification == ColumnInfo::Left)\n            tp.m_os << colStr << padding << ' ';\n        else\n            tp.m_os << padding << colStr << ' ';\n        return tp;\n    }\n\n    friend TablePrinter& operator << (TablePrinter& tp, RowBreak) {\n        if (tp.m_currentColumn > 0) {\n            tp.m_os << '\\n';\n            tp.m_currentColumn = -1;\n        }\n        return tp;\n    }\n};\n\nConsoleReporter::ConsoleReporter(ReporterConfig const& config)\n    : StreamingReporterBase(config),\n    m_tablePrinter(new TablePrinter(config.stream(),\n        [&config]() -> std::vector<ColumnInfo> {\n        if (config.fullConfig()->benchmarkNoAnalysis())\n        {\n            return{\n                { \"benchmark name\", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left },\n                { \"     samples\", 14, ColumnInfo::Right },\n                { \"  iterations\", 14, ColumnInfo::Right },\n                { \"        mean\", 14, ColumnInfo::Right }\n            };\n        }\n        else\n        {\n            return{\n                { \"benchmark name\", CATCH_CONFIG_CONSOLE_WIDTH - 43, ColumnInfo::Left },\n                { \"samples      mean       std dev\", 14, ColumnInfo::Right },\n                { \"iterations   low mean   low std dev\", 14, ColumnInfo::Right },\n                { \"estimated    high mean  high std dev\", 14, ColumnInfo::Right }\n            };\n        }\n    }())) {}\nConsoleReporter::~ConsoleReporter() = default;\n\nstd::string ConsoleReporter::getDescription() {\n    return \"Reports test results as plain lines of text\";\n}\n\nvoid ConsoleReporter::noMatchingTestCases(std::string const& spec) {\n    stream << \"No test cases matched '\" << spec << '\\'' << std::endl;\n}\n\nvoid ConsoleReporter::reportInvalidArguments(std::string const&arg){\n    stream << \"Invalid Filter: \" << arg << std::endl;\n}\n\nvoid ConsoleReporter::assertionStarting(AssertionInfo const&) {}\n\nbool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) {\n    AssertionResult const& result = _assertionStats.assertionResult;\n\n    bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();\n\n    // Drop out if result was successful but we're not printing them.\n    if (!includeResults && result.getResultType() != ResultWas::Warning)\n        return false;\n\n    lazyPrint();\n\n    ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults);\n    printer.print();\n    stream << std::endl;\n    return true;\n}\n\nvoid ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) {\n    m_tablePrinter->close();\n    m_headerPrinted = false;\n    StreamingReporterBase::sectionStarting(_sectionInfo);\n}\nvoid ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) {\n    m_tablePrinter->close();\n    if (_sectionStats.missingAssertions) {\n        lazyPrint();\n        Colour colour(Colour::ResultError);\n        if (m_sectionStack.size() > 1)\n            stream << \"\\nNo assertions in section\";\n        else\n            stream << \"\\nNo assertions in test case\";\n        stream << \" '\" << _sectionStats.sectionInfo.name << \"'\\n\" << std::endl;\n    }\n    double dur = _sectionStats.durationInSeconds;\n    if (shouldShowDuration(*m_config, dur)) {\n        stream << getFormattedDuration(dur) << \" s: \" << _sectionStats.sectionInfo.name << std::endl;\n    }\n    if (m_headerPrinted) {\n        m_headerPrinted = false;\n    }\n    StreamingReporterBase::sectionEnded(_sectionStats);\n}\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\nvoid ConsoleReporter::benchmarkPreparing(std::string const& name) {\n\tlazyPrintWithoutClosingBenchmarkTable();\n\n\tauto nameCol = Column(name).width(static_cast<std::size_t>(m_tablePrinter->columnInfos()[0].width - 2));\n\n\tbool firstLine = true;\n\tfor (auto line : nameCol) {\n\t\tif (!firstLine)\n\t\t\t(*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak();\n\t\telse\n\t\t\tfirstLine = false;\n\n\t\t(*m_tablePrinter) << line << ColumnBreak();\n\t}\n}\n\nvoid ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) {\n    (*m_tablePrinter) << info.samples << ColumnBreak()\n        << info.iterations << ColumnBreak();\n    if (!m_config->benchmarkNoAnalysis())\n        (*m_tablePrinter) << Duration(info.estimatedDuration) << ColumnBreak();\n}\nvoid ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) {\n    if (m_config->benchmarkNoAnalysis())\n    {\n        (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak();\n    }\n    else\n    {\n        (*m_tablePrinter) << ColumnBreak()\n            << Duration(stats.mean.point.count()) << ColumnBreak()\n            << Duration(stats.mean.lower_bound.count()) << ColumnBreak()\n            << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak()\n            << Duration(stats.standardDeviation.point.count()) << ColumnBreak()\n            << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak()\n            << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak();\n    }\n}\n\nvoid ConsoleReporter::benchmarkFailed(std::string const& error) {\n\tColour colour(Colour::Red);\n    (*m_tablePrinter)\n        << \"Benchmark failed (\" << error << ')'\n        << ColumnBreak() << RowBreak();\n}\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nvoid ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) {\n    m_tablePrinter->close();\n    StreamingReporterBase::testCaseEnded(_testCaseStats);\n    m_headerPrinted = false;\n}\nvoid ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) {\n    if (currentGroupInfo.used) {\n        printSummaryDivider();\n        stream << \"Summary for group '\" << _testGroupStats.groupInfo.name << \"':\\n\";\n        printTotals(_testGroupStats.totals);\n        stream << '\\n' << std::endl;\n    }\n    StreamingReporterBase::testGroupEnded(_testGroupStats);\n}\nvoid ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) {\n    printTotalsDivider(_testRunStats.totals);\n    printTotals(_testRunStats.totals);\n    stream << std::endl;\n    StreamingReporterBase::testRunEnded(_testRunStats);\n}\nvoid ConsoleReporter::testRunStarting(TestRunInfo const& _testInfo) {\n    StreamingReporterBase::testRunStarting(_testInfo);\n    printTestFilters();\n}\n\nvoid ConsoleReporter::lazyPrint() {\n\n    m_tablePrinter->close();\n    lazyPrintWithoutClosingBenchmarkTable();\n}\n\nvoid ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {\n\n    if (!currentTestRunInfo.used)\n        lazyPrintRunInfo();\n    if (!currentGroupInfo.used)\n        lazyPrintGroupInfo();\n\n    if (!m_headerPrinted) {\n        printTestCaseAndSectionHeader();\n        m_headerPrinted = true;\n    }\n}\nvoid ConsoleReporter::lazyPrintRunInfo() {\n    stream << '\\n' << getLineOfChars<'~'>() << '\\n';\n    Colour colour(Colour::SecondaryText);\n    stream << currentTestRunInfo->name\n        << \" is a Catch v\" << libraryVersion() << \" host application.\\n\"\n        << \"Run with -? for options\\n\\n\";\n\n    if (m_config->rngSeed() != 0)\n        stream << \"Randomness seeded to: \" << m_config->rngSeed() << \"\\n\\n\";\n\n    currentTestRunInfo.used = true;\n}\nvoid ConsoleReporter::lazyPrintGroupInfo() {\n    if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) {\n        printClosedHeader(\"Group: \" + currentGroupInfo->name);\n        currentGroupInfo.used = true;\n    }\n}\nvoid ConsoleReporter::printTestCaseAndSectionHeader() {\n    assert(!m_sectionStack.empty());\n    printOpenHeader(currentTestCaseInfo->name);\n\n    if (m_sectionStack.size() > 1) {\n        Colour colourGuard(Colour::Headers);\n\n        auto\n            it = m_sectionStack.begin() + 1, // Skip first section (test case)\n            itEnd = m_sectionStack.end();\n        for (; it != itEnd; ++it)\n            printHeaderString(it->name, 2);\n    }\n\n    SourceLineInfo lineInfo = m_sectionStack.back().lineInfo;\n\n    stream << getLineOfChars<'-'>() << '\\n';\n    Colour colourGuard(Colour::FileName);\n    stream << lineInfo << '\\n';\n    stream << getLineOfChars<'.'>() << '\\n' << std::endl;\n}\n\nvoid ConsoleReporter::printClosedHeader(std::string const& _name) {\n    printOpenHeader(_name);\n    stream << getLineOfChars<'.'>() << '\\n';\n}\nvoid ConsoleReporter::printOpenHeader(std::string const& _name) {\n    stream << getLineOfChars<'-'>() << '\\n';\n    {\n        Colour colourGuard(Colour::Headers);\n        printHeaderString(_name);\n    }\n}\n\n// if string has a : in first line will set indent to follow it on\n// subsequent lines\nvoid ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) {\n    std::size_t i = _string.find(\": \");\n    if (i != std::string::npos)\n        i += 2;\n    else\n        i = 0;\n    stream << Column(_string).indent(indent + i).initialIndent(indent) << '\\n';\n}\n\nstruct SummaryColumn {\n\n    SummaryColumn( std::string _label, Colour::Code _colour )\n    :   label( std::move( _label ) ),\n        colour( _colour ) {}\n    SummaryColumn addRow( std::size_t count ) {\n        ReusableStringStream rss;\n        rss << count;\n        std::string row = rss.str();\n        for (auto& oldRow : rows) {\n            while (oldRow.size() < row.size())\n                oldRow = ' ' + oldRow;\n            while (oldRow.size() > row.size())\n                row = ' ' + row;\n        }\n        rows.push_back(row);\n        return *this;\n    }\n\n    std::string label;\n    Colour::Code colour;\n    std::vector<std::string> rows;\n\n};\n\nvoid ConsoleReporter::printTotals( Totals const& totals ) {\n    if (totals.testCases.total() == 0) {\n        stream << Colour(Colour::Warning) << \"No tests ran\\n\";\n    } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) {\n        stream << Colour(Colour::ResultSuccess) << \"All tests passed\";\n        stream << \" (\"\n            << pluralise(totals.assertions.passed, \"assertion\") << \" in \"\n            << pluralise(totals.testCases.passed, \"test case\") << ')'\n            << '\\n';\n    } else {\n\n        std::vector<SummaryColumn> columns;\n        columns.push_back(SummaryColumn(\"\", Colour::None)\n                          .addRow(totals.testCases.total())\n                          .addRow(totals.assertions.total()));\n        columns.push_back(SummaryColumn(\"passed\", Colour::Success)\n                          .addRow(totals.testCases.passed)\n                          .addRow(totals.assertions.passed));\n        columns.push_back(SummaryColumn(\"failed\", Colour::ResultError)\n                          .addRow(totals.testCases.failed)\n                          .addRow(totals.assertions.failed));\n        columns.push_back(SummaryColumn(\"failed as expected\", Colour::ResultExpectedFailure)\n                          .addRow(totals.testCases.failedButOk)\n                          .addRow(totals.assertions.failedButOk));\n\n        printSummaryRow(\"test cases\", columns, 0);\n        printSummaryRow(\"assertions\", columns, 1);\n    }\n}\nvoid ConsoleReporter::printSummaryRow(std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row) {\n    for (auto col : cols) {\n        std::string value = col.rows[row];\n        if (col.label.empty()) {\n            stream << label << \": \";\n            if (value != \"0\")\n                stream << value;\n            else\n                stream << Colour(Colour::Warning) << \"- none -\";\n        } else if (value != \"0\") {\n            stream << Colour(Colour::LightGrey) << \" | \";\n            stream << Colour(col.colour)\n                << value << ' ' << col.label;\n        }\n    }\n    stream << '\\n';\n}\n\nvoid ConsoleReporter::printTotalsDivider(Totals const& totals) {\n    if (totals.testCases.total() > 0) {\n        std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total());\n        std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total());\n        std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total());\n        while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1)\n            findMax(failedRatio, failedButOkRatio, passedRatio)++;\n        while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1)\n            findMax(failedRatio, failedButOkRatio, passedRatio)--;\n\n        stream << Colour(Colour::Error) << std::string(failedRatio, '=');\n        stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '=');\n        if (totals.testCases.allPassed())\n            stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '=');\n        else\n            stream << Colour(Colour::Success) << std::string(passedRatio, '=');\n    } else {\n        stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '=');\n    }\n    stream << '\\n';\n}\nvoid ConsoleReporter::printSummaryDivider() {\n    stream << getLineOfChars<'-'>() << '\\n';\n}\n\nvoid ConsoleReporter::printTestFilters() {\n    if (m_config->testSpec().hasFilters()) {\n        Colour guard(Colour::BrightYellow);\n        stream << \"Filters: \" << serializeFilters(m_config->getTestsOrTags()) << '\\n';\n    }\n}\n\nCATCH_REGISTER_REPORTER(\"console\", ConsoleReporter)\n\n} // end namespace Catch\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\n#if defined(__clang__)\n#  pragma clang diagnostic pop\n#endif\n// end catch_reporter_console.cpp\n// start catch_reporter_junit.cpp\n\n#include <cassert>\n#include <sstream>\n#include <ctime>\n#include <algorithm>\n#include <iomanip>\n\nnamespace Catch {\n\n    namespace {\n        std::string getCurrentTimestamp() {\n            // Beware, this is not reentrant because of backward compatibility issues\n            // Also, UTC only, again because of backward compatibility (%z is C++11)\n            time_t rawtime;\n            std::time(&rawtime);\n            auto const timeStampSize = sizeof(\"2017-01-16T17:06:45Z\");\n\n#ifdef _MSC_VER\n            std::tm timeInfo = {};\n            gmtime_s(&timeInfo, &rawtime);\n#else\n            std::tm* timeInfo;\n            timeInfo = std::gmtime(&rawtime);\n#endif\n\n            char timeStamp[timeStampSize];\n            const char * const fmt = \"%Y-%m-%dT%H:%M:%SZ\";\n\n#ifdef _MSC_VER\n            std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);\n#else\n            std::strftime(timeStamp, timeStampSize, fmt, timeInfo);\n#endif\n            return std::string(timeStamp, timeStampSize-1);\n        }\n\n        std::string fileNameTag(const std::vector<std::string> &tags) {\n            auto it = std::find_if(begin(tags),\n                                   end(tags),\n                                   [] (std::string const& tag) {return tag.front() == '#'; });\n            if (it != tags.end())\n                return it->substr(1);\n            return std::string();\n        }\n\n        // Formats the duration in seconds to 3 decimal places.\n        // This is done because some genius defined Maven Surefire schema\n        // in a way that only accepts 3 decimal places, and tools like\n        // Jenkins use that schema for validation JUnit reporter output.\n        std::string formatDuration( double seconds ) {\n            ReusableStringStream rss;\n            rss << std::fixed << std::setprecision( 3 ) << seconds;\n            return rss.str();\n        }\n\n    } // anonymous namespace\n\n    JunitReporter::JunitReporter( ReporterConfig const& _config )\n        :   CumulativeReporterBase( _config ),\n            xml( _config.stream() )\n        {\n            m_reporterPrefs.shouldRedirectStdOut = true;\n            m_reporterPrefs.shouldReportAllAssertions = true;\n        }\n\n    JunitReporter::~JunitReporter() {}\n\n    std::string JunitReporter::getDescription() {\n        return \"Reports test results in an XML format that looks like Ant's junitreport target\";\n    }\n\n    void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {}\n\n    void JunitReporter::testRunStarting( TestRunInfo const& runInfo )  {\n        CumulativeReporterBase::testRunStarting( runInfo );\n        xml.startElement( \"testsuites\" );\n    }\n\n    void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) {\n        suiteTimer.start();\n        stdOutForSuite.clear();\n        stdErrForSuite.clear();\n        unexpectedExceptions = 0;\n        CumulativeReporterBase::testGroupStarting( groupInfo );\n    }\n\n    void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) {\n        m_okToFail = testCaseInfo.okToFail();\n    }\n\n    bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) {\n        if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )\n            unexpectedExceptions++;\n        return CumulativeReporterBase::assertionEnded( assertionStats );\n    }\n\n    void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {\n        stdOutForSuite += testCaseStats.stdOut;\n        stdErrForSuite += testCaseStats.stdErr;\n        CumulativeReporterBase::testCaseEnded( testCaseStats );\n    }\n\n    void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {\n        double suiteTime = suiteTimer.getElapsedSeconds();\n        CumulativeReporterBase::testGroupEnded( testGroupStats );\n        writeGroup( *m_testGroups.back(), suiteTime );\n    }\n\n    void JunitReporter::testRunEndedCumulative() {\n        xml.endElement();\n    }\n\n    void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) {\n        XmlWriter::ScopedElement e = xml.scopedElement( \"testsuite\" );\n\n        TestGroupStats const& stats = groupNode.value;\n        xml.writeAttribute( \"name\", stats.groupInfo.name );\n        xml.writeAttribute( \"errors\", unexpectedExceptions );\n        xml.writeAttribute( \"failures\", stats.totals.assertions.failed-unexpectedExceptions );\n        xml.writeAttribute( \"tests\", stats.totals.assertions.total() );\n        xml.writeAttribute( \"hostname\", \"tbd\" ); // !TBD\n        if( m_config->showDurations() == ShowDurations::Never )\n            xml.writeAttribute( \"time\", \"\" );\n        else\n            xml.writeAttribute( \"time\", formatDuration( suiteTime ) );\n        xml.writeAttribute( \"timestamp\", getCurrentTimestamp() );\n\n        // Write properties if there are any\n        if (m_config->hasTestFilters() || m_config->rngSeed() != 0) {\n            auto properties = xml.scopedElement(\"properties\");\n            if (m_config->hasTestFilters()) {\n                xml.scopedElement(\"property\")\n                    .writeAttribute(\"name\", \"filters\")\n                    .writeAttribute(\"value\", serializeFilters(m_config->getTestsOrTags()));\n            }\n            if (m_config->rngSeed() != 0) {\n                xml.scopedElement(\"property\")\n                    .writeAttribute(\"name\", \"random-seed\")\n                    .writeAttribute(\"value\", m_config->rngSeed());\n            }\n        }\n\n        // Write test cases\n        for( auto const& child : groupNode.children )\n            writeTestCase( *child );\n\n        xml.scopedElement( \"system-out\" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline );\n        xml.scopedElement( \"system-err\" ).writeText( trim( stdErrForSuite ), XmlFormatting::Newline );\n    }\n\n    void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) {\n        TestCaseStats const& stats = testCaseNode.value;\n\n        // All test cases have exactly one section - which represents the\n        // test case itself. That section may have 0-n nested sections\n        assert( testCaseNode.children.size() == 1 );\n        SectionNode const& rootSection = *testCaseNode.children.front();\n\n        std::string className = stats.testInfo.className;\n\n        if( className.empty() ) {\n            className = fileNameTag(stats.testInfo.tags);\n            if ( className.empty() )\n                className = \"global\";\n        }\n\n        if ( !m_config->name().empty() )\n            className = m_config->name() + \".\" + className;\n\n        writeSection( className, \"\", rootSection, stats.testInfo.okToFail() );\n    }\n\n    void JunitReporter::writeSection( std::string const& className,\n                                      std::string const& rootName,\n                                      SectionNode const& sectionNode,\n                                      bool testOkToFail) {\n        std::string name = trim( sectionNode.stats.sectionInfo.name );\n        if( !rootName.empty() )\n            name = rootName + '/' + name;\n\n        if( !sectionNode.assertions.empty() ||\n            !sectionNode.stdOut.empty() ||\n            !sectionNode.stdErr.empty() ) {\n            XmlWriter::ScopedElement e = xml.scopedElement( \"testcase\" );\n            if( className.empty() ) {\n                xml.writeAttribute( \"classname\", name );\n                xml.writeAttribute( \"name\", \"root\" );\n            }\n            else {\n                xml.writeAttribute( \"classname\", className );\n                xml.writeAttribute( \"name\", name );\n            }\n            xml.writeAttribute( \"time\", formatDuration( sectionNode.stats.durationInSeconds ) );\n            // This is not ideal, but it should be enough to mimic gtest's\n            // junit output.\n            // Ideally the JUnit reporter would also handle `skipTest`\n            // events and write those out appropriately.\n            xml.writeAttribute( \"status\", \"run\" );\n\n            if (sectionNode.stats.assertions.failedButOk) {\n                xml.scopedElement(\"skipped\")\n                    .writeAttribute(\"message\", \"TEST_CASE tagged with !mayfail\");\n            }\n\n            writeAssertions( sectionNode );\n\n            if( !sectionNode.stdOut.empty() )\n                xml.scopedElement( \"system-out\" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline );\n            if( !sectionNode.stdErr.empty() )\n                xml.scopedElement( \"system-err\" ).writeText( trim( sectionNode.stdErr ), XmlFormatting::Newline );\n        }\n        for( auto const& childNode : sectionNode.childSections )\n            if( className.empty() )\n                writeSection( name, \"\", *childNode, testOkToFail );\n            else\n                writeSection( className, name, *childNode, testOkToFail );\n    }\n\n    void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {\n        for( auto const& assertion : sectionNode.assertions )\n            writeAssertion( assertion );\n    }\n\n    void JunitReporter::writeAssertion( AssertionStats const& stats ) {\n        AssertionResult const& result = stats.assertionResult;\n        if( !result.isOk() ) {\n            std::string elementName;\n            switch( result.getResultType() ) {\n                case ResultWas::ThrewException:\n                case ResultWas::FatalErrorCondition:\n                    elementName = \"error\";\n                    break;\n                case ResultWas::ExplicitFailure:\n                case ResultWas::ExpressionFailed:\n                case ResultWas::DidntThrowException:\n                    elementName = \"failure\";\n                    break;\n\n                // We should never see these here:\n                case ResultWas::Info:\n                case ResultWas::Warning:\n                case ResultWas::Ok:\n                case ResultWas::Unknown:\n                case ResultWas::FailureBit:\n                case ResultWas::Exception:\n                    elementName = \"internalError\";\n                    break;\n            }\n\n            XmlWriter::ScopedElement e = xml.scopedElement( elementName );\n\n            xml.writeAttribute( \"message\", result.getExpression() );\n            xml.writeAttribute( \"type\", result.getTestMacroName() );\n\n            ReusableStringStream rss;\n            if (stats.totals.assertions.total() > 0) {\n                rss << \"FAILED\" << \":\\n\";\n                if (result.hasExpression()) {\n                    rss << \"  \";\n                    rss << result.getExpressionInMacro();\n                    rss << '\\n';\n                }\n                if (result.hasExpandedExpression()) {\n                    rss << \"with expansion:\\n\";\n                    rss << Column(result.getExpandedExpression()).indent(2) << '\\n';\n                }\n            } else {\n                rss << '\\n';\n            }\n\n            if( !result.getMessage().empty() )\n                rss << result.getMessage() << '\\n';\n            for( auto const& msg : stats.infoMessages )\n                if( msg.type == ResultWas::Info )\n                    rss << msg.message << '\\n';\n\n            rss << \"at \" << result.getSourceInfo();\n            xml.writeText( rss.str(), XmlFormatting::Newline );\n        }\n    }\n\n    CATCH_REGISTER_REPORTER( \"junit\", JunitReporter )\n\n} // end namespace Catch\n// end catch_reporter_junit.cpp\n// start catch_reporter_listening.cpp\n\n#include <cassert>\n\nnamespace Catch {\n\n    ListeningReporter::ListeningReporter() {\n        // We will assume that listeners will always want all assertions\n        m_preferences.shouldReportAllAssertions = true;\n    }\n\n    void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) {\n        m_listeners.push_back( std::move( listener ) );\n    }\n\n    void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) {\n        assert(!m_reporter && \"Listening reporter can wrap only 1 real reporter\");\n        m_reporter = std::move( reporter );\n        m_preferences.shouldRedirectStdOut = m_reporter->getPreferences().shouldRedirectStdOut;\n    }\n\n    ReporterPreferences ListeningReporter::getPreferences() const {\n        return m_preferences;\n    }\n\n    std::set<Verbosity> ListeningReporter::getSupportedVerbosities() {\n        return std::set<Verbosity>{ };\n    }\n\n    void ListeningReporter::noMatchingTestCases( std::string const& spec ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->noMatchingTestCases( spec );\n        }\n        m_reporter->noMatchingTestCases( spec );\n    }\n\n    void ListeningReporter::reportInvalidArguments(std::string const&arg){\n        for ( auto const& listener : m_listeners ) {\n            listener->reportInvalidArguments( arg );\n        }\n        m_reporter->reportInvalidArguments( arg );\n    }\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    void ListeningReporter::benchmarkPreparing( std::string const& name ) {\n\t\tfor (auto const& listener : m_listeners) {\n\t\t\tlistener->benchmarkPreparing(name);\n\t\t}\n\t\tm_reporter->benchmarkPreparing(name);\n\t}\n    void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->benchmarkStarting( benchmarkInfo );\n        }\n        m_reporter->benchmarkStarting( benchmarkInfo );\n    }\n    void ListeningReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->benchmarkEnded( benchmarkStats );\n        }\n        m_reporter->benchmarkEnded( benchmarkStats );\n    }\n\n\tvoid ListeningReporter::benchmarkFailed( std::string const& error ) {\n\t\tfor (auto const& listener : m_listeners) {\n\t\t\tlistener->benchmarkFailed(error);\n\t\t}\n\t\tm_reporter->benchmarkFailed(error);\n\t}\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testRunStarting( testRunInfo );\n        }\n        m_reporter->testRunStarting( testRunInfo );\n    }\n\n    void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testGroupStarting( groupInfo );\n        }\n        m_reporter->testGroupStarting( groupInfo );\n    }\n\n    void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testCaseStarting( testInfo );\n        }\n        m_reporter->testCaseStarting( testInfo );\n    }\n\n    void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->sectionStarting( sectionInfo );\n        }\n        m_reporter->sectionStarting( sectionInfo );\n    }\n\n    void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->assertionStarting( assertionInfo );\n        }\n        m_reporter->assertionStarting( assertionInfo );\n    }\n\n    // The return value indicates if the messages buffer should be cleared:\n    bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) {\n        for( auto const& listener : m_listeners ) {\n            static_cast<void>( listener->assertionEnded( assertionStats ) );\n        }\n        return m_reporter->assertionEnded( assertionStats );\n    }\n\n    void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->sectionEnded( sectionStats );\n        }\n        m_reporter->sectionEnded( sectionStats );\n    }\n\n    void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testCaseEnded( testCaseStats );\n        }\n        m_reporter->testCaseEnded( testCaseStats );\n    }\n\n    void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testGroupEnded( testGroupStats );\n        }\n        m_reporter->testGroupEnded( testGroupStats );\n    }\n\n    void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testRunEnded( testRunStats );\n        }\n        m_reporter->testRunEnded( testRunStats );\n    }\n\n    void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->skipTest( testInfo );\n        }\n        m_reporter->skipTest( testInfo );\n    }\n\n    bool ListeningReporter::isMulti() const {\n        return true;\n    }\n\n} // end namespace Catch\n// end catch_reporter_listening.cpp\n// start catch_reporter_xml.cpp\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch\n                              // Note that 4062 (not all labels are handled\n                              // and default is missing) is enabled\n#endif\n\nnamespace Catch {\n    XmlReporter::XmlReporter( ReporterConfig const& _config )\n    :   StreamingReporterBase( _config ),\n        m_xml(_config.stream())\n    {\n        m_reporterPrefs.shouldRedirectStdOut = true;\n        m_reporterPrefs.shouldReportAllAssertions = true;\n    }\n\n    XmlReporter::~XmlReporter() = default;\n\n    std::string XmlReporter::getDescription() {\n        return \"Reports test results as an XML document\";\n    }\n\n    std::string XmlReporter::getStylesheetRef() const {\n        return std::string();\n    }\n\n    void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) {\n        m_xml\n            .writeAttribute( \"filename\", sourceInfo.file )\n            .writeAttribute( \"line\", sourceInfo.line );\n    }\n\n    void XmlReporter::noMatchingTestCases( std::string const& s ) {\n        StreamingReporterBase::noMatchingTestCases( s );\n    }\n\n    void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) {\n        StreamingReporterBase::testRunStarting( testInfo );\n        std::string stylesheetRef = getStylesheetRef();\n        if( !stylesheetRef.empty() )\n            m_xml.writeStylesheetRef( stylesheetRef );\n        m_xml.startElement( \"Catch\" );\n        if( !m_config->name().empty() )\n            m_xml.writeAttribute( \"name\", m_config->name() );\n        if (m_config->testSpec().hasFilters())\n            m_xml.writeAttribute( \"filters\", serializeFilters( m_config->getTestsOrTags() ) );\n        if( m_config->rngSeed() != 0 )\n            m_xml.scopedElement( \"Randomness\" )\n                .writeAttribute( \"seed\", m_config->rngSeed() );\n    }\n\n    void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) {\n        StreamingReporterBase::testGroupStarting( groupInfo );\n        m_xml.startElement( \"Group\" )\n            .writeAttribute( \"name\", groupInfo.name );\n    }\n\n    void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) {\n        StreamingReporterBase::testCaseStarting(testInfo);\n        m_xml.startElement( \"TestCase\" )\n            .writeAttribute( \"name\", trim( testInfo.name ) )\n            .writeAttribute( \"description\", testInfo.description )\n            .writeAttribute( \"tags\", testInfo.tagsAsString() );\n\n        writeSourceInfo( testInfo.lineInfo );\n\n        if ( m_config->showDurations() == ShowDurations::Always )\n            m_testCaseTimer.start();\n        m_xml.ensureTagClosed();\n    }\n\n    void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) {\n        StreamingReporterBase::sectionStarting( sectionInfo );\n        if( m_sectionDepth++ > 0 ) {\n            m_xml.startElement( \"Section\" )\n                .writeAttribute( \"name\", trim( sectionInfo.name ) );\n            writeSourceInfo( sectionInfo.lineInfo );\n            m_xml.ensureTagClosed();\n        }\n    }\n\n    void XmlReporter::assertionStarting( AssertionInfo const& ) { }\n\n    bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) {\n\n        AssertionResult const& result = assertionStats.assertionResult;\n\n        bool includeResults = m_config->includeSuccessfulResults() || !result.isOk();\n\n        if( includeResults || result.getResultType() == ResultWas::Warning ) {\n            // Print any info messages in <Info> tags.\n            for( auto const& msg : assertionStats.infoMessages ) {\n                if( msg.type == ResultWas::Info && includeResults ) {\n                    m_xml.scopedElement( \"Info\" )\n                            .writeText( msg.message );\n                } else if ( msg.type == ResultWas::Warning ) {\n                    m_xml.scopedElement( \"Warning\" )\n                            .writeText( msg.message );\n                }\n            }\n        }\n\n        // Drop out if result was successful but we're not printing them.\n        if( !includeResults && result.getResultType() != ResultWas::Warning )\n            return true;\n\n        // Print the expression if there is one.\n        if( result.hasExpression() ) {\n            m_xml.startElement( \"Expression\" )\n                .writeAttribute( \"success\", result.succeeded() )\n                .writeAttribute( \"type\", result.getTestMacroName() );\n\n            writeSourceInfo( result.getSourceInfo() );\n\n            m_xml.scopedElement( \"Original\" )\n                .writeText( result.getExpression() );\n            m_xml.scopedElement( \"Expanded\" )\n                .writeText( result.getExpandedExpression() );\n        }\n\n        // And... Print a result applicable to each result type.\n        switch( result.getResultType() ) {\n            case ResultWas::ThrewException:\n                m_xml.startElement( \"Exception\" );\n                writeSourceInfo( result.getSourceInfo() );\n                m_xml.writeText( result.getMessage() );\n                m_xml.endElement();\n                break;\n            case ResultWas::FatalErrorCondition:\n                m_xml.startElement( \"FatalErrorCondition\" );\n                writeSourceInfo( result.getSourceInfo() );\n                m_xml.writeText( result.getMessage() );\n                m_xml.endElement();\n                break;\n            case ResultWas::Info:\n                m_xml.scopedElement( \"Info\" )\n                    .writeText( result.getMessage() );\n                break;\n            case ResultWas::Warning:\n                // Warning will already have been written\n                break;\n            case ResultWas::ExplicitFailure:\n                m_xml.startElement( \"Failure\" );\n                writeSourceInfo( result.getSourceInfo() );\n                m_xml.writeText( result.getMessage() );\n                m_xml.endElement();\n                break;\n            default:\n                break;\n        }\n\n        if( result.hasExpression() )\n            m_xml.endElement();\n\n        return true;\n    }\n\n    void XmlReporter::sectionEnded( SectionStats const& sectionStats ) {\n        StreamingReporterBase::sectionEnded( sectionStats );\n        if( --m_sectionDepth > 0 ) {\n            XmlWriter::ScopedElement e = m_xml.scopedElement( \"OverallResults\" );\n            e.writeAttribute( \"successes\", sectionStats.assertions.passed );\n            e.writeAttribute( \"failures\", sectionStats.assertions.failed );\n            e.writeAttribute( \"expectedFailures\", sectionStats.assertions.failedButOk );\n\n            if ( m_config->showDurations() == ShowDurations::Always )\n                e.writeAttribute( \"durationInSeconds\", sectionStats.durationInSeconds );\n\n            m_xml.endElement();\n        }\n    }\n\n    void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {\n        StreamingReporterBase::testCaseEnded( testCaseStats );\n        XmlWriter::ScopedElement e = m_xml.scopedElement( \"OverallResult\" );\n        e.writeAttribute( \"success\", testCaseStats.totals.assertions.allOk() );\n\n        if ( m_config->showDurations() == ShowDurations::Always )\n            e.writeAttribute( \"durationInSeconds\", m_testCaseTimer.getElapsedSeconds() );\n\n        if( !testCaseStats.stdOut.empty() )\n            m_xml.scopedElement( \"StdOut\" ).writeText( trim( testCaseStats.stdOut ), XmlFormatting::Newline );\n        if( !testCaseStats.stdErr.empty() )\n            m_xml.scopedElement( \"StdErr\" ).writeText( trim( testCaseStats.stdErr ), XmlFormatting::Newline );\n\n        m_xml.endElement();\n    }\n\n    void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {\n        StreamingReporterBase::testGroupEnded( testGroupStats );\n        // TODO: Check testGroupStats.aborting and act accordingly.\n        m_xml.scopedElement( \"OverallResults\" )\n            .writeAttribute( \"successes\", testGroupStats.totals.assertions.passed )\n            .writeAttribute( \"failures\", testGroupStats.totals.assertions.failed )\n            .writeAttribute( \"expectedFailures\", testGroupStats.totals.assertions.failedButOk );\n        m_xml.scopedElement( \"OverallResultsCases\")\n            .writeAttribute( \"successes\", testGroupStats.totals.testCases.passed )\n            .writeAttribute( \"failures\", testGroupStats.totals.testCases.failed )\n            .writeAttribute( \"expectedFailures\", testGroupStats.totals.testCases.failedButOk );\n        m_xml.endElement();\n    }\n\n    void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) {\n        StreamingReporterBase::testRunEnded( testRunStats );\n        m_xml.scopedElement( \"OverallResults\" )\n            .writeAttribute( \"successes\", testRunStats.totals.assertions.passed )\n            .writeAttribute( \"failures\", testRunStats.totals.assertions.failed )\n            .writeAttribute( \"expectedFailures\", testRunStats.totals.assertions.failedButOk );\n        m_xml.scopedElement( \"OverallResultsCases\")\n            .writeAttribute( \"successes\", testRunStats.totals.testCases.passed )\n            .writeAttribute( \"failures\", testRunStats.totals.testCases.failed )\n            .writeAttribute( \"expectedFailures\", testRunStats.totals.testCases.failedButOk );\n        m_xml.endElement();\n    }\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    void XmlReporter::benchmarkPreparing(std::string const& name) {\n        m_xml.startElement(\"BenchmarkResults\")\n            .writeAttribute(\"name\", name);\n    }\n\n    void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) {\n        m_xml.writeAttribute(\"samples\", info.samples)\n            .writeAttribute(\"resamples\", info.resamples)\n            .writeAttribute(\"iterations\", info.iterations)\n            .writeAttribute(\"clockResolution\", info.clockResolution)\n            .writeAttribute(\"estimatedDuration\", info.estimatedDuration)\n            .writeComment(\"All values in nano seconds\");\n    }\n\n    void XmlReporter::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) {\n        m_xml.startElement(\"mean\")\n            .writeAttribute(\"value\", benchmarkStats.mean.point.count())\n            .writeAttribute(\"lowerBound\", benchmarkStats.mean.lower_bound.count())\n            .writeAttribute(\"upperBound\", benchmarkStats.mean.upper_bound.count())\n            .writeAttribute(\"ci\", benchmarkStats.mean.confidence_interval);\n        m_xml.endElement();\n        m_xml.startElement(\"standardDeviation\")\n            .writeAttribute(\"value\", benchmarkStats.standardDeviation.point.count())\n            .writeAttribute(\"lowerBound\", benchmarkStats.standardDeviation.lower_bound.count())\n            .writeAttribute(\"upperBound\", benchmarkStats.standardDeviation.upper_bound.count())\n            .writeAttribute(\"ci\", benchmarkStats.standardDeviation.confidence_interval);\n        m_xml.endElement();\n        m_xml.startElement(\"outliers\")\n            .writeAttribute(\"variance\", benchmarkStats.outlierVariance)\n            .writeAttribute(\"lowMild\", benchmarkStats.outliers.low_mild)\n            .writeAttribute(\"lowSevere\", benchmarkStats.outliers.low_severe)\n            .writeAttribute(\"highMild\", benchmarkStats.outliers.high_mild)\n            .writeAttribute(\"highSevere\", benchmarkStats.outliers.high_severe);\n        m_xml.endElement();\n        m_xml.endElement();\n    }\n\n    void XmlReporter::benchmarkFailed(std::string const &error) {\n        m_xml.scopedElement(\"failed\").\n            writeAttribute(\"message\", error);\n        m_xml.endElement();\n    }\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    CATCH_REGISTER_REPORTER( \"xml\", XmlReporter )\n\n} // end namespace Catch\n\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n// end catch_reporter_xml.cpp\n\nnamespace Catch {\n    LeakDetector leakDetector;\n}\n\n#ifdef __clang__\n#pragma clang diagnostic pop\n#endif\n\n// end catch_impl.hpp\n#endif\n\n#ifdef CATCH_CONFIG_MAIN\n// start catch_default_main.hpp\n\n#ifndef __OBJC__\n\n#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)\n// Standard C/C++ Win32 Unicode wmain entry point\nextern \"C\" int wmain (int argc, wchar_t * argv[], wchar_t * []) {\n#else\n// Standard C/C++ main entry point\nint main (int argc, char * argv[]) {\n#endif\n\n    return Catch::Session().run( argc, argv );\n}\n\n#else // __OBJC__\n\n// Objective-C entry point\nint main (int argc, char * const argv[]) {\n#if !CATCH_ARC_ENABLED\n    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];\n#endif\n\n    Catch::registerTestMethods();\n    int result = Catch::Session().run( argc, (char**)argv );\n\n#if !CATCH_ARC_ENABLED\n    [pool drain];\n#endif\n\n    return result;\n}\n\n#endif // __OBJC__\n\n// end catch_default_main.hpp\n#endif\n\n#if !defined(CATCH_CONFIG_IMPL_ONLY)\n\n#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED\n#  undef CLARA_CONFIG_MAIN\n#endif\n\n#if !defined(CATCH_CONFIG_DISABLE)\n//////\n// If this config identifier is defined then all CATCH macros are prefixed with CATCH_\n#ifdef CATCH_CONFIG_PREFIX_ALL\n\n#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( \"CATCH_REQUIRE\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( \"CATCH_REQUIRE_FALSE\", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n\n#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( \"CATCH_REQUIRE_THROWS\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"CATCH_REQUIRE_THROWS_AS\", exceptionType, Catch::ResultDisposition::Normal, expr )\n#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"CATCH_REQUIRE_THROWS_WITH\", Catch::ResultDisposition::Normal, matcher, expr )\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"CATCH_REQUIRE_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )\n#endif// CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"CATCH_REQUIRE_NOTHROW\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n\n#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( \"CATCH_CHECK\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( \"CATCH_CHECK_FALSE\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( \"CATCH_CHECKED_IF\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( \"CATCH_CHECKED_ELSE\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( \"CATCH_CHECK_NOFAIL\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n\n#define CATCH_CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( \"CATCH_CHECK_THROWS\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"CATCH_CHECK_THROWS_AS\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )\n#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"CATCH_CHECK_THROWS_WITH\", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"CATCH_CHECK_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"CATCH_CHECK_NOTHROW\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"CATCH_CHECK_THAT\", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )\n\n#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"CATCH_REQUIRE_THAT\", matcher, Catch::ResultDisposition::Normal, arg )\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n\n#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( \"CATCH_INFO\", msg )\n#define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( \"CATCH_UNSCOPED_INFO\", msg )\n#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( \"CATCH_WARN\", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )\n#define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), \"CATCH_CAPTURE\",__VA_ARGS__ )\n\n#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )\n#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )\n#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )\n#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )\n#define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )\n#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( \"CATCH_FAIL\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( \"CATCH_FAIL_CHECK\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( \"CATCH_SUCCEED\", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n\n#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )\n#else\n#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n#endif\n\n#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)\n#define CATCH_STATIC_REQUIRE( ... )       static_assert(   __VA_ARGS__ ,      #__VA_ARGS__ );     CATCH_SUCCEED( #__VA_ARGS__ )\n#define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\" ); CATCH_SUCCEED( #__VA_ARGS__ )\n#else\n#define CATCH_STATIC_REQUIRE( ... )       CATCH_REQUIRE( __VA_ARGS__ )\n#define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ )\n#endif\n\n// \"BDD-style\" convenience wrappers\n#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( \"Scenario: \" __VA_ARGS__ )\n#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, \"Scenario: \" __VA_ARGS__ )\n#define CATCH_GIVEN( desc )     INTERNAL_CATCH_DYNAMIC_SECTION( \"    Given: \" << desc )\n#define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( \"And given: \" << desc )\n#define CATCH_WHEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( \"     When: \" << desc )\n#define CATCH_AND_WHEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( \" And when: \" << desc )\n#define CATCH_THEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( \"     Then: \" << desc )\n#define CATCH_AND_THEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( \"      And: \" << desc )\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n#define CATCH_BENCHMARK(...) \\\n    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))\n#define CATCH_BENCHMARK_ADVANCED(name) \\\n    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), name)\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required\n#else\n\n#define REQUIRE( ... ) INTERNAL_CATCH_TEST( \"REQUIRE\", Catch::ResultDisposition::Normal, __VA_ARGS__  )\n#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( \"REQUIRE_FALSE\", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n\n#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( \"REQUIRE_THROWS\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"REQUIRE_THROWS_AS\", exceptionType, Catch::ResultDisposition::Normal, expr )\n#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"REQUIRE_THROWS_WITH\", Catch::ResultDisposition::Normal, matcher, expr )\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"REQUIRE_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"REQUIRE_NOTHROW\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n\n#define CHECK( ... ) INTERNAL_CATCH_TEST( \"CHECK\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( \"CHECK_FALSE\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( \"CHECKED_IF\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( \"CHECKED_ELSE\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( \"CHECK_NOFAIL\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n\n#define CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( \"CHECK_THROWS\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"CHECK_THROWS_AS\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )\n#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"CHECK_THROWS_WITH\", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"CHECK_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"CHECK_NOTHROW\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"CHECK_THAT\", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )\n\n#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"REQUIRE_THAT\", matcher, Catch::ResultDisposition::Normal, arg )\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n\n#define INFO( msg ) INTERNAL_CATCH_INFO( \"INFO\", msg )\n#define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( \"UNSCOPED_INFO\", msg )\n#define WARN( msg ) INTERNAL_CATCH_MSG( \"WARN\", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )\n#define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), \"CAPTURE\",__VA_ARGS__ )\n\n#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )\n#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )\n#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )\n#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )\n#define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ )\n#define FAIL( ... ) INTERNAL_CATCH_MSG( \"FAIL\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( \"FAIL_CHECK\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define SUCCEED( ... ) INTERNAL_CATCH_MSG( \"SUCCEED\", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE()\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ )\n#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )\n#define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__)\n#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#else\n#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )\n#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) )\n#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) )\n#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n#define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) )\n#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n#endif\n\n#if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)\n#define STATIC_REQUIRE( ... )       static_assert(   __VA_ARGS__,  #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ )\n#define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\" ); SUCCEED( \"!(\" #__VA_ARGS__ \")\" )\n#else\n#define STATIC_REQUIRE( ... )       REQUIRE( __VA_ARGS__ )\n#define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ )\n#endif\n\n#endif\n\n#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature )\n\n// \"BDD-style\" convenience wrappers\n#define SCENARIO( ... ) TEST_CASE( \"Scenario: \" __VA_ARGS__ )\n#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, \"Scenario: \" __VA_ARGS__ )\n\n#define GIVEN( desc )     INTERNAL_CATCH_DYNAMIC_SECTION( \"    Given: \" << desc )\n#define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( \"And given: \" << desc )\n#define WHEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( \"     When: \" << desc )\n#define AND_WHEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( \" And when: \" << desc )\n#define THEN( desc )      INTERNAL_CATCH_DYNAMIC_SECTION( \"     Then: \" << desc )\n#define AND_THEN( desc )  INTERNAL_CATCH_DYNAMIC_SECTION( \"      And: \" << desc )\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n#define BENCHMARK(...) \\\n    INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,))\n#define BENCHMARK_ADVANCED(name) \\\n    INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(C_A_T_C_H_B_E_N_C_H_), name)\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\nusing Catch::Detail::Approx;\n\n#else // CATCH_CONFIG_DISABLE\n\n//////\n// If this config identifier is defined then all CATCH macros are prefixed with CATCH_\n#ifdef CATCH_CONFIG_PREFIX_ALL\n\n#define CATCH_REQUIRE( ... )        (void)(0)\n#define CATCH_REQUIRE_FALSE( ... )  (void)(0)\n\n#define CATCH_REQUIRE_THROWS( ... ) (void)(0)\n#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)\n#define CATCH_REQUIRE_THROWS_WITH( expr, matcher )     (void)(0)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)\n#endif// CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0)\n\n#define CATCH_CHECK( ... )         (void)(0)\n#define CATCH_CHECK_FALSE( ... )   (void)(0)\n#define CATCH_CHECKED_IF( ... )    if (__VA_ARGS__)\n#define CATCH_CHECKED_ELSE( ... )  if (!(__VA_ARGS__))\n#define CATCH_CHECK_NOFAIL( ... )  (void)(0)\n\n#define CATCH_CHECK_THROWS( ... )  (void)(0)\n#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0)\n#define CATCH_CHECK_THROWS_WITH( expr, matcher )     (void)(0)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_CHECK_NOTHROW( ... ) (void)(0)\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THAT( arg, matcher )   (void)(0)\n\n#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0)\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n\n#define CATCH_INFO( msg )          (void)(0)\n#define CATCH_UNSCOPED_INFO( msg ) (void)(0)\n#define CATCH_WARN( msg )          (void)(0)\n#define CATCH_CAPTURE( msg )       (void)(0)\n\n#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n#define CATCH_METHOD_AS_TEST_CASE( method, ... )\n#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0)\n#define CATCH_SECTION( ... )\n#define CATCH_DYNAMIC_SECTION( ... )\n#define CATCH_FAIL( ... ) (void)(0)\n#define CATCH_FAIL_CHECK( ... ) (void)(0)\n#define CATCH_SUCCEED( ... ) (void)(0)\n\n#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#else\n#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) )\n#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#endif\n\n// \"BDD-style\" convenience wrappers\n#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), className )\n#define CATCH_GIVEN( desc )\n#define CATCH_AND_GIVEN( desc )\n#define CATCH_WHEN( desc )\n#define CATCH_AND_WHEN( desc )\n#define CATCH_THEN( desc )\n#define CATCH_AND_THEN( desc )\n\n#define CATCH_STATIC_REQUIRE( ... )       (void)(0)\n#define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0)\n\n// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required\n#else\n\n#define REQUIRE( ... )       (void)(0)\n#define REQUIRE_FALSE( ... ) (void)(0)\n\n#define REQUIRE_THROWS( ... ) (void)(0)\n#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0)\n#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n#define REQUIRE_NOTHROW( ... ) (void)(0)\n\n#define CHECK( ... ) (void)(0)\n#define CHECK_FALSE( ... ) (void)(0)\n#define CHECKED_IF( ... ) if (__VA_ARGS__)\n#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__))\n#define CHECK_NOFAIL( ... ) (void)(0)\n\n#define CHECK_THROWS( ... )  (void)(0)\n#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0)\n#define CHECK_THROWS_WITH( expr, matcher ) (void)(0)\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0)\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n#define CHECK_NOTHROW( ... ) (void)(0)\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THAT( arg, matcher ) (void)(0)\n\n#define REQUIRE_THAT( arg, matcher ) (void)(0)\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n\n#define INFO( msg ) (void)(0)\n#define UNSCOPED_INFO( msg ) (void)(0)\n#define WARN( msg ) (void)(0)\n#define CAPTURE( ... ) (void)(0)\n\n#define TEST_CASE( ... )  INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n#define METHOD_AS_TEST_CASE( method, ... )\n#define REGISTER_TEST_CASE( Function, ... ) (void)(0)\n#define SECTION( ... )\n#define DYNAMIC_SECTION( ... )\n#define FAIL( ... ) (void)(0)\n#define FAIL_CHECK( ... ) (void)(0)\n#define SUCCEED( ... ) (void)(0)\n#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n\n#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__)\n#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)\n#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)\n#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#else\n#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) )\n#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) )\n#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) )\n#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) )\n#define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#endif\n\n#define STATIC_REQUIRE( ... )       (void)(0)\n#define STATIC_REQUIRE_FALSE( ... ) (void)(0)\n\n#endif\n\n#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )\n\n// \"BDD-style\" convenience wrappers\n#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ) )\n#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ), className )\n\n#define GIVEN( desc )\n#define AND_GIVEN( desc )\n#define WHEN( desc )\n#define AND_WHEN( desc )\n#define THEN( desc )\n#define AND_THEN( desc )\n\nusing Catch::Detail::Approx;\n\n#endif\n\n#endif // ! CATCH_CONFIG_IMPL_ONLY\n\n// start catch_reenable_warnings.h\n\n\n#ifdef __clang__\n#    ifdef __ICC // icpc defines the __clang__ macro\n#        pragma warning(pop)\n#    else\n#        pragma clang diagnostic pop\n#    endif\n#elif defined __GNUC__\n#    pragma GCC diagnostic pop\n#endif\n\n// end catch_reenable_warnings.h\n// end catch.hpp\n#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n"
  },
  {
    "path": "tests/catch2/catchMain.cpp",
    "content": "\n//use the main function provided by catch2\n#define CATCH_CONFIG_MAIN\n\n//include the catch2 library\n#include \"./catch.hpp\""
  },
  {
    "path": "tests/helpers/testHelper.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <catch2/catch.hpp>\n#include <catch2/catch.hpp>\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Memory.h>\n\nusing namespace MicroOcpp;\n\nunsigned long mtime = 10000;\nunsigned long custom_timer_cb() {\n    return mtime;\n}\n\nvoid loop() {\n    for (int i = 0; i < 30; i++) {\n        mtime += 100;\n        mocpp_loop();\n    }\n}\n\nclass TestRunListener : public Catch::TestEventListenerBase {\npublic:\n    using Catch::TestEventListenerBase::TestEventListenerBase;\n\n    void testRunEnded( Catch::TestRunStats const& testRunStats ) override {\n        MO_MEM_PRINT_STATS();\n        MO_MEM_DEINIT();\n    }\n};\n\nCATCH_REGISTER_LISTENER(TestRunListener)\n"
  },
  {
    "path": "tests/helpers/testHelper.h",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#ifndef MO_TESTHELPER_H\n#define MO_TESTHELPER_H\n\n#define UNIT_MEM_TAG \"UnitTests\"\n\nextern unsigned long mtime;\nunsigned long custom_timer_cb();\n\nvoid loop();\n\n#endif\n"
  },
  {
    "path": "tests/ocppEngineLifecycle.cpp",
    "content": "// matth-x/MicroOcpp\n// Copyright Matthias Akstaller 2019 - 2024\n// MIT License\n\n#include <MicroOcpp.h>\n#include <MicroOcpp/Core/Connection.h>\n#include <catch2/catch.hpp>\n#include \"./helpers/testHelper.h\"\n\nTEST_CASE( \"Context lifecycle\" ) {\n    printf(\"\\nRun %s\\n\",  \"Context lifecycle\");\n\n    //initialize Context with dummy socket\n    MicroOcpp::LoopbackConnection loopback;\n    mocpp_initialize(loopback);\n\n    SECTION(\"OCPP is initialized\"){\n        REQUIRE( getOcppContext() );\n    }\n\n    mocpp_deinitialize();\n\n    SECTION(\"OCPP is deinitialized\"){\n        REQUIRE( !( getOcppContext() ) );\n    }\n}\n"
  }
]