diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 19f1b397..1d2cf18a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,209 +1,241 @@ name: "Aries-Askar" -"on": +env: + RUST_VERSION: "1.65.0" + CROSS_VERSION: "0.2.4" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: push: - branches: "**" + branches: + - '**' pull_request: - branches: [main] + branches: + - '**' release: types: [created] workflow_dispatch: inputs: - publish: - description: "Publish packages" + publish-binaries: + description: "Publish Binaries to Release (will create a release if no release exists for branch or tag)" + required: true + default: false + type: boolean + publish-python-wrapper: + description: "Publish Python Wrapper to Registries" required: true - default: "false" + default: false + type: boolean + publish-javascript-wrapper: + description: "Publish JavaScript Wrapper to Registries" + required: true + default: false + type: boolean jobs: - check: + checks: name: Run checks strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-11, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: - profile: minimal - toolchain: 1.56 - override: true + toolchain: ${{ env.RUST_VERSION }} components: clippy, rustfmt - name: Cache cargo resources - uses: Swatinem/rust-cache@v1 + uses: Swatinem/rust-cache@v2 with: - sharedKey: check + shared-key: deps cache-on-failure: true - name: Cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + run: cargo fmt --all -- --check - name: Cargo check - uses: actions-rs/cargo@v1 - with: - command: check - args: --workspace - - - name: Debug build - uses: actions-rs/cargo@v1 - with: - command: build - args: --all-targets + run: cargo check --workspace - - if: "runner.os == 'Linux'" - name: Start postgres (Linux) + - if: ${{ runner.os == 'Linux' }} + name: Pre-install cross run: | - sudo systemctl start postgresql.service - pg_isready - sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'postgres'" - echo "POSTGRES_URL=postgres://postgres:postgres@localhost:5432/test-db" >> $GITHUB_ENV - echo "TEST_FEATURES=pg_test" >> $GITHUB_ENV - - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --workspace --features "${{ env.TEST_FEATURES }}" -- --nocapture --test-threads 1 --skip contention - env: - RUST_BACKTRACE: full - # RUST_LOG: debug - - - name: Test askar-crypto no_std - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path ./askar-crypto/Cargo.toml --no-default-features - - - name: Test askar-bbs no_std - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path ./askar-bbs/Cargo.toml --no-default-features - - build-manylinux: - name: Build (manylinux) - needs: [check] + cargo install --bins --git https://github.com/rust-embedded/cross --tag v${{ env.CROSS_VERSION }} cross + tests: + name: Run tests + needs: [checks] strategy: fail-fast: false matrix: - include: - - os: ubuntu-latest - lib: libaries_askar.so - container: andrewwhitehead/manylinux2014-base - - container: ${{ matrix.container }} + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: - profile: minimal - toolchain: 1.56 - override: true + toolchain: ${{ env.RUST_VERSION }} - name: Cache cargo resources - uses: Swatinem/rust-cache@v1 + uses: Swatinem/rust-cache@v2 with: - sharedKey: check - - - name: Build library - env: - BUILD_TARGET: ${{ matrix.target }} - # LIBSQLITE3_FLAGS: SQLITE_DEBUG SQLITE_MEMDEBUG - run: sh ./build.sh - - - name: Upload library artifacts - uses: actions/upload-artifact@v2 - with: - name: library-${{ runner.os }} - path: target/release/${{ matrix.lib }} - - build-native: - name: Build (native) - needs: [check] - - strategy: - fail-fast: false - matrix: - include: - - os: macos-11 - lib: libaries_askar.dylib - target: apple-darwin # creates a universal library - toolchain: nightly-2021-10-21 # beta required for aarch64-apple-darwin target - - os: windows-latest - lib: aries_askar.dll - toolchain: 1.56 - - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout - uses: actions/checkout@v2 + shared-key: deps + save-if: false - - name: Install Rust toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.toolchain }} - override: true + - name: Debug build + run: cargo build --all-targets - - name: Cache cargo resources - uses: Swatinem/rust-cache@v1 - with: - sharedKey: check + - if: ${{ runner.os == 'Linux' }} + name: Start postgres (Linux) + run: | + sudo systemctl start postgresql.service + pg_isready + sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'postgres'" + echo "POSTGRES_URL=postgres://postgres:postgres@localhost:5432/test-db" >> $GITHUB_ENV + echo "TEST_FEATURES=pg_test" >> $GITHUB_ENV - - name: Build library + - name: Run tests + run: cargo test --workspace --features "${{ env.TEST_FEATURES || 'default' }}" -- --nocapture --test-threads 1 --skip contention env: - BUILD_TARGET: ${{ matrix.target }} - BUILD_TOOLCHAIN: ${{ matrix.toolchain }} - # LIBSQLITE3_FLAGS: SQLITE_DEBUG SQLITE_MEMDEBUG - run: sh ./build.sh + RUST_BACKTRACE: full + # RUST_LOG: debug - - name: Upload library artifacts - uses: actions/upload-artifact@v2 - with: - name: library-${{ runner.os }} - path: target/release/${{ matrix.lib }} + - name: Test askar-crypto no default features + run: cargo test --manifest-path ./askar-crypto/Cargo.toml --no-default-features + +# build-release: +# name: Build library +# needs: [checks] +# +# strategy: +# matrix: +# include: +# - architecture: linux-aarch64 +# os: ubuntu-latest +# lib: libaries_askar.so +# target: aarch64-unknown-linux-gnu +# use_cross: true +# - architecture: linux-x86_64 +# os: ubuntu-latest +# lib: libaries_askar.so +# target: x86_64-unknown-linux-gnu +# use_cross: true +# - architecture: darwin-universal +# os: macos-latest +# lib: libaries_askar.dylib +# target: darwin-universal +# # beta or nightly required for aarch64-apple-darwin target +# toolchain: beta +# - architecture: windows-x86_64 +# os: windows-latest +# lib: aries_askar.dll +# target: x86_64-pc-windows-msvc +# +# runs-on: ${{ matrix.os }} +# steps: +# - name: Checkout +# uses: actions/checkout@v3 +# +# - name: Install Rust toolchain +# uses: actions-rs/toolchain@v1 +# with: +# toolchain: ${{ matrix.toolchain || env.RUST_VERSION }} +# +# - name: Cache cargo resources +# uses: Swatinem/rust-cache@v2 +# with: +# shared-key: deps +# save-if: false +# +# - name: Build +# shell: sh +# run: | +# if [ -n "${{ matrix.use_cross }}" ]; then +# cargo install --bins --git https://github.com/rust-embedded/cross --tag v${{ env.CROSS_VERSION }} cross +# cross build --lib --release --target ${{ matrix.target }} +# elif [ "${{ matrix.architecture }}" == "darwin-universal" ]; then +# ./build-universal.sh +# else +# cargo build --lib --release --target ${{ matrix.target }} +# fi +# +# - name: Upload artifacts +# uses: actions/upload-artifact@v3 +# with: +# name: library-${{ matrix.architecture }} +# path: target/${{ matrix.target }}/release/${{ matrix.lib }} +# +# - name: Create artifacts directory +# if: | +# github.event_name == 'release' || +# (github.event_name == 'workflow_dispatch' && github.event.inputs.publish-binaries == 'true') +# run: | +# mkdir release-artifacts +# cp target/${{ matrix.target }}/release/${{ matrix.lib }} release-artifacts/ +# +# - uses: a7ul/tar-action@v1.1.2 +# if: | +# github.event_name == 'release' || +# (github.event_name == 'workflow_dispatch' && github.event.inputs.publish-binaries == 'true') +# with: +# command: c +# cwd: release-artifacts +# files: . +# outPath: "library-${{ matrix.architecture }}.tar.gz" +# +# - name: Add artifacts to release +# if: | +# github.event_name == 'release' || +# (github.event_name == 'workflow_dispatch' && github.event.inputs.publish-binaries == 'true') +# uses: svenstaro/upload-release-action@v2 +# with: +# file: library-${{ matrix.architecture }}.tar.gz +# asset_name: "library-${{ matrix.architecture }}.tar.gz" build-py: - name: Build Python packages - needs: [build-manylinux, build-native] + name: Build and test Python wrapper + needs: [build-release] strategy: - fail-fast: false matrix: - os: [ubuntu-latest, macos-11, windows-latest] - python-version: [3.7] + architecture: + [linux-aarch64, linux-x86_64, darwin-universal, windows-x86_64] + python-version: ["3.8"] include: - os: ubuntu-latest + architecture: linux-aarch64 + plat-name: manylinux2014_aarch64 + - os: ubuntu-latest + architecture: linux-x86_64 plat-name: manylinux2014_x86_64 - - os: macos-11 + - os: macos-latest + architecture: darwin-universal plat-name: macosx_10_9_universal2 # macosx_10_9_x86_64 - os: windows-latest + architecture: windows-x86_64 plat-name: win_amd64 runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -213,12 +245,12 @@ jobs: pip install setuptools wheel twine auditwheel - name: Fetch library artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: - name: library-${{ runner.os }} + name: library-${{ matrix.architecture }} path: wrappers/python/aries_askar/ - - if: "runner.os == 'Linux'" + - if: ${{ runner.os == 'Linux' }} name: Start postgres (Linux) run: | sudo systemctl start postgresql.service @@ -226,17 +258,16 @@ jobs: sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'postgres'" echo "POSTGRES_URL=postgres://postgres:postgres@localhost:5432/test-db" >> $GITHUB_ENV - - name: Build package + - name: Build wheel package shell: sh run: | python setup.py bdist_wheel --python-tag=py3 --plat-name=${{ matrix.plat-name }} working-directory: wrappers/python - - if: "runner.os == 'Linux'" - name: Audit wheel - run: auditwheel show wrappers/python/dist/* - - - name: Test package + - name: Run tests + # FIXME cross platform test the python package + # maybe use the cross docker image? + if: ${{ matrix.architecture != 'linux-aarch64' }} shell: sh run: | pip install pytest pytest-asyncio dist/* @@ -254,20 +285,274 @@ jobs: RUST_BACKTRACE: full # RUST_LOG: debug - - name: Upload python package - uses: actions/upload-artifact@v2 - with: - name: python-${{ runner.os }} - path: wrappers/python/dist/* + - if: ${{ runner.os == 'Linux' }} + name: Audit wheel + run: | + auditwheel show wrappers/python/dist/* | tee auditwheel.log + grep -q manylinux_2_17_ auditwheel.log - if: | - (github.event_name == 'release' || - (github.event_name == 'workflow_dispatch' && - github.event.inputs.publish == 'true')) - name: Publish python package + github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && github.event.inputs.publish-python-wrapper == 'true') + name: Publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | twine upload --skip-existing dist/* working-directory: wrappers/python + + build-javascript: + name: Build and test JavaScript wrapper + needs: [build-release] + runs-on: ubuntu-latest + defaults: + run: + working-directory: wrappers/javascript + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Node.JS 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + + - name: Fetch library artifacts + uses: actions/download-artifact@v3 + with: + name: library-linux-x86_64 + + - name: Install dependencies + run: yarn install + + - name: Build + run: yarn build + + - name: Lint + run: yarn lint + + - name: Check format + run: yarn check-format + + - name: Check types + run: yarn check-types + + - name: Run tests + env: + # binary is downloaded to root of repository + LIB_ARIES_ASKAR_PATH: ../../../ + run: yarn test + + - name: Set NPM config + if: | + github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && github.event.inputs.publish-javascript-wrapper == 'true') + run: | + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc + echo "registry=https://registry.npmjs.org/" >> .npmrc + echo "always-auth=true" >> .npmrc + + # TODO: this will currently publish the version as defined in the package.json. If the version already + # exists on NPM it will skip the publishing. This means if a new version is released, but the version hasn't been + # updated in the package.json files yet, it won't publish to NPM (which is kinda nice). We should however add a check + # to see if the JS version matches the release version (do we want to keep the js and rust version in sync?) + - name: Publish + if: | + github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && github.event.inputs.publish-javascript-wrapper == 'true') + run: npx lerna publish from-package --no-push --no-private --yes --no-git-tag-version + + build-ios: + name: Build library (iOS) + needs: [checks] + runs-on: macos-latest + strategy: + matrix: + target: [aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.RUST_VERSION }} + targets: ${{ matrix.target }} + + - name: Build + run: | + cargo build --lib --release --target ${{matrix.target}} + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: library-${{ matrix.target }} + path: target/${{ matrix.target }}/release/libaries_askar.a + + build-jvm: + name: Build library (JVM) + needs: [checks] + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.RUST_VERSION }} + + - name: Cache cargo resources + uses: Swatinem/rust-cache@v2 + with: + shared-key: deps + save-if: false + + - name: Publish jvm kotlin + working-directory: wrappers/kotlin + run: ./gradlew publishAllPublicationsToGithubRepository + + build-android: + name: Build library (Android) + needs: [checks] + # NB: RUST_VERSION must be 1.64+ here for lower NDK support + + runs-on: ubuntu-latest + + strategy: + matrix: + target: + [ + aarch64-linux-android, + armv7-linux-androideabi, + i686-linux-android, + x86_64-linux-android, + ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.RUST_VERSION }} + + - name: Cache cargo resources + uses: Swatinem/rust-cache@v2 + with: + shared-key: deps + save-if: false + + - name: Build + run: | + cargo install --bins --git https://github.com/rust-embedded/cross --tag v${{ env.CROSS_VERSION }} cross + cross build --lib --release --target ${{matrix.target}} + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: library-${{ matrix.target }} + path: target/${{ matrix.target }}/release/libaries_askar.so + +# create-ios-xcframework: +# name: Create iOS xcframework +# runs-on: macos-latest +# needs: [build-ios] +# +# steps: +# - name: Checkout +# uses: actions/checkout@v3 +# +# - name: Fetch static libraries +# uses: actions/download-artifact@v3 +# +# - run: > +# ./build-xcframework.sh library-aarch64-apple-ios \ +# library-aarch64-apple-ios-sim \ +# library-x86_64-apple-ios \ +# include +# +# - name: Save xcframework +# uses: actions/upload-artifact@v3 +# with: +# name: aries_askar.xcframework +# path: ./out +# +# - uses: geekyeggo/delete-artifact@v2 +# with: +# name: | +# library-aarch64-apple-ios +# library-aarch64-apple-ios-sim +# library-x86_64-apple-ios +# failOnError: false + + create-android-library: + name: Create library (Android) + runs-on: ubuntu-latest + needs: [build-android] + steps: + - name: Fetch libraries + uses: actions/download-artifact@v3 + + - run: | + sudo mkdir ./libs + sudo mv library-aarch64-linux-android ./libs/arm64-v8a + sudo mv library-armv7-linux-androideabi ./libs/armeabi-v7a + sudo mv library-i686-linux-android ./libs/x86 + sudo mv library-x86_64-linux-android ./libs/x86_64 + + - name: Save Android library + uses: actions/upload-artifact@v3 + with: + name: android-libraries + path: ./libs + + - uses: geekyeggo/delete-artifact@v2 + with: + name: | + library-aarch64-linux-android + library-armv7-linux-androideabi + library-i686-linux-android + library-x86_64-linux-android + failOnError: false + + create-ios-android-release-asset: + name: Create iOS and Android release assets + runs-on: ubuntu-latest + needs: + - create-ios-xcframework + - create-android-library + if: | + (github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && + github.event.inputs.publish-binaries == 'true')) + + steps: + - name: Fetch Android libraries + uses: actions/download-artifact@v3 + with: + name: android-libraries + path: mobile/android/ + + - name: Fetch iOS Framework + uses: actions/download-artifact@v3 + with: + name: aries_askar.xcframework + path: mobile/ios/ + + - uses: a7ul/tar-action@v1.1.2 + with: + command: c + files: ./mobile + outPath: "library-ios-android.tar.gz" + + - name: Add library artifacts to release + uses: svenstaro/upload-release-action@v2 + with: + file: library-ios-android.tar.gz + asset_name: "library-ios-android.tar.gz" diff --git a/.gitignore b/.gitignore index 9bb76df8..821b8191 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,11 @@ .vscode target +*.bak.db *.db-shm *.db-wal Cargo.lock +.DS_Store +*.tgz +*.so +.npmrc +out diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..82defcd4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,166 @@ +# [Hyperledger Code of Conduct](https://wiki.hyperledger.org/community/hyperledger-project-code-of-conduct) + +Hyperledger is a collaborative project at The Linux Foundation. It is an open-source and open +community project where participants choose to work together, and in that process experience +differences in language, location, nationality, and experience. In such a diverse environment, +misunderstandings and disagreements happen, which in most cases can be resolved informally. In rare +cases, however, behavior can intimidate, harass, or otherwise disrupt one or more people in the +community, which Hyperledger will not tolerate. + +A **Code of Conduct** is useful to define accepted and acceptable behaviors and to promote high +standards of professional practice. It also provides a benchmark for self evaluation and acts as a +vehicle for better identity of the organization. + +This code (**CoC**) applies to any member of the Hyperledger community – developers, participants in +meetings, teleconferences, mailing lists, conferences or functions, etc. Note that this code +complements rather than replaces legal rights and obligations pertaining to any particular +situation. + +## Statement of Intent + +Hyperledger is committed to maintain a **positive** [work environment](#work-environment). This +commitment calls for a workplace where [participants](#participant) at all levels behave according +to the rules of the following code. A foundational concept of this code is that we all share +responsibility for our work environment. + +## Code + +1. Treat each other with [respect](#respect), professionalism, fairness, and sensitivity to our many + differences and strengths, including in situations of high pressure and urgency. + +2. Never [harass](#harassment) or [bully](#workplace-bullying) anyone verbally, physically or + [sexually](#sexual-harassment). + +3. Never [discriminate](#discrimination) on the basis of personal characteristics or group + membership. + +4. Communicate constructively and avoid [demeaning](#demeaning-behavior) or + [insulting](#insulting-behavior) behavior or language. + +5. Seek, accept, and offer objective work criticism, and [acknowledge](#acknowledgement) properly + the contributions of others. + +6. Be honest about your own qualifications, and about any circumstances that might lead to conflicts + of interest. + +7. Respect the privacy of others and the confidentiality of data you access. + +8. With respect to cultural differences, be conservative in what you do and liberal in what you + accept from others, but not to the point of accepting disrespectful, unprofessional or unfair or + [unwelcome behavior](#unwelcome-behavior) or [advances](#unwelcome-sexual-advance). + +9. Promote the rules of this Code and take action (especially if you are in a + [leadership position](#leadership-position)) to bring the discussion back to a more civil level + whenever inappropriate behaviors are observed. + +10. Stay on topic: Make sure that you are posting to the correct channel and avoid off-topic + discussions. Remember when you update an issue or respond to an email you are potentially + sending to a large number of people. + +11. Step down considerately: Members of every project come and go, and the Hyperledger is no + different. When you leave or disengage from the project, in whole or in part, we ask that you do + so in a way that minimizes disruption to the project. This means you should tell people you are + leaving and take the proper steps to ensure that others can pick up where you left off. + +## Glossary + +### Demeaning Behavior + +is acting in a way that reduces another person's dignity, sense of self-worth or respect within the +community. + +### Discrimination + +is the prejudicial treatment of an individual based on criteria such as: physical appearance, race, +ethnic origin, genetic differences, national or social origin, name, religion, gender, sexual +orientation, family or health situation, pregnancy, disability, age, education, wealth, domicile, +political view, morals, employment, or union activity. + +### Insulting Behavior + +is treating another person with scorn or disrespect. + +### Acknowledgement + +is a record of the origin(s) and author(s) of a contribution. + +### Harassment + +is any conduct, verbal or physical, that has the intent or effect of interfering with an individual, +or that creates an intimidating, hostile, or offensive environment. + +### Leadership Position + +includes group Chairs, project maintainers, staff members, and Board members. + +### Participant + +includes the following persons: + +- Developers +- Member representatives +- Staff members +- Anyone from the Public partaking in the Hyperledger work environment (e.g. contribute code, + comment on our code or specs, email us, attend our conferences, functions, etc) + +### Respect + +is the genuine consideration you have for someone (if only because of their status as participant in +Hyperledger, like yourself), and that you show by treating them in a polite and kind way. + +### Sexual Harassment + +includes visual displays of degrading sexual images, sexually suggestive conduct, offensive remarks +of a sexual nature, requests for sexual favors, unwelcome physical contact, and sexual assault. + +### Unwelcome Behavior + +Hard to define? Some questions to ask yourself are: + +- how would I feel if I were in the position of the recipient? +- would my spouse, parent, child, sibling or friend like to be treated this way? +- would I like an account of my behavior published in the organization's newsletter? +- could my behavior offend or hurt other members of the work group? +- could someone misinterpret my behavior as intentionally harmful or harassing? +- would I treat my boss or a person I admire at work like that ? +- Summary: if you are unsure whether something might be welcome or unwelcome, don't do it. + +### Unwelcome Sexual Advance + +includes requests for sexual favors, and other verbal or physical conduct of a sexual nature, where: + +- submission to such conduct is made either explicitly or implicitly a term or condition of an + individual's employment, +- submission to or rejection of such conduct by an individual is used as a basis for employment + decisions affecting the individual, +- such conduct has the purpose or effect of unreasonably interfering with an individual's work + performance or creating an intimidating hostile or offensive working environment. + +### Workplace Bullying + +is a tendency of individuals or groups to use persistent aggressive or unreasonable behavior (e.g. +verbal or written abuse, offensive conduct or any interference which undermines or impedes work) +against a co-worker or any professional relations. + +### Work Environment + +is the set of all available means of collaboration, including, but not limited to messages to +mailing lists, private correspondence, Web pages, chat channels, phone and video teleconferences, +and any kind of face-to-face meetings or discussions. + +## Incident Procedure + +To report incidents or to appeal reports of incidents, send email to Mike Dolan +(mdolan@linuxfoundation.org) or Angela Brown (angela@linuxfoundation.org). Please include any +available relevant information, including links to any publicly accessible material relating to the +matter. Every effort will be taken to ensure a safe and collegial environment in which to +collaborate on matters relating to the Project. In order to protect the community, the Project +reserves the right to take appropriate action, potentially including the removal of an individual +from any and all participation in the project. The Project will work towards an equitable resolution +in the event of a misunderstanding. + +## Credits + +This code is based on the +[W3C’s Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc) with some +additions from the [Cloud Foundry](https://www.cloudfoundry.org/)‘s Code of Conduct. \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index af2f5141..90771446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,23 @@ [workspace] -members = ["askar-bbs", "askar-crypto"] +members = ["askar-crypto", "askar-storage"] [package] name = "aries-askar" -version = "0.2.5" +version = "0.2.9" authors = ["Hyperledger Aries Contributors "] -edition = "2018" +edition = "2021" description = "Hyperledger Aries Askar secure storage" license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/hyperledger/aries-askar/" categories = ["cryptography", "database"] keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] +rust-version = "1.65" + +[[bin]] +name = "uniffi-bindgen" +path = "uniffi/uniffi-bindgen.rs" +required-features = ["uffi"] [lib] name = "aries_askar" @@ -24,61 +30,54 @@ no-default-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["all_backends", "ffi", "logger"] -all_backends = ["any", "postgres", "sqlite"] -any = [] -ffi = ["any", "ffi-support", "logger"] +default = ["all_backends", "ffi", "logger", "migration"] +all_backends = ["postgres", "sqlite"] +ffi = ["ffi-support", "logger"] +uffi = ["uniffi", "all_backends", "logger", "tokio", "ffi"] jemalloc = ["jemallocator"] -logger = ["env_logger", "log"] -postgres = ["sqlx", "sqlx/postgres", "sqlx/tls"] -sqlite = ["num_cpus", "sqlx", "sqlx/sqlite"] -pg_test = ["postgres"] +logger = ["env_logger", "log", "askar-storage/log"] +postgres = ["askar-storage/postgres"] +sqlite = ["askar-storage/sqlite"] +pg_test = ["askar-storage/pg_test"] +migration = ["askar-storage/migration"] -[dev-dependencies] -hex-literal = "0.3" +[build-dependencies] +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "uniffi-kotlin-multiplatform-bindings" } +camino = "1.1.1" [dependencies] async-lock = "2.5" -async-stream = "0.3" -bs58 = "0.4" -chrono = "0.4" -digest = "0.10" env_logger = { version = "0.9", optional = true } ffi-support = { version = "0.4", optional = true } -futures-lite = "1.11" -hex = "0.4" -hmac = "0.12" -indy-wql = "0.4" -itertools = "0.10" -jemallocator = { version = "0.3", optional = true } +jemallocator = { version = "0.5", optional = true } log = { version = "0.4", optional = true } -num_cpus = { version = "1.0", optional = true } once_cell = "1.5" -percent-encoding = "2.0" serde = { version = "1.0", features = ["derive"] } -serde_bytes = "0.11" serde_cbor = "0.11" serde_json = "1.0" -sha2 = "0.10" -tokio = { version = "1.5", features = ["time"] } -url = { version = "2.1", default-features = false } -uuid = { version = "0.8", features = ["v4"] } -zeroize = "1.4" +tokio = { version = "1.5", optional = true } +uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e", features = ["build", "cli", "tokio"], optional = true} +uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "uniffi-kotlin-multiplatform-bindings" } +camino = "1.1.1" + +zeroize = "1.5" [dependencies.askar-crypto] version = "0.2.5" path = "./askar-crypto" features = ["all_keys", "any_key", "argon2", "crypto_box", "std"] -[dependencies.sqlx] -version = "0.5.12" +[dependencies.askar-storage] +version = "0.1.0" +path = "./askar-storage" default-features = false -features = ["chrono", "runtime-tokio-rustls"] -optional = true +features = ["any"] [profile.release] - lto = true codegen-units = 1 - -[[test]] -name = "backends" +lto = true +panic = "abort" diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 00000000..9451a822 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,6 @@ +[target.aarch64-unknown-linux-gnu] +image = "ghcr.io/rust-cross/manylinux2014-cross:aarch64" + +[target.x86_64-unknown-linux-gnu] +image = "ghcr.io/rust-cross/manylinux2014-cross:x86_64" + diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 00000000..3a49d58f --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,118 @@ +# Maintainers + +## Maintainer Scopes, GitHub Roles and GitHub Teams + +Maintainers are assigned the following scopes in this repository: + +| Scope | Definition | GitHub Role | GitHub Team | +| ---------- | ------------------------ | ----------- | ----------------------------------- | +| Admin | | Admin | [aries-admins] | +| Maintainer | The GitHub Maintain role | Maintain | [aries-askar committers] | +| Maintainer | The GitHub Maintain role | Maintain | [aries committers] | +| Read | The GitHub Read role | Read | [Aries Contributors] | +| Read | The GitHub Read role | Read | [TOC] | + +[aries-admins]: https://github.com/orgs/hyperledger/teams/aries-admins +[aries-cloudagent-python committers]: https://github.com/orgs/hyperledger/teams/aries-cloudagent-python-committers +[aries-askar committers]: https://github.com/orgs/hyperledger/teams/aries-askar-committers +[Aries Contributors]: https://github.com/orgs/hyperledger/teams/aries-contributors +[TOC]: https://github.com/orgs/hyperledger/teams/toc + +## Active Maintainers + + + +| GitHub ID | Name | Scope | LFID | Discord ID | Email | Company Affiliation | +| --------------- | ----------------- | ---------- | ---- | ---------- | ------------------------ | ------------------- | +| andrewwhitehead | Andrew Whitehead | Admin | | | cywolf@gmail.com | BC Gov | +| dbluhm | Daniel Bluhm | Admin | | | daniel@indicio.tech | Indicio PBC | +| blu3beri | Berend Sliedrecht | Maintainer | | | berend@animo.id | Animo Solutions | +| dhh1128 | Daniel Hardman | Admin | | | daniel.hardman@gmail.com | Provident | +| ianco | Ian Costanzo | Maintainer | | | iancostanzo@gmail.com | Anon Solutions | +| nage | Nathan George | Maintainer | | | nathang@kiva.org | Kiva | +| swcurran | Stephen Curran | Admin | | | swcurran@cloudcompass.ca | BC Gov | +| WadeBarnes | Wade Barnes | Admin | | | wade@neoterictech.ca | BC Gov | + +## Emeritus Maintainers + +| Name | GitHub ID | Scope | LFID | Discord ID | Email | Company Affiliation | +|----- | --------- | ----- | ---- | ---------- | ----- | ------------------- | +| | | | | | | | + +## The Duties of a Maintainer + +Maintainers are expected to perform the following duties for this repository. The duties are listed in more or less priority order: + +- Review, respond, and act on any security vulnerabilities reported against the repository. +- Review, provide feedback on, and merge or reject GitHub Pull Requests from + Contributors. +- Review, triage, comment on, and close GitHub Issues + submitted by Contributors. +- When appropriate, lead/facilitate architectural discussions in the community. +- When appropriate, lead/facilitate the creation of a product roadmap. +- Create, clarify, and label issues to be worked on by Contributors. +- Ensure that there is a well defined (and ideally automated) product test and + release pipeline, including the publication of release artifacts. +- When appropriate, execute the product release process. +- Maintain the repository CONTRIBUTING.md file and getting started documents to + give guidance and encouragement to those wanting to contribute to the product, and those wanting to become maintainers. +- Contribute to the product via GitHub Pull Requests. +- Monitor requests from the Hyperledger Technical Oversight Committee about the +contents and management of Hyperledger repositories, such as branch handling, +required files in repositories and so on. +- Contribute to the Hyperledger Project's Quarterly Report. + +## Becoming a Maintainer + +This community welcomes contributions. Interested contributors are encouraged to +progress to become maintainers. To become a maintainer the following steps +occur, roughly in order. + +- The proposed maintainer establishes their reputation in the community, + including authoring five (5) significant merged pull requests, and expresses + an interest in becoming a maintainer for the repository. +- A PR is created to update this file to add the proposed maintainer to the list of active maintainers. +- The PR is authored by an existing maintainer or has a comment on the PR from an existing maintainer supporting the proposal. +- The PR is authored by the proposed maintainer or has a comment on the PR from the proposed maintainer confirming their interest in being a maintainer. + - The PR or comment from the proposed maintainer must include their + willingness to be a long-term (more than 6 month) maintainer. +- Once the PR and necessary comments have been received, an approval timeframe begins. +- The PR **MUST** be communicated on all appropriate communication channels, including relevant community calls, chat channels and mailing lists. Comments of support from the community are welcome. +- The PR is merged and the proposed maintainer becomes a maintainer if either: + - Two weeks have passed since at least three (3) Maintainer PR approvals have been recorded, OR + - An absolute majority of maintainers have approved the PR. +- If the PR does not get the requisite PR approvals, it may be closed. +- Once the add maintainer PR has been merged, any necessary updates to the GitHub Teams are made. + +## Removing Maintainers + +Being a maintainer is not a status symbol or a title to be carried +indefinitely. It will occasionally be necessary and appropriate to move a +maintainer to emeritus status. This can occur in the following situations: + +- Resignation of a maintainer. +- Violation of the Code of Conduct warranting removal. +- Inactivity. + - A general measure of inactivity will be no commits or code review comments + for one reporting quarter. This will not be strictly enforced if + the maintainer expresses a reasonable intent to continue contributing. + - Reasonable exceptions to inactivity will be granted for known long term + leave such as parental leave and medical leave. +- Other circumstances at the discretion of the other Maintainers. + +The process to move a maintainer from active to emeritus status is comparable to the process for adding a maintainer, outlined above. In the case of voluntary +resignation, the Pull Request can be merged following a maintainer PR approval. If the removal is for any other reason, the following steps **SHOULD** be followed: + +- A PR is created to update this file to move the maintainer to the list of emeritus maintainers. +- The PR is authored by, or has a comment supporting the proposal from, an existing maintainer or Hyperledger GitHub organization administrator. +- Once the PR and necessary comments have been received, the approval timeframe begins. +- The PR **MAY** be communicated on appropriate communication channels, including relevant community calls, chat channels and mailing lists. +- The PR is merged and the maintainer transitions to maintainer emeritus if: + - The PR is approved by the maintainer to be transitioned, OR + - Two weeks have passed since at least three (3) Maintainer PR approvals have been recorded, OR + - An absolute majority of maintainers have approved the PR. +- If the PR does not get the requisite PR approvals, it may be closed. + +Returning to active status from emeritus status uses the same steps as adding a +new maintainer. Note that the emeritus maintainer already has the 5 required +significant changes as there is no contribution time horizon for those. diff --git a/README.md b/README.md index 9d3ba202..324dec18 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,70 @@ [![Rust Documentation](https://docs.rs/aries-askar/badge.svg)](https://docs.rs/aries-askar) [![Python Package](https://img.shields.io/pypi/v/aries_askar)](https://pypi.org/project/aries-askar/) -Secure storage and cryptographic support designed for Hyperledger Aries agents. +Aries Askar is a secure (encrypted at rest) storage and a key management service +suitable for use with [Hyperledger Aries] agents and possibly other digital +trust agents. Askar is a replacement implementation (with lessons learned!) of the +[indy-wallet] part of the [Hyperledger Indy SDK]. Askar has been demonstrated to +be more performant and stable than the Indy SDK when under comparable load. + +Askar has a pluggable storage interface that currently supports in-memory (for +testing only), [SQLite] and [PostgreSQL] databases. For details about the +storage scheme used in Askar, please this [storage] overview in the `docs` +folder. + +Askar is implemented in Rust and this repository contains Askar wrappers for +Askar JavaScript and Python, reflecting the key Aries frameworks that embed +Askar, [Aries Framework JavaScript] and [Aries Cloud Agent Python]. Other +wrappers are welcome, although there is some debate as to whether the wrappers +should be within this repository or in their own repository. + +The name Askar (from the Arabic askar, meaning “guard” or “soldier”) is used +because of the "guard" reference, and because it is an alternate name for the +star [Hamal in the constellation of Aries], the 50th brightest star in our sky. + +[Hyperledger Aries]: https://www.hyperledger.org/projects/aries +[indy-wallet]: https://github.com/hyperledger/indy-sdk/tree/main/libindy/indy-wallet +[Hyperledger Indy SDK]: https://github.com/hyperledger/indy-sdk +[SQLite]: https://www.sqlite.org/index.html +[PostgreSQL]: https://www.postgresql.org/ +[storage]: /docs/storage.md +[Aries Framework JavaScript]: https://github.com/hyperledger/aries-framework-javascript +[Aries Cloud Agent Python]: https://github.com/hyperledger/aries-cloudagent-python +[Hamal in the constellation of Aries]: https://www.star-facts.com/hamal/ + +## Askar Concepts Borrowed from the indy-wallet Implementation + +As noted above, Askar is a re-implementation (with lessons learned!) of the +[indy-wallet] part of the [Hyperledger Indy SDK]. As such, a number of the +concept documents written about [indy-wallet] apply similarly to Askar. These +are linked here: + +* [Encryption and storage passphrases](https://github.com/hyperledger/indy-sdk/blob/main/docs/concepts/default-wallet.md) +* [Object Storage](https://github.com/hyperledger/indy-sdk/blob/main/docs/design/003-wallet-storage/README.md) +* [Storage Import/Export](https://github.com/hyperledger/indy-sdk/blob/main/docs/design/009-wallet-export-import/README.md) + +> **To Do**: These documents should be copied to this repository and updated +> specifically for the Askar implementation. + +## Migrating to Aries Askar + +If you have an implementation of Aries that is currently based on the [Hyperledger Indy SDK], there are migration tools +built into Askar. The use of these tools is demonstrated in the [Aries Cloud Agent Python] migration tool that can be +found in the [aries-acapy-tools] repository. + +[aries-acapy-tools]: https://github.com/hyperledger/aries-acapy-tools ## Credit -The initial implementation of `aries-askar` was developed by the Verifiable Organizations Network (VON) team based at the Province of British Columbia, and inspired by the wallet design within [Hyperledger Indy-SDK](https://github.com/hyperledger/indy-sdk). To learn more about VON and what's happening with decentralized identity in British Columbia, please go to [https://vonx.io](https://vonx.io). +The initial implementation of `aries-askar` was developed by the Digital Trust +team within the Province of British Columbia, and inspired by the wallet design +within [Hyperledger Indy SDK]. To learn +more about BC's Digital Trust Team, and what's happening with decentralized identity in British +Columbia, please go to [Digital Trust website](https://digital.gov.bc.ca/digital-trust/). ## Contributing -Pull requests are welcome! Please read our [contributions guide](https://github.com/hyperledger/aries-askar/blob/master/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing. See guidance [here](https://github.com/apps/dco). +Pull requests are welcome! Please read our [contributions guide](https://github.com/hyperledger/aries-askar/blob/main/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing. See guidance [here](https://github.com/apps/dco). We also welcome issues submitted about problems you encounter in using `aries-askar`. @@ -21,7 +76,11 @@ We also welcome issues submitted about problems you encounter in using `aries-as Licensed under either of -- Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/hyperledger/aries-askar/blob/main/LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](https://github.com/hyperledger/aries-askar/blob/main/LICENSE-MIT) or http://opensource.org/licenses/MIT) +- Apache License, Version 2.0 + ([LICENSE-APACHE](https://github.com/hyperledger/aries-askar/blob/main/LICENSE-APACHE) + or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license + ([LICENSE-MIT](https://github.com/hyperledger/aries-askar/blob/main/LICENSE-MIT) + or [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT)) at your option. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..576db5ab --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Hyperledger Security Policy + +## Reporting a Security Bug + +If you think you have discovered a security issue in any of the Hyperledger projects, we'd love to +hear from you. We will take all security bugs seriously and if confirmed upon investigation we will +patch it within a reasonable amount of time and release a public security bulletin discussing the +impact and credit the discoverer. + +If you have found what you think is a vulnerability in the code in this +repository, please email a description of the flaw and any related information +(e.g. reproduction steps, version) to +[security@hyperledger.org](mailto:security@hyperledger.org). If we agree with +your assessment, we'll open a GitHub security issue in the reposioty, and invite +you to collaborate towards resolving the issue. + +The process by which the Hyperledger Security Team handles security bugs is documented further in +our [Defect Response page](https://wiki.hyperledger.org/display/SEC/Defect+Response) on our +[wiki](https://wiki.hyperledger.org). diff --git a/askar-bbs/Cargo.toml b/askar-bbs/Cargo.toml deleted file mode 100644 index 3a4d60b5..00000000 --- a/askar-bbs/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -[package] -name = "askar-bbs" -version = "0.1.0" -authors = ["Hyperledger Aries Contributors "] -edition = "2018" -description = "Hyperledger Aries Askar BBS+ Signatures" -license = "MIT OR Apache-2.0" -readme = "README.md" -repository = "https://github.com/hyperledger/aries-askar/" -categories = ["cryptography", "no-std"] -keywords = ["hyperledger", "aries", "credentials", "bbs", "signatures"] - -[package.metadata.docs.rs] -features = ["argon2", "std"] -rustdoc-args = ["--cfg", "docsrs"] - -[features] -default = ["alloc", "getrandom"] -alloc = ["askar-crypto/alloc"] -std = ["alloc", "askar-crypto/std", "getrandom", "rand/std", "sha3/std"] -getrandom = ["askar-crypto/getrandom", "rand/getrandom"] - -[dependencies] -askar-crypto = { version = "0.2.1", default-features = false, features = ["bls"], path = "../askar-crypto" } -bls12_381 = { version = "0.6", default-features = false, features = ["experimental", "groups", "pairings"] } -group = { version = "0.11", default-features = false } -heapless = "0.7" -rand = { version = "0.8", default-features = false } -sha3 = { version = "0.9", default-features = false } -subtle = "2.4" - -[dev-dependencies] -criterion = "0.3" -hex-literal = "0.3" -serde-json-core = { version = "0.4", default-features = false, features = ["std"] } - - -[[bench]] -name = "blind_sign" -harness = false -required-features = ["getrandom"] - -[[bench]] -name = "generators" -harness = false - -[[bench]] -name = "proof" -harness = false -required-features = ["getrandom"] - -[[bench]] -name = "signature" -harness = false -required-features = ["getrandom"] diff --git a/askar-bbs/README.md b/askar-bbs/README.md deleted file mode 100644 index fd35f9ab..00000000 --- a/askar-bbs/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# askar-bbs - -[![Rust Crate](https://img.shields.io/crates/v/askar-bbs.svg)](https://crates.io/crates/askar-bbs) -[![Rust Documentation](https://docs.rs/askar-bbs/badge.svg)](https://docs.rs/askar-bbs) - -The `askar-bbs` crate provides support for BBS+ signature generation and verification used by [`aries-askar`](https://github.com/hyperledger/aries-askar). - -The implementation will be targeting the 2022 standard which is in progress. Please **DO NOT** use this crate and expect it to be compatible with the released version just yet. - -## no-std - -This crate supports the optional `alloc` feature, gating types and operations that depend on a global allocator. The `std` feature depends on `alloc`, and adds support for `std::error::Error`. - -## Quick Start - -### Keypairs - -Signing and verification keys are managed as `askar-crypto` BLS keypairs. Keys may be generated randomly or from a seed value, or loaded from an binary encoded key or JWK. - -```rust -use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, repr::KeyGen, -}; - -let keypair = BlsKeyPair::::random().unwrap(); -``` - -### Signing - -```rust -use askar_bbs::{ - io::FixedLengthBytes, DynGenerators, Message, Signature, SignatureBuilder, -}; - -let messages = [Message::hash("message 1"), Message::hash("message 2")]; -let generators = DynGenerators::new(&keypair, messages.len()); -let signature = SignatureBuilder::sign(&generators, &keypair, messages.iter().copied()).unwrap(); -let signature_bytes = signature.to_bytes(); -``` - -### Verifying a Signature - -```rust -let messages = [Message::hash("message 1"), Message::hash("message 2")]; -let generators = DynGenerators::new(&keypair, messages.len()); -let signature = Signature::from_bytes(&signature_bytes).unwrap(); -signature.verify(&generators, messages.iter().copied()).unwrap(); -``` - -### Generating a Signature Proof of Knowledge - -This zero-knowledge proof protocol is used by a prover to perform a selective reveal of the signed messages to a verifier. - -```rust -let nonce = Nonce::random(); // provided by the verifier -let messages = [Message::hash("message 1"), Message::hash("message 2")]; -let generators = DynGenerators::new(&keypair, messages.len()); -let signature = Signature::from_bytes(&signature_bytes).unwrap(); -let mut prover = signature.prover(&generators); -prover.push_hidden_message(messages[0]).unwrap(); -prover.push_message(messages[1]).unwrap(); -let (challenge, proof) = prover.complete(nonce).unwrap(); -``` - -### Verifying a Signature Proof of Knowledge - -```rust -let mut verifier = proof.verifier(&generators, challenge).unwrap(); -verifier.push_hidden_count(1).unwrap(); -verifier.push_revealed(messages[1]).unwrap(); -let challenge_v = verifier.complete(nonce).unwrap(); -verifier.verify(challenge_v).unwrap(); -``` - -## License - -Licensed under either of - -- Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/hyperledger/aries-askar/blob/main/LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](https://github.com/hyperledger/aries-askar/blob/main/LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. diff --git a/askar-bbs/benches/blind_sign.rs b/askar-bbs/benches/blind_sign.rs deleted file mode 100644 index 26a1c12d..00000000 --- a/askar-bbs/benches/blind_sign.rs +++ /dev/null @@ -1,80 +0,0 @@ -#[macro_use] -extern crate criterion; - -use askar_bbs::{CommitmentBuilder, DynGenerators, Message, Nonce, SignatureBuilder}; -use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - repr::KeyGen, -}; -use rand::{rngs::OsRng, RngCore}; - -use criterion::Criterion; - -fn criterion_benchmark(c: &mut Criterion) { - let keypair = BlsKeyPair::::generate(OsRng).unwrap(); - - for message_count in vec![5, 25, 125] { - let gens = DynGenerators::new(&keypair, message_count) - .to_vec() - .unwrap(); - let commit_msg = Message::from(OsRng.next_u64()); - let nonce = Nonce::random(); - - if message_count == 5 { - c.bench_function("create commitment", |b| { - b.iter(|| { - let mut committer = CommitmentBuilder::new(&gens); - committer.add_message(0, commit_msg).unwrap(); - let (_challenge, _blind, _commit, _proof) = committer.complete(nonce).unwrap(); - }); - }); - } - - let mut committer = CommitmentBuilder::new(&gens); - committer.add_message(0, commit_msg).unwrap(); - let (challenge, blinding, commitment, proof) = committer.complete(nonce).unwrap(); - - if message_count == 5 { - c.bench_function(&format!("verify commitment"), |b| { - b.iter(|| { - proof - .verify(&gens, commitment, [0].iter().copied(), challenge, nonce) - .unwrap() - }); - }); - } - - let messages: Vec = (1..message_count) - .map(|_| Message::from(OsRng.next_u64())) - .collect(); - c.bench_function(&format!("blind sign for {} messages", message_count), |b| { - b.iter(|| { - let mut signer = SignatureBuilder::from_commitment(&gens, &keypair, commitment); - signer.push_committed_count(1).unwrap(); - signer.append_messages(messages.iter().copied()).unwrap(); - signer.to_signature().unwrap() - }); - }); - - let mut signer = SignatureBuilder::from_commitment(&gens, &keypair, commitment); - signer.push_committed_count(1).unwrap(); - signer.append_messages(messages.iter().copied()).unwrap(); - let sig = signer.to_signature().unwrap(); - - c.bench_function( - &format!("unblind and verify for {} messages", message_count), - |b| { - b.iter(|| { - let sig = sig.unblind(blinding); - let mut verifier = sig.verifier(&gens); - verifier.push_message(commit_msg).unwrap(); - verifier.append_messages(messages.iter().copied()).unwrap(); - verifier.verify().unwrap() - }); - }, - ); - } -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/askar-bbs/benches/generators.rs b/askar-bbs/benches/generators.rs deleted file mode 100644 index d9dfc6db..00000000 --- a/askar-bbs/benches/generators.rs +++ /dev/null @@ -1,31 +0,0 @@ -#[macro_use] -extern crate criterion; - -use askar_bbs::{DynGenerators, Generators}; -use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - repr::KeySecretBytes, -}; -use hex_literal::hex; - -use criterion::{black_box, Criterion}; - -fn criterion_benchmark(c: &mut Criterion) { - let keypair = BlsKeyPair::::from_secret_bytes(&hex!( - "0011223344556677889900112233445566778899001122334455667788990011" - )) - .unwrap(); - - for message_count in vec![5, 25, 125] { - c.bench_function(&format!("keygen for {} messages", message_count), |b| { - b.iter(|| { - for gen in DynGenerators::new(&keypair, message_count).iter() { - black_box(gen); - } - }); - }); - } -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/askar-bbs/benches/proof.rs b/askar-bbs/benches/proof.rs deleted file mode 100644 index f64c37e4..00000000 --- a/askar-bbs/benches/proof.rs +++ /dev/null @@ -1,82 +0,0 @@ -#[macro_use] -extern crate criterion; - -use askar_bbs::{CreateChallenge, DynGenerators, Message, Nonce, SignatureBuilder}; -use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - repr::KeyGen, -}; -use rand::{rngs::OsRng, RngCore}; - -use criterion::Criterion; - -fn criterion_benchmark(c: &mut Criterion) { - let keypair = BlsKeyPair::::generate(OsRng).unwrap(); - - for message_count in vec![5, 25, 125] { - let gens = DynGenerators::new(&keypair, message_count) - .to_vec() - .unwrap(); - - let messages: Vec = (0..message_count) - .map(|_| Message::from(OsRng.next_u64())) - .collect(); - - let mut signer = SignatureBuilder::new(&gens, &keypair); - signer.append_messages(messages.iter().copied()).unwrap(); - let sig = signer.to_signature().unwrap(); - let nonce = Nonce::random(); - - c.bench_function( - &format!("create signature pok for {} messages", message_count), - |b| { - b.iter(|| { - let mut prover = sig.prover(&gens); - let hidden_count = message_count / 2; - for (index, msg) in messages.iter().enumerate() { - if index < hidden_count { - prover.push_hidden_message(*msg).unwrap(); - } else { - prover.push_message(*msg).unwrap(); - } - } - let ctx = prover.prepare().unwrap(); - let challenge = ctx.create_challenge(nonce, None).unwrap(); - let _proof = ctx.complete(challenge).unwrap(); - }); - }, - ); - - let mut prover = sig.prover(&gens); - let hidden_count = message_count / 2; - for (index, msg) in messages.iter().enumerate() { - if index < hidden_count { - prover.push_hidden_message(*msg).unwrap(); - } else { - prover.push_message(*msg).unwrap(); - } - } - let ctx = prover.prepare().unwrap(); - let challenge = ctx.create_challenge(nonce, None).unwrap(); - let proof = ctx.complete(challenge).unwrap(); - c.bench_function( - &format!("verify signature pok for {} messages", message_count), - |b| { - b.iter(|| { - let mut verifier = proof.verifier(&gens, challenge).unwrap(); - verifier.push_hidden_count(hidden_count).unwrap(); - for index in hidden_count..messages.len() { - verifier.push_revealed(messages[index]).unwrap(); - } - let challenge_v = verifier.create_challenge(nonce, None).unwrap(); - verifier - .verify(challenge_v) - .expect("Error verifying signature PoK") - }); - }, - ); - } -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/askar-bbs/benches/signature.rs b/askar-bbs/benches/signature.rs deleted file mode 100644 index ea7d2ffc..00000000 --- a/askar-bbs/benches/signature.rs +++ /dev/null @@ -1,42 +0,0 @@ -#[macro_use] -extern crate criterion; - -use askar_bbs::{DynGenerators, Message, SignatureBuilder}; -use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - repr::KeyGen, -}; -use rand::{rngs::OsRng, RngCore}; - -use criterion::Criterion; - -fn criterion_benchmark(c: &mut Criterion) { - let keypair = BlsKeyPair::::generate(OsRng).unwrap(); - - for message_count in vec![5, 25, 125] { - let gens = DynGenerators::new(&keypair, message_count) - .to_vec() - .unwrap(); - let messages: Vec = (0..message_count) - .map(|_| Message::from(OsRng.next_u64())) - .collect(); - - c.bench_function(&format!("sign for {} messages", message_count), |b| { - b.iter(|| { - let mut signer = SignatureBuilder::new(&gens, &keypair); - signer.append_messages(messages.iter().copied()).unwrap(); - signer.to_signature().unwrap(); - }); - }); - - let mut signer = SignatureBuilder::new(&gens, &keypair); - signer.append_messages(messages.iter().copied()).unwrap(); - let sig = signer.to_signature().unwrap(); - c.bench_function(&format!("verify for {} messages", message_count), |b| { - b.iter(|| sig.verify(&gens, messages.iter().copied()).unwrap()); - }); - } -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/askar-bbs/src/challenge.rs b/askar-bbs/src/challenge.rs deleted file mode 100644 index 27b2435d..00000000 --- a/askar-bbs/src/challenge.rs +++ /dev/null @@ -1,35 +0,0 @@ -use askar_crypto::buffer::WriteBuffer; - -use crate::{hash::HashScalar, util::Nonce, Error}; - -impl_scalar_type!(ProofChallenge, "Fiat-Shamir proof challenge value"); - -impl ProofChallenge { - /// Create a new proof challenge value from a set of prepared proofs - pub fn create( - proofs: &[&dyn CreateChallenge], - nonce: Nonce, - dst: Option<&[u8]>, - ) -> Result { - let mut c_hash = HashScalar::new(dst); - for proof in proofs { - proof.write_challenge_bytes(&mut c_hash)?; - } - c_hash.update(&nonce.0.to_bytes()); - Ok(ProofChallenge(c_hash.finalize().next())) - } -} - -/// Support for outputting bytes for use in proof challenge generation -pub trait CreateChallenge { - /// Create a new independent proof challenge - fn create_challenge(&self, nonce: Nonce, dst: Option<&[u8]>) -> Result { - let mut c_hash = HashScalar::new(dst); - self.write_challenge_bytes(&mut c_hash)?; - c_hash.update(&nonce.0.to_bytes()); - Ok(ProofChallenge(c_hash.finalize().next())) - } - - /// Write the challenge bytes to a target - fn write_challenge_bytes(&self, writer: &mut dyn WriteBuffer) -> Result<(), Error>; -} diff --git a/askar-bbs/src/collect.rs b/askar-bbs/src/collect.rs deleted file mode 100644 index b403e12b..00000000 --- a/askar-bbs/src/collect.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! Support for heapless and heap-allocated sequences - -use core::{ - fmt::{self, Debug, Formatter}, - ops::{Deref, DerefMut}, - slice::Iter, -}; - -use crate::Error; - -// NOTE: in future, it should be possible to simplify this with GATs - -#[cfg(feature = "alloc")] -/// The default generic sequence type -pub type DefaultSeq = Heap; -#[cfg(not(feature = "alloc"))] -/// The default generic sequence type -pub type DefaultSeq = Stack; - -/// A wrapper type for a generic backing sequence -pub struct Vec -where - B: Seq, -{ - inner: B::Vec, -} - -impl Vec -where - B: Seq, -{ - #[inline] - /// Create a new, empty sequence - pub fn new() -> Self { - Self { inner: B::new() } - } - - #[inline] - /// Create a new sequence with a minimum capacity - pub fn with_capacity(cap: usize) -> Self { - Self { - inner: B::with_capacity(cap), - } - } - - #[inline] - /// Push a new value at the end of the sequence, failing if the - /// maximum length has been exceeded - pub fn push(&mut self, item: Item) -> Result<(), Error> { - B::push(&mut self.inner, item) - } - - #[inline] - /// Get the current length of the sequence - pub fn len(&self) -> usize { - B::len(&self.inner) - } - - /// Get an iterator over the sequence values - pub fn iter(&self) -> Iter<'_, Item> { - B::as_slice(&self.inner).into_iter() - } - - /// Create a new sequence from an iterator of values - pub fn from_iter(iter: impl IntoIterator) -> Result { - let iter = iter.into_iter(); - let mut slf = Self::with_capacity(iter.size_hint().0); - for item in iter { - slf.push(item)?; - } - Ok(slf) - } -} - -impl Clone for Vec -where - B: Seq, - Item: Clone, -{ - fn clone(&self) -> Self { - Self { - inner: B::clone(&self.inner), - } - } -} - -impl Debug for Vec -where - B: Seq, - Item: Debug, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_list().entries(B::as_slice(&self.inner)).finish() - } -} - -impl Deref for Vec -where - B: Seq, -{ - type Target = [Item]; - - fn deref(&self) -> &Self::Target { - B::as_slice(&self.inner) - } -} - -impl DerefMut for Vec -where - B: Seq, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - B::as_slice_mut(&mut self.inner) - } -} - -impl PartialEq for Vec -where - B: Seq, - Item: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - &*self == &*other - } -} - -impl Eq for Vec -where - B: Seq, - Item: Eq, -{ -} - -/// A generic trait for a backing sequence type -pub trait Seq: Debug { - /// The backing type - type Vec; - - /// Create a new instance of the backing type - fn new() -> Self::Vec; - - #[inline] - /// Create a new instance of the backing type with a minimum capacity - fn with_capacity(_cap: usize) -> Self::Vec { - Self::new() - } - - /// Push a new value onto the sequence - fn push(vec: &mut Self::Vec, item: Item) -> Result<(), Error>; - - /// Access the contained values as a slice - fn as_slice(vec: &Self::Vec) -> &[Item]; - - /// Access the contained values as a mutable slice - fn as_slice_mut(vec: &mut Self::Vec) -> &mut [Item]; - - /// Get the current length of the sequence - fn len(vec: &Self::Vec) -> usize; - - /// Clone the backing type - fn clone(vec: &Self::Vec) -> Self::Vec - where - Item: Clone; -} - -#[cfg(feature = "alloc")] -#[derive(Debug)] -/// A heap-based (std::vec::Vec) sequence type -pub struct Heap; - -#[cfg(feature = "alloc")] -impl Seq for Heap { - type Vec = alloc::vec::Vec; - - #[inline] - fn new() -> Self::Vec { - alloc::vec::Vec::new() - } - - #[inline] - fn with_capacity(cap: usize) -> Self::Vec { - alloc::vec::Vec::with_capacity(cap) - } - - #[inline] - fn push(vec: &mut Self::Vec, item: Item) -> Result<(), Error> { - vec.push(item); - Ok(()) - } - - #[inline] - fn as_slice(vec: &Self::Vec) -> &[Item] { - vec.as_ref() - } - - #[inline] - fn as_slice_mut(vec: &mut Self::Vec) -> &mut [Item] { - &mut vec[..] - } - - #[inline] - fn len(vec: &Self::Vec) -> usize { - vec.len() - } - - #[inline] - fn clone(vec: &Self::Vec) -> Self::Vec - where - Item: Clone, - { - vec.clone() - } -} - -#[derive(Debug)] -/// A stack-based (heapless) sequence type -pub struct Stack; - -impl Seq for Stack { - type Vec = heapless::Vec; - - #[inline] - fn new() -> Self::Vec { - heapless::Vec::new() - } - - fn push(vec: &mut Self::Vec, item: Item) -> Result<(), Error> { - vec.push(item) - .map_err(|_| err_msg!(Usage, "Exceeded storage capacity")) - } - - #[inline] - fn as_slice(vec: &Self::Vec) -> &[Item] { - vec.as_ref() - } - - #[inline] - fn as_slice_mut(vec: &mut Self::Vec) -> &mut [Item] { - &mut vec[..] - } - - #[inline] - fn len(vec: &Self::Vec) -> usize { - vec.len() - } - - #[inline] - fn clone(vec: &Self::Vec) -> Self::Vec - where - Item: Clone, - { - vec.clone() - } -} diff --git a/askar-bbs/src/commitment.rs b/askar-bbs/src/commitment.rs deleted file mode 100644 index b3cbff0c..00000000 --- a/askar-bbs/src/commitment.rs +++ /dev/null @@ -1,409 +0,0 @@ -#[cfg(feature = "alloc")] -use alloc::vec::Vec as StdVec; - -use askar_crypto::buffer::WriteBuffer; -use bls12_381::{G1Affine, G1Projective, Scalar}; -use group::Curve; -use rand::{CryptoRng, Rng}; - -#[cfg(feature = "getrandom")] -use askar_crypto::random::default_rng; - -use crate::{ - challenge::{CreateChallenge, ProofChallenge}, - collect::{DefaultSeq, Seq, Vec}, - generators::Generators, - io::{Cursor, FixedLengthBytes}, - signature::Message, - util::{random_scalar, AccumG1, Nonce}, - Error, -}; - -/// A standard domain-specific input for use in blinded message commitment proofs -pub const COMMITMENT_PROOF_DST_G1: &[u8] = b"BLS12381G1_BBS+_SIGNATURES_COMMITMENT_POK:1_0_0"; - -const G1_COMPRESSED_SIZE: usize = 48; - -/// A nonce value used as a blinding -pub type Blinding = Nonce; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -/// A commitment to a set of blinded messages for signing -pub struct Commitment(pub(crate) G1Affine); - -impl FixedLengthBytes for Commitment { - const LENGTH: usize = G1_COMPRESSED_SIZE; - - type Buffer = [u8; G1_COMPRESSED_SIZE]; - - fn from_bytes(buf: &Self::Buffer) -> Result { - if let Some(pt) = G1Affine::from_compressed(buf).into() { - Ok(Self(pt)) - } else { - Err(err_msg!(Invalid)) - } - } - - fn with_bytes(&self, f: impl FnOnce(&Self::Buffer) -> R) -> R { - f(&self.0.to_compressed()) - } -} - -impl From for Commitment { - fn from(pt: G1Affine) -> Self { - Self(pt) - } -} - -#[derive(Clone, Debug)] -/// A builder used to generate and prove a commitment to a set of messages -pub struct CommitmentBuilder<'g, G, S> -where - G: Generators, - S: Seq<(Message, Blinding)>, -{ - accum_commitment: AccumG1, - accum_c1: AccumG1, - messages: Vec<(Message, Blinding), S>, - generators: &'g G, -} - -impl<'g, G> CommitmentBuilder<'g, G, DefaultSeq<32>> -where - G: Generators, -{ - /// Create a new commitment builder - pub fn new(generators: &'g G) -> Self { - Self::new_sized(generators) - } -} - -impl<'g, G, S> CommitmentBuilder<'g, G, S> -where - G: Generators, - S: Seq<(Message, Blinding)>, -{ - /// Create a new commitment builder with a specific backing sequence type - pub fn new_sized(generators: &'g G) -> Self { - Self { - accum_commitment: AccumG1::zero(), - accum_c1: AccumG1::zero(), - messages: Vec::with_capacity(16), - generators, - } - } -} - -impl CommitmentBuilder<'_, G, S> -where - G: Generators, - S: Seq<(Message, Blinding)>, -{ - #[cfg(feature = "getrandom")] - /// Add a hidden message with a random blinding value - pub fn add_message(&mut self, index: usize, message: Message) -> Result<(), Error> { - self.add_message_with(index, message, Blinding::random()) - } - - /// Add a hidden message with a pre-selected blinding value - pub fn add_message_with( - &mut self, - index: usize, - message: Message, - blinding: Blinding, - ) -> Result<(), Error> { - if index > self.generators.message_count() { - Err(err_msg!(Usage, "Message index exceeds generator count")) - } else { - self.messages.push((message, blinding))?; - let base = self.generators.message(index); - self.accum_commitment.push(base, message.0); - self.accum_c1.push(base, blinding.0); - Ok(()) - } - } -} - -impl CommitmentBuilder<'_, G, S> -where - G: Generators, - S: Seq<(Message, Blinding)> + Seq, -{ - #[cfg(feature = "getrandom")] - /// Prepare the commitment proof context - pub fn prepare(self) -> Result, Error> { - self.prepare_with_rng(default_rng()) - } - - /// Prepare the commitment proof context with a specific RNG - pub fn prepare_with_rng( - mut self, - mut rng: impl CryptoRng + Rng, - ) -> Result, Error> { - if self.messages.is_empty() { - return Err(err_msg!(Usage, "No messages provided for commitment")); - } - - let h0 = self.generators.blinding(); - let s_prime = random_scalar(&mut rng); // s' - let s_blind = random_scalar(&mut rng); // s~ - self.accum_commitment.push(h0, s_prime); - self.accum_c1.push(h0, s_blind); - - let mut affine = [G1Affine::identity(); 2]; - G1Projective::batch_normalize( - &[self.accum_commitment.sum(), self.accum_c1.sum()], - &mut affine[..], - ); - Ok(CommitmentProofContext { - commitment: affine[0].into(), - c1: affine[1], - messages: self.messages, - s_prime, - s_blind, - }) - } - - #[cfg(feature = "getrandom")] - /// Complete an independent commitment proof of knowledge - pub fn complete( - self, - nonce: Nonce, - ) -> Result<(ProofChallenge, Blinding, Commitment, CommitmentProof), Error> { - self.complete_with_rng(default_rng(), nonce) - } - - /// Complete an independent commitment proof with a specific RNG - pub fn complete_with_rng( - self, - rng: impl CryptoRng + Rng, - nonce: Nonce, - ) -> Result<(ProofChallenge, Blinding, Commitment, CommitmentProof), Error> { - let context = self.prepare_with_rng(rng)?; - let challenge = context.create_challenge(nonce, Some(COMMITMENT_PROOF_DST_G1))?; - let (blinding, commitment, proof) = context.complete(challenge)?; - Ok((challenge, blinding, commitment, proof)) - } -} - -#[derive(Clone, Debug)] -/// A prepared context for generating a commitment proof of knowledge -pub struct CommitmentProofContext -where - S: Seq<(Message, Blinding)>, -{ - commitment: Commitment, - c1: G1Affine, - messages: Vec<(Message, Blinding), S>, - s_prime: Scalar, - s_blind: Scalar, -} - -impl CommitmentProofContext -where - S: Seq<(Message, Blinding)>, - S: Seq, -{ - /// Complete the commitment proof of knowledge given a Fiat-Shamir challenge value - pub fn complete( - &self, - challenge: ProofChallenge, - ) -> Result<(Blinding, Commitment, CommitmentProof), Error> { - let c = challenge.0; - let s_resp = self.s_blind + c * self.s_prime; - let mut m_resp = Vec::with_capacity(self.messages.len()); - for (msg, m_rand) in self.messages.iter().copied() { - m_resp.push(m_rand.0 + c * msg.0)?; - } - Ok(( - self.s_prime.into(), - self.commitment, - CommitmentProof { s_resp, m_resp }, - )) - } -} - -impl CreateChallenge for CommitmentProofContext -where - S: Seq<(Message, Blinding)>, -{ - fn write_challenge_bytes(&self, writer: &mut dyn WriteBuffer) -> Result<(), Error> { - writer.buffer_write(&self.commitment.0.to_uncompressed())?; - writer.buffer_write(&self.c1.to_uncompressed())?; - Ok(()) - } -} - -#[derive(Clone, Debug)] -/// A proof of a commitment to hidden messages for signing -pub struct CommitmentProof -where - S: Seq, -{ - pub(crate) s_resp: Scalar, - pub(crate) m_resp: Vec, -} - -impl CommitmentProof> { - /// Convert a signature proof of knowledge from a byte slice - pub fn from_bytes(buf: &[u8]) -> Result { - Self::from_bytes_sized(buf) - } -} - -impl CommitmentProof -where - S: Seq, -{ - /// Verify an independent commitment proof - pub fn verify( - &self, - generators: &G, - commitment: Commitment, - committed_indices: I, - challenge: ProofChallenge, - nonce: Nonce, - ) -> Result<(), Error> - where - G: Generators, - I: IntoIterator, - { - let verifier = self.verifier(generators, commitment, committed_indices, challenge)?; - let challenge_v = verifier.create_challenge(nonce, Some(COMMITMENT_PROOF_DST_G1))?; - verifier.verify(challenge_v) - } - - /// Create a verifier for the commitment proof - pub fn verifier( - &self, - generators: &G, - commitment: Commitment, - committed_indices: I, - challenge: ProofChallenge, - ) -> Result - where - G: Generators, - I: IntoIterator, - { - CommitmentProofVerifier::new( - generators, - commitment, - self, - committed_indices.into_iter(), - challenge, - ) - } - - /// Write the commitment proof of knowledge to an output buffer - pub fn write_bytes(&self, buf: &mut dyn WriteBuffer) -> Result<(), Error> { - buf.buffer_write(&((self.m_resp.len() + 1) as u32).to_be_bytes())?; - self.s_resp.write_bytes(&mut *buf)?; - for resp in self.m_resp.iter() { - resp.write_bytes(&mut *buf)?; - } - Ok(()) - } - - #[cfg(feature = "alloc")] - /// Output the signature proof of knowledge as a byte vec - pub fn to_bytes(&self) -> Result, Error> { - let mut out = StdVec::with_capacity(4 + (1 + self.m_resp.len()) * 32); - self.write_bytes(&mut out)?; - Ok(out) - } - - /// Convert a signature proof of knowledge from a byte slice - pub fn from_bytes_sized(buf: &[u8]) -> Result { - let mut cur = Cursor::new(buf); - let mut m_len = u32::from_be_bytes(*cur.read_fixed()?) as usize; - if m_len < 2 { - return Err(err_msg!(Invalid, "Invalid proof response count")); - } - let s_resp = Scalar::read_bytes(&mut cur)?; - m_len -= 1; - let mut m_resp = Vec::with_capacity(m_len); - for _ in 0..m_len { - m_resp.push(Scalar::read_bytes(&mut cur)?)?; - } - Ok(Self { s_resp, m_resp }) - } - - /// Get the response value from the post-challenge phase of the sigma protocol - /// for a given message index - pub fn get_response(&self, index: usize) -> Result { - self.m_resp - .get(index) - .map(Blinding::from) - .ok_or_else(|| err_msg!(Usage, "Invalid index for committed message")) - } -} - -impl PartialEq> for CommitmentProof -where - S: Seq, - T: Seq, -{ - fn eq(&self, other: &CommitmentProof) -> bool { - self.s_resp == other.s_resp && &*self.m_resp == &*other.m_resp - } -} -impl Eq for CommitmentProof where S: Seq {} - -#[derive(Clone, Debug)] -/// A verifier for a commitment proof of knowledge -pub struct CommitmentProofVerifier { - challenge: Scalar, - commitment: G1Affine, - c1: G1Affine, -} - -impl CommitmentProofVerifier { - pub(crate) fn new( - generators: &G, - commitment: Commitment, - proof: &CommitmentProof, - committed_indices: I, - challenge: ProofChallenge, - ) -> Result - where - G: Generators, - S: Seq, - I: Iterator, - { - let mut accum_c1 = AccumG1::from( - &[ - (commitment.0.into(), -challenge.0), - (generators.blinding(), proof.s_resp), - ][..], - ); - for (index, resp) in committed_indices.zip(proof.m_resp.iter().copied()) { - if index >= generators.message_count() { - return Err(err_msg!(Invalid, "Message index exceeds generator count")); - } - accum_c1.push(generators.message(index), resp); - } - - Ok(Self { - challenge: challenge.0, - commitment: commitment.0, - c1: accum_c1.sum().to_affine(), - }) - } - - /// Verify the commitment proof of knowledge - pub fn verify(&self, challenge_v: ProofChallenge) -> Result<(), Error> { - if challenge_v.0 != self.challenge { - Err(err_msg!(Invalid, "Commitment proof challenge mismatch")) - } else { - Ok(()) - } - } -} - -impl CreateChallenge for CommitmentProofVerifier { - fn write_challenge_bytes(&self, writer: &mut dyn WriteBuffer) -> Result<(), Error> { - writer.buffer_write(&self.commitment.to_uncompressed())?; - writer.buffer_write(&self.c1.to_uncompressed())?; - Ok(()) - } -} diff --git a/askar-bbs/src/generators.rs b/askar-bbs/src/generators.rs deleted file mode 100644 index 41c4dcd6..00000000 --- a/askar-bbs/src/generators.rs +++ /dev/null @@ -1,183 +0,0 @@ -use core::fmt::Debug; - -use askar_crypto::alg::bls::{BlsKeyPair, G2}; -use bls12_381::{ - hash_to_curve::{ExpandMsgXof, HashToCurve}, - G1Projective, G2Affine, -}; - -use crate::{ - collect::{DefaultSeq, Seq, Vec}, - Error, -}; - -/// A standard domain-specific input for use in signature message generators -pub const GENERATORS_DST_G1: &'static [u8] = - b"BLS12381G1_XOF:SHAKE256_SSWU_RO_BBS+_SIGNATURES:1_0_0"; - -const G2_UNCOMPRESSED_SIZE: usize = 192; - -/// Message generators used in signature building and verification -pub trait Generators: Clone + Debug { - /// Get the blinding message generator (h_0) - #[inline] - fn blinding(&self) -> G1Projective { - self.generator(0) - } - - /// Get the message generator for a given message index (h_i) - #[inline] - fn message(&self, index: usize) -> G1Projective { - self.generator(index + 1) - } - - /// The number of message generators, not including the blinding - fn message_count(&self) -> usize; - - /// The public key associated with the generators - fn public_key(&self) -> G2Affine; - - /// Fetch a zero-based message generator - fn generator(&self, index: usize) -> G1Projective; - - /// Create an iterator over the message generators - fn iter(&self) -> GeneratorsRefIter<'_, Self> { - GeneratorsRefIter { - index: 0, - count: self.message_count() + 1, - gens: self, - } - } -} - -#[derive(Clone, Debug)] -pub struct GeneratorsRefIter<'g, G: Generators> { - index: usize, - count: usize, - gens: &'g G, -} - -impl Iterator for GeneratorsRefIter<'_, G> { - type Item = G1Projective; - - fn size_hint(&self) -> (usize, Option) { - let len = self.count - self.index; - (len, Some(len)) - } - - fn next(&mut self) -> Option { - let idx = self.index; - if idx < self.count { - self.index += 1; - Some(self.gens.generator(idx)) - } else { - None - } - } -} - -impl ExactSizeIterator for GeneratorsRefIter<'_, G> {} - -/// The default pre-computed message generators -pub type VecGenerators = GeneratorsSeq>; - -#[derive(Debug)] -/// A pre-computed sequence of message generators -pub struct GeneratorsSeq -where - S: Seq, -{ - h: Vec, - pk: G2Affine, -} - -impl Clone for GeneratorsSeq -where - S: Seq, - Vec: Clone, -{ - fn clone(&self) -> Self { - Self { - h: self.h.clone(), - pk: self.pk.clone(), - } - } -} - -impl Generators for GeneratorsSeq -where - S: Seq, -{ - fn generator(&self, index: usize) -> G1Projective { - self.h[index] - } - - fn message_count(&self) -> usize { - self.h.len() - 1 - } - - fn public_key(&self) -> G2Affine { - self.pk - } -} - -impl GeneratorsSeq -where - S: Seq, -{ - /// Populate the message generators from another source - pub fn copy_from(gens: &G) -> Result { - Ok(Self { - h: Vec::from_iter(gens.iter())?, - pk: gens.public_key(), - }) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -/// A dynamic (not pre-computed) message generator source -pub struct DynGenerators { - count: usize, - pk: G2Affine, -} - -impl DynGenerators { - /// Create a new instance of the message generators - pub fn new(pk: &BlsKeyPair, message_count: usize) -> Self { - Self { - count: message_count, - pk: *pk.bls_public_key(), - } - } - - /// Compute all the generators - pub fn to_vec(&self) -> Result { - VecGenerators::copy_from(self) - } -} - -impl Generators for DynGenerators { - fn generator(&self, index: usize) -> G1Projective { - const HASH_BUF_SIZE: usize = 10 + G2_UNCOMPRESSED_SIZE; - - let mut hash_buf = [0u8; HASH_BUF_SIZE]; - hash_buf[..G2_UNCOMPRESSED_SIZE].copy_from_slice(&self.pk.to_uncompressed()[..]); - hash_buf[(G2_UNCOMPRESSED_SIZE + 1)..(G2_UNCOMPRESSED_SIZE + 5)] - .copy_from_slice(&(index as u32).to_be_bytes()[..]); - hash_buf[(G2_UNCOMPRESSED_SIZE + 6)..(G2_UNCOMPRESSED_SIZE + 10)] - .copy_from_slice(&(self.count as u32).to_be_bytes()[..]); - - >>::hash_to_curve( - &hash_buf[..], - GENERATORS_DST_G1, - ) - } - - fn message_count(&self) -> usize { - self.count - } - - fn public_key(&self) -> G2Affine { - self.pk - } -} diff --git a/askar-bbs/src/hash.rs b/askar-bbs/src/hash.rs deleted file mode 100644 index 0fb6f748..00000000 --- a/askar-bbs/src/hash.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Support for hashing inputs into scalar values - -use core::fmt::{self, Debug, Formatter}; - -use askar_crypto::buffer::WriteBuffer; -use bls12_381::Scalar; -use sha3::{ - digest::{ExtendableOutput, Update, XofReader}, - Sha3XofReader, Shake256, -}; -use subtle::ConstantTimeEq; - -use crate::Error; - -#[derive(Clone, Debug)] -/// Derive Scalar values by hashing an arbitrary length input using Shake256 -pub struct HashScalar<'d> { - hasher: Shake256, - dst: Option<&'d [u8]>, -} - -impl<'d> HashScalar<'d> { - /// Create a new HashScalar instance - pub fn new(dst: Option<&'d [u8]>) -> Self { - Self { - hasher: Shake256::default(), - dst, - } - } - - /// Create a new HashScalar instance with initial input to the hasher - pub fn new_with_input(input: &[u8], dst: Option<&'d [u8]>) -> Self { - let mut slf = Self::new(dst); - slf.update(input); - slf - } -} - -impl HashScalar<'_> { - #[inline] - /// Utility method to hash the input and return a single Scalar - pub fn digest(input: impl AsRef<[u8]>, dst: Option<&[u8]>) -> Scalar { - let mut state = HashScalar::new(dst); - state.update(input.as_ref()); - state.finalize().next() - } - - #[inline] - /// Add more input to the hash state - pub fn update(&mut self, input: impl AsRef<[u8]>) { - self.hasher.update(input.as_ref()); - } - - /// Finalize the hasher and return a factory for Scalar values - pub fn finalize(mut self) -> HashScalarRead { - if let Some(dst) = self.dst { - self.hasher.update(dst); - } - HashScalarRead(self.hasher.finalize_xof()) - } -} - -impl WriteBuffer for HashScalar<'_> { - fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { - self.update(data); - Ok(()) - } -} - -/// The output of a HashScalar, allowing for multiple Scalar values to be read -pub struct HashScalarRead(Sha3XofReader); - -impl HashScalarRead { - /// Read the next non-zero Scalar value from the extensible hash output - pub fn next(&mut self) -> Scalar { - let mut buf = [0u8; 64]; - let mut s; - loop { - self.0.read(&mut buf); - s = Scalar::from_bytes_wide(&buf); - if !bool::from(s.ct_eq(&Scalar::zero())) { - break s; - } - } - } -} - -impl Debug for HashScalarRead { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("HashScalarRead").finish() - } -} diff --git a/askar-bbs/src/io.rs b/askar-bbs/src/io.rs deleted file mode 100644 index ff106740..00000000 --- a/askar-bbs/src/io.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Support for reading and writing structures as bytes - -use core::{ - array::TryFromSliceError, - convert::{TryFrom, TryInto}, - fmt::Debug, -}; - -use askar_crypto::buffer::WriteBuffer; -use bls12_381::{G1Affine, Scalar}; - -use crate::Error; - -/// Convert to and from a fixed-length byte array -pub trait FixedLengthBytes: Sized { - /// The length of the byte array - const LENGTH: usize; - - /// The type of the byte array - type Buffer: AsRef<[u8]> + Clone + Copy + Debug; - - /// Work with a reference to the byte array - fn with_bytes(&self, f: impl FnOnce(&Self::Buffer) -> R) -> R; - - /// Convert from a byte array - fn from_bytes(buf: &Self::Buffer) -> Result; - - /// Read an instance from a cursor - fn read_bytes(cur: &mut Cursor<'_>) -> Result - where - for<'a> &'a Self::Buffer: TryFrom<&'a [u8], Error = TryFromSliceError>, - { - let buf = cur.read(Self::LENGTH)?.try_into().unwrap(); - Self::from_bytes(buf) - } - - /// Write the byte array to a target - fn write_bytes(&self, buf: &mut dyn WriteBuffer) -> Result<(), Error> { - self.with_bytes(|b| buf.buffer_write(b.as_ref())) - } -} - -impl FixedLengthBytes for Scalar { - const LENGTH: usize = 32; - - type Buffer = [u8; 32]; - - fn from_bytes(buf: &Self::Buffer) -> Result { - let mut b = *buf; - b.reverse(); // into little-endian - if let Some(s) = bls12_381::Scalar::from_bytes(&b).into() { - Ok(s) - } else { - Err(err_msg!(Usage, "Scalar bytes not in canonical format")) - } - } - - fn with_bytes(&self, f: impl FnOnce(&Self::Buffer) -> R) -> R { - let mut b = self.to_bytes(); - b.reverse(); // into big-endian - f(&b) - } -} - -#[derive(Clone, Debug)] -/// A cursor for incrementally parsing a byte slice -pub struct Cursor<'r>(&'r [u8]); - -impl<'r> Cursor<'r> { - /// Create a new cursor instance - pub fn new(buf: &'r [u8]) -> Self { - Self(buf) - } -} - -impl Cursor<'_> { - /// The remaining length of the slice - pub fn len(&self) -> usize { - self.0.len() - } - - /// Read a number of bytes from the slice - pub fn read(&mut self, len: usize) -> Result<&[u8], Error> { - if self.0.len() < len { - Err(err_msg!(ExceededBuffer)) - } else { - let (pfx, rest) = self.0.split_at(len); - self.0 = rest; - Ok(pfx) - } - } - - /// Read a type-safe number of bytes from the slice - pub fn read_fixed(&mut self) -> Result<&[u8; L], Error> { - if self.0.len() < L { - Err(err_msg!(ExceededBuffer)) - } else { - let (pfx, rest) = self.0.split_at(L); - self.0 = rest; - Ok(pfx.try_into().unwrap()) - } - } -} - -pub(crate) trait CompressedBytes: Sized { - fn read_compressed(cur: &mut Cursor<'_>) -> Result; - - fn write_compressed(&self, buf: &mut dyn WriteBuffer) -> Result<(), Error>; -} - -impl CompressedBytes for G1Affine { - fn read_compressed(cur: &mut Cursor<'_>) -> Result { - if let Some(pt) = G1Affine::from_compressed(cur.read_fixed()?).into() { - Ok(pt) - } else { - Err(err_msg!(Invalid, "Invalid G1 element")) - } - } - - fn write_compressed(&self, buf: &mut dyn WriteBuffer) -> Result<(), Error> { - buf.buffer_write(&self.to_compressed()) - } -} diff --git a/askar-bbs/src/lib.rs b/askar-bbs/src/lib.rs deleted file mode 100644 index a3d746b7..00000000 --- a/askar-bbs/src/lib.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! BBS+ signature support for aries-askar - -#![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![deny(missing_debug_implementations, missing_docs, rust_2018_idioms)] - -#[cfg(feature = "alloc")] -extern crate alloc; - -pub use bls12_381; - -#[macro_use] -extern crate askar_crypto; -pub use askar_crypto::{Error, ErrorKind}; - -#[macro_use] -mod macros; - -mod challenge; -pub use challenge::{CreateChallenge, ProofChallenge}; - -pub mod collect; - -mod commitment; -pub use commitment::{ - Blinding, Commitment, CommitmentBuilder, CommitmentProof, CommitmentProofContext, - CommitmentProofVerifier, COMMITMENT_PROOF_DST_G1, -}; - -mod generators; -pub use generators::{DynGenerators, Generators, GeneratorsSeq, VecGenerators, GENERATORS_DST_G1}; - -pub mod hash; - -pub mod io; - -mod proof; -pub use proof::{ - SignatureProof, SignatureProofContext, SignatureProofVerifier, SignatureProver, - SIGNATURE_PROOF_DST_G1, -}; - -mod signature; -pub use signature::{Message, Signature, SignatureBuilder, SignatureVerifier}; - -mod util; -pub use util::Nonce; diff --git a/askar-bbs/src/macros.rs b/askar-bbs/src/macros.rs deleted file mode 100644 index d6d873a2..00000000 --- a/askar-bbs/src/macros.rs +++ /dev/null @@ -1,46 +0,0 @@ -macro_rules! impl_scalar_type { - ($type:ident, $doc:expr) => { - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - #[doc = $doc] - pub struct $type(pub(crate) bls12_381::Scalar); - - impl $crate::io::FixedLengthBytes for $type { - const LENGTH: usize = 32; - - type Buffer = [u8; 32]; - - fn from_bytes(buf: &Self::Buffer) -> Result { - let s = ::from_bytes(buf)?; - Ok(Self(s)) - } - - fn with_bytes(&self, f: impl FnOnce(&Self::Buffer) -> R) -> R { - ::with_bytes(&self.0, f) - } - } - - impl subtle::ConstantTimeEq for $type { - fn ct_eq(&self, other: &Self) -> subtle::Choice { - self.0.ct_eq(&other.0) - } - } - - impl From<&bls12_381::Scalar> for $type { - fn from(s: &bls12_381::Scalar) -> Self { - Self(*s) - } - } - - impl From for $type { - fn from(s: bls12_381::Scalar) -> Self { - Self(s) - } - } - - impl From for $type { - fn from(s: u64) -> Self { - Self(bls12_381::Scalar::from(s)) - } - } - }; -} diff --git a/askar-bbs/src/proof.rs b/askar-bbs/src/proof.rs deleted file mode 100644 index 9617b77e..00000000 --- a/askar-bbs/src/proof.rs +++ /dev/null @@ -1,565 +0,0 @@ -#[cfg(feature = "alloc")] -use alloc::vec::Vec as StdVec; - -use askar_crypto::buffer::WriteBuffer; -use bls12_381::{pairing, G1Affine, G1Projective, G2Affine, Scalar}; -use rand::{CryptoRng, Rng}; -use subtle::ConstantTimeEq; - -#[cfg(feature = "getrandom")] -use askar_crypto::random::default_rng; - -use crate::{ - challenge::{CreateChallenge, ProofChallenge}, - collect::{DefaultSeq, Seq, Vec}, - commitment::Blinding, - generators::Generators, - io::{CompressedBytes, Cursor, FixedLengthBytes}, - signature::{Message, Signature}, - util::{random_scalar, AccumG1, Nonce}, - Error, -}; - -/// A standard domain-specific input for use in signature proofs of knowledge -pub const SIGNATURE_PROOF_DST_G1: &[u8] = b"BLS12381G1_BBS+_SIGNATURES_POK:1_0_0"; - -#[derive(Clone, Debug)] -/// Generate a signature proof of knowledge -pub struct SignatureProver<'g, G, S = DefaultSeq<128>> -where - G: Generators, - S: Seq<(Message, Blinding)>, -{ - accum_b: AccumG1, - accum_c2: AccumG1, - count: usize, - generators: &'g G, - hidden: Vec<(Message, Blinding), S>, - signature: Signature, -} - -impl<'g, G> SignatureProver<'g, G> -where - G: Generators, -{ - /// Create a new signature prover - pub fn new( - generators: &'g G, - signature: &Signature, - ) -> SignatureProver<'g, G, DefaultSeq<128>> { - Self::new_sized(generators, signature) - } -} - -impl<'g, G, S> SignatureProver<'g, G, S> -where - G: Generators, - S: Seq<(Message, Blinding)>, -{ - /// Create a new signature prover with a specific backing sequence type - pub fn new_sized(generators: &'g G, signature: &Signature) -> Self { - Self { - accum_b: AccumG1::new_with(G1Projective::generator()), - accum_c2: AccumG1::zero(), - count: 0, - generators, - hidden: Vec::new(), - signature: *signature, - } - } -} - -impl SignatureProver<'_, G, S> -where - G: Generators, - S: Seq<(Message, Blinding)>, -{ - /// Push a revealed signed message - pub fn push_message(&mut self, message: Message) -> Result<(), Error> { - let c = self.count; - if c >= self.generators.message_count() { - return Err(err_msg!(Usage, "Message index exceeds generator count")); - } - self.accum_b.push(self.generators.message(c), message.0); - self.count = c + 1; - Ok(()) - } - - /// Push a sequence of revealed signed messages - pub fn append_messages( - &mut self, - messages: impl IntoIterator, - ) -> Result<(), Error> { - for msg in messages { - self.push_message(msg)?; - } - Ok(()) - } - - #[cfg(feature = "getrandom")] - /// Push a hidden signed message - pub fn push_hidden_message(&mut self, message: Message) -> Result<(), Error> { - self.push_hidden_message_with(message, Blinding::random()) - } - - /// Push a hidden signed message with a specific blinding value - pub fn push_hidden_message_with( - &mut self, - message: Message, - blinding: Blinding, - ) -> Result<(), Error> { - let c = self.count; - if c >= self.generators.message_count() { - return Err(err_msg!(Usage, "Message index exceeds generator count")); - } - let base = self.generators.message(c); - self.hidden.push((message, blinding))?; - self.accum_b.push(base, message.0); - self.accum_c2.push(base, blinding.0); - self.count = c + 1; - Ok(()) - } - - #[cfg(feature = "getrandom")] - /// Prepare the context for generating the final proof - pub fn prepare(self) -> Result, Error> { - self.prepare_with_rng(default_rng()) - } - - /// Prepare the context for generating the final proof - pub fn prepare_with_rng( - mut self, - mut rng: impl CryptoRng + Rng, - ) -> Result, Error> { - if self.count != self.generators.message_count() { - return Err(err_msg!( - Usage, - "Message count does not match generator count" - )); - } - - let Signature { a, e, s } = self.signature; - self.accum_b.push(self.generators.blinding(), s); - let b = self.accum_b.sum(); - let h0 = self.generators.blinding(); - let r1 = random_scalar(&mut rng); - let r2 = random_scalar(&mut rng); - let r3 = r1.invert().unwrap(); - let e_rand = random_scalar(&mut rng); - let r2_rand = random_scalar(&mut rng); - let r3_rand = random_scalar(&mut rng); - let s_rand = random_scalar(&mut rng); - - let b_r1 = b * r1; - let a_prime = a * r1; - let a_bar = a_prime * (-e) + b_r1; - let d = h0 * (-r2) + b_r1; - let s_prime = s - r2 * r3; - - let c1 = AccumG1::calc(&[(a_prime, e_rand), (h0, r2_rand)]); - self.accum_c2.append(&[(d, r3_rand), (h0, s_rand)][..]); - - let mut affine = [G1Affine::identity(); 5]; - G1Projective::batch_normalize( - &[a_prime, a_bar, d, c1, self.accum_c2.sum()], - &mut affine[..], - ); - - Ok(SignatureProofContext { - params: ProofPublicParams { - a_prime: affine[0], - a_bar: affine[1], - d: affine[2], - }, - c1: affine[3], - c2: affine[4], - e, - e_rand, - r2, - r2_rand, - r3, - r3_rand, - s_prime, - s_rand, - hidden: self.hidden, - }) - } - - #[cfg(feature = "getrandom")] - /// Complete an independent signature proof of knowledge - pub fn complete(self, nonce: Nonce) -> Result<(ProofChallenge, SignatureProof), Error> - where - S: Seq, - { - self.complete_with_rng(default_rng(), nonce) - } - - /// Complete an independent signature proof of knowledge with a given RNG - pub fn complete_with_rng( - self, - rng: impl CryptoRng + Rng, - nonce: Nonce, - ) -> Result<(ProofChallenge, SignatureProof), Error> - where - S: Seq, - { - let context = self.prepare_with_rng(rng)?; - let challenge = context.create_challenge(nonce, Some(SIGNATURE_PROOF_DST_G1))?; - let proof = context.complete(challenge)?; - Ok((challenge, proof)) - } -} - -#[derive(Clone, Debug)] -/// A prepared context for generating a signature proof of knowledge -pub struct SignatureProofContext -where - S: Seq<(Message, Blinding)>, -{ - params: ProofPublicParams, - c1: G1Affine, - c2: G1Affine, - e: Scalar, - e_rand: Scalar, - r2: Scalar, - r2_rand: Scalar, - r3: Scalar, - r3_rand: Scalar, - s_prime: Scalar, - s_rand: Scalar, - hidden: Vec<(Message, Blinding), S>, -} - -impl SignatureProofContext -where - S: Seq<(Message, Blinding)>, - S: Seq, -{ - /// Complete the signature proof of knowledge given a Fiat-Shamir challenge value - pub fn complete(&self, challenge: ProofChallenge) -> Result, Error> { - let c = challenge.0; - let mut m_resp = Vec::with_capacity(self.hidden.len()); - for (msg, m_rand) in self.hidden.iter() { - m_resp.push(m_rand.0 - c * msg.0)?; - } - Ok(SignatureProof { - params: self.params, - e_resp: self.e_rand + c * self.e, - r2_resp: self.r2_rand - c * self.r2, - r3_resp: self.r3_rand + c * self.r3, - s_resp: self.s_rand - c * self.s_prime, - m_resp, - }) - } -} - -impl CreateChallenge for SignatureProofContext -where - S: Seq<(Message, Blinding)>, -{ - fn write_challenge_bytes(&self, writer: &mut dyn WriteBuffer) -> Result<(), Error> { - self.params - .write_challenge_bytes(&self.c1, &self.c2, writer) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct ProofPublicParams { - a_prime: G1Affine, - a_bar: G1Affine, - d: G1Affine, -} - -impl ProofPublicParams { - pub fn write_challenge_bytes( - &self, - c1: &G1Affine, - c2: &G1Affine, - writer: &mut dyn WriteBuffer, - ) -> Result<(), askar_crypto::Error> { - writer.buffer_write(&self.a_bar.to_uncompressed())?; - writer.buffer_write(&self.a_prime.to_uncompressed())?; - writer.buffer_write(&self.d.to_uncompressed())?; - writer.buffer_write(&c1.to_uncompressed())?; - writer.buffer_write(&c2.to_uncompressed())?; - Ok(()) - } -} - -#[derive(Clone, Debug)] -/// A signature proof of knowledge -pub struct SignatureProof -where - S: Seq, -{ - params: ProofPublicParams, - e_resp: Scalar, - r2_resp: Scalar, - r3_resp: Scalar, - s_resp: Scalar, - m_resp: Vec, -} - -impl SignatureProof> { - /// Convert a signature proof of knowledge from a byte slice - pub fn from_bytes(buf: &[u8]) -> Result { - Self::from_bytes_sized(buf) - } -} - -impl SignatureProof -where - S: Seq, -{ - /// Create a verifier for the signature proof of knowledge - pub fn verifier<'v, G>( - &'v self, - generators: &'v G, - challenge: ProofChallenge, - ) -> Result, Error> - where - G: Generators, - { - SignatureProofVerifier::new(generators, self, challenge) - } - - /// Write the signature proof of knowledge to an output buffer - pub fn write_bytes(&self, buf: &mut dyn WriteBuffer) -> Result<(), Error> { - self.params.a_prime.write_compressed(&mut *buf)?; - self.params.a_bar.write_compressed(&mut *buf)?; - self.params.d.write_compressed(&mut *buf)?; - self.e_resp.write_bytes(&mut *buf)?; - self.r2_resp.write_bytes(&mut *buf)?; - self.r3_resp.write_bytes(&mut *buf)?; - self.s_resp.write_bytes(&mut *buf)?; - buf.buffer_write(&(self.m_resp.len() as u32).to_be_bytes())?; - for resp in self.m_resp.iter() { - resp.write_bytes(&mut *buf)?; - } - Ok(()) - } - - #[cfg(feature = "alloc")] - /// Output the signature proof of knowledge as a byte vec - pub fn to_bytes(&self) -> Result, Error> { - let mut out = StdVec::with_capacity(48 * 3 + 32 * 5 + 4); - self.write_bytes(&mut out)?; - Ok(out) - } - - /// Convert a signature proof of knowledge from a byte slice - pub fn from_bytes_sized(buf: &[u8]) -> Result { - let mut cur = Cursor::new(buf); - let params = ProofPublicParams { - a_prime: G1Affine::read_compressed(&mut cur)?, - a_bar: G1Affine::read_compressed(&mut cur)?, - d: G1Affine::read_compressed(&mut cur)?, - }; - let e_resp = Scalar::read_bytes(&mut cur)?; - let r2_resp = Scalar::read_bytes(&mut cur)?; - let r3_resp = Scalar::read_bytes(&mut cur)?; - let s_resp = Scalar::read_bytes(&mut cur)?; - let m_len = u32::from_be_bytes(*cur.read_fixed()?) as usize; - let mut m_resp = Vec::with_capacity(m_len); - for _ in 0..m_len { - m_resp.push(Scalar::read_bytes(&mut cur)?)?; - } - if cur.len() != 0 { - return Err(err_msg!(Invalid, "Invalid length")); - } - Ok(Self { - params, - e_resp, - r2_resp, - r3_resp, - s_resp, - m_resp, - }) - } - - /// Get the response value from the post-challenge phase of the sigma protocol - /// for a given message index - pub fn get_response(&self, index: usize) -> Result { - self.m_resp - .get(index) - .map(Blinding::from) - .ok_or_else(|| err_msg!(Usage, "Invalid index for hidden message")) - } -} - -impl PartialEq> for SignatureProof -where - S: Seq, - T: Seq, -{ - fn eq(&self, other: &SignatureProof) -> bool { - self.params == other.params - && self.e_resp == other.e_resp - && self.r2_resp == other.r2_resp - && self.r3_resp == other.r3_resp - && self.s_resp == other.s_resp - && &*self.m_resp == &*other.m_resp - } -} -impl Eq for SignatureProof where S: Seq {} - -#[derive(Clone, Debug)] -/// A verifier for a signature proof of knowledge -pub struct SignatureProofVerifier<'v, G, S> -where - G: Generators, - S: Seq, -{ - generators: &'v G, - proof: &'v SignatureProof, - neg_challenge: Scalar, - c1: G1Projective, - accum_c2: AccumG1, - hidden_count: usize, - message_count: usize, -} - -impl<'v, G, S> SignatureProofVerifier<'v, G, S> -where - G: Generators, - S: Seq, -{ - pub(crate) fn new( - generators: &'v G, - proof: &'v SignatureProof, - challenge: ProofChallenge, - ) -> Result { - let ProofPublicParams { a_prime, a_bar, d } = proof.params; - let challenge = challenge.0; - let neg_challenge = -challenge; // negated early for multiplying - - let h0 = generators.blinding(); - let c1 = AccumG1::calc(&[ - (a_prime.into(), proof.e_resp), - (h0, proof.r2_resp), - (G1Projective::from(a_bar) - d, challenge), - ]); - let accum_c2 = AccumG1::from( - &[ - (d.into(), proof.r3_resp), - (h0, proof.s_resp), - (G1Projective::generator(), neg_challenge), - ][..], - ); - - Ok(Self { - generators, - proof, - neg_challenge, - c1, - accum_c2, - hidden_count: 0, - message_count: 0, - }) - } -} - -impl SignatureProofVerifier<'_, G, S> -where - G: Generators, - S: Seq, -{ - /// Push a revealed signed message - pub fn push_revealed(&mut self, message: Message) -> Result<(), Error> { - let c = self.message_count; - if c >= self.generators.message_count() { - return Err(err_msg!(Usage, "Message index exceeds generator count")); - } - self.accum_c2 - .push(self.generators.message(c), message.0 * self.neg_challenge); - self.message_count = c + 1; - Ok(()) - } - - /// Push a sequence of revealed signed messages - pub fn append_revealed( - &mut self, - messages: impl IntoIterator, - ) -> Result<(), Error> { - for msg in messages { - self.push_revealed(msg)?; - } - Ok(()) - } - - /// Push a number of hidden signed messages - pub fn push_hidden_count(&mut self, count: usize) -> Result<(), Error> { - let c = self.message_count + count; - if c > self.generators.message_count() { - return Err(err_msg!(Usage, "Message index exceeds generator count")); - } - if self.hidden_count + c > self.proof.m_resp.len() { - return Err(err_msg!( - Usage, - "Hidden message count exceeded response count" - )); - } - for index in self.message_count..c { - self.accum_c2.push( - self.generators.message(index), - self.proof.m_resp[self.hidden_count], - ); - self.hidden_count += 1; - } - self.message_count = c; - Ok(()) - } - - /// Complete the proof challenge value for an independent proof - pub fn complete(&self, nonce: Nonce) -> Result { - self.create_challenge(nonce, Some(SIGNATURE_PROOF_DST_G1)) - } - - /// Verify the signature proof of knowledge - pub fn verify(&self, challenge_v: ProofChallenge) -> Result<(), Error> { - if self.message_count != self.generators.message_count() { - return Err(err_msg!( - Invalid, - "Number of messages does not correspond with generators" - )); - } - if self.hidden_count != self.proof.m_resp.len() { - return Err(err_msg!( - Invalid, - "Number of hidden messages does not correspond with responses" - )); - } - // the challenge value is negated on this struct, so compare the sum to zero - if challenge_v.0 + self.neg_challenge != Scalar::zero() { - return Err(err_msg!( - Invalid, - "Signature proof of knowledge challenge mismatch" - )); - } - - let ProofPublicParams { a_prime, a_bar, .. } = self.proof.params; - let check_pair = pairing(&a_prime, &self.generators.public_key()) - .ct_eq(&pairing(&a_bar, &G2Affine::generator())); - - let verify: bool = (!a_prime.is_identity() & check_pair).into(); - if verify { - Ok(()) - } else { - Err(err_msg!(Invalid)) - } - } -} - -impl CreateChallenge for SignatureProofVerifier<'_, G, S> -where - G: Generators, - S: Seq, -{ - fn write_challenge_bytes(&self, writer: &mut dyn WriteBuffer) -> Result<(), Error> { - let mut checks = [G1Affine::identity(); 2]; - G1Projective::batch_normalize(&[self.c1, self.accum_c2.sum()], &mut checks[..]); - self.proof - .params - .write_challenge_bytes(&checks[0], &checks[1], writer) - } -} diff --git a/askar-bbs/src/signature.rs b/askar-bbs/src/signature.rs deleted file mode 100644 index ba638c8e..00000000 --- a/askar-bbs/src/signature.rs +++ /dev/null @@ -1,287 +0,0 @@ -use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - buffer::Writer, -}; -use bls12_381::{pairing, G1Affine, G1Projective, G2Affine, G2Projective, Scalar}; -use group::Curve; -use subtle::ConstantTimeEq; - -use crate::{ - commitment::{Blinding, Commitment}, - generators::Generators, - hash::HashScalar, - io::{CompressedBytes, Cursor, FixedLengthBytes}, - proof::SignatureProver, - util::AccumG1, - Error, -}; - -const SIGNATURE_LENGTH: usize = 48 + 32 + 32; - -impl_scalar_type!(Message, "A message value used in a signature"); - -impl Message { - /// Generate a message value by hashing arbitrary length input - pub fn hash(input: impl AsRef<[u8]>) -> Self { - Self(HashScalar::digest(input, None)) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -/// A BBS+ signature value -pub struct Signature { - pub(crate) a: G1Affine, - pub(crate) e: Scalar, - pub(crate) s: Scalar, -} - -impl Signature { - /// Create a prover for this signature - pub fn prover<'g, G>(&self, generators: &'g G) -> SignatureProver<'g, G> - where - G: Generators, - { - SignatureProver::new(generators, &self) - } - - /// Unblind a signature created against a commitment - pub fn unblind(self, blinding: Blinding) -> Self { - let Signature { a, e, s } = self; - Self { - a, - e, - s: s + blinding.0, - } - } - - /// Verify a signature with a set of known messages - pub fn verify( - &self, - generators: &G, - messages: impl IntoIterator, - ) -> Result<(), Error> - where - G: Generators, - { - let mut verifier = SignatureVerifier::new(generators, self); - verifier.append_messages(messages)?; - verifier.verify() - } - - /// Create a new signature verifier - pub fn verifier<'g, G>(&self, generators: &'g G) -> SignatureVerifier<'g, G> - where - G: Generators, - { - SignatureVerifier::new(generators, self) - } -} - -impl FixedLengthBytes for Signature { - const LENGTH: usize = SIGNATURE_LENGTH; - - type Buffer = [u8; SIGNATURE_LENGTH]; - - fn with_bytes(&self, f: impl FnOnce(&Self::Buffer) -> R) -> R { - let mut buf = [0u8; Self::LENGTH]; - let mut w = Writer::from_slice(&mut buf); - self.a.write_compressed(&mut w).unwrap(); - self.e.write_bytes(&mut w).unwrap(); - self.s.write_bytes(&mut w).unwrap(); - f(&buf) - } - - fn from_bytes(buf: &Self::Buffer) -> Result { - let mut cur = Cursor::new(buf); - let a = G1Affine::read_compressed(&mut cur)?; - let e = Scalar::read_bytes(&mut cur)?; - let s = Scalar::read_bytes(&mut cur)?; - Ok(Self { a, e, s }) - } -} - -#[derive(Clone, Debug)] -/// A builder for a signature -pub struct SignatureBuilder<'g, G: Generators> { - accum_b: AccumG1, - generators: &'g G, - hash_es: HashScalar<'static>, - key: &'g BlsKeyPair, - message_count: usize, -} - -impl<'g, G: Generators> SignatureBuilder<'g, G> { - /// Create a new signature builder - pub fn new(generators: &'g G, key: &'g BlsKeyPair) -> Self { - Self::from_accum(generators, key, G1Projective::generator()) - } - - /// Create a new signature builder with a blinded messages commitment value - pub fn from_commitment( - generators: &'g G, - key: &'g BlsKeyPair, - commitment: Commitment, - ) -> Self { - Self::from_accum(generators, key, G1Projective::generator() + commitment.0) - } - - /// Utility method to sign a set of messages with no blinded commitment - pub fn sign( - generators: &'g G, - key: &'g BlsKeyPair, - messages: impl IntoIterator, - ) -> Result { - let mut slf = Self::from_accum(generators, key, G1Projective::generator()); - slf.append_messages(messages)?; - slf.to_signature() - } - - #[inline] - fn from_accum(generators: &'g G, key: &'g BlsKeyPair, sum: G1Projective) -> Self { - Self { - accum_b: AccumG1::new_with(sum), - generators, - hash_es: HashScalar::new_with_input(&sum.to_affine().to_compressed(), None), - key, - message_count: 0, - } - } -} - -impl SignatureBuilder<'_, G> { - /// Push a message to be signed - pub fn push_message(&mut self, message: Message) -> Result<(), Error> { - let c = self.message_count; - if c >= self.generators.message_count() { - return Err(err_msg!(Usage, "Message index exceeds generator count")); - } - self.accum_b.push(self.generators.message(c), message.0); - self.hash_es.update(&message.0.to_bytes()); - self.message_count = c + 1; - Ok(()) - } - - /// Push a sequence of messages to be signed - pub fn append_messages( - &mut self, - messages: impl IntoIterator, - ) -> Result<(), Error> { - for msg in messages { - self.push_message(msg)?; - } - Ok(()) - } - - /// Push a number of blind (committed) messages - pub fn push_committed_count(&mut self, count: usize) -> Result<(), Error> { - let c = self.message_count + count; - if c > self.generators.message_count() { - return Err(err_msg!(Usage, "Message index exceeds generator count")); - } - self.message_count = c; - Ok(()) - } - - /// Get the current number of added messages - pub fn len(&self) -> usize { - self.message_count - } - - /// Create a signature from the builder - pub fn to_signature(&self) -> Result { - if self.message_count != self.generators.message_count() { - return Err(err_msg!( - Usage, - "Message count does not match generator count" - )); - } - let sk = self - .key - .bls_secret_scalar() - .ok_or_else(|| err_msg!(MissingSecretKey))?; - if sk == &Scalar::zero() { - return Err(err_msg!(MissingSecretKey)); - } - let mut hash_es = self.hash_es.clone(); - hash_es.update(sk.to_bytes()); - let mut hash_read = hash_es.finalize(); - let e = hash_read.next(); - let s = hash_read.next(); - let b = self.accum_b.sum_with(self.generators.blinding(), s); - let a = (b * (sk + e).invert().unwrap()).to_affine(); - Ok(Signature { a, e, s }) - } -} - -#[derive(Clone, Debug)] -/// A verifier for a BBS+ signature -pub struct SignatureVerifier<'g, G: Generators> { - accum_b: AccumG1, - generators: &'g G, - message_count: usize, - signature: Signature, -} - -impl<'g, G: Generators> SignatureVerifier<'g, G> { - /// Create a new signature verifier - pub fn new(generators: &'g G, signature: &Signature) -> Self { - Self { - accum_b: AccumG1::new_with(G1Projective::generator()), - generators, - message_count: 0, - signature: *signature, - } - } -} - -impl SignatureVerifier<'_, G> { - /// Push a signed message - pub fn push_message(&mut self, message: Message) -> Result<(), Error> { - let c = self.message_count; - if c >= self.generators.message_count() { - return Err(err_msg!(Usage, "Message index exceeds generator count")); - } - self.accum_b.push(self.generators.message(c), message.0); - self.message_count = c + 1; - Ok(()) - } - - /// Push a sequence of signed messages - pub fn append_messages( - &mut self, - messages: impl IntoIterator, - ) -> Result<(), Error> { - for msg in messages { - self.push_message(msg)?; - } - Ok(()) - } - - /// Get the current number of added messages - pub fn len(&self) -> usize { - self.message_count - } - - /// Verify a signature - pub fn verify(&self) -> Result<(), Error> { - if self.message_count != self.generators.message_count() { - return Err(err_msg!( - Usage, - "Message count does not match generator count" - )); - } - let Signature { a, e, s } = self.signature; - let b = self.accum_b.sum_with(self.generators.blinding(), s); - let valid: bool = pairing( - &a, - &(G2Projective::generator() * e + self.generators.public_key()).to_affine(), - ) - .ct_eq(&pairing(&b.to_affine(), &G2Affine::generator())) - .into(); - if valid { - Ok(()) - } else { - Err(err_msg!(Invalid)) - } - } -} diff --git a/askar-bbs/src/util.rs b/askar-bbs/src/util.rs deleted file mode 100644 index c783315a..00000000 --- a/askar-bbs/src/util.rs +++ /dev/null @@ -1,192 +0,0 @@ -use bls12_381::{G1Projective, Scalar}; -use rand::{CryptoRng, Rng}; -use subtle::ConstantTimeEq; - -#[cfg(feature = "getrandom")] -use askar_crypto::random::default_rng; - -pub(crate) fn random_scalar(mut rng: R) -> Scalar { - let mut buf = [0u8; 64]; - let mut s; - loop { - rng.fill_bytes(&mut buf); - s = Scalar::from_bytes_wide(&buf); - if !bool::from(s.ct_eq(&Scalar::zero())) { - break s; - } - } -} - -impl_scalar_type!(Nonce, "A nonce used in proof verification"); - -impl Nonce { - #[cfg(feature = "getrandom")] - /// Generate a new random nonce value - pub fn random() -> Self { - Self(random_scalar(default_rng())) - } - - /// Generate a new random nonce value from a specific RNG - pub fn random_with_rng(rng: impl CryptoRng + Rng) -> Self { - Self(random_scalar(rng)) - } -} - -// sum-of-products impl: - -// const ACCUM_BATCH: usize = 16; - -// #[derive(Clone, Debug)] -// pub(crate) struct AccumG1 { -// accum: G1Projective, -// stack_base: [G1Projective; ACCUM_BATCH], -// stack_factor: [Scalar; ACCUM_BATCH], -// stack_size: usize, -// } - -// impl AccumG1 { -// pub fn zero() -> Self { -// Self::new_with(G1Projective::identity()) -// } - -// pub fn new_with(accum: impl Into) -> Self { -// Self { -// accum: accum.into(), -// stack_base: [G1Projective::identity(); ACCUM_BATCH], -// stack_factor: [Scalar::zero(); ACCUM_BATCH], -// stack_size: 0, -// } -// } - -// pub fn calc(pairs: &[(G1Projective, Scalar)]) -> G1Projective { -// let mut acc = Self::zero(); -// acc.append(pairs); -// acc.sum() -// } - -// #[inline] -// fn rollup(&mut self) -> G1Projective { -// let sz = self.stack_size; -// G1Projective::sum_of_products_in_place(&self.stack_base[..sz], &mut self.stack_factor[..sz]) -// } - -// #[inline] -// pub fn push(&mut self, base: G1Projective, factor: Scalar) { -// let mut sz = self.stack_size; -// if sz == ACCUM_BATCH { -// let sum = self.rollup(); -// self.accum += sum; -// sz = 0; -// }; -// self.stack_base[sz] = base; -// self.stack_factor[sz] = factor; -// self.stack_size = sz + 1; -// } - -// pub fn append(&mut self, pairs: &[(G1Projective, Scalar)]) { -// for (base, factor) in pairs.into_iter().copied() { -// self.push(base, factor); -// } -// } - -// pub fn sum(&self) -> G1Projective { -// let mut sum = self.accum; -// let sz = self.stack_size; -// if sz > 0 { -// let mut factors = self.stack_factor; -// sum += -// G1Projective::sum_of_products_in_place(&self.stack_base[..sz], &mut factors[..sz]); -// } -// sum -// } - -// pub fn sum_mut(&mut self) -> G1Projective { -// if self.stack_size > 0 { -// let sum = self.rollup(); -// self.accum += sum; -// self.stack_size = 0; -// } -// self.accum -// } - -// pub fn sum_with(&self, base: G1Projective, factor: Scalar) -> G1Projective { -// let sum = self.accum; -// let mut sz = self.stack_size; -// if sz > 0 { -// let mut bases = [G1Projective::identity(); ACCUM_BATCH + 1]; -// let mut factors = [Scalar::zero(); ACCUM_BATCH + 1]; -// bases[..sz].copy_from_slice(&self.stack_base[..sz]); -// factors[..sz].copy_from_slice(&self.stack_factor[..sz]); -// bases[sz] = base; -// factors[sz] = factor; -// sz += 1; -// sum + G1Projective::sum_of_products_in_place(&bases[..sz], &mut factors[..sz]) -// } else { -// sum + base * factor -// } -// } -// } - -#[derive(Clone, Debug)] -pub(crate) struct AccumG1 { - accum: G1Projective, -} - -impl AccumG1 { - pub fn zero() -> Self { - Self::new_with(G1Projective::identity()) - } - - pub fn new_with(accum: impl Into) -> Self { - Self { - accum: accum.into(), - } - } - - pub fn calc(pairs: &[(G1Projective, Scalar)]) -> G1Projective { - let mut acc = Self::zero(); - acc.append(pairs); - acc.sum() - } - - #[inline] - pub fn push(&mut self, base: G1Projective, factor: Scalar) { - self.accum += base * factor; - } - - pub fn append(&mut self, pairs: &[(G1Projective, Scalar)]) { - for (base, factor) in pairs.into_iter().copied() { - self.push(base, factor); - } - } - - pub fn sum(&self) -> G1Projective { - self.accum - } - - pub fn sum_with(&self, base: G1Projective, factor: Scalar) -> G1Projective { - self.accum + base * factor - } -} - -impl From for AccumG1 { - fn from(accum: G1Projective) -> Self { - AccumG1::new_with(accum) - } -} - -impl From<(G1Projective, Scalar)> for AccumG1 { - fn from((base, factor): (G1Projective, Scalar)) -> Self { - let mut acc = AccumG1::zero(); - acc.push(base, factor); - acc - } -} - -impl From<&[(G1Projective, Scalar)]> for AccumG1 { - fn from(pairs: &[(G1Projective, Scalar)]) -> Self { - let mut acc = AccumG1::zero(); - acc.append(pairs); - acc - } -} diff --git a/askar-bbs/tests/blind_sign.rs b/askar-bbs/tests/blind_sign.rs deleted file mode 100644 index 3aa4af36..00000000 --- a/askar-bbs/tests/blind_sign.rs +++ /dev/null @@ -1,74 +0,0 @@ -#[cfg(feature = "getrandom")] -use askar_bbs::{ - CommitmentBuilder, CommitmentProof, DynGenerators, Message, Nonce, SignatureBuilder, -}; - -#[cfg(feature = "getrandom")] -use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - buffer::Writer, - repr::KeyGen, -}; - -#[cfg(feature = "getrandom")] -#[test] -fn test_commitment_verify() { - let keypair = BlsKeyPair::::random().unwrap(); - let gens = DynGenerators::new(&keypair, 5); - let nonce = Nonce::random(); - let commit_messages = [(0, Message::hash(b"hello"))]; - let mut committer = CommitmentBuilder::new(&gens); - for (index, message) in commit_messages.iter().copied() { - committer.add_message(index, message).unwrap(); - } - let (challenge, _blinding, commitment, proof) = committer - .complete(nonce) - .expect("Error completing commitment"); - proof - .verify(&gens, commitment, [0].iter().copied(), challenge, nonce) - .expect("Error verifying commitment"); - - // test serialization round trip - let mut buf = [0u8; 1024]; - let mut w = Writer::from_slice(&mut buf); - proof.write_bytes(&mut w).expect("Error serializing proof"); - let proof_len = w.position(); - let proof_de = - CommitmentProof::from_bytes(&buf[..proof_len]).expect("Error deserializing proof"); - assert_eq!(proof, proof_de); -} - -#[cfg(feature = "getrandom")] -#[test] -fn test_blind_signature() { - let keypair = BlsKeyPair::::random().unwrap(); - let gens = DynGenerators::new(&keypair, 2); - let nonce = Nonce::random(); - let commit_messages = [(0, Message::hash(b"hello"))]; - let mut committer = CommitmentBuilder::new(&gens); - for (index, message) in commit_messages.iter().copied() { - committer.add_message(index, message).unwrap(); - } - let (challenge, blinding, commitment, proof) = committer - .complete(nonce) - .expect("Error completing commitment"); - proof - .verify(&gens, commitment, [0].iter().copied(), challenge, nonce) - .expect("Error verifying commitment"); - - let sign_messages = [Message::hash(b"world")]; - let mut signer = SignatureBuilder::from_commitment(&gens, &keypair, commitment); - signer.push_committed_count(1).unwrap(); - signer - .append_messages(sign_messages.iter().copied()) - .unwrap(); - let blind_signature = signer.to_signature().expect("Error creating signature"); - - let signature = blind_signature.unblind(blinding); - let mut verifier = signature.verifier(&gens); - verifier.push_message(commit_messages[0].1).unwrap(); - verifier - .append_messages(sign_messages.iter().copied()) - .unwrap(); - verifier.verify().expect("Error verifying signature"); -} diff --git a/askar-bbs/tests/generators.rs b/askar-bbs/tests/generators.rs deleted file mode 100644 index dffcfb74..00000000 --- a/askar-bbs/tests/generators.rs +++ /dev/null @@ -1,22 +0,0 @@ -use askar_bbs::{DynGenerators, Generators}; -use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - repr::KeySecretBytes, -}; -use bls12_381::G1Projective; -use hex_literal::hex; - -#[test] -fn dyn_generators_expected() { - let keypair = BlsKeyPair::::from_secret_bytes(&hex!( - "0011223344556677889900112233445566778899001122334455667788990011" - )) - .unwrap(); - let message_count = 10; - let gens_count = message_count + 1; - let gens = DynGenerators::new(&keypair, message_count); - let iter = gens.iter(); - assert_eq!(iter.size_hint(), (gens_count, Some(gens_count))); - let hm: Vec = iter.collect(); - assert_eq!(hm.len(), gens_count); -} diff --git a/askar-bbs/tests/proof.rs b/askar-bbs/tests/proof.rs deleted file mode 100644 index 317a844c..00000000 --- a/askar-bbs/tests/proof.rs +++ /dev/null @@ -1,144 +0,0 @@ -#[cfg(feature = "getrandom")] -#[test] -fn prove_single_signature_hidden_message() { - use askar_bbs::{DynGenerators, Message, Nonce, SignatureBuilder, SignatureProof}; - use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - buffer::Writer, - repr::KeySecretBytes, - }; - use hex_literal::hex; - - let keypair = BlsKeyPair::::from_secret_bytes(&hex!( - "0011223344556677889900112233445566778899001122334455667788990011" - )) - .unwrap(); - let messages = [Message::hash("hello"), Message::hash("there")]; - let gens = DynGenerators::new(&keypair, messages.len()); - let mut builder = SignatureBuilder::new(&gens, &keypair); - builder - .append_messages(messages.iter().copied()) - .expect("Error building signature"); - let sig = builder.to_signature().expect("Error creating signature"); - - // verifier creates a nonce for the proof presentation - let nonce = Nonce::random(); - - // prover constructs the proof and challenge value for an independent proof - let mut prover = sig.prover(&gens); - prover.push_hidden_message(messages[0]).unwrap(); - prover.push_message(messages[1]).unwrap(); - let (challenge, proof) = prover - .complete(nonce) - .expect("Error creating signature pok"); - - // verifier checks the proof with the challenge value - let mut verifier = proof.verifier(&gens, challenge).unwrap(); - verifier.push_hidden_count(1).unwrap(); - verifier.push_revealed(messages[1]).unwrap(); - let challenge_v = verifier - .complete(nonce) - .expect("Error creating verification challenge"); - verifier - .verify(challenge_v) - .expect("Error verifying signature PoK"); - // double check challenge comparison for testing - assert_eq!(challenge, challenge_v); - - // test serialization round trip - let mut buf = [0u8; 1024]; - let mut w = Writer::from_slice(&mut buf); - proof.write_bytes(&mut w).expect("Error serializing proof"); - let proof_len = w.position(); - let proof_de = - SignatureProof::from_bytes(&buf[..proof_len]).expect("Error deserializing proof"); - assert_eq!(proof, proof_de); -} - -#[cfg(feature = "getrandom")] -#[test] -fn multi_proof_matching_hidden_message() { - use askar_bbs::{Blinding, DynGenerators, Message, Nonce, ProofChallenge, SignatureBuilder}; - use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - repr::KeySecretBytes, - }; - use hex_literal::hex; - - let test_proof_dst = b"test proof"; - let keypair = BlsKeyPair::::from_secret_bytes(&hex!( - "0011223344556677889900112233445566778899001122334455667788990011" - )) - .unwrap(); - let messages_1 = [Message::hash("hello"), Message::hash("there")]; - let messages_2 = [ - Message::hash("indeed"), - Message::hash("hello"), - Message::hash("stranger"), - ]; - let gens_1 = DynGenerators::new(&keypair, messages_1.len()); - let gens_2 = DynGenerators::new(&keypair, messages_2.len()); - let sig_1 = SignatureBuilder::sign(&gens_1, &keypair, messages_1.iter().copied()) - .expect("Error creating signature"); - let sig_2 = SignatureBuilder::sign(&gens_2, &keypair, messages_2.iter().copied()) - .expect("Error creating signature"); - - // verifier creates a nonce for the proof presentation - let nonce = Nonce::random(); - - // a common blinding value for the two messages to be proven equal - let msg_blind = Blinding::random(); - - // construct provers for the two signatures - let mut prover_1 = sig_1.prover(&gens_1); - prover_1 - .push_hidden_message_with(messages_1[0], msg_blind) - .unwrap(); - prover_1.push_message(messages_1[1]).unwrap(); - let prepare_1 = prover_1.prepare().unwrap(); - let mut prover_2 = sig_2.prover(&gens_2); - prover_2.push_hidden_message(messages_2[0]).unwrap(); - prover_2 - .push_hidden_message_with(messages_2[1], msg_blind) - .unwrap(); - prover_2.push_message(messages_2[2]).unwrap(); - let prepare_2 = prover_2.prepare().unwrap(); - - // prover creates a combined challenge value for the two sub-proofs - let challenge = ProofChallenge::create(&[&prepare_1, &prepare_2], nonce, Some(test_proof_dst)) - .expect("Error creating proof challenge"); - let proof_1 = prepare_1 - .complete(challenge) - .expect("Error completing signature pok"); - let proof_2 = prepare_2 - .complete(challenge) - .expect("Error completing signature pok"); - - // construct verifiers for the two sub-proofs - let mut verifier_1 = proof_1.verifier(&gens_1, challenge).unwrap(); - verifier_1.push_hidden_count(1).unwrap(); - verifier_1.push_revealed(messages_1[1]).unwrap(); - let mut verifier_2 = proof_2.verifier(&gens_2, challenge).unwrap(); - verifier_2.push_hidden_count(2).unwrap(); - verifier_2.push_revealed(messages_2[2]).unwrap(); - - // now verifier computes the challenge value - let challenge_v = - ProofChallenge::create(&[&verifier_1, &verifier_2], nonce, Some(test_proof_dst)) - .expect("Error creating proof challenge"); - // check the proofs - verifier_1 - .verify(challenge_v) - .expect("Error verifying signature PoK"); - verifier_2 - .verify(challenge_v) - .expect("Error verifying signature PoK"); - // double check challenge comparison for testing - assert_eq!(challenge, challenge_v); - - // check that the responses match, meaning that the hidden messages also match - assert_eq!( - proof_1.get_response(0).unwrap(), - proof_2.get_response(1).unwrap() - ); -} diff --git a/askar-bbs/tests/signature.rs b/askar-bbs/tests/signature.rs deleted file mode 100644 index 6666b919..00000000 --- a/askar-bbs/tests/signature.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[test] -fn sign_verify_expected() { - use askar_bbs::{io::FixedLengthBytes, DynGenerators, Message, Signature, SignatureBuilder}; - use askar_crypto::{ - alg::bls::{BlsKeyPair, G2}, - buffer::Writer, - repr::KeySecretBytes, - }; - use hex_literal::hex; - - let keypair = BlsKeyPair::::from_secret_bytes(&hex!( - "0011223344556677889900112233445566778899001122334455667788990011" - )) - .unwrap(); - let messages = [Message::hash("hello")]; - let gens = DynGenerators::new(&keypair, messages.len()); - let sig = SignatureBuilder::sign(&gens, &keypair, messages.iter().copied()) - .expect("Error creating signature"); - - let mut verifier = sig.verifier(&gens); - verifier - .append_messages(messages.iter().copied()) - .expect("Error verifying signature"); - verifier.verify().expect("Error verifying signature"); - - // test serialization round trip - let mut buf = [0u8; 112]; - let mut w = Writer::from_slice(&mut buf); - sig.write_bytes(&mut w) - .expect("Error serializing signature"); - let sig_len = w.position(); - assert_eq!(sig_len, 112); - let sig_de = Signature::from_bytes(&buf).expect("Error deserializing signature"); - assert_eq!(sig, sig_de); -} diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 1848df01..6802f1ae 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -2,13 +2,14 @@ name = "askar-crypto" version = "0.2.5" authors = ["Hyperledger Aries Contributors "] -edition = "2018" +edition = "2021" description = "Hyperledger Aries Askar cryptography" license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/hyperledger/aries-askar/" categories = ["cryptography", "no-std"] keywords = ["hyperledger", "aries", "didcomm", "ssi"] +rust-version = "1.65" [package.metadata.docs.rs] features = ["argon2", "std"] @@ -20,21 +21,21 @@ alloc = [] std = ["alloc", "serde/std", "serde-json-core/std", "std_rng"] all_keys = ["aes", "bls", "chacha", "ec_curves", "ed25519"] any_key = ["alloc"] -aes = ["aes-core", "aes-gcm", "block-modes", "hmac"] +aes = ["aes-core", "aes-gcm", "block-modes", "cbc", "cipher", "hmac"] bls = ["bls12_381", "hkdf"] chacha = ["chacha20poly1305"] crypto_box = ["alloc", "crypto_box_rs", "ed25519", "getrandom"] -ec_curves = ["elliptic-curve", "k256", "p256"] +ec_curves = ["elliptic-curve", "k256", "p256", "p384"] ed25519 = ["curve25519-dalek", "ed25519-dalek", "x25519-dalek"] getrandom = ["rand/getrandom"] std_rng = ["getrandom", "rand/std", "rand/std_rng"] [dev-dependencies] -base64 = { version = "0.13", default-features = false, features = ["alloc"] } -criterion = "0.3" -hex-literal = "0.3" +base64 = { version = "0.21", default-features = false, features = ["alloc"] } +criterion = "0.5" +hex-literal = "0.4" serde_cbor = "0.11" -serde-json-core = { version = "0.4", default-features = false, features = ["std"] } +serde-json-core = { version = "0.5", default-features = false, features = ["std"] } [[bench]] name = "enc" @@ -45,31 +46,34 @@ name = "kdf" harness = false [dependencies] -aead = "0.4" -aes-core = { package = "aes", version = "0.7", default-features = false, optional = true } -aes-gcm = { version = "0.9", default-features = false, features = ["aes"], optional = true } +aead = "0.5" +aes-core = { package = "aes", version = "0.8", default-features = false, optional = true } +aes-gcm = { version = "0.10", default-features = false, features = ["aes"], optional = true } arbitrary = { version = "1.0", optional = true, features = ["derive"] } -argon2 = { version = "0.3", default-features = false, features = ["alloc", "password-hash"], optional = true } -base64 = { version = "0.13", default-features = false } +argon2 = { version = "0.5", default-features = false, features = ["alloc", "password-hash"], optional = true } +base64 = { version = "0.21", default-features = false } blake2 = { version = "0.10", default-features = false } -block-modes = { version = "0.8", default-features = false, optional = true } -bls12_381 = { version = "0.6", default-features = false, features = ["groups", "zeroize"], optional = true } -chacha20 = { version = "0.7" } # should match chacha20poly1305 -chacha20poly1305 = { version = "0.8", default-features = false, optional = true } -crypto_box_rs = { package = "crypto_box", version = "0.6", default-features = false, features = ["u64_backend"], optional = true } +block-modes = { version = "0.9", default-features = false, optional = true } +bls12_381 = { version = "0.8", default-features = false, features = ["groups", "zeroize"], optional = true } +cbc = { version = "0.1", default-features = false, optional = true } +chacha20 = { version = "0.9" } # should match dependency of chacha20poly1305 +chacha20poly1305 = { version = "0.10", default-features = false, optional = true } +cipher = { version = "0.4", default-features = false, features = ["block-padding"], optional = true } +crypto_box_rs = { package = "crypto_box", version = "0.8", default-features = false, features = ["u64_backend"], optional = true } curve25519-dalek = { version = "3.1", default-features = false, features = ["u64_backend"], optional = true } ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"], optional = true } -elliptic-curve = { version = "0.11", optional = true } +elliptic-curve = { version = "0.13", optional = true } digest = "0.10" -group = "0.11" +group = "0.13" hkdf = { version = "0.12", optional = true } hmac = { version = "0.12", optional = true } -k256 = { version = "0.10", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256"], optional = true } -p256 = { version = "0.10", default-features = false, features = ["arithmetic", "ecdsa", "ecdh"], optional = true } +k256 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256"], optional = true } +p256 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdsa", "ecdh"], optional = true } +p384 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdsa", "ecdh"], optional = true } rand = { version = "0.8", default-features = false } serde = { version = "1.0", default-features = false, features = ["derive"] } -serde-json-core = { version = "0.4", default-features = false } +serde-json-core = { version = "0.5", default-features = false } subtle = "2.4" sha2 = { version = "0.10", default-features = false } x25519-dalek = { version = "=1.1", default-features = false, features = ["u64_backend"], optional = true } -zeroize = { version = "1.4", features = ["zeroize_derive"] } +zeroize = { version = "1.5", features = ["zeroize_derive"] } diff --git a/askar-crypto/benches/enc.rs b/askar-crypto/benches/enc.rs index eb700d11..21c01a1b 100644 --- a/askar-crypto/benches/enc.rs +++ b/askar-crypto/benches/enc.rs @@ -21,88 +21,88 @@ fn criterion_benchmark(c: &mut Criterion) { let mut message = vec![0u8; MSG_SIZE]; fill_random(&mut message[..]); - let message = &message[..]; + let message = message.as_slice(); - c.bench_function(&format!("random nonce"), move |b| { - b.iter(|| AesKey::::random_nonce()) + c.bench_function("random nonce", move |b| { + b.iter(AesKey::::random_nonce) }); - c.bench_function(&format!("aes128gcm encrypt"), move |b| { + c.bench_function("aes128gcm encrypt", move |b| { let key = AesKey::::random().unwrap(); let nonce = AesKey::::random_nonce(); let mut buffer = Vec::with_capacity(ALLOC_SIZE); b.iter(|| { buffer.clear(); - buffer.extend_from_slice(black_box(&message[..])); + buffer.extend_from_slice(black_box(message)); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); }) }); - c.bench_function(&format!("aes256gcm encrypt"), move |b| { + c.bench_function("aes256gcm encrypt", move |b| { let key = AesKey::::random().unwrap(); let nonce = AesKey::::random_nonce(); let mut buffer = Vec::with_capacity(ALLOC_SIZE); b.iter(|| { buffer.clear(); - buffer.extend_from_slice(black_box(&message[..])); + buffer.extend_from_slice(black_box(message)); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); }) }); - c.bench_function(&format!("aes128cbc-hs256 encrypt"), move |b| { + c.bench_function("aes128cbc-hs256 encrypt", move |b| { let key = AesKey::::random().unwrap(); let nonce = AesKey::::random_nonce(); let mut buffer = Vec::with_capacity(ALLOC_SIZE); b.iter(|| { buffer.clear(); - buffer.extend_from_slice(black_box(&message[..])); + buffer.extend_from_slice(black_box(message)); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); }) }); - c.bench_function(&format!("aes256cbc-hs512 encrypt"), move |b| { + c.bench_function("aes256cbc-hs512 encrypt", move |b| { let key = AesKey::::random().unwrap(); let nonce = AesKey::::random_nonce(); let mut buffer = Vec::with_capacity(ALLOC_SIZE); b.iter(|| { buffer.clear(); - buffer.extend_from_slice(black_box(&message[..])); + buffer.extend_from_slice(black_box(message)); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); }) }); - c.bench_function(&format!("chacha20-poly1305 encrypt"), move |b| { + c.bench_function("chacha20-poly1305 encrypt", move |b| { let key = Chacha20Key::::random().unwrap(); let nonce = Chacha20Key::::random_nonce(); let mut buffer = Vec::with_capacity(ALLOC_SIZE); b.iter(|| { buffer.clear(); - buffer.extend_from_slice(black_box(&message[..])); + buffer.extend_from_slice(black_box(message)); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); }) }); - c.bench_function(&format!("xchacha20-poly1305 encrypt"), move |b| { + c.bench_function("xchacha20-poly1305 encrypt", move |b| { let key = Chacha20Key::::random().unwrap(); let nonce = Chacha20Key::::random_nonce(); let mut buffer = Vec::with_capacity(ALLOC_SIZE); b.iter(|| { buffer.clear(); - buffer.extend_from_slice(black_box(&message[..])); + buffer.extend_from_slice(black_box(message)); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); }) }); // test overhead of SecretBytes - c.bench_function(&format!("chacha20-poly1305 encrypt alloc"), move |b| { + c.bench_function("chacha20-poly1305 encrypt alloc", move |b| { let key = Chacha20Key::::random().unwrap(); let nonce = Chacha20Key::::random_nonce(); let mut buffer = SecretBytes::with_capacity(ALLOC_SIZE); b.iter(|| { buffer.clear(); - buffer.buffer_write(black_box(&message[..])).unwrap(); + buffer.buffer_write(black_box(message)).unwrap(); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); }) }); // test overhead of AnyKey - c.bench_function(&format!("chacha20-poly1305 encrypt as any"), move |b| { + c.bench_function("chacha20-poly1305 encrypt as any", move |b| { let key = Box::::random(KeyAlg::Chacha20(Chacha20Types::C20P)).unwrap(); let mut nonce = [0u8; 255]; let nonce_len = key.aead_params().nonce_length; @@ -110,7 +110,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut buffer = Vec::with_capacity(ALLOC_SIZE); b.iter(|| { buffer.clear(); - buffer.extend_from_slice(black_box(&message[..])); + buffer.extend_from_slice(black_box(message)); key.encrypt_in_place(&mut buffer, &nonce[..nonce_len], &[]) .unwrap(); }) diff --git a/askar-crypto/benches/kdf.rs b/askar-crypto/benches/kdf.rs index 861869f9..bf0dbd4f 100644 --- a/askar-crypto/benches/kdf.rs +++ b/askar-crypto/benches/kdf.rs @@ -18,7 +18,7 @@ fn criterion_benchmark(c: &mut Criterion) { prv_info: &[], }; - c.bench_function(&format!("concat kdf sha256"), move |b| { + c.bench_function("concat kdf sha256", move |b| { b.iter(|| { let mut output = [0u8; 32]; ConcatKDF::::derive_key(black_box(message), black_box(params), &mut output) diff --git a/askar-crypto/src/alg/aes/cbc_hmac.rs b/askar-crypto/src/alg/aes/cbc_hmac.rs index 81348e2f..27533296 100644 --- a/askar-crypto/src/alg/aes/cbc_hmac.rs +++ b/askar-crypto/src/alg/aes/cbc_hmac.rs @@ -4,10 +4,9 @@ use core::marker::PhantomData; use aead::generic_array::ArrayLength; use aes_core::{Aes128, Aes256}; -use block_modes::{ - block_padding::Pkcs7, - cipher::{BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher}, - BlockMode, Cbc, +use cbc::{Decryptor as CbcDec, Encryptor as CbcEnc}; +use cipher::{ + block_padding::Pkcs7, BlockCipher, BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit, }; use digest::{crypto_common::BlockSizeUser, Digest}; use hmac::{Mac, SimpleHmac}; @@ -60,7 +59,7 @@ where impl KeyAeadMeta for AesKey> where AesCbcHmac: AesType, - C: BlockCipher + NewBlockCipher, + C: BlockCipher + KeyInit, { type NonceSize = C::BlockSize; type TagSize = C::KeySize; @@ -69,7 +68,7 @@ where impl KeyAeadInPlace for AesKey> where AesCbcHmac: AesType, - C: BlockCipher + NewBlockCipher + BlockEncrypt + BlockDecrypt, + C: BlockCipher + KeyInit + BlockEncryptMut + BlockDecryptMut, D: Digest + BlockSizeUser, C::KeySize: core::ops::Shl, >::Output: ArrayLength, @@ -101,12 +100,12 @@ where let pad_len = AesCbcHmac::::padding_length(msg_len); buffer.buffer_extend(pad_len + TagSize::::USIZE)?; let enc_key = GenericArray::from_slice(&self.0[C::KeySize::USIZE..]); - Cbc::::new_fix(enc_key, GenericArray::from_slice(nonce)) - .encrypt(buffer.as_mut(), msg_len) + as KeyIvInit>::new(enc_key, GenericArray::from_slice(nonce)) + .encrypt_padded_mut::(buffer.as_mut(), msg_len) .map_err(|_| err_msg!(Encryption, "AES-CBC encryption error"))?; let ctext_end = msg_len + pad_len; - let mut hmac = SimpleHmac::::new_from_slice(&self.0[..C::KeySize::USIZE]) + let mut hmac = as Mac>::new_from_slice(&self.0[..C::KeySize::USIZE]) .expect("Incompatible HMAC key length"); hmac.update(aad); hmac.update(nonce.as_ref()); @@ -141,7 +140,7 @@ where let ctext_end = buf_len - TagSize::::USIZE; let tag = GenericArray::>::from_slice(&buffer.as_ref()[ctext_end..]); - let mut hmac = SimpleHmac::::new_from_slice(&self.0[..C::KeySize::USIZE]) + let mut hmac = as Mac>::new_from_slice(&self.0[..C::KeySize::USIZE]) .expect("Incompatible HMAC key length"); hmac.update(aad); hmac.update(nonce.as_ref()); @@ -151,8 +150,8 @@ where let tag_match = tag.as_ref().ct_eq(&mac[..TagSize::::USIZE]); let enc_key = GenericArray::from_slice(&self.0[C::KeySize::USIZE..]); - let dec_len = Cbc::::new_fix(enc_key, GenericArray::from_slice(nonce)) - .decrypt(&mut buffer.as_mut()[..ctext_end]) + let dec_len = as KeyIvInit>::new(enc_key, GenericArray::from_slice(nonce)) + .decrypt_padded_mut::(&mut buffer.as_mut()[..ctext_end]) .map_err(|_| err_msg!(Encryption, "AES-CBC decryption error"))? .len(); buffer.buffer_resize(dec_len)?; @@ -178,10 +177,12 @@ where #[cfg(test)] mod tests { + use base64::Engine; + use std::string::ToString; + use super::*; use crate::buffer::SecretBytes; use crate::repr::KeySecretBytes; - use std::string::ToString; #[test] fn encrypt_expected_cbc_128_hmac_256() { @@ -246,15 +247,17 @@ mod tests { \"apu\":\"QWxpY2U\",\"apv\":\"Qm9iIGFuZCBDaGFybGll\",\"epk\":{\ \"kty\":\"OKP\",\"crv\":\"X25519\",\ \"x\":\"k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc\"}}"; - let aad = base64::encode_config(protected, base64::URL_SAFE_NO_PAD); + let aad = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(protected); let input = b"Three is a magic number."; let key = AesKey::::from_secret_bytes(key_data).unwrap(); let mut buffer = SecretBytes::from_slice(input); let ct_len = key .encrypt_in_place(&mut buffer, &nonce[..], aad.as_bytes()) .unwrap(); - let ctext = base64::encode_config(&buffer.as_ref()[..ct_len], base64::URL_SAFE_NO_PAD); - let tag = base64::encode_config(&buffer.as_ref()[ct_len..], base64::URL_SAFE_NO_PAD); + let ctext = + base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&buffer.as_ref()[..ct_len]); + let tag = + base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&buffer.as_ref()[ct_len..]); assert_eq!(ctext, "Az2IWsISEMDJvyc5XRL-3-d-RgNBOGolCsxFFoUXFYw"); assert_eq!(tag, "HLb4fTlm8spGmij3RyOs2gJ4DpHM4hhVRwdF_hGb3WQ"); key.decrypt_in_place(&mut buffer, &nonce[..], aad.as_bytes()) diff --git a/askar-crypto/src/alg/aes/key_wrap.rs b/askar-crypto/src/alg/aes/key_wrap.rs index ee7d4e16..15580cfa 100644 --- a/askar-crypto/src/alg/aes/key_wrap.rs +++ b/askar-crypto/src/alg/aes/key_wrap.rs @@ -1,9 +1,11 @@ //! AES key wrap -use core::{convert::TryInto, marker::PhantomData}; +use core::marker::PhantomData; -use aes_core::{Aes128, Aes256}; -use block_modes::cipher::{BlockCipher, BlockDecrypt, BlockEncrypt, NewBlockCipher}; +use aes_core::{ + cipher::{BlockCipher, BlockDecrypt, BlockEncrypt, KeyInit, KeySizeUser}, + Aes128, Aes256, +}; use subtle::ConstantTimeEq; use super::{AesKey, AesType, NonceSize, TagSize}; @@ -24,7 +26,7 @@ const AES_KW_DEFAULT_IV: [u8; 8] = [166, 166, 166, 166, 166, 166, 166, 166]; pub type A128Kw = AesKeyWrap; impl AesType for A128Kw { - type KeySize = ::KeySize; + type KeySize = ::KeySize; const ALG_TYPE: AesTypes = AesTypes::A128Kw; const JWK_ALG: &'static str = "A128KW"; } @@ -33,7 +35,7 @@ impl AesType for A128Kw { pub type A256Kw = AesKeyWrap; impl AesType for A256Kw { - type KeySize = ::KeySize; + type KeySize = ::KeySize; const ALG_TYPE: AesTypes = AesTypes::A256Kw; const JWK_ALG: &'static str = "A256KW"; } @@ -53,7 +55,8 @@ where impl KeyAeadInPlace for AesKey> where AesKeyWrap: AesType, - C: NewBlockCipher as AesType>::KeySize> + C: KeyInit + + KeySizeUser as AesType>::KeySize> + BlockCipher + BlockDecrypt + BlockEncrypt, @@ -64,10 +67,10 @@ where nonce: &[u8], aad: &[u8], ) -> Result { - if nonce.len() != 0 { + if !nonce.is_empty() { return Err(err_msg!(Unsupported, "Custom nonce not supported")); } - if aad.len() != 0 { + if !aad.is_empty() { return Err(err_msg!(Unsupported, "AAD not supported")); } let mut buf_len = buffer.as_ref().len(); @@ -108,10 +111,10 @@ where nonce: &[u8], aad: &[u8], ) -> Result<(), Error> { - if nonce.len() != 0 { + if !nonce.is_empty() { return Err(err_msg!(Unsupported, "Custom nonce not supported")); } - if aad.len() != 0 { + if !aad.is_empty() { return Err(err_msg!(Unsupported, "AAD not supported")); } if buffer.as_ref().len() % 8 != 0 { @@ -131,7 +134,7 @@ where buffer.buffer_remove(0..8)?; let mut block = GenericArray::default(); - for j in (0..6).into_iter().rev() { + for j in (0..6).rev() { for (i, chunk) in buffer.as_mut().chunks_exact_mut(8).enumerate().rev() { block[0..8].copy_from_slice(iv.as_ref()); let t = (((blocks * j) + i + 1) as u64).to_be_bytes(); diff --git a/askar-crypto/src/alg/aes/mod.rs b/askar-crypto/src/alg/aes/mod.rs index 0d757de6..12013120 100644 --- a/askar-crypto/src/alg/aes/mod.rs +++ b/askar-crypto/src/alg/aes/mod.rs @@ -2,7 +2,7 @@ use core::fmt::{self, Debug, Formatter}; -use aead::{generic_array::ArrayLength, AeadCore, AeadInPlace, NewAead}; +use aead::{generic_array::ArrayLength, AeadCore, AeadInPlace, KeyInit, KeySizeUser}; use aes_gcm::{Aes128Gcm, Aes256Gcm}; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; @@ -26,7 +26,7 @@ mod key_wrap; pub use key_wrap::{A128Kw, A256Kw}; /// The 'kty' value of a symmetric key JWK -pub static JWK_KEY_TYPE: &'static str = "oct"; +pub static JWK_KEY_TYPE: &str = "oct"; /// Trait implemented by supported AES authenticated encryption algorithms pub trait AesType: 'static { @@ -157,7 +157,7 @@ where pub type A128Gcm = Aes128Gcm; impl AesType for A128Gcm { - type KeySize = ::KeySize; + type KeySize = ::KeySize; const ALG_TYPE: AesTypes = AesTypes::A128Gcm; const JWK_ALG: &'static str = "A128GCM"; @@ -167,7 +167,7 @@ impl AesType for A128Gcm { pub type A256Gcm = Aes256Gcm; impl AesType for A256Gcm { - type KeySize = ::KeySize; + type KeySize = ::KeySize; const ALG_TYPE: AesTypes = AesTypes::A256Gcm; const JWK_ALG: &'static str = "A256GCM"; @@ -182,7 +182,7 @@ impl KeyAeadMeta for AesKey { // generic implementation applying to AesGcm impl KeyAeadInPlace for AesKey where - T: NewAead + AeadInPlace + AesType::KeySize>, + T: KeyInit + AeadInPlace + AesType::KeySize>, { /// Encrypt a secret value in place, appending the verification tag fn encrypt_in_place( @@ -194,7 +194,7 @@ where if nonce.len() != T::NonceSize::USIZE { return Err(err_msg!(InvalidNonce)); } - let enc = ::new(self.0.as_ref()); + let enc = ::new(self.0.as_ref()); let tag = enc .encrypt_in_place_detached(GenericArray::from_slice(nonce), aad, buffer.as_mut()) .map_err(|_| err_msg!(Encryption, "AEAD encryption error"))?; @@ -220,7 +220,7 @@ where let tag_start = buf_len - T::TagSize::USIZE; let mut tag = GenericArray::default(); tag.clone_from_slice(&buffer.as_ref()[tag_start..]); - let enc = ::new(self.0.as_ref()); + let enc = ::new(self.0.as_ref()); enc.decrypt_in_place_detached( GenericArray::from_slice(nonce), aad, @@ -286,7 +286,8 @@ mod tests { let mut buffer = [0u8; 255]; buffer[0..message.len()].copy_from_slice(&message[..]); let mut writer = Writer::from_slice_position(&mut buffer, message.len()); - key.encrypt_in_place(&mut writer, &nonce, &[]).unwrap(); + key.encrypt_in_place(&mut writer, nonce.as_slice(), &[]) + .unwrap(); } #[test] diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index e54c85b8..93e53a5f 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -1,6 +1,4 @@ use alloc::{boxed::Box, sync::Arc}; -#[cfg(feature = "ed25519")] -use core::convert::TryFrom; use core::{ any::{Any, TypeId}, fmt::Debug, @@ -36,6 +34,9 @@ use super::k256::{self, K256KeyPair}; #[cfg(feature = "p256")] use super::p256::{self, P256KeyPair}; +#[cfg(feature = "p384")] +use super::p384::{self, P384KeyPair}; + use super::{HasKeyAlg, KeyAlg}; use crate::{ buffer::{ResizeBuffer, WriteBuffer}, @@ -48,7 +49,7 @@ use crate::{ sign::{KeySigVerify, KeySign, SignatureType}, }; -#[cfg(any(feature = "k256", feature = "p256"))] +#[cfg(any(feature = "k256", feature = "p256", feature = "p384"))] use super::EcCurves; #[cfg(any(feature = "aes", feature = "chacha"))] @@ -229,13 +230,13 @@ fn generate_any(alg: KeyAlg, rng: impl KeyMaterial) -> Result K256KeyPair::generate(rng).map(R::alloc_key), #[cfg(feature = "p256")] KeyAlg::EcCurve(EcCurves::Secp256r1) => P256KeyPair::generate(rng).map(R::alloc_key), + #[cfg(feature = "p384")] + KeyAlg::EcCurve(EcCurves::Secp384r1) => P384KeyPair::generate(rng).map(R::alloc_key), #[allow(unreachable_patterns)] - _ => { - return Err(err_msg!( - Unsupported, - "Unsupported algorithm for key generation" - )) - } + _ => Err(err_msg!( + Unsupported, + "Unsupported algorithm for key generation" + )), } } @@ -266,13 +267,15 @@ fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result { P256KeyPair::from_public_bytes(public).map(R::alloc_key) } - #[allow(unreachable_patterns)] - _ => { - return Err(err_msg!( - Unsupported, - "Unsupported algorithm for public key import" - )) + #[cfg(feature = "p384")] + KeyAlg::EcCurve(EcCurves::Secp384r1) => { + P384KeyPair::from_public_bytes(public).map(R::alloc_key) } + #[allow(unreachable_patterns)] + _ => Err(err_msg!( + Unsupported, + "Unsupported algorithm for public key import" + )), } } @@ -335,13 +338,15 @@ fn from_secret_bytes_any(alg: KeyAlg, secret: &[u8]) -> Result { P256KeyPair::from_secret_bytes(secret).map(R::alloc_key) } - #[allow(unreachable_patterns)] - _ => { - return Err(err_msg!( - Unsupported, - "Unsupported algorithm for secret key import" - )) + #[cfg(feature = "p384")] + KeyAlg::EcCurve(EcCurves::Secp384r1) => { + P384KeyPair::from_secret_bytes(secret).map(R::alloc_key) } + #[allow(unreachable_patterns)] + _ => Err(err_msg!( + Unsupported, + "Unsupported algorithm for secret key import" + )), } } @@ -387,12 +392,10 @@ where Chacha20Key::::from_key_exchange(secret, public).map(R::alloc_key) } #[allow(unreachable_patterns)] - _ => { - return Err(err_msg!( - Unsupported, - "Unsupported algorithm for key exchange" - )); - } + _ => Err(err_msg!( + Unsupported, + "Unsupported algorithm for key exchange" + )), } } @@ -449,12 +452,10 @@ fn from_key_derivation_any( Chacha20Key::::from_key_derivation(derive).map(R::alloc_key) } #[allow(unreachable_patterns)] - _ => { - return Err(err_msg!( - Unsupported, - "Unsupported algorithm for key derivation" - )); - } + _ => Err(err_msg!( + Unsupported, + "Unsupported algorithm for key derivation" + )), } } @@ -486,12 +487,10 @@ fn convert_key_any(key: &AnyKey, alg: KeyAlg) -> Result { ) .map(R::alloc_key)?), #[allow(unreachable_patterns)] - _ => { - return Err(err_msg!( - Unsupported, - "Unsupported key conversion operation" - )) - } + _ => Err(err_msg!( + Unsupported, + "Unsupported key conversion operation" + )), } } @@ -534,6 +533,8 @@ fn from_jwk_any(jwk: JwkParts<'_>) -> Result { ("EC", c) if c == k256::JWK_CURVE => K256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), #[cfg(feature = "p256")] ("EC", c) if c == p256::JWK_CURVE => P256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), + #[cfg(feature = "p384")] + ("EC", c) if c == p384::JWK_CURVE => P384KeyPair::from_jwk_parts(jwk).map(R::alloc_key), // FIXME implement symmetric keys? _ => Err(err_msg!(Unsupported, "Unsupported JWK for key import")), } @@ -631,6 +632,13 @@ macro_rules! match_key_alg { } match_key_alg!(@ $($rest)*; $key, $alg) }}; + (@ P384 $($rest:ident)*; $key:ident, $alg:ident) => {{ + #[cfg(feature = "p384")] + if $alg == KeyAlg::EcCurve(EcCurves::Secp384r1) { + return Ok($key.assume::()) + } + match_key_alg!(@ $($rest)*; $key, $alg) + }}; } impl AnyKey { @@ -654,6 +662,7 @@ impl AnyKey { Ed25519, K256, P256, + P384, X25519, "Secret key export is not supported for this key type" } @@ -667,6 +676,7 @@ impl AnyKey { Ed25519, K256, P256, + P384, X25519, "Public key export is not supported for this key type" } @@ -711,10 +721,14 @@ impl KeyExchange for AnyKey { KeyAlg::EcCurve(EcCurves::Secp256r1) => Ok(self .assume::() .write_key_exchange(other.assume::(), out)?), + #[cfg(feature = "p384")] + KeyAlg::EcCurve(EcCurves::Secp384r1) => Ok(self + .assume::() + .write_key_exchange(other.assume::(), out)?), #[allow(unreachable_patterns)] _ => { let _ = out; - return Err(err_msg!(Unsupported, "Unsupported key exchange")); + Err(err_msg!(Unsupported, "Unsupported key exchange")) } } } @@ -767,6 +781,7 @@ impl ToJwk for AnyKey { Ed25519, K256, P256, + P384, X25519, "JWK export is not supported for this key type" }?; @@ -787,6 +802,7 @@ impl KeySign for AnyKey { Ed25519, K256, P256, + P384, "Signing is not supported for this key type" }?; key.write_signature(message, sig_type, out) @@ -806,6 +822,7 @@ impl KeySigVerify for AnyKey { Ed25519, K256, P256, + P384, "Signature verification is not supported for this key type" }?; key.verify_signature(message, signature, sig_type) diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs index df785859..97d07ff9 100644 --- a/askar-crypto/src/alg/bls.rs +++ b/askar-crypto/src/alg/bls.rs @@ -1,7 +1,6 @@ //! BLS12-381 key support use core::{ - convert::TryInto, fmt::{self, Debug, Formatter}, ops::Add, }; @@ -29,7 +28,7 @@ use crate::{ }; /// The 'kty' value of a BLS key JWK -pub const JWK_KEY_TYPE: &'static str = "OKP"; +pub const JWK_KEY_TYPE: &str = "OKP"; /// A BLS12-381 key pair #[derive(Clone, Zeroize)] @@ -187,24 +186,21 @@ impl FromJwk for BlsKeyPair { ArrayKey::::temp(|pk_arr| { if jwk.x.decode_base64(pk_arr)? != pk_arr.len() { Err(err_msg!(InvalidKeyData)) + } else if jwk.d.is_some() { + ArrayKey::::temp(|sk_arr| { + if jwk.d.decode_base64(sk_arr)? != sk_arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + let result = BlsKeyPair::from_secret_key(BlsSecretKey::from_bytes(sk_arr)?); + result.check_public_bytes(pk_arr)?; + Ok(result) + } + }) } else { - if jwk.d.is_some() { - ArrayKey::::temp(|sk_arr| { - if jwk.d.decode_base64(sk_arr)? != sk_arr.len() { - Err(err_msg!(InvalidKeyData)) - } else { - let result = - BlsKeyPair::from_secret_key(BlsSecretKey::from_bytes(sk_arr)?); - result.check_public_bytes(pk_arr)?; - Ok(result) - } - }) - } else { - Ok(Self { - secret: None, - public: Pk::from_public_bytes(pk_arr)?, - }) - } + Ok(Self { + secret: None, + public: Pk::from_public_bytes(pk_arr)?, + }) } }) } @@ -430,7 +426,7 @@ impl From<&BlsKeyPair> for BlsKeyPair { fn from(kp: &BlsKeyPair) -> Self { BlsKeyPair { secret: kp.secret.clone(), - public: kp.public.0.clone(), + public: kp.public.0, } } } @@ -439,7 +435,7 @@ impl From<&BlsKeyPair> for BlsKeyPair { fn from(kp: &BlsKeyPair) -> Self { BlsKeyPair { secret: kp.secret.clone(), - public: kp.public.1.clone(), + public: kp.public.1, } } } @@ -450,9 +446,11 @@ pub struct G1G2Pair(G1Affine, G2Affine); #[cfg(test)] mod tests { + use base64::Engine; + use std::string::ToString; + use super::*; use crate::repr::{ToPublicBytes, ToSecretBytes}; - use std::string::ToString; // test against EIP-2333 (as updated for signatures draft 4) #[test] @@ -531,12 +529,14 @@ mod tests { let kp = BlsKeyPair::::from_secret_bytes(&test_pvt[..]).expect("Error creating key"); let jwk = kp.to_jwk_public(None).expect("Error converting key to JWK"); - let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK"); + let jwk = JwkParts::try_from_str(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, JWK_KEY_TYPE); assert_eq!(jwk.crv, G1::JWK_CURVE); assert_eq!( jwk.x, - base64::encode_config(test_pub_g1, base64::URL_SAFE_NO_PAD).as_str() + base64::engine::general_purpose::URL_SAFE_NO_PAD + .encode(test_pub_g1) + .as_str() ); assert_eq!(jwk.d, None); let pk_load = BlsKeyPair::::from_jwk_parts(jwk).unwrap(); @@ -548,11 +548,15 @@ mod tests { assert_eq!(jwk.crv, G1::JWK_CURVE); assert_eq!( jwk.x, - base64::encode_config(test_pub_g1, base64::URL_SAFE_NO_PAD).as_str() + base64::engine::general_purpose::URL_SAFE_NO_PAD + .encode(test_pub_g1) + .as_str() ); assert_eq!( jwk.d, - base64::encode_config(test_pvt, base64::URL_SAFE_NO_PAD).as_str() + base64::engine::general_purpose::URL_SAFE_NO_PAD + .encode(test_pvt) + .as_str() ); let _sk_load = BlsKeyPair::::from_jwk_parts(jwk).unwrap(); // assert_eq!( diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 621cd612..e61d4164 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -2,7 +2,7 @@ use core::fmt::{self, Debug, Formatter}; -use aead::{AeadCore, AeadInPlace, NewAead}; +use aead::{AeadCore, AeadInPlace, KeyInit, KeySizeUser}; use chacha20poly1305::{ChaCha20Poly1305, XChaCha20Poly1305}; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; @@ -20,12 +20,12 @@ use crate::{ }; /// The 'kty' value of a symmetric key JWK -pub static JWK_KEY_TYPE: &'static str = "oct"; +pub static JWK_KEY_TYPE: &str = "oct"; /// Trait implemented by supported ChaCha20 algorithms pub trait Chacha20Type: 'static { /// The AEAD implementation - type Aead: NewAead + AeadCore + AeadInPlace; + type Aead: KeyInit + AeadCore + AeadInPlace; /// The associated algorithm type const ALG_TYPE: Chacha20Types; @@ -55,7 +55,7 @@ impl Chacha20Type for XC20P { const JWK_ALG: &'static str = "XC20P"; } -type KeyType = ArrayKey<<::Aead as NewAead>::KeySize>; +type KeyType = ArrayKey<<::Aead as KeySizeUser>::KeySize>; type NonceSize = <::Aead as AeadCore>::NonceSize; @@ -112,7 +112,7 @@ impl HasKeyAlg for Chacha20Key { } impl KeyMeta for Chacha20Key { - type KeySize = ::KeySize; + type KeySize = ::KeySize; } impl KeyGen for Chacha20Key { diff --git a/askar-crypto/src/alg/ec_common.rs b/askar-crypto/src/alg/ec_common.rs index d35eea2b..91246778 100644 --- a/askar-crypto/src/alg/ec_common.rs +++ b/askar-crypto/src/alg/ec_common.rs @@ -4,14 +4,14 @@ use elliptic_curve::{ }; pub fn write_sk(sk: &SecretKey, out: &mut [u8]) { - let limbs = sk.as_scalar_core().as_limbs(); - debug_assert_eq!(out.len(), Limb::BYTE_SIZE * limbs.len()); + let limbs = sk.as_scalar_primitive().as_limbs(); + debug_assert_eq!(out.len(), Limb::BYTES * limbs.len()); for (src, dst) in limbs .iter() .rev() .cloned() - .zip(out.chunks_exact_mut(Limb::BYTE_SIZE)) + .zip(out.chunks_exact_mut(Limb::BYTES)) { dst.copy_from_slice(&src.to_be_bytes()); } diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index c702314f..4183fac6 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -33,9 +33,9 @@ pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; /// The 'kty' value of an Ed25519 JWK -pub static JWK_KEY_TYPE: &'static str = "OKP"; +pub static JWK_KEY_TYPE: &str = "OKP"; /// The 'crv' value of an Ed25519 JWK -pub static JWK_CURVE: &'static str = "Ed25519"; +pub static JWK_CURVE: &str = "Ed25519"; /// An Ed25519 public key or keypair pub struct Ed25519KeyPair { @@ -111,7 +111,7 @@ impl Clone for Ed25519KeyPair { .secret .as_ref() .map(|sk| SecretKey::from_bytes(&sk.as_bytes()[..]).unwrap()), - public: self.public.clone(), + public: self.public, } } } @@ -278,20 +278,18 @@ impl FromJwk for Ed25519KeyPair { ArrayKey::::temp(|pk_arr| { if jwk.x.decode_base64(pk_arr)? != pk_arr.len() { Err(err_msg!(InvalidKeyData)) + } else if jwk.d.is_some() { + ArrayKey::::temp(|sk_arr| { + if jwk.d.decode_base64(sk_arr)? != sk_arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + let kp = Ed25519KeyPair::from_secret_bytes(sk_arr)?; + kp.check_public_bytes(pk_arr)?; + Ok(kp) + } + }) } else { - if jwk.d.is_some() { - ArrayKey::::temp(|sk_arr| { - if jwk.d.decode_base64(sk_arr)? != sk_arr.len() { - Err(err_msg!(InvalidKeyData)) - } else { - let kp = Ed25519KeyPair::from_secret_bytes(sk_arr)?; - kp.check_public_bytes(pk_arr)?; - Ok(kp) - } - }) - } else { - Ed25519KeyPair::from_public_bytes(pk_arr) - } + Ed25519KeyPair::from_public_bytes(pk_arr) } }) } @@ -304,7 +302,7 @@ pub struct Ed25519SigningKey<'p>(ExpandedSecretKey, &'p PublicKey); impl Ed25519SigningKey<'_> { /// Sign a message with the secret key pub fn sign(&self, message: &[u8]) -> [u8; EDDSA_SIGNATURE_LENGTH] { - self.0.sign(message, &self.1).to_bytes() + self.0.sign(message, self.1).to_bytes() } } @@ -319,6 +317,8 @@ impl Debug for Ed25519SigningKey<'_> { #[cfg(test)] mod tests { + use base64::Engine; + use super::*; use crate::repr::{ToPublicBytes, ToSecretBytes}; @@ -363,12 +363,14 @@ mod tests { // } let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"; let test_pub_b64 = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"; - let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); + let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(test_pvt_b64) + .unwrap(); let kp = Ed25519KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = kp .to_jwk_public(None) .expect("Error converting public key to JWK"); - let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK output"); + let jwk = JwkParts::try_from_str(&jwk).expect("Error parsing JWK output"); assert_eq!(jwk.kty, JWK_KEY_TYPE); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); @@ -404,9 +406,9 @@ mod tests { let kp = Ed25519KeyPair::from_keypair_bytes(test_keypair).unwrap(); let sig = &kp.sign(test_msg).unwrap(); assert_eq!(sig, test_sig); - assert_eq!(kp.verify_signature(test_msg, &sig[..]), true); - assert_eq!(kp.verify_signature(b"Not the message", &sig[..]), false); - assert_eq!(kp.verify_signature(test_msg, &[0u8; 64]), false); + assert!(kp.verify_signature(test_msg, &sig[..])); + assert!(!kp.verify_signature(b"Not the message", &sig[..])); + assert!(!kp.verify_signature(test_msg, &[0u8; 64])); } #[test] diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index ce0e6ab3..e90a3e9f 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -1,7 +1,5 @@ //! Elliptic curve ECDH and ECDSA support on curve secp256k1 -use core::convert::{TryFrom, TryInto}; - use k256::{ ecdsa::{ signature::{Signer, Verifier}, @@ -47,11 +45,11 @@ pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; /// The 'kty' value of an elliptic curve key JWK -pub static JWK_KEY_TYPE: &'static str = "EC"; +pub static JWK_KEY_TYPE: &str = "EC"; /// The 'crv' value of a K-256 key JWK -pub static JWK_CURVE: &'static str = "secp256k1"; +pub static JWK_CURVE: &str = "secp256k1"; -type FieldSize = elliptic_curve::FieldSize; +type FieldSize = elliptic_curve::FieldBytesSize; /// A K-256 (secp256k1) public key or keypair #[derive(Clone, Debug)] @@ -87,7 +85,7 @@ impl K256KeyPair { pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256K_SIGNATURE_LENGTH]> { if let Some(skey) = self.to_signing_key() { let sig: Signature = skey.sign(message); - let sigb: [u8; 64] = sig.as_ref().try_into().unwrap(); + let sigb: [u8; 64] = sig.to_bytes().try_into().unwrap(); Some(sigb) } else { None @@ -97,7 +95,7 @@ impl K256KeyPair { /// Verify a signature with the public key pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { if let Ok(sig) = Signature::try_from(signature) { - let vk = VerifyingKey::from(self.public.as_affine()); + let vk = VerifyingKey::from(&self.public); vk.verify(message, &sig).is_ok() } else { false @@ -119,7 +117,7 @@ impl KeyGen for K256KeyPair { fn generate(mut rng: impl KeyMaterial) -> Result { ArrayKey::::temp(|buf| loop { rng.read_okm(buf); - if let Ok(key) = SecretKey::from_be_bytes(&buf) { + if let Ok(key) = SecretKey::from_bytes(buf) { return Ok(Self::from_secret_key(key)); } }) @@ -128,16 +126,19 @@ impl KeyGen for K256KeyPair { impl KeySecretBytes for K256KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { - Ok(Self::from_secret_key( - SecretKey::from_be_bytes(key).map_err(|_| err_msg!(InvalidKeyData))?, - )) + if let Ok(key) = key.try_into() { + if let Ok(sk) = SecretKey::from_bytes(key) { + return Ok(Self::from_secret_key(sk)); + } + } + Err(err_msg!(InvalidKeyData)) } fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { if let Some(sk) = self.secret.as_ref() { ArrayKey::::temp(|arr| { ec_common::write_sk(sk, &mut arr[..]); - f(Some(&arr)) + f(Some(arr)) }) } else { f(None) @@ -310,7 +311,7 @@ impl KeyExchange for K256KeyPair { match self.secret.as_ref() { Some(sk) => { let xk = diffie_hellman(sk.to_nonzero_scalar(), other.public.as_affine()); - out.buffer_write(xk.as_bytes())?; + out.buffer_write(xk.raw_secret_bytes().as_ref())?; Ok(()) } None => Err(err_msg!(MissingSecretKey)), @@ -320,6 +321,8 @@ impl KeyExchange for K256KeyPair { #[cfg(test)] mod tests { + use base64::Engine; + use super::*; use crate::repr::ToPublicBytes; @@ -339,11 +342,13 @@ mod tests { "dWCvM4fTdeM0KmloF57zxtBPXTOythHPMm1HCLrdd3A", "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA", ); - let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); + let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(test_pvt_b64) + .unwrap(); let sk = K256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = sk.to_jwk_public(None).expect("Error converting key to JWK"); - let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK"); + let jwk = JwkParts::try_from_str(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, JWK_KEY_TYPE); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64.0); @@ -373,17 +378,15 @@ mod tests { "a2a3affbe18cda8c5a7b6375f05b304c2303ab8beb21428709a43a519f8f946f 6ffa7966afdb337e9b1f70bb575282e71d4fe5bbe6bfa97b229d6bd7e97df1e5" ); - let test_pvt = base64::decode_config( - "jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs", - base64::URL_SAFE_NO_PAD, - ) - .unwrap(); + let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode("jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs") + .unwrap(); let kp = K256KeyPair::from_secret_bytes(&test_pvt).unwrap(); let sig = kp.sign(&test_msg[..]).unwrap(); assert_eq!(sig, &test_sig[..]); - assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); - assert_eq!(kp.verify_signature(b"Not the message", &sig[..]), false); - assert_eq!(kp.verify_signature(&test_msg[..], &[0u8; 64]), false); + assert!(kp.verify_signature(&test_msg[..], &sig[..])); + assert!(!kp.verify_signature(b"Not the message", &sig[..])); + assert!(!kp.verify_signature(&test_msg[..], &[0u8; 64])); } #[test] diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 5bc921b2..be92488b 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -50,6 +50,10 @@ pub mod k256; #[cfg_attr(docsrs, doc(cfg(feature = "p256")))] pub mod p256; +#[cfg(feature = "p384")] +#[cfg_attr(docsrs, doc(cfg(feature = "p384")))] +pub mod p384; + /// Supported key algorithms #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] @@ -60,9 +64,9 @@ pub enum KeyAlg { Bls12_381(BlsCurves), /// (X)ChaCha20-Poly1305 Chacha20(Chacha20Types), - /// Curve25519 signing key + /// Ed25519 signing key Ed25519, - /// Curve25519 diffie-hellman key exchange key + /// Curve25519 elliptic curve key exchange key X25519, /// Elliptic Curve key for signing or key exchange EcCurve(EcCurves), @@ -87,6 +91,7 @@ impl KeyAlg { Self::X25519 => "x25519", Self::EcCurve(EcCurves::Secp256k1) => "k256", Self::EcCurve(EcCurves::Secp256r1) => "p256", + Self::EcCurve(EcCurves::Secp384r1) => "p384", } } } @@ -123,6 +128,7 @@ impl FromStr for KeyAlg { a if a == "x25519" => Ok(Self::X25519), a if a == "k256" || a == "secp256k1" => Ok(Self::EcCurve(EcCurves::Secp256k1)), a if a == "p256" || a == "secp256r1" => Ok(Self::EcCurve(EcCurves::Secp256r1)), + a if a == "p384" || a == "secp384r1" => Ok(Self::EcCurve(EcCurves::Secp384r1)), _ => Err(err_msg!(Unsupported, "Unknown key algorithm")), } } @@ -184,6 +190,7 @@ impl<'a> NormalizedIter<'a> { impl Iterator for NormalizedIter<'_> { type Item = char; fn next(&mut self) -> Option { + #[allow(clippy::while_let_on_iterator)] while let Some(c) = self.chars.next() { if c != '-' && c != '_' && c != ' ' { return Some(c.to_ascii_lowercase()); @@ -247,6 +254,8 @@ pub enum EcCurves { Secp256r1, /// Koblitz 256 curve Secp256k1, + /// NIST P-384 curve + Secp384r1, } /// A trait for accessing the algorithm of a key, used when @@ -262,10 +271,10 @@ mod tests { #[test] fn cmp_normalize() { - assert_eq!(normalize_alg("Test").unwrap() == "test", true); - assert_eq!(normalize_alg("t-e-s-t").unwrap() == "test", true); - assert_eq!(normalize_alg("--TE__ST--").unwrap() == "test", true); - assert_eq!(normalize_alg("t-e-s-t").unwrap() == "tes", false); - assert_eq!(normalize_alg("t-e-s-t").unwrap() == "testt", false); + assert!(normalize_alg("Test").unwrap() == "test"); + assert!(normalize_alg("t-e-s-t").unwrap() == "test"); + assert!(normalize_alg("--TE__ST--").unwrap() == "test"); + assert!(normalize_alg("t-e-s-t").unwrap() != "tes"); + assert!(normalize_alg("t-e-s-t").unwrap() != "testt"); } } diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index daa6b743..c74ebe85 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -1,6 +1,6 @@ //! Elliptic curve ECDH and ECDSA support on curve secp256r1 -use core::convert::{TryFrom, TryInto}; +use core::convert::TryFrom; use p256::{ ecdsa::{ @@ -47,11 +47,11 @@ pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; /// The 'kty' value of an elliptic curve key JWK -pub static JWK_KEY_TYPE: &'static str = "EC"; +pub static JWK_KEY_TYPE: &str = "EC"; /// The 'crv' value of a P-256 key JWK -pub static JWK_CURVE: &'static str = "P-256"; +pub static JWK_CURVE: &str = "P-256"; -type FieldSize = elliptic_curve::FieldSize; +type FieldSize = elliptic_curve::FieldBytesSize; /// A P-256 (secp256r1) public key or keypair #[derive(Clone, Debug)] @@ -87,7 +87,7 @@ impl P256KeyPair { pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256_SIGNATURE_LENGTH]> { if let Some(skey) = self.to_signing_key() { let sig: Signature = skey.sign(message); - let sigb: [u8; 64] = sig.as_ref().try_into().unwrap(); + let sigb: [u8; 64] = sig.to_bytes().try_into().unwrap(); Some(sigb) } else { None @@ -119,7 +119,7 @@ impl KeyGen for P256KeyPair { fn generate(mut rng: impl KeyMaterial) -> Result { ArrayKey::::temp(|buf| loop { rng.read_okm(buf); - if let Ok(key) = SecretKey::from_be_bytes(&buf) { + if let Ok(key) = SecretKey::from_bytes(buf) { return Ok(Self::from_secret_key(key)); } }) @@ -128,16 +128,19 @@ impl KeyGen for P256KeyPair { impl KeySecretBytes for P256KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { - Ok(Self::from_secret_key( - SecretKey::from_be_bytes(key).map_err(|_| err_msg!(InvalidKeyData))?, - )) + if let Ok(key) = key.try_into() { + if let Ok(sk) = SecretKey::from_bytes(key) { + return Ok(Self::from_secret_key(sk)); + } + } + Err(err_msg!(InvalidKeyData)) } fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { if let Some(sk) = self.secret.as_ref() { ArrayKey::::temp(|arr| { ec_common::write_sk(sk, &mut arr[..]); - f(Some(&arr)) + f(Some(arr)) }) } else { f(None) @@ -310,7 +313,7 @@ impl KeyExchange for P256KeyPair { match self.secret.as_ref() { Some(sk) => { let xk = diffie_hellman(sk.to_nonzero_scalar(), other.public.as_affine()); - out.buffer_write(xk.as_bytes())?; + out.buffer_write(xk.raw_secret_bytes().as_ref())?; Ok(()) } None => Err(err_msg!(MissingSecretKey)), @@ -320,6 +323,8 @@ impl KeyExchange for P256KeyPair { #[cfg(test)] mod tests { + use base64::Engine; + use super::*; use crate::repr::ToPublicBytes; @@ -337,11 +342,13 @@ mod tests { "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", ); - let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); + let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(test_pvt_b64) + .unwrap(); let sk = P256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = sk.to_jwk_public(None).expect("Error converting key to JWK"); - let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK"); + let jwk = JwkParts::try_from_str(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, JWK_KEY_TYPE); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64.0); @@ -388,17 +395,15 @@ mod tests { "241f765f19d4e6148452f2249d2fa69882244a6ad6e70aadb8848a6409d20712 4e85faf9587100247de7bdace13a3073b47ec8a531ca91c1375b2b6134344413" ); - let test_pvt = base64::decode_config( - "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI", - base64::URL_SAFE_NO_PAD, - ) - .unwrap(); + let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode("jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI") + .unwrap(); let kp = P256KeyPair::from_secret_bytes(&test_pvt).unwrap(); let sig = kp.sign(&test_msg[..]).unwrap(); assert_eq!(sig, &test_sig[..]); - assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); - assert_eq!(kp.verify_signature(b"Not the message", &sig[..]), false); - assert_eq!(kp.verify_signature(&test_msg[..], &[0u8; 64]), false); + assert!(kp.verify_signature(&test_msg[..], &sig[..])); + assert!(!kp.verify_signature(b"Not the message", &sig[..])); + assert!(!kp.verify_signature(&test_msg[..], &[0u8; 64])); } #[test] diff --git a/askar-crypto/src/alg/p384.rs b/askar-crypto/src/alg/p384.rs new file mode 100644 index 00000000..8a9fca02 --- /dev/null +++ b/askar-crypto/src/alg/p384.rs @@ -0,0 +1,433 @@ +//! Elliptic curve ECDH and ECDSA support on curve secp384r1 + +use core::convert::{TryFrom, TryInto}; + +use p384::{ + ecdsa::{ + signature::{Signer, Verifier}, + Signature, SigningKey, VerifyingKey, + }, + elliptic_curve::{ + self, + ecdh::diffie_hellman, + sec1::{Coordinates, FromEncodedPoint, ToEncodedPoint}, + }, + EncodedPoint, PublicKey, SecretKey, +}; +use subtle::ConstantTimeEq; + +use super::{ec_common, EcCurves, HasKeyAlg, KeyAlg}; +use crate::{ + buffer::{ArrayKey, WriteBuffer}, + error::Error, + generic_array::typenum::{U48, U49, U97}, + jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, + kdf::KeyExchange, + random::KeyMaterial, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}, + sign::{KeySigVerify, KeySign, SignatureType}, +}; + +// SECURITY: PublicKey contains a p384::AffinePoint, which is always checked +// to be on the curve when loaded. +// The identity point is rejected when converting into a p384::PublicKey. +// This satisfies 5.6.2.3.4 ECC Partial Public-Key Validation Routine from +// NIST SP 800-56A: _Recommendation for Pair-Wise Key-Establishment Schemes +// Using Discrete Logarithm Cryptography_. + +/// The length of an ES384 signature +pub const ES384_SIGNATURE_LENGTH: usize = 96; + +/// The length of a compressed public key in bytes +pub const PUBLIC_KEY_LENGTH: usize = 49; +/// The length of a secret key +pub const SECRET_KEY_LENGTH: usize = 48; +/// The length of a keypair in bytes +pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; + +/// The 'kty' value of an elliptic curve key JWK +pub static JWK_KEY_TYPE: &str = "EC"; +/// The 'crv' value of a P-384 key JWK +pub static JWK_CURVE: &str = "P-384"; + +type FieldSize = elliptic_curve::FieldBytesSize; + +/// A P-384 (secp384r1) public key or keypair +#[derive(Clone, Debug)] +pub struct P384KeyPair { + // SECURITY: SecretKey zeroizes on drop + secret: Option, + public: PublicKey, +} + +impl P384KeyPair { + #[inline] + pub(crate) fn from_secret_key(sk: SecretKey) -> Self { + let pk = sk.public_key(); + Self { + secret: Some(sk), + public: pk, + } + } + + pub(crate) fn check_public_bytes(&self, pk: &[u8]) -> Result<(), Error> { + if self.with_public_bytes(|slf| slf.ct_eq(pk)).into() { + Ok(()) + } else { + Err(err_msg!(InvalidKeyData, "invalid p384 keypair")) + } + } + + pub(crate) fn to_signing_key(&self) -> Option { + self.secret.clone().map(SigningKey::from) + } + + /// Sign a message with the secret key + pub fn sign(&self, message: &[u8]) -> Option<[u8; ES384_SIGNATURE_LENGTH]> { + if let Some(skey) = self.to_signing_key() { + let sig: Signature = skey.sign(message); + let mut sigb = [0u8; 96]; + sigb.copy_from_slice(&sig.to_bytes()); + Some(sigb) + } else { + None + } + } + + /// Verify a signature with the public key + pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { + if let Ok(sig) = Signature::try_from(signature) { + let vk = VerifyingKey::from(&self.public); + vk.verify(message, &sig).is_ok() + } else { + false + } + } +} + +impl HasKeyAlg for P384KeyPair { + fn algorithm(&self) -> KeyAlg { + KeyAlg::EcCurve(EcCurves::Secp384r1) + } +} + +impl KeyMeta for P384KeyPair { + type KeySize = U48; +} + +impl KeyGen for P384KeyPair { + fn generate(mut rng: impl KeyMaterial) -> Result { + ArrayKey::::temp(|buf| loop { + rng.read_okm(buf); + if let Ok(key) = SecretKey::from_bytes(buf) { + return Ok(Self::from_secret_key(key)); + } + }) + } +} + +impl KeySecretBytes for P384KeyPair { + fn from_secret_bytes(key: &[u8]) -> Result { + if let Ok(key) = key.try_into() { + if let Ok(sk) = SecretKey::from_bytes(key) { + return Ok(Self::from_secret_key(sk)); + } + } + Err(err_msg!(InvalidKeyData)) + } + + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + if let Some(sk) = self.secret.as_ref() { + ArrayKey::::temp(|arr| { + ec_common::write_sk(sk, &mut arr[..]); + f(Some(arr)) + }) + } else { + f(None) + } + } +} + +impl KeypairMeta for P384KeyPair { + type PublicKeySize = U49; + type KeypairSize = U97; +} + +impl KeypairBytes for P384KeyPair { + fn from_keypair_bytes(kp: &[u8]) -> Result { + if kp.len() != KEYPAIR_LENGTH { + return Err(err_msg!(InvalidKeyData)); + } + let result = P384KeyPair::from_secret_bytes(&kp[..SECRET_KEY_LENGTH]) + .map_err(|_| err_msg!(InvalidKeyData))?; + result.check_public_bytes(&kp[SECRET_KEY_LENGTH..])?; + Ok(result) + } + + fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + if let Some(sk) = self.secret.as_ref() { + ArrayKey::<::KeypairSize>::temp(|arr| { + ec_common::write_sk(sk, &mut arr[..SECRET_KEY_LENGTH]); + let pk_enc = self.public.to_encoded_point(true); + arr[SECRET_KEY_LENGTH..].copy_from_slice(pk_enc.as_bytes()); + f(Some(&*arr)) + }) + } else { + f(None) + } + } +} + +impl KeyPublicBytes for P384KeyPair { + fn from_public_bytes(key: &[u8]) -> Result { + let pk = PublicKey::from_sec1_bytes(key).map_err(|_| err_msg!(InvalidKeyData))?; + Ok(Self { + secret: None, + public: pk, + }) + } + + fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O { + f(self.public.to_encoded_point(true).as_bytes()) + } +} + +impl KeySign for P384KeyPair { + fn write_signature( + &self, + message: &[u8], + sig_type: Option, + out: &mut dyn WriteBuffer, + ) -> Result<(), Error> { + match sig_type { + None | Some(SignatureType::ES384) => { + if let Some(sig) = self.sign(message) { + out.buffer_write(&sig[..])?; + Ok(()) + } else { + Err(err_msg!(Unsupported, "Undefined secret key")) + } + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl KeySigVerify for P384KeyPair { + fn verify_signature( + &self, + message: &[u8], + signature: &[u8], + sig_type: Option, + ) -> Result { + match sig_type { + None | Some(SignatureType::ES256) => Ok(self.verify_signature(message, signature)), + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl ToJwk for P384KeyPair { + fn encode_jwk(&self, enc: &mut dyn JwkEncoder) -> Result<(), Error> { + let pk_enc = self.public.to_encoded_point(false); + let (x, y) = match pk_enc.coordinates() { + Coordinates::Identity => { + return Err(err_msg!( + Unsupported, + "Cannot convert identity point to JWK" + )) + } + Coordinates::Uncompressed { x, y } => (x, y), + Coordinates::Compressed { .. } | Coordinates::Compact { .. } => unreachable!(), + }; + + enc.add_str("crv", JWK_CURVE)?; + enc.add_str("kty", JWK_KEY_TYPE)?; + enc.add_as_base64("x", &x[..])?; + enc.add_as_base64("y", &y[..])?; + if enc.is_secret() { + self.with_secret_bytes(|buf| { + if let Some(sk) = buf { + enc.add_as_base64("d", sk) + } else { + Ok(()) + } + })?; + } + Ok(()) + } +} + +impl FromJwk for P384KeyPair { + fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { + if jwk.kty != JWK_KEY_TYPE { + return Err(err_msg!(InvalidKeyData, "Unsupported key type")); + } + if jwk.crv != JWK_CURVE { + return Err(err_msg!(InvalidKeyData, "Unsupported key algorithm")); + } + let pk_x = ArrayKey::::try_new_with(|arr| { + if jwk.x.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + Ok(()) + } + })?; + let pk_y = ArrayKey::::try_new_with(|arr| { + if jwk.y.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + Ok(()) + } + })?; + let pk = Option::from(PublicKey::from_encoded_point( + &EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false), + )) + .ok_or_else(|| err_msg!(InvalidKeyData))?; + if jwk.d.is_some() { + ArrayKey::::temp(|arr| { + if jwk.d.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + let kp = P384KeyPair::from_secret_bytes(arr)?; + if kp.public != pk { + Err(err_msg!(InvalidKeyData)) + } else { + Ok(kp) + } + } + }) + } else { + Ok(Self { + secret: None, + public: pk, + }) + } + } +} + +impl KeyExchange for P384KeyPair { + fn write_key_exchange(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { + match self.secret.as_ref() { + Some(sk) => { + let xk = diffie_hellman(sk.to_nonzero_scalar(), other.public.as_affine()); + out.buffer_write(xk.raw_secret_bytes().as_ref())?; + Ok(()) + } + None => Err(err_msg!(MissingSecretKey)), + } + } +} + +#[cfg(test)] +mod tests { + use base64::Engine; + + use super::*; + use crate::repr::ToPublicBytes; + + #[test] + fn jwk_expected() { + // { + // "kty": "EC", + // "x": "p3ZI8DAmxn8BJ3936Y5MHRLXTAg6SxCNhuH6JBEuieuicUY9wqZk8C63SZIj4htA", + // "y": "eqSjvs1X7eI9V2o8sYUpsrj6WUKOymqFtkCxMwWQuDPtZKOHC3fSWkjQvf_73GH-", + // "crv": "P-384", + // "d": "rgFYq-b_toGb-wN3URCk_e-6Sj2PtUvoefF284q9oKnVCi7sglAmCZkOv-2nOAeE" + // } + let test_pvt_b64 = "rgFYq-b_toGb-wN3URCk_e-6Sj2PtUvoefF284q9oKnVCi7sglAmCZkOv-2nOAeE"; + let test_pub_b64 = ( + "p3ZI8DAmxn8BJ3936Y5MHRLXTAg6SxCNhuH6JBEuieuicUY9wqZk8C63SZIj4htA", + "eqSjvs1X7eI9V2o8sYUpsrj6WUKOymqFtkCxMwWQuDPtZKOHC3fSWkjQvf_73GH-", + ); + let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(test_pvt_b64) + .unwrap(); + let sk = P384KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); + + let jwk = sk.to_jwk_public(None).expect("Error converting key to JWK"); + let jwk = JwkParts::try_from_str(&jwk).expect("Error parsing JWK"); + assert_eq!(jwk.kty, JWK_KEY_TYPE); + assert_eq!(jwk.crv, JWK_CURVE); + assert_eq!(jwk.x, test_pub_b64.0); + assert_eq!(jwk.y, test_pub_b64.1); + assert_eq!(jwk.d, None); + let pk_load = P384KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!(sk.to_public_bytes(), pk_load.to_public_bytes()); + + let jwk = sk.to_jwk_secret(None).expect("Error converting key to JWK"); + let jwk = JwkParts::from_slice(&jwk).expect("Error parsing JWK"); + assert_eq!(jwk.kty, JWK_KEY_TYPE); + assert_eq!(jwk.crv, JWK_CURVE); + assert_eq!(jwk.x, test_pub_b64.0); + assert_eq!(jwk.y, test_pub_b64.1); + assert_eq!(jwk.d, test_pvt_b64); + let sk_load = P384KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!( + sk.to_keypair_bytes().unwrap(), + sk_load.to_keypair_bytes().unwrap() + ); + } + + #[test] + fn jwk_thumbprint() { + let pk = P384KeyPair::from_jwk( + r#"{ + "kty": "EC", + "x": "p3ZI8DAmxn8BJ3936Y5MHRLXTAg6SxCNhuH6JBEuieuicUY9wqZk8C63SZIj4htA", + "y": "eqSjvs1X7eI9V2o8sYUpsrj6WUKOymqFtkCxMwWQuDPtZKOHC3fSWkjQvf_73GH-", + "crv": "P-384" + }"#, + ) + .unwrap(); + assert_eq!( + pk.to_jwk_thumbprint(None).unwrap(), + "4zlc15_l012-r5pFk7mnEFs6MghkhSAkdMeNeyL00u4" + ); + } + + #[test] + fn sign_verify_expected() { + let test_msg = b"This is a dummy message for use with tests"; + let test_sig = &hex!( + "acf7e9f0975738d446b26aa1651ad699cac490a496d6f70221126c35d8e4fcc5a28f63f611557be9d4c321d8fa24dbf2 + 846e3bcbea2e45eff577974664b1e98fffdad8ddbe7bfa792c17a9981915aa63755cfd338fd28874de02c42d966ece67" + ); + let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode("rgFYq-b_toGb-wN3URCk_e-6Sj2PtUvoefF284q9oKnVCi7sglAmCZkOv-2nOAeE") + .unwrap(); + let kp = P384KeyPair::from_secret_bytes(&test_pvt).unwrap(); + let sig = kp.sign(&test_msg[..]).unwrap(); + assert_eq!(sig, &test_sig[..]); + assert!(kp.verify_signature(&test_msg[..], &sig[..])); + assert!(!kp.verify_signature(b"Not the message", &sig[..])); + assert!(!kp.verify_signature(&test_msg[..], &[0u8; 96])); + } + + #[test] + fn key_exchange_random() { + let kp1 = P384KeyPair::random().unwrap(); + let kp2 = P384KeyPair::random().unwrap(); + assert_ne!( + kp1.to_keypair_bytes().unwrap(), + kp2.to_keypair_bytes().unwrap() + ); + + let xch1 = kp1.key_exchange_bytes(&kp2).unwrap(); + let xch2 = kp2.key_exchange_bytes(&kp1).unwrap(); + assert_eq!(xch1.len(), 48); + assert_eq!(xch1, xch2); + } + + #[test] + fn round_trip_bytes() { + let kp = P384KeyPair::random().unwrap(); + let cmp = P384KeyPair::from_keypair_bytes(&kp.to_keypair_bytes().unwrap()).unwrap(); + assert_eq!( + kp.to_keypair_bytes().unwrap(), + cmp.to_keypair_bytes().unwrap() + ); + } +} diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index b6772e0d..370d40a4 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -32,9 +32,9 @@ pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; /// The 'kty' value of an X25519 JWK -pub static JWK_KEY_TYPE: &'static str = "OKP"; +pub static JWK_KEY_TYPE: &str = "OKP"; /// The 'crv' value of an X25519 JWK -pub static JWK_CURVE: &'static str = "X25519"; +pub static JWK_CURVE: &str = "X25519"; /// An X25519 public key or keypair #[derive(Clone)] @@ -202,20 +202,18 @@ impl FromJwk for X25519KeyPair { ArrayKey::::temp(|pk_arr| { if jwk.x.decode_base64(pk_arr)? != pk_arr.len() { Err(err_msg!(InvalidKeyData)) + } else if jwk.d.is_some() { + ArrayKey::::temp(|sk_arr| { + if jwk.d.decode_base64(sk_arr)? != sk_arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + let kp = X25519KeyPair::from_secret_bytes(sk_arr)?; + kp.check_public_bytes(pk_arr)?; + Ok(kp) + } + }) } else { - if jwk.d.is_some() { - ArrayKey::::temp(|sk_arr| { - if jwk.d.decode_base64(sk_arr)? != sk_arr.len() { - Err(err_msg!(InvalidKeyData)) - } else { - let kp = X25519KeyPair::from_secret_bytes(sk_arr)?; - kp.check_public_bytes(pk_arr)?; - Ok(kp) - } - }) - } else { - X25519KeyPair::from_public_bytes(pk_arr) - } + X25519KeyPair::from_public_bytes(pk_arr) } }) } @@ -244,6 +242,8 @@ impl TryFrom<&Ed25519KeyPair> for X25519KeyPair { #[cfg(test)] mod tests { + use base64::Engine; + use super::*; use crate::repr::ToPublicBytes; @@ -257,13 +257,15 @@ mod tests { // "x": "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU" // } let test_pvt_b64 = "qL25gw-HkNJC9m4EsRzCoUx1KntjwHPzxo6a2xUcyFQ"; - let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); + let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(test_pvt_b64) + .unwrap(); let kp = X25519KeyPair::from_secret_bytes(&test_pvt).expect("Error creating x25519 keypair"); let jwk = kp .to_jwk_public(None) .expect("Error converting public key to JWK"); - let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK output"); + let jwk = JwkParts::try_from_str(&jwk).expect("Error parsing JWK output"); assert_eq!(jwk.kty, JWK_KEY_TYPE); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); diff --git a/askar-crypto/src/buffer/array.rs b/askar-crypto/src/buffer/array.rs index 037cb56f..67e44a9c 100644 --- a/askar-crypto/src/buffer/array.rs +++ b/askar-crypto/src/buffer/array.rs @@ -1,5 +1,6 @@ use core::{ fmt::{self, Debug, Formatter}, + hash, marker::{PhantomData, PhantomPinned}, ops::Deref, }; @@ -17,7 +18,7 @@ use crate::{ }; /// A secure representation for fixed-length keys -#[derive(Clone, Hash)] +#[derive(Clone)] #[repr(transparent)] pub struct ArrayKey>( GenericArray, @@ -128,7 +129,7 @@ impl> From> for ArrayKey { impl> Debug for ArrayKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if cfg!(test) { - f.debug_tuple("ArrayKey").field(&*self).finish() + f.debug_tuple("ArrayKey").field(&self.0).finish() } else { f.debug_tuple("ArrayKey").field(&"").finish() } @@ -149,6 +150,12 @@ impl> PartialEq for ArrayKey { } impl> Eq for ArrayKey {} +impl> hash::Hash for ArrayKey { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + impl> Serialize for ArrayKey { fn serialize(&self, serializer: S) -> Result where diff --git a/askar-crypto/src/buffer/hash.rs b/askar-crypto/src/buffer/hash.rs index 0807ed62..c6647ab1 100644 --- a/askar-crypto/src/buffer/hash.rs +++ b/askar-crypto/src/buffer/hash.rs @@ -22,6 +22,12 @@ impl HashBuffer { } } +impl Default for HashBuffer { + fn default() -> Self { + Self::new() + } +} + impl WriteBuffer for HashBuffer { fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { self.0.update(data); diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index faae2496..f9f4f30f 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -64,7 +64,7 @@ impl WriteBuffer for Vec { #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl ResizeBuffer for Vec { fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { - self.splice(pos..pos, data.into_iter().cloned()); + self.splice(pos..pos, data.iter().cloned()); Ok(()) } @@ -87,19 +87,19 @@ mod tests { pub(crate) fn test_write_buffer>(mut w: B) { w.buffer_write(b"he").unwrap(); w.buffer_write(b"y").unwrap(); - assert_eq!(&w.as_ref()[..], b"hey"); + assert_eq!(w.as_ref(), b"hey"); } pub(crate) fn test_resize_buffer(mut w: B) { w.buffer_write(b"hello").unwrap(); w.buffer_insert(1, b"world").unwrap(); - assert_eq!(&w.as_ref()[..], b"hworldello"); + assert_eq!(w.as_ref(), b"hworldello"); w.buffer_resize(12).unwrap(); - assert_eq!(&w.as_ref()[..], b"hworldello\0\0"); + assert_eq!(w.as_ref(), b"hworldello\0\0"); w.buffer_resize(6).unwrap(); - assert_eq!(&w.as_ref()[..], b"hworld"); + assert_eq!(w.as_ref(), b"hworld"); w.buffer_insert(1, b"ello").unwrap(); - assert_eq!(&w.as_ref()[..], b"helloworld"); + assert_eq!(w.as_ref(), b"helloworld"); } #[test] diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index 81982882..8ebce8ef 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -1,7 +1,7 @@ use alloc::{boxed::Box, string::String, vec::Vec}; use core::{ fmt::{self, Debug, Formatter}, - mem, + hash, mem, ops::{Deref, Range}, }; @@ -13,7 +13,7 @@ use super::{string::MaybeStr, HexRepr, ResizeBuffer, WriteBuffer}; use crate::error::Error; /// A heap-allocated, zeroized byte buffer -#[derive(Clone, Default, Hash, Zeroize)] +#[derive(Clone, Default, Zeroize)] pub struct SecretBytes(Vec); impl SecretBytes { @@ -59,6 +59,12 @@ impl SecretBytes { self.0.len() } + /// Determine if the buffer has zero length + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Try to convert the buffer value to a string reference pub fn as_opt_str(&self) -> Option<&str> { core::str::from_utf8(self.0.as_slice()).ok() @@ -205,6 +211,12 @@ impl PartialEq for SecretBytes { } impl Eq for SecretBytes {} +impl hash::Hash for SecretBytes { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + impl From<&[u8]> for SecretBytes { fn from(inner: &[u8]) -> Self { Self(inner.to_vec()) @@ -256,7 +268,7 @@ impl WriteBuffer for SecretBytes { impl ResizeBuffer for SecretBytes { fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { - self.splice(pos..pos, data.into_iter().cloned()) + self.splice(pos..pos, data.iter().cloned()) } fn buffer_remove(&mut self, range: Range) -> Result<(), Error> { diff --git a/askar-crypto/src/buffer/string.rs b/askar-crypto/src/buffer/string.rs index 05205727..8a0c744f 100644 --- a/askar-crypto/src/buffer/string.rs +++ b/askar-crypto/src/buffer/string.rs @@ -29,7 +29,7 @@ impl> PartialEq<[u8]> for HexRepr { } } - write!(&mut CmpWrite(other.into_iter()), "{}", self).is_ok() + write!(&mut CmpWrite(other.iter()), "{}", self).is_ok() } } diff --git a/askar-crypto/src/buffer/writer.rs b/askar-crypto/src/buffer/writer.rs index df9d0626..f273dae5 100644 --- a/askar-crypto/src/buffer/writer.rs +++ b/askar-crypto/src/buffer/writer.rs @@ -45,19 +45,23 @@ impl Writer<'_, [u8]> { assert!(range.end >= range.start); let rem_len = range.len(); let ins_len = iter.len(); - if ins_len > rem_len { - let diff = ins_len - rem_len; - if self.pos + diff > self.inner.len() { - return Err(err_msg!(ExceededBuffer)); + match ins_len { + _ if ins_len > rem_len => { + let diff = ins_len - rem_len; + if self.pos + diff > self.inner.len() { + return Err(err_msg!(ExceededBuffer)); + } + self.inner + .copy_within((range.end - diff)..self.pos, range.end); + self.pos += diff; } - self.inner - .copy_within((range.end - diff)..self.pos, range.end); - self.pos += diff; - } else if ins_len < rem_len { - let diff = rem_len - ins_len; - self.inner - .copy_within(range.end..self.pos, range.end - diff); - self.pos -= diff; + _ if ins_len < rem_len => { + let diff = rem_len - ins_len; + self.inner + .copy_within(range.end..self.pos, range.end - diff); + self.pos -= diff; + } + _ => {} } for idx in 0..ins_len { self.inner[range.start + idx] = iter.next().unwrap(); @@ -93,7 +97,7 @@ impl WriteBuffer for Writer<'_, [u8]> { impl ResizeBuffer for Writer<'_, [u8]> { fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { - self.splice(pos..pos, data.into_iter().cloned()) + self.splice(pos..pos, data.iter().cloned()) } fn buffer_remove(&mut self, range: Range) -> Result<(), Error> { diff --git a/askar-crypto/src/jwk/encode.rs b/askar-crypto/src/jwk/encode.rs index 7265b9bb..73054ebd 100644 --- a/askar-crypto/src/jwk/encode.rs +++ b/askar-crypto/src/jwk/encode.rs @@ -16,7 +16,10 @@ fn write_hex_buffer(mut buffer: impl Write, value: &[u8]) -> Result<(), Error> { write!( buffer, "{}", - base64::display::Base64Display::with_config(value, base64::URL_SAFE_NO_PAD) + base64::display::Base64Display::new( + value, + &base64::engine::general_purpose::URL_SAFE_NO_PAD + ) ) .map_err(|_| err_msg!(Unexpected, "Error writing to JWK buffer")) } diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index bdf7d485..bd93d321 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -3,6 +3,7 @@ #[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; +use base64::Engine; use sha2::Sha256; #[cfg(feature = "alloc")] @@ -71,7 +72,9 @@ pub fn write_jwk_thumbprint( buf.finalize()?; let hash = hasher.finalize(); let mut buf = [0u8; 43]; - let len = base64::encode_config_slice(&hash, base64::URL_SAFE_NO_PAD, &mut buf); + let len = base64::engine::general_purpose::URL_SAFE_NO_PAD + .encode_slice(hash, &mut buf) + .map_err(|_| err_msg!(Unexpected, "Base64 encoding error"))?; output.buffer_write(&buf[..len])?; Ok(()) } @@ -80,7 +83,7 @@ pub fn write_jwk_thumbprint( pub trait FromJwk: Sized { /// Import the key from a JWK string reference fn from_jwk(jwk: &str) -> Result { - JwkParts::from_str(jwk).and_then(Self::from_jwk_parts) + JwkParts::try_from_str(jwk).and_then(Self::from_jwk_parts) } /// Import the key from a JWK byte slice diff --git a/askar-crypto/src/jwk/ops.rs b/askar-crypto/src/jwk/ops.rs index 3da4c8a0..670df2d2 100644 --- a/askar-crypto/src/jwk/ops.rs +++ b/askar-crypto/src/jwk/ops.rs @@ -67,7 +67,7 @@ impl KeyOps { } /// Parse a key operation from a string reference - pub fn from_str(key: &str) -> Option { + pub fn try_from_str(key: &str) -> Option { match key { "sign" => Some(Self::Sign), "verify" => Some(Self::Verify), @@ -218,7 +218,7 @@ impl<'de> Visitor<'de> for KeyOpsVisitor { { let mut ops = KeyOpsSet::new(); while let Some(op) = seq.next_element()? { - if let Some(op) = KeyOps::from_str(op) { + if let Some(op) = KeyOps::try_from_str(op) { if ops & op { return Err(serde::de::Error::duplicate_field(op.as_str())); } else { @@ -258,8 +258,8 @@ mod tests { #[test] fn invariants() { - assert_eq!(KeyOpsSet::new().is_empty(), true); - assert_eq!(KeyOpsSet::from(KeyOps::Decrypt).is_empty(), false); + assert!(KeyOpsSet::new().is_empty()); + assert!(!KeyOpsSet::from(KeyOps::Decrypt).is_empty()); assert_eq!(KeyOpsSet::new(), KeyOpsSet::new()); assert_ne!(KeyOpsSet::from(KeyOps::Decrypt), KeyOpsSet::new()); assert_ne!(KeyOps::Decrypt, KeyOps::Encrypt); diff --git a/askar-crypto/src/jwk/parts.rs b/askar-crypto/src/jwk/parts.rs index 1fdb2978..055464af 100644 --- a/askar-crypto/src/jwk/parts.rs +++ b/askar-crypto/src/jwk/parts.rs @@ -5,6 +5,7 @@ use core::{ #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; +use base64::Engine; use serde::{ de::{Deserialize, Deserializer, MapAccess, Visitor}, ser::{Serialize, SerializeMap, Serializer}, @@ -39,7 +40,7 @@ pub struct JwkParts<'a> { impl<'de> JwkParts<'de> { /// Parse a JWK from a string reference - pub fn from_str(jwk: &'de str) -> Result { + pub fn try_from_str(jwk: &'de str) -> Result { let (parts, _read) = serde_json_core::from_str(jwk).map_err(err_map!(Invalid, "Error parsing JWK"))?; Ok(parts) @@ -67,7 +68,7 @@ impl OptAttr<'_> { self.0.is_some() } - pub fn to_option(&self) -> Option<&str> { + pub fn as_opt_str(&self) -> Option<&str> { self.0 } @@ -77,7 +78,8 @@ impl OptAttr<'_> { if s.len() > max_input { Err(err_msg!(Invalid, "Base64 length exceeds max")) } else { - base64::decode_config_slice(s, base64::URL_SAFE_NO_PAD, output) + base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode_slice_unchecked(s, output) .map_err(|_| err_msg!(Invalid, "Base64 decoding error")) } } else { @@ -211,26 +213,26 @@ impl Serialize for JwkParts<'_> { S: Serializer, { let mut map = serializer.serialize_map(None)?; - if let Some(alg) = self.alg.to_option() { + if let Some(alg) = self.alg.as_opt_str() { map.serialize_entry("alg", alg)?; } - if let Some(crv) = self.crv.to_option() { + if let Some(crv) = self.crv.as_opt_str() { map.serialize_entry("crv", crv)?; } - if let Some(d) = self.d.to_option() { + if let Some(d) = self.d.as_opt_str() { map.serialize_entry("d", d)?; } - if let Some(k) = self.k.to_option() { + if let Some(k) = self.k.as_opt_str() { map.serialize_entry("k", k)?; } - if let Some(kid) = self.kid.to_option() { + if let Some(kid) = self.kid.as_opt_str() { map.serialize_entry("kid", kid)?; } map.serialize_entry("kty", self.kty)?; - if let Some(x) = self.x.to_option() { + if let Some(x) = self.x.as_opt_str() { map.serialize_entry("x", x)?; } - if let Some(y) = self.y.to_option() { + if let Some(y) = self.y.as_opt_str() { map.serialize_entry("y", y)?; } if let Some(ops) = self.key_ops { @@ -254,7 +256,7 @@ mod tests { "key_ops": ["sign", "verify"], "kid": "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" }"#; - let parts = JwkParts::from_str(jwk).unwrap(); + let parts = JwkParts::try_from_str(jwk).unwrap(); assert_eq!(parts.kty, "OKP"); assert_eq!( parts.kid, diff --git a/askar-crypto/src/kdf/argon2.rs b/askar-crypto/src/kdf/argon2.rs index aac17486..c05d1346 100644 --- a/askar-crypto/src/kdf/argon2.rs +++ b/askar-crypto/src/kdf/argon2.rs @@ -69,12 +69,13 @@ impl KeyDerivation for Argon2<'_> { )); } let mut pbuild = argon2::ParamsBuilder::new(); - pbuild.m_cost(self.params.mem_cost).unwrap(); - pbuild.t_cost(self.params.time_cost).unwrap(); + pbuild + .m_cost(self.params.mem_cost) + .t_cost(self.params.time_cost); argon2::Argon2::new( self.params.alg, self.params.version, - pbuild.params().unwrap(), + pbuild.build().unwrap(), ) .hash_password_into(self.password, self.salt, key_output) .map_err(|_| err_msg!(Unexpected, "Error deriving key")) diff --git a/askar-crypto/src/kdf/concat.rs b/askar-crypto/src/kdf/concat.rs index 7a1707ea..1051c316 100644 --- a/askar-crypto/src/kdf/concat.rs +++ b/askar-crypto/src/kdf/concat.rs @@ -106,6 +106,12 @@ impl ConcatKDFHash { } } +impl Default for ConcatKDFHash { + fn default() -> Self { + Self::new() + } +} + impl WriteBuffer for ConcatKDFHash { fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { self.hasher.update(data); diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index 0b0fff80..fd71024a 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -27,6 +27,7 @@ pub struct Ecdh1PU<'d, Key: KeyExchange + ?Sized> { impl<'d, Key: KeyExchange + ?Sized> Ecdh1PU<'d, Key> { /// Create a new KDF instance + #[allow(clippy::too_many_arguments)] pub fn new( ephem_key: &'d Key, send_key: &'d Key, @@ -80,7 +81,7 @@ impl KeyDerivation for Ecdh1PU<'_, Key> { pub_w.buffer_write(&((output_len as u32) * 8).to_be_bytes())?; // output length in bits if !self.cc_tag.is_empty() { pub_w.buffer_write(&(self.cc_tag.len() as u32).to_be_bytes())?; - pub_w.buffer_write(&self.cc_tag)?; + pub_w.buffer_write(self.cc_tag)?; } kdf.hash_params(ConcatKDFParams { diff --git a/askar-crypto/src/random.rs b/askar-crypto/src/random.rs index e7e8cada..dc387caf 100644 --- a/askar-crypto/src/random.rs +++ b/askar-crypto/src/random.rs @@ -4,7 +4,7 @@ use core::fmt::{self, Debug, Formatter}; use aead::generic_array::{typenum::Unsigned, GenericArray}; use chacha20::{ - cipher::{NewCipher, StreamCipher}, + cipher::{KeyIvInit, KeySizeUser, StreamCipher}, ChaCha20, }; use rand::{CryptoRng, RngCore, SeedableRng}; @@ -14,7 +14,7 @@ use crate::buffer::SecretBytes; use crate::error::Error; /// The expected length of a seed for `fill_random_deterministic` -pub const DETERMINISTIC_SEED_LENGTH: usize = ::KeySize::USIZE; +pub const DETERMINISTIC_SEED_LENGTH: usize = ::KeySize::USIZE; /// Combined trait for CryptoRng and RngCore pub trait Rng: CryptoRng + RngCore + Debug {} @@ -124,7 +124,7 @@ impl RandomDet { /// Construct a new `RandomDet` instance from a seed value pub fn new(seed: &[u8]) -> Self { let mut sd = [0u8; DETERMINISTIC_SEED_LENGTH]; - let seed_len = seed.len().max(DETERMINISTIC_SEED_LENGTH); + let seed_len = seed.len().min(DETERMINISTIC_SEED_LENGTH); sd[..seed_len].copy_from_slice(&seed[..seed_len]); Self::from_seed(sd) } diff --git a/askar-crypto/src/sign.rs b/askar-crypto/src/sign.rs index abdd6b41..e00e23ca 100644 --- a/askar-crypto/src/sign.rs +++ b/askar-crypto/src/sign.rs @@ -53,6 +53,8 @@ pub enum SignatureType { ES256, /// Elliptic curve DSA using K-256 and SHA-256 ES256K, + /// Elliptic curve DSA using P-384 and SHA-384 + ES384, } impl FromStr for SignatureType { @@ -63,6 +65,7 @@ impl FromStr for SignatureType { a if a == "eddsa" => Ok(Self::EdDSA), a if a == "es256" => Ok(Self::ES256), a if a == "es256k" => Ok(Self::ES256K), + a if a == "es384" => Ok(Self::ES384), _ => Err(err_msg!(Unsupported, "Unknown signature algorithm")), } } @@ -73,6 +76,7 @@ impl SignatureType { pub const fn signature_length(&self) -> usize { match self { Self::EdDSA | Self::ES256 | Self::ES256K => 64, + Self::ES384 => 96, } } } diff --git a/askar-storage/Cargo.toml b/askar-storage/Cargo.toml new file mode 100644 index 00000000..14d80863 --- /dev/null +++ b/askar-storage/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "askar-storage" +version = "0.1.0" +authors = ["Hyperledger Aries Contributors "] +edition = "2021" +description = "Hyperledger Aries Askar secure storage" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/hyperledger/aries-askar/" +categories = ["cryptography", "database"] +keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] +rust-version = "1.58" + +[package.metadata.docs.rs] +features = ["all_backends"] +no-default-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[features] +default = ["all_backends", "log"] +all_backends = ["any", "postgres", "sqlite"] +any = [] +migration = ["rmp-serde", "sqlx/macros"] +postgres = ["sqlx", "sqlx/postgres", "sqlx/tls-rustls"] +sqlite = ["sqlx", "sqlx/sqlite"] +pg_test = ["postgres"] + +[dependencies] +arc-swap = "1.6" +async-lock = "2.5" +async-stream = "0.3" +bs58 = "0.5" +chrono = "0.4" +digest = "0.10" +futures-lite = "1.11" +hex = "0.4" +hmac = "0.12" +itertools = "0.11" +log = { version = "0.4", optional = true } +once_cell = "1.5" +percent-encoding = "2.0" +rmp-serde = { version= "1.1", optional = true } +serde = { version = "1.0", features = ["derive"] } +serde_cbor = "0.11" +serde_json = "1.0" +sha2 = "0.10" +tokio = { version = "1.5", features = ["rt-multi-thread", "time"] } +url = { version = "2.1", default-features = false } +uuid = { version = "1.2", features = ["v4"] } +zeroize = "1.5" + +[dependencies.askar-crypto] +version = "0.2.5" +path = "../askar-crypto" +default-features = false +features = ["alloc", "argon2", "chacha", "std_rng"] + +[dependencies.sqlx] +version = "0.7.1" +default-features = false +features = ["chrono", "runtime-tokio"] +optional = true + +[dev-dependencies] +env_logger = "0.10" +hex-literal = "0.4" +rand = { version = "0.8" } + +[[test]] +name = "backends" diff --git a/askar-storage/src/any.rs b/askar-storage/src/any.rs new file mode 100644 index 00000000..93644cff --- /dev/null +++ b/askar-storage/src/any.rs @@ -0,0 +1,346 @@ +//! Generic backend support + +use std::{fmt::Debug, sync::Arc}; + +use super::{Backend, BackendSession, ManageBackend}; +use crate::{ + entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, + error::Error, + future::BoxFuture, + options::IntoOptions, + protect::{PassKey, StoreKeyMethod}, +}; + +#[cfg(feature = "postgres")] +use super::postgres; + +#[cfg(feature = "sqlite")] +use super::sqlite; + +/// A dynamic store backend instance +#[derive(Clone, Debug)] +pub struct AnyBackend(Arc>); + +/// Wrap a backend instance into an AnyBackend +pub fn into_any_backend(inst: impl Backend + 'static) -> AnyBackend { + AnyBackend(Arc::new(WrapBackend(inst))) +} + +/// This structure turns a generic backend into a concrete type +#[derive(Debug)] +struct WrapBackend(B); + +impl Backend for WrapBackend { + type Session = AnyBackendSession; + + #[inline] + fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { + self.0.create_profile(name) + } + + #[inline] + fn get_active_profile(&self) -> String { + self.0.get_active_profile() + } + + #[inline] + fn get_default_profile(&self) -> BoxFuture<'_, Result> { + self.0.get_default_profile() + } + + #[inline] + fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>> { + self.0.set_default_profile(profile) + } + + #[inline] + fn list_profiles(&self) -> BoxFuture<'_, Result, Error>> { + self.0.list_profiles() + } + + #[inline] + fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { + self.0.remove_profile(name) + } + + #[inline] + fn scan( + &self, + profile: Option, + kind: Option, + category: Option, + tag_filter: Option, + offset: Option, + limit: Option, + ) -> BoxFuture<'_, Result, Error>> { + self.0 + .scan(profile, kind, category, tag_filter, offset, limit) + } + + #[inline] + fn session(&self, profile: Option, transaction: bool) -> Result { + Ok(AnyBackendSession(Box::new( + self.0.session(profile, transaction)?, + ))) + } + + #[inline] + fn rekey( + &mut self, + method: StoreKeyMethod, + key: PassKey<'_>, + ) -> BoxFuture<'_, Result<(), Error>> { + self.0.rekey(method, key) + } + + #[inline] + fn close(&self) -> BoxFuture<'_, Result<(), Error>> { + self.0.close() + } +} + +// Forward to the concrete inner backend instance +impl Backend for AnyBackend { + type Session = AnyBackendSession; + + #[inline] + fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { + self.0.create_profile(name) + } + + #[inline] + fn get_active_profile(&self) -> String { + self.0.get_active_profile() + } + + #[inline] + fn get_default_profile(&self) -> BoxFuture<'_, Result> { + self.0.get_default_profile() + } + + #[inline] + fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>> { + self.0.set_default_profile(profile) + } + + #[inline] + fn list_profiles(&self) -> BoxFuture<'_, Result, Error>> { + self.0.list_profiles() + } + + #[inline] + fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { + self.0.remove_profile(name) + } + + #[inline] + fn scan( + &self, + profile: Option, + kind: Option, + category: Option, + tag_filter: Option, + offset: Option, + limit: Option, + ) -> BoxFuture<'_, Result, Error>> { + self.0 + .scan(profile, kind, category, tag_filter, offset, limit) + } + + #[inline] + fn session(&self, profile: Option, transaction: bool) -> Result { + Ok(AnyBackendSession(Box::new( + self.0.session(profile, transaction)?, + ))) + } + + #[inline] + fn rekey( + &mut self, + method: StoreKeyMethod, + key: PassKey<'_>, + ) -> BoxFuture<'_, Result<(), Error>> { + match Arc::get_mut(&mut self.0) { + Some(inner) => inner.rekey(method, key), + None => Box::pin(std::future::ready(Err(err_msg!( + "Cannot re-key a store with multiple references" + )))), + } + } + + #[inline] + fn close(&self) -> BoxFuture<'_, Result<(), Error>> { + self.0.close() + } +} + +/// A dynamic store session instance +#[derive(Debug)] +pub struct AnyBackendSession(Box); + +impl BackendSession for AnyBackendSession { + /// Count the number of matching records in the store + fn count<'q>( + &'q mut self, + kind: Option, + category: Option<&'q str>, + tag_filter: Option, + ) -> BoxFuture<'q, Result> { + self.0.count(kind, category, tag_filter) + } + + /// Fetch a single record from the store by category and name + fn fetch<'q>( + &'q mut self, + kind: EntryKind, + category: &'q str, + name: &'q str, + for_update: bool, + ) -> BoxFuture<'q, Result, Error>> { + self.0.fetch(kind, category, name, for_update) + } + + /// Fetch all matching records from the store + fn fetch_all<'q>( + &'q mut self, + kind: Option, + category: Option<&'q str>, + tag_filter: Option, + limit: Option, + for_update: bool, + ) -> BoxFuture<'q, Result, Error>> { + self.0 + .fetch_all(kind, category, tag_filter, limit, for_update) + } + + /// Remove all matching records from the store + fn remove_all<'q>( + &'q mut self, + kind: Option, + category: Option<&'q str>, + tag_filter: Option, + ) -> BoxFuture<'q, Result> { + self.0.remove_all(kind, category, tag_filter) + } + + /// Insert or replace a record in the store + #[allow(clippy::too_many_arguments)] + fn update<'q>( + &'q mut self, + kind: EntryKind, + operation: EntryOperation, + category: &'q str, + name: &'q str, + value: Option<&'q [u8]>, + tags: Option<&'q [EntryTag]>, + expiry_ms: Option, + ) -> BoxFuture<'q, Result<(), Error>> { + self.0 + .update(kind, operation, category, name, value, tags, expiry_ms) + } + + /// Close the current store session + fn close(&mut self, commit: bool) -> BoxFuture<'_, Result<(), Error>> { + self.0.close(commit) + } +} + +impl<'a> ManageBackend<'a> for &'a str { + type Backend = AnyBackend; + + fn open_backend( + self, + method: Option, + pass_key: PassKey<'a>, + profile: Option, + ) -> BoxFuture<'a, Result> { + Box::pin(async move { + let opts = self.into_options()?; + debug!("Open store with options: {:?}", &opts); + + match opts.schema.as_ref() { + #[cfg(feature = "postgres")] + "postgres" => { + let opts = postgres::PostgresStoreOptions::new(opts)?; + let mgr = opts.open(method, pass_key, profile).await?; + Ok(into_any_backend(mgr)) + } + + #[cfg(feature = "sqlite")] + "sqlite" => { + let opts = sqlite::SqliteStoreOptions::new(opts)?; + let mgr = opts.open(method, pass_key, profile).await?; + Ok(into_any_backend(mgr)) + } + + _ => Err(err_msg!( + Unsupported, + "Unsupported backend: {}", + &opts.schema + )), + } + }) + } + + fn provision_backend( + self, + method: StoreKeyMethod, + pass_key: PassKey<'a>, + profile: Option, + recreate: bool, + ) -> BoxFuture<'a, Result> { + Box::pin(async move { + let opts = self.into_options()?; + debug!("Provision store with options: {:?}", &opts); + + match opts.schema.as_ref() { + #[cfg(feature = "postgres")] + "postgres" => { + let opts = postgres::PostgresStoreOptions::new(opts)?; + let mgr = opts.provision(method, pass_key, profile, recreate).await?; + Ok(into_any_backend(mgr)) + } + + #[cfg(feature = "sqlite")] + "sqlite" => { + let opts = sqlite::SqliteStoreOptions::new(opts)?; + let mgr = opts.provision(method, pass_key, profile, recreate).await?; + Ok(into_any_backend(mgr)) + } + + _ => Err(err_msg!( + Unsupported, + "Unsupported backend: {}", + &opts.schema + )), + } + }) + } + + fn remove_backend(self) -> BoxFuture<'a, Result> { + Box::pin(async move { + let opts = self.into_options()?; + debug!("Remove store with options: {:?}", &opts); + + match opts.schema.as_ref() { + #[cfg(feature = "postgres")] + "postgres" => { + let opts = postgres::PostgresStoreOptions::new(opts)?; + Ok(opts.remove().await?) + } + + #[cfg(feature = "sqlite")] + "sqlite" => { + let opts = sqlite::SqliteStoreOptions::new(opts)?; + Ok(opts.remove().await?) + } + + _ => Err(err_msg!( + Unsupported, + "Unsupported backend: {}", + &opts.schema + )), + } + }) + } +} diff --git a/src/backend/db_utils.rs b/askar-storage/src/backend/db_utils.rs similarity index 81% rename from src/backend/db_utils.rs rename to askar-storage/src/backend/db_utils.rs index 0f043188..b4c0ab76 100644 --- a/src/backend/db_utils.rs +++ b/askar-storage/src/backend/db_utils.rs @@ -8,26 +8,27 @@ use sqlx::{ }; use crate::{ + entry::{EncEntryTag, Entry, EntryKind, EntryTag, TagFilter}, error::Error, future::BoxFuture, protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKey, StoreKeyMethod}, - storage::{ - wql::{ - sql::TagSqlEncoder, - tags::{tag_query, TagQueryEncoder}, - }, - {EncEntryTag, Entry, EntryTag, TagFilter}, + wql::{ + sql::TagSqlEncoder, + tags::{tag_query, TagQueryEncoder}, }, }; +/// cbindgen:ignore pub const PAGE_SIZE: usize = 32; pub type Expiry = chrono::DateTime; +pub(crate) type Connection = ::Connection; + #[derive(Debug)] pub(crate) enum DbSessionState { Active { conn: PoolConnection }, - Pending { pool: Pool }, + Pending { pool: Pool, transaction: bool }, } unsafe impl Sync for DbSessionState where DB::Connection: Send {} @@ -36,7 +37,7 @@ unsafe impl Sync for DbSessionState where DB::Connection: S pub struct DbSession { profile_key: DbSessionKey, state: DbSessionState, - transaction: bool, + txn_depth: usize, } impl DbSession { @@ -51,8 +52,8 @@ impl DbSession { { Self { profile_key: DbSessionKey::Pending { cache, profile }, - state: DbSessionState::Pending { pool }, - transaction, + state: DbSessionState::Pending { pool, transaction }, + txn_depth: 0, } } @@ -66,17 +67,17 @@ impl DbSession { } #[inline] - pub fn is_transaction(&self) -> bool { - self.transaction - } - - #[inline] - fn pool(&self) -> Option<&Pool> { - if let DbSessionState::Pending { pool, .. } = &self.state { - Some(pool) - } else { - None + pub fn in_transaction(&self) -> bool { + if self.txn_depth > 0 { + return true; } + if let DbSessionState::Pending { + transaction: true, .. + } = &self.state + { + return true; + } + false } pub(crate) fn profile_and_key(&mut self) -> Option<(ProfileId, Arc)> { @@ -98,12 +99,13 @@ impl DbSession { where I: for<'a> GetProfileKey<'a, DB>, { - if matches!(self.state, DbSessionState::Pending { .. }) { - info!("Acquire pool connection"); - let mut conn = self.pool().unwrap().acquire().await?; - if self.transaction { - info!("Start transaction"); + if let DbSessionState::Pending { pool, transaction } = &self.state { + debug!("Acquire pool connection"); + let mut conn = pool.acquire().await?; + if *transaction { + debug!("Start transaction"); DB::start_transaction(&mut conn, false).await?; + self.txn_depth += 1; } self.state = DbSessionState::Active { conn }; } @@ -120,12 +122,9 @@ impl DbSession { } DbSessionKey::Active { profile_id, .. } => *profile_id, }; - let txn_depth = if self.transaction { 1 } else { 0 }; Ok(DbSessionActive { inner: self, profile_id, - txn_depth, - false_txn: false, }) } @@ -139,33 +138,34 @@ impl DbSession { DbSessionRef::Owned(self) } - pub(crate) async fn close(mut self, commit: bool) -> Result<(), Error> { - if self.transaction { + pub(crate) async fn close(&mut self, commit: bool) -> Result<(), Error> { + if self.txn_depth > 0 { + self.txn_depth = 0; if let Some(conn) = self.connection_mut() { if commit { - info!("Commit transaction on close"); + debug!("Commit transaction on close"); DB::TransactionManager::commit(conn).await } else { - info!("Roll-back transaction on close"); + debug!("Roll-back transaction on close"); DB::TransactionManager::rollback(conn).await } .map_err(err_map!(Backend, "Error closing transaction"))?; } - self.transaction = false; } Ok(()) } } -impl<'q, DB: ExtDatabase> Drop for DbSession { +impl Drop for DbSession { fn drop(&mut self) { - if self.transaction { + if self.txn_depth > 0 { + self.txn_depth = 0; if let Some(conn) = self.connection_mut() { - info!("Dropped transaction: roll-back"); + debug!("Dropped transaction: roll-back"); DB::TransactionManager::start_rollback(conn); } } else { - info!("Dropped pool connection") + debug!("Dropped pool connection") } } } @@ -210,7 +210,7 @@ pub(crate) enum DbSessionKey { pub trait ExtDatabase: Database { fn start_transaction( - conn: &mut PoolConnection, + conn: &mut Connection, _nested: bool, ) -> BoxFuture<'_, Result<(), SqlxError>> { ::TransactionManager::begin(conn) @@ -245,75 +245,85 @@ impl<'q, DB: ExtDatabase> DerefMut for DbSessionRef<'q, DB> { pub(crate) struct DbSessionActive<'a, DB: ExtDatabase> { inner: &'a mut DbSession, pub(crate) profile_id: ProfileId, - txn_depth: usize, - false_txn: bool, } impl<'q, DB: ExtDatabase> DbSessionActive<'q, DB> { #[inline] - pub fn connection_mut(&mut self) -> &mut PoolConnection { - self.inner.connection_mut().unwrap() - } - - pub async fn commit(mut self) -> Result<(), Error> { - if self.txn_depth > 0 && !self.false_txn { - let conn = self.connection_mut(); - info!("Commit transaction"); - DB::TransactionManager::commit(conn).await?; - self.txn_depth = 0; - } - Ok(()) + pub fn connection_mut(&mut self) -> &mut Connection { + self.inner.connection_mut().unwrap().as_mut() } #[allow(unused)] - #[inline] - pub fn is_transaction(&self) -> bool { - self.txn_depth > 0 + pub fn in_transaction(&self) -> bool { + self.inner.in_transaction() } #[allow(unused)] - pub async fn transaction<'t>(&'t mut self) -> Result, Error> + pub async fn begin<'t>(&'t mut self) -> Result, Error> where 'q: 't, { - info!("Start nested transaction"); + debug!("Start nested transaction"); DB::start_transaction(self.connection_mut(), true).await?; - Ok(DbSessionActive { + self.inner.txn_depth += 1; + Ok(DbSessionTxn { inner: &mut *self.inner, profile_id: self.profile_id, - txn_depth: self.txn_depth + 1, - false_txn: false, + rollback: true, }) } - pub async fn as_transaction<'t>(&'t mut self) -> Result, Error> + pub async fn as_transaction<'t>(&'t mut self) -> Result, Error> where 'q: 't, { - if self.txn_depth == 0 { - info!("Start transaction"); + if self.inner.txn_depth == 0 { + debug!("Start transaction"); DB::start_transaction(self.connection_mut(), false).await?; - Ok(DbSessionActive { + self.inner.txn_depth += 1; + Ok(DbSessionTxn { inner: &mut *self.inner, profile_id: self.profile_id, - txn_depth: self.txn_depth + 1, - false_txn: false, + rollback: true, }) } else { - Ok(DbSessionActive { + Ok(DbSessionTxn { inner: &mut *self.inner, profile_id: self.profile_id, - txn_depth: self.txn_depth + 1, - false_txn: true, + rollback: false, }) } } } -impl<'a, DB: ExtDatabase> Drop for DbSessionActive<'a, DB> { +pub(crate) struct DbSessionTxn<'a, DB: ExtDatabase> { + inner: &'a mut DbSession, + pub(crate) profile_id: ProfileId, + rollback: bool, +} + +impl<'a, DB: ExtDatabase> DbSessionTxn<'a, DB> { + pub fn connection_mut(&mut self) -> &mut Connection { + self.inner.connection_mut().unwrap().as_mut() + } + + pub async fn commit(mut self) -> Result<(), Error> { + if self.rollback { + self.rollback = false; + self.inner.txn_depth -= 1; + let conn = self.connection_mut(); + debug!("Commit transaction"); + DB::TransactionManager::commit(conn).await?; + } + Ok(()) + } +} + +impl<'a, DB: ExtDatabase> Drop for DbSessionTxn<'a, DB> { fn drop(&mut self) { - if self.txn_depth > 1 && !self.false_txn { - info!("Roll-back dropped nested transaction"); + if self.rollback { + self.inner.txn_depth -= 1; + debug!("Roll-back dropped nested transaction"); DB::TransactionManager::start_rollback(self.connection_mut()); } } @@ -336,6 +346,8 @@ where } pub struct EncScanEntry { + pub kind: EntryKind, + pub category: Vec, pub name: Vec, pub value: Vec, pub tags: Vec, @@ -428,8 +440,8 @@ pub fn replace_arg_placeholders( '$' => Some((start_offs + 2, index)), '0'..='9' => { let mut end_offs = start_offs + 2; - while let Some(c) = iter.next() { - if ('0'..='9').contains(&c) { + for c in iter { + if c.is_ascii_digit() { end_offs += 1; } else { break; @@ -499,28 +511,32 @@ pub(crate) fn decode_tags(tags: Vec) -> Result, ()> { } pub fn decrypt_scan_batch( - category: String, + category: Option, enc_rows: Vec, key: &ProfileKey, ) -> Result, Error> { let mut batch = Vec::with_capacity(enc_rows.len()); for enc_entry in enc_rows { - batch.push(decrypt_scan_entry(category.clone(), enc_entry, key)?); + batch.push(decrypt_scan_entry(category.as_deref(), enc_entry, key)?); } Ok(batch) } pub fn decrypt_scan_entry( - category: String, + category: Option<&str>, enc_entry: EncScanEntry, key: &ProfileKey, ) -> Result { + let category = match category { + Some(c) => c.to_owned(), + None => key.decrypt_entry_category(enc_entry.category)?, + }; let name = key.decrypt_entry_name(enc_entry.name)?; let value = key.decrypt_entry_value(category.as_bytes(), name.as_bytes(), enc_entry.value)?; let tags = key.decrypt_entry_tags( decode_tags(enc_entry.tags).map_err(|_| err_msg!(Unexpected, "Error decoding tags"))?, )?; - Ok(Entry::new(category.to_string(), name, value, tags)) + Ok(Entry::new(enc_entry.kind, category, name, value, tags)) } pub fn expiry_timestamp(expire_ms: i64) -> Result { @@ -529,6 +545,7 @@ pub fn expiry_timestamp(expire_ms: i64) -> Result { .ok_or_else(|| err_msg!(Unexpected, "Invalid expiry timestamp")) } +#[allow(clippy::type_complexity)] pub fn encode_tag_filter( tag_filter: Option, key: &ProfileKey, @@ -537,8 +554,8 @@ pub fn encode_tag_filter( if let Some(tag_filter) = tag_filter { let tag_query = tag_query(tag_filter.query)?; let mut enc = TagSqlEncoder::new( - |name| Ok(key.encrypt_tag_name(ProfileKey::prepare_input(name.as_bytes()))?), - |value| Ok(key.encrypt_tag_value(ProfileKey::prepare_input(value.as_bytes()))?), + |name| key.encrypt_tag_name(ProfileKey::prepare_input(name.as_bytes())), + |value| key.encrypt_tag_value(ProfileKey::prepare_input(value.as_bytes())), ); if let Some(filter) = enc.encode_query(&tag_query)? { let filter = replace_arg_placeholders::(&filter, (offset as i64) + 1); @@ -597,9 +614,9 @@ where Ok(query) } -pub fn init_keys<'a>( +pub fn init_keys( method: StoreKeyMethod, - pass_key: PassKey<'a>, + pass_key: PassKey<'_>, ) -> Result<(ProfileKey, Vec, StoreKey, String), Error> { if method == StoreKeyMethod::RawKey && pass_key.is_empty() { // disallow random key for a new database diff --git a/askar-storage/src/backend/mod.rs b/askar-storage/src/backend/mod.rs new file mode 100644 index 00000000..4e01a0a8 --- /dev/null +++ b/askar-storage/src/backend/mod.rs @@ -0,0 +1,219 @@ +//! Storage backends supported by aries-askar + +use std::fmt::Debug; + +use crate::{ + entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, + error::{Error, ErrorKind}, + future::BoxFuture, + protect::{PassKey, StoreKeyMethod}, +}; + +#[cfg(any(feature = "postgres", feature = "sqlite"))] +pub(crate) mod db_utils; + +#[cfg(feature = "postgres")] +#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))] +/// Postgres database support +pub mod postgres; + +#[cfg(feature = "sqlite")] +#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] +/// Sqlite database support +pub mod sqlite; + +/// Represents a generic backend implementation +pub trait Backend: Debug + Send + Sync { + /// The type of session managed by this backend + type Session: BackendSession + 'static; + + /// Create a new profile + fn create_profile(&self, name: Option) -> BoxFuture<'_, Result>; + + /// Get the name of the active profile + fn get_active_profile(&self) -> String; + + /// Get the name of the default profile + fn get_default_profile(&self) -> BoxFuture<'_, Result>; + + /// Set the the default profile + fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>>; + + /// Get the details of all store profiles + fn list_profiles(&self) -> BoxFuture<'_, Result, Error>>; + + /// Remove an existing profile + fn remove_profile(&self, name: String) -> BoxFuture<'_, Result>; + + /// Create a [`Scan`] against the store + fn scan( + &self, + profile: Option, + kind: Option, + category: Option, + tag_filter: Option, + offset: Option, + limit: Option, + ) -> BoxFuture<'_, Result, Error>>; + + /// Create a new session against the store + fn session(&self, profile: Option, transaction: bool) -> Result; + + /// Replace the wrapping key of the store + fn rekey( + &mut self, + method: StoreKeyMethod, + key: PassKey<'_>, + ) -> BoxFuture<'_, Result<(), Error>>; + + /// Close the store instance + fn close(&self) -> BoxFuture<'_, Result<(), Error>>; +} + +/// Create, open, or remove a generic backend implementation +pub trait ManageBackend<'a> { + /// The type of backend being managed + type Backend: Backend; + + /// Open an existing store + fn open_backend( + self, + method: Option, + pass_key: PassKey<'a>, + profile: Option, + ) -> BoxFuture<'a, Result>; + + /// Provision a new store + fn provision_backend( + self, + method: StoreKeyMethod, + pass_key: PassKey<'a>, + profile: Option, + recreate: bool, + ) -> BoxFuture<'a, Result>; + + /// Remove an existing store + fn remove_backend(self) -> BoxFuture<'a, Result>; +} + +/// Query from a generic backend implementation +pub trait BackendSession: Debug + Send { + /// Count the number of matching records in the store + fn count<'q>( + &'q mut self, + kind: Option, + category: Option<&'q str>, + tag_filter: Option, + ) -> BoxFuture<'q, Result>; + + /// Fetch a single record from the store by category and name + fn fetch<'q>( + &'q mut self, + kind: EntryKind, + category: &'q str, + name: &'q str, + for_update: bool, + ) -> BoxFuture<'q, Result, Error>>; + + /// Fetch all matching records from the store + fn fetch_all<'q>( + &'q mut self, + kind: Option, + category: Option<&'q str>, + tag_filter: Option, + limit: Option, + for_update: bool, + ) -> BoxFuture<'q, Result, Error>>; + + /// Insert scan results from another profile or store + fn import_scan<'q>( + &'q mut self, + mut scan: Scan<'q, Entry>, + ) -> BoxFuture<'_, Result<(), Error>> { + Box::pin(async move { + while let Some(rows) = scan.fetch_next().await? { + for entry in rows { + self.update( + entry.kind, + EntryOperation::Insert, + entry.category.as_str(), + entry.name.as_str(), + Some(entry.value.as_ref()), + Some(entry.tags.as_ref()), + None, + ) + .await?; + } + } + Ok(()) + }) + } + + /// Remove all matching records from the store + fn remove_all<'q>( + &'q mut self, + kind: Option, + category: Option<&'q str>, + tag_filter: Option, + ) -> BoxFuture<'q, Result>; + + /// Insert or replace a record in the store + #[allow(clippy::too_many_arguments)] + fn update<'q>( + &'q mut self, + kind: EntryKind, + operation: EntryOperation, + category: &'q str, + name: &'q str, + value: Option<&'q [u8]>, + tags: Option<&'q [EntryTag]>, + expiry_ms: Option, + ) -> BoxFuture<'q, Result<(), Error>>; + + /// Close the current store session + fn close(&mut self, commit: bool) -> BoxFuture<'_, Result<(), Error>>; +} + +/// Insert all records from a given profile +pub async fn copy_profile( + from_backend: &A, + to_backend: &B, + from_profile: &str, + to_profile: &str, +) -> Result<(), Error> { + let scan = from_backend + .scan(Some(from_profile.into()), None, None, None, None, None) + .await?; + if let Err(e) = to_backend.create_profile(Some(to_profile.into())).await { + if e.kind() != ErrorKind::Duplicate { + return Err(e); + } + } + let mut txn = to_backend.session(Some(to_profile.into()), true)?; + let count = txn.count(None, None, None).await?; + if count > 0 { + return Err(err_msg!(Input, "Profile targeted for import is not empty")); + } + txn.import_scan(scan).await?; + txn.close(true).await?; + Ok(()) +} + +/// Export an entire Store to another location +pub async fn copy_store<'m, B: Backend, M: ManageBackend<'m>>( + source: &B, + target: M, + key_method: StoreKeyMethod, + pass_key: PassKey<'m>, + recreate: bool, +) -> Result<(), Error> { + let default_profile = source.get_default_profile().await?; + let profile_ids = source.list_profiles().await?; + let target = target + .provision_backend(key_method, pass_key, Some(default_profile), recreate) + .await?; + for profile in profile_ids { + copy_profile(source, &target, &profile, &profile).await?; + } + Ok(()) +} diff --git a/src/backend/postgres/mod.rs b/askar-storage/src/backend/postgres/mod.rs similarity index 72% rename from src/backend/postgres/mod.rs rename to askar-storage/src/backend/postgres/mod.rs index 73faaa1f..4c3ca84e 100644 --- a/src/backend/postgres/mod.rs +++ b/askar-storage/src/backend/postgres/mod.rs @@ -15,87 +15,97 @@ use sqlx::{ Row, }; -use crate::{ - backend::{ - db_utils::{ - decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, - expiry_timestamp, extend_query, prepare_tags, random_profile_name, - replace_arg_placeholders, DbSession, DbSessionActive, DbSessionRef, EncScanEntry, - ExtDatabase, QueryParams, QueryPrepare, PAGE_SIZE, - }, - types::{Backend, QueryBackend}, +use super::{ + db_utils::{ + decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, expiry_timestamp, + extend_query, prepare_tags, random_profile_name, replace_arg_placeholders, DbSession, + DbSessionActive, DbSessionRef, DbSessionTxn, EncScanEntry, ExtDatabase, QueryParams, + QueryPrepare, PAGE_SIZE, }, + Backend, BackendSession, +}; +use crate::{ + entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, error::Error, future::{unblock, BoxFuture}, protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKeyMethod}, - storage::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, }; -const COUNT_QUERY: &'static str = "SELECT COUNT(*) FROM items i - WHERE profile_id = $1 AND kind = $2 AND category = $3 +mod provision; +pub use self::provision::PostgresStoreOptions; + +#[cfg(any(test, feature = "pg_test"))] +mod test_db; +#[cfg(any(test, feature = "pg_test"))] +pub use self::test_db::TestDB; + +const CONFIG_FETCH_QUERY: &str = "SELECT value FROM config WHERE name = $1"; +const CONFIG_UPDATE_QUERY: &str = "INSERT INTO config (name, value) VALUES ($1, $2) + ON CONFLICT(name) DO UPDATE SET value = excluded.value"; +const COUNT_QUERY: &str = "SELECT COUNT(*) FROM items i + WHERE profile_id = $1 + AND (kind = $2 OR $2 IS NULL) + AND (category = $3 OR $3 IS NULL) AND (expiry IS NULL OR expiry > CURRENT_TIMESTAMP)"; -const DELETE_QUERY: &'static str = "DELETE FROM items +const DELETE_QUERY: &str = "DELETE FROM items WHERE profile_id = $1 AND kind = $2 AND category = $3 AND name = $4"; -const FETCH_QUERY: &'static str = "SELECT id, value, +const FETCH_QUERY: &str = "SELECT id, value, (SELECT ARRAY_TO_STRING(ARRAY_AGG(it.plaintext || ':' || ENCODE(it.name, 'hex') || ':' || ENCODE(it.value, 'hex')), ',') FROM items_tags it WHERE it.item_id = i.id) tags FROM items i WHERE profile_id = $1 AND kind = $2 AND category = $3 AND name = $4 AND (expiry IS NULL OR expiry > CURRENT_TIMESTAMP)"; -const FETCH_QUERY_UPDATE: &'static str = "SELECT id, value, +const FETCH_QUERY_UPDATE: &str = "SELECT id, value, (SELECT ARRAY_TO_STRING(ARRAY_AGG(it.plaintext || ':' || ENCODE(it.name, 'hex') || ':' || ENCODE(it.value, 'hex')), ',') FROM items_tags it WHERE it.item_id = i.id) tags FROM items i WHERE profile_id = $1 AND kind = $2 AND category = $3 AND name = $4 AND (expiry IS NULL OR expiry > CURRENT_TIMESTAMP) FOR NO KEY UPDATE"; -const INSERT_QUERY: &'static str = - "INSERT INTO items (profile_id, kind, category, name, value, expiry) +const INSERT_QUERY: &str = "INSERT INTO items (profile_id, kind, category, name, value, expiry) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT DO NOTHING RETURNING id"; -const UPDATE_QUERY: &'static str = "UPDATE items SET value=$5, expiry=$6 +const UPDATE_QUERY: &str = "UPDATE items SET value=$5, expiry=$6 WHERE profile_id=$1 AND kind=$2 AND category=$3 AND name=$4 RETURNING id"; -const SCAN_QUERY: &'static str = "SELECT id, name, value, +const SCAN_QUERY: &str = "SELECT id, kind, category, name, value, (SELECT ARRAY_TO_STRING(ARRAY_AGG(it.plaintext || ':' || ENCODE(it.name, 'hex') || ':' || ENCODE(it.value, 'hex')), ',') FROM items_tags it WHERE it.item_id = i.id) tags - FROM items i WHERE profile_id = $1 AND kind = $2 AND category = $3 + FROM items i WHERE profile_id = $1 + AND (kind = $2 OR $2 IS NULL) + AND (category = $3 OR $3 IS NULL) AND (expiry IS NULL OR expiry > CURRENT_TIMESTAMP)"; -const DELETE_ALL_QUERY: &'static str = "DELETE FROM items i - WHERE i.profile_id = $1 AND i.kind = $2 AND i.category = $3"; -const TAG_INSERT_QUERY: &'static str = "INSERT INTO items_tags +const DELETE_ALL_QUERY: &str = "DELETE FROM items i + WHERE profile_id = $1 + AND (kind = $2 OR $2 IS NULL) + AND (category = $3 OR $3 IS NULL)"; +const TAG_INSERT_QUERY: &str = "INSERT INTO items_tags (item_id, name, value, plaintext) VALUES ($1, $2, $3, $4)"; -const TAG_DELETE_QUERY: &'static str = "DELETE FROM items_tags +const TAG_DELETE_QUERY: &str = "DELETE FROM items_tags WHERE item_id=$1"; -mod provision; -pub use provision::PostgresStoreOptions; - -#[cfg(any(test, feature = "pg_test"))] -pub mod test_db; - /// A PostgreSQL database store -pub struct PostgresStore { +pub struct PostgresBackend { conn_pool: PgPool, - default_profile: String, + active_profile: String, key_cache: Arc, host: String, name: String, } -impl PostgresStore { +impl PostgresBackend { pub(crate) fn new( conn_pool: PgPool, - default_profile: String, + active_profile: String, key_cache: KeyCache, host: String, name: String, ) -> Self { Self { conn_pool, - default_profile, + active_profile, key_cache: Arc::new(key_cache), host, name, @@ -103,7 +113,7 @@ impl PostgresStore { } } -impl Backend for PostgresStore { +impl Backend for PostgresBackend { type Session = DbSession; fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { @@ -123,7 +133,7 @@ impl Backend for PostgresStore { ) .bind(&name) .bind(enc_key) - .fetch_optional(&mut conn) + .fetch_optional(conn.as_mut()) .await? { self.key_cache @@ -136,8 +146,42 @@ impl Backend for PostgresStore { }) } - fn get_profile_name(&self) -> &str { - self.default_profile.as_str() + fn get_active_profile(&self) -> String { + self.active_profile.clone() + } + + fn get_default_profile(&self) -> BoxFuture<'_, Result> { + Box::pin(async move { + let mut conn = self.conn_pool.acquire().await?; + let profile: Option = sqlx::query_scalar(CONFIG_FETCH_QUERY) + .bind("default_profile") + .fetch_one(conn.as_mut()) + .await?; + Ok(profile.unwrap_or_default()) + }) + } + + fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>> { + Box::pin(async move { + let mut conn = self.conn_pool.acquire().await?; + sqlx::query(CONFIG_UPDATE_QUERY) + .bind("default_profile") + .bind(profile) + .execute(conn.as_mut()) + .await?; + Ok(()) + }) + } + + fn list_profiles(&self) -> BoxFuture<'_, Result, Error>> { + Box::pin(async move { + let mut conn = self.conn_pool.acquire().await?; + let rows = sqlx::query("SELECT name FROM profiles") + .fetch_all(conn.as_mut()) + .await?; + let names = rows.into_iter().flat_map(|r| r.try_get(0)).collect(); + Ok(names) + }) } fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { @@ -145,14 +189,14 @@ impl Backend for PostgresStore { let mut conn = self.conn_pool.acquire().await?; Ok(sqlx::query("DELETE FROM profiles WHERE name=$1") .bind(&name) - .execute(&mut conn) + .execute(conn.as_mut()) .await? .rows_affected() != 0) }) } - fn rekey_backend( + fn rekey( &mut self, method: StoreKeyMethod, pass_key: PassKey<'_>, @@ -162,7 +206,7 @@ impl Backend for PostgresStore { let (store_key, store_key_ref) = unblock(move || method.resolve(pass_key)).await?; let store_key = Arc::new(store_key); let mut txn = self.conn_pool.begin().await?; - let mut rows = sqlx::query("SELECT id, profile_key FROM profiles").fetch(&mut txn); + let mut rows = sqlx::query("SELECT id, profile_key FROM profiles").fetch(txn.as_mut()); let mut upd_keys = BTreeMap::>::new(); while let Some(row) = rows.next().await { let row = row?; @@ -181,7 +225,7 @@ impl Backend for PostgresStore { if sqlx::query("UPDATE profiles SET profile_key=$1 WHERE id=$2") .bind(key) .bind(pid) - .execute(&mut txn) + .execute(txn.as_mut()) .await? .rows_affected() != 1 @@ -191,7 +235,7 @@ impl Backend for PostgresStore { } if sqlx::query("UPDATE config SET value=$1 WHERE name='key'") .bind(store_key_ref.into_uri()) - .execute(&mut txn) + .execute(txn.as_mut()) .await? .rows_affected() != 1 @@ -207,8 +251,8 @@ impl Backend for PostgresStore { fn scan( &self, profile: Option, - kind: EntryKind, - category: String, + kind: Option, + category: Option, tag_filter: Option, offset: Option, limit: Option, @@ -216,7 +260,7 @@ impl Backend for PostgresStore { Box::pin(async move { let session = self.session(profile, false)?; let mut active = session.owned_ref(); - let (profile_id, key) = acquire_key(&mut *active).await?; + let (profile_id, key) = acquire_key(&mut active).await?; let scan = perform_scan( active, profile_id, @@ -241,7 +285,7 @@ impl Backend for PostgresStore { Ok(DbSession::new( self.conn_pool.clone(), self.key_cache.clone(), - profile.unwrap_or_else(|| self.default_profile.clone()), + profile.unwrap_or_else(|| self.active_profile.clone()), transaction, )) } @@ -254,43 +298,45 @@ impl Backend for PostgresStore { } } -impl Debug for PostgresStore { +impl Debug for PostgresBackend { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("PostgresStore") - .field("default_profile", &self.default_profile) + .field("active_profile", &self.active_profile) .field("host", &self.host) .field("name", &self.name) .finish() } } -impl QueryBackend for DbSession { +impl BackendSession for DbSession { fn count<'q>( &'q mut self, - kind: EntryKind, - category: &'q str, + kind: Option, + category: Option<&'q str>, tag_filter: Option, ) -> BoxFuture<'q, Result> { - let category = ProfileKey::prepare_input(category.as_bytes()); + let enc_category = category.map(|c| ProfileKey::prepare_input(c.as_bytes())); Box::pin(async move { let (profile_id, key) = acquire_key(&mut *self).await?; let mut params = QueryParams::new(); params.push(profile_id); - params.push(kind as i16); + params.push(kind.map(|k| k as i16)); let (enc_category, tag_filter) = unblock({ let params_len = params.len() + 1; // plus category move || { Result::<_, Error>::Ok(( - key.encrypt_entry_category(category)?, - encode_tag_filter::(tag_filter, &key, params_len)?, + enc_category + .map(|c| key.encrypt_entry_category(c)) + .transpose()?, + encode_tag_filter::(tag_filter, &key, params_len)?, )) } }) .await?; params.push(enc_category); let query = - extend_query::(COUNT_QUERY, &mut params, tag_filter, None, None)?; + extend_query::(COUNT_QUERY, &mut params, tag_filter, None, None)?; let mut active = acquire_session(&mut *self).await?; let count = sqlx::query_scalar_with(query.as_str(), params) .fetch_one(active.connection_mut()) @@ -324,7 +370,7 @@ impl QueryBackend for DbSession { }) .await?; let mut active = acquire_session(&mut *self).await?; - if let Some(row) = sqlx::query(if for_update && active.is_transaction() { + if let Some(row) = sqlx::query(if for_update && active.in_transaction() { FETCH_QUERY_UPDATE } else { FETCH_QUERY @@ -351,7 +397,7 @@ impl QueryBackend for DbSession { Result::<_, Error>::Ok((category, name, value, tags)) }) .await?; - Ok(Some(Entry::new(category, name, value, tags))) + Ok(Some(Entry::new(kind, category, name, value, tags))) } else { Ok(None) } @@ -360,17 +406,17 @@ impl QueryBackend for DbSession { fn fetch_all<'q>( &'q mut self, - kind: EntryKind, - category: &'q str, + kind: Option, + category: Option<&'q str>, tag_filter: Option, limit: Option, for_update: bool, ) -> BoxFuture<'q, Result, Error>> { - let category = category.to_string(); + let category = category.map(|c| c.to_string()); Box::pin(async move { - let for_update = for_update && self.is_transaction(); + let for_update = for_update && self.in_transaction(); let mut active = self.borrow_mut(); - let (profile_id, key) = acquire_key(&mut *active).await?; + let (profile_id, key) = acquire_key(&mut active).await?; let scan = perform_scan( active, profile_id, @@ -384,12 +430,8 @@ impl QueryBackend for DbSession { ); pin!(scan); let mut enc_rows = vec![]; - loop { - if let Some(rows) = scan.try_next().await? { - enc_rows.extend(rows) - } else { - break; - } + while let Some(rows) = scan.try_next().await? { + enc_rows.extend(rows) } unblock(move || decrypt_scan_batch(category, enc_rows, &key)).await }) @@ -397,29 +439,31 @@ impl QueryBackend for DbSession { fn remove_all<'q>( &'q mut self, - kind: EntryKind, - category: &'q str, + kind: Option, + category: Option<&'q str>, tag_filter: Option, ) -> BoxFuture<'q, Result> { - let category = ProfileKey::prepare_input(category.as_bytes()); + let enc_category = category.map(|c| ProfileKey::prepare_input(c.as_bytes())); Box::pin(async move { let (profile_id, key) = acquire_key(&mut *self).await?; let mut params = QueryParams::new(); params.push(profile_id); - params.push(kind as i16); + params.push(kind.map(|k| k as i16)); let (enc_category, tag_filter) = unblock({ let params_len = params.len() + 1; // plus category move || { Result::<_, Error>::Ok(( - key.encrypt_entry_category(category)?, - encode_tag_filter::(tag_filter, &key, params_len)?, + enc_category + .map(|c| key.encrypt_entry_category(c)) + .transpose()?, + encode_tag_filter::(tag_filter, &key, params_len)?, )) } }) .await?; params.push(enc_category); - let query = extend_query::( + let query = extend_query::( DELETE_ALL_QUERY, &mut params, tag_filter, @@ -451,7 +495,7 @@ impl QueryBackend for DbSession { match operation { op @ EntryOperation::Insert | op @ EntryOperation::Replace => { - let value = ProfileKey::prepare_input(value.unwrap()); + let value = ProfileKey::prepare_input(value.unwrap_or_default()); let tags = tags.map(prepare_tags); Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; @@ -496,19 +540,19 @@ impl QueryBackend for DbSession { }) .await?; let mut active = acquire_session(&mut *self).await?; - Ok(perform_remove(&mut active, kind, &enc_category, &enc_name, false).await?) + perform_remove(&mut active, kind, &enc_category, &enc_name, false).await }), } } - fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>> { - Box::pin(DbSession::close(self, commit)) + fn close(&mut self, commit: bool) -> BoxFuture<'_, Result<(), Error>> { + Box::pin(self.close(commit)) } } impl ExtDatabase for Postgres {} -impl QueryPrepare for PostgresStore { +impl QueryPrepare for PostgresBackend { type DB = Postgres; fn placeholder(index: i64) -> String { @@ -546,9 +590,9 @@ async fn acquire_key( } } -async fn acquire_session<'q>( - session: &'q mut DbSession, -) -> Result, Error> { +async fn acquire_session( + session: &'_ mut DbSession, +) -> Result, Error> { session.make_active(&resolve_profile_key).await } @@ -559,24 +603,23 @@ async fn resolve_profile_key( ) -> Result<(ProfileId, Arc), Error> { if let Some((pid, key)) = cache.get_profile(profile.as_str()).await { Ok((pid, key)) + } else if let Some(row) = sqlx::query("SELECT id, profile_key FROM profiles WHERE name=$1") + .bind(profile.as_str()) + .fetch_optional(conn.as_mut()) + .await? + { + let pid = row.try_get(0)?; + let key = Arc::new(cache.load_key(row.try_get(1)?).await?); + cache.add_profile(profile, pid, key.clone()).await; + Ok((pid, key)) } else { - if let Some(row) = sqlx::query("SELECT id, profile_key FROM profiles WHERE name=$1") - .bind(profile.as_str()) - .fetch_optional(conn) - .await? - { - let pid = row.try_get(0)?; - let key = Arc::new(cache.load_key(row.try_get(1)?).await?); - cache.add_profile(profile, pid, key.clone()).await; - Ok((pid, key)) - } else { - Err(err_msg!(NotFound, "Profile not found")) - } + Err(err_msg!(NotFound, "Profile not found")) } } -async fn perform_insert<'q>( - active: &mut DbSessionActive<'q, Postgres>, +#[allow(clippy::too_many_arguments)] +async fn perform_insert( + active: &mut DbSessionTxn<'_, Postgres>, kind: EntryKind, enc_category: &[u8], enc_name: &[u8], @@ -651,55 +694,59 @@ async fn perform_remove<'q>( } } -fn perform_scan<'q>( - mut active: DbSessionRef<'q, Postgres>, +#[allow(clippy::too_many_arguments)] +fn perform_scan( + mut active: DbSessionRef<'_, Postgres>, profile_id: ProfileId, key: Arc, - kind: EntryKind, - category: String, + kind: Option, + category: Option, tag_filter: Option, offset: Option, limit: Option, for_update: bool, -) -> impl Stream, Error>> + 'q { +) -> impl Stream, Error>> + '_ { try_stream! { let mut params = QueryParams::new(); params.push(profile_id); - params.push(kind as i16); + params.push(kind.map(|k| k as i16)); let (enc_category, tag_filter) = unblock({ let key = key.clone(); - let category = ProfileKey::prepare_input(category.as_bytes()); + let enc_category = category.map(|c| ProfileKey::prepare_input(c.as_bytes())); let params_len = params.len() + 1; // plus category move || { Result::<_, Error>::Ok(( - key.encrypt_entry_category(category)?, - encode_tag_filter::(tag_filter, &key, params_len)? + enc_category + .map(|c| key.encrypt_entry_category(c)) + .transpose()?, + encode_tag_filter::(tag_filter, &key, params_len)? )) } }).await?; params.push(enc_category); - let mut query = extend_query::(SCAN_QUERY, &mut params, tag_filter, offset, limit)?; + let mut query = extend_query::(SCAN_QUERY, &mut params, tag_filter, offset, limit)?; if for_update { query.push_str(" FOR NO KEY UPDATE"); } let mut batch = Vec::with_capacity(PAGE_SIZE); - let mut acquired = acquire_session(&mut *active).await?; + let mut acquired = acquire_session(&mut active).await?; let mut rows = sqlx::query_with(query.as_str(), params).fetch(acquired.connection_mut()); while let Some(row) = rows.try_next().await? { - let tags = row.try_get::, _>(3)?.map(String::into_bytes).unwrap_or_default(); + let tags = row.try_get::, _>(5)?.map(String::into_bytes).unwrap_or_default(); + let kind: i16 = row.try_get(1)?; + let kind = EntryKind::try_from(kind as usize)?; batch.push(EncScanEntry { - name: row.try_get(1)?, value: row.try_get(2)?, tags + kind, category: row.try_get(2)?, name: row.try_get(3)?, value: row.try_get(4)?, tags }); if batch.len() == PAGE_SIZE { yield batch.split_off(0); } } drop(rows); - drop(acquired); drop(active); - if batch.len() > 0 { + if !batch.is_empty() { yield batch; } } @@ -713,7 +760,7 @@ mod tests { #[test] fn postgres_simple_and_convert_args_works() { assert_eq!( - &replace_arg_placeholders::("This $$ is $10 a $$ string!", 3), + &replace_arg_placeholders::("This $$ is $10 a $$ string!", 3), "This $3 is $12 a $5 string!", ); } diff --git a/src/backend/postgres/provision.rs b/askar-storage/src/backend/postgres/provision.rs similarity index 88% rename from src/backend/postgres/provision.rs rename to askar-storage/src/backend/postgres/provision.rs index bb5cd544..c55cbd04 100644 --- a/src/backend/postgres/provision.rs +++ b/askar-storage/src/backend/postgres/provision.rs @@ -10,15 +10,15 @@ use sqlx::{ use crate::{ backend::{ db_utils::{init_keys, random_profile_name}, - types::ManageBackend, + ManageBackend, }, error::Error, future::{unblock, BoxFuture}, + options::IntoOptions, protect::{KeyCache, PassKey, ProfileId, StoreKeyMethod, StoreKeyReference}, - storage::{IntoOptions, Store}, }; -use super::PostgresStore; +use super::PostgresBackend; const DEFAULT_CONNECT_TIMEOUT: u64 = 30; const DEFAULT_IDLE_TIMEOUT: u64 = 300; @@ -89,7 +89,7 @@ impl PostgresStoreOptions { if path.len() < 2 { return Err(err_msg!(Input, "Missing database name")); } - let name = (&path[1..]).to_string(); + let name = path[1..].to_string(); if name.find(|c| c == '"' || c == '\0').is_some() { return Err(err_msg!( Input, @@ -115,11 +115,12 @@ impl PostgresStoreOptions { let mut conn_opts = PgConnectOptions::from_str(self.uri.as_str())?; #[cfg(feature = "log")] { - conn_opts.log_statements(log::LevelFilter::Debug); - conn_opts.log_slow_statements(log::LevelFilter::Debug, Default::default()); + conn_opts = conn_opts + .log_statements(log::LevelFilter::Debug) + .log_slow_statements(log::LevelFilter::Debug, Default::default()); } PgPoolOptions::default() - .connect_timeout(self.connect_timeout) + .acquire_timeout(self.connect_timeout) .idle_timeout(self.idle_timeout) .max_connections(self.max_connections) .min_connections(self.min_connections) @@ -162,7 +163,7 @@ impl PostgresStoreOptions { admin_conn.close().await?; Ok(self.pool().await?) } - Err(err) => return Err(err_msg!(Backend, "Error opening database").with_cause(err)), + Err(err) => Err(err_msg!(Backend, "Error opening database").with_cause(err)), } } @@ -171,57 +172,53 @@ impl PostgresStoreOptions { self, method: StoreKeyMethod, pass_key: PassKey<'_>, - profile: Option<&str>, + profile: Option, recreate: bool, - ) -> Result, Error> { + ) -> Result { let conn_pool = self.create_db_pool().await?; let mut txn = conn_pool.begin().await?; if recreate { // remove expected tables - reset_db(&mut *txn).await?; - } else { - if sqlx::query_scalar::<_, i64>( - "SELECT COUNT(*) FROM information_schema.tables + reset_db(&mut txn).await?; + } else if sqlx::query_scalar::<_, i64>( + "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_name='config'", + ) + .fetch_one(txn.as_mut()) + .await? + == 1 + { + // proceed to open, will fail if the version doesn't match + return open_db( + conn_pool, + Some(method), + pass_key, + profile, + self.host, + self.name, ) - .fetch_one(&mut txn) - .await? - == 1 - { - // proceed to open, will fail if the version doesn't match - return open_db( - conn_pool, - Some(method), - pass_key, - profile, - self.host, - self.name, - ) - .await; - } - // no 'config' table, assume empty database + .await; } + // else: no 'config' table, assume empty database let (profile_key, enc_profile_key, store_key, store_key_ref) = unblock({ let pass_key = pass_key.into_owned(); move || init_keys(method, pass_key) }) .await?; - let default_profile = profile - .map(str::to_string) - .unwrap_or_else(random_profile_name); + let default_profile = profile.unwrap_or_else(random_profile_name); let profile_id = init_db(txn, &default_profile, store_key_ref, enc_profile_key).await?; let mut key_cache = KeyCache::new(store_key); key_cache.add_profile_mut(default_profile.clone(), profile_id, profile_key); - Ok(Store::new(PostgresStore::new( + Ok(PostgresBackend::new( conn_pool, default_profile, key_cache, self.host, self.name, - ))) + )) } /// Open an existing Postgres store from this set of configuration options @@ -229,8 +226,8 @@ impl PostgresStoreOptions { self, method: Option, pass_key: PassKey<'_>, - profile: Option<&str>, - ) -> Result, Error> { + profile: Option, + ) -> Result { let pool = match self.pool().await { Ok(p) => Ok(p), Err(SqlxError::Database(db_err)) if db_err.code() == Some(Cow::Borrowed("3D000")) => { @@ -265,14 +262,14 @@ impl PostgresStoreOptions { } impl<'a> ManageBackend<'a> for PostgresStoreOptions { - type Store = Store; + type Backend = PostgresBackend; fn open_backend( self, method: Option, pass_key: PassKey<'_>, - profile: Option<&'a str>, - ) -> BoxFuture<'a, Result, Error>> { + profile: Option, + ) -> BoxFuture<'a, Result> { let pass_key = pass_key.into_owned(); Box::pin(self.open(method, pass_key, profile)) } @@ -281,9 +278,9 @@ impl<'a> ManageBackend<'a> for PostgresStoreOptions { self, method: StoreKeyMethod, pass_key: PassKey<'_>, - profile: Option<&'a str>, + profile: Option, recreate: bool, - ) -> BoxFuture<'a, Result, Error>> { + ) -> BoxFuture<'a, Result> { let pass_key = pass_key.into_owned(); Box::pin(self.provision(method, pass_key, profile, recreate)) } @@ -341,8 +338,8 @@ pub(crate) async fn init_db<'t>( ON DELETE CASCADE ON UPDATE CASCADE ); CREATE INDEX ix_items_tags_item_id ON items_tags(item_id); - CREATE INDEX ix_items_tags_name_enc ON items_tags(name, SUBSTR(value, 1, 12)) WHERE plaintext=0; - CREATE INDEX ix_items_tags_name_plain ON items_tags(name, value) WHERE plaintext=1; + CREATE INDEX ix_items_tags_name_enc ON items_tags(name, SUBSTR(value, 1, 12)) include (item_id) WHERE plaintext=0; + CREATE INDEX ix_items_tags_name_plain ON items_tags(name, value) include (item_id) WHERE plaintext=1; ", ) .await?; @@ -356,14 +353,14 @@ pub(crate) async fn init_db<'t>( .persistent(false) .bind(profile_name) .bind(store_key_ref) - .execute(&mut txn) + .execute(txn.as_mut()) .await?; let profile_id = sqlx::query_scalar("INSERT INTO profiles (name, profile_key) VALUES ($1, $2) RETURNING id") .bind(profile_name) .bind(enc_profile_key) - .fetch_one(&mut txn) + .fetch_one(txn.as_mut()) .await?; txn.commit().await?; @@ -388,10 +385,10 @@ pub(crate) async fn open_db( conn_pool: PgPool, method: Option, pass_key: PassKey<'_>, - profile: Option<&str>, + profile: Option, host: String, name: String, -) -> Result, Error> { +) -> Result { let mut conn = conn_pool.acquire().await?; let mut ver_ok = false; let mut default_profile: Option = None; @@ -401,7 +398,7 @@ pub(crate) async fn open_db( r#"SELECT name, value FROM config WHERE name IN ('default_profile', 'key', 'version')"#, ) - .fetch_all(&mut conn) + .fetch_all(conn.as_mut()) .await?; for row in config { match row.try_get(0)? { @@ -424,7 +421,6 @@ pub(crate) async fn open_db( return Err(err_msg!(Unsupported, "Store version not found")); } let profile = profile - .map(str::to_string) .or(default_profile) .ok_or_else(|| err_msg!(Unsupported, "Default store profile not found"))?; let store_key = if let Some(store_key_ref) = store_key_ref { @@ -446,15 +442,15 @@ pub(crate) async fn open_db( let row = sqlx::query("SELECT id, profile_key FROM profiles WHERE name = $1") .bind(&profile) - .fetch_one(&mut conn) + .fetch_one(conn.as_mut()) .await?; let profile_id = row.try_get(0)?; let profile_key = key_cache.load_key(row.try_get(1)?).await?; key_cache.add_profile_mut(profile.clone(), profile_id, profile_key); - Ok(Store::new(PostgresStore::new( + Ok(PostgresBackend::new( conn_pool, profile, key_cache, host, name, - ))) + )) } #[cfg(test)] diff --git a/src/backend/postgres/test_db.rs b/askar-storage/src/backend/postgres/test_db.rs similarity index 82% rename from src/backend/postgres/test_db.rs rename to askar-storage/src/backend/postgres/test_db.rs index 8dccf404..4890b99f 100644 --- a/src/backend/postgres/test_db.rs +++ b/askar-storage/src/backend/postgres/test_db.rs @@ -7,37 +7,40 @@ use sqlx::{ use std::time::Duration; use super::provision::{init_db, reset_db, PostgresStoreOptions}; -use super::PostgresStore; +use super::PostgresBackend; use crate::{ - backend::db_utils::{init_keys, random_profile_name}, + any::{into_any_backend, AnyBackend}, + backend::{ + db_utils::{init_keys, random_profile_name}, + Backend, + }, error::Error, future::{sleep, spawn_ok, timeout, unblock}, protect::{generate_raw_store_key, KeyCache, StoreKeyMethod}, - storage::Store, }; #[derive(Debug)] /// Postgres test database wrapper instance pub struct TestDB { - inst: Option>, + inst: Option, lock_txn: Option, } impl TestDB { + /// Access the backend instance + pub fn backend(&self) -> AnyBackend { + self.inst.clone().expect("Database not opened") + } + /// Provision a new instance of the test database. /// This method blocks until the database lock can be acquired. - pub async fn provision() -> Result { - let path = match std::env::var("POSTGRES_URL") { - Ok(p) if !p.is_empty() => p, - _ => panic!("'POSTGRES_URL' must be defined"), - }; - + pub async fn provision(db_url: &str) -> Result { let key = generate_raw_store_key(None)?; let (profile_key, enc_profile_key, store_key, store_key_ref) = unblock(|| init_keys(StoreKeyMethod::RawKey, key)).await?; let default_profile = random_profile_name(); - let opts = PostgresStoreOptions::new(path.as_str())?; + let opts = PostgresStoreOptions::new(db_url)?; let conn_pool = opts.create_db_pool().await?; // we hold a transaction open with a fixed advisory lock value. @@ -60,7 +63,7 @@ impl TestDB { let mut init_txn = conn_pool.begin().await?; // delete existing tables - reset_db(&mut *init_txn).await?; + reset_db(&mut init_txn).await?; // create tables and add default profile let profile_id = @@ -68,7 +71,7 @@ impl TestDB { let mut key_cache = KeyCache::new(store_key); key_cache.add_profile_mut(default_profile.clone(), profile_id, profile_key); - let inst = Store::new(PostgresStore::new( + let inst = into_any_backend(PostgresBackend::new( conn_pool, default_profile, key_cache, @@ -84,10 +87,12 @@ impl TestDB { async fn close_internal( mut lock_txn: Option, - mut inst: Option>, + mut inst: Option, ) -> Result<(), Error> { if let Some(lock_txn) = lock_txn.take() { - lock_txn.close().await?; + if let Err(e) = lock_txn.close().await { + warn!("Error closing lock transaction: {}", e); + } } if let Some(inst) = inst.take() { timeout(Duration::from_secs(30), inst.close()) @@ -109,14 +114,6 @@ impl TestDB { } } -impl std::ops::Deref for TestDB { - type Target = Store; - - fn deref(&self) -> &Self::Target { - self.inst.as_ref().unwrap() - } -} - impl Drop for TestDB { fn drop(&mut self) { if self.lock_txn.is_some() || self.inst.is_some() { diff --git a/src/backend/sqlite/mod.rs b/askar-storage/src/backend/sqlite/mod.rs similarity index 73% rename from src/backend/sqlite/mod.rs rename to askar-storage/src/backend/sqlite/mod.rs index 5d5265de..4cecc41f 100644 --- a/src/backend/sqlite/mod.rs +++ b/askar-storage/src/backend/sqlite/mod.rs @@ -14,92 +14,99 @@ use sqlx::{ Database, Error as SqlxError, Row, TransactionManager, }; -use crate::{ - backend::{ - db_utils::{ - decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, - expiry_timestamp, extend_query, prepare_tags, random_profile_name, DbSession, - DbSessionActive, DbSessionRef, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, - PAGE_SIZE, - }, - types::{Backend, QueryBackend}, +use super::{ + db_utils::{ + decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, expiry_timestamp, + extend_query, prepare_tags, random_profile_name, Connection, DbSession, DbSessionActive, + DbSessionRef, DbSessionTxn, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, + PAGE_SIZE, }, + Backend, BackendSession, +}; +use crate::{ + entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, error::Error, future::{unblock, BoxFuture}, protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKeyMethod}, - storage::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, }; mod provision; pub use provision::SqliteStoreOptions; -const COUNT_QUERY: &'static str = "SELECT COUNT(*) FROM items i - WHERE profile_id = ?1 AND kind = ?2 AND category = ?3 +const CONFIG_FETCH_QUERY: &str = "SELECT value FROM config WHERE name = ?1"; +const CONFIG_UPDATE_QUERY: &str = "INSERT OR REPLACE INTO config (name, value) VALUES (?1, ?2)"; +const COUNT_QUERY: &str = "SELECT COUNT(*) FROM items i + WHERE profile_id = ?1 + AND (kind = ?2 OR ?2 IS NULL) + AND (category = ?3 OR ?3 IS NULL) AND (expiry IS NULL OR expiry > DATETIME('now'))"; -const DELETE_QUERY: &'static str = "DELETE FROM items +const DELETE_QUERY: &str = "DELETE FROM items WHERE profile_id = ?1 AND kind = ?2 AND category = ?3 AND name = ?4"; -const FETCH_QUERY: &'static str = "SELECT i.id, i.value, +const FETCH_QUERY: &str = "SELECT i.id, i.value, (SELECT GROUP_CONCAT(it.plaintext || ':' || HEX(it.name) || ':' || HEX(it.value)) FROM items_tags it WHERE it.item_id = i.id) AS tags FROM items i WHERE i.profile_id = ?1 AND i.kind = ?2 AND i.category = ?3 AND i.name = ?4 AND (i.expiry IS NULL OR i.expiry > DATETIME('now'))"; -const INSERT_QUERY: &'static str = +const INSERT_QUERY: &str = "INSERT OR IGNORE INTO items (profile_id, kind, category, name, value, expiry) VALUES (?1, ?2, ?3, ?4, ?5, ?6)"; -const UPDATE_QUERY: &'static str = - "UPDATE items SET value=?5, expiry=?6 WHERE profile_id=?1 AND kind=?2 +const UPDATE_QUERY: &str = "UPDATE items SET value=?5, expiry=?6 WHERE profile_id=?1 AND kind=?2 AND category=?3 AND name=?4 RETURNING id"; -const SCAN_QUERY: &'static str = "SELECT i.id, i.name, i.value, +const SCAN_QUERY: &str = "SELECT i.id, i.kind, i.category, i.name, i.value, (SELECT GROUP_CONCAT(it.plaintext || ':' || HEX(it.name) || ':' || HEX(it.value)) FROM items_tags it WHERE it.item_id = i.id) AS tags - FROM items i WHERE i.profile_id = ?1 AND i.kind = ?2 AND i.category = ?3 + FROM items i WHERE i.profile_id = ?1 + AND (i.kind = ?2 OR ?2 IS NULL) + AND (i.category = ?3 OR ?3 IS NULL) AND (i.expiry IS NULL OR i.expiry > DATETIME('now'))"; -const DELETE_ALL_QUERY: &'static str = "DELETE FROM items AS i - WHERE i.profile_id = ?1 AND i.kind = ?2 AND i.category = ?3"; -const TAG_INSERT_QUERY: &'static str = "INSERT INTO items_tags +const DELETE_ALL_QUERY: &str = "DELETE FROM items AS i + WHERE i.profile_id = ?1 + AND (i.kind = ?2 OR ?2 IS NULL) + AND (i.category = ?3 OR ?3 IS NULL)"; +const TAG_INSERT_QUERY: &str = "INSERT INTO items_tags (item_id, name, value, plaintext) VALUES (?1, ?2, ?3, ?4)"; -const TAG_DELETE_QUERY: &'static str = "DELETE FROM items_tags +const TAG_DELETE_QUERY: &str = "DELETE FROM items_tags WHERE item_id=?1"; /// A Sqlite database store -pub struct SqliteStore { +pub struct SqliteBackend { conn_pool: SqlitePool, - default_profile: String, + active_profile: String, key_cache: Arc, path: String, } -impl SqliteStore { +impl SqliteBackend { pub(crate) fn new( conn_pool: SqlitePool, - default_profile: String, + active_profile: String, key_cache: KeyCache, path: String, ) -> Self { Self { conn_pool, - default_profile, + active_profile, key_cache: Arc::new(key_cache), path, } } } -impl Debug for SqliteStore { +impl Debug for SqliteBackend { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("SqliteStore") - .field("default_profile", &self.default_profile) + .field("active_profile", &self.active_profile) .field("path", &self.path) .finish() } } -impl QueryPrepare for SqliteStore { +impl QueryPrepare for SqliteBackend { type DB = Sqlite; } -impl Backend for SqliteStore { +impl Backend for SqliteBackend { type Session = DbSession; fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { @@ -117,7 +124,7 @@ impl Backend for SqliteStore { sqlx::query("INSERT OR IGNORE INTO profiles (name, profile_key) VALUES (?1, ?2)") .bind(&name) .bind(enc_key) - .execute(&mut conn) + .execute(conn.as_mut()) .await?; if done.rows_affected() == 0 { return Err(err_msg!(Duplicate, "Duplicate profile name")); @@ -133,8 +140,42 @@ impl Backend for SqliteStore { }) } - fn get_profile_name(&self) -> &str { - self.default_profile.as_str() + fn get_active_profile(&self) -> String { + self.active_profile.clone() + } + + fn get_default_profile(&self) -> BoxFuture<'_, Result> { + Box::pin(async move { + let mut conn = self.conn_pool.acquire().await?; + let profile: Option = sqlx::query_scalar(CONFIG_FETCH_QUERY) + .bind("default_profile") + .fetch_one(conn.as_mut()) + .await?; + Ok(profile.unwrap_or_default()) + }) + } + + fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>> { + Box::pin(async move { + let mut conn = self.conn_pool.acquire().await?; + sqlx::query(CONFIG_UPDATE_QUERY) + .bind("default_profile") + .bind(profile) + .execute(conn.as_mut()) + .await?; + Ok(()) + }) + } + + fn list_profiles(&self) -> BoxFuture<'_, Result, Error>> { + Box::pin(async move { + let mut conn = self.conn_pool.acquire().await?; + let rows = sqlx::query("SELECT name FROM profiles") + .fetch_all(conn.as_mut()) + .await?; + let names = rows.into_iter().flat_map(|r| r.try_get(0)).collect(); + Ok(names) + }) } fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { @@ -142,14 +183,14 @@ impl Backend for SqliteStore { let mut conn = self.conn_pool.acquire().await?; Ok(sqlx::query("DELETE FROM profiles WHERE name=?") .bind(&name) - .execute(&mut conn) + .execute(conn.as_mut()) .await? .rows_affected() != 0) }) } - fn rekey_backend( + fn rekey( &mut self, method: StoreKeyMethod, pass_key: PassKey<'_>, @@ -159,7 +200,7 @@ impl Backend for SqliteStore { let (store_key, store_key_ref) = unblock(move || method.resolve(pass_key)).await?; let store_key = Arc::new(store_key); let mut txn = self.conn_pool.begin().await?; - let mut rows = sqlx::query("SELECT id, profile_key FROM profiles").fetch(&mut txn); + let mut rows = sqlx::query("SELECT id, profile_key FROM profiles").fetch(txn.as_mut()); let mut upd_keys = BTreeMap::>::new(); while let Some(row) = rows.next().await { let row = row?; @@ -178,7 +219,7 @@ impl Backend for SqliteStore { if sqlx::query("UPDATE profiles SET profile_key=?1 WHERE id=?2") .bind(key) .bind(pid) - .execute(&mut txn) + .execute(txn.as_mut()) .await? .rows_affected() != 1 @@ -188,7 +229,7 @@ impl Backend for SqliteStore { } if sqlx::query("UPDATE config SET value=?1 WHERE name='key'") .bind(store_key_ref.into_uri()) - .execute(&mut txn) + .execute(txn.as_mut()) .await? .rows_affected() != 1 @@ -204,8 +245,8 @@ impl Backend for SqliteStore { fn scan( &self, profile: Option, - kind: EntryKind, - category: String, + kind: Option, + category: Option, tag_filter: Option, offset: Option, limit: Option, @@ -213,7 +254,7 @@ impl Backend for SqliteStore { Box::pin(async move { let session = self.session(profile, false)?; let mut active = session.owned_ref(); - let (profile_id, key) = acquire_key(&mut *active).await?; + let (profile_id, key) = acquire_key(&mut active).await?; let scan = perform_scan( active, profile_id, @@ -237,7 +278,7 @@ impl Backend for SqliteStore { Ok(DbSession::new( self.conn_pool.clone(), self.key_cache.clone(), - profile.unwrap_or_else(|| self.default_profile.clone()), + profile.unwrap_or_else(|| self.active_profile.clone()), transaction, )) } @@ -250,33 +291,35 @@ impl Backend for SqliteStore { } } -impl QueryBackend for DbSession { +impl BackendSession for DbSession { fn count<'q>( &'q mut self, - kind: EntryKind, - category: &'q str, + kind: Option, + category: Option<&'q str>, tag_filter: Option, ) -> BoxFuture<'q, Result> { - let category = ProfileKey::prepare_input(category.as_bytes()); + let enc_category = category.map(|c| ProfileKey::prepare_input(c.as_bytes())); Box::pin(async move { let (profile_id, key) = acquire_key(&mut *self).await?; let mut params = QueryParams::new(); params.push(profile_id); - params.push(kind as i16); + params.push(kind.map(|k| k as i16)); let (enc_category, tag_filter) = unblock({ let params_len = params.len() + 1; // plus category move || { Result::<_, Error>::Ok(( - key.encrypt_entry_category(category)?, - encode_tag_filter::(tag_filter, &key, params_len)?, + enc_category + .map(|c| key.encrypt_entry_category(c)) + .transpose()?, + encode_tag_filter::(tag_filter, &key, params_len)?, )) } }) .await?; params.push(enc_category); let query = - extend_query::(COUNT_QUERY, &mut params, tag_filter, None, None)?; + extend_query::(COUNT_QUERY, &mut params, tag_filter, None, None)?; let mut active = acquire_session(&mut *self).await?; let count = sqlx::query_scalar_with(query.as_str(), params) .fetch_one(active.connection_mut()) @@ -328,7 +371,7 @@ impl QueryBackend for DbSession { Result::<_, Error>::Ok((category, name, value, tags)) }) .await?; - Ok(Some(Entry::new(category, name, value, tags))) + Ok(Some(Entry::new(kind, category, name, value, tags))) } else { Ok(None) } @@ -337,16 +380,16 @@ impl QueryBackend for DbSession { fn fetch_all<'q>( &'q mut self, - kind: EntryKind, - category: &'q str, + kind: Option, + category: Option<&'q str>, tag_filter: Option, limit: Option, _for_update: bool, ) -> BoxFuture<'q, Result, Error>> { - let category = category.to_string(); + let category = category.map(|c| c.to_string()); Box::pin(async move { let mut active = self.borrow_mut(); - let (profile_id, key) = acquire_key(&mut *active).await?; + let (profile_id, key) = acquire_key(&mut active).await?; let scan = perform_scan( active, profile_id, @@ -359,12 +402,8 @@ impl QueryBackend for DbSession { ); pin!(scan); let mut enc_rows = vec![]; - loop { - if let Some(rows) = scan.try_next().await? { - enc_rows.extend(rows) - } else { - break; - } + while let Some(rows) = scan.try_next().await? { + enc_rows.extend(rows) } unblock(move || decrypt_scan_batch(category, enc_rows, &key)).await }) @@ -372,30 +411,37 @@ impl QueryBackend for DbSession { fn remove_all<'q>( &'q mut self, - kind: EntryKind, - category: &'q str, + kind: Option, + category: Option<&'q str>, tag_filter: Option, ) -> BoxFuture<'q, Result> { - let category = ProfileKey::prepare_input(category.as_bytes()); + let enc_category = category.map(|c| ProfileKey::prepare_input(c.as_bytes())); Box::pin(async move { let (profile_id, key) = acquire_key(&mut *self).await?; let mut params = QueryParams::new(); params.push(profile_id); - params.push(kind as i16); + params.push(kind.map(|k| k as i16)); let (enc_category, tag_filter) = unblock({ let params_len = params.len() + 1; // plus category move || { Result::<_, Error>::Ok(( - key.encrypt_entry_category(category)?, - encode_tag_filter::(tag_filter, &key, params_len)?, + enc_category + .map(|c| key.encrypt_entry_category(c)) + .transpose()?, + encode_tag_filter::(tag_filter, &key, params_len)?, )) } }) .await?; params.push(enc_category); - let query = - extend_query::(DELETE_ALL_QUERY, &mut params, tag_filter, None, None)?; + let query = extend_query::( + DELETE_ALL_QUERY, + &mut params, + tag_filter, + None, + None, + )?; let mut active = acquire_session(&mut *self).await?; let removed = sqlx::query_with(query.as_str(), params) @@ -421,7 +467,7 @@ impl QueryBackend for DbSession { match operation { op @ EntryOperation::Insert | op @ EntryOperation::Replace => { - let value = ProfileKey::prepare_input(value.unwrap()); + let value = ProfileKey::prepare_input(value.unwrap_or_default()); let tags = tags.map(prepare_tags); Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; @@ -466,30 +512,30 @@ impl QueryBackend for DbSession { }) .await?; let mut active = acquire_session(&mut *self).await?; - Ok(perform_remove(&mut active, kind, &enc_category, &enc_name, false).await?) + perform_remove(&mut active, kind, &enc_category, &enc_name, false).await }), } } - fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>> { - Box::pin(DbSession::close(self, commit)) + fn close(&mut self, commit: bool) -> BoxFuture<'_, Result<(), Error>> { + Box::pin(self.close(commit)) } } impl ExtDatabase for Sqlite { fn start_transaction( - conn: &mut PoolConnection, + conn: &mut Connection, nested: bool, ) -> BoxFuture<'_, std::result::Result<(), SqlxError>> { // FIXME - this is a horrible workaround because there is currently // no good way to start an immediate transaction with sqlx. Without this // adjustment, updates will run into 'database is locked' errors. Box::pin(async move { - ::TransactionManager::begin(&mut *conn).await?; + ::TransactionManager::begin(conn).await?; if !nested { // a no-op write transaction sqlx::query("DELETE FROM config WHERE 0") - .execute(&mut *conn) + .execute(conn) .await?; } Ok(()) @@ -508,9 +554,9 @@ async fn acquire_key( } } -async fn acquire_session<'q>( - session: &'q mut DbSession, -) -> Result, Error> { +async fn acquire_session( + session: &mut DbSession, +) -> Result, Error> { session.make_active(&resolve_profile_key).await } @@ -521,24 +567,23 @@ async fn resolve_profile_key( ) -> Result<(ProfileId, Arc), Error> { if let Some((pid, key)) = cache.get_profile(profile.as_str()).await { Ok((pid, key)) + } else if let Some(row) = sqlx::query("SELECT id, profile_key FROM profiles WHERE name=?1") + .bind(profile.as_str()) + .fetch_optional(conn.as_mut()) + .await? + { + let pid = row.try_get(0)?; + let key = Arc::new(cache.load_key(row.try_get(1)?).await?); + cache.add_profile(profile, pid, key.clone()).await; + Ok((pid, key)) } else { - if let Some(row) = sqlx::query("SELECT id, profile_key FROM profiles WHERE name=?1") - .bind(profile.as_str()) - .fetch_optional(conn) - .await? - { - let pid = row.try_get(0)?; - let key = Arc::new(cache.load_key(row.try_get(1)?).await?); - cache.add_profile(profile, pid, key.clone()).await; - Ok((pid, key)) - } else { - Err(err_msg!(NotFound, "Profile not found")) - } + Err(err_msg!(NotFound, "Profile not found")) } } -async fn perform_insert<'q>( - active: &mut DbSessionActive<'q, Sqlite>, +#[allow(clippy::too_many_arguments)] +async fn perform_insert( + active: &mut DbSessionTxn<'_, Sqlite>, kind: EntryKind, enc_category: &[u8], enc_name: &[u8], @@ -616,48 +661,50 @@ async fn perform_remove<'q>( } } -fn perform_scan<'q>( - mut active: DbSessionRef<'q, Sqlite>, +#[allow(clippy::too_many_arguments)] +fn perform_scan( + mut active: DbSessionRef<'_, Sqlite>, profile_id: ProfileId, key: Arc, - kind: EntryKind, - category: String, + kind: Option, + category: Option, tag_filter: Option, offset: Option, limit: Option, -) -> impl Stream, Error>> + 'q { +) -> impl Stream, Error>> + '_ { try_stream! { let mut params = QueryParams::new(); params.push(profile_id); - params.push(kind as i16); + params.push(kind.map(|k| k as i16)); let (enc_category, tag_filter) = unblock({ let key = key.clone(); - let category = ProfileKey::prepare_input(category.as_bytes()); + let enc_category = category.as_ref().map(|c| ProfileKey::prepare_input(c.as_bytes())); let params_len = params.len() + 1; // plus category move || { Result::<_, Error>::Ok(( - key.encrypt_entry_category(category)?, - encode_tag_filter::(tag_filter, &key, params_len)? + enc_category.map(|c| key.encrypt_entry_category(c)).transpose()?, + encode_tag_filter::(tag_filter, &key, params_len)? )) } }).await?; params.push(enc_category); - let query = extend_query::(SCAN_QUERY, &mut params, tag_filter, offset, limit)?; + let query = extend_query::(SCAN_QUERY, &mut params, tag_filter, offset, limit)?; let mut batch = Vec::with_capacity(PAGE_SIZE); - let mut acquired = acquire_session(&mut *active).await?; + let mut acquired = acquire_session(&mut active).await?; let mut rows = sqlx::query_with(query.as_str(), params).fetch(acquired.connection_mut()); while let Some(row) = rows.try_next().await? { + let kind: u32 = row.try_get(1)?; + let kind = EntryKind::try_from(kind as usize)?; batch.push(EncScanEntry { - name: row.try_get(1)?, value: row.try_get(2)?, tags: row.try_get(3)? + kind, category: row.try_get(2)?, name: row.try_get(3)?, value: row.try_get(4)?, tags: row.try_get(5)? }); if batch.len() == PAGE_SIZE { yield batch.split_off(0); } } drop(rows); - drop(acquired); drop(active); if !batch.is_empty() { @@ -683,7 +730,7 @@ mod tests { let ts = expiry_timestamp(1000).unwrap(); let check = sqlx::query("SELECT datetime('now'), ?1, ?1 > datetime('now')") .bind(ts) - .fetch_one(&db.inner().conn_pool) + .fetch_one(&db.conn_pool) .await?; let now: String = check.try_get(0)?; let cmp_ts: String = check.try_get(1)?; @@ -699,11 +746,11 @@ mod tests { #[test] fn sqlite_query_placeholders() { assert_eq!( - &replace_arg_placeholders::("This $$ is $10 a $$ string!", 3), + &replace_arg_placeholders::("This $$ is $10 a $$ string!", 3), "This ?3 is ?12 a ?5 string!", ); assert_eq!( - &replace_arg_placeholders::("This $a is a string!", 1), + &replace_arg_placeholders::("This $a is a string!", 1), "This $a is a string!", ); } diff --git a/src/backend/sqlite/provision.rs b/askar-storage/src/backend/sqlite/provision.rs similarity index 84% rename from src/backend/sqlite/provision.rs rename to askar-storage/src/backend/sqlite/provision.rs index d57b9e2b..1792f781 100644 --- a/src/backend/sqlite/provision.rs +++ b/askar-storage/src/backend/sqlite/provision.rs @@ -1,5 +1,6 @@ use std::{ - borrow::Cow, fs::remove_file, io::ErrorKind as IoErrorKind, str::FromStr, time::Duration, + borrow::Cow, fs::remove_file, io::ErrorKind as IoErrorKind, str::FromStr, + thread::available_parallelism, time::Duration, }; use sqlx::{ @@ -10,23 +11,24 @@ use sqlx::{ ConnectOptions, Error as SqlxError, Row, }; -use super::SqliteStore; +use super::SqliteBackend; use crate::{ backend::{ db_utils::{init_keys, random_profile_name}, - types::ManageBackend, + ManageBackend, }, error::Error, future::{unblock, BoxFuture}, + options::{IntoOptions, Options}, protect::{KeyCache, PassKey, StoreKeyMethod, StoreKeyReference}, - storage::{IntoOptions, Options, Store}, }; -const DEFAULT_MIN_CONNECTIONS: u32 = 1; +const DEFAULT_MIN_CONNECTIONS: usize = 1; +const DEFAULT_LOWER_MAX_CONNECTIONS: usize = 2; +const DEFAULT_UPPER_MAX_CONNECTIONS: usize = 8; const DEFAULT_BUSY_TIMEOUT: Duration = Duration::from_secs(5); const DEFAULT_JOURNAL_MODE: SqliteJournalMode = SqliteJournalMode::Wal; const DEFAULT_LOCKING_MODE: SqliteLockingMode = SqliteLockingMode::Normal; -const DEFAULT_SHARED_CACHE: bool = true; const DEFAULT_SYNCHRONOUS: SqliteSynchronous = SqliteSynchronous::Full; /// Configuration options for Sqlite stores @@ -53,6 +55,10 @@ impl SqliteStoreOptions { /// Initialize `SqliteStoreOptions` from a generic set of options pub fn new<'a>(options: impl IntoOptions<'a>) -> Result { let mut opts = options.into_options()?; + let mut path = opts.host.to_string(); + path.push_str(&opts.path); + let in_memory = path == ":memory:"; + let busy_timeout = if let Some(timeout) = opts.query.remove("busy_timeout") { Duration::from_millis( timeout @@ -67,14 +73,21 @@ impl SqliteStoreOptions { .parse() .map_err(err_map!(Input, "Error parsing 'max_connections' parameter"))? } else { - num_cpus::get() as u32 + available_parallelism() + .map_err(err_map!( + Unexpected, + "Error determining available parallelism" + ))? + .get() + .max(DEFAULT_LOWER_MAX_CONNECTIONS) + .min(DEFAULT_UPPER_MAX_CONNECTIONS) as u32 }; let min_connections = if let Some(min_conn) = opts.query.remove("min_connections") { min_conn .parse() .map_err(err_map!(Input, "Error parsing 'min_connections' parameter"))? } else { - DEFAULT_MIN_CONNECTIONS + DEFAULT_MIN_CONNECTIONS as u32 }; let journal_mode = if let Some(mode) = opts.query.remove("journal_mode") { SqliteJournalMode::from_str(&mode) @@ -91,7 +104,7 @@ impl SqliteStoreOptions { let shared_cache = if let Some(cache) = opts.query.remove("cache") { cache.eq_ignore_ascii_case("shared") } else { - DEFAULT_SHARED_CACHE + in_memory }; let synchronous = if let Some(sync) = opts.query.remove("synchronous") { SqliteSynchronous::from_str(&sync) @@ -100,10 +113,8 @@ impl SqliteStoreOptions { DEFAULT_SYNCHRONOUS }; - let mut path = opts.host.to_string(); - path.push_str(&*opts.path); Ok(Self { - in_memory: path == ":memory:", + in_memory, path, busy_timeout, max_connections, @@ -127,8 +138,9 @@ impl SqliteStoreOptions { .synchronous(self.synchronous); #[cfg(feature = "log")] { - conn_opts.log_statements(log::LevelFilter::Debug); - conn_opts.log_slow_statements(log::LevelFilter::Debug, Default::default()); + conn_opts = conn_opts + .log_statements(log::LevelFilter::Debug) + .log_slow_statements(log::LevelFilter::Debug, Default::default()); } SqlitePoolOptions::default() // maintains at least 1 connection. @@ -146,45 +158,42 @@ impl SqliteStoreOptions { self, method: StoreKeyMethod, pass_key: PassKey<'_>, - profile: Option<&'_ str>, + profile: Option, recreate: bool, - ) -> Result, Error> { + ) -> Result { if recreate && !self.in_memory { try_remove_file(self.path.to_string()).await?; } let conn_pool = self.pool(true).await?; - if !recreate { - if sqlx::query_scalar::<_, i64>( + if !recreate + && sqlx::query_scalar::<_, i64>( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='config'", ) .fetch_one(&conn_pool) .await? == 1 - { - return open_db( - conn_pool, - Some(method), - pass_key, - profile, - self.path.to_string(), - ) - .await; - } - // no 'config' table, assume empty database + { + return open_db( + conn_pool, + Some(method), + pass_key, + profile, + self.path.to_string(), + ) + .await; } + // else: no 'config' table, assume empty database - let default_profile = profile - .map(str::to_string) - .unwrap_or_else(random_profile_name); + let default_profile = profile.unwrap_or_else(random_profile_name); let key_cache = init_db(&conn_pool, &default_profile, method, pass_key).await?; - Ok(Store::new(SqliteStore::new( + Ok(SqliteBackend::new( conn_pool, default_profile, key_cache, self.path.to_string(), - ))) + )) } /// Open an existing Sqlite store from this set of configuration options @@ -192,8 +201,8 @@ impl SqliteStoreOptions { self, method: Option, pass_key: PassKey<'_>, - profile: Option<&'_ str>, - ) -> Result, Error> { + profile: Option, + ) -> Result { let conn_pool = match self.pool(false).await { Ok(pool) => Ok(pool), Err(SqlxError::Database(db_err)) => { @@ -209,7 +218,7 @@ impl SqliteStoreOptions { } Err(err) => Err(err.into()), }?; - Ok(open_db(conn_pool, method, pass_key, profile, self.path.to_string()).await?) + open_db(conn_pool, method, pass_key, profile, self.path.to_string()).await } /// Remove the Sqlite store defined by these configuration options @@ -223,28 +232,28 @@ impl SqliteStoreOptions { /// Default options for an in-memory Sqlite store pub fn in_memory() -> Self { - let mut opts = Options::default(); - opts.host = Cow::Borrowed(":memory:"); - Self::new(opts).unwrap() + Self::from_path(":memory:") } /// Default options for a given Sqlite database path pub fn from_path(path: &str) -> Self { - let mut opts = Options::default(); - opts.host = Cow::Borrowed(path); + let opts = Options { + host: Cow::Borrowed(path), + ..Default::default() + }; Self::new(opts).unwrap() } } impl<'a> ManageBackend<'a> for SqliteStoreOptions { - type Store = Store; + type Backend = SqliteBackend; fn open_backend( self, method: Option, pass_key: PassKey<'a>, - profile: Option<&'a str>, - ) -> BoxFuture<'a, Result, Error>> { + profile: Option, + ) -> BoxFuture<'a, Result> { Box::pin(self.open(method, pass_key, profile)) } @@ -252,9 +261,9 @@ impl<'a> ManageBackend<'a> for SqliteStoreOptions { self, method: StoreKeyMethod, pass_key: PassKey<'a>, - profile: Option<&'a str>, + profile: Option, recreate: bool, - ) -> BoxFuture<'a, Result, Error>> { + ) -> BoxFuture<'a, Result> { Box::pin(self.provision(method, pass_key, profile, recreate)) } @@ -337,7 +346,7 @@ async fn init_db( .bind(profile_name) .bind(store_key_ref) .bind(enc_profile_key) - .execute(&mut conn) + .execute(conn.as_mut()) .await?; let mut key_cache = KeyCache::new(store_key); @@ -345,7 +354,7 @@ async fn init_db( let row = sqlx::query("SELECT id FROM profiles WHERE name = ?1") .persistent(false) .bind(profile_name) - .fetch_one(&mut conn) + .fetch_one(conn.as_mut()) .await?; key_cache.add_profile_mut(profile_name.to_string(), row.try_get(0)?, profile_key); @@ -356,9 +365,9 @@ async fn open_db( conn_pool: SqlitePool, method: Option, pass_key: PassKey<'_>, - profile: Option<&str>, + profile: Option, path: String, -) -> Result, Error> { +) -> Result { let mut conn = conn_pool.acquire().await?; let mut ver_ok = false; let mut default_profile: Option = None; @@ -368,7 +377,7 @@ async fn open_db( r#"SELECT name, value FROM config WHERE name IN ("default_profile", "key", "version")"#, ) - .fetch_all(&mut conn) + .fetch_all(conn.as_mut()) .await?; for row in config { match row.try_get(0)? { @@ -391,7 +400,6 @@ async fn open_db( return Err(err_msg!(Unsupported, "Store version not found")); } let profile = profile - .map(str::to_string) .or(default_profile) .ok_or_else(|| err_msg!(Unsupported, "Default store profile not found"))?; let store_key = if let Some(store_key_ref) = store_key_ref { @@ -413,15 +421,13 @@ async fn open_db( let row = sqlx::query("SELECT id, profile_key FROM profiles WHERE name = ?1") .bind(&profile) - .fetch_one(&mut conn) + .fetch_one(conn.as_mut()) .await?; let profile_id = row.try_get(0)?; let profile_key = key_cache.load_key(row.try_get(1)?).await?; key_cache.add_profile_mut(profile.clone(), profile_id, profile_key); - Ok(Store::new(SqliteStore::new( - conn_pool, profile, key_cache, path, - ))) + Ok(SqliteBackend::new(conn_pool, profile, key_cache, path)) } async fn try_remove_file(path: String) -> Result { diff --git a/src/storage/entry.rs b/askar-storage/src/entry.rs similarity index 55% rename from src/storage/entry.rs rename to askar-storage/src/entry.rs index c5d4629d..13d02f35 100644 --- a/src/storage/entry.rs +++ b/askar-storage/src/entry.rs @@ -1,34 +1,33 @@ +//! Entry type definitions + use std::{ - borrow::Cow, fmt::{self, Debug, Formatter}, pin::Pin, str::FromStr, }; use futures_lite::stream::{Stream, StreamExt}; -use serde::{ - de::{Error as SerdeError, MapAccess, SeqAccess, Visitor}, - ser::SerializeMap, - Deserialize, Deserializer, Serialize, Serializer, -}; use zeroize::Zeroize; use super::wql; use crate::{crypto::buffer::SecretBytes, error::Error}; pub(crate) fn sorted_tags(tags: &Vec) -> Vec<&EntryTag> { - if tags.len() > 0 { + if tags.is_empty() { + Vec::new() + } else { let mut tags = tags.iter().collect::>(); tags.sort(); tags - } else { - Vec::new() } } /// A record in the store #[derive(Clone, Debug, Eq)] pub struct Entry { + /// The entry kind discriminator + pub kind: EntryKind, + /// The category of the entry record pub category: String, @@ -46,12 +45,14 @@ impl Entry { /// Create a new `Entry` #[inline] pub fn new, N: Into, V: Into>( + kind: EntryKind, category: C, name: N, value: V, tags: Vec, ) -> Self { Self { + kind, category: category.into(), name: name.into(), value: value.into(), @@ -73,12 +74,27 @@ impl PartialEq for Entry { } } +/// Set of distinct entry kinds for separating records. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum EntryKind { + /// Key manager entry Kms = 1, + /// General stored item Item = 2, } +impl TryFrom for EntryKind { + type Error = Error; + + fn try_from(value: usize) -> Result { + match value { + 1 => Ok(Self::Kms), + 2 => Ok(Self::Item), + _ => Err(err_msg!("Unknown entry kind: {value}")), + } + } +} + /// Supported operations for entries in the store #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum EntryOperation { @@ -107,7 +123,8 @@ impl EntryTag { } } - pub(crate) fn map_ref(&self, f: impl FnOnce(&str, &str) -> (String, String)) -> Self { + /// Create a new EntryTag using references to the name and value + pub fn map_ref(&self, f: impl FnOnce(&str, &str) -> (String, String)) -> Self { match self { Self::Encrypted(name, val) => { let (name, val) = f(name.as_str(), val.as_str()); @@ -121,7 +138,7 @@ impl EntryTag { } /// Setter for the tag name - pub(crate) fn update_name(&mut self, f: impl FnOnce(&mut String)) { + pub fn update_name(&mut self, f: impl FnOnce(&mut String)) { match self { Self::Encrypted(name, _) | Self::Plaintext(name, _) => f(name), } @@ -135,7 +152,7 @@ impl EntryTag { } /// Unwrap the tag value - pub(crate) fn into_value(self) -> String { + pub fn into_value(self) -> String { match self { Self::Encrypted(_, value) | Self::Plaintext(_, value) => value, } @@ -159,184 +176,6 @@ impl Debug for EntryTag { } } -/// A wrapper type used for managing (de)serialization of tags -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct EntryTagSet<'e>(Cow<'e, [EntryTag]>); - -impl EntryTagSet<'_> { - #[inline] - pub fn into_vec(self) -> Vec { - self.0.into_owned() - } -} - -impl<'e> From<&'e [EntryTag]> for EntryTagSet<'e> { - fn from(tags: &'e [EntryTag]) -> Self { - Self(Cow::Borrowed(tags)) - } -} - -impl From> for EntryTagSet<'static> { - fn from(tags: Vec) -> Self { - Self(Cow::Owned(tags)) - } -} - -impl<'de> Deserialize<'de> for EntryTagSet<'static> { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct TagSetVisitor; - - impl<'d> Visitor<'d> for TagSetVisitor { - type Value = EntryTagSet<'static>; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("an object containing zero or more entry tags") - } - - fn visit_map(self, mut access: M) -> Result - where - M: MapAccess<'d>, - { - let mut v = Vec::with_capacity(access.size_hint().unwrap_or_default()); - - while let Some((key, values)) = access.next_entry::<&str, EntryTagValues>()? { - let (tag, enc) = match key.chars().next() { - Some('~') => (key[1..].to_owned(), false), - None => return Err(M::Error::custom("invalid tag name: empty string")), - _ => (key.to_owned(), true), - }; - match (values, enc) { - (EntryTagValues::Single(value), true) => { - v.push(EntryTag::Encrypted(tag, value)) - } - (EntryTagValues::Single(value), false) => { - v.push(EntryTag::Plaintext(tag, value)) - } - (EntryTagValues::Multiple(values), true) => { - for value in values { - v.push(EntryTag::Encrypted(tag.clone(), value)) - } - } - (EntryTagValues::Multiple(values), false) => { - for value in values { - v.push(EntryTag::Plaintext(tag.clone(), value)) - } - } - } - } - - Ok(EntryTagSet(Cow::Owned(v))) - } - } - - deserializer.deserialize_map(TagSetVisitor) - } -} - -enum EntryTagValues { - Single(String), - Multiple(Vec), -} - -impl<'de> Deserialize<'de> for EntryTagValues { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct TagValuesVisitor; - - impl<'d> Visitor<'d> for TagValuesVisitor { - type Value = EntryTagValues; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a string or list of strings") - } - - fn visit_str(self, value: &str) -> Result - where - E: SerdeError, - { - Ok(EntryTagValues::Single(value.to_owned())) - } - - fn visit_string(self, value: String) -> Result - where - E: SerdeError, - { - Ok(EntryTagValues::Single(value)) - } - - fn visit_seq(self, mut access: S) -> Result - where - S: SeqAccess<'d>, - { - let mut v = Vec::with_capacity(access.size_hint().unwrap_or_default()); - while let Some(value) = access.next_element()? { - v.push(value) - } - Ok(EntryTagValues::Multiple(v)) - } - } - - deserializer.deserialize_any(TagValuesVisitor) - } -} - -impl Serialize for EntryTagSet<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - use std::collections::BTreeMap; - - #[derive(PartialOrd, Ord)] - struct TagName<'a>(&'a str, bool); - - impl<'a> PartialEq for TagName<'a> { - fn eq(&self, other: &Self) -> bool { - self.1 == other.1 && self.0 == other.0 - } - } - - impl<'a> Eq for TagName<'a> {} - - impl Serialize for TagName<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if self.1 { - serializer.serialize_str(&self.0) - } else { - serializer.collect_str(&format_args!("~{}", self.0)) - } - } - } - - let mut tags = BTreeMap::new(); - for tag in self.0.iter() { - let (name, value) = match tag { - EntryTag::Encrypted(name, val) => (TagName(name.as_str(), true), val.as_str()), - EntryTag::Plaintext(name, val) => (TagName(name.as_str(), false), val.as_str()), - }; - tags.entry(name).or_insert_with(|| vec![]).push(value); - } - - let mut map = serializer.serialize_map(Some(tags.len()))?; - for (tag_name, values) in tags.into_iter() { - if values.len() > 1 { - map.serialize_entry(&tag_name, &values)?; - } else { - map.serialize_entry(&tag_name, &values[0])?; - } - } - map.end() - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct EncEntryTag { pub name: Vec, @@ -370,7 +209,7 @@ impl TagFilter { /// Get the inverse of a tag filter #[inline] - pub fn not(filter: TagFilter) -> Self { + pub fn negate(filter: TagFilter) -> Self { Self { query: wql::Query::Not(Box::new(filter.query)), } @@ -452,6 +291,11 @@ impl TagFilter { pub fn to_string(&self) -> Result { serde_json::to_string(&self.query).map_err(err_map!("Error encoding tag filter")) } + + /// Unwrap into a wql::Query + pub fn into_query(self) -> wql::Query { + self.query + } } impl From for TagFilter { @@ -471,6 +315,7 @@ impl FromStr for TagFilter { /// An active record scan of a store backend pub struct Scan<'s, T> { + #[allow(clippy::type_complexity)] stream: Option, Error>> + Send + 's>>>, page_size: usize, } @@ -511,21 +356,3 @@ impl Debug for Scan<'_, S> { .finish() } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialize_tags() { - let tags = EntryTagSet::from(vec![ - EntryTag::Encrypted("a".to_owned(), "aval".to_owned()), - EntryTag::Plaintext("b".to_owned(), "bval".to_owned()), - EntryTag::Plaintext("b".to_owned(), "bval-2".to_owned()), - ]); - let ser = serde_json::to_string(&tags).unwrap(); - assert_eq!(ser, r#"{"a":"aval","~b":["bval","bval-2"]}"#); - let tags2 = serde_json::from_str(&ser).unwrap(); - assert_eq!(tags, tags2); - } -} diff --git a/askar-storage/src/error.rs b/askar-storage/src/error.rs new file mode 100644 index 00000000..6d0232ec --- /dev/null +++ b/askar-storage/src/error.rs @@ -0,0 +1,190 @@ +use std::error::Error as StdError; +use std::fmt::{self, Display, Formatter}; + +use crate::crypto::{Error as CryptoError, ErrorKind as CryptoErrorKind}; + +/// The possible kinds of error produced by the crate +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ErrorKind { + /// An unexpected error from the store backend + Backend, + + /// The store backend was too busy to handle the request + Busy, + + /// A custom error type for external integrations + Custom, + + /// An insert operation failed due to a unique key conflict + Duplicate, + + /// An encryption or decryption operation failed + Encryption, + + /// The input parameters to the method were incorrect + Input, + + /// The requested record was not found + NotFound, + + /// An unexpected error occurred + Unexpected, + + /// An unsupported operation was requested + Unsupported, +} + +impl ErrorKind { + /// Convert the error kind to a string reference + pub fn as_str(&self) -> &'static str { + match self { + Self::Backend => "Backend error", + Self::Busy => "Busy", + Self::Custom => "Custom error", + Self::Duplicate => "Duplicate", + Self::Encryption => "Encryption error", + Self::Input => "Input error", + Self::NotFound => "Not found", + Self::Unexpected => "Unexpected error", + Self::Unsupported => "Unsupported", + } + } +} + +impl Display for ErrorKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// The standard crate error type +#[derive(Debug)] +pub struct Error { + pub(crate) kind: ErrorKind, + pub(crate) cause: Option>, + pub(crate) message: Option, +} + +impl Error { + pub(crate) fn from_msg>(kind: ErrorKind, msg: T) -> Self { + Self { + kind, + cause: None, + message: Some(msg.into()), + } + } + + /// Accessor for the error kind + pub fn kind(&self) -> ErrorKind { + self.kind + } + + /// Accessor for the error message + pub fn message(&self) -> Option<&str> { + self.message.as_deref() + } + + /// Split the error into its components + pub fn into_parts( + self, + ) -> ( + ErrorKind, + Option>, + Option, + ) { + (self.kind, self.cause, self.message) + } + + pub(crate) fn with_cause>>( + mut self, + err: T, + ) -> Self { + self.cause = Some(err.into()); + self + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(msg) = self.message.as_ref() { + f.write_str(msg)?; + } else { + f.write_str(self.kind.as_str())?; + } + if let Some(cause) = self.cause.as_ref() { + write!(f, "\nCaused by: {}", cause)?; + } + Ok(()) + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.cause + .as_ref() + .map(|err| &**err as &(dyn StdError + 'static)) + } +} + +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + self.kind == other.kind && self.message == other.message + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Self { + kind, + cause: None, + message: None, + } + } +} + +// FIXME would be preferable to remove this auto-conversion and handle +// all sqlx errors manually, to ensure there is some context around the error +#[cfg(any(feature = "indy_compat", feature = "postgres", feature = "sqlite"))] +impl From for Error { + fn from(err: sqlx::Error) -> Self { + Error::from(ErrorKind::Backend).with_cause(err) + } +} + +impl From for Error { + fn from(err: CryptoError) -> Self { + let kind = match err.kind() { + CryptoErrorKind::Custom => ErrorKind::Custom, + CryptoErrorKind::Encryption => ErrorKind::Encryption, + CryptoErrorKind::ExceededBuffer | CryptoErrorKind::Unexpected => ErrorKind::Unexpected, + CryptoErrorKind::Invalid + | CryptoErrorKind::InvalidKeyData + | CryptoErrorKind::InvalidNonce + | CryptoErrorKind::MissingSecretKey + | CryptoErrorKind::Usage => ErrorKind::Input, + CryptoErrorKind::Unsupported => ErrorKind::Unsupported, + }; + Error::from_msg(kind, err.message()) + } +} + +macro_rules! err_msg { + () => { + $crate::error::Error::from($crate::error::ErrorKind::Input) + }; + ($kind:ident) => { + $crate::error::Error::from($crate::error::ErrorKind::$kind) + }; + ($kind:ident, $($args:tt)+) => { + $crate::error::Error::from_msg($crate::error::ErrorKind::$kind, format!($($args)+)) + }; + ($($args:tt)+) => { + $crate::error::Error::from_msg($crate::error::ErrorKind::Input, format!($($args)+)) + }; +} + +macro_rules! err_map { + ($($params:tt)*) => { + |err| err_msg!($($params)*).with_cause(err) + }; +} diff --git a/askar-storage/src/future.rs b/askar-storage/src/future.rs new file mode 100644 index 00000000..398048ab --- /dev/null +++ b/askar-storage/src/future.rs @@ -0,0 +1,93 @@ +use std::{ + future::Future, + pin::Pin, + sync::Arc, + thread, + time::{Duration, Instant}, +}; + +use arc_swap::ArcSwapOption; +use once_cell::sync::Lazy; +use tokio::runtime::Runtime; + +pub type BoxFuture<'a, T> = Pin + Send + 'a>>; + +static RUNTIME: Lazy> = Lazy::new(|| { + ArcSwapOption::new(Some(Arc::new( + Runtime::new().expect("Error creating tokio runtime"), + ))) +}); + +/// Block the current thread on an async task, when not running inside the scheduler. +pub fn block_on(f: impl Future) -> R { + if let Some(rt) = RUNTIME.load().clone() { + rt.block_on(f) + } else { + panic!("Runtime has been shut down"); + } +} + +/// Run a blocking task without interrupting the async scheduler. +#[inline] +pub async fn unblock(f: F) -> T +where + T: Send + 'static, + F: FnOnce() -> T + Send + 'static, +{ + if let Some(rt) = RUNTIME.load().clone() { + rt.spawn_blocking(f) + .await + .expect("Error running blocking task") + } else { + panic!("Runtime has been shut down"); + } +} + +/// Spawn an async task into the runtime. +#[inline] +pub fn spawn_ok(fut: impl Future + Send + 'static) { + if let Some(rt) = RUNTIME.load().clone() { + rt.spawn(fut); + } +} + +/// Wait until a specific duration has passed (used in tests). +/// This method must be called within `block_on` or a spawned task in order to have +/// access to the async runtime. +#[doc(hidden)] +pub async fn sleep(dur: Duration) { + tokio::time::sleep(dur).await +} + +/// Cancel an async task if it does not complete after a timeout (used in tests). +/// This method must be called within `block_on` or a spawned task in order to have +/// access to the async runtime. +#[doc(hidden)] +pub async fn timeout(dur: Duration, f: impl Future) -> Option { + tokio::time::timeout(dur, f).await.ok() +} + +/// Shut down the async runtime. +#[doc(hidden)] +pub fn shutdown(max_dur: Duration) { + let start = Instant::now(); + if let Some(rt_swap) = Lazy::get(&RUNTIME) { + if let Some(mut rt) = rt_swap.swap(None) { + loop { + match Arc::try_unwrap(rt) { + Ok(rt) => { + rt.shutdown_timeout(max_dur.saturating_sub(start.elapsed())); + break; + } + Err(new_rt) => { + rt = new_rt; + if start.elapsed() >= max_dur { + break; + } + thread::sleep(Duration::from_millis(1)); + } + } + } + } + } +} diff --git a/askar-storage/src/lib.rs b/askar-storage/src/lib.rs new file mode 100644 index 00000000..a744a9f6 --- /dev/null +++ b/askar-storage/src/lib.rs @@ -0,0 +1,57 @@ +//! Secure storage designed for Hyperledger Aries agents + +#![cfg_attr(docsrs, feature(doc_cfg))] +#![deny(missing_docs, missing_debug_implementations, rust_2018_idioms)] + +pub use askar_crypto as crypto; + +#[macro_use] +mod error; +pub use self::error::{Error, ErrorKind}; + +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + +#[macro_use] +mod macros; + +#[cfg(any(test, feature = "log"))] +#[macro_use] +extern crate log; + +#[cfg(feature = "migration")] +#[macro_use] +extern crate serde; + +pub mod backend; +pub use self::backend::{Backend, BackendSession, ManageBackend}; + +#[cfg(feature = "any")] +pub mod any; + +#[cfg(feature = "postgres")] +pub use self::backend::postgres; + +#[cfg(feature = "sqlite")] +pub use self::backend::sqlite; + +pub mod entry; + +#[doc(hidden)] +pub mod future; + +#[cfg(all(feature = "migration", feature = "sqlite"))] +pub mod migration; + +mod options; +pub use options::{IntoOptions, Options}; + +mod protect; +pub use protect::{ + generate_raw_store_key, + kdf::{Argon2Level, KdfMethod}, + PassKey, StoreKeyMethod, +}; + +mod wql; diff --git a/src/macros.rs b/askar-storage/src/macros.rs similarity index 100% rename from src/macros.rs rename to askar-storage/src/macros.rs diff --git a/askar-storage/src/migration/mod.rs b/askar-storage/src/migration/mod.rs new file mode 100644 index 00000000..be0a5809 --- /dev/null +++ b/askar-storage/src/migration/mod.rs @@ -0,0 +1,407 @@ +//! Support for migration from Indy-SDK wallets. + +use sha2::Sha256; +use sqlx::sqlite::SqliteRow; +use sqlx::{Connection, Row, SqliteConnection}; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +use self::strategy::Strategy; +use crate::backend::sqlite::SqliteStoreOptions; +use crate::backend::Backend; +use crate::crypto::alg::chacha20::{Chacha20Key, C20P}; +use crate::crypto::generic_array::typenum::U32; +use crate::entry::EncEntryTag; +use crate::error::Error; +use crate::protect::kdf::Argon2Level; +use crate::protect::{ProfileKey, StoreKey, StoreKeyReference}; + +mod strategy; + +const CHACHAPOLY_NONCE_LEN: u8 = 12; + +#[derive(Deserialize, Debug, Default)] +pub(crate) struct IndyKeyMetadata { + keys: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + master_key_salt: Option>, +} + +pub(crate) type EncryptionKey = Chacha20Key; +pub(crate) type MacKey = crate::protect::hmac_key::HmacKey; + +/// Copies: https://github.com/hyperledger/indy-sdk/blob/83547c4c01162f6323cf138f8b071da2e15f0c90/libindy/indy-wallet/src/wallet.rs#L18 +#[derive(Deserialize)] +pub(crate) struct IndyKey { + type_key: EncryptionKey, + name_key: EncryptionKey, + value_key: EncryptionKey, + #[allow(unused)] + item_hmac_key: MacKey, + tag_name_key: EncryptionKey, + tag_value_key: EncryptionKey, + #[allow(unused)] + tag_hmac_key: MacKey, +} + +#[derive(Default)] +pub(crate) struct UpdatedIndyItem { + pub id: u32, + pub category: Vec, + pub name: Vec, + pub value: Vec, + pub tags: Vec, +} + +pub(crate) struct UpdatedKey { + master: StoreKey, + key_ref: StoreKeyReference, +} + +#[derive(Debug)] +pub(crate) enum KdfMethod { + Argon2i(Argon2Level), + Raw, +} + +impl KdfMethod { + pub(crate) fn to_store_key_reference( + &self, + salt: Option<&[u8]>, + ) -> Result { + match self { + KdfMethod::Raw => Ok(StoreKeyReference::RawKey), + KdfMethod::Argon2i(level) => { + let detail = salt + .map(|s| format!("?salt={}", hex::encode(s))) + .ok_or_else(|| err_msg!("Salt must be provided for argon2i kdf method"))?; + Ok(StoreKeyReference::DeriveKey( + crate::protect::kdf::KdfMethod::Argon2i(*level), + detail, + )) + } + } + } +} + +impl FromStr for KdfMethod { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "ARGON2I_MOD" => Ok(Self::Argon2i(Argon2Level::Moderate)), + "ARGON2I_INT" => Ok(Self::Argon2i(Argon2Level::Interactive)), + "RAW" => Ok(Self::Raw), + _ => Err(err_msg!("Invalid key derivation method")), + } + } +} + +/// Indy-SDK migrator implementation +#[derive(Debug)] +pub struct IndySdkToAriesAskarMigration { + conn: SqliteConnection, + spec_uri: String, + wallet_key: String, + wallet_name: String, + kdf_method: KdfMethod, +} + +impl IndySdkToAriesAskarMigration { + /// Create a new migrator connected to a database + pub async fn connect( + spec_uri: &str, + wallet_name: &str, + wallet_key: &str, + kdf_method: &str, + ) -> Result { + let kdf_method = KdfMethod::from_str(kdf_method)?; + let conn = SqliteConnection::connect(spec_uri).await?; + Ok(Self { + conn, + spec_uri: spec_uri.into(), + wallet_key: wallet_key.to_owned(), + wallet_name: wallet_name.to_owned(), + kdf_method, + }) + } + + /// Close the instance without migrating + pub async fn close(self) -> Result<(), Error> { + Ok(self.conn.close().await?) + } + + /// Perform the migration + pub async fn migrate(mut self) -> Result<(), Error> { + if self.is_migrated().await? { + self.close().await?; + return Err(err_msg!(Backend, "Database is already migrated")); + } + + self.pre_upgrade().await?; + debug!("Completed wallet pre-upgrade"); + + let (indy_key, upd_key) = self.fetch_indy_key().await?; + self.create_config(&upd_key).await?; + let profile_key = self.init_profile(&upd_key).await?; + debug!("Created wallet profile"); + + self.update_items(&indy_key, &profile_key).await?; + self.finish_upgrade().await?; + self.conn.close().await?; + debug!("Completed wallet upgrade"); + + debug!("Re-opening wallet"); + let db_opts = SqliteStoreOptions::new(self.spec_uri.as_str())?; + let key_method = upd_key.key_ref.into(); + let db = db_opts + .open(Some(key_method), self.wallet_key.as_str().into(), None) + .await?; + db.close().await?; + debug!("Verified wallet upgrade"); + + Ok(()) + } + + #[inline] + async fn is_migrated(&mut self) -> Result { + let res: Option = + sqlx::query("SELECT name FROM sqlite_master WHERE type='table' AND name='metadata'") + .fetch_optional(&mut self.conn) + .await?; + Ok(res.is_none()) + } + + async fn pre_upgrade(&mut self) -> Result<(), Error> { + sqlx::query( + " + BEGIN EXCLUSIVE TRANSACTION; + CREATE TABLE config ( + name TEXT NOT NULL, + value TEXT, + PRIMARY KEY (name) + ); + CREATE TABLE profiles ( + id INTEGER NOT NULL, + name TEXT NOT NULL, + reference TEXT NULL, + profile_key BLOB NULL, + PRIMARY KEY (id) + ); + CREATE UNIQUE INDEX ix_profile_name ON profiles (name); + ALTER TABLE items RENAME TO items_old; + CREATE TABLE items ( + id INTEGER NOT NULL, + profile_id INTEGER NOT NULL, + kind INTEGER NOT NULL, + category BLOB NOT NULL, + name BLOB NOT NULL, + value BLOB NOT NULL, + expiry DATETIME NULL, + PRIMARY KEY (id), + FOREIGN KEY (profile_id) REFERENCES profiles (id) + ON DELETE CASCADE ON UPDATE CASCADE + ); + CREATE UNIQUE INDEX ix_items_uniq ON items + (profile_id, kind, category, name); + CREATE TABLE items_tags ( + id INTEGER NOT NULL, + item_id INTEGER NOT NULL, + name BLOB NOT NULL, + value BLOB NOT NULL, + plaintext BOOLEAN NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (item_id) REFERENCES items (id) + ON DELETE CASCADE ON UPDATE CASCADE + ); + CREATE INDEX ix_items_tags_item_id ON items_tags (item_id); + CREATE INDEX ix_items_tags_name_enc ON items_tags + (name, SUBSTR(value, 1, 12)) WHERE plaintext=0; + CREATE INDEX ix_items_tags_name_plain ON items_tags + (name, value) WHERE plaintext=1; + COMMIT; + ", + ) + .execute(&mut self.conn) + .await?; + Ok(()) + } + + async fn fetch_indy_key(&mut self) -> Result<(IndyKey, UpdatedKey), Error> { + let metadata_row: Vec = sqlx::query("SELECT value FROM metadata") + .fetch_one(&mut self.conn) + .await? + .try_get(0)?; + let metadata_json = String::from_utf8_lossy(&metadata_row); + let metadata: IndyKeyMetadata = serde_json::from_str(&metadata_json).map_err(err_map!( + Input, + "Could not convert value from metadata to IndyKey", + ))?; + let keys_enc = metadata.keys; + let salt = metadata.master_key_salt.map(|s| s[..16].to_vec()); + + let key_ref = self.kdf_method.to_store_key_reference(salt.as_deref())?; + let master = key_ref.resolve(self.wallet_key.as_str().into())?; + + let keys_mpk = master + .unwrap_data(keys_enc) + .map_err(err_map!(Input, "Error decrypting wallet key"))?; + let indy_key = rmp_serde::from_slice(&keys_mpk) + .map_err(err_map!(Input, "indy key not valid msgpack"))?; + + Ok((indy_key, UpdatedKey { master, key_ref })) + } + + async fn init_profile(&mut self, key: &UpdatedKey) -> Result { + let profile_row: Option = sqlx::query("SELECT profile_key FROM profiles") + .fetch_optional(&mut self.conn) + .await?; + let profile_row: Option> = match profile_row { + Some(row) => row.try_get(0).ok(), + None => None, + }; + + let profile_key = match profile_row { + Some(profile_row) => serde_cbor::from_slice(&profile_row) + .map_err(err_map!(Input, "Invalid cbor encoding for profile_key"))?, + None => { + let pk = ProfileKey::new()?; + let enc_pk = key.master.wrap_data(pk.to_bytes()?)?; + self.insert_profile(enc_pk.as_slice()).await?; + pk + } + }; + + Ok(profile_key) + } + + async fn update_items( + &mut self, + indy_key: &IndyKey, + profile_key: &ProfileKey, + ) -> Result<(), Error> { + Strategy::update_items(self, indy_key, profile_key).await?; + Ok(()) + } + + async fn finish_upgrade(&mut self) -> Result<(), Error> { + sqlx::query( + r#" + BEGIN EXCLUSIVE TRANSACTION; + DROP TABLE items_old; + DROP TABLE metadata; + DROP TABLE tags_encrypted; + DROP TABLE tags_plaintext; + INSERT INTO config (name, value) VALUES ("version", "1"); + COMMIT;"#, + ) + .execute(&mut self.conn) + .await?; + Ok(()) + } + + async fn update_items_in_db(&mut self, items: Vec) -> Result<(), Error> { + let mut del_ids = vec![]; + + for item in items { + del_ids.push(item.id); + let ins = sqlx::query( + "INSERT INTO items (profile_id, kind, category, name, value) + VALUES (1, 2, ?1, ?2, ?3)", + ) + .bind(item.category) + .bind(item.name) + .bind(item.value) + .execute(&mut self.conn) + .await?; + let item_id = ins.last_insert_rowid(); + for EncEntryTag { + name, + value, + plaintext, + } in item.tags + { + sqlx::query("INSERT INTO items_tags (item_id, plaintext, name, value) VALUES (?1, ?2, ?3, ?4)") + .bind(item_id) + .bind(plaintext) + .bind(name) + .bind(value) + .execute(&mut self.conn) + .await?; + } + } + sqlx::query("DELETE FROM items_old WHERE id IN (?1)") + .bind(Separated(&del_ids, ",").to_string()) + .execute(&mut self.conn) + .await?; + Ok(()) + } + + async fn create_config(&mut self, key: &UpdatedKey) -> Result<(), Error> { + let pass_key = key.key_ref.clone().into_uri(); + + sqlx::query("INSERT INTO config (name, value) VALUES (?1, ?2)") + .bind("default_profile") + .bind(&self.wallet_name) + .execute(&mut self.conn) + .await?; + + sqlx::query("INSERT INTO config (name, value) VALUES (?1, ?2)") + .bind("key") + .bind(pass_key) + .execute(&mut self.conn) + .await?; + + Ok(()) + } + + async fn insert_profile(&mut self, key: &[u8]) -> Result<(), Error> { + sqlx::query("INSERT INTO profiles (name, profile_key) VALUES (?1, ?2)") + .bind(&self.wallet_name) + .bind(key.to_vec()) + .execute(&mut self.conn) + .await?; + + Ok(()) + } + + async fn fetch_pending_items< + T: Send + Unpin + for<'r> sqlx::FromRow<'r, sqlx::sqlite::SqliteRow>, + >( + &mut self, + limit: u8, + ) -> Result>, Error> { + let res = sqlx::query_as( + "SELECT i.id, i.type, i.name, i.value, i.key, + (SELECT GROUP_CONCAT(HEX(te.name) || ':' || HEX(te.value)) + FROM tags_encrypted te WHERE te.item_id = i.id) AS tags_enc, + (SELECT GROUP_CONCAT(HEX(tp.name) || ':' || HEX(tp.value)) + FROM tags_plaintext tp WHERE tp.item_id = i.id) AS tags_plain + FROM items_old i LIMIT ?1", + ) + .bind(limit) + .fetch_all(&mut self.conn) + .await?; + + match res.len() { + 0 => Ok(None), + _ => Ok(Some(res)), + } + } +} + +struct Separated<'a, T>(&'a [T], &'static str); + +impl Display for Separated<'_, T> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut first = true; + for item in self.0 { + if !first { + f.write_str(self.1)?; + } + item.fmt(f)?; + first = false; + } + Ok(()) + } +} diff --git a/askar-storage/src/migration/strategy.rs b/askar-storage/src/migration/strategy.rs new file mode 100644 index 00000000..b8c64bfd --- /dev/null +++ b/askar-storage/src/migration/strategy.rs @@ -0,0 +1,152 @@ +use super::{ + EncryptionKey, IndyKey, IndySdkToAriesAskarMigration, ProfileKey, UpdatedIndyItem, + CHACHAPOLY_NONCE_LEN, +}; +use crate::crypto::buffer::SecretBytes; +use crate::crypto::encrypt::KeyAeadInPlace; +use crate::crypto::repr::KeySecretBytes; +use crate::entry::EntryTag; +use crate::protect::EntryEncryptor; +use crate::Error; + +#[derive(Default)] +pub(crate) struct IndyItem { + id: u32, + typ: Vec, + name: Vec, + value: Option>, + tags: Vec, +} + +// TODO: should tags_enc and tags_plain be empty in the example? +#[derive(sqlx::FromRow, Debug)] +pub(crate) struct IndyRow { + id: u32, + #[sqlx(rename = "type")] + typ: Vec, + name: Vec, + value: Option>, + key: Vec, + tags_enc: Option, + tags_plain: Option, +} + +pub(crate) struct Strategy {} + +impl Strategy { + pub fn decrypt_merged(enc_value: &[u8], key: &EncryptionKey) -> Result, Error> { + let (nonce, ciphertext) = enc_value.split_at(CHACHAPOLY_NONCE_LEN.into()); + + let mut buffer = SecretBytes::from_slice(ciphertext); + + key.decrypt_in_place(&mut buffer, nonce, &[])?; + + Ok(buffer.to_vec()) + } + + pub fn decrypt_tags( + tags: &str, + name_key: &EncryptionKey, + value_key: Option<&EncryptionKey>, + ) -> Result, Error> { + let mut ret = vec![]; + for tag in tags.split(',') { + let mut t = tag.split(':'); + + let tag_name = hex::decode(t.next().unwrap()) + .map_err(err_map!(Input, "tag is not valid hex encoded"))?; + let tag_value = hex::decode(t.next().unwrap()) + .map_err(err_map!(Input, "tag is not valid hex encoded"))?; + + let name = String::from_utf8(Self::decrypt_merged(&tag_name, name_key)?) + .map_err(err_map!(Input, "tag name is not valid utf-8"))?; + let value = String::from_utf8(match value_key { + None => tag_value, + Some(value_key) => Self::decrypt_merged(&tag_value, value_key)?, + }) + .map_err(err_map!(Input, "tag value is not valid utf-8"))?; + ret.push((name, value)); + } + Ok(ret) + } + + pub fn decrypt_item(row: IndyRow, keys: &IndyKey) -> Result { + let value_key = Self::decrypt_merged(&row.key, &keys.value_key)?; + let value_key = EncryptionKey::from_secret_bytes(&value_key)?; + let value = match row.value { + Some(ref value) => Some(Self::decrypt_merged(value, &value_key)?), + None => None, + }; + let mut tags: Vec = vec![]; + + let resp = match row.tags_enc { + None => vec![], + Some(tags_enc) => Self::decrypt_tags( + tags_enc.as_str(), + &keys.tag_name_key, + Some(&keys.tag_value_key), + )?, + }; + for (name, value) in resp { + tags.push(EntryTag::Encrypted(name, value)); + } + + let resp_plain = match row.tags_plain { + None => vec![], + Some(tags_plain) => Self::decrypt_tags(tags_plain.as_str(), &keys.tag_name_key, None)?, + }; + for (name, value) in resp_plain { + tags.push(EntryTag::Plaintext(name, value)); + } + + let indy_item = IndyItem { + id: row.id, + typ: Self::decrypt_merged(&row.typ, &keys.type_key)?, + name: Self::decrypt_merged(&row.name, &keys.name_key)?, + value, + tags, + }; + + Ok(indy_item) + } + + pub fn update_item(item: IndyItem, key: &ProfileKey) -> Result { + let value = match item.value { + Some(v) => key.encrypt_entry_value(&item.typ, &item.name, v.into())?, + None => Default::default(), + }; + + let updated_indy_item = UpdatedIndyItem { + id: item.id, + category: key.encrypt_entry_category(item.typ.into())?, + name: key.encrypt_entry_name(item.name.into())?, + value, + tags: key.encrypt_entry_tags(item.tags)?, + }; + + Ok(updated_indy_item) + } + + pub async fn update_items( + conn: &mut IndySdkToAriesAskarMigration, + indy_key: &IndyKey, + profile_key: &ProfileKey, + ) -> Result<(), Error> { + loop { + let rows = conn.fetch_pending_items::(1).await?; + match rows { + None => break, + Some(rows) => { + let mut upd = vec![]; + for row in rows { + let result = Self::decrypt_item(row, indy_key)?; + upd.push(Self::update_item(result, profile_key)?); + } + conn.update_items_in_db(upd).await?; + } + } + } + + Ok(()) + } +} diff --git a/src/storage/options.rs b/askar-storage/src/options.rs similarity index 92% rename from src/storage/options.rs rename to askar-storage/src/options.rs index 2f9cd9c3..9b3f739c 100644 --- a/src/storage/options.rs +++ b/askar-storage/src/options.rs @@ -6,17 +6,26 @@ use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC use crate::error::Error; #[derive(Clone, Debug, Default, PartialEq, Eq)] +/// Parsed representation of database connection URI pub struct Options<'a> { + /// The URI schema pub schema: Cow<'a, str>, + /// The authenticating user name pub user: Cow<'a, str>, + /// The authenticating user password pub password: Cow<'a, str>, + /// The host name pub host: Cow<'a, str>, + /// The path component pub path: Cow<'a, str>, + /// The query component pub query: HashMap, + /// The fragment component pub fragment: Cow<'a, str>, } impl<'a> Options<'a> { + /// Parse a URI string into an Options structure pub fn parse_uri(uri: &str) -> Result, Error> { let mut fragment_and_remain = uri.splitn(2, '#'); let uri = fragment_and_remain.next().unwrap_or_default(); @@ -79,6 +88,7 @@ impl<'a> Options<'a> { }) } + /// Convert an options structure back into a string pub fn into_uri(self) -> String { let mut uri = String::new(); if !self.schema.is_empty() { @@ -126,7 +136,9 @@ fn percent_encode_into(result: &mut String, s: &str) { push_iter_str(result, utf8_percent_encode(s, NON_ALPHANUMERIC)) } +/// A trait implemented by types that can be converted into Options pub trait IntoOptions<'a> { + /// Try to convert self into an Options structure fn into_options(self) -> Result, Error>; } @@ -145,7 +157,6 @@ impl<'a> IntoOptions<'a> for &'a str { #[cfg(test)] mod tests { use super::*; - use std::iter::FromIterator; #[test] fn options_basic() { diff --git a/src/protect/hmac_key.rs b/askar-storage/src/protect/hmac_key.rs similarity index 98% rename from src/protect/hmac_key.rs rename to askar-storage/src/protect/hmac_key.rs index 611640b7..c1ad6a64 100644 --- a/src/protect/hmac_key.rs +++ b/askar-storage/src/protect/hmac_key.rs @@ -54,7 +54,7 @@ impl> AsRef> for HmacKey { impl> Debug for HmacKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if cfg!(test) { - f.debug_tuple("HmacKey").field(&*self).finish() + f.debug_tuple("HmacKey").field(&self.0).finish() } else { f.debug_tuple("HmacKey").field(&"").finish() } diff --git a/src/protect/kdf/argon2.rs b/askar-storage/src/protect/kdf/argon2.rs similarity index 75% rename from src/protect/kdf/argon2.rs rename to askar-storage/src/protect/kdf/argon2.rs index 060271f6..08c5055c 100644 --- a/src/protect/kdf/argon2.rs +++ b/askar-storage/src/protect/kdf/argon2.rs @@ -12,12 +12,15 @@ use crate::{ pub use crate::crypto::kdf::argon2::SaltSize; -pub const LEVEL_INTERACTIVE: &'static str = "13:int"; -pub const LEVEL_MODERATE: &'static str = "13:mod"; +pub const LEVEL_INTERACTIVE: &str = "13:int"; +pub const LEVEL_MODERATE: &str = "13:mod"; +/// Argon2i derivation methods #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Level { + /// Interactive method Interactive, + /// Stronger Moderate method Moderate, } @@ -28,7 +31,7 @@ impl Default for Level { } impl Level { - pub fn from_str(level: &str) -> Option { + pub(crate) fn from_str(level: &str) -> Option { match level { "int" | LEVEL_INTERACTIVE => Some(Self::Interactive), "mod" | LEVEL_MODERATE => Some(Self::Moderate), @@ -37,14 +40,14 @@ impl Level { } } - pub fn as_str(&self) -> &'static str { + pub(crate) fn as_str(&self) -> &'static str { match self { Self::Interactive => LEVEL_INTERACTIVE, Self::Moderate => LEVEL_MODERATE, } } - pub fn generate_salt(&self) -> ArrayKey { + pub(crate) fn generate_salt(&self) -> ArrayKey { ArrayKey::random() } @@ -55,7 +58,7 @@ impl Level { } } - pub fn derive_key(&self, password: &[u8], salt: &[u8]) -> Result { + pub(crate) fn derive_key(&self, password: &[u8], salt: &[u8]) -> Result { ArrayKey::<::KeySize>::temp(|key| { Argon2::new(password, salt, *self.params())?.derive_key_bytes(key)?; Ok(StoreKey::from(StoreKeyType::from_secret_bytes(&*key)?)) diff --git a/src/protect/kdf/mod.rs b/askar-storage/src/protect/kdf/mod.rs similarity index 61% rename from src/protect/kdf/mod.rs rename to askar-storage/src/protect/kdf/mod.rs index a04ff2ef..2eb75d9b 100644 --- a/src/protect/kdf/mod.rs +++ b/askar-storage/src/protect/kdf/mod.rs @@ -1,51 +1,51 @@ +//! Key derivations + use super::store_key::{StoreKey, PREFIX_KDF}; use crate::{ crypto::{buffer::ArrayKey, generic_array::ArrayLength}, error::Error, - storage::Options, + options::Options, }; mod argon2; -use self::argon2::{Level as Argon2Level, SaltSize as Argon2Salt}; +pub use self::argon2::Level as Argon2Level; +use self::argon2::SaltSize as Argon2Salt; -pub const METHOD_ARGON2I: &'static str = "argon2i"; +pub const METHOD_ARGON2I: &str = "argon2i"; +/// Supported KDF methods for generating or referencing a store key #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum KdfMethod { + /// Argon2i derivation method Argon2i(Argon2Level), } impl KdfMethod { - pub fn from_str(method: &str) -> Option<(Self, String)> { + pub(crate) fn decode(method: &str) -> Result<(Self, String), Error> { let mut method_and_detail = method.splitn(3, ':'); let prefix = method_and_detail.next(); - if prefix != Some(PREFIX_KDF) { - return None; - } - let method = method_and_detail.next().unwrap_or_default(); - let mut level_and_detail = method_and_detail.next().unwrap_or_default().splitn(2, '?'); - let level = level_and_detail.next().unwrap_or_default(); - let detail = level_and_detail.next().unwrap_or_default(); - match method { - METHOD_ARGON2I => { + if prefix == Some(PREFIX_KDF) { + let method = method_and_detail.next().unwrap_or_default(); + let mut level_and_detail = method_and_detail.next().unwrap_or_default().splitn(2, '?'); + let level = level_and_detail.next().unwrap_or_default(); + let detail = level_and_detail.next().unwrap_or_default(); + if method == METHOD_ARGON2I { if let Some(level) = Argon2Level::from_str(level) { - Some(( + return Ok(( Self::Argon2i(level), if detail.is_empty() { "".to_owned() } else { format!("?{}", detail) }, - )) - } else { - None + )); } } - _ => None, } + Err(err_msg!(Unsupported, "Invalid key derivation method")) } - pub fn to_string(&self, detail: Option<&str>) -> String { + pub(crate) fn encode(&self, detail: Option<&str>) -> String { match self { Self::Argon2i(level) => format!( "{}:{}:{}{}", @@ -57,23 +57,23 @@ impl KdfMethod { } } - pub fn derive_new_key(&self, password: &str) -> Result<(StoreKey, String), Error> { + pub(crate) fn derive_new_key(&self, password: &str) -> Result<(StoreKey, String), Error> { match self { Self::Argon2i(level) => { let salt = level.generate_salt(); let key = level.derive_key(password.as_bytes(), salt.as_ref())?; let detail = format!("?salt={}", salt.as_hex()); - Ok((key.into(), detail)) + Ok((key, detail)) } } } - pub fn derive_key(&self, password: &str, detail: &str) -> Result { + pub(crate) fn derive_key(&self, password: &str, detail: &str) -> Result { match self { Self::Argon2i(level) => { let salt = parse_salt::(detail)?; let key = level.derive_key(password.as_bytes(), salt.as_ref())?; - Ok(key.into()) + Ok(key) } } } diff --git a/src/protect/mod.rs b/askar-storage/src/protect/mod.rs similarity index 88% rename from src/protect/mod.rs rename to askar-storage/src/protect/mod.rs index e3cf6ce9..9ec5d942 100644 --- a/src/protect/mod.rs +++ b/askar-storage/src/protect/mod.rs @@ -1,10 +1,12 @@ +//! Storage encryption + use std::{collections::HashMap, sync::Arc}; use async_lock::RwLock; pub mod kdf; -mod hmac_key; +pub mod hmac_key; mod pass_key; pub use self::pass_key::PassKey; @@ -17,9 +19,9 @@ pub use self::store_key::{generate_raw_store_key, StoreKey, StoreKeyMethod, Stor use crate::{ crypto::buffer::SecretBytes, + entry::{EncEntryTag, EntryTag}, error::Error, future::unblock, - storage::{EncEntryTag, EntryTag}, }; pub type ProfileId = i64; @@ -44,7 +46,7 @@ impl KeyCache { let data = store_key .unwrap_data(ciphertext) .map_err(err_map!(Encryption, "Error decrypting profile key"))?; - Ok(ProfileKey::from_slice(data.as_ref())?) + ProfileKey::from_slice(data.as_ref()) }) .await } @@ -112,13 +114,13 @@ impl EntryEncryptor for NullEncryptor { .into_iter() .map(|tag| match tag { EntryTag::Encrypted(name, value) => EncEntryTag { - name: name.into_bytes().into(), - value: value.into_bytes().into(), + name: name.into_bytes(), + value: value.into_bytes(), plaintext: false, }, EntryTag::Plaintext(name, value) => EncEntryTag { - name: name.into_bytes().into(), - value: value.into_bytes().into(), + name: name.into_bytes(), + value: value.into_bytes(), plaintext: true, }, }) @@ -126,10 +128,10 @@ impl EntryEncryptor for NullEncryptor { } fn decrypt_entry_category(&self, enc_category: Vec) -> Result { - Ok(String::from_utf8(enc_category).map_err(err_map!(Encryption))?) + String::from_utf8(enc_category).map_err(err_map!(Encryption)) } fn decrypt_entry_name(&self, enc_name: Vec) -> Result { - Ok(String::from_utf8(enc_name).map_err(err_map!(Encryption))?) + String::from_utf8(enc_name).map_err(err_map!(Encryption)) } fn decrypt_entry_value( &self, @@ -140,7 +142,7 @@ impl EntryEncryptor for NullEncryptor { Ok(enc_value.into()) } fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error> { - Ok(enc_tags.into_iter().try_fold(vec![], |mut acc, tag| { + enc_tags.into_iter().try_fold(vec![], |mut acc, tag| { let name = String::from_utf8(tag.name).map_err(err_map!(Encryption))?; let value = String::from_utf8(tag.value).map_err(err_map!(Encryption))?; acc.push(if tag.plaintext { @@ -149,6 +151,6 @@ impl EntryEncryptor for NullEncryptor { EntryTag::Encrypted(name, value) }); Result::<_, Error>::Ok(acc) - })?) + }) } } diff --git a/src/protect/pass_key.rs b/askar-storage/src/protect/pass_key.rs similarity index 82% rename from src/protect/pass_key.rs rename to askar-storage/src/protect/pass_key.rs index 83901d6c..544bc530 100644 --- a/src/protect/pass_key.rs +++ b/askar-storage/src/protect/pass_key.rs @@ -8,10 +8,10 @@ use std::{ }; /// A possibly-empty password or key used to derive a store key -#[derive(Clone)] +#[derive(Clone, Default)] pub struct PassKey<'a>(Option>); -impl PassKey<'_> { +impl<'a> PassKey<'a> { /// Create a scoped reference to the passkey pub fn as_ref(&self) -> PassKey<'_> { PassKey(Some(Cow::Borrowed(&**self))) @@ -26,7 +26,8 @@ impl PassKey<'_> { self.0.is_none() } - pub(crate) fn into_owned(self) -> PassKey<'static> { + /// Convert to an owned instance, allocating if necessary + pub fn into_owned(self) -> PassKey<'static> { let mut slf = ManuallyDrop::new(self); let val = slf.0.take(); PassKey(match val { @@ -40,19 +41,13 @@ impl PassKey<'_> { impl Debug for PassKey<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if cfg!(test) { - f.debug_tuple("PassKey").field(&*self).finish() + f.debug_tuple("PassKey").field(&self.0).finish() } else { f.debug_tuple("PassKey").field(&"").finish() } } } -impl Default for PassKey<'_> { - fn default() -> Self { - Self(None) - } -} - impl Deref for PassKey<'_> { type Target = str; @@ -90,18 +85,15 @@ impl<'a> From> for PassKey<'a> { impl<'a, 'b> PartialEq> for PassKey<'a> { fn eq(&self, other: &PassKey<'b>) -> bool { - &**self == &**other + **self == **other } } impl Eq for PassKey<'_> {} impl Zeroize for PassKey<'_> { fn zeroize(&mut self) { - match self.0.take() { - Some(Cow::Owned(mut s)) => { - s.zeroize(); - } - _ => (), + if let Some(Cow::Owned(mut s)) = self.0.take() { + s.zeroize(); } } } diff --git a/src/protect/profile_key.rs b/askar-storage/src/protect/profile_key.rs similarity index 97% rename from src/protect/profile_key.rs rename to askar-storage/src/protect/profile_key.rs index c8d0e873..fd4b29fb 100644 --- a/src/protect/profile_key.rs +++ b/askar-storage/src/protect/profile_key.rs @@ -12,8 +12,8 @@ use crate::{ kdf::FromKeyDerivation, repr::KeyGen, }, + entry::{EncEntryTag, EntryTag}, error::Error, - storage::{EncEntryTag, EntryTag}, }; pub type ProfileKey = ProfileKeyImpl, HmacKey>; @@ -210,7 +210,7 @@ where let name = self.encrypt_tag_name(name.into())?; Ok(EncEntryTag { name, - value: value.into_bytes().into(), + value: value.into_bytes(), plaintext: true, }) } @@ -250,12 +250,13 @@ fn decode_utf8(value: Vec) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::storage::Entry; + use crate::entry::{Entry, EntryKind}; #[test] fn encrypt_entry_round_trip() { let key = ProfileKey::new().unwrap(); let test_record = Entry::new( + EntryKind::Item, "category", "name", "value", @@ -274,7 +275,7 @@ mod tests { .encrypt_entry_value( test_record.category.as_bytes(), test_record.name.as_bytes(), - test_record.value.clone().into(), + test_record.value.clone(), ) .unwrap(); let enc_tags = key.encrypt_entry_tags(test_record.tags.clone()).unwrap(); @@ -283,6 +284,7 @@ mod tests { assert_ne!(test_record.value, enc_value); let cmp_record = Entry::new( + EntryKind::Item, key.decrypt_entry_category(enc_category).unwrap(), key.decrypt_entry_name(enc_name).unwrap(), key.decrypt_entry_value( diff --git a/src/protect/store_key.rs b/askar-storage/src/protect/store_key.rs similarity index 84% rename from src/protect/store_key.rs rename to askar-storage/src/protect/store_key.rs index b2df9c92..b365dbe3 100644 --- a/src/protect/store_key.rs +++ b/askar-storage/src/protect/store_key.rs @@ -12,9 +12,9 @@ use crate::{ error::Error, }; -pub const PREFIX_KDF: &'static str = "kdf"; -pub const PREFIX_RAW: &'static str = "raw"; -pub const PREFIX_NONE: &'static str = "none"; +pub const PREFIX_KDF: &str = "kdf"; +pub const PREFIX_RAW: &str = "raw"; +pub const PREFIX_NONE: &str = "none"; pub type StoreKeyType = Chacha20Key; @@ -33,7 +33,7 @@ pub fn generate_raw_store_key(seed: Option<&[u8]>) -> Result, E pub fn parse_raw_store_key(raw_key: &str) -> Result { ArrayKey::<::KeySize>::temp(|key| { let key_len = bs58::decode(raw_key) - .into(&mut *key) + .onto(key.as_mut_slice()) .map_err(|_| err_msg!(Input, "Error parsing raw key as base58 value"))?; if key_len != key.len() { Err(err_msg!(Input, "Incorrect length for encoded raw key")) @@ -55,6 +55,7 @@ impl StoreKey { Ok(Self(Some(StoreKeyType::random()?))) } + #[allow(unused)] pub fn is_empty(&self) -> bool { self.0.is_none() } @@ -113,16 +114,17 @@ pub enum StoreKeyMethod { } impl StoreKeyMethod { - pub(crate) fn parse_uri(uri: &str) -> Result { + /// Parse a URI string into a store key method + pub fn parse_uri(uri: &str) -> Result { let mut prefix_and_detail = uri.splitn(2, ':'); let prefix = prefix_and_detail.next().unwrap_or_default(); // let detail = prefix_and_detail.next().unwrap_or_default(); match prefix { PREFIX_RAW => Ok(Self::RawKey), - PREFIX_KDF => match KdfMethod::from_str(uri) { - Some((method, _)) => Ok(Self::DeriveKey(method)), - None => Err(err_msg!(Unsupported, "Invalid key derivation method")), - }, + PREFIX_KDF => { + let (method, _) = KdfMethod::decode(uri)?; + Ok(Self::DeriveKey(method)) + } PREFIX_NONE => Ok(Self::Unprotected), _ => Err(err_msg!(Unsupported, "Invalid store key method")), } @@ -137,7 +139,7 @@ impl StoreKeyMethod { // Self::ExistingManagedKey(String) => unimplemented!(), Self::DeriveKey(method) => { if !pass_key.is_none() { - let (key, detail) = method.derive_new_key(&*pass_key)?; + let (key, detail) = method.derive_new_key(&pass_key)?; let key_ref = StoreKeyReference::DeriveKey(*method, detail); Ok((key, key_ref)) } else { @@ -146,7 +148,7 @@ impl StoreKeyMethod { } Self::RawKey => { let key = if !pass_key.is_empty() { - parse_raw_store_key(&*pass_key)? + parse_raw_store_key(&pass_key)? } else { StoreKey::random()? }; @@ -163,6 +165,16 @@ impl Default for StoreKeyMethod { } } +impl From for StoreKeyMethod { + fn from(key_ref: StoreKeyReference) -> Self { + match key_ref { + StoreKeyReference::DeriveKey(method, _) => Self::DeriveKey(method), + StoreKeyReference::RawKey => Self::RawKey, + StoreKeyReference::Unprotected => Self::Unprotected, + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum StoreKeyReference { // ManagedKey(String), @@ -177,13 +189,10 @@ impl StoreKeyReference { let prefix = prefix_and_detail.next().unwrap_or_default(); match prefix { PREFIX_RAW => Ok(Self::RawKey), - PREFIX_KDF => match KdfMethod::from_str(uri) { - Some((method, detail)) => Ok(Self::DeriveKey(method, detail)), - None => Err(err_msg!( - Unsupported, - "Invalid key derivation method for reference" - )), - }, + PREFIX_KDF => { + let (method, detail) = KdfMethod::decode(uri)?; + Ok(Self::DeriveKey(method, detail)) + } PREFIX_NONE => Ok(Self::Unprotected), _ => Err(err_msg!( Unsupported, @@ -195,10 +204,9 @@ impl StoreKeyReference { pub fn compare_method(&self, method: &StoreKeyMethod) -> bool { match self { // Self::ManagedKey(_keyref) => matches!(method, WrapKeyMethod::CreateManagedKey(..)), - Self::DeriveKey(kdf_method, _detail) => match method { - StoreKeyMethod::DeriveKey(m) if m == kdf_method => true, - _ => false, - }, + Self::DeriveKey(kdf_method, _detail) => { + matches!(method, StoreKeyMethod::DeriveKey(m) if m == kdf_method) + } Self::RawKey => *method == StoreKeyMethod::RawKey, Self::Unprotected => *method == StoreKeyMethod::Unprotected, } @@ -207,7 +215,7 @@ impl StoreKeyReference { pub fn into_uri(self) -> String { match self { // Self::ManagedKey(keyref) => keyref, - Self::DeriveKey(method, detail) => method.to_string(Some(detail.as_str())), + Self::DeriveKey(method, detail) => method.encode(Some(detail.as_str())), Self::RawKey => PREFIX_RAW.to_string(), Self::Unprotected => PREFIX_NONE.to_string(), } @@ -218,14 +226,14 @@ impl StoreKeyReference { // Self::ManagedKey(_key_ref) => unimplemented!(), Self::DeriveKey(method, detail) => { if !pass_key.is_none() { - method.derive_key(&*pass_key, detail) + method.derive_key(&pass_key, detail) } else { Err(err_msg!(Input, "Key derivation password not provided")) } } Self::RawKey => { if !pass_key.is_empty() { - parse_raw_store_key(&*pass_key) + parse_raw_store_key(&pass_key) } else { Err(err_msg!(Input, "Encoded raw key not provided")) } @@ -272,7 +280,7 @@ mod tests { let unwrapped = key.unwrap_data(wrapped).expect("Error unwrapping data"); assert_eq!(unwrapped, &input[..]); let key_uri = key_ref.into_uri(); - assert_eq!(key_uri.starts_with("kdf:argon2i:13:mod?salt="), true); + assert!(key_uri.starts_with("kdf:argon2i:13:mod?salt=")); } #[test] @@ -304,7 +312,19 @@ mod tests { .resolve("not my pass".into()) .expect("Error deriving comparison key"); let unwrapped_err = check_bad_pass.unwrap_data(wrapped); - assert_eq!(unwrapped_err.is_err(), true); + assert!(unwrapped_err.is_err()); + } + + #[test] + fn raw_key_seed_lengths() { + // 'short' is less than 32 bytes + let _ = generate_raw_store_key(Some(b"short key")) + .expect("Error creating raw key from short seed"); + // 'long' is greater than 32 bytes + let _ = generate_raw_store_key(Some( + b"long key long key long key long key long key long key long key", + )) + .expect("Error creating raw key from long seed"); } #[test] @@ -315,7 +335,7 @@ mod tests { let (key, key_ref) = StoreKeyMethod::RawKey .resolve(raw_key.as_ref()) .expect("Error resolving raw key"); - assert_eq!(key.is_empty(), false); + assert!(!key.is_empty()); let wrapped = key .wrap_data((&input[..]).into()) .expect("Error wrapping input"); @@ -330,10 +350,10 @@ mod tests { assert_eq!(unwrapped, &input[..]); let check_no_key = key_ref.resolve(None.into()); - assert_eq!(check_no_key.is_err(), true); + assert!(check_no_key.is_err()); let check_bad_key = key_ref.resolve("not the key".into()); - assert_eq!(check_bad_key.is_err(), true); + assert!(check_bad_key.is_err()); } #[test] @@ -342,7 +362,7 @@ mod tests { let (key, key_ref) = StoreKeyMethod::Unprotected .resolve(None.into()) .expect("Error resolving unprotected"); - assert_eq!(key.is_empty(), true); + assert!(key.is_empty()); let wrapped = key .wrap_data((&input[..]).into()) .expect("Error wrapping unprotected"); diff --git a/askar-storage/src/wql/mod.rs b/askar-storage/src/wql/mod.rs new file mode 100644 index 00000000..1ae7505e --- /dev/null +++ b/askar-storage/src/wql/mod.rs @@ -0,0 +1,5 @@ +mod query; +pub use query::{AbstractQuery, Query}; + +pub mod sql; +pub mod tags; diff --git a/askar-storage/src/wql/query.rs b/askar-storage/src/wql/query.rs new file mode 100644 index 00000000..7b31cbea --- /dev/null +++ b/askar-storage/src/wql/query.rs @@ -0,0 +1,2965 @@ +//! Askar WQL (wallet query language) parsing and optimization + +#![deny(missing_debug_implementations, missing_docs)] + +/// An abstract query representation over a key and value type +#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum AbstractQuery { + /// Logical AND of multiple clauses + And(Vec), + /// Logical OR of multiple clauses + Or(Vec), + /// Negation of a clause + Not(Box), + /// Equality comparison for a field value + Eq(K, V), + /// Inequality comparison for a field value + Neq(K, V), + /// Greater-than comparison for a field value + Gt(K, V), + /// Greater-than-or-equal comparison for a field value + Gte(K, V), + /// Less-than comparison for a field value + Lt(K, V), + /// Less-than-or-equal comparison for a field value + Lte(K, V), + /// SQL 'LIKE'-compatible string comparison for a field value + Like(K, V), + /// Match one of multiple field values in a set + In(K, Vec), + /// Match any non-null field value of the given field names + Exist(Vec), +} + +/// A concrete query implementation with String keys and values +pub type Query = AbstractQuery; + +impl AbstractQuery { + /// Perform basic query clause optimization + pub fn optimise(self) -> Option { + match self { + Self::Not(boxed_query) => match boxed_query.optimise() { + None => None, + Some(Self::Not(nested_query)) => Some(*nested_query), + Some(other) => Some(Self::Not(Box::new(other))), + }, + Self::And(subqueries) => { + let mut subqueries: Vec = + subqueries.into_iter().filter_map(Self::optimise).collect(); + + match subqueries.len() { + 0 => None, + 1 => Some(subqueries.remove(0)), + _ => Some(Self::And(subqueries)), + } + } + Self::Or(subqueries) => { + let mut subqueries: Vec = + subqueries.into_iter().filter_map(Self::optimise).collect(); + + match subqueries.len() { + 0 => None, + 1 => Some(subqueries.remove(0)), + _ => Some(Self::Or(subqueries)), + } + } + Self::In(key, mut targets) if targets.len() == 1 => { + Some(Self::Eq(key, targets.remove(0))) + } + other => Some(other), + } + } + + /// Perform a transformation on all field names in query clauses + pub fn map_names( + self, + mut f: impl FnMut(K) -> Result, + ) -> Result, E> { + self.map(&mut f, &mut |_k, v| Ok(v)) + } + + /// Perform a transformation on all field values in query clauses + pub fn map_values( + self, + mut f: impl FnMut(&K, V) -> Result, + ) -> Result, E> { + self.map(&mut |k| Ok(k), &mut f) + } + + /// Transform all query clauses using field name and value conversions + pub fn map( + self, + kf: &mut KF, + vf: &mut VF, + ) -> Result, E> + where + KF: FnMut(K) -> Result, + VF: FnMut(&K, V) -> Result, + { + match self { + Self::Eq(tag_name, tag_value) => { + let tag_value = vf(&tag_name, tag_value)?; + Ok(AbstractQuery::::Eq(kf(tag_name)?, tag_value)) + } + Self::Neq(tag_name, tag_value) => { + let tag_value = vf(&tag_name, tag_value)?; + Ok(AbstractQuery::::Neq(kf(tag_name)?, tag_value)) + } + Self::Gt(tag_name, tag_value) => { + let tag_value = vf(&tag_name, tag_value)?; + Ok(AbstractQuery::::Gt(kf(tag_name)?, tag_value)) + } + Self::Gte(tag_name, tag_value) => { + let tag_value = vf(&tag_name, tag_value)?; + Ok(AbstractQuery::::Gte(kf(tag_name)?, tag_value)) + } + Self::Lt(tag_name, tag_value) => { + let tag_value = vf(&tag_name, tag_value)?; + Ok(AbstractQuery::::Lt(kf(tag_name)?, tag_value)) + } + Self::Lte(tag_name, tag_value) => { + let tag_value = vf(&tag_name, tag_value)?; + Ok(AbstractQuery::::Lte(kf(tag_name)?, tag_value)) + } + Self::Like(tag_name, tag_value) => { + let tag_value = vf(&tag_name, tag_value)?; + Ok(AbstractQuery::::Like(kf(tag_name)?, tag_value)) + } + Self::In(tag_name, tag_values) => { + let tag_values = tag_values + .into_iter() + .map(|value| vf(&tag_name, value)) + .collect::, E>>()?; + Ok(AbstractQuery::::In(kf(tag_name)?, tag_values)) + } + Self::Exist(tag_names) => Ok(AbstractQuery::::Exist( + tag_names.into_iter().try_fold(vec![], |mut v, tag_name| { + v.push(kf(tag_name)?); + Result::<_, E>::Ok(v) + })?, + )), + Self::And(subqueries) => { + let subqueries = subqueries + .into_iter() + .map(|query| query.map(kf, vf)) + .collect::, E>>()?; + Ok(AbstractQuery::::And(subqueries)) + } + Self::Or(subqueries) => { + let subqueries = subqueries + .into_iter() + .map(|query| query.map(kf, vf)) + .collect::, E>>()?; + Ok(AbstractQuery::::Or(subqueries)) + } + Self::Not(boxed_query) => Ok(AbstractQuery::::Not(Box::new( + boxed_query.map(kf, vf)?, + ))), + } + } +} + +impl Default for AbstractQuery { + fn default() -> Self { + Self::And(Vec::new()) + } +} + +mod serde_support { + use std::string; + + use serde::ser::{Serialize, Serializer}; + use serde::{de, Deserialize, Deserializer}; + use serde_json::{self, json, Value as JsonValue}; + + use super::{AbstractQuery, Query}; + + impl Serialize for AbstractQuery + where + for<'a> &'a K: Into, + V: Serialize, + { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_value().serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for Query { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let v = JsonValue::deserialize(deserializer)?; + + match v { + JsonValue::Object(map) => parse_query(map).map_err(de::Error::missing_field), + JsonValue::Array(array) => { + // cast old restrictions format to wql + let mut res: Vec = Vec::new(); + for sub_query in array { + let sub_query: serde_json::Map = sub_query + .as_object() + .ok_or_else(|| de::Error::custom("Restriction is invalid"))? + .clone() + .into_iter() + .filter(|(_, v)| !v.is_null()) + .collect(); + + if !sub_query.is_empty() { + res.push(JsonValue::Object(sub_query)); + } + } + + let mut map = serde_json::Map::new(); + map.insert("$or".to_string(), JsonValue::Array(res)); + + parse_query(map).map_err(de::Error::custom) + } + _ => Err(de::Error::missing_field( + "Restriction must be either object or array", + )), + } + } + } + + impl AbstractQuery + where + for<'a> &'a K: Into, + V: Serialize, + { + fn to_value(&self) -> JsonValue { + match self { + Self::Eq(ref tag_name, ref tag_value) => json!({ tag_name: tag_value }), + Self::Neq(ref tag_name, ref tag_value) => json!({tag_name: {"$neq": tag_value}}), + Self::Gt(ref tag_name, ref tag_value) => json!({tag_name: {"$gt": tag_value}}), + Self::Gte(ref tag_name, ref tag_value) => json!({tag_name: {"$gte": tag_value}}), + Self::Lt(ref tag_name, ref tag_value) => json!({tag_name: {"$lt": tag_value}}), + Self::Lte(ref tag_name, ref tag_value) => json!({tag_name: {"$lte": tag_value}}), + Self::Like(ref tag_name, ref tag_value) => json!({tag_name: {"$like": tag_value}}), + Self::In(ref tag_name, ref tag_values) => json!({tag_name: {"$in":tag_values}}), + Self::Exist(ref tag_names) => { + json!({ "$exist": tag_names.iter().map(Into::into).collect::>() }) + } + Self::And(ref queries) => { + if queries.is_empty() { + json!({}) + } else { + json!({ + "$and": queries.iter().map(Self::to_value).collect::>() + }) + } + } + Self::Or(ref queries) => { + if queries.is_empty() { + json!({}) + } else { + json!({ + "$or": queries.iter().map(Self::to_value).collect::>() + }) + } + } + Self::Not(ref query) => json!({"$not": query.to_value()}), + } + } + } + + impl string::ToString for Query { + fn to_string(&self) -> String { + self.to_value().to_string() + } + } + + fn parse_query(map: serde_json::Map) -> Result { + let mut operators: Vec = Vec::new(); + + for (key, value) in map { + if let Some(operator_) = parse_operator(key, value)? { + operators.push(operator_); + } + } + + let query = if operators.len() == 1 { + operators.remove(0) + } else { + Query::And(operators) + }; + + Ok(query) + } + + fn parse_operator(key: String, value: JsonValue) -> Result, &'static str> { + match (key.as_str(), value) { + ("$and", JsonValue::Array(values)) => { + if values.is_empty() { + Ok(None) + } else { + let operators: Vec = parse_list_operators(values)?; + Ok(Some(Query::And(operators))) + } + } + ("$and", _) => Err("$and must be array of JSON objects"), + ("$or", JsonValue::Array(values)) => { + if values.is_empty() { + Ok(None) + } else { + let operators: Vec = parse_list_operators(values)?; + Ok(Some(Query::Or(operators))) + } + } + ("$or", _) => Err("$or must be array of JSON objects"), + ("$not", JsonValue::Object(map)) => { + let operator = parse_query(map)?; + Ok(Some(Query::Not(Box::new(operator)))) + } + ("$not", _) => Err("$not must be JSON object"), + ("$exist", JsonValue::String(key)) => Ok(Some(Query::Exist(vec![key]))), + ("$exist", JsonValue::Array(keys)) => { + if keys.is_empty() { + Ok(None) + } else { + let mut ks = vec![]; + for key in keys { + if let JsonValue::String(key) = key { + ks.push(key); + } else { + return Err("$exist must be used with a string or array of strings"); + } + } + Ok(Some(Query::Exist(ks))) + } + } + ("$exist", _) => Err("$exist must be used with a string or array of strings"), + (_, JsonValue::String(value)) => Ok(Some(Query::Eq(key, value))), + (_, JsonValue::Object(map)) => { + if map.len() == 1 { + let (operator_name, value) = map.into_iter().next().unwrap(); + parse_single_operator(operator_name.as_str(), key, value).map(Some) + } else { + Err("value must be JSON object of length 1") + } + } + (_, _) => Err("Unsupported value"), + } + } + + fn parse_list_operators(operators: Vec) -> Result, &'static str> { + let mut out_operators: Vec = Vec::with_capacity(operators.len()); + + for value in operators { + if let JsonValue::Object(map) = value { + let subquery = parse_query(map)?; + out_operators.push(subquery); + } else { + return Err("operator must be array of JSON objects"); + } + } + + Ok(out_operators) + } + + fn parse_single_operator( + operator_name: &str, + key: String, + value: JsonValue, + ) -> Result { + match (operator_name, value) { + ("$neq", JsonValue::String(value_)) => Ok(Query::Neq(key, value_)), + ("$neq", _) => Err("$neq must be used with string"), + ("$gt", JsonValue::String(value_)) => Ok(Query::Gt(key, value_)), + ("$gt", _) => Err("$gt must be used with string"), + ("$gte", JsonValue::String(value_)) => Ok(Query::Gte(key, value_)), + ("$gte", _) => Err("$gte must be used with string"), + ("$lt", JsonValue::String(value_)) => Ok(Query::Lt(key, value_)), + ("$lt", _) => Err("$lt must be used with string"), + ("$lte", JsonValue::String(value_)) => Ok(Query::Lte(key, value_)), + ("$lte", _) => Err("$lte must be used with string"), + ("$like", JsonValue::String(value_)) => Ok(Query::Like(key, value_)), + ("$like", _) => Err("$like must be used with string"), + ("$in", JsonValue::Array(values)) => { + let mut target_values: Vec = Vec::with_capacity(values.len()); + + for v in values { + if let JsonValue::String(s) = v { + target_values.push(s); + } else { + return Err("$in must be used with array of strings"); + } + } + + Ok(Query::In(key, target_values)) + } + ("$in", _) => Err("$in must be used with array of strings"), + (_, _) => Err("Unknown operator"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::distributions::Alphanumeric; + use rand::{thread_rng, Rng}; + use serde_json::json; + + fn _random_string(len: usize) -> String { + String::from_utf8(thread_rng().sample_iter(&Alphanumeric).take(len).collect()).unwrap() + } + + /// parse + #[test] + fn test_simple_operator_empty_json_parse() { + let json = "{}"; + + let query: Query = ::serde_json::from_str(json).unwrap(); + + let expected = Query::And(vec![]); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_explicit_empty_and_parse() { + let json = r#"{"$and":[]}"#; + + let query: Query = ::serde_json::from_str(json).unwrap(); + + let expected = Query::And(vec![]); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_empty_or_parse() { + let json = r#"{"$or":[]}"#; + + let query: Query = ::serde_json::from_str(json).unwrap(); + + let expected = Query::And(vec![]); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_empty_not_parse() { + let json = r#"{"$not":{}}"#; + + let query: Query = ::serde_json::from_str(json).unwrap(); + + let expected = Query::Not(Box::new(Query::And(vec![]))); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_eq_plaintext_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"{}":"{}"}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Eq(name1, value1); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_neq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"{}":{{"$neq":"{}"}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Neq(name1, value1); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_gt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"{}":{{"$gt":"{}"}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Gt(name1, value1); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_gte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"{}":{{"$gte":"{}"}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Gte(name1, value1); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_lt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"{}":{{"$lt":"{}"}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Lt(name1, value1); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_lte_plaintext_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"{}":{{"$lte":"{}"}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Lte(name1, value1); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_like_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"{}":{{"$like":"{}"}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Like(name1, value1); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_in_plaintext_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"{}":{{"$in":["{}"]}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::In(name1, vec![value1]); + + assert_eq!(query, expected); + } + + #[test] + fn test_simple_operator_in_plaintexts_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let value2 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"{}":{{"$in":["{}","{}","{}"]}}}}"#, + name1, value1, value2, value3 + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::In(name1, vec![value1, value2, value3]); + + assert_eq!(query, expected); + } + + #[test] + fn test_exist_parse_string() { + let name1 = _random_string(10); + + let json = format!(r#"{{"$exist":"{}"}}"#, name1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Exist(vec![name1]); + + assert_eq!(query, expected); + } + + #[test] + fn test_exist_parse_array() { + let name1 = _random_string(10); + let name2 = _random_string(10); + + let json = format!(r#"{{"$exist":["{}","{}"]}}"#, name1, name2); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Exist(vec![name1, name2]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_exist() { + let name1 = _random_string(10); + let name2 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"$exist":"{}"}},{{"$exist":"{}"}}]}}"#, + name1, name2 + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::Exist(vec![name1]), Query::Exist(vec![name2])]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_one_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$and":[{{"{}":"{}"}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::Eq(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_one_neq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$and":[{{"{}":{{"$neq":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::Neq(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_one_gt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$and":[{{"{}":{{"$gt":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::Gt(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_one_gte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$and":[{{"{}":{{"$gte":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::Gte(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_one_lt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$and":[{{"{}":{{"$lt":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::Lt(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_one_lte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$and":[{{"{}":{{"$lte":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::Lte(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_one_like_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$and":[{{"{}":{{"$like":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::Like(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_one_in_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$and":[{{"{}":{{"$in":["{}"]}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::In(name1, vec![value1])]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_one_not_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$and":[{{"$not":{{"{}":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![Query::Not(Box::new(Query::Eq(name1, value1)))]); + + assert_eq!(query, expected); + } + + #[test] + fn test_short_and_with_multiple_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"{}":"{}","{}":"{}","{}":"{}"}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + let mut clauses = vec![ + Query::Eq(name1, value1), + Query::Eq(name2, value2), + Query::Eq(name3, value3), + ]; + clauses.sort(); + + let expected = Query::And(clauses); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"{}":"{}"}},{{"{}":"{}"}},{{"{}":"{}"}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::Eq(name1, value1), + Query::Eq(name2, value2), + Query::Eq(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_neq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$neq":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::Neq(name1, value1), + Query::Neq(name2, value2), + Query::Neq(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_gt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gt":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::Gt(name1, value1), + Query::Gt(name2, value2), + Query::Gt(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_gte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$gte":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::Gte(name1, value1), + Query::Gte(name2, value2), + Query::Gte(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_lt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lt":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::Lt(name1, value1), + Query::Lt(name2, value2), + Query::Lt(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_lte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$lte":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::Lte(name1, value1), + Query::Lte(name2, value2), + Query::Lte(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_like_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$like":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::Like(name1, value1), + Query::Like(name2, value2), + Query::Like(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_in_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"{}":{{"$in":["{}"]}}}},{{"{}":{{"$in":["{}"]}}}},{{"{}":{{"$in":["{}"]}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::In(name1, vec![value1]), + Query::In(name2, vec![value2]), + Query::In(name3, vec![value3]), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_not_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"$not":{{"{}":"{}"}}}},{{"$not":{{"{}":"{}"}}}},{{"$not":{{"{}":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::Not(Box::new(Query::Eq(name1, value1))), + Query::Not(Box::new(Query::Eq(name2, value2))), + Query::Not(Box::new(Query::Eq(name3, value3))), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_with_multiple_mixed_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + let name4 = _random_string(10); + let value4 = _random_string(10); + let name5 = _random_string(10); + let value5 = _random_string(10); + let name6 = _random_string(10); + let value6 = _random_string(10); + let name7 = _random_string(10); + let value7 = _random_string(10); + let name8 = _random_string(10); + let value8a = _random_string(10); + let value8b = _random_string(10); + let name9 = _random_string(10); + let value9 = _random_string(10); + + let json = format!( + r#"{{"$and":[{{"{}":"{}"}},{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$in":["{}","{}"]}}}},{{"$not":{{"{}":"{}"}}}}]}}"#, + name1, + value1, + name2, + value2, + name3, + value3, + name4, + value4, + name5, + value5, + name6, + value6, + name7, + value7, + name8, + value8a, + value8b, + name9, + value9, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::And(vec![ + Query::Eq(name1, value1), + Query::Neq(name2, value2), + Query::Gt(name3, value3), + Query::Gte(name4, value4), + Query::Lt(name5, value5), + Query::Lte(name6, value6), + Query::Like(name7, value7), + Query::In(name8, vec![value8a, value8b]), + Query::Not(Box::new(Query::Eq(name9, value9))), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_one_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$or":[{{"{}":"{}"}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Eq(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_one_neq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$or":[{{"{}":{{"$neq":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Neq(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_one_gt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$or":[{{"{}":{{"$gt":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Gt(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_one_gte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$or":[{{"{}":{{"$gte":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Gte(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_one_lt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$or":[{{"{}":{{"$lt":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Lt(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_one_lte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$or":[{{"{}":{{"$lte":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Lte(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_one_like_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$or":[{{"{}":{{"$like":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Like(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_one_in_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$or":[{{"{}":{{"$in":["{}"]}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::In(name1, vec![value1])]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_one_not_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$or":[{{"$not":{{"{}":"{}"}}}}]}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Not(Box::new(Query::Eq(name1, value1)))]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"{}":"{}"}},{{"{}":"{}"}},{{"{}":"{}"}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::Eq(name1, value1), + Query::Eq(name2, value2), + Query::Eq(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_neq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$neq":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::Neq(name1, value1), + Query::Neq(name2, value2), + Query::Neq(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_gt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gt":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::Gt(name1, value1), + Query::Gt(name2, value2), + Query::Gt(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_gte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$gte":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::Gte(name1, value1), + Query::Gte(name2, value2), + Query::Gte(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_lt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lt":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::Lt(name1, value1), + Query::Lt(name2, value2), + Query::Lt(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_lte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$lte":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::Lte(name1, value1), + Query::Lte(name2, value2), + Query::Lte(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_like_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$like":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::Like(name1, value1), + Query::Like(name2, value2), + Query::Like(name3, value3), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_in_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"{}":{{"$in":["{}"]}}}},{{"{}":{{"$in":["{}"]}}}},{{"{}":{{"$in":["{}"]}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::In(name1, vec![value1]), + Query::In(name2, vec![value2]), + Query::In(name3, vec![value3]), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_not_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"$not":{{"{}":"{}"}}}},{{"$not":{{"{}":"{}"}}}},{{"$not":{{"{}":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::Not(Box::new(Query::Eq(name1, value1))), + Query::Not(Box::new(Query::Eq(name2, value2))), + Query::Not(Box::new(Query::Eq(name3, value3))), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_or_with_multiple_mixed_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + let name4 = _random_string(10); + let value4 = _random_string(10); + let name5 = _random_string(10); + let value5 = _random_string(10); + let name6 = _random_string(10); + let value6 = _random_string(10); + let name7 = _random_string(10); + let value7 = _random_string(10); + let name8 = _random_string(10); + let value8a = _random_string(10); + let value8b = _random_string(10); + let name9 = _random_string(10); + let value9 = _random_string(10); + + let json = format!( + r#"{{"$or":[{{"{}":"{}"}},{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$in":["{}","{}"]}}}},{{"$not":{{"{}":"{}"}}}}]}}"#, + name1, + value1, + name2, + value2, + name3, + value3, + name4, + value4, + name5, + value5, + name6, + value6, + name7, + value7, + name8, + value8a, + value8b, + name9, + value9, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![ + Query::Eq(name1, value1), + Query::Neq(name2, value2), + Query::Gt(name3, value3), + Query::Gte(name4, value4), + Query::Lt(name5, value5), + Query::Lte(name6, value6), + Query::Like(name7, value7), + Query::In(name8, vec![value8a, value8b]), + Query::Not(Box::new(Query::Eq(name9, value9))), + ]); + + assert_eq!(query, expected); + } + + #[test] + fn test_not_with_one_eq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$not":{{"{}":"{}"}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Not(Box::new(Query::Eq(name1, value1))); + + assert_eq!(query, expected); + } + + #[test] + fn test_not_with_one_neq_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$not":{{"{}":{{"$neq":"{}"}}}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Not(Box::new(Query::Neq(name1, value1))); + + assert_eq!(query, expected); + } + + #[test] + fn test_not_with_one_gt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$not":{{"{}":{{"$gt":"{}"}}}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Not(Box::new(Query::Gt(name1, value1))); + + assert_eq!(query, expected); + } + + #[test] + fn test_not_with_one_gte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$not":{{"{}":{{"$gte":"{}"}}}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Not(Box::new(Query::Gte(name1, value1))); + + assert_eq!(query, expected); + } + + #[test] + fn test_not_with_one_lt_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$not":{{"{}":{{"$lt":"{}"}}}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Not(Box::new(Query::Lt(name1, value1))); + + assert_eq!(query, expected); + } + + #[test] + fn test_not_with_one_lte_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$not":{{"{}":{{"$lte":"{}"}}}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Not(Box::new(Query::Lte(name1, value1))); + + assert_eq!(query, expected); + } + + #[test] + fn test_not_with_one_like_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$not":{{"{}":{{"$like":"{}"}}}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Not(Box::new(Query::Like(name1, value1))); + + assert_eq!(query, expected); + } + + #[test] + fn test_not_with_one_in_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let json = format!(r#"{{"$not":{{"{}":{{"$in":["{}"]}}}}}}"#, name1, value1); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Not(Box::new(Query::In(name1, vec![value1]))); + + assert_eq!(query, expected); + } + + #[test] + fn test_and_or_not_complex_case_parse() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + let name4 = _random_string(10); + let value4 = _random_string(10); + let name5 = _random_string(10); + let value5 = _random_string(10); + let name6 = _random_string(10); + let value6 = _random_string(10); + let name7 = _random_string(10); + let value7 = _random_string(10); + let name8 = _random_string(10); + let value8 = _random_string(10); + + let json = format!( + r#"{{"$not":{{"$and":[{{"{}":"{}"}},{{"$or":[{{"{}":{{"$gt":"{}"}}}},{{"$not":{{"{}":{{"$lte":"{}"}}}}}},{{"$and":[{{"{}":{{"$lt":"{}"}}}},{{"$not":{{"{}":{{"$gte":"{}"}}}}}}]}}]}},{{"$not":{{"{}":{{"$like":"{}"}}}}}},{{"$and":[{{"{}":"{}"}},{{"$not":{{"{}":{{"$neq":"{}"}}}}}}]}}]}}}}"#, + name1, + value1, + name2, + value2, + name3, + value3, + name4, + value4, + name5, + value5, + name6, + value6, + name7, + value7, + name8, + value8, + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Not(Box::new(Query::And(vec![ + Query::Eq(name1, value1), + Query::Or(vec![ + Query::Gt(name2, value2), + Query::Not(Box::new(Query::Lte(name3, value3))), + Query::And(vec![ + Query::Lt(name4, value4), + Query::Not(Box::new(Query::Gte(name5, value5))), + ]), + ]), + Query::Not(Box::new(Query::Like(name6, value6))), + Query::And(vec![ + Query::Eq(name7, value7), + Query::Not(Box::new(Query::Neq(name8, value8))), + ]), + ]))); + + assert_eq!(query, expected); + } + + /// to string + #[test] + fn test_simple_operator_empty_and_to_string() { + let query = Query::And(vec![]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = "{}"; + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_empty_or_to_string() { + let query = Query::Or(vec![]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = "{}"; + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_empty_not_to_string() { + let query = Query::Not(Box::new(Query::And(vec![]))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = r#"{"$not":{}}"#; + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Eq(name1.clone(), value1.clone()); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"{}":"{}"}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_neq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Neq(name1.clone(), value1.clone()); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"{}":{{"$neq":"{}"}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_gt_plaintext_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Gt(name1.clone(), value1.clone()); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"{}":{{"$gt":"{}"}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_gte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Gte(name1.clone(), value1.clone()); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"{}":{{"$gte":"{}"}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_lt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Lt(name1.clone(), value1.clone()); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"{}":{{"$lt":"{}"}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_lte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Lte(name1.clone(), value1.clone()); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"{}":{{"$lte":"{}"}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_like_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Like(name1.clone(), value1.clone()); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"{}":{{"$like":"{}"}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_in_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::In(name1.clone(), vec![value1.clone()]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"{}":{{"$in":["{}"]}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_simple_operator_in_multimply_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let value2 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::In( + name1.clone(), + vec![value1.clone(), value2.clone(), value3.clone()], + ); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"{}":{{"$in":["{}","{}","{}"]}}}}"#, + name1, value1, value2, value3 + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_one_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::And(vec![Query::Eq(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$and":[{{"{}":"{}"}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_one_neq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::And(vec![Query::Neq(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$and":[{{"{}":{{"$neq":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_one_gt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::And(vec![Query::Gt(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$and":[{{"{}":{{"$gt":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_one_gte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::And(vec![Query::Gte(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$and":[{{"{}":{{"$gte":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_one_lt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::And(vec![Query::Lt(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$and":[{{"{}":{{"$lt":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_one_lte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::And(vec![Query::Lte(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$and":[{{"{}":{{"$lte":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_one_like_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::And(vec![Query::Like(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$and":[{{"{}":{{"$like":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_one_in_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::And(vec![Query::In(name1.clone(), vec![value1.clone()])]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$and":[{{"{}":{{"$in":["{}"]}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_one_not_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::And(vec![Query::Not(Box::new(Query::Eq( + name1.clone(), + value1.clone(), + )))]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$and":[{{"$not":{{"{}":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::And(vec![ + Query::Eq(name1.clone(), value1.clone()), + Query::Eq(name2.clone(), value2.clone()), + Query::Eq(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"{}":"{}"}},{{"{}":"{}"}},{{"{}":"{}"}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_neq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::And(vec![ + Query::Neq(name1.clone(), value1.clone()), + Query::Neq(name2.clone(), value2.clone()), + Query::Neq(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$neq":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_gt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::And(vec![ + Query::Gt(name1.clone(), value1.clone()), + Query::Gt(name2.clone(), value2.clone()), + Query::Gt(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gt":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_gte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::And(vec![ + Query::Gte(name1.clone(), value1.clone()), + Query::Gte(name2.clone(), value2.clone()), + Query::Gte(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$gte":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_lt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::And(vec![ + Query::Lt(name1.clone(), value1.clone()), + Query::Lt(name2.clone(), value2.clone()), + Query::Lt(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lt":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_lte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::And(vec![ + Query::Lte(name1.clone(), value1.clone()), + Query::Lte(name2.clone(), value2.clone()), + Query::Lte(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$lte":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_like_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::And(vec![ + Query::Like(name1.clone(), value1.clone()), + Query::Like(name2.clone(), value2.clone()), + Query::Like(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$like":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_in_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::And(vec![ + Query::In(name1.clone(), vec![value1.clone()]), + Query::In(name2.clone(), vec![value2.clone()]), + Query::In(name3.clone(), vec![value3.clone()]), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"{}":{{"$in":["{}"]}}}},{{"{}":{{"$in":["{}"]}}}},{{"{}":{{"$in":["{}"]}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_not_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::And(vec![ + Query::Not(Box::new(Query::Eq(name1.clone(), value1.clone()))), + Query::Not(Box::new(Query::Eq(name2.clone(), value2.clone()))), + Query::Not(Box::new(Query::Eq(name3.clone(), value3.clone()))), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"$not":{{"{}":"{}"}}}},{{"$not":{{"{}":"{}"}}}},{{"$not":{{"{}":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_with_multiple_mixed_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + let name4 = _random_string(10); + let value4 = _random_string(10); + let name5 = _random_string(10); + let value5 = _random_string(10); + let name6 = _random_string(10); + let value6 = _random_string(10); + let name7 = _random_string(10); + let value7 = _random_string(10); + let name8 = _random_string(10); + let value8a = _random_string(10); + let value8b = _random_string(10); + let name9 = _random_string(10); + let value9 = _random_string(10); + + let query = Query::And(vec![ + Query::Eq(name1.clone(), value1.clone()), + Query::Neq(name2.clone(), value2.clone()), + Query::Gt(name3.clone(), value3.clone()), + Query::Gte(name4.clone(), value4.clone()), + Query::Lt(name5.clone(), value5.clone()), + Query::Lte(name6.clone(), value6.clone()), + Query::Like(name7.clone(), value7.clone()), + Query::In(name8.clone(), vec![value8a.clone(), value8b.clone()]), + Query::Not(Box::new(Query::Eq(name9.clone(), value9.clone()))), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$and":[{{"{}":"{}"}},{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$in":["{}","{}"]}}}},{{"$not":{{"{}":"{}"}}}}]}}"#, + name1, + value1, + name2, + value2, + name3, + value3, + name4, + value4, + name5, + value5, + name6, + value6, + name7, + value7, + name8, + value8a, + value8b, + name9, + value9, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_one_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Or(vec![Query::Eq(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$or":[{{"{}":"{}"}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_one_neq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Or(vec![Query::Neq(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$or":[{{"{}":{{"$neq":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_one_gt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Or(vec![Query::Gt(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$or":[{{"{}":{{"$gt":"{}"}}}}]}}"#, name1, value1); + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_one_gte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Or(vec![Query::Gte(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$or":[{{"{}":{{"$gte":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_one_lt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Or(vec![Query::Lt(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$or":[{{"{}":{{"$lt":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_one_lte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Or(vec![Query::Lte(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$or":[{{"{}":{{"$lte":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_one_like_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Or(vec![Query::Like(name1.clone(), value1.clone())]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$or":[{{"{}":{{"$like":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_one_in_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Or(vec![Query::In(name1.clone(), vec![value1.clone()])]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$or":[{{"{}":{{"$in":["{}"]}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_one_not_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Or(vec![Query::Not(Box::new(Query::Eq( + name1.clone(), + value1.clone(), + )))]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$or":[{{"$not":{{"{}":"{}"}}}}]}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::Or(vec![ + Query::Eq(name1.clone(), value1.clone()), + Query::Eq(name2.clone(), value2.clone()), + Query::Eq(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"{}":"{}"}},{{"{}":"{}"}},{{"{}":"{}"}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_neq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::Or(vec![ + Query::Neq(name1.clone(), value1.clone()), + Query::Neq(name2.clone(), value2.clone()), + Query::Neq(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$neq":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_gt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::Or(vec![ + Query::Gt(name1.clone(), value1.clone()), + Query::Gt(name2.clone(), value2.clone()), + Query::Gt(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gt":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_gte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::Or(vec![ + Query::Gte(name1.clone(), value1.clone()), + Query::Gte(name2.clone(), value2.clone()), + Query::Gte(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$gte":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_lt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::Or(vec![ + Query::Lt(name1.clone(), value1.clone()), + Query::Lt(name2.clone(), value2.clone()), + Query::Lt(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lt":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_lte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::Or(vec![ + Query::Lte(name1.clone(), value1.clone()), + Query::Lte(name2.clone(), value2.clone()), + Query::Lte(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$lte":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_like_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::Or(vec![ + Query::Like(name1.clone(), value1.clone()), + Query::Like(name2.clone(), value2.clone()), + Query::Like(name3.clone(), value3.clone()), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$like":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_in_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::Or(vec![ + Query::In(name1.clone(), vec![value1.clone()]), + Query::In(name2.clone(), vec![value2.clone()]), + Query::In(name3.clone(), vec![value3.clone()]), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"{}":{{"$in":["{}"]}}}},{{"{}":{{"$in":["{}"]}}}},{{"{}":{{"$in":["{}"]}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_not_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + + let query = Query::Or(vec![ + Query::Not(Box::new(Query::Eq(name1.clone(), value1.clone()))), + Query::Not(Box::new(Query::Eq(name2.clone(), value2.clone()))), + Query::Not(Box::new(Query::Eq(name3.clone(), value3.clone()))), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"$not":{{"{}":"{}"}}}},{{"$not":{{"{}":"{}"}}}},{{"$not":{{"{}":"{}"}}}}]}}"#, + name1, value1, name2, value2, name3, value3, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_or_with_multiple_mixed_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + let name4 = _random_string(10); + let value4 = _random_string(10); + let name5 = _random_string(10); + let value5 = _random_string(10); + let name6 = _random_string(10); + let value6 = _random_string(10); + let name7 = _random_string(10); + let value7 = _random_string(10); + let name8 = _random_string(10); + let value8a = _random_string(10); + let value8b = _random_string(10); + let name9 = _random_string(10); + let value9 = _random_string(10); + + let query = Query::Or(vec![ + Query::Eq(name1.clone(), value1.clone()), + Query::Neq(name2.clone(), value2.clone()), + Query::Gt(name3.clone(), value3.clone()), + Query::Gte(name4.clone(), value4.clone()), + Query::Lt(name5.clone(), value5.clone()), + Query::Lte(name6.clone(), value6.clone()), + Query::Like(name7.clone(), value7.clone()), + Query::In(name8.clone(), vec![value8a.clone(), value8b.clone()]), + Query::Not(Box::new(Query::Eq(name9.clone(), value9.clone()))), + ]); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$or":[{{"{}":"{}"}},{{"{}":{{"$neq":"{}"}}}},{{"{}":{{"$gt":"{}"}}}},{{"{}":{{"$gte":"{}"}}}},{{"{}":{{"$lt":"{}"}}}},{{"{}":{{"$lte":"{}"}}}},{{"{}":{{"$like":"{}"}}}},{{"{}":{{"$in":["{}","{}"]}}}},{{"$not":{{"{}":"{}"}}}}]}}"#, + name1, + value1, + name2, + value2, + name3, + value3, + name4, + value4, + name5, + value5, + name6, + value6, + name7, + value7, + name8, + value8a, + value8b, + name9, + value9, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_not_with_one_eq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Not(Box::new(Query::Eq(name1.clone(), value1.clone()))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$not":{{"{}":"{}"}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_not_with_one_neq_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Not(Box::new(Query::Neq(name1.clone(), value1.clone()))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$not":{{"{}":{{"$neq":"{}"}}}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_not_with_one_gt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Not(Box::new(Query::Gt(name1.clone(), value1.clone()))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$not":{{"{}":{{"$gt":"{}"}}}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_not_with_one_gte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Not(Box::new(Query::Gte(name1.clone(), value1.clone()))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$not":{{"{}":{{"$gte":"{}"}}}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_not_with_one_lt_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Not(Box::new(Query::Lt(name1.clone(), value1.clone()))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$not":{{"{}":{{"$lt":"{}"}}}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_not_with_one_lte_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Not(Box::new(Query::Lte(name1.clone(), value1.clone()))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$not":{{"{}":{{"$lte":"{}"}}}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_not_with_one_like_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Not(Box::new(Query::Like(name1.clone(), value1.clone()))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$not":{{"{}":{{"$like":"{}"}}}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_not_with_one_in_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + + let query = Query::Not(Box::new(Query::In(name1.clone(), vec![value1.clone()]))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!(r#"{{"$not":{{"{}":{{"$in":["{}"]}}}}}}"#, name1, value1); + + assert_eq!(json, expected); + } + + #[test] + fn test_and_or_not_complex_case_to_string() { + let name1 = _random_string(10); + let value1 = _random_string(10); + let name2 = _random_string(10); + let value2 = _random_string(10); + let name3 = _random_string(10); + let value3 = _random_string(10); + let name4 = _random_string(10); + let value4 = _random_string(10); + let name5 = _random_string(10); + let value5 = _random_string(10); + let name6 = _random_string(10); + let value6 = _random_string(10); + let name7 = _random_string(10); + let value7 = _random_string(10); + let name8 = _random_string(10); + let value8 = _random_string(10); + + let query = Query::Not(Box::new(Query::And(vec![ + Query::Eq(name1.clone(), value1.clone()), + Query::Or(vec![ + Query::Gt(name2.clone(), value2.clone()), + Query::Not(Box::new(Query::Lte(name3.clone(), value3.clone()))), + Query::And(vec![ + Query::Lt(name4.clone(), value4.clone()), + Query::Not(Box::new(Query::Gte(name5.clone(), value5.clone()))), + ]), + ]), + Query::Not(Box::new(Query::Like(name6.clone(), value6.clone()))), + Query::And(vec![ + Query::Eq(name7.clone(), value7.clone()), + Query::Not(Box::new(Query::Neq(name8.clone(), value8.clone()))), + ]), + ]))); + + let json = ::serde_json::to_string(&query).unwrap(); + + let expected = format!( + r#"{{"$not":{{"$and":[{{"{}":"{}"}},{{"$or":[{{"{}":{{"$gt":"{}"}}}},{{"$not":{{"{}":{{"$lte":"{}"}}}}}},{{"$and":[{{"{}":{{"$lt":"{}"}}}},{{"$not":{{"{}":{{"$gte":"{}"}}}}}}]}}]}},{{"$not":{{"{}":{{"$like":"{}"}}}}}},{{"$and":[{{"{}":"{}"}},{{"$not":{{"{}":{{"$neq":"{}"}}}}}}]}}]}}}}"#, + name1, + value1, + name2, + value2, + name3, + value3, + name4, + value4, + name5, + value5, + name6, + value6, + name7, + value7, + name8, + value8, + ); + + assert_eq!(json, expected); + } + + #[test] + fn test_old_format() { + let name1 = _random_string(10); + let name2 = _random_string(10); + let value1 = _random_string(10); + let value2 = _random_string(10); + + let json = format!( + r#"[{{"{}":"{}"}}, {{"{}":"{}"}}]"#, + name1, value1, name2, value2 + ); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Eq(name1, value1), Query::Eq(name2, value2)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_old_format_empty() { + let json = r#"[]"#; + + let query: Query = ::serde_json::from_str(json).unwrap(); + + let expected = Query::And(vec![]); + + assert_eq!(query, expected); + } + + #[test] + fn test_old_format_with_nulls() { + let name1 = _random_string(10); + let name2 = _random_string(10); + let value1 = _random_string(10); + + let json = json!(vec![ + json!({ name1.clone(): value1 }), + json!({ name2: ::serde_json::Value::Null }) + ]) + .to_string(); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + let expected = Query::Or(vec![Query::Eq(name1, value1)]); + + assert_eq!(query, expected); + } + + #[test] + fn test_optimise_and() { + let json = r#"{}"#; + + let query: Query = ::serde_json::from_str(json).unwrap(); + + assert_eq!(query.optimise(), None); + } + + #[test] + fn test_optimise_or() { + let json = r#"[]"#; + + let query: Query = ::serde_json::from_str(json).unwrap(); + + assert_eq!(query.optimise(), None); + } + + #[test] + fn test_optimise_single_nested_and() { + let json = json!({ + "$and": [ + { + "$and": [] + } + ] + }) + .to_string(); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + assert_eq!(query.optimise(), None); + } + + #[test] + fn test_optimise_several_nested_and() { + let json = json!({ + "$and": [ + { + "$and": [] + }, + { + "$and": [] + } + ] + }) + .to_string(); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + assert_eq!(query.optimise(), None); + } + + #[test] + fn test_optimise_single_nested_or() { + let json = json!({ + "$and": [ + { + "$or": [] + } + ] + }) + .to_string(); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + assert_eq!(query.optimise(), None); + } + + #[test] + fn test_optimise_several_nested_or() { + let json = json!({ + "$and": [ + { + "$or": [] + }, + { + "$or": [] + } + ] + }) + .to_string(); + + let query: Query = ::serde_json::from_str(&json).unwrap(); + + assert_eq!(query.optimise(), None); + } +} diff --git a/src/storage/wql/sql.rs b/askar-storage/src/wql/sql.rs similarity index 90% rename from src/storage/wql/sql.rs rename to askar-storage/src/wql/sql.rs index 3d548c52..f644cc23 100644 --- a/src/storage/wql/sql.rs +++ b/askar-storage/src/wql/sql.rs @@ -37,15 +37,15 @@ where fn encode_name(&mut self, name: &TagName) -> Result { Ok(match name { - TagName::Encrypted(name) | TagName::Plaintext(name) => (&self.enc_name)(name)?, + TagName::Encrypted(name) | TagName::Plaintext(name) => (self.enc_name)(name)?, }) } - fn encode_value(&mut self, value: &String, is_plaintext: bool) -> Result { + fn encode_value(&mut self, value: &str, is_plaintext: bool) -> Result { Ok(if is_plaintext { value.as_bytes().to_vec() } else { - (&self.enc_value)(value)? + (self.enc_value)(value)? }) } @@ -55,6 +55,7 @@ where enc_name: Self::Arg, enc_value: Self::Arg, is_plaintext: bool, + negate: bool, ) -> Result, Error> { let idx = self.arguments.len(); let (op_prefix, match_prefix) = match (is_plaintext, op.as_sql_str_for_prefix()) { @@ -77,12 +78,13 @@ where } let query = format!( - "i.id IN (SELECT item_id FROM items_tags WHERE name = ${} AND value {} ${}{} AND plaintext = {})", + "i.id {} (SELECT item_id FROM items_tags WHERE name = ${} AND value {} ${}{} AND plaintext = {})", + if negate { "NOT IN" } else { "IN" }, idx + 1, op.as_sql_str(), idx + 2, op_prefix.as_str(), - if is_plaintext { 1 } else { 0 } + i32::from(is_plaintext) ); Ok(Some(query)) } @@ -97,10 +99,10 @@ where let args_in = Itertools::intersperse(std::iter::repeat("$$").take(enc_values.len()), ", ") .collect::(); let query = format!( - "i.id IN (SELECT item_id FROM items_tags WHERE name = $$ AND value {} ({}) AND plaintext = {})", + "i.id {} (SELECT item_id FROM items_tags WHERE name = $$ AND value IN ({}) AND plaintext = {})", if negate { "NOT IN" } else { "IN" }, args_in, - if is_plaintext { 1 } else { 0 } + i32::from(is_plaintext) ); self.arguments.push(enc_name); self.arguments.extend(enc_values); @@ -116,7 +118,7 @@ where let query = format!( "i.id {} (SELECT item_id FROM items_tags WHERE name = $$ AND plaintext = {})", if negate { "NOT IN" } else { "IN" }, - if is_plaintext { 1 } else { 0 } + i32::from(is_plaintext) ); self.arguments.push(enc_name); Ok(Some(query)) @@ -185,7 +187,7 @@ mod tests { |value: &str| Ok(value.to_uppercase().into_bytes()), ); let query_str = enc.encode_query(&query).unwrap().unwrap(); - assert_eq!(query_str, "((i.id IN (SELECT item_id FROM items_tags WHERE name = $1 AND value = $2 AND SUBSTR(value, 1, 12) = $3 AND plaintext = 0) AND i.id IN (SELECT item_id FROM items_tags WHERE name = $4 AND value = $5 AND plaintext = 1)) OR (i.id IN (SELECT item_id FROM items_tags WHERE name = $6 AND value = $7 AND SUBSTR(value, 1, 12) = $8 AND plaintext = 0) AND i.id IN (SELECT item_id FROM items_tags WHERE name = $9 AND value != $10 AND plaintext = 1)))"); + assert_eq!(query_str, "((i.id IN (SELECT item_id FROM items_tags WHERE name = $1 AND value = $2 AND SUBSTR(value, 1, 12) = $3 AND plaintext = 0) AND i.id IN (SELECT item_id FROM items_tags WHERE name = $4 AND value = $5 AND plaintext = 1)) OR (i.id IN (SELECT item_id FROM items_tags WHERE name = $6 AND value = $7 AND SUBSTR(value, 1, 12) = $8 AND plaintext = 0) AND i.id NOT IN (SELECT item_id FROM items_tags WHERE name = $9 AND value = $10 AND plaintext = 1)))"); let args = enc.arguments; assert_eq!( args, diff --git a/src/storage/wql/tags.rs b/askar-storage/src/wql/tags.rs similarity index 86% rename from src/storage/wql/tags.rs rename to askar-storage/src/wql/tags.rs index f877f4de..322f8148 100644 --- a/src/storage/wql/tags.rs +++ b/askar-storage/src/wql/tags.rs @@ -6,8 +6,8 @@ pub type TagQuery = AbstractQuery; pub fn tag_query(query: Query) -> Result { let result = query .map_names(|k| { - if k.starts_with("~") { - Result::<_, ()>::Ok(TagName::Plaintext(k[1..].to_string())) + if let Some(plain) = k.strip_prefix('~') { + Result::<_, ()>::Ok(TagName::Plaintext(plain.to_string())) } else { Ok(TagName::Encrypted(k)) } @@ -37,9 +37,9 @@ impl ToString for TagName { } } -impl Into for &TagName { - fn into(self) -> String { - self.to_string() +impl From<&TagName> for String { + fn from(tag: &TagName) -> String { + tag.to_string() } } @@ -56,7 +56,7 @@ pub trait TagQueryEncoder { fn encode_name(&mut self, name: &TagName) -> Result; - fn encode_value(&mut self, value: &String, is_plaintext: bool) -> Result; + fn encode_value(&mut self, value: &str, is_plaintext: bool) -> Result; fn encode_op_clause( &mut self, @@ -64,6 +64,7 @@ pub trait TagQueryEncoder { enc_name: Self::Arg, enc_value: Self::Arg, is_plaintext: bool, + negate: bool, ) -> Result, Error>; fn encode_in_clause( @@ -97,7 +98,6 @@ pub enum CompareOp { Lt, Lte, Like, - NotLike, } impl CompareOp { @@ -110,7 +110,6 @@ impl CompareOp { Self::Lt => "<", Self::Lte => "<=", Self::Like => "LIKE", - Self::NotLike => "NOT LIKE", } } @@ -123,19 +122,6 @@ impl CompareOp { _ => None, } } - - pub fn negate(&self) -> Self { - match self { - Self::Eq => Self::Neq, - Self::Neq => Self::Eq, - Self::Gt => Self::Lte, - Self::Gte => Self::Lt, - Self::Lt => Self::Gte, - Self::Lte => Self::Gt, - Self::Like => Self::NotLike, - Self::NotLike => Self::Like, - } - } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -199,40 +185,33 @@ where fn encode_tag_op( op: CompareOp, name: &TagName, - value: &String, + value: &str, enc: &mut E, negate: bool, ) -> Result, Error> where E: TagQueryEncoder, { - let is_plaintext = match &name { - TagName::Plaintext(_) => true, - _ => false, - }; + let is_plaintext = matches!(name, TagName::Plaintext(_)); let enc_name = enc.encode_name(name)?; let enc_value = enc.encode_value(value, is_plaintext)?; - let op = if negate { op.negate() } else { op }; - enc.encode_op_clause(op, enc_name, enc_value, is_plaintext) + enc.encode_op_clause(op, enc_name, enc_value, is_plaintext, negate) } fn encode_tag_in( name: &TagName, - values: &Vec, + values: &[String], enc: &mut E, negate: bool, ) -> Result, Error> where E: TagQueryEncoder, { - let is_plaintext = match &name { - TagName::Plaintext(_) => true, - _ => false, - }; + let is_plaintext = matches!(name, TagName::Plaintext(_)); let enc_name = enc.encode_name(name)?; let enc_values = values - .into_iter() + .iter() .map(|val| enc.encode_value(val, is_plaintext)) .collect::, Error>>()?; @@ -246,10 +225,7 @@ where match names.len() { 0 => Ok(None), 1 => { - let is_plaintext = match names[0] { - TagName::Plaintext(_) => true, - _ => false, - }; + let is_plaintext = matches!(names[0], TagName::Plaintext(_)); let enc_name = enc.encode_name(&names[0])?; enc.encode_exist_clause(enc_name, is_plaintext, negate) } @@ -267,7 +243,7 @@ where fn encode_tag_conj( op: ConjunctionOp, - subqueries: &Vec, + subqueries: &[TagQuery], enc: &mut E, negate: bool, ) -> Result, Error> @@ -276,7 +252,7 @@ where { let op = if negate { op.negate() } else { op }; let clauses = subqueries - .into_iter() + .iter() .flat_map(|q| encode_tag_query(q, enc, negate).transpose()) .collect::, Error>>()?; @@ -299,8 +275,8 @@ mod tests { Ok(name.to_string()) } - fn encode_value(&mut self, value: &String, _is_plaintext: bool) -> Result { - Ok(value.clone()) + fn encode_value(&mut self, value: &str, _is_plaintext: bool) -> Result { + Ok(value.to_string()) } fn encode_op_clause( @@ -309,8 +285,13 @@ mod tests { name: Self::Arg, value: Self::Arg, _is_plaintext: bool, + negate: bool, ) -> Result, Error> { - Ok(Some(format!("{} {} {}", name, op.as_sql_str(), value))) + let mut s = format!("{} {} {}", name, op.as_sql_str(), value); + if negate { + s = format!("NOT ({})", s); + } + Ok(Some(s)) } fn encode_exist_clause( @@ -342,12 +323,12 @@ mod tests { clauses: Vec, ) -> Result, Error> { let mut r = String::new(); - r.push_str("("); + r.push('('); r.extend(Itertools::intersperse( clauses.iter().map(String::as_str), op.as_sql_str(), )); - r.push_str(")"); + r.push(')'); Ok(Some(r)) } } @@ -417,7 +398,7 @@ mod tests { ]); let query = TagQuery::Or(vec![condition_1, condition_2]); let query_str = TestEncoder {}.encode_query(&query).unwrap().unwrap(); - assert_eq!(query_str, "((enctag = encval AND ~plaintag = plainval) OR (enctag = encval AND ~plaintag != eggs))") + assert_eq!(query_str, "((enctag = encval AND ~plaintag = plainval) OR (enctag = encval AND NOT (~plaintag = eggs)))") } #[test] @@ -444,6 +425,6 @@ mod tests { ]); let query = TagQuery::Not(Box::new(TagQuery::Or(vec![condition_1, condition_2]))); let query_str = TestEncoder {}.encode_query(&query).unwrap().unwrap(); - assert_eq!(query_str, "((enctag != encval OR ~plaintag != plainval) AND (enctag != encval OR ~plaintag = eggs))") + assert_eq!(query_str, "((NOT (enctag = encval) OR NOT (~plaintag = plainval)) AND (NOT (enctag = encval) OR ~plaintag = eggs))") } } diff --git a/askar-storage/tests/.gitignore b/askar-storage/tests/.gitignore new file mode 100644 index 00000000..654cceab --- /dev/null +++ b/askar-storage/tests/.gitignore @@ -0,0 +1 @@ +*upgraded.db* diff --git a/tests/backends.rs b/askar-storage/tests/backends.rs similarity index 58% rename from tests/backends.rs rename to askar-storage/tests/backends.rs index ca77a734..9dc7239a 100644 --- a/tests/backends.rs +++ b/askar-storage/tests/backends.rs @@ -1,217 +1,121 @@ +#![allow(clippy::bool_assert_comparison)] + mod utils; -const ERR_CLOSE: &'static str = "Error closing database"; +const ERR_CLOSE: &str = "Error closing database"; macro_rules! backend_tests { - ($init:expr) => { - use aries_askar::future::block_on; - use std::sync::Arc; - use $crate::utils::TestStore; - + ($run:expr) => { #[test] fn init() { - block_on(async { - let db = $init.await; - db.close().await.expect(ERR_CLOSE); - }); + $run(|db| async move { + let _ = db; + }) } #[test] fn create_remove_profile() { - block_on(async { - let db = $init.await; - super::utils::db_create_remove_profile(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_create_remove_profile) + } + + #[test] + fn list_profiles() { + $run(super::utils::db_list_profiles) + } + + #[test] + fn get_set_default_profile() { + $run(super::utils::db_get_set_default_profile) } #[test] fn fetch_fail() { - block_on(async { - let db = $init.await; - super::utils::db_fetch_fail(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_fetch_fail) } #[test] fn insert_fetch() { - block_on(async { - let db = $init.await; - super::utils::db_insert_fetch(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_insert_fetch) } #[test] fn insert_duplicate() { - block_on(async { - let db = $init.await; - super::utils::db_insert_duplicate(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_insert_duplicate) } #[test] fn insert_remove() { - block_on(async { - let db = $init.await; - super::utils::db_insert_remove(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_insert_remove) } #[test] fn remove_missing() { - block_on(async { - let db = $init.await; - super::utils::db_remove_missing(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_remove_missing) } #[test] fn replace_fetch() { - block_on(async { - let db = $init.await; - super::utils::db_replace_fetch(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_replace_fetch) } #[test] fn replace_missing() { - block_on(async { - let db = $init.await; - super::utils::db_replace_missing(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_replace_missing) } #[test] fn count() { - block_on(async { - let db = $init.await; - super::utils::db_count(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_count) } #[test] fn count_exist() { - block_on(async { - let db = $init.await; - super::utils::db_count_exist(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_count_exist) } #[test] fn scan() { - block_on(async { - let db = $init.await; - super::utils::db_scan(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_scan) } #[test] fn remove_all() { - block_on(async { - let db = $init.await; - super::utils::db_remove_all(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) - } - - #[test] - fn keypair_create_fetch() { - block_on(async { - let db = $init.await; - super::utils::db_keypair_insert_fetch(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_remove_all) } - // #[test] - // fn keypair_sign_verify() { - // block_on(async { - // let db = $init.await; - // super::utils::db_keypair_sign_verify(db.clone()).await; - // db.close().await.expect(ERR_CLOSE); - // }) - // } - - // #[test] - // fn keypair_pack_unpack_anon() { - // block_on(async { - // let db = $init.await; - // super::utils::db_keypair_pack_unpack_anon(db.clone()).await; - // db.close().await.expect(ERR_CLOSE); - // }) - // } - - // #[test] - // fn keypair_pack_unpack_auth() { - // block_on(async { - // let db = $init.await; - // super::utils::db_keypair_pack_unpack_auth(db).await; - // db.close().await.expect(ERR_CLOSE); - // }) - // } - #[test] fn txn_rollback() { - block_on(async { - let db = $init.await; - super::utils::db_txn_rollback(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_txn_rollback) } #[test] fn txn_drop() { - block_on(async { - let db = $init.await; - super::utils::db_txn_drop(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_txn_drop) } #[test] fn session_drop() { - block_on(async { - let db = $init.await; - super::utils::db_session_drop(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_session_drop) } #[test] fn txn_commit() { - block_on(async { - let db = $init.await; - super::utils::db_txn_commit(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_txn_commit) } #[test] fn txn_fetch_for_update() { - block_on(async { - let db = $init.await; - super::utils::db_txn_fetch_for_update(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_txn_fetch_for_update) } #[test] fn txn_contention() { - block_on(async { - let db = $init.await; - super::utils::db_txn_contention(db.clone()).await; - db.close().await.expect(ERR_CLOSE); - }) + $run(super::utils::db_txn_contention) + } + + #[test] + fn db_import() { + $run(super::utils::db_import_scan) } }; } @@ -222,16 +126,19 @@ fn log_init() { #[cfg(feature = "sqlite")] mod sqlite { - use aries_askar::backend::sqlite::{SqliteStore, SqliteStoreOptions}; - use aries_askar::{generate_raw_store_key, ManageBackend, Store, StoreKeyMethod}; - use std::path::Path; + use askar_storage::any::{into_any_backend, AnyBackend}; + use askar_storage::backend::copy_store; + use askar_storage::backend::sqlite::SqliteStoreOptions; + use askar_storage::future::block_on; + use askar_storage::{generate_raw_store_key, Backend, ManageBackend, StoreKeyMethod}; + use std::{future::Future, path::Path}; use super::*; #[test] fn create_remove_db() { log_init(); - let fname = format!("sqlite-test-{}.db", uuid::Uuid::new_v4().to_string()); + let fname = format!("sqlite-test-{}.db", uuid::Uuid::new_v4()); assert_eq!( Path::new(&fname).exists(), false, @@ -281,7 +188,7 @@ mod sqlite { #[test] fn rekey_db() { log_init(); - let fname = format!("sqlite-rekey-{}.db", uuid::Uuid::new_v4().to_string()); + let fname = format!("sqlite-rekey-{}.db", uuid::Uuid::new_v4()); let key1 = generate_raw_store_key(None).expect("Error creating raw key"); let key2 = generate_raw_store_key(None).expect("Error creating raw key"); assert_ne!(key1, key2); @@ -317,10 +224,62 @@ mod sqlite { }) } + #[test] + fn copy_db() { + log_init(); + let fname_source = format!("sqlite-copy-{}.db", uuid::Uuid::new_v4()); + let url_target = format!("sqlite://sqlite-copy-{}.db", uuid::Uuid::new_v4()); + let key_source = generate_raw_store_key(None).expect("Error creating raw key"); + let key_target = generate_raw_store_key(None).expect("Error creating raw key"); + + block_on(async move { + let source = SqliteStoreOptions::new(fname_source.as_str()) + .expect("Error initializing sqlite store options") + .provision_backend(StoreKeyMethod::RawKey, key_source.as_ref(), None, false) + .await + .expect("Error provisioning sqlite store"); + let profile = source + .get_default_profile() + .await + .expect("Error fetching default profile"); + + copy_store( + &source, + url_target.as_str(), + StoreKeyMethod::RawKey, + key_target.as_ref(), + false, + ) + .await + .expect("Error copying store"); + + source.close().await.expect("Error closing store"); + SqliteStoreOptions::new(fname_source.as_str()) + .expect("Error initializing sqlite store options") + .remove_backend() + .await + .expect("Error removing sqlite store"); + + let copied = SqliteStoreOptions::new(url_target.as_str()) + .expect("Error initializing sqlite store options") + .open_backend(Some(StoreKeyMethod::RawKey), key_target.as_ref(), None) + .await + .expect("Error opening rekeyed store"); + assert_eq!(copied.get_active_profile(), profile); + copied.close().await.expect("Error closing store"); + + SqliteStoreOptions::new(url_target.as_str()) + .expect("Error initializing sqlite store options") + .remove_backend() + .await + .expect("Error removing sqlite store"); + }) + } + #[test] fn txn_contention_file() { log_init(); - let fname = format!("sqlite-contention-{}.db", uuid::Uuid::new_v4().to_string()); + let fname = format!("sqlite-contention-{}.db", uuid::Uuid::new_v4()); let key = generate_raw_store_key(None).expect("Error creating raw key"); block_on(async move { @@ -330,7 +289,7 @@ mod sqlite { .await .expect("Error provisioning sqlite store"); - let db = std::sync::Arc::new(store); + let db = into_any_backend(store); super::utils::db_txn_contention(db.clone()).await; db.close().await.expect("Error closing sqlite store"); @@ -411,18 +370,26 @@ mod sqlite { }); } - async fn init_db() -> Arc> { + fn with_sqlite_in_memory(f: F) + where + F: FnOnce(AnyBackend) -> G, + G: Future, + { log_init(); - let key = generate_raw_store_key(None).expect("Error creating raw key"); - Arc::new( - SqliteStoreOptions::in_memory() - .provision(StoreKeyMethod::RawKey, key, None, false) - .await - .expect("Error provisioning sqlite store"), - ) + let key = generate_raw_store_key(None).expect("Error generating store key"); + block_on(async move { + let db = into_any_backend( + SqliteStoreOptions::in_memory() + .provision(StoreKeyMethod::RawKey, key, None, false) + .await + .expect("Error provisioning sqlite store"), + ); + f(db.clone()).await; + db.close().await.expect(ERR_CLOSE); + }) } - backend_tests!(init_db()); + backend_tests!(with_sqlite_in_memory); #[test] fn provision_from_str() { @@ -448,39 +415,31 @@ mod sqlite { #[cfg(feature = "pg_test")] mod postgres { - use aries_askar::{backend::postgres::test_db::TestDB, postgres::PostgresStore, Store}; - use std::{future::Future, ops::Deref, pin::Pin}; + use askar_storage::any::AnyBackend; + use askar_storage::backend::postgres::TestDB; + use askar_storage::future::block_on; + use std::future::Future; use super::*; - #[derive(Clone, Debug)] - struct Wrap(Arc); - - impl Deref for Wrap { - type Target = Store; - - fn deref(&self) -> &Self::Target { - &**self.0 - } - } - - impl TestStore for Wrap { - type DB = PostgresStore; - - fn close(self) -> Pin>>> { - let db = Arc::try_unwrap(self.0).unwrap(); - Box::pin(db.close()) - } - } - - async fn init_db() -> Wrap { + fn with_postgres(f: F) + where + F: FnOnce(AnyBackend) -> G, + G: Future, + { + let db_url = match std::env::var("POSTGRES_URL") { + Ok(p) if !p.is_empty() => p, + _ => panic!("'POSTGRES_URL' must be defined"), + }; log_init(); - Wrap(Arc::new( - TestDB::provision() + block_on(async move { + let db = TestDB::provision(db_url.as_str()) .await - .expect("Error provisioning postgres test database"), - )) + .expect("Error provisioning postgres test database"); + f(db.backend()).await; + db.close().await.expect(ERR_CLOSE); + }) } - backend_tests!(init_db()); + backend_tests!(with_postgres); } diff --git a/tests/docker_pg.sh b/askar-storage/tests/docker_pg.sh similarity index 100% rename from tests/docker_pg.sh rename to askar-storage/tests/docker_pg.sh diff --git a/askar-storage/tests/indy_wallet_sqlite.db b/askar-storage/tests/indy_wallet_sqlite.db new file mode 100644 index 00000000..97c3f25a Binary files /dev/null and b/askar-storage/tests/indy_wallet_sqlite.db differ diff --git a/askar-storage/tests/migration.rs b/askar-storage/tests/migration.rs new file mode 100644 index 00000000..102ee761 --- /dev/null +++ b/askar-storage/tests/migration.rs @@ -0,0 +1,49 @@ +#![cfg(all(feature = "sqlite", feature = "migration"))] + +use std::path::PathBuf; + +use askar_storage::future::block_on; +use askar_storage::migration::IndySdkToAriesAskarMigration; +use askar_storage::Error; + +const DB_TEMPLATE_PATH: &str = "./tests/indy_wallet_sqlite.db"; +const DB_UPGRADE_PATH: &str = "./tests/indy_wallet_sqlite_upgraded.db"; + +/// Create a copy of the input DB for migration +fn prepare_db() { + let tpl_paths = [ + PathBuf::from(DB_TEMPLATE_PATH), + PathBuf::from(format!("{}-shm", DB_TEMPLATE_PATH)), + PathBuf::from(format!("{}-wal", DB_TEMPLATE_PATH)), + ]; + let upd_paths = [ + PathBuf::from(DB_UPGRADE_PATH), + PathBuf::from(format!("{}-shm", DB_UPGRADE_PATH)), + PathBuf::from(format!("{}-wal", DB_UPGRADE_PATH)), + ]; + for (tpl, upd) in tpl_paths.iter().zip(upd_paths) { + if tpl.exists() { + std::fs::copy(tpl, upd).expect("Error copying wallet database"); + } else { + std::fs::remove_file(upd).ok(); + } + } +} + +#[test] +fn test_sqlite_migration() { + prepare_db(); + + let res = block_on(async { + let wallet_name = "walletwallet.0"; + let wallet_key = "GfwU1DC7gEZNs3w41tjBiZYj7BNToDoFEqKY6wZXqs1A"; + let migrator = + IndySdkToAriesAskarMigration::connect(DB_UPGRADE_PATH, wallet_name, wallet_key, "RAW") + .await?; + migrator.migrate().await?; + Result::<_, Error>::Ok(()) + }); + + // We still need some indication if something returned with an error + res.expect("Migration failed"); +} diff --git a/askar-storage/tests/utils/mod.rs b/askar-storage/tests/utils/mod.rs new file mode 100644 index 00000000..4608e6e7 --- /dev/null +++ b/askar-storage/tests/utils/mod.rs @@ -0,0 +1,892 @@ +use askar_storage::{ + any::AnyBackend, + entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, + Backend, BackendSession, ErrorKind, +}; + +use tokio::task::spawn; + +const ERR_PROFILE: &str = "Error creating profile"; +const ERR_SESSION: &str = "Error starting session"; +const ERR_TRANSACTION: &str = "Error starting transaction"; +const ERR_COMMIT: &str = "Error committing transaction"; +const ERR_COUNT: &str = "Error performing count"; +const ERR_FETCH: &str = "Error fetching test row"; +const ERR_FETCH_ALL: &str = "Error fetching all test rows"; +const ERR_REQ_ROW: &str = "Expected row"; +const ERR_REQ_ERR: &str = "Expected error"; +const ERR_INSERT: &str = "Error inserting test row"; +const ERR_REPLACE: &str = "Error replacing test row"; +const ERR_REMOVE_ALL: &str = "Error removing test rows"; +const ERR_SCAN: &str = "Error starting scan"; +const ERR_SCAN_NEXT: &str = "Error fetching scan rows"; + +pub async fn db_create_remove_profile(db: AnyBackend) { + let profile = db.create_profile(None).await.expect(ERR_PROFILE); + assert!(db + .remove_profile(profile) + .await + .expect("Error removing profile"),); + assert!(!db + .remove_profile("not a profile".to_string()) + .await + .expect("Error removing profile"),); +} + +pub async fn db_fetch_fail(db: AnyBackend) { + let mut conn = db.session(None, false).expect(ERR_SESSION); + let result = conn + .fetch(EntryKind::Item, "cat", "name", false) + .await + .expect(ERR_FETCH); + assert!(result.is_none()); +} + +pub async fn db_insert_fetch(db: AnyBackend) { + let test_row = Entry::new( + EntryKind::Item, + "category", + "name", + "value", + vec![ + EntryTag::Encrypted("t1".to_string(), "v1".to_string()), + EntryTag::Plaintext("t2".to_string(), "v2".to_string()), + ], + ); + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + let row = conn + .fetch(EntryKind::Item, &test_row.category, &test_row.name, false) + .await + .expect(ERR_FETCH) + .expect(ERR_REQ_ROW); + assert_eq!(row, test_row); + + let rows = conn + .fetch_all( + Some(EntryKind::Item), + Some(&test_row.category), + None, + None, + false, + ) + .await + .expect(ERR_FETCH_ALL); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0], test_row); +} + +pub async fn db_insert_duplicate(db: AnyBackend) { + let test_row = Entry::new(EntryKind::Item, "category", "name", "value", Vec::new()); + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + let err = conn + .update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect_err(ERR_REQ_ERR); + assert_eq!(err.kind(), ErrorKind::Duplicate); +} + +pub async fn db_insert_remove(db: AnyBackend) { + let test_row = Entry::new(EntryKind::Item, "category", "name", "value", Vec::new()); + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + conn.update( + EntryKind::Item, + EntryOperation::Remove, + &test_row.category, + &test_row.name, + None, + None, + None, + ) + .await + .expect(ERR_REQ_ROW); +} + +pub async fn db_remove_missing(db: AnyBackend) { + let mut conn = db.session(None, false).expect(ERR_SESSION); + + let err = conn + .update( + EntryKind::Item, + EntryOperation::Remove, + "cat", + "name", + None, + None, + None, + ) + .await + .expect_err(ERR_REQ_ERR); + assert_eq!(err.kind(), ErrorKind::NotFound); +} + +pub async fn db_replace_fetch(db: AnyBackend) { + let test_row = Entry::new(EntryKind::Item, "category", "name", "value", Vec::new()); + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + let mut replace_row = test_row.clone(); + replace_row.value = "new value".into(); + conn.update( + EntryKind::Item, + EntryOperation::Replace, + &replace_row.category, + &replace_row.name, + Some(&replace_row.value), + Some(replace_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_REPLACE); + + let row = conn + .fetch( + EntryKind::Item, + &replace_row.category, + &replace_row.name, + false, + ) + .await + .expect(ERR_FETCH) + .expect(ERR_REQ_ROW); + assert_eq!(row, replace_row); +} + +pub async fn db_replace_missing(db: AnyBackend) { + let test_row = Entry::new(EntryKind::Item, "category", "name", "value", Vec::new()); + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + let err = conn + .update( + EntryKind::Item, + EntryOperation::Replace, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect_err(ERR_REQ_ERR); + assert_eq!(err.kind(), ErrorKind::NotFound); +} + +pub async fn db_count(db: AnyBackend) { + let category = "category".to_string(); + let test_rows = vec![Entry::new( + EntryKind::Item, + &category, + "name", + "value", + Vec::new(), + )]; + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + for upd in test_rows.iter() { + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &upd.category, + &upd.name, + Some(&upd.value), + Some(upd.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + } + + let tag_filter = None; + let count = conn + .count(Some(EntryKind::Item), Some(&category), tag_filter) + .await + .expect(ERR_COUNT); + assert_eq!(count, 1); + + let tag_filter = Some(TagFilter::is_eq("sometag", "someval")); + let count = conn + .count(Some(EntryKind::Item), Some(&category), tag_filter) + .await + .expect(ERR_COUNT); + assert_eq!(count, 0); +} + +pub async fn db_count_exist(db: AnyBackend) { + let test_row = Entry::new( + EntryKind::Item, + "category", + "name", + "value", + vec![ + EntryTag::Encrypted("enc".to_string(), "v1".to_string()), + EntryTag::Plaintext("plain".to_string(), "v2".to_string()), + ], + ); + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + assert_eq!( + conn.count(Some(EntryKind::Item), Some(&test_row.category), None) + .await + .expect(ERR_COUNT), + 1 + ); + + assert_eq!( + conn.count(Some(EntryKind::Kms), Some(&test_row.category), None) + .await + .expect(ERR_COUNT), + 0 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::exist(vec!["enc".to_string()])) + ) + .await + .expect(ERR_COUNT), + 1 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::exist(vec!["~plain".to_string()])) + ) + .await + .expect(ERR_COUNT), + 1 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::exist(vec!["~enc".to_string()])) + ) + .await + .expect(ERR_COUNT), + 0 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::exist(vec!["plain".to_string()])) + ) + .await + .expect(ERR_COUNT), + 0 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::exist(vec!["other".to_string()])) + ) + .await + .expect(ERR_COUNT), + 0 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::exist(vec![ + "enc".to_string(), + "other".to_string() + ])) + ) + .await + .expect(ERR_COUNT), + 0 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::all_of(vec![ + TagFilter::exist(vec!["enc".to_string()]), + TagFilter::exist(vec!["~plain".to_string()]) + ])) + ) + .await + .expect(ERR_COUNT), + 1 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::any_of(vec![ + TagFilter::exist(vec!["~enc".to_string()]), + TagFilter::exist(vec!["~plain".to_string()]) + ])) + ) + .await + .expect(ERR_COUNT), + 1 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::all_of(vec![ + TagFilter::exist(vec!["~enc".to_string()]), + TagFilter::exist(vec!["~plain".to_string()]) + ])) + ) + .await + .expect(ERR_COUNT), + 0 + ); + + assert_eq!( + conn.count( + Some(EntryKind::Item), + Some(&test_row.category), + Some(TagFilter::negate(TagFilter::exist(vec![ + "enc".to_string(), + "other".to_string() + ]),)) + ) + .await + .expect(ERR_COUNT), + 0 + ); +} + +pub async fn db_scan(db: AnyBackend) { + let category = "category".to_string(); + let test_rows = vec![Entry::new( + EntryKind::Item, + &category, + "name", + "value", + vec![ + EntryTag::Encrypted("t1".to_string(), "v1".to_string()), + EntryTag::Plaintext("t2".to_string(), "v2".to_string()), + ], + )]; + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + for upd in test_rows.iter() { + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &upd.category, + &upd.name, + Some(&upd.value), + Some(upd.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + } + drop(conn); + + let tag_filter = None; + let offset = None; + let limit = None; + let mut scan = db + .scan( + None, + Some(EntryKind::Item), + Some(category.clone()), + tag_filter, + offset, + limit, + ) + .await + .expect(ERR_SCAN); + let rows = scan.fetch_next().await.expect(ERR_SCAN_NEXT); + assert_eq!(rows, Some(test_rows)); + let rows = scan.fetch_next().await.expect(ERR_SCAN_NEXT); + assert_eq!(rows, None); + + let tag_filter = Some(TagFilter::is_eq("sometag", "someval")); + let mut scan = db + .scan( + None, + Some(EntryKind::Item), + Some(category.clone()), + tag_filter, + offset, + limit, + ) + .await + .expect(ERR_SCAN); + let rows = scan.fetch_next().await.expect(ERR_SCAN_NEXT); + assert_eq!(rows, None); +} + +pub async fn db_remove_all(db: AnyBackend) { + let test_rows = vec![ + Entry::new( + EntryKind::Item, + "category", + "item1", + "value", + vec![ + EntryTag::Encrypted("t1".to_string(), "del".to_string()), + EntryTag::Plaintext("t2".to_string(), "del".to_string()), + ], + ), + Entry::new( + EntryKind::Item, + "category", + "item2", + "value", + vec![ + EntryTag::Encrypted("t1".to_string(), "del".to_string()), + EntryTag::Plaintext("t2".to_string(), "del".to_string()), + ], + ), + Entry::new( + EntryKind::Item, + "category", + "item3", + "value", + vec![ + EntryTag::Encrypted("t1".to_string(), "keep".to_string()), + EntryTag::Plaintext("t2".to_string(), "keep".to_string()), + ], + ), + ]; + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + for test_row in test_rows.iter() { + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + } + + // could detect that a second transaction would block here? + // depends on the backend. just checking that no SQL errors occur for now. + let removed = conn + .remove_all( + Some(EntryKind::Item), + Some("category"), + Some(TagFilter::all_of(vec![ + TagFilter::is_eq("t1", "del"), + TagFilter::is_eq("~t2", "del"), + ])), + ) + .await + .expect(ERR_REMOVE_ALL); + assert_eq!(removed, 2); +} + +pub async fn db_txn_rollback(db: AnyBackend) { + let test_row = Entry::new(EntryKind::Item, "category", "name", "value", Vec::new()); + + let mut conn = db.session(None, true).expect(ERR_TRANSACTION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + conn.close(false) + .await + .expect("Error rolling back transaction"); + + let mut conn = db.session(None, false).expect("Error starting new session"); + + let row = conn + .fetch(EntryKind::Item, &test_row.category, &test_row.name, false) + .await + .expect("Error fetching test row"); + assert_eq!(row, None); +} + +pub async fn db_txn_drop(db: AnyBackend) { + let test_row = Entry::new(EntryKind::Item, "category", "name", "value", Vec::new()); + + let mut conn = db + .session(None, true) + .expect("Error starting new transaction"); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + drop(conn); + + let mut conn = db.session(None, false).expect("Error starting new session"); + + let row = conn + .fetch(EntryKind::Item, &test_row.category, &test_row.name, false) + .await + .expect("Error fetching test row"); + assert_eq!(row, None); +} + +// test that session does NOT have transaction rollback behaviour +pub async fn db_session_drop(db: AnyBackend) { + let test_row = Entry::new(EntryKind::Item, "category", "name", "value", Vec::new()); + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + drop(conn); + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + let row = conn + .fetch(EntryKind::Item, &test_row.category, &test_row.name, false) + .await + .expect(ERR_FETCH); + assert_eq!(row, Some(test_row)); +} + +pub async fn db_txn_commit(db: AnyBackend) { + let test_row = Entry::new(EntryKind::Item, "category", "name", "value", Vec::new()); + + let mut conn = db.session(None, true).expect(ERR_TRANSACTION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + conn.close(true).await.expect(ERR_COMMIT); + + let mut conn = db.session(None, false).expect(ERR_SESSION); + + let row = conn + .fetch(EntryKind::Item, &test_row.category, &test_row.name, false) + .await + .expect(ERR_FETCH); + assert_eq!(row, Some(test_row)); +} + +pub async fn db_txn_fetch_for_update(db: AnyBackend) { + let test_row = Entry::new(EntryKind::Item, "category", "name", "value", Vec::new()); + + let mut conn = db.session(None, true).expect(ERR_TRANSACTION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + // could detect that a second transaction would block here? + // depends on the backend. just checking that no SQL errors occur for now. + let row = conn + .fetch(EntryKind::Item, &test_row.category, &test_row.name, true) + .await + .expect(ERR_FETCH) + .expect(ERR_REQ_ROW); + assert_eq!(row, test_row); + + let rows = conn + .fetch_all( + Some(EntryKind::Item), + Some(&test_row.category), + None, + Some(2), + true, + ) + .await + .expect(ERR_FETCH_ALL); + assert_eq!(rows.len(), 1); + assert_eq!(rows[0], test_row); + + conn.close(true).await.expect(ERR_COMMIT); +} + +pub async fn db_txn_contention(db: AnyBackend) { + let test_row = Entry::new( + EntryKind::Item, + "category", + "count", + "0", + vec![ + EntryTag::Encrypted("t1".to_string(), "v1".to_string()), + EntryTag::Plaintext("t2".to_string(), "v2".to_string()), + ], + ); + + let mut conn = db.session(None, true).expect(ERR_TRANSACTION); + + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &test_row.category, + &test_row.name, + Some(&test_row.value), + Some(test_row.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + + conn.close(true).await.expect(ERR_COMMIT); + + const TASKS: usize = 10; + const INC: usize = 1000; + + async fn inc(db: AnyBackend, category: String, name: String) -> Result<(), &'static str> { + // try to avoid panics in this section, as they will be raised on a tokio worker thread + for _ in 0..INC { + let mut conn = db.session(None, true).expect(ERR_TRANSACTION); + let row = conn + .fetch(EntryKind::Item, &category, &name, true) + .await + .map_err(|e| { + log::error!("{:?}", e); + ERR_FETCH + })? + .ok_or(ERR_REQ_ROW)?; + let val: usize = str::parse(row.value.as_opt_str().ok_or("Non-string counter value")?) + .map_err(|_| "Error parsing counter value")?; + conn.update( + EntryKind::Item, + EntryOperation::Replace, + &category, + &name, + Some(format!("{}", val + 1).as_bytes()), + Some(row.tags.as_slice()), + None, + ) + .await + .map_err(|e| { + log::error!("{:?}", e); + ERR_REPLACE + })?; + conn.close(true).await.map_err(|_| ERR_COMMIT)?; + } + Ok(()) + } + + let mut tasks = vec![]; + for _ in 0..TASKS { + tasks.push(spawn(inc( + db.clone(), + test_row.category.clone(), + test_row.name.clone(), + ))); + } + + // JoinSet is not stable yet, just await all the tasks + for task in tasks { + if let Err(s) = task.await.unwrap() { + panic!("Error in concurrent update task: {}", s); + } + } + + // check the total + let mut conn = db.session(None, false).expect(ERR_SESSION); + let row = conn + .fetch(EntryKind::Item, &test_row.category, &test_row.name, false) + .await + .expect(ERR_FETCH) + .expect(ERR_REQ_ROW); + assert_eq!(row.value, format!("{}", TASKS * INC).as_bytes()); +} + +pub async fn db_list_profiles(db: AnyBackend) { + let p_active = db.get_active_profile(); + assert_eq!(vec![p_active.clone()], db.list_profiles().await.unwrap()); + + let p_new = db.create_profile(None).await.unwrap(); + let mut profs = vec![p_active, p_new]; + profs.sort(); + let mut found = db.list_profiles().await.unwrap(); + found.sort(); + assert_eq!(profs, found); +} + +pub async fn db_get_set_default_profile(db: AnyBackend) { + let p_default = db.get_default_profile().await.unwrap(); + let p_new = db.create_profile(None).await.unwrap(); + assert_ne!(p_new, p_default); + db.set_default_profile(p_new.clone()).await.unwrap(); + assert_eq!(db.get_default_profile().await.unwrap(), p_new); +} + +pub async fn db_import_scan(db: AnyBackend) { + let test_rows = vec![Entry::new( + EntryKind::Item, + "category", + "name", + "value", + vec![ + EntryTag::Encrypted("t1".to_string(), "v1".to_string()), + EntryTag::Plaintext("t2".to_string(), "v2".to_string()), + ], + )]; + + let mut conn = db.session(None, false).expect(ERR_SESSION); + for upd in test_rows.iter() { + conn.update( + EntryKind::Item, + EntryOperation::Insert, + &upd.category, + &upd.name, + Some(&upd.value), + Some(upd.tags.as_slice()), + None, + ) + .await + .expect(ERR_INSERT); + } + drop(conn); + + let copy = db.create_profile(None).await.expect(ERR_PROFILE); + let mut copy_conn = db.session(Some(copy.clone()), true).expect(ERR_SESSION); + let records = db + .scan(None, Some(EntryKind::Item), None, None, None, None) + .await + .expect(ERR_SCAN); + copy_conn + .import_scan(records) + .await + .expect("Error importing records"); + copy_conn.close(true).await.expect(ERR_COMMIT); + + let mut scan = db + .scan(Some(copy), Some(EntryKind::Item), None, None, None, None) + .await + .expect(ERR_SCAN); + + let rows = scan.fetch_next().await.expect(ERR_SCAN_NEXT); + assert_eq!(rows, Some(test_rows)); + let rows = scan.fetch_next().await.expect(ERR_SCAN_NEXT); + assert_eq!(rows, None); +} diff --git a/build-kotlin.sh b/build-kotlin.sh new file mode 100755 index 00000000..796c929f --- /dev/null +++ b/build-kotlin.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +set -eo pipefail + +NAME="aries_askar" +VERSION=$(cargo generate-lockfile && cargo pkgid | sed -e "s/^.*#//") +BUNDLE_IDENTIFIER="org.hyperledger.$NAME" +LIBRARY_NAME="lib$NAME.a" +FRAMEWORK_LIBRARY_NAME=${NAME}FFI +HEADER_NAME="${NAME}FFI.h" +OUT_PATH="kotlinOut" +WRAPPER_PATH="wrappers/kotlin/Askar/Sources/Askar" + +AARCH64_LINUX_ANDROID="./target/aarch64-linux-android/release" +ARMV7_LINUX_ANDROIDEABI="./target/armv7-linux-androideabi/release" +I686_LINUX_ANDROID="./target/i686-linux-android/release" +X86_64_LINUX_ANDROID="./target/x86_64-linux-android/release" + +targets=("aarch64-linux-android" "armv7-linux-androideabi" "i686-linux-android" "x86_64-linux-android") + +cargo install cross --git https://github.com/cross-rs/cross +# Build for all targets +for target in "${targets[@]}"; do + echo "Building for $target..." + rustup toolchain install 1.65.0 --target $target --no-self-update + cross build --features uffi --target $target +done + +# Generate kotlin wrapper +echo "Generating kotlin wrapper..." +mkdir -p $OUT_PATH +CURRENT_ARCH=$(rustc --version --verbose | grep host | cut -f2 -d' ') +cargo run --features uffi --bin uniffi-bindgen generate uniffi/askar.udl --language kotlin -o $OUT_PATH --lib-file ./target/$CURRENT_ARCH/release/$LIBRARY_NAME diff --git a/build-swift-framework.sh b/build-swift-framework.sh new file mode 100755 index 00000000..5b742148 --- /dev/null +++ b/build-swift-framework.sh @@ -0,0 +1,119 @@ +#!/bin/sh + +set -eo pipefail + +NAME="aries_askar" +VERSION=$(cargo generate-lockfile && cargo pkgid | sed -e "s/^.*#//") +BUNDLE_IDENTIFIER="org.hyperledger.$NAME" +LIBRARY_NAME="lib$NAME.a" +FRAMEWORK_LIBRARY_NAME=${NAME}FFI +FRAMEWORK_NAME="$FRAMEWORK_LIBRARY_NAME.framework" +XC_FRAMEWORK_NAME="$FRAMEWORK_LIBRARY_NAME.xcframework" +HEADER_NAME="${NAME}FFI.h" +OUT_PATH="out" +MIN_IOS_VERSION="15.0" +WRAPPER_PATH="wrappers/swift/Askar/Sources/Askar" + +AARCH64_APPLE_IOS_PATH="./target/aarch64-apple-ios/release" +AARCH64_APPLE_IOS_SIM_PATH="./target/aarch64-apple-ios-sim/release" +X86_64_APPLE_IOS_PATH="./target/x86_64-apple-ios/release" +AARCH64_APPLE_DARWIN_PATH="./target/aarch64-apple-darwin/release" +X86_64_APPLE_DARWIN_PATH="./target/x86_64-apple-darwin/release" + +targets=("aarch64-apple-ios" "aarch64-apple-ios-sim" "x86_64-apple-ios" "aarch64-apple-darwin" "x86_64-apple-darwin") + +# Build for all targets +for target in "${targets[@]}"; do + echo "Building for $target..." + rustup target add $target + cargo build --release --no-default-features --features uffi --target $target +done + +# Generate swift wrapper +echo "Generating swift wrapper..." +mkdir -p $OUT_PATH +CURRENT_ARCH=$(rustc --version --verbose | grep host | cut -f2 -d' ') +cargo run --features uffi --bin uniffi-bindgen generate uniffi/askar.udl --language swift -o $OUT_PATH --lib-file ./target/$CURRENT_ARCH/release/$LIBRARY_NAME + +# Merge libraries with lipo +echo "Merging libraries with lipo..." +lipo -create $AARCH64_APPLE_IOS_SIM_PATH/$LIBRARY_NAME \ + $X86_64_APPLE_IOS_PATH/$LIBRARY_NAME \ + -output $OUT_PATH/sim-$LIBRARY_NAME +lipo -create $AARCH64_APPLE_DARWIN_PATH/$LIBRARY_NAME \ + $X86_64_APPLE_DARWIN_PATH/$LIBRARY_NAME \ + -output $OUT_PATH/macos-$LIBRARY_NAME + +# Create framework template +rm -rf $OUT_PATH/$FRAMEWORK_NAME +mkdir -p $OUT_PATH/$FRAMEWORK_NAME/Headers +mkdir -p $OUT_PATH/$FRAMEWORK_NAME/Modules +cp $OUT_PATH/$HEADER_NAME $OUT_PATH/$FRAMEWORK_NAME/Headers +cat < $OUT_PATH/$FRAMEWORK_NAME/Modules/module.modulemap +framework module $FRAMEWORK_LIBRARY_NAME { + umbrella header "$HEADER_NAME" + + export * + module * { export * } +} +EOT + +cat < $OUT_PATH/$FRAMEWORK_NAME/Info.plist + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $FRAMEWORK_LIBRARY_NAME + CFBundleIdentifier + $BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $FRAMEWORK_LIBRARY_NAME + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $VERSION + NSPrincipalClass + + MinimumOSVersion + $MIN_IOS_VERSION + + +EOT + +# Prepare frameworks for each platform +rm -rf $OUT_PATH/frameworks +mkdir -p $OUT_PATH/frameworks/sim +mkdir -p $OUT_PATH/frameworks/ios +mkdir -p $OUT_PATH/frameworks/macos +cp -r $OUT_PATH/$FRAMEWORK_NAME $OUT_PATH/frameworks/sim/ +cp -r $OUT_PATH/$FRAMEWORK_NAME $OUT_PATH/frameworks/ios/ +cp -r $OUT_PATH/$FRAMEWORK_NAME $OUT_PATH/frameworks/macos/ +mv $OUT_PATH/sim-$LIBRARY_NAME $OUT_PATH/frameworks/sim/$FRAMEWORK_NAME/$FRAMEWORK_LIBRARY_NAME +mv $OUT_PATH/macos-$LIBRARY_NAME $OUT_PATH/frameworks/macos/$FRAMEWORK_NAME/$FRAMEWORK_LIBRARY_NAME +cp $AARCH64_APPLE_IOS_PATH/$LIBRARY_NAME $OUT_PATH/frameworks/ios/$FRAMEWORK_NAME/$FRAMEWORK_LIBRARY_NAME + +# Create xcframework +echo "Creating xcframework..." +rm -rf $OUT_PATH/$XC_FRAMEWORK_NAME +xcodebuild -create-xcframework \ + -framework $OUT_PATH/frameworks/sim/$FRAMEWORK_NAME \ + -framework $OUT_PATH/frameworks/ios/$FRAMEWORK_NAME \ + -framework $OUT_PATH/frameworks/macos/$FRAMEWORK_NAME \ + -output $OUT_PATH/$XC_FRAMEWORK_NAME + +# Copy swift wrapper +# Need some temporary workarounds to compile swift wrapper +# https://github.com/rust-lang/cargo/issues/11953 +cat < $OUT_PATH/import.txt +#if os(macOS) +import SystemConfiguration +#endif +EOT +cat $OUT_PATH/import.txt $OUT_PATH/$NAME.swift > $WRAPPER_PATH/$NAME.swift diff --git a/build-universal.sh b/build-universal.sh new file mode 100755 index 00000000..978fa1eb --- /dev/null +++ b/build-universal.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +# NOTE: +# MacOS universal build currently requires MacOS 11 (Big Sur) for the appropriate SDK, +# and `sudo xcode-select --install` must be run to install the command line utilities. +# Rust's `beta` channel must be installed because aarch64 is still a tier-2 target: +# `rustup toolchain install beta`. + +RUSTUP=${RUSTUP:-`command -v rustup`} +PROJECT=aries-askar + +if [ ! -x "$RUSTUP" ]; then + echo "rustup command not found: it can be obtained from https://rustup.rs/" + exit 1 +fi + +TOOLCHAIN=`$RUSTUP default` +TARGET_DIR="${TARGET_DIR-./target/darwin-universal}" + +if [ -z "$BUILD_TOOLCHAIN" ]; then + BUILD_TOOLCHAIN=${TOOLCHAIN%%-*} + if [ -z "$BUILD_TOOLCHAIN" ]; then + echo "Error: Could not determine default Rust toolchain" + exit 1 + fi +fi + +MACOS_UNIVERSAL_TARGETS="aarch64-apple-darwin x86_64-apple-darwin" + +# Fail on any execution errors +set -e + +INSTALLED_TARGETS=`$RUSTUP +$BUILD_TOOLCHAIN target list --installed` +# Install target(s) as needed +echo "Checking install targets for MacOS universal build .." +for target in $MACOS_UNIVERSAL_TARGETS; do + if ! `echo "$INSTALLED_TARGETS" | grep -q $target`; then + $RUSTUP +$BUILD_TOOLCHAIN target add $target + fi +done + +MAJOR_VER=`sw_vers | grep ProductVersion | cut -f 2 | cut -f 1 -d .` +if [ "$MAJOR_VER" -lt 11 ]; then + echo "MacOS universal build requires OS 11 (Big Sur) or newer" + TARGET= +fi + +# Build both targets and combine them into a universal library with `lipo` +TARGET_LIBS= +for target in $MACOS_UNIVERSAL_TARGETS; do + echo "Building $PROJECT for toolchain '$BUILD_TOOLCHAIN', target '$target'.." + $RUSTUP run $BUILD_TOOLCHAIN cargo build --lib --release --features uffi --target $target + TARGET_LIBS="./target/$target/release/libaries_askar.dylib $TARGET_LIBS" +done + +mkdir -p "${TARGET_DIR}/release" ./target/release +OUTPUT="${TARGET_DIR}/release/libaries_askar.dylib" +echo "Combining targets into universal library" +lipo -create -output $OUTPUT $TARGET_LIBS diff --git a/build-xcframework.sh b/build-xcframework.sh new file mode 100755 index 00000000..7988170b --- /dev/null +++ b/build-xcframework.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env sh + +set -eo pipefail + +# Check if lipo and xcodebuild exist +if [ -z `command -v lipo` ] || [ -z `command -v xcodebuild` ] || [ -z `command -v sed` ] +then + echo "!!! lipo, xcodebuild or sed could not be found !!!" + help +fi + +NAME="aries_askar" +BUNDLE_NAME="aries-askar" +VERSION=$(cargo generate-lockfile && cargo pkgid | sed -e "s/^.*#//") +BUNDLE_IDENTIFIER="org.hyperledger.$BUNDLE_NAME" +LIBRARY_NAME="lib$NAME.a" +XC_FRAMEWORK_NAME="$NAME.xcframework" +FRAMEWORK_LIBRARY_NAME=$NAME +FRAMEWORK_NAME="$FRAMEWORK_LIBRARY_NAME.framework" +HEADER_NAME="lib$NAME.h" +OUT_PATH="out" +MIN_IOS_VERSION="12.0" + +# Setting some default paths +AARCH64_APPLE_IOS_PATH="./target/aarch64-apple-ios/release" +AARCH64_APPLE_IOS_SIM_PATH="./target/aarch64-apple-ios-sim/release" +X86_64_APPLE_IOS_PATH="./target/x86_64-apple-ios/release" +HEADER_PATH="./include" + +# Simple helper command to display some information +Help() { + echo "required dependencies:" + echo " - lipo" + echo " - sed" + echo " - xcodebuild" + echo "To build an xcframework with underlying Frameworks" + echo "the following can be passed in as positional arguments" + echo " 1. Path to the aarch64-apple-ios directory where $LIBRARY_NAME is stored" + echo " 2. Path to the aarch64-apple-ios-sim directory where $LIBRARY_NAME is stored" + echo " 3. Path to the x86_64-apple-ios directory where $LIBRARY_NAME is stored" + echo " 4. Path to the header file, excluding the header" + echo "Make sure to add the 'release' section of the path for a" + echo "release build." + exit 1 +} + +# override if its provided +if [ ! -z "$1" ] +then + AARCH64_APPLE_IOS_PATH=$1 +fi + +# override if its provided +if [ ! -z "$2" ] +then + AARCH64_APPLE_IOS_SIM_PATH=$2 +fi + +# override if its provided +if [ ! -z "$3" ] +then + X86_64_APPLE_IOS_PATH=$3 +fi + +# override if its provided +if [ ! -z "$4" ] +then + HEADER_PATH=$4 +fi + +if [ ! -f $AARCH64_APPLE_IOS_SIM_PATH/$LIBRARY_NAME ] +then + echo "$AARCH64_APPLE_IOS_SIM_PATH/$LIBRARY_NAME does not exist!" + exit 1 +fi + +if [ ! -f $AARCH64_APPLE_IOS_PATH/$LIBRARY_NAME ] +then + echo "$AARCH64_APPLE_IOS_PATH/$LIBRARY_NAME does not exist!" + exit 1 +fi + +if [ ! -f $X86_64_APPLE_IOS_PATH/$LIBRARY_NAME ] +then + echo "$X86_64_APPLE_IOS_PATH/$LIBRARY_NAME does not exist!" + exit 1 +fi + +if [ ! -f $HEADER_PATH/$HEADER_NAME ] +then + echo "$HEADER_PATH/$HEADER_NAME does not exist!" + exit 1 +fi + +# Displaying the supplied paths to the user +# So there will not be any mistakes +cat << EOF +Using $AARCH64_APPLE_IOS_PATH for aarch64-apple-ios +Using $AARCH64_APPLE_IOS_SIM_PATH for aarch64-apple-ios-sim +Using $X86_64_APPLE_IOS_PATH for x86_64-apple-ios + +Building xcframework with the following values: + +Name: $NAME +Version: $VERSION +Bundle identifier: $BUNDLE_IDENTIFIER +Library name: $LIBRARY_NAME +Framework name: $FRAMEWORK_NAME +XCFramework name: $XC_FRAMEWORK_NAME +Framework library name: $FRAMEWORK_LIBRARY_NAME + +EOF + +echo "Setting op output directory in $OUT_PATH" +mkdir $OUT_PATH + +echo "Combining aarch64 and x86-64 for the simulator.." +lipo -create $AARCH64_APPLE_IOS_SIM_PATH/$LIBRARY_NAME \ + $X86_64_APPLE_IOS_PATH/$LIBRARY_NAME \ + -output $OUT_PATH/sim-$LIBRARY_NAME + +echo "Creating a framework template..." +mkdir $OUT_PATH/$FRAMEWORK_NAME +cd $OUT_PATH/$FRAMEWORK_NAME +mkdir Headers +cp ../../$HEADER_PATH/$HEADER_NAME Headers/$FRAMEWORK_LIBRARY_NAME.h +mkdir Modules +touch Modules/module.modulemap +cat <> Modules/module.modulemap +framework module $FRAMEWORK_LIBRARY_NAME { + umbrella header "$FRAMEWORK_LIBRARY_NAME.h" + + export * + module * { export * } +} +EOT + +cat <> Info.plist + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $FRAMEWORK_LIBRARY_NAME + CFBundleIdentifier + $BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $NAME + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $VERSION + NSPrincipalClass + + MinimumOSVersion + $MIN_IOS_VERSION + + +EOT + +cd .. + +echo "Creating both frameworks (real device and simulator)..." +mkdir sim +mkdir real +cp -r $FRAMEWORK_NAME sim/ +cp -r $FRAMEWORK_NAME real/ +mv sim-$LIBRARY_NAME sim/$FRAMEWORK_NAME/$FRAMEWORK_LIBRARY_NAME +cp ../$AARCH64_APPLE_IOS_PATH/$LIBRARY_NAME real/$FRAMEWORK_NAME/$FRAMEWORK_LIBRARY_NAME + +echo "Creating XC Framework..." +xcodebuild -create-xcframework \ + -framework sim/$FRAMEWORK_NAME \ + -framework real/$FRAMEWORK_NAME \ + -output $XC_FRAMEWORK_NAME + +echo "cleaning up..." +rm -rf $FRAMEWORK_NAME real sim + +echo "Framework written to $OUT_PATH/$XC_FRAMEWORK_NAME" diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..3e122080 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi_build::generate_scaffolding("./uniffi/askar.udl").unwrap(); +} diff --git a/build.sh b/build.sh deleted file mode 100755 index b07a2c52..00000000 --- a/build.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/sh - -# NOTE: -# MacOS universal build currently requires MacOS 11 (Big Sur) for the appropriate SDK, -# and `sudo xcode-select --install` must be run to install the command line utilities. -# Rust's `beta` channel must be installed because aarch64 is still a tier-2 target: -# `rustup toolchain install beta`. -# The build command becomes `BUILD_TARGET=apple-darwin BUILD_TOOLCHAIN=beta ./build.sh` - -RUSTUP=${RUSTUP:-`command -v rustup`} -PROJECT=aries-askar - -if [ ! -x "$RUSTUP" ]; then - echo "rustup command not found: it can be obtained from https://rustup.rs/" - exit 1 -fi - -TOOLCHAIN=`$RUSTUP default` -TARGET="${BUILD_TARGET-}" - -if [ -z "$BUILD_TOOLCHAIN" ]; then - BUILD_TOOLCHAIN=${TOOLCHAIN%%-*} - if [ -z "$BUILD_TOOLCHAIN" ]; then - echo "Error: Could not determine default Rust toolchain" - exit 1 - fi -fi - -MACOS_UNIVERSAL_TARGETS="aarch64-apple-darwin x86_64-apple-darwin" - -# Fail on any execution errors -set -e - -if [ "$TARGET" = "apple-darwin" ]; then - # MacOS universal build - INSTALLED_TARGETS=`$RUSTUP +$BUILD_TOOLCHAIN target list --installed` - # Install target(s) as needed - echo "Checking install targets for MacOS universal build .." - for target in $MACOS_UNIVERSAL_TARGETS; do - if ! `echo "$INSTALLED_TARGETS" | grep -q $target`; then - $RUSTUP +$BUILD_TOOLCHAIN target add $target - fi - done -elif [ -z "$TARGET" ]; then - case "$TOOLCHAIN" in - *apple-darwin*) - # Check if the required targets for a universal build are installed - INSTALLED_TARGETS=`$RUSTUP +$BUILD_TOOLCHAIN target list --installed` - TARGET="apple-darwin" - for target in $MACOS_UNIVERSAL_TARGETS; do - if ! `echo "$INSTALLED_TARGETS" | grep -q $target`; then - TARGET= - break - fi - done - if [ "$TARGET" = "apple-darwin" ]; then - echo "Automatically enabled MacOS universal build" - else - echo "Universal MacOS build not enabled" - fi - esac -fi - -if [ "$TARGET" = "apple-darwin" ]; then - MAJOR_VER=`sw_vers | grep ProductVersion | cut -f 2 | cut -f 1 -d .` - if [ "$MAJOR_VER" -lt 11 ]; then - echo "MacOS universal build requires OS 11 (Big Sur) or newer" - TARGET= - fi -fi - -if [ "$TARGET" = "apple-darwin" ]; then - # Build both targets and combine them into a universal library with `lipo` - TARGET_LIBS= - for target in $MACOS_UNIVERSAL_TARGETS; do - echo "Building $PROJECT for toolchain '$BUILD_TOOLCHAIN', target '$target'.." - $RUSTUP run $BUILD_TOOLCHAIN cargo build --release --target $target - TARGET_LIBS="./target/$target/release/libaries_askar.dylib $TARGET_LIBS" - done - - mkdir -p ./target/release - OUTPUT="./target/release/libaries_askar.dylib" - echo "Combining targets into universal library" - lipo -create -output $OUTPUT $TARGET_LIBS -else - # Build normal target - echo "Building $PROJECT for toolchain '$BUILD_TOOLCHAIN'.." - CMD="$RUSTUP run $BUILD_TOOLCHAIN cargo build --release" - if [ -n "$TARGET" ]; then - $CMD --target "$TARGET" - else - $CMD - fi -fi diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..7227cee5 --- /dev/null +++ b/include/README.md @@ -0,0 +1,15 @@ +_Generating the C header:_ + +Install [cbindgen](https://github.com/eqrion/cbindgen/): + +```sh +cargo install cbindgen +``` + +From the root directory, generate the header file: + +```sh +cbindgen --config include/cbindgen.toml --output include/libaries_askar.h +``` + +Note that a few types are currently defined manually, such as `ByteBuffer`, because of limitations in the binding generator. diff --git a/include/cbindgen.toml b/include/cbindgen.toml new file mode 100644 index 00000000..b3f40f3d --- /dev/null +++ b/include/cbindgen.toml @@ -0,0 +1,18 @@ +language = "C" +cpp_compat = true + +pragma_once = true +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = false +usize_is_size_t = true +sort_by = "Name" +after_includes = """ + +typedef size_t ScanHandle; +typedef size_t StoreHandle; +typedef size_t SessionHandle; +""" + +[parse] +parse_deps = true +include = ["ffi-support", "ffi"] diff --git a/include/libaries_askar.h b/include/libaries_askar.h new file mode 100644 index 00000000..3dae491f --- /dev/null +++ b/include/libaries_askar.h @@ -0,0 +1,574 @@ +#pragma once + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include +#include + +typedef size_t ScanHandle; +typedef size_t StoreHandle; +typedef size_t SessionHandle; + + +enum ErrorCode +#ifdef __cplusplus + : int64_t +#endif // __cplusplus + { + Success = 0, + Backend = 1, + Busy = 2, + Duplicate = 3, + Encryption = 4, + Input = 5, + NotFound = 6, + Unexpected = 7, + Unsupported = 8, + Custom = 100, +}; +#ifndef __cplusplus +typedef int64_t ErrorCode; +#endif // __cplusplus + +typedef struct FfiResultList_Entry FfiResultList_Entry; + +typedef struct FfiResultList_KeyEntry FfiResultList_KeyEntry; + +/** + * A stored key entry + */ +typedef struct LocalKey LocalKey; + +typedef struct Option_EnabledCallback Option_EnabledCallback; + +typedef struct Option_FlushCallback Option_FlushCallback; + +typedef struct SecretBuffer { + int64_t len; + uint8_t *data; +} SecretBuffer; + +typedef struct FfiResultList_Entry FfiEntryList; + +typedef struct ArcHandle_FfiEntryList { + const FfiEntryList *_0; +} ArcHandle_FfiEntryList; + +typedef struct ArcHandle_FfiEntryList EntryListHandle; + +typedef struct ArcHandle_LocalKey { + const struct LocalKey *_0; +} ArcHandle_LocalKey; + +typedef struct ArcHandle_LocalKey LocalKeyHandle; + +/** + * ByteBuffer is a struct that represents an array of bytes to be sent over the FFI boundaries. + * There are several cases when you might want to use this, but the primary one for us + * is for returning protobuf-encoded data to Swift and Java. The type is currently rather + * limited (implementing almost no functionality), however in the future it may be + * more expanded. + * + * ## Caveats + * + * Note that the order of the fields is `len` (an i64) then `data` (a `*mut u8`), getting + * this wrong on the other side of the FFI will cause memory corruption and crashes. + * `i64` is used for the length instead of `u64` and `usize` because JNA has interop + * issues with both these types. + * + * ### `Drop` is not implemented + * + * ByteBuffer does not implement Drop. This is intentional. Memory passed into it will + * be leaked if it is not explicitly destroyed by calling [`ByteBuffer::destroy`], or + * [`ByteBuffer::destroy_into_vec`]. This is for two reasons: + * + * 1. In the future, we may allow it to be used for data that is not managed by + * the Rust allocator\*, and `ByteBuffer` assuming it's okay to automatically + * deallocate this data with the Rust allocator. + * + * 2. Automatically running destructors in unsafe code is a + * [frequent footgun](https://without.boats/blog/two-memory-bugs-from-ringbahn/) + * (among many similar issues across many crates). + * + * Note that calling `destroy` manually is often not needed, as usually you should + * be passing these to the function defined by [`define_bytebuffer_destructor!`] from + * the other side of the FFI. + * + * Because this type is essentially *only* useful in unsafe or FFI code (and because + * the most common usage pattern does not require manually managing the memory), it + * does not implement `Drop`. + * + * \* Note: in the case of multiple Rust shared libraries loaded at the same time, + * there may be multiple instances of "the Rust allocator" (one per shared library), + * in which case we're referring to whichever instance is active for the code using + * the `ByteBuffer`. Note that this doesn't occur on all platforms or build + * configurations, but treating allocators in different shared libraries as fully + * independent is always safe. + * + * ## Layout/fields + * + * This struct's field are not `pub` (mostly so that we can soundly implement `Send`, but also so + * that we can verify rust users are constructing them appropriately), the fields, their types, and + * their order are *very much* a part of the public API of this type. Consumers on the other side + * of the FFI will need to know its layout. + * + * If this were a C struct, it would look like + * + * ```c,no_run + * struct ByteBuffer { + * // Note: This should never be negative, but values above + * // INT64_MAX / i64::MAX are not allowed. + * int64_t len; + * // Note: nullable! + * uint8_t *data; + * }; + * ``` + * + * In rust, there are two fields, in this order: `len: i64`, and `data: *mut u8`. + * + * For clarity, the fact that the data pointer is nullable means that `Option` is not + * the same size as ByteBuffer, and additionally is not FFI-safe (the latter point is not + * currently guaranteed anyway as of the time of writing this comment). + * + * ### Description of fields + * + * `data` is a pointer to an array of `len` bytes. Note that data can be a null pointer and therefore + * should be checked. + * + * The bytes array is allocated on the heap and must be freed on it as well. Critically, if there + * are multiple rust shared libraries using being used in the same application, it *must be freed + * on the same heap that allocated it*, or you will corrupt both heaps. + * + * Typically, this object is managed on the other side of the FFI (on the "FFI consumer"), which + * means you must expose a function to release the resources of `data` which can be done easily + * using the [`define_bytebuffer_destructor!`] macro provided by this crate. + */ +typedef struct ByteBuffer { + int64_t len; + uint8_t *data; +} ByteBuffer; + +typedef struct EncryptedBuffer { + struct SecretBuffer buffer; + int64_t tag_pos; + int64_t nonce_pos; +} EncryptedBuffer; + +typedef struct AeadParams { + int32_t nonce_length; + int32_t tag_length; +} AeadParams; + +/** + * `FfiStr<'a>` is a safe (`#[repr(transparent)]`) wrapper around a + * nul-terminated `*const c_char` (e.g. a C string). Conceptually, it is + * similar to [`std::ffi::CStr`], except that it may be used in the signatures + * of extern "C" functions. + * + * Functions accepting strings should use this instead of accepting a C string + * directly. This allows us to write those functions using safe code without + * allowing safe Rust to cause memory unsafety. + * + * A single function for constructing these from Rust ([`FfiStr::from_raw`]) + * has been provided. Most of the time, this should not be necessary, and users + * should accept `FfiStr` in the parameter list directly. + * + * ## Caveats + * + * An effort has been made to make this struct hard to misuse, however it is + * still possible, if the `'static` lifetime is manually specified in the + * struct. E.g. + * + * ```rust,no_run + * # use ffi_support::FfiStr; + * // NEVER DO THIS + * #[no_mangle] + * extern "C" fn never_do_this(s: FfiStr<'static>) { + * // save `s` somewhere, and access it after this + * // function returns. + * } + * ``` + * + * Instead, one of the following patterns should be used: + * + * ``` + * # use ffi_support::FfiStr; + * #[no_mangle] + * extern "C" fn valid_use_1(s: FfiStr<'_>) { + * // Use of `s` after this function returns is impossible + * } + * // Alternative: + * #[no_mangle] + * extern "C" fn valid_use_2(s: FfiStr) { + * // Use of `s` after this function returns is impossible + * } + * ``` + */ +typedef const char *FfiStr; + +typedef struct FfiResultList_KeyEntry FfiKeyEntryList; + +typedef struct ArcHandle_FfiKeyEntryList { + const FfiKeyEntryList *_0; +} ArcHandle_FfiKeyEntryList; + +typedef struct ArcHandle_FfiKeyEntryList KeyEntryListHandle; + +typedef int64_t CallbackId; + +typedef void (*LogCallback)(const void *context, int32_t level, const char *target, const char *message, const char *module_path, const char *file, int32_t line); + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void askar_buffer_free(struct SecretBuffer buffer); + +void askar_clear_custom_logger(void); + +ErrorCode askar_entry_list_count(EntryListHandle handle, int32_t *count); + +void askar_entry_list_free(EntryListHandle handle); + +ErrorCode askar_entry_list_get_category(EntryListHandle handle, + int32_t index, + const char **category); + +ErrorCode askar_entry_list_get_name(EntryListHandle handle, int32_t index, const char **name); + +ErrorCode askar_entry_list_get_tags(EntryListHandle handle, int32_t index, const char **tags); + +ErrorCode askar_entry_list_get_value(EntryListHandle handle, + int32_t index, + struct SecretBuffer *value); + +ErrorCode askar_get_current_error(const char **error_json_p); + +ErrorCode askar_key_aead_decrypt(LocalKeyHandle handle, + struct ByteBuffer ciphertext, + struct ByteBuffer nonce, + struct ByteBuffer tag, + struct ByteBuffer aad, + struct SecretBuffer *out); + +ErrorCode askar_key_aead_encrypt(LocalKeyHandle handle, + struct ByteBuffer message, + struct ByteBuffer nonce, + struct ByteBuffer aad, + struct EncryptedBuffer *out); + +ErrorCode askar_key_aead_get_padding(LocalKeyHandle handle, int64_t msg_len, int32_t *out); + +ErrorCode askar_key_aead_get_params(LocalKeyHandle handle, struct AeadParams *out); + +ErrorCode askar_key_aead_random_nonce(LocalKeyHandle handle, struct SecretBuffer *out); + +ErrorCode askar_key_convert(LocalKeyHandle handle, FfiStr alg, LocalKeyHandle *out); + +ErrorCode askar_key_crypto_box(LocalKeyHandle recip_key, + LocalKeyHandle sender_key, + struct ByteBuffer message, + struct ByteBuffer nonce, + struct SecretBuffer *out); + +ErrorCode askar_key_crypto_box_open(LocalKeyHandle recip_key, + LocalKeyHandle sender_key, + struct ByteBuffer message, + struct ByteBuffer nonce, + struct SecretBuffer *out); + +ErrorCode askar_key_crypto_box_random_nonce(struct SecretBuffer *out); + +ErrorCode askar_key_crypto_box_seal(LocalKeyHandle handle, + struct ByteBuffer message, + struct SecretBuffer *out); + +ErrorCode askar_key_crypto_box_seal_open(LocalKeyHandle handle, + struct ByteBuffer ciphertext, + struct SecretBuffer *out); + +ErrorCode askar_key_derive_ecdh_1pu(FfiStr alg, + LocalKeyHandle ephem_key, + LocalKeyHandle sender_key, + LocalKeyHandle recip_key, + struct ByteBuffer alg_id, + struct ByteBuffer apu, + struct ByteBuffer apv, + struct ByteBuffer cc_tag, + int8_t receive, + LocalKeyHandle *out); + +ErrorCode askar_key_derive_ecdh_es(FfiStr alg, + LocalKeyHandle ephem_key, + LocalKeyHandle recip_key, + struct ByteBuffer alg_id, + struct ByteBuffer apu, + struct ByteBuffer apv, + int8_t receive, + LocalKeyHandle *out); + +ErrorCode askar_key_entry_list_count(KeyEntryListHandle handle, int32_t *count); + +void askar_key_entry_list_free(KeyEntryListHandle handle); + +ErrorCode askar_key_entry_list_get_algorithm(KeyEntryListHandle handle, + int32_t index, + const char **alg); + +ErrorCode askar_key_entry_list_get_metadata(KeyEntryListHandle handle, + int32_t index, + const char **metadata); + +ErrorCode askar_key_entry_list_get_name(KeyEntryListHandle handle, + int32_t index, + const char **name); + +ErrorCode askar_key_entry_list_get_tags(KeyEntryListHandle handle, + int32_t index, + const char **tags); + +ErrorCode askar_key_entry_list_load_local(KeyEntryListHandle handle, + int32_t index, + LocalKeyHandle *out); + +void askar_key_free(LocalKeyHandle handle); + +ErrorCode askar_key_from_jwk(struct ByteBuffer jwk, LocalKeyHandle *out); + +ErrorCode askar_key_from_key_exchange(FfiStr alg, + LocalKeyHandle sk_handle, + LocalKeyHandle pk_handle, + LocalKeyHandle *out); + +ErrorCode askar_key_from_public_bytes(FfiStr alg, struct ByteBuffer public_, LocalKeyHandle *out); + +ErrorCode askar_key_from_secret_bytes(FfiStr alg, struct ByteBuffer secret, LocalKeyHandle *out); + +ErrorCode askar_key_from_seed(FfiStr alg, + struct ByteBuffer seed, + FfiStr method, + LocalKeyHandle *out); + +ErrorCode askar_key_generate(FfiStr alg, int8_t ephemeral, LocalKeyHandle *out); + +ErrorCode askar_key_get_algorithm(LocalKeyHandle handle, const char **out); + +ErrorCode askar_key_get_ephemeral(LocalKeyHandle handle, int8_t *out); + +ErrorCode askar_key_get_jwk_public(LocalKeyHandle handle, FfiStr alg, const char **out); + +ErrorCode askar_key_get_jwk_secret(LocalKeyHandle handle, struct SecretBuffer *out); + +ErrorCode askar_key_get_jwk_thumbprint(LocalKeyHandle handle, FfiStr alg, const char **out); + +ErrorCode askar_key_get_public_bytes(LocalKeyHandle handle, struct SecretBuffer *out); + +ErrorCode askar_key_get_secret_bytes(LocalKeyHandle handle, struct SecretBuffer *out); + +ErrorCode askar_key_sign_message(LocalKeyHandle handle, + struct ByteBuffer message, + FfiStr sig_type, + struct SecretBuffer *out); + +ErrorCode askar_key_unwrap_key(LocalKeyHandle handle, + FfiStr alg, + struct ByteBuffer ciphertext, + struct ByteBuffer nonce, + struct ByteBuffer tag, + LocalKeyHandle *out); + +ErrorCode askar_key_verify_signature(LocalKeyHandle handle, + struct ByteBuffer message, + struct ByteBuffer signature, + FfiStr sig_type, + int8_t *out); + +ErrorCode askar_key_wrap_key(LocalKeyHandle handle, + LocalKeyHandle other, + struct ByteBuffer nonce, + struct EncryptedBuffer *out); + +/** + * Migrate an sqlite wallet from an indy-sdk structure to an aries-askar structure. + * It is important to note that this does not do any post-processing. If the record values, tags, + * names, etc. have changed, it must be processed manually afterwards. This script does the following: + * + * 1. Create and rename the required tables + * 2. Fetch the indy key from the wallet + * 3. Create a new configuration + * 4. Initialize a profile + * 5. Update the items from the indy-sdk + * 6. Clean up (drop tables and add a version of "1") + */ +ErrorCode askar_migrate_indy_sdk(FfiStr spec_uri, + FfiStr wallet_name, + FfiStr wallet_key, + FfiStr kdf_level, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_scan_free(ScanHandle handle); + +ErrorCode askar_scan_next(ScanHandle handle, + void (*cb)(CallbackId cb_id, ErrorCode err, EntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_scan_start(StoreHandle handle, + FfiStr profile, + FfiStr category, + FfiStr tag_filter, + int64_t offset, + int64_t limit, + void (*cb)(CallbackId cb_id, ErrorCode err, ScanHandle handle), + CallbackId cb_id); + +ErrorCode askar_session_close(SessionHandle handle, + int8_t commit, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_session_count(SessionHandle handle, + FfiStr category, + FfiStr tag_filter, + void (*cb)(CallbackId cb_id, ErrorCode err, int64_t count), + CallbackId cb_id); + +ErrorCode askar_session_fetch(SessionHandle handle, + FfiStr category, + FfiStr name, + int8_t for_update, + void (*cb)(CallbackId cb_id, ErrorCode err, EntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_session_fetch_all(SessionHandle handle, + FfiStr category, + FfiStr tag_filter, + int64_t limit, + int8_t for_update, + void (*cb)(CallbackId cb_id, ErrorCode err, EntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_session_fetch_all_keys(SessionHandle handle, + FfiStr alg, + FfiStr thumbprint, + FfiStr tag_filter, + int64_t limit, + int8_t for_update, + void (*cb)(CallbackId cb_id, ErrorCode err, KeyEntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_session_fetch_key(SessionHandle handle, + FfiStr name, + int8_t for_update, + void (*cb)(CallbackId cb_id, ErrorCode err, KeyEntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_session_insert_key(SessionHandle handle, + LocalKeyHandle key_handle, + FfiStr name, + FfiStr metadata, + FfiStr tags, + int64_t expiry_ms, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_session_remove_all(SessionHandle handle, + FfiStr category, + FfiStr tag_filter, + void (*cb)(CallbackId cb_id, ErrorCode err, int64_t removed), + CallbackId cb_id); + +ErrorCode askar_session_remove_key(SessionHandle handle, + FfiStr name, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_session_start(StoreHandle handle, + FfiStr profile, + int8_t as_transaction, + void (*cb)(CallbackId cb_id, ErrorCode err, SessionHandle handle), + CallbackId cb_id); + +ErrorCode askar_session_update(SessionHandle handle, + int8_t operation, + FfiStr category, + FfiStr name, + struct ByteBuffer value, + FfiStr tags, + int64_t expiry_ms, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_session_update_key(SessionHandle handle, + FfiStr name, + FfiStr metadata, + FfiStr tags, + int64_t expiry_ms, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_set_custom_logger(const void *context, + LogCallback log, + struct Option_EnabledCallback enabled, + struct Option_FlushCallback flush, + int32_t max_level); + +ErrorCode askar_set_default_logger(void); + +ErrorCode askar_set_max_log_level(int32_t max_level); + +ErrorCode askar_store_close(StoreHandle handle, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_store_create_profile(StoreHandle handle, + FfiStr profile, + void (*cb)(CallbackId cb_id, ErrorCode err, const char *result_p), + CallbackId cb_id); + +ErrorCode askar_store_generate_raw_key(struct ByteBuffer seed, const char **out); + +ErrorCode askar_store_get_profile_name(StoreHandle handle, + void (*cb)(CallbackId cb_id, ErrorCode err, const char *name), + CallbackId cb_id); + +ErrorCode askar_store_open(FfiStr spec_uri, + FfiStr key_method, + FfiStr pass_key, + FfiStr profile, + void (*cb)(CallbackId cb_id, ErrorCode err, StoreHandle handle), + CallbackId cb_id); + +ErrorCode askar_store_provision(FfiStr spec_uri, + FfiStr key_method, + FfiStr pass_key, + FfiStr profile, + int8_t recreate, + void (*cb)(CallbackId cb_id, ErrorCode err, StoreHandle handle), + CallbackId cb_id); + +ErrorCode askar_store_rekey(StoreHandle handle, + FfiStr key_method, + FfiStr pass_key, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_store_remove(FfiStr spec_uri, + void (*cb)(CallbackId cb_id, ErrorCode err, int8_t), + CallbackId cb_id); + +ErrorCode askar_store_remove_profile(StoreHandle handle, + FfiStr profile, + void (*cb)(CallbackId cb_id, ErrorCode err, int8_t removed), + CallbackId cb_id); + +void askar_terminate(void); + +char *askar_version(void); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/src/backend/any.rs b/src/backend/any.rs deleted file mode 100644 index 1d084c6c..00000000 --- a/src/backend/any.rs +++ /dev/null @@ -1,343 +0,0 @@ -use super::{Backend, ManageBackend, QueryBackend}; -use crate::{ - error::Error, - future::BoxFuture, - protect::{PassKey, StoreKeyMethod}, - storage::{ - Entry, EntryKind, EntryOperation, EntryTag, IntoOptions, Scan, Session, Store, TagFilter, - }, -}; - -#[cfg(feature = "postgres")] -use super::postgres::{self, PostgresStore}; - -#[cfg(feature = "sqlite")] -use super::sqlite::{self, SqliteStore}; - -/// A generic `Store` implementation for any supported backend -pub type AnyStore = Store; - -/// A generic `Session` implementation for any supported backend -pub type AnySession = Session; - -/// An enumeration of supported store backends -#[derive(Debug)] -pub enum AnyBackend { - /// A PostgreSQL store - #[cfg(feature = "postgres")] - Postgres(PostgresStore), - - /// A Sqlite store - #[cfg(feature = "sqlite")] - Sqlite(SqliteStore), - - #[allow(unused)] - #[doc(hidden)] - Other, -} - -macro_rules! with_backend { - ($slf:ident, $ident:ident, $body:expr) => { - match $slf { - #[cfg(feature = "postgres")] - Self::Postgres($ident) => $body, - - #[cfg(feature = "sqlite")] - Self::Sqlite($ident) => $body, - - _ => unreachable!(), - } - }; -} - -impl Backend for AnyBackend { - type Session = AnyQueryBackend; - - fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { - with_backend!(self, store, store.create_profile(name)) - } - - fn get_profile_name(&self) -> &str { - with_backend!(self, store, store.get_profile_name()) - } - - fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { - with_backend!(self, store, store.remove_profile(name)) - } - - fn scan( - &self, - profile: Option, - kind: EntryKind, - category: String, - tag_filter: Option, - offset: Option, - limit: Option, - ) -> BoxFuture<'_, Result, Error>> { - with_backend!( - self, - store, - store.scan(profile, kind, category, tag_filter, offset, limit) - ) - } - - fn session(&self, profile: Option, transaction: bool) -> Result { - match self { - #[cfg(feature = "postgres")] - Self::Postgres(store) => { - let session = store.session(profile, transaction)?; - Ok(AnyQueryBackend::PostgresSession(session)) - } - - #[cfg(feature = "sqlite")] - Self::Sqlite(store) => { - let session = store.session(profile, transaction)?; - Ok(AnyQueryBackend::SqliteSession(session)) - } - - _ => unreachable!(), - } - } - - fn rekey_backend( - &mut self, - method: StoreKeyMethod, - pass_key: PassKey<'_>, - ) -> BoxFuture<'_, Result<(), Error>> { - with_backend!(self, store, store.rekey_backend(method, pass_key)) - } - - fn close(&self) -> BoxFuture<'_, Result<(), Error>> { - with_backend!(self, store, store.close()) - } -} - -/// An enumeration of supported backend session types -#[derive(Debug)] -pub enum AnyQueryBackend { - /// A PostgreSQL store session - #[cfg(feature = "postgres")] - PostgresSession(::Session), - - /// A Sqlite store session - #[cfg(feature = "sqlite")] - SqliteSession(::Session), - - #[allow(unused)] - #[doc(hidden)] - Other, -} - -impl QueryBackend for AnyQueryBackend { - fn count<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - tag_filter: Option, - ) -> BoxFuture<'q, Result> { - match self { - #[cfg(feature = "postgres")] - Self::PostgresSession(session) => session.count(kind, category, tag_filter), - - #[cfg(feature = "sqlite")] - Self::SqliteSession(session) => session.count(kind, category, tag_filter), - - _ => unreachable!(), - } - } - - fn fetch<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - name: &'q str, - for_update: bool, - ) -> BoxFuture<'q, Result, Error>> { - match self { - #[cfg(feature = "postgres")] - Self::PostgresSession(session) => session.fetch(kind, category, name, for_update), - - #[cfg(feature = "sqlite")] - Self::SqliteSession(session) => session.fetch(kind, category, name, for_update), - - _ => unreachable!(), - } - } - - fn fetch_all<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - tag_filter: Option, - limit: Option, - for_update: bool, - ) -> BoxFuture<'q, Result, Error>> { - match self { - #[cfg(feature = "postgres")] - Self::PostgresSession(session) => { - session.fetch_all(kind, category, tag_filter, limit, for_update) - } - - #[cfg(feature = "sqlite")] - Self::SqliteSession(session) => { - session.fetch_all(kind, category, tag_filter, limit, for_update) - } - - _ => unreachable!(), - } - } - - fn remove_all<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - tag_filter: Option, - ) -> BoxFuture<'q, Result> { - match self { - #[cfg(feature = "postgres")] - Self::PostgresSession(session) => session.remove_all(kind, category, tag_filter), - - #[cfg(feature = "sqlite")] - Self::SqliteSession(session) => session.remove_all(kind, category, tag_filter), - - _ => unreachable!(), - } - } - - fn update<'q>( - &'q mut self, - kind: EntryKind, - operation: EntryOperation, - category: &'q str, - name: &'q str, - value: Option<&'q [u8]>, - tags: Option<&'q [EntryTag]>, - expiry_ms: Option, - ) -> BoxFuture<'q, Result<(), Error>> { - match self { - #[cfg(feature = "postgres")] - Self::PostgresSession(session) => { - session.update(kind, operation, category, name, value, tags, expiry_ms) - } - - #[cfg(feature = "sqlite")] - Self::SqliteSession(session) => { - session.update(kind, operation, category, name, value, tags, expiry_ms) - } - - _ => unreachable!(), - } - } - - fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>> { - match self { - #[cfg(feature = "postgres")] - Self::PostgresSession(session) => Box::pin(session.close(commit)), - - #[cfg(feature = "sqlite")] - Self::SqliteSession(session) => Box::pin(session.close(commit)), - - _ => unreachable!(), - } - } -} - -impl<'a> ManageBackend<'a> for &'a str { - type Store = AnyStore; - - fn open_backend( - self, - method: Option, - pass_key: PassKey<'a>, - profile: Option<&'a str>, - ) -> BoxFuture<'a, Result> { - Box::pin(async move { - let opts = self.into_options()?; - debug!("Open store with options: {:?}", &opts); - - match opts.schema.as_ref() { - #[cfg(feature = "postgres")] - "postgres" => { - let opts = postgres::PostgresStoreOptions::new(opts)?; - let mgr = opts.open(method, pass_key, profile).await?; - Ok(Store::new(AnyBackend::Postgres(mgr.into_inner()))) - } - - #[cfg(feature = "sqlite")] - "sqlite" => { - let opts = sqlite::SqliteStoreOptions::new(opts)?; - let mgr = opts.open(method, pass_key, profile).await?; - Ok(Store::new(AnyBackend::Sqlite(mgr.into_inner()))) - } - - _ => Err(err_msg!( - Unsupported, - "Unsupported backend: {}", - &opts.schema - )), - } - }) - } - - fn provision_backend( - self, - method: StoreKeyMethod, - pass_key: PassKey<'a>, - profile: Option<&'a str>, - recreate: bool, - ) -> BoxFuture<'a, Result> { - Box::pin(async move { - let opts = self.into_options()?; - debug!("Provision store with options: {:?}", &opts); - - match opts.schema.as_ref() { - #[cfg(feature = "postgres")] - "postgres" => { - let opts = postgres::PostgresStoreOptions::new(opts)?; - let mgr = opts.provision(method, pass_key, profile, recreate).await?; - Ok(Store::new(AnyBackend::Postgres(mgr.into_inner()))) - } - - #[cfg(feature = "sqlite")] - "sqlite" => { - let opts = sqlite::SqliteStoreOptions::new(opts)?; - let mgr = opts.provision(method, pass_key, profile, recreate).await?; - Ok(Store::new(AnyBackend::Sqlite(mgr.into_inner()))) - } - - _ => Err(err_msg!( - Unsupported, - "Unsupported backend: {}", - &opts.schema - )), - } - }) - } - - fn remove_backend(self) -> BoxFuture<'a, Result> { - Box::pin(async move { - let opts = self.into_options()?; - debug!("Remove store with options: {:?}", &opts); - - match opts.schema.as_ref() { - #[cfg(feature = "postgres")] - "postgres" => { - let opts = postgres::PostgresStoreOptions::new(opts)?; - Ok(opts.remove().await?) - } - - #[cfg(feature = "sqlite")] - "sqlite" => { - let opts = sqlite::SqliteStoreOptions::new(opts)?; - Ok(opts.remove().await?) - } - - _ => Err(err_msg!( - Unsupported, - "Unsupported backend: {}", - &opts.schema - )), - } - }) - } -} diff --git a/src/backend/mod.rs b/src/backend/mod.rs deleted file mode 100644 index 537e24ea..00000000 --- a/src/backend/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Storage backends supported by aries-askar - -#[cfg(feature = "any")] -#[cfg_attr(docsrs, doc(cfg(feature = "any")))] -/// Generic backend (from URI) support -pub mod any; - -#[cfg(any(feature = "postgres", feature = "sqlite"))] -pub(crate) mod db_utils; - -#[cfg(feature = "postgres")] -#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))] -/// Postgres database support -pub mod postgres; - -#[cfg(feature = "sqlite")] -#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] -/// Sqlite database support -pub mod sqlite; - -mod types; -pub use self::types::{Backend, ManageBackend, QueryBackend}; diff --git a/src/backend/types.rs b/src/backend/types.rs deleted file mode 100644 index d397a529..00000000 --- a/src/backend/types.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::{ - error::Error, - future::BoxFuture, - protect::{PassKey, StoreKeyMethod}, - storage::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, -}; - -/// Represents a generic backend implementation -pub trait Backend: Send + Sync { - /// The type of session managed by this backend - type Session: QueryBackend; - - /// Create a new profile - fn create_profile(&self, name: Option) -> BoxFuture<'_, Result>; - - /// Get the name of the active profile - fn get_profile_name(&self) -> &str; - - /// Remove an existing profile - fn remove_profile(&self, name: String) -> BoxFuture<'_, Result>; - - /// Create a [`Scan`] against the store - fn scan( - &self, - profile: Option, - kind: EntryKind, - category: String, - tag_filter: Option, - offset: Option, - limit: Option, - ) -> BoxFuture<'_, Result, Error>>; - - /// Create a new session against the store - fn session(&self, profile: Option, transaction: bool) -> Result; - - /// Replace the wrapping key of the store - fn rekey_backend( - &mut self, - method: StoreKeyMethod, - key: PassKey<'_>, - ) -> BoxFuture<'_, Result<(), Error>>; - - /// Close the store instance - fn close(&self) -> BoxFuture<'_, Result<(), Error>>; -} - -/// Create, open, or remove a generic backend implementation -pub trait ManageBackend<'a> { - /// The type of store being managed - type Store; - - /// Open an existing store - fn open_backend( - self, - method: Option, - pass_key: PassKey<'a>, - profile: Option<&'a str>, - ) -> BoxFuture<'a, Result>; - - /// Provision a new store - fn provision_backend( - self, - method: StoreKeyMethod, - pass_key: PassKey<'a>, - profile: Option<&'a str>, - recreate: bool, - ) -> BoxFuture<'a, Result>; - - /// Remove an existing store - fn remove_backend(self) -> BoxFuture<'a, Result>; -} - -/// Query from a generic backend implementation -pub trait QueryBackend: Send { - /// Count the number of matching records in the store - fn count<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - tag_filter: Option, - ) -> BoxFuture<'q, Result>; - - /// Fetch a single record from the store by category and name - fn fetch<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - name: &'q str, - for_update: bool, - ) -> BoxFuture<'q, Result, Error>>; - - /// Fetch all matching records from the store - fn fetch_all<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - tag_filter: Option, - limit: Option, - for_update: bool, - ) -> BoxFuture<'q, Result, Error>>; - - /// Remove all matching records from the store - fn remove_all<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - tag_filter: Option, - ) -> BoxFuture<'q, Result>; - - /// Insert or replace a record in the store - fn update<'q>( - &'q mut self, - kind: EntryKind, - operation: EntryOperation, - category: &'q str, - name: &'q str, - value: Option<&'q [u8]>, - tags: Option<&'q [EntryTag]>, - expiry_ms: Option, - ) -> BoxFuture<'q, Result<(), Error>>; - - /// Close the current store session - fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>>; -} diff --git a/src/error.rs b/src/error.rs index b20aa9f7..0d0f805e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,7 @@ use std::error::Error as StdError; use std::fmt::{self, Display, Formatter}; use crate::crypto::{Error as CryptoError, ErrorKind as CryptoErrorKind}; +use crate::storage::{Error as StorageError, ErrorKind as StorageErrorKind}; /// The possible kinds of error produced by the crate #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -81,7 +82,7 @@ impl Error { /// Accessor for the error message pub fn message(&self) -> Option<&str> { - self.message.as_ref().map(String::as_str) + self.message.as_deref() } pub(crate) fn with_cause>>( @@ -131,15 +132,6 @@ impl From for Error { } } -// FIXME would be preferable to remove this auto-conversion and handle -// all sqlx errors manually, to ensure there is some context around the error -#[cfg(any(feature = "indy_compat", feature = "postgres", feature = "sqlite"))] -impl From for Error { - fn from(err: sqlx::Error) -> Self { - Error::from(ErrorKind::Backend).with_cause(err) - } -} - impl From for Error { fn from(err: CryptoError) -> Self { let kind = match err.kind() { @@ -157,6 +149,28 @@ impl From for Error { } } +impl From for Error { + fn from(err: StorageError) -> Self { + let (kind, cause, message) = err.into_parts(); + let kind = match kind { + StorageErrorKind::Backend => ErrorKind::Backend, + StorageErrorKind::Busy => ErrorKind::Busy, + StorageErrorKind::Custom => ErrorKind::Custom, + StorageErrorKind::Duplicate => ErrorKind::Duplicate, + StorageErrorKind::Encryption => ErrorKind::Encryption, + StorageErrorKind::Input => ErrorKind::Input, + StorageErrorKind::NotFound => ErrorKind::NotFound, + StorageErrorKind::Unexpected => ErrorKind::Unexpected, + StorageErrorKind::Unsupported => ErrorKind::Unsupported, + }; + Error { + kind, + cause, + message, + } + } +} + macro_rules! err_msg { () => { $crate::error::Error::from($crate::error::ErrorKind::Input) diff --git a/src/ffi/error.rs b/src/ffi/error.rs index 1ab29e6c..cf20bc8e 100644 --- a/src/ffi/error.rs +++ b/src/ffi/error.rs @@ -9,7 +9,7 @@ use once_cell::sync::Lazy; static LAST_ERROR: Lazy>> = Lazy::new(|| RwLock::new(None)); -#[derive(Debug, PartialEq, Copy, Clone, Serialize)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize)] #[repr(i64)] pub enum ErrorCode { Success = 0, @@ -60,11 +60,16 @@ pub extern "C" fn askar_get_current_error(error_json_p: *mut *const c_char) -> E } pub fn get_current_error_json() -> String { + #[derive(Serialize)] + struct ErrorJson { + code: usize, + message: String, + } + if let Some(err) = Option::take(&mut *LAST_ERROR.write().unwrap()) { let message = err.to_string(); let code = ErrorCode::from(err.kind()) as usize; - // let extra = err.extra(); - json!({"code": code, "message": message}).to_string() + serde_json::json!(&ErrorJson { code, message }).to_string() } else { r#"{"code":0,"message":null}"#.to_owned() } diff --git a/src/ffi/handle.rs b/src/ffi/handle.rs index a1da1c79..09d2a3b2 100644 --- a/src/ffi/handle.rs +++ b/src/ffi/handle.rs @@ -1,7 +1,12 @@ -use std::{fmt::Display, mem, ptr, sync::Arc}; +use std::{ + fmt::{Debug, Display}, + mem, ptr, + sync::Arc, +}; use crate::error::Error; +#[derive(Debug)] #[repr(C)] pub struct ArcHandle(*const T); @@ -17,15 +22,13 @@ impl ArcHandle { pub fn load(&self) -> Result, Error> { self.validate()?; - unsafe { - let result = mem::ManuallyDrop::new(Arc::from_raw(self.0)); - Ok((&*result).clone()) - } + let result = unsafe { mem::ManuallyDrop::new(Arc::from_raw(self.0)) }; + Ok(Arc::clone(&result)) } pub fn remove(&self) { - unsafe { - if !self.0.is_null() { + if !self.0.is_null() { + unsafe { // Drop the initial reference. There could be others outstanding. Arc::decrement_strong_count(self.0); } @@ -48,7 +51,7 @@ impl std::fmt::Display for ArcHandle { } } -pub trait ResourceHandle: Copy + Ord + From + Display { +pub trait ResourceHandle: Copy + Eq + Ord + From + Debug + Display { fn invalid() -> Self { Self::from(0) } diff --git a/src/ffi/key.rs b/src/ffi/key.rs index 49ab97ad..099570de 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -383,7 +383,7 @@ pub extern "C" fn askar_key_wrap_key( check_useful_c_ptr!(out); let key = handle.load()?; let other = other.load()?; - let result = key.wrap_key(&*other, nonce.as_slice())?; + let result = key.wrap_key(&other, nonce.as_slice())?; unsafe { *out = EncryptedBuffer::from_encrypted(result) }; Ok(ErrorCode::Success) } @@ -435,8 +435,8 @@ pub extern "C" fn askar_key_crypto_box( let recip_key = recip_key.load()?; let sender_key = sender_key.load()?; let message = crypto_box( - &*recip_key, - &*sender_key, + &recip_key, + &sender_key, message.as_slice(), nonce.as_slice() )?; @@ -459,8 +459,8 @@ pub extern "C" fn askar_key_crypto_box_open( let recip_key = recip_key.load()?; let sender_key = sender_key.load()?; let message = crypto_box_open( - &*recip_key, - &*sender_key, + &recip_key, + &sender_key, message.as_slice(), nonce.as_slice() )?; diff --git a/src/ffi/log.rs b/src/ffi/log.rs index 4b522812..abcc7c8e 100644 --- a/src/ffi/log.rs +++ b/src/ffi/log.rs @@ -50,13 +50,13 @@ impl CustomLogger { } fn disable(&self) { - self.disabled.store(false, Ordering::Release); + self.disabled.store(true, Ordering::Release); } } impl log::Log for CustomLogger { fn enabled(&self, metadata: &Metadata<'_>) -> bool { - if !self.disabled.load(Ordering::Acquire) { + if self.disabled.load(Ordering::Acquire) { false } else if let Some(enabled_cb) = self.enabled { enabled_cb(self.context, metadata.level() as i32) != 0 @@ -66,6 +66,10 @@ impl log::Log for CustomLogger { } fn log(&self, record: &Record<'_>) { + if !self.enabled(record.metadata()) { + return; + } + let log_cb = self.log; let level = record.level() as i32; diff --git a/src/ffi/migration.rs b/src/ffi/migration.rs new file mode 100644 index 00000000..c397aa84 --- /dev/null +++ b/src/ffi/migration.rs @@ -0,0 +1,54 @@ +use ffi_support::FfiStr; + +use crate::storage::future::spawn_ok; +use crate::storage::migration::IndySdkToAriesAskarMigration; + +use super::{ + error::{set_last_error, ErrorCode}, + CallbackId, EnsureCallback, +}; + +/// Migrate an sqlite wallet from an indy-sdk structure to an aries-askar structure. +/// It is important to note that this does not do any post-processing. If the record values, tags, +/// names, etc. have changed, it must be processed manually afterwards. This script does the following: +/// +/// 1. Create and rename the required tables +/// 2. Fetch the indy key from the wallet +/// 3. Create a new configuration +/// 4. Initialize a profile +/// 5. Update the items from the indy-sdk +/// 6. Clean up (drop tables and add a version of "1") +#[no_mangle] +pub extern "C" fn askar_migrate_indy_sdk( + spec_uri: FfiStr<'_>, + wallet_name: FfiStr<'_>, + wallet_key: FfiStr<'_>, + kdf_level: FfiStr<'_>, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err!( + trace!("Migrate sqlite wallet from indy-sdk structure to aries-askar"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let spec_uri = spec_uri.into_opt_string().ok_or_else(|| err_msg!("No provision spec URI provided"))?; + let wallet_name = wallet_name.into_opt_string().ok_or_else(|| err_msg!("No wallet name provided"))?; + let wallet_key = wallet_key.into_opt_string().ok_or_else(|| err_msg!("No wallet key provided"))?; + let kdf_level = kdf_level.into_opt_string().ok_or_else(|| err_msg!("No KDF level provided"))?; + + let cb = EnsureCallback::new(move |result| + match result { + Ok(_) => cb(cb_id, ErrorCode::Success), + Err(err) => cb(cb_id, set_last_error(Some(err))), + }); + + spawn_ok(async move { + let result = async { + let migrator = IndySdkToAriesAskarMigration::connect(&spec_uri, &wallet_name, &wallet_key, &kdf_level).await?; + migrator.migrate().await?; + Ok(()) + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + ) +} diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 29460c9f..d52fc47c 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -1,5 +1,6 @@ use std::marker::PhantomData; use std::os::raw::c_char; +use std::time::Duration; use ffi_support::rust_string_to_c; @@ -22,6 +23,10 @@ mod log; mod result_list; mod secret; mod store; +pub(crate) mod tags; + +#[cfg(all(feature = "migration", feature = "sqlite"))] +mod migration; use self::error::ErrorCode; use crate::error::Error; @@ -60,6 +65,11 @@ impl)> Drop for EnsureCallback { } } +#[no_mangle] +pub extern "C" fn askar_terminate() { + crate::future::shutdown(Duration::from_secs(5)); +} + #[no_mangle] pub extern "C" fn askar_version() -> *mut c_char { rust_string_to_c(LIB_VERSION.to_owned()) diff --git a/src/ffi/result_list.rs b/src/ffi/result_list.rs index 38b15a9c..a4edd0f1 100644 --- a/src/ffi/result_list.rs +++ b/src/ffi/result_list.rs @@ -1,11 +1,9 @@ use std::{ffi::CString, os::raw::c_char, ptr}; -use super::{handle::ArcHandle, key::LocalKeyHandle, secret::SecretBuffer, ErrorCode}; -use crate::{ - error::Error, - kms::KeyEntry, - storage::{Entry, EntryTagSet}, +use super::{ + handle::ArcHandle, key::LocalKeyHandle, secret::SecretBuffer, tags::EntryTagSet, ErrorCode, }; +use crate::{entry::Entry, error::Error, kms::KeyEntry}; pub enum FfiResultList { Single(R), @@ -28,7 +26,7 @@ impl FfiResultList { } } } - return Err(err_msg!(Input, "Invalid index for result set")); + Err(err_msg!(Input, "Invalid index for result set")) } pub fn len(&self) -> i32 { @@ -246,3 +244,37 @@ pub extern "C" fn askar_key_entry_list_load_local( Ok(ErrorCode::Success) } } + +pub type StringListHandle = ArcHandle; + +pub type FfiStringList = FfiResultList; + +#[no_mangle] +pub extern "C" fn askar_string_list_count(handle: StringListHandle, count: *mut i32) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(count); + let results = handle.load()?; + unsafe { *count = results.len() }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_string_list_get_item( + handle: StringListHandle, + index: i32, + item: *mut *const c_char, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(item); + let results = handle.load()?; + let entry = results.get_row(index)?; + unsafe { *item = CString::new(entry.clone()).unwrap().into_raw() }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_string_list_free(handle: StringListHandle) { + handle.remove(); +} diff --git a/src/ffi/secret.rs b/src/ffi/secret.rs index e24b0fb7..0452f8c5 100644 --- a/src/ffi/secret.rs +++ b/src/ffi/secret.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, mem, ptr}; +use std::{mem, ptr}; use crate::{crypto::buffer::SecretBytes, kms::Encrypted}; diff --git a/src/ffi/store.rs b/src/ffi/store.rs index 6d43ac03..95e7f0c7 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, os::raw::c_char, ptr, str::FromStr, sync::Arc}; +use std::{collections::BTreeMap, ffi::CString, os::raw::c_char, ptr, str::FromStr, sync::Arc}; use async_lock::{Mutex as TryMutex, MutexGuardArc as TryMutexGuard, RwLock}; use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; @@ -7,40 +7,40 @@ use once_cell::sync::Lazy; use super::{ error::set_last_error, key::LocalKeyHandle, - result_list::{EntryListHandle, FfiEntryList, FfiKeyEntryList, KeyEntryListHandle}, + result_list::{ + EntryListHandle, FfiEntryList, FfiKeyEntryList, KeyEntryListHandle, StringListHandle, + }, + tags::EntryTagSet, CallbackId, EnsureCallback, ErrorCode, ResourceHandle, }; use crate::{ - backend::{ - any::{AnySession, AnyStore}, - ManageBackend, - }, + entry::{Entry, EntryOperation, Scan, TagFilter}, error::Error, + ffi::result_list::FfiStringList, future::spawn_ok, - protect::{generate_raw_store_key, PassKey, StoreKeyMethod}, - storage::{Entry, EntryOperation, EntryTagSet, Scan, TagFilter}, + store::{PassKey, Session, Store, StoreKeyMethod}, }; new_sequence_handle!(StoreHandle, FFI_STORE_COUNTER); new_sequence_handle!(SessionHandle, FFI_SESSION_COUNTER); new_sequence_handle!(ScanHandle, FFI_SCAN_COUNTER); -static FFI_STORES: Lazy>>> = +static FFI_STORES: Lazy>> = Lazy::new(|| RwLock::new(BTreeMap::new())); -static FFI_SESSIONS: Lazy> = - Lazy::new(|| StoreResourceMap::new()); +static FFI_SESSIONS: Lazy> = + Lazy::new(StoreResourceMap::new); static FFI_SCANS: Lazy>> = - Lazy::new(|| StoreResourceMap::new()); + Lazy::new(StoreResourceMap::new); impl StoreHandle { - pub async fn create(value: AnyStore) -> Self { + pub async fn create(value: Store) -> Self { let handle = Self::next(); let mut repo = FFI_STORES.write().await; - repo.insert(handle, Arc::new(value)); + repo.insert(handle, value); handle } - pub async fn load(&self) -> Result, Error> { + pub async fn load(&self) -> Result { FFI_STORES .read() .await @@ -49,7 +49,7 @@ impl StoreHandle { .ok_or_else(|| err_msg!("Invalid store handle")) } - pub async fn remove(&self) -> Result, Error> { + pub async fn remove(&self) -> Result { FFI_STORES .write() .await @@ -57,12 +57,13 @@ impl StoreHandle { .ok_or_else(|| err_msg!("Invalid store handle")) } - pub async fn replace(&self, store: Arc) { + pub async fn replace(&self, store: Store) { FFI_STORES.write().await.insert(*self, store); } } struct StoreResourceMap { + #[allow(clippy::type_complexity)] map: RwLock>)>>, } @@ -92,14 +93,15 @@ where } pub async fn borrow(&self, handle: K) -> Result, Error> { - self.map + Ok(self + .map .read() .await .get(&handle) .ok_or_else(|| err_msg!("Invalid resource handle"))? .1 - .try_lock_arc() - .ok_or_else(|| err_msg!(Busy, "Resource handle in use")) + .lock_arc() + .await) } pub async fn remove_all(&self, store: StoreHandle) -> Result<(), Error> { @@ -136,7 +138,7 @@ pub extern "C" fn askar_store_generate_raw_key( s if s.is_empty() => None, s => Some(s) }; - let key = generate_raw_store_key(seed)?; + let key = Store::new_raw_key(seed)?; unsafe { *out = rust_string_to_c(key.to_string()); } Ok(ErrorCode::Success) } @@ -165,7 +167,7 @@ pub extern "C" fn askar_store_provision( let cb = EnsureCallback::new(move |result| match result { Ok(sid) => { - info!("Provisioned store {}", sid); + debug!("Provisioned store {}", sid); cb(cb_id, ErrorCode::Success, sid) } Err(err) => cb(cb_id, set_last_error(Some(err)), StoreHandle::invalid()), @@ -173,10 +175,11 @@ pub extern "C" fn askar_store_provision( ); spawn_ok(async move { let result = async { - let store = spec_uri.provision_backend( + let store = Store::provision( + spec_uri.as_str(), key_method, pass_key, - profile.as_ref().map(String::as_str), + profile, recreate != 0 ).await?; Ok(StoreHandle::create(store).await) @@ -209,7 +212,7 @@ pub extern "C" fn askar_store_open( let cb = EnsureCallback::new(move |result| match result { Ok(sid) => { - info!("Opened store {}", sid); + debug!("Opened store {}", sid); cb(cb_id, ErrorCode::Success, sid) } Err(err) => cb(cb_id, set_last_error(Some(err)), StoreHandle::invalid()), @@ -217,10 +220,11 @@ pub extern "C" fn askar_store_open( ); spawn_ok(async move { let result = async { - let store = spec_uri.open_backend( + let store = Store::open ( + spec_uri.as_str(), key_method, pass_key, - profile.as_ref().map(String::as_str) + profile ).await?; Ok(StoreHandle::create(store).await) }.await; @@ -247,10 +251,7 @@ pub extern "C" fn askar_store_remove( } ); spawn_ok(async move { - let result = async { - let removed = spec_uri.remove_backend().await?; - Ok(removed) - }.await; + let result = Store::remove(spec_uri.as_str()).await; cb.resolve(result); }); Ok(ErrorCode::Success) @@ -304,7 +305,37 @@ pub extern "C" fn askar_store_get_profile_name( spawn_ok(async move { let result = async { let store = handle.load().await?; - Ok(store.get_profile_name().to_string()) + Ok(store.get_active_profile()) + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_store_list_profiles( + handle: StoreHandle, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err! { + trace!("List profiles"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let cb = EnsureCallback::new(move |result| + match result { + Ok(rows) => { + let res = StringListHandle::create(FfiStringList::from(rows)); + cb(cb_id, ErrorCode::Success, res) + }, + Err(err) => cb(cb_id, set_last_error(Some(err)), StringListHandle::invalid()), + } + ); + spawn_ok(async move { + let result = async { + let store = handle.load().await?; + let rows = store.list_profiles().await?; + Ok(rows) }.await; cb.resolve(result); }); @@ -332,7 +363,62 @@ pub extern "C" fn askar_store_remove_profile( spawn_ok(async move { let result = async { let store = handle.load().await?; - Ok(store.remove_profile(profile).await?) + store.remove_profile(profile).await + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_store_get_default_profile( + handle: StoreHandle, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err! { + trace!("Get default profile"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let cb = EnsureCallback::new(move |result: Result| + match result { + Ok(name) => cb(cb_id, ErrorCode::Success, + CString::new(name.as_str()).unwrap().into_raw() as *const c_char), + Err(err) => cb(cb_id, set_last_error(Some(err)), ptr::null()), + } + ); + spawn_ok(async move { + let result = async { + let store = handle.load().await?; + store.get_default_profile().await + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_store_set_default_profile( + handle: StoreHandle, + profile: FfiStr<'_>, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err! { + trace!("Set default profile"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let profile = profile.into_opt_string().ok_or_else(|| err_msg!("Profile name not provided"))?; + let cb = EnsureCallback::new(move |result| + match result { + Ok(_) => cb(cb_id, ErrorCode::Success), + Err(err) => cb(cb_id, set_last_error(Some(err))), + } + ); + spawn_ok(async move { + let result = async { + let store = handle.load().await?; + store.set_default_profile(profile).await }.await; cb.resolve(result); }); @@ -364,18 +450,48 @@ pub extern "C" fn askar_store_rekey( ); spawn_ok(async move { let result = async { - let store = handle.remove().await?; - match Arc::try_unwrap(store) { - Ok(mut store) => { - store.rekey(key_method, pass_key.as_ref()).await?; - handle.replace(Arc::new(store)).await; - Ok(()) - } - Err(arc_store) => { - handle.replace(arc_store).await; - Err(err_msg!("Cannot re-key store with multiple references")) - } - } + let mut store = handle.remove().await?; + let result = store.rekey(key_method, pass_key.as_ref()).await; + handle.replace(store).await; + result + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_store_copy( + handle: StoreHandle, + target_uri: FfiStr<'_>, + key_method: FfiStr<'_>, + pass_key: FfiStr<'_>, + recreate: i8, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err! { + trace!("Copy store"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let target_uri = target_uri.into_opt_string().ok_or_else(|| err_msg!("No target URI provided"))?; + let key_method = match key_method.as_opt_str() { + Some(method) => StoreKeyMethod::parse_uri(method)?, + None => StoreKeyMethod::default() + }; + let pass_key = PassKey::from(pass_key.as_opt_str()).into_owned(); + let cb = EnsureCallback::new(move |result| + match result { + Ok(handle) => cb(cb_id, ErrorCode::Success, handle), + Err(err) => cb(cb_id, set_last_error(Some(err)), StoreHandle::invalid()), + } + ); + spawn_ok(async move { + let result = async move { + let store = handle.load().await?; + let copied = store.copy_to(target_uri.as_str(), key_method, pass_key.as_ref(), recreate != 0).await?; + debug!("Copied store {}", handle); + Ok(StoreHandle::create(copied).await) }.await; cb.resolve(result); }); @@ -407,8 +523,8 @@ pub extern "C" fn askar_store_close( // been dropped yet (this will invalidate associated handles) FFI_SESSIONS.remove_all(handle).await?; FFI_SCANS.remove_all(handle).await?; - store.arc_close().await?; - info!("Closed store {}", handle); + store.close().await?; + debug!("Closed store {}", handle); Ok(()) }.await; if let Some(cb) = cb { @@ -437,12 +553,12 @@ pub extern "C" fn askar_scan_start( trace!("Scan store start"); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; let profile = profile.into_opt_string(); - let category = category.into_opt_string().ok_or_else(|| err_msg!("Category not provided"))?; + let category = category.into_opt_string(); let tag_filter = tag_filter.as_opt_str().map(TagFilter::from_str).transpose()?; let cb = EnsureCallback::new(move |result: Result| match result { Ok(scan_handle) => { - info!("Started scan {} on store {}", scan_handle, handle); + debug!("Started scan {} on store {}", scan_handle, handle); cb(cb_id, ErrorCode::Success, scan_handle) } Err(err) => cb(cb_id, set_last_error(Some(err)), ScanHandle::invalid()), @@ -499,9 +615,9 @@ pub extern "C" fn askar_scan_free(handle: ScanHandle) -> ErrorCode { // the Scan may have been removed due to the Store being closed if let Some(scan) = FFI_SCANS.remove(handle).await { scan.ok(); - info!("Closed scan {}", handle); + debug!("Closed scan {}", handle); } else { - info!("Scan not found for closing: {}", handle); + debug!("Scan not found for closing: {}", handle); } }); Ok(ErrorCode::Success) @@ -523,7 +639,7 @@ pub extern "C" fn askar_session_start( let cb = EnsureCallback::new(move |result: Result| match result { Ok(sess_handle) => { - info!("Started session {} on store {} (txn: {})", sess_handle, handle, as_transaction != 0); + debug!("Started session {} on store {} (txn: {})", sess_handle, handle, as_transaction != 0); cb(cb_id, ErrorCode::Success, sess_handle) } Err(err) => cb(cb_id, set_last_error(Some(err)), SessionHandle::invalid()), @@ -556,7 +672,7 @@ pub extern "C" fn askar_session_count( catch_err! { trace!("Count from store"); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let category = category.into_opt_string().ok_or_else(|| err_msg!("Category not provided"))?; + let category = category.into_opt_string(); let tag_filter = tag_filter.as_opt_str().map(TagFilter::from_str).transpose()?; let cb = EnsureCallback::new(move |result: Result| match result { @@ -567,8 +683,7 @@ pub extern "C" fn askar_session_count( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let count = session.count(&category, tag_filter).await; - count + session.count(category.as_deref(), tag_filter).await }.await; cb.resolve(result); }); @@ -603,8 +718,7 @@ pub extern "C" fn askar_session_fetch( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let found = session.fetch(&category, &name, for_update != 0).await; - found + session.fetch(&category, &name, for_update != 0).await }.await; cb.resolve(result); }); @@ -625,7 +739,7 @@ pub extern "C" fn askar_session_fetch_all( catch_err! { trace!("Count from store"); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let category = category.into_opt_string().ok_or_else(|| err_msg!("Category not provided"))?; + let category = category.into_opt_string(); let tag_filter = tag_filter.as_opt_str().map(TagFilter::from_str).transpose()?; let limit = if limit < 0 { None } else {Some(limit)}; let cb = EnsureCallback::new(move |result| @@ -640,8 +754,7 @@ pub extern "C" fn askar_session_fetch_all( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let found = session.fetch_all(&category, tag_filter, limit, for_update != 0).await; - found + session.fetch_all(category.as_deref(), tag_filter, limit, for_update != 0).await }.await; cb.resolve(result); }); @@ -660,7 +773,7 @@ pub extern "C" fn askar_session_remove_all( catch_err! { trace!("Count from store"); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let category = category.into_opt_string().ok_or_else(|| err_msg!("Category not provided"))?; + let category = category.into_opt_string(); let tag_filter = tag_filter.as_opt_str().map(TagFilter::from_str).transpose()?; let cb = EnsureCallback::new(move |result| match result { @@ -673,8 +786,7 @@ pub extern "C" fn askar_session_remove_all( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let removed = session.remove_all(&category, tag_filter).await; - removed + session.remove_all(category.as_deref(), tag_filter).await }.await; cb.resolve(result); }); @@ -729,8 +841,7 @@ pub extern "C" fn askar_session_update( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let result = session.update(operation, &category, &name, Some(value.as_slice()), tags.as_ref().map(Vec::as_slice), expiry_ms).await; - result + session.update(operation, &category, &name, Some(value.as_slice()), tags.as_deref(), expiry_ms).await }.await; cb.resolve(result); }); @@ -781,14 +892,13 @@ pub extern "C" fn askar_session_insert_key( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let result = session.insert_key( + session.insert_key( name.as_str(), &key, - metadata.as_ref().map(String::as_str), - tags.as_ref().map(Vec::as_slice), + metadata.as_deref(), + tags.as_deref(), expiry_ms, - ).await; - result + ).await }.await; cb.resolve(result); }); @@ -825,11 +935,10 @@ pub extern "C" fn askar_session_fetch_key( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let result = session.fetch_key( + session.fetch_key( name.as_str(), for_update != 0 - ).await; - result + ).await }.await; cb.resolve(result); }); @@ -869,14 +978,13 @@ pub extern "C" fn askar_session_fetch_all_keys( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let result = session.fetch_all_keys( - alg.as_ref().map(String::as_str), - thumbprint.as_ref().map(String::as_str), + session.fetch_all_keys( + alg.as_deref(), + thumbprint.as_deref(), tag_filter, limit, for_update != 0 - ).await; - result + ).await }.await; cb.resolve(result); }); @@ -925,14 +1033,13 @@ pub extern "C" fn askar_session_update_key( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let result = session.update_key( + session.update_key( &name, - metadata.as_ref().map(String::as_str), - tags.as_ref().map(Vec::as_slice), + metadata.as_deref(), + tags.as_deref(), expiry_ms, - ).await; - result + ).await }.await; cb.resolve(result); }); @@ -963,10 +1070,9 @@ pub extern "C" fn askar_session_remove_key( spawn_ok(async move { let result = async { let mut session = FFI_SESSIONS.borrow(handle).await?; - let result = session.remove_key( + session.remove_key( &name, - ).await; - result + ).await }.await; cb.resolve(result); }); @@ -1005,9 +1111,9 @@ pub extern "C" fn askar_session_close( } else { session.commit().await?; } - info!("Closed session {}", handle); + debug!("Closed session {}", handle); } else { - info!("Session not found for closing: {}", handle); + debug!("Session not found for closing: {}", handle); } Ok(()) }.await; diff --git a/src/ffi/tags.rs b/src/ffi/tags.rs new file mode 100644 index 00000000..82ce242d --- /dev/null +++ b/src/ffi/tags.rs @@ -0,0 +1,210 @@ +use std::{borrow::Cow, fmt}; + +use serde::{ + de::{Error as SerdeError, MapAccess, SeqAccess, Visitor}, + ser::SerializeMap, + Deserialize, Deserializer, Serialize, Serializer, +}; + +use crate::entry::EntryTag; + +/// A wrapper type used for managing (de)serialization of tags +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct EntryTagSet<'e>(Cow<'e, [EntryTag]>); + +impl EntryTagSet<'_> { + pub fn into_vec(self) -> Vec { + self.into() + } +} + +impl<'e> From<&'e [EntryTag]> for EntryTagSet<'e> { + fn from(tags: &'e [EntryTag]) -> Self { + Self(Cow::Borrowed(tags)) + } +} + +impl From> for EntryTagSet<'static> { + fn from(tags: Vec) -> Self { + Self(Cow::Owned(tags)) + } +} + +impl<'e> From> for Vec { + fn from(set: EntryTagSet<'e>) -> Self { + set.0.into_owned() + } +} + +impl<'de> Deserialize<'de> for EntryTagSet<'static> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct TagSetVisitor; + + impl<'d> Visitor<'d> for TagSetVisitor { + type Value = EntryTagSet<'static>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("an object containing zero or more entry tags") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'d>, + { + let mut v = Vec::with_capacity(access.size_hint().unwrap_or_default()); + + while let Some((key, values)) = access.next_entry::<&str, EntryTagValues>()? { + let (tag, enc) = match key.chars().next() { + Some('~') => (key[1..].to_owned(), false), + None => return Err(M::Error::custom("invalid tag name: empty string")), + _ => (key.to_owned(), true), + }; + match (values, enc) { + (EntryTagValues::Single(value), true) => { + v.push(EntryTag::Encrypted(tag, value)) + } + (EntryTagValues::Single(value), false) => { + v.push(EntryTag::Plaintext(tag, value)) + } + (EntryTagValues::Multiple(values), true) => { + for value in values { + v.push(EntryTag::Encrypted(tag.clone(), value)) + } + } + (EntryTagValues::Multiple(values), false) => { + for value in values { + v.push(EntryTag::Plaintext(tag.clone(), value)) + } + } + } + } + + Ok(EntryTagSet(Cow::Owned(v))) + } + } + + deserializer.deserialize_map(TagSetVisitor) + } +} + +enum EntryTagValues { + Single(String), + Multiple(Vec), +} + +impl<'de> Deserialize<'de> for EntryTagValues { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct TagValuesVisitor; + + impl<'d> Visitor<'d> for TagValuesVisitor { + type Value = EntryTagValues; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a string or list of strings") + } + + fn visit_str(self, value: &str) -> Result + where + E: SerdeError, + { + Ok(EntryTagValues::Single(value.to_owned())) + } + + fn visit_string(self, value: String) -> Result + where + E: SerdeError, + { + Ok(EntryTagValues::Single(value)) + } + + fn visit_seq(self, mut access: S) -> Result + where + S: SeqAccess<'d>, + { + let mut v = Vec::with_capacity(access.size_hint().unwrap_or_default()); + while let Some(value) = access.next_element()? { + v.push(value) + } + Ok(EntryTagValues::Multiple(v)) + } + } + + deserializer.deserialize_any(TagValuesVisitor) + } +} + +impl Serialize for EntryTagSet<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use std::collections::BTreeMap; + + #[derive(PartialOrd, Ord)] + struct TagName<'a>(&'a str, bool); + + impl<'a> PartialEq for TagName<'a> { + fn eq(&self, other: &Self) -> bool { + self.1 == other.1 && self.0 == other.0 + } + } + + impl<'a> Eq for TagName<'a> {} + + impl Serialize for TagName<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.1 { + serializer.serialize_str(self.0) + } else { + serializer.collect_str(&format_args!("~{}", self.0)) + } + } + } + + let mut tags = BTreeMap::new(); + for tag in self.0.iter() { + let (name, value) = match tag { + EntryTag::Encrypted(name, val) => (TagName(name.as_str(), true), val.as_str()), + EntryTag::Plaintext(name, val) => (TagName(name.as_str(), false), val.as_str()), + }; + tags.entry(name).or_insert_with(Vec::new).push(value); + } + + let mut map = serializer.serialize_map(Some(tags.len()))?; + for (tag_name, values) in tags.into_iter() { + if values.len() > 1 { + map.serialize_entry(&tag_name, &values)?; + } else { + map.serialize_entry(&tag_name, &values[0])?; + } + } + map.end() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_tags() { + let tags = EntryTagSet::from(vec![ + EntryTag::Encrypted("a".to_owned(), "aval".to_owned()), + EntryTag::Plaintext("b".to_owned(), "bval".to_owned()), + EntryTag::Plaintext("b".to_owned(), "bval-2".to_owned()), + ]); + let ser = serde_json::to_string(&tags).unwrap(); + assert_eq!(ser, r#"{"a":"aval","~b":["bval","bval-2"]}"#); + let tags2 = serde_json::from_str(&ser).unwrap(); + assert_eq!(tags, tags2); + } +} diff --git a/src/future.rs b/src/future.rs deleted file mode 100644 index b87544ac..00000000 --- a/src/future.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::{future::Future, pin::Pin, time::Duration}; - -use once_cell::sync::Lazy; -use tokio::runtime::Runtime; - -pub type BoxFuture<'a, T> = Pin + Send + 'a>>; - -static RUNTIME: Lazy = Lazy::new(|| Runtime::new().expect("Error creating tokio runtime")); - -/// Block the current thread on an async task, when not running inside the scheduler. -pub fn block_on(f: impl Future) -> R { - RUNTIME.block_on(f) -} - -/// Run a blocking task without interrupting the async scheduler. -#[inline] -pub async fn unblock(f: F) -> T -where - T: Send + 'static, - F: FnOnce() -> T + Send + 'static, -{ - RUNTIME - .spawn_blocking(f) - .await - .expect("Error running blocking task") -} - -/// Spawn an async task into the runtime. -#[inline] -pub fn spawn_ok(fut: impl Future + Send + 'static) { - RUNTIME.spawn(fut); -} - -/// Wait until a specific duration has passed (used in tests). -#[doc(hidden)] -pub async fn sleep(dur: Duration) { - let _rt = RUNTIME.enter(); - tokio::time::sleep(dur).await -} - -/// Cancel an async task if it does not complete after a timeout (used in tests). -#[doc(hidden)] -pub async fn timeout(dur: Duration, f: impl Future) -> Option { - let _rt = RUNTIME.enter(); - tokio::time::timeout(dur, f).await.ok() -} diff --git a/src/kms/enc.rs b/src/kms/enc.rs index 148ade25..2a706589 100644 --- a/src/kms/enc.rs +++ b/src/kms/enc.rs @@ -61,6 +61,7 @@ pub struct ToDecrypt<'d> { impl<'d> ToDecrypt<'d> { /// Accessor for the combined length + #[allow(clippy::len_without_is_empty)] #[inline] pub fn len(&self) -> usize { self.ciphertext.len() + self.tag.len() diff --git a/src/kms/entry.rs b/src/kms/entry.rs index 070d587d..f4135df8 100644 --- a/src/kms/entry.rs +++ b/src/kms/entry.rs @@ -1,8 +1,8 @@ use super::local_key::LocalKey; use crate::{ crypto::{alg::AnyKey, buffer::SecretBytes, jwk::FromJwk}, + entry::{Entry, EntryTag}, error::Error, - storage::{Entry, EntryTag}, }; /// Parameters defining a stored key @@ -29,9 +29,8 @@ impl KeyParams { } pub(crate) fn from_slice(params: &[u8]) -> Result { - let result = serde_cbor::from_slice(params) - .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); - result + serde_cbor::from_slice(params) + .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)) } } diff --git a/src/kms/envelope.rs b/src/kms/envelope.rs index 640866bb..f5407a53 100644 --- a/src/kms/envelope.rs +++ b/src/kms/envelope.rs @@ -75,6 +75,7 @@ pub fn crypto_box_seal_open( } /// Derive an ECDH-1PU shared key for authenticated encryption +#[allow(clippy::too_many_arguments)] pub fn derive_key_ecdh_1pu( key_alg: KeyAlg, ephem_key: &LocalKey, @@ -87,14 +88,7 @@ pub fn derive_key_ecdh_1pu( receive: bool, ) -> Result { let derive = Ecdh1PU::new( - &*ephem_key, - &*sender_key, - &*recip_key, - alg_id, - apu, - apv, - cc_tag, - receive, + ephem_key, sender_key, recip_key, alg_id, apu, apv, cc_tag, receive, ); LocalKey::from_key_derivation(key_alg, derive) } @@ -109,6 +103,6 @@ pub fn derive_key_ecdh_es( apv: &[u8], receive: bool, ) -> Result { - let derive = EcdhEs::new(&*ephem_key, &*recip_key, alg_id, apu, apv, receive); + let derive = EcdhEs::new(ephem_key, recip_key, alg_id, apu, apv, receive); LocalKey::from_key_derivation(key_alg, derive) } diff --git a/src/kms/local_key.rs b/src/kms/local_key.rs index 4d9fd1cc..d25f5ef0 100644 --- a/src/kms/local_key.rs +++ b/src/kms/local_key.rs @@ -146,12 +146,12 @@ impl LocalKey { /// Get the set of indexed JWK thumbprints for this key or keypair pub fn to_jwk_thumbprints(&self) -> Result, Error> { if self.inner.algorithm() == KeyAlg::Bls12_381(BlsCurves::G1G2) { - return Ok(vec![ + Ok(vec![ self.inner .to_jwk_thumbprint(Some(KeyAlg::Bls12_381(BlsCurves::G1)))?, self.inner .to_jwk_thumbprint(Some(KeyAlg::Bls12_381(BlsCurves::G2)))?, - ]); + ]) } else { Ok(vec![self.inner.to_jwk_thumbprint(None)?]) } @@ -189,8 +189,7 @@ impl LocalKey { if nonce_len == 0 { return Ok(Vec::new()); } - let mut buf = Vec::with_capacity(nonce_len); - buf.resize(nonce_len, 0u8); + let mut buf = vec![0; nonce_len]; fill_random(&mut buf); Ok(buf) } diff --git a/src/kms/mod.rs b/src/kms/mod.rs index 6640f2b0..fab09041 100644 --- a/src/kms/mod.rs +++ b/src/kms/mod.rs @@ -10,7 +10,7 @@ use zeroize::Zeroize; use crate::error::Error; mod enc; -pub use enc::{Encrypted, SecretBytes, ToDecrypt}; +pub use self::enc::{Encrypted, SecretBytes, ToDecrypt}; mod envelope; pub use self::envelope::{ diff --git a/src/lib.rs b/src/lib.rs index 0d63bbe0..d7cb8fe6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,12 @@ //! Secure storage designed for Hyperledger Aries agents #![cfg_attr(docsrs, feature(doc_cfg))] -#![deny(missing_docs, missing_debug_implementations, rust_2018_idioms)] +#![deny(missing_docs, rust_2018_idioms)] #[macro_use] mod error; pub use self::error::{Error, ErrorKind}; -#[cfg(test)] -#[macro_use] -extern crate hex_literal; - -#[macro_use] -mod macros; - #[cfg(any(test, feature = "log"))] #[macro_use] extern crate log; @@ -21,34 +14,37 @@ extern crate log; #[macro_use] extern crate serde; -pub mod backend; -pub use self::backend::{Backend, ManageBackend}; - -#[cfg(feature = "any")] -pub use self::backend::any; - -#[cfg(feature = "postgres")] -pub use self::backend::postgres; - -#[cfg(feature = "sqlite")] -pub use self::backend::sqlite; - +#[doc(hidden)] pub use askar_crypto as crypto; - #[doc(hidden)] -pub mod future; - -#[cfg(feature = "ffi")] -#[macro_use] -extern crate serde_json; +pub use askar_storage as storage; +#[doc(hidden)] +pub use askar_storage::future; #[cfg(feature = "ffi")] mod ffi; -pub mod kms; +#[cfg(feature = "uffi")] +mod uffi; -mod protect; -pub use protect::{generate_raw_store_key, PassKey, StoreKeyMethod}; +pub mod kms; -mod storage; -pub use storage::{Entry, EntryTag, Scan, Store, TagFilter}; +mod store; +pub use store::{entry, PassKey, Session, Store, StoreKeyMethod}; + +#[cfg(feature = "uffi")] +pub use storage::entry::{Entry, EntryOperation, EntryTag, Scan, TagFilter}; + +#[cfg(feature = "uffi")] +pub use uffi::{ + crypto::{AskarCrypto, AskarEcdhEs, AskarEcdh1PU}, + error::ErrorCode, + entry::{AskarEntry, AskarKeyEntry}, + key::{AskarLocalKey, AskarKeyAlg, SeedMethod, LocalKeyFactory, EncryptedBuffer}, + scan::AskarScan, + session::{AskarSession, AskarEntryOperation}, + store::{AskarStore, AskarStoreManager}, +}; + +#[cfg(feature = "uffi")] +uniffi::include_scaffolding!("askar"); diff --git a/src/storage/mod.rs b/src/storage/mod.rs deleted file mode 100644 index f7d00491..00000000 --- a/src/storage/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod entry; -pub(crate) use self::entry::{EncEntryTag, EntryTagSet}; -pub use self::entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}; - -mod options; -pub(crate) use self::options::{IntoOptions, Options}; - -mod store; -pub use self::store::{Session, Store}; - -pub(crate) mod wql; diff --git a/src/storage/wql/mod.rs b/src/storage/wql/mod.rs deleted file mode 100644 index a34d1a49..00000000 --- a/src/storage/wql/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub use indy_wql::{AbstractQuery, Query}; - -pub mod sql; -pub mod tags; diff --git a/src/storage/store.rs b/src/store.rs similarity index 73% rename from src/storage/store.rs rename to src/store.rs index b7331cb3..36834231 100644 --- a/src/storage/store.rs +++ b/src/store.rs @@ -1,37 +1,75 @@ -use std::sync::Arc; +use askar_storage::backend::copy_profile; -use super::entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}; use crate::{ - backend::{Backend, QueryBackend}, error::Error, kms::{KeyEntry, KeyParams, KmsCategory, LocalKey}, - protect::{PassKey, StoreKeyMethod}, + storage::{ + any::{AnyBackend, AnyBackendSession}, + backend::{Backend, BackendSession, ManageBackend}, + entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, + generate_raw_store_key, + }, }; -#[derive(Debug)] +pub use crate::storage::{entry, PassKey, StoreKeyMethod}; + +#[derive(Debug, Clone)] /// An instance of an opened store -pub struct Store(B); +pub struct Store(AnyBackend); -impl Store { - pub(crate) fn new(inner: B) -> Self { +impl Store { + pub(crate) fn new(inner: AnyBackend) -> Self { Self(inner) } - #[cfg(test)] - #[allow(unused)] - pub(crate) fn inner(&self) -> &B { - &self.0 + /// Provision a new store instance using a database URL + pub async fn provision( + db_url: &str, + key_method: StoreKeyMethod, + pass_key: PassKey<'_>, + profile: Option, + recreate: bool, + ) -> Result { + let backend = db_url + .provision_backend(key_method, pass_key, profile, recreate) + .await?; + Ok(Self::new(backend)) } - pub(crate) fn into_inner(self) -> B { - self.0 + /// Open a store instance from a database URL + pub async fn open( + db_url: &str, + key_method: Option, + pass_key: PassKey<'_>, + profile: Option, + ) -> Result { + let backend = db_url.open_backend(key_method, pass_key, profile).await?; + Ok(Self::new(backend)) + } + + /// Remove a store instance using a database URL + pub async fn remove(db_url: &str) -> Result { + Ok(db_url.remove_backend().await?) + } + + /// Generate a new raw store key + pub fn new_raw_key(seed: Option<&[u8]>) -> Result, Error> { + Ok(generate_raw_store_key(seed)?) } -} -impl Store { /// Get the default profile name used when starting a scan or a session - pub fn get_profile_name(&self) -> &str { - self.0.get_profile_name() + pub fn get_active_profile(&self) -> String { + self.0.get_active_profile() + } + + /// Get the default profile name used when opening the Store + pub async fn get_default_profile(&self) -> Result { + Ok(self.0.get_default_profile().await?) + } + + /// Set the default profile name used when opening the Store + pub async fn set_default_profile(&self, profile: String) -> Result<(), Error> { + Ok(self.0.set_default_profile(profile).await?) } /// Replace the wrapping key on a store @@ -40,7 +78,27 @@ impl Store { method: StoreKeyMethod, pass_key: PassKey<'_>, ) -> Result<(), Error> { - Ok(self.0.rekey_backend(method, pass_key).await?) + Ok(self.0.rekey(method, pass_key).await?) + } + + /// Copy to a new store instance using a database URL + pub async fn copy_to( + &self, + target_url: &str, + key_method: StoreKeyMethod, + pass_key: PassKey<'_>, + recreate: bool, + ) -> Result { + let default_profile = self.get_default_profile().await?; + let profile_ids = self.list_profiles().await?; + let target = target_url + .provision_backend(key_method, pass_key, Some(default_profile), recreate) + .await?; + for profile in profile_ids { + println!("copy profile: {}", profile); + copy_profile(&self.0, &target, &profile, &profile).await?; + } + Ok(Self::new(target)) } /// Create a new profile with the given profile name @@ -48,6 +106,11 @@ impl Store { Ok(self.0.create_profile(name).await?) } + /// Get the details of all store profiles + pub async fn list_profiles(&self) -> Result, Error> { + Ok(self.0.list_profiles().await?) + } + /// Remove an existing profile with the given profile name pub async fn remove_profile(&self, name: String) -> Result { Ok(self.0.remove_profile(name).await?) @@ -59,7 +122,7 @@ impl Store { pub async fn scan( &self, profile: Option, - category: String, + category: Option, tag_filter: Option, offset: Option, limit: Option, @@ -68,7 +131,7 @@ impl Store { .0 .scan( profile, - EntryKind::Item, + Some(EntryKind::Item), category, tag_filter, offset, @@ -78,13 +141,12 @@ impl Store { } /// Create a new session against the store - pub async fn session(&self, profile: Option) -> Result, Error> { - // FIXME - add 'immediate' flag + pub async fn session(&self, profile: Option) -> Result { Ok(Session::new(self.0.session(profile, false)?)) } /// Create a new transaction session against the store - pub async fn transaction(&self, profile: Option) -> Result, Error> { + pub async fn transaction(&self, profile: Option) -> Result { Ok(Session::new(self.0.session(profile, true)?)) } @@ -92,30 +154,33 @@ impl Store { pub async fn close(self) -> Result<(), Error> { Ok(self.0.close().await?) } +} - pub(crate) async fn arc_close(self: Arc) -> Result<(), Error> { - Ok(self.0.close().await?) +impl From for Store { + fn from(backend: AnyBackend) -> Self { + Self::new(backend) } } /// An active connection to the store backend #[derive(Debug)] -pub struct Session(Q); +pub struct Session(AnyBackendSession); -impl Session { - pub(crate) fn new(inner: Q) -> Self { +impl Session { + pub(crate) fn new(inner: AnyBackendSession) -> Self { Self(inner) } -} -impl Session { /// Count the number of entries for a given record category pub async fn count( &mut self, - category: &str, + category: Option<&str>, tag_filter: Option, ) -> Result { - Ok(self.0.count(EntryKind::Item, category, tag_filter).await?) + Ok(self + .0 + .count(Some(EntryKind::Item), category, tag_filter) + .await?) } /// Retrieve the current record at `(category, name)`. @@ -141,14 +206,20 @@ impl Session { /// requirements pub async fn fetch_all( &mut self, - category: &str, + category: Option<&str>, tag_filter: Option, limit: Option, for_update: bool, ) -> Result, Error> { Ok(self .0 - .fetch_all(EntryKind::Item, category, tag_filter, limit, for_update) + .fetch_all( + Some(EntryKind::Item), + category, + tag_filter, + limit, + for_update, + ) .await?) } @@ -217,12 +288,12 @@ impl Session { /// Remove all records in the store matching a given `category` and `tag_filter` pub async fn remove_all( &mut self, - category: &str, + category: Option<&str>, tag_filter: Option, ) -> Result { Ok(self .0 - .remove_all(EntryKind::Item, category, tag_filter) + .remove_all(Some(EntryKind::Item), category, tag_filter) .await?) } @@ -334,7 +405,7 @@ impl Session { for_update: bool, ) -> Result, Error> { let mut query_parts = Vec::with_capacity(3); - if let Some(query) = tag_filter.map(|f| f.query) { + if let Some(query) = tag_filter.map(|f| f.into_query()) { query_parts.push(TagFilter::from( query .map_names(|mut k| { @@ -358,8 +429,8 @@ impl Session { let rows = self .0 .fetch_all( - EntryKind::Kms, - KmsCategory::CryptoKey.as_str(), + Some(EntryKind::Kms), + Some(KmsCategory::CryptoKey.as_str()), tag_filter, limit, for_update, @@ -374,7 +445,8 @@ impl Session { /// Remove an existing key from the store pub async fn remove_key(&mut self, name: &str) -> Result<(), Error> { - self.0 + Ok(self + .0 .update( EntryKind::Kms, EntryOperation::Remove, @@ -384,7 +456,7 @@ impl Session { None, None, ) - .await + .await?) } /// Replace the metadata and tags on an existing key in the store @@ -433,12 +505,12 @@ impl Session { } /// Commit the pending transaction - pub async fn commit(self) -> Result<(), Error> { + pub async fn commit(mut self) -> Result<(), Error> { Ok(self.0.close(true).await?) } /// Roll back the pending transaction - pub async fn rollback(self) -> Result<(), Error> { + pub async fn rollback(mut self) -> Result<(), Error> { Ok(self.0.close(false).await?) } } diff --git a/src/uffi/crypto.rs b/src/uffi/crypto.rs new file mode 100644 index 00000000..821485a3 --- /dev/null +++ b/src/uffi/crypto.rs @@ -0,0 +1,340 @@ +use std::sync::Arc; +use crate::{ + kms::{ + crypto_box, crypto_box_open, crypto_box_random_nonce, crypto_box_seal, crypto_box_seal_open, + derive_key_ecdh_1pu, derive_key_ecdh_es, + }, + uffi::{ + error::ErrorCode, + key::{AskarLocalKey, AskarKeyAlg, EncryptedBuffer}, + }, +}; + +pub struct AskarCrypto {} + +impl AskarCrypto { + pub fn new() -> Self { + Self {} + } +} + +#[uniffi::export] +impl AskarCrypto { + pub fn random_nonce(&self) -> Result, ErrorCode> { + Ok(crypto_box_random_nonce()?.to_vec()) + } + + pub fn crypto_box( + &self, + receiver_key: Arc, + sender_key: Arc, + message: Vec, + nonce: Vec, + ) -> Result, ErrorCode> { + Ok(crypto_box(&receiver_key.key, &sender_key.key, &message, &nonce)?) + } + + pub fn box_open( + &self, + receiver_key: Arc, + sender_key: Arc, + message: Vec, + nonce: Vec, + ) -> Result, ErrorCode> { + Ok(crypto_box_open(&receiver_key.key, &sender_key.key, &message, &nonce)?.to_vec()) + } + + pub fn box_seal( + &self, + receiver_key: Arc, + message: Vec, + ) -> Result, ErrorCode> { + Ok(crypto_box_seal(&receiver_key.key, &message)?) + } + + pub fn box_seal_open( + &self, + receiver_key: Arc, + ciphertext: Vec, + ) -> Result, ErrorCode> { + Ok(crypto_box_seal_open(&receiver_key.key, &ciphertext)?.to_vec()) + } +} + +pub struct AskarEcdhEs { + alg_id: Vec, + apu: Vec, + apv: Vec, +} + +impl AskarEcdhEs { + pub fn new( + alg_id: String, + apu: String, + apv: String, + ) -> Self { + Self { + alg_id: alg_id.into_bytes(), + apu: apu.into_bytes(), + apv: apv.into_bytes(), + } + } +} + +#[uniffi::export] +impl AskarEcdhEs { + pub fn derive_key( + &self, + enc_alg: AskarKeyAlg, + ephemeral_key: Arc, + receiver_key: Arc, + receive: bool, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_es( + enc_alg.into(), + &ephemeral_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + receive, + )?; + Ok(Arc::new(AskarLocalKey { key })) + } + + pub fn encrypt_direct( + &self, + enc_alg: AskarKeyAlg, + ephemeral_key: Arc, + receiver_key: Arc, + message: Vec, + nonce: Option>, + aad: Option>, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_es( + enc_alg.into(), + &ephemeral_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + false, + )?; + let derived = AskarLocalKey { key }; + Ok(derived.aead_encrypt(message, nonce, aad)?) + } + + pub fn decrypt_direct( + &self, + enc_alg: AskarKeyAlg, + ephemeral_key: Arc, + receiver_key: Arc, + ciphertext: Vec, + tag: Option>, + nonce: Vec, + aad: Option>, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_es( + enc_alg.into(), + &ephemeral_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + true, + )?; + let derived = AskarLocalKey { key }; + Ok(derived.aead_decrypt(ciphertext, tag, nonce, aad)?) + } + + pub fn sender_wrap_key( + &self, + wrap_alg: AskarKeyAlg, + ephemeral_key: Arc, + receiver_key: Arc, + cek: Arc, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_es( + wrap_alg.into(), + &ephemeral_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + false, + )?; + let derived = AskarLocalKey { key }; + Ok(derived.wrap_key(cek, None)?) + } + + pub fn receiver_unwrap_key( + &self, + wrap_alg: AskarKeyAlg, + enc_alg: AskarKeyAlg, + ephemeral_key: Arc, + receiver_key: Arc, + ciphertext: Vec, + nonce: Option>, + tag: Option>, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_es( + wrap_alg.into(), + &ephemeral_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + true, + )?; + let derived = AskarLocalKey { key }; + Ok(derived.unwrap_key(enc_alg, ciphertext, tag, nonce)?) + } +} + +pub struct AskarEcdh1PU { + alg_id: Vec, + apu: Vec, + apv: Vec, +} + +impl AskarEcdh1PU { + pub fn new( + alg_id: String, + apu: String, + apv: String, + ) -> Self { + Self { + alg_id: alg_id.into_bytes(), + apu: apu.into_bytes(), + apv: apv.into_bytes(), + } + } +} + +#[uniffi::export] +impl AskarEcdh1PU { + pub fn derive_key( + &self, + enc_alg: AskarKeyAlg, + ephemeral_key: Arc, + sender_key: Arc, + receiver_key: Arc, + cc_tag: Vec, + receive: bool, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_1pu( + enc_alg.into(), + &ephemeral_key.key, + &sender_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + &cc_tag, + receive, + )?; + Ok(Arc::new(AskarLocalKey { key })) + } + + pub fn encrypt_direct( + &self, + enc_alg: AskarKeyAlg, + ephemeral_key: Arc, + sender_key: Arc, + receiver_key: Arc, + message: Vec, + nonce: Option>, + aad: Option>, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_1pu( + enc_alg.into(), + &ephemeral_key.key, + &sender_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + &[], + false, + )?; + let derived = AskarLocalKey { key }; + Ok(derived.aead_encrypt(message, nonce, aad)?) + } + + pub fn decrypt_direct( + &self, + enc_alg: AskarKeyAlg, + ephemeral_key: Arc, + sender_key: Arc, + receiver_key: Arc, + ciphertext: Vec, + tag: Option>, + nonce: Vec, + aad: Option>, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_1pu( + enc_alg.into(), + &ephemeral_key.key, + &sender_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + &[], + true, + )?; + let derived = AskarLocalKey { key }; + Ok(derived.aead_decrypt(ciphertext, tag, nonce, aad)?) + } + + pub fn sender_wrap_key( + &self, + wrap_alg: AskarKeyAlg, + ephemeral_key: Arc, + sender_key: Arc, + receiver_key: Arc, + cek: Arc, + cc_tag: Vec, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_1pu( + wrap_alg.into(), + &ephemeral_key.key, + &sender_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + &cc_tag, + false, + )?; + let derived = AskarLocalKey { key }; + Ok(derived.wrap_key(cek, None)?) + } + + pub fn receiver_unwrap_key( + &self, + wrap_alg: AskarKeyAlg, + enc_alg: AskarKeyAlg, + ephemeral_key: Arc, + sender_key: Arc, + receiver_key: Arc, + ciphertext: Vec, + cc_tag: Vec, + nonce: Option>, + tag: Option>, + ) -> Result, ErrorCode> { + let key = derive_key_ecdh_1pu( + enc_alg.into(), + &ephemeral_key.key, + &sender_key.key, + &receiver_key.key, + &self.alg_id, + &self.apu, + &self.apv, + &cc_tag, + true, + )?; + let derived = AskarLocalKey { key }; + Ok(derived.unwrap_key(wrap_alg, ciphertext, tag, nonce)?) + } +} diff --git a/src/uffi/entry.rs b/src/uffi/entry.rs new file mode 100644 index 00000000..abd70500 --- /dev/null +++ b/src/uffi/entry.rs @@ -0,0 +1,82 @@ +use std::collections::HashMap; +use std::sync::Arc; +use crate::{ + kms::KeyEntry, + storage::entry::Entry, + uffi::{error::ErrorCode, key::AskarLocalKey}, +}; + +pub struct AskarEntry { + entry: Entry, +} + +impl AskarEntry { + pub fn new(entry: Entry) -> Self { + Self { entry } + } +} + +#[uniffi::export] +impl AskarEntry { + pub fn category(&self) -> String { + self.entry.category.clone() + } + + pub fn name(&self) -> String { + self.entry.name.clone() + } + + pub fn tags(&self) -> HashMap { + let mut map = HashMap::new(); + for tag in &self.entry.tags { + map.insert(tag.name().to_string(), tag.value().to_string()); + } + map + } + + pub fn value(&self) -> Vec { + self.entry.value.to_vec() + } +} + +pub struct AskarKeyEntry { + entry: KeyEntry, +} + +impl AskarKeyEntry { + pub fn new(entry: KeyEntry) -> Self { + Self { entry } + } +} + +#[uniffi::export] +impl AskarKeyEntry { + pub fn algorithm(&self) -> Option { + self.entry.algorithm().map(String::from) + } + + pub fn metadata(&self) -> Option { + self.entry.metadata().map(String::from) + } + + pub fn name(&self) -> String { + self.entry.name().to_string() + } + + pub fn is_local(&self) -> bool { + self.entry.is_local() + } + + pub fn tags(&self) -> HashMap { + let mut map = HashMap::new(); + for tag in &self.entry.tags { + map.insert(tag.name().to_string(), tag.value().to_string()); + } + map + } + + pub fn load_local_key(&self) -> Result, ErrorCode> { + let key = self.entry.load_local_key()?; + Ok(Arc::new(AskarLocalKey { key })) + } +} diff --git a/src/uffi/error.rs b/src/uffi/error.rs new file mode 100644 index 00000000..75b5d8e4 --- /dev/null +++ b/src/uffi/error.rs @@ -0,0 +1,56 @@ +use std::fmt::{self, Display, Formatter}; + +use crate::error::{Error, ErrorKind}; +use crate::storage::{Error as StorageError, ErrorKind as StorageErrorKind}; + +#[derive(Debug, PartialEq, Clone, Serialize, uniffi::Error)] +pub enum ErrorCode { + Success { message: String }, + Backend { message: String }, + Busy { message: String }, + Duplicate { message: String }, + Encryption { message: String }, + Input { message: String }, + NotFound { message: String }, + Unexpected { message: String }, + Unsupported { message: String }, + Custom { message: String }, +} + +impl From for ErrorCode { + fn from(err: Error) -> ErrorCode { + match err.kind() { + ErrorKind::Backend => ErrorCode::Backend { message: err.to_string() }, + ErrorKind::Busy => ErrorCode::Busy { message: err.to_string() }, + ErrorKind::Duplicate => ErrorCode::Duplicate { message: err.to_string() }, + ErrorKind::Encryption => ErrorCode::Encryption { message: err.to_string() }, + ErrorKind::Input => ErrorCode::Input { message: err.to_string() }, + ErrorKind::NotFound => ErrorCode::NotFound { message: err.to_string() }, + ErrorKind::Unexpected => ErrorCode::Unexpected { message: err.to_string() }, + ErrorKind::Unsupported => ErrorCode::Unsupported { message: err.to_string() }, + ErrorKind::Custom => ErrorCode::Custom { message: err.to_string() }, + } + } +} + +impl From for ErrorCode { + fn from(err: StorageError) -> ErrorCode { + match err.kind() { + StorageErrorKind::Backend => ErrorCode::Backend { message: err.to_string() }, + StorageErrorKind::Busy => ErrorCode::Busy { message: err.to_string() }, + StorageErrorKind::Duplicate => ErrorCode::Duplicate { message: err.to_string() }, + StorageErrorKind::Encryption => ErrorCode::Encryption { message: err.to_string() }, + StorageErrorKind::Input => ErrorCode::Input { message: err.to_string() }, + StorageErrorKind::NotFound => ErrorCode::NotFound { message: err.to_string() }, + StorageErrorKind::Unexpected => ErrorCode::Unexpected { message: err.to_string() }, + StorageErrorKind::Unsupported => ErrorCode::Unsupported { message: err.to_string() }, + StorageErrorKind::Custom => ErrorCode::Custom { message: err.to_string() }, + } + } +} + +impl Display for ErrorCode { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/src/uffi/key.rs b/src/uffi/key.rs new file mode 100644 index 00000000..14ce1444 --- /dev/null +++ b/src/uffi/key.rs @@ -0,0 +1,250 @@ +use std::sync::Arc; +use crate::{ + crypto::alg::{KeyAlg, AesTypes, BlsCurves, Chacha20Types, EcCurves}, + kms::{LocalKey, Encrypted}, + uffi::error::ErrorCode, +}; + +#[derive(uniffi::Enum)] +pub enum AskarKeyAlg { + A128Gcm, + A256Gcm, + A128CbcHs256, + A256CbcHs512, + A128Kw, + A256Kw, + Bls12_381G1, + Bls12_381G2, + Bls12_381G1g2, + C20P, + XC20P, + Ed25519, + X25519, + K256, + P256, + P384, +} + +impl Into for AskarKeyAlg { + fn into(self) -> KeyAlg { + match self { + AskarKeyAlg::A128Gcm => KeyAlg::Aes(AesTypes::A128Gcm), + AskarKeyAlg::A256Gcm => KeyAlg::Aes(AesTypes::A256Gcm), + AskarKeyAlg::A128CbcHs256 => KeyAlg::Aes(AesTypes::A128CbcHs256), + AskarKeyAlg::A256CbcHs512 => KeyAlg::Aes(AesTypes::A256CbcHs512), + AskarKeyAlg::A128Kw => KeyAlg::Aes(AesTypes::A128Kw), + AskarKeyAlg::A256Kw => KeyAlg::Aes(AesTypes::A256Kw), + AskarKeyAlg::Bls12_381G1 => KeyAlg::Bls12_381(BlsCurves::G1), + AskarKeyAlg::Bls12_381G2 => KeyAlg::Bls12_381(BlsCurves::G2), + AskarKeyAlg::Bls12_381G1g2 => KeyAlg::Bls12_381(BlsCurves::G1G2), + AskarKeyAlg::C20P => KeyAlg::Chacha20(Chacha20Types::C20P), + AskarKeyAlg::XC20P => KeyAlg::Chacha20(Chacha20Types::XC20P), + AskarKeyAlg::Ed25519 => KeyAlg::Ed25519, + AskarKeyAlg::X25519 => KeyAlg::X25519, + AskarKeyAlg::K256 => KeyAlg::EcCurve(EcCurves::Secp256k1), + AskarKeyAlg::P256 => KeyAlg::EcCurve(EcCurves::Secp256r1), + AskarKeyAlg::P384 => KeyAlg::EcCurve(EcCurves::Secp384r1), + } + } +} + +impl From for AskarKeyAlg { + fn from(key_alg: KeyAlg) -> Self { + match key_alg { + KeyAlg::Aes(AesTypes::A128Gcm) => AskarKeyAlg::A128Gcm, + KeyAlg::Aes(AesTypes::A256Gcm) => AskarKeyAlg::A256Gcm, + KeyAlg::Aes(AesTypes::A128CbcHs256) => AskarKeyAlg::A128CbcHs256, + KeyAlg::Aes(AesTypes::A256CbcHs512) => AskarKeyAlg::A256CbcHs512, + KeyAlg::Aes(AesTypes::A128Kw) => AskarKeyAlg::A128Kw, + KeyAlg::Aes(AesTypes::A256Kw) => AskarKeyAlg::A256Kw, + KeyAlg::Bls12_381(BlsCurves::G1) => AskarKeyAlg::Bls12_381G1, + KeyAlg::Bls12_381(BlsCurves::G2) => AskarKeyAlg::Bls12_381G2, + KeyAlg::Bls12_381(BlsCurves::G1G2) => AskarKeyAlg::Bls12_381G1g2, + KeyAlg::Chacha20(Chacha20Types::C20P) => AskarKeyAlg::C20P, + KeyAlg::Chacha20(Chacha20Types::XC20P) => AskarKeyAlg::XC20P, + KeyAlg::Ed25519 => AskarKeyAlg::Ed25519, + KeyAlg::X25519 => AskarKeyAlg::X25519, + KeyAlg::EcCurve(EcCurves::Secp256k1) => AskarKeyAlg::K256, + KeyAlg::EcCurve(EcCurves::Secp256r1) => AskarKeyAlg::P256, + KeyAlg::EcCurve(EcCurves::Secp384r1) => AskarKeyAlg::P384, + } + } +} + +#[derive(uniffi::Enum)] +pub enum SeedMethod { + BlsKeyGen, +} + +impl Into<&str> for SeedMethod { + fn into(self) -> &'static str { + match self { + SeedMethod::BlsKeyGen => "bls_keygen", + } + } +} + +#[derive(uniffi::Record)] +pub struct AeadParams { + nonce_length: i32, + tag_length: i32, +} + +pub struct EncryptedBuffer { + enc: Encrypted, +} + +#[uniffi::export] +impl EncryptedBuffer { + pub fn ciphertext(&self) -> Vec { + self.enc.ciphertext().to_vec() + } + + pub fn nonce(&self) -> Vec { + self.enc.nonce().to_vec() + } + + pub fn tag(&self) -> Vec { + self.enc.tag().to_vec() + } + + pub fn ciphertext_tag(&self) -> Vec { + self.enc.buffer[0..(self.enc.nonce_pos)].to_vec() + } +} + +pub struct AskarLocalKey { + pub key: LocalKey, +} + +pub struct LocalKeyFactory { +} + +impl LocalKeyFactory { + pub fn new() -> Self { + Self {} + } +} + +#[uniffi::export] +impl LocalKeyFactory { + pub fn generate(&self, alg: AskarKeyAlg, ephemeral: bool) -> Result, ErrorCode> { + let key = LocalKey::generate(alg.into(), ephemeral)?; + Ok(Arc::new(AskarLocalKey { key })) + } + + pub fn from_seed(&self, alg: AskarKeyAlg, seed: Vec, method: Option) -> Result, ErrorCode> { + let key = LocalKey::from_seed(alg.into(), &seed, method.map(|m| m.into()))?; + Ok(Arc::new(AskarLocalKey { key })) + } + + pub fn from_jwk_slice(&self, jwk: Vec) -> Result, ErrorCode> { + let key = LocalKey::from_jwk_slice(&jwk)?; + Ok(Arc::new(AskarLocalKey { key })) + } + + pub fn from_jwk(&self, jwk: String) -> Result, ErrorCode> { + let key = LocalKey::from_jwk(&jwk)?; + Ok(Arc::new(AskarLocalKey { key })) + } + + pub fn from_public_bytes(&self, alg: AskarKeyAlg, bytes: Vec) -> Result, ErrorCode> { + let key = LocalKey::from_public_bytes(alg.into(), &bytes)?; + Ok(Arc::new(AskarLocalKey { key })) + } + + pub fn from_secret_bytes(&self, alg: AskarKeyAlg, bytes: Vec) -> Result, ErrorCode> { + let key = LocalKey::from_secret_bytes(alg.into(), &bytes)?; + Ok(Arc::new(AskarLocalKey { key })) + } +} + +#[uniffi::export] +impl AskarLocalKey { + pub fn to_public_bytes(&self) -> Result, ErrorCode> { + Ok(self.key.to_public_bytes()?.into_vec()) + } + + pub fn to_secret_bytes(&self) -> Result, ErrorCode> { + Ok(self.key.to_secret_bytes()?.into_vec()) + } + + pub fn to_key_exchange(&self, alg: AskarKeyAlg, pk: Arc) -> Result, ErrorCode> { + let key = self.key.to_key_exchange(alg.into(), &pk.key)?; + Ok(Arc::new(AskarLocalKey { key })) + } + + pub fn algorithm(&self) -> AskarKeyAlg { + self.key.algorithm().into() + } + + pub fn to_jwk_public(&self, alg: Option) -> Result { + Ok(self.key.to_jwk_public(alg.map(|a| a.into()))?) + } + + pub fn to_jwk_secret(&self) -> Result, ErrorCode> { + Ok(self.key.to_jwk_secret()?.into_vec()) + } + + pub fn to_jwk_thumbprint(&self, alg: Option) -> Result { + Ok(self.key.to_jwk_thumbprint(alg.map(|a| a.into()))?) + } + + pub fn to_jwk_thumbprints(&self) -> Result, ErrorCode> { + Ok(self.key.to_jwk_thumbprints()?) + } + + pub fn convert_key(&self, alg: AskarKeyAlg) -> Result, ErrorCode> { + let key = self.key.convert_key(alg.into())?; + Ok(Arc::new(AskarLocalKey { key })) + } + + pub fn aead_params(&self) -> Result { + let params = self.key.aead_params()?; + Ok(AeadParams { + nonce_length: params.nonce_length as i32, + tag_length: params.tag_length as i32, + }) + } + + pub fn aead_padding(&self, msg_len: i32) -> i32 { + self.key.aead_padding(msg_len as usize) as i32 + } + + pub fn aead_random_nonce(&self) -> Result, ErrorCode> { + Ok(self.key.aead_random_nonce()?) + } + + pub fn aead_encrypt(&self, message: Vec, nonce: Option>, aad: Option>) -> Result, ErrorCode> { + Ok(Arc::new(EncryptedBuffer { + enc: self.key.aead_encrypt(&message, &nonce.unwrap_or_default(), &aad.unwrap_or_default())?, + })) + } + + pub fn aead_decrypt(&self, ciphertext: Vec, tag: Option>, nonce: Vec, aad: Option>) -> Result, ErrorCode> { + Ok(self.key.aead_decrypt((ciphertext.as_slice(), tag.unwrap_or_default().as_slice()), &nonce, &aad.unwrap_or_default())?.to_vec()) + } + + pub fn sign_message(&self, message: Vec, sig_type: Option) -> Result, ErrorCode> { + Ok(self.key.sign_message(&message, sig_type.as_deref())?) + } + + pub fn verify_signature(&self, message: Vec, signature: Vec, sig_type: Option) -> Result { + Ok(self.key.verify_signature(&message, &signature, sig_type.as_deref())?) + } + + pub fn wrap_key(&self, key: Arc, nonce: Option>) -> Result, ErrorCode> { + Ok(Arc::new(EncryptedBuffer { + enc: self.key.wrap_key(&key.key, &nonce.unwrap_or_default())?, + })) + } + + pub fn unwrap_key(&self, alg: AskarKeyAlg, ciphertext: Vec, tag: Option>, nonce: Option>) -> Result, ErrorCode> { + let key = self.key.unwrap_key( + alg.into(), + (ciphertext.as_slice(), tag.unwrap_or_default().as_slice()), + &nonce.unwrap_or_default() + )?; + Ok(Arc::new(AskarLocalKey { key })) + } +} diff --git a/src/uffi/mod.rs b/src/uffi/mod.rs new file mode 100644 index 00000000..6cc4d2ce --- /dev/null +++ b/src/uffi/mod.rs @@ -0,0 +1,8 @@ +#![allow(missing_docs)] +pub mod entry; +pub mod error; +pub mod key; +pub mod scan; +pub mod session; +pub mod store; +pub mod crypto; diff --git a/src/uffi/scan.rs b/src/uffi/scan.rs new file mode 100644 index 00000000..beaf3cc2 --- /dev/null +++ b/src/uffi/scan.rs @@ -0,0 +1,47 @@ +use std::sync::Arc; +use tokio::sync::Mutex; +use crate::{ + uffi::{error::ErrorCode, entry::AskarEntry}, + storage::entry::{Entry, Scan}, +}; + +pub struct AskarScan { + scan: Mutex>, +} + +impl AskarScan { + pub fn new(scan: Scan<'static, Entry>) -> Self { + Self { scan: Mutex::new(scan) } + } +} + +#[uniffi::export(async_runtime = "tokio")] +impl AskarScan { + pub async fn next(&self) -> Result>>, ErrorCode> { + let mut scan = self.scan.lock().await; + let entries = scan.fetch_next().await?; + let entries: Vec> = entries + .unwrap_or(vec![]) + .into_iter() + .map(|entry| Arc::new(AskarEntry::new(entry))) + .collect(); + if entries.is_empty() { + Ok(None) + } else { + Ok(Some(entries)) + } + } + + pub async fn fetch_all(&self) -> Result>, ErrorCode> { + let mut scan = self.scan.lock().await; + let mut entries = vec![]; + while let Some(mut batch) = scan.fetch_next().await? { + entries.append(&mut batch); + } + let entries = entries + .into_iter() + .map(|entry| Arc::new(AskarEntry::new(entry))) + .collect(); + Ok(entries) + } +} diff --git a/src/uffi/session.rs b/src/uffi/session.rs new file mode 100644 index 00000000..055c4acd --- /dev/null +++ b/src/uffi/session.rs @@ -0,0 +1,251 @@ +use std::{ + str::FromStr, + sync::Arc, +}; +use tokio::sync::Mutex; +use crate::{ + ffi::tags::EntryTagSet, + storage::entry::{EntryOperation, TagFilter}, + store::Session, + uffi::{error::ErrorCode, entry::AskarEntry, entry::AskarKeyEntry, key::AskarLocalKey}, +}; + +#[derive(uniffi::Enum)] +pub enum AskarEntryOperation { + Insert, + Replace, + Remove, +} + +impl Into for AskarEntryOperation { + fn into(self) -> EntryOperation { + match self { + AskarEntryOperation::Insert => EntryOperation::Insert, + AskarEntryOperation::Replace => EntryOperation::Replace, + AskarEntryOperation::Remove => EntryOperation::Remove, + } + } +} + +macro_rules! SESSION_CLOSED_ERROR { + () => { + ErrorCode::Unexpected { message: String::from("Session is already closed") } + }; +} + +pub struct AskarSession { + session: Mutex>, +} + +impl AskarSession { + pub fn new(session: Session) -> Self { + Self { session: Mutex::new(Some(session)) } + } +} + +#[uniffi::export(async_runtime = "tokio")] +impl AskarSession { + pub async fn close(&self) -> Result<(), ErrorCode> { + self.session.lock().await.take(); + Ok(()) + } + + pub async fn count( + &self, + category: String, + tag_filter: Option, + ) -> Result { + Ok(self. + session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .count(Some(&category), tag_filter.as_deref().map(TagFilter::from_str).transpose()?) + .await?) + } + + pub async fn fetch( + &self, + category: String, + name: String, + for_update: bool, + ) -> Result>, ErrorCode> { + let entry = self + .session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .fetch(&category, &name, for_update) + .await?; + Ok(entry.map(|entry| Arc::new(AskarEntry::new(entry)))) + } + + pub async fn fetch_all( + &self, + category: String, + tag_filter: Option, + limit: Option, + for_update: bool, + ) -> Result>, ErrorCode> { + let entries = self + .session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .fetch_all(Some(&category), tag_filter.as_deref().map(TagFilter::from_str).transpose()?, limit, for_update) + .await?; + Ok(entries + .into_iter() + .map(|entry| Arc::new(AskarEntry::new(entry))) + .collect()) + } + + pub async fn update( + &self, + operation: AskarEntryOperation, + category: String, + name: String, + value: Vec, + tags: Option, + expiry_ms: Option, + ) -> Result<(), ErrorCode> { + let tags = if let Some(tags) = tags { + Some( + serde_json::from_str::>(&tags) + .map_err(err_map!("Error decoding tags"))? + .into_vec(), + ) + } else { + None + }; + self.session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .update(operation.into(), &category, &name, Some(&value), tags.as_deref(), expiry_ms) + .await?; + Ok(()) + } + + pub async fn remove_all( + &self, + category: String, + tag_filter: Option, + ) -> Result { + Ok(self + .session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .remove_all(Some(&category), tag_filter.as_deref().map(TagFilter::from_str).transpose()?) + .await?) + } + + pub async fn insert_key( + &self, + name: String, + key: Arc, + metadata: Option, + tags: Option, + expiry_ms: Option, + ) -> Result<(), ErrorCode> { + let tags = if let Some(tags) = tags { + Some( + serde_json::from_str::>(&tags) + .map_err(err_map!("Error decoding tags"))? + .into_vec(), + ) + } else { + None + }; + self.session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .insert_key(&name, &key.key, metadata.as_deref(), tags.as_deref(), expiry_ms) + .await?; + Ok(()) + } + + pub async fn fetch_key( + &self, + name: String, + for_update: bool, + ) -> Result>, ErrorCode> { + let key = self + .session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .fetch_key(&name, for_update) + .await?; + Ok(key.map(|entry| Arc::new(AskarKeyEntry::new(entry)))) + } + + pub async fn fetch_all_keys( + &self, + algorithm: Option, + thumbprint: Option, + tag_filter: Option, + limit: Option, + for_update: bool, + ) -> Result>, ErrorCode> { + let tag_filter = tag_filter.as_deref().map(TagFilter::from_str).transpose()?; + let keys = self + .session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .fetch_all_keys(algorithm.as_deref(), thumbprint.as_deref(), tag_filter, limit, for_update) + .await?; + Ok(keys + .into_iter() + .map(|entry| Arc::new(AskarKeyEntry::new(entry))) + .collect()) + } + + pub async fn remove_key(&self, name: String) -> Result<(), ErrorCode> { + self + .session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .remove_key(&name).await?; + Ok(()) + } + + pub async fn update_key( + &self, + name: String, + metadata: Option, + tags: Option, + expiry_ms: Option, + ) -> Result<(), ErrorCode> { + let tags = if let Some(tags) = tags { + Some( + serde_json::from_str::>(&tags) + .map_err(err_map!("Error decoding tags"))? + .into_vec(), + ) + } else { + None + }; + self.session + .lock() + .await + .as_mut() + .ok_or(SESSION_CLOSED_ERROR!())? + .update_key(&name, metadata.as_deref(), tags.as_deref(), expiry_ms) + .await?; + Ok(()) + } +} diff --git a/src/uffi/store.rs b/src/uffi/store.rs new file mode 100644 index 00000000..01237889 --- /dev/null +++ b/src/uffi/store.rs @@ -0,0 +1,195 @@ +use std::{ + sync::Arc, + str::FromStr, +}; +use tokio::sync::RwLock; +use crate::{ + store::{PassKey, Store, StoreKeyMethod}, + uffi::{error::ErrorCode, scan::AskarScan, session::AskarSession}, + storage::{generate_raw_store_key, entry::TagFilter}, +}; + +macro_rules! STORE_CLOSED_ERROR { + () => { + ErrorCode::Unexpected { message: String::from("Store is already closed") } + }; +} + +pub struct AskarStoreManager {} + +impl AskarStoreManager { + pub fn new() -> Self { + Self {} + } +} + +#[uniffi::export] +impl AskarStoreManager { + pub fn generate_raw_store_key(&self, seed: Option) -> Result { + let key = generate_raw_store_key(seed.as_ref().map(|s| s.as_bytes()))?; + Ok(key.to_string()) + } + + pub fn set_default_logger(&self) -> Result<(), ErrorCode> { + env_logger::try_init().map_err( + |e| ErrorCode::Unexpected { message: format!("{}", e) })?; + Ok(()) + } +} + +#[uniffi::export(async_runtime = "tokio")] +impl AskarStoreManager { + pub async fn provision( + &self, + spec_uri: String, + key_method: Option, + pass_key: Option, + profile: Option, + recreate: bool, + ) -> Result, ErrorCode> { + let key_method = match key_method { + Some(method) => StoreKeyMethod::parse_uri(&method)?, + None => StoreKeyMethod::default() + }; + let pass_key = PassKey::from(pass_key.as_deref()).into_owned(); + let store = Store::provision( + spec_uri.as_str(), + key_method, + pass_key, + profile, + recreate, + ).await?; + Ok(Arc::new(AskarStore { store: RwLock::new(Some(store)) })) + } + + pub async fn open( + &self, + spec_uri: String, + key_method: Option, + pass_key: Option, + profile: Option, + ) -> Result, ErrorCode> { + let key_method = match key_method { + Some(method) => Some(StoreKeyMethod::parse_uri(&method)?), + None => None + }; + let pass_key = PassKey::from(pass_key.as_deref()).into_owned(); + let store = Store::open( + spec_uri.as_str(), + key_method, + pass_key, + profile, + ).await?; + Ok(Arc::new(AskarStore { store: RwLock::new(Some(store)) })) + } + + pub async fn remove(&self, spec_uri: String) -> Result { + let removed = Store::remove(spec_uri.as_str()).await?; + Ok(removed) + } +} + +pub struct AskarStore { + store: RwLock>, // Option is used to allow for the store to be closed +} + +#[uniffi::export(async_runtime = "tokio")] +impl AskarStore { + pub async fn get_profile_name(&self) -> Result { + let name = self + .store + .read() + .await + .as_ref() + .ok_or(STORE_CLOSED_ERROR!())? + .get_active_profile().to_string(); + Ok(name) + } + + pub async fn create_profile(&self, profile: Option) -> Result { + let name = self + .store + .read() + .await + .as_ref() + .ok_or(STORE_CLOSED_ERROR!())? + .create_profile(profile) + .await?; + Ok(name) + } + + pub async fn remove_profile(&self, profile: String) -> Result { + let removed = self + .store + .read() + .await + .as_ref() + .ok_or(STORE_CLOSED_ERROR!())? + .remove_profile(profile) + .await?; + Ok(removed) + } + + pub async fn rekey(&self, key_method: Option, pass_key: Option) -> Result<(), ErrorCode> { + let key_method = match key_method { + Some(method) => StoreKeyMethod::parse_uri(&method)?, + None => StoreKeyMethod::default() + }; + let pass_key = PassKey::from(pass_key.as_deref()).into_owned(); + self + .store + .write() + .await + .as_mut() + .ok_or(STORE_CLOSED_ERROR!())? + .rekey(key_method, pass_key) + .await?; + Ok(()) + } + + pub async fn close(&self) -> Result<(), ErrorCode> { + let store = self.store.write().await.take(); + store + .ok_or(STORE_CLOSED_ERROR!())? + .close().await?; + Ok(()) + } + + pub async fn scan( + &self, + profile: Option, + categogy: String, + tag_filter: Option, + offset: Option, + limit: Option, + ) -> Result, ErrorCode> { + let tag_filter = tag_filter.as_deref().map(TagFilter::from_str).transpose()?; + let scan = self + .store + .read() + .await + .as_ref() + .ok_or(STORE_CLOSED_ERROR!())? + .scan( + profile, + Some(categogy), + tag_filter, + offset, + limit, + ) + .await?; + Ok(Arc::new(AskarScan::new(scan))) + } + + pub async fn session(&self, profile: Option) -> Result, ErrorCode> { + let session = self + .store + .read() + .await + .as_ref() + .ok_or(STORE_CLOSED_ERROR!())? + .session(profile) + .await?; + Ok(Arc::new(AskarSession::new(session))) + } +} diff --git a/tests/local_key.rs b/tests/local_key.rs index 47d0da53..92280982 100644 --- a/tests/local_key.rs +++ b/tests/local_key.rs @@ -1,10 +1,13 @@ +#![allow(clippy::bool_assert_comparison)] + use aries_askar::kms::{KeyAlg, LocalKey}; -const ERR_CREATE_KEYPAIR: &'static str = "Error creating keypair"; -const ERR_SIGN: &'static str = "Error signing message"; -const ERR_VERIFY: &'static str = "Error verifying signature"; +const ERR_CREATE_KEYPAIR: &str = "Error creating keypair"; +const ERR_SIGN: &str = "Error signing message"; +const ERR_VERIFY: &str = "Error verifying signature"; -pub async fn localkey_sign_verify() { +#[test] +pub fn localkey_sign_verify() { let keypair = LocalKey::generate(KeyAlg::Ed25519, true).expect(ERR_CREATE_KEYPAIR); let message = b"message".to_vec(); @@ -36,8 +39,8 @@ pub async fn localkey_sign_verify() { assert_eq!( keypair .verify_signature(&message, b"bad sig", None) - .is_err(), - true + .expect(ERR_VERIFY), + false ); assert_eq!( diff --git a/tests/store_key.rs b/tests/store_key.rs new file mode 100644 index 00000000..90ca2267 --- /dev/null +++ b/tests/store_key.rs @@ -0,0 +1,51 @@ +use aries_askar::{ + future::block_on, + kms::{KeyAlg, LocalKey}, + Store, StoreKeyMethod, +}; + +const ERR_RAW_KEY: &str = "Error creating raw store key"; +const ERR_SESSION: &str = "Error creating store session"; +const ERR_OPEN: &str = "Error opening test store instance"; +const ERR_REQ_ROW: &str = "Row required"; +const ERR_CLOSE: &str = "Error closing test store instance"; + +#[test] +fn keypair_create_fetch() { + block_on(async { + let pass_key = Store::new_raw_key(None).expect(ERR_RAW_KEY); + let db = Store::provision( + "sqlite://:memory:", + StoreKeyMethod::RawKey, + pass_key, + None, + true, + ) + .await + .expect(ERR_OPEN); + + let keypair = LocalKey::generate(KeyAlg::Ed25519, false).expect("Error creating keypair"); + + let mut conn = db.session(None).await.expect(ERR_SESSION); + + let key_name = "testkey"; + let metadata = "meta"; + conn.insert_key(key_name, &keypair, Some(metadata), None, None) + .await + .expect("Error inserting key"); + + let found = conn + .fetch_key(key_name, false) + .await + .expect("Error fetching key") + .expect(ERR_REQ_ROW); + assert_eq!(found.algorithm(), Some(KeyAlg::Ed25519.as_str())); + assert_eq!(found.name(), key_name); + assert_eq!(found.metadata(), Some(metadata)); + assert!(found.is_local()); + found.load_local_key().expect("Error loading key"); + + drop(conn); + db.close().await.expect(ERR_CLOSE); + }) +} diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs deleted file mode 100644 index f6da69d2..00000000 --- a/tests/utils/mod.rs +++ /dev/null @@ -1,736 +0,0 @@ -use std::{fmt::Debug, future::Future, ops::Deref, pin::Pin, sync::Arc}; - -use aries_askar::{ - kms::{KeyAlg, LocalKey}, - Backend, Entry, EntryTag, Error, ErrorKind, Store, TagFilter, -}; - -use tokio::task::spawn; - -const ERR_PROFILE: &'static str = "Error creating profile"; -const ERR_SESSION: &'static str = "Error starting session"; -const ERR_TRANSACTION: &'static str = "Error starting transaction"; -const ERR_COMMIT: &'static str = "Error committing transaction"; -const ERR_COUNT: &'static str = "Error performing count"; -const ERR_FETCH: &'static str = "Error fetching test row"; -const ERR_FETCH_ALL: &'static str = "Error fetching all test rows"; -const ERR_REQ_ROW: &'static str = "Expected row"; -const ERR_REQ_ERR: &'static str = "Expected error"; -const ERR_INSERT: &'static str = "Error inserting test row"; -const ERR_REPLACE: &'static str = "Error replacing test row"; -const ERR_REMOVE_ALL: &'static str = "Error removing test rows"; -const ERR_SCAN: &'static str = "Error starting scan"; -const ERR_SCAN_NEXT: &'static str = "Error fetching scan rows"; -const ERR_CREATE_KEYPAIR: &'static str = "Error creating keypair"; -const ERR_INSERT_KEY: &'static str = "Error inserting key"; -const ERR_FETCH_KEY: &'static str = "Error fetching key"; -const ERR_LOAD_KEY: &'static str = "Error loading key"; - -pub trait TestStore: Clone + Deref> + Send + Sync { - type DB: Backend + Debug + 'static; - - fn close(self) -> Pin>>>; -} - -impl TestStore for Arc> { - type DB = B; - - fn close(self) -> Pin>>> { - let db = Arc::try_unwrap(self).unwrap(); - Box::pin(db.close()) - } -} - -pub async fn db_create_remove_profile(db: impl TestStore) { - let profile = db.create_profile(None).await.expect(ERR_PROFILE); - assert_eq!( - db.remove_profile(profile) - .await - .expect("Error removing profile"), - true - ); - assert_eq!( - db.remove_profile("not a profile".to_string()) - .await - .expect("Error removing profile"), - false - ); -} - -pub async fn db_fetch_fail(db: impl TestStore) { - let mut conn = db.session(None).await.expect(ERR_SESSION); - let result = conn.fetch("cat", "name", false).await.expect(ERR_FETCH); - assert_eq!(result.is_none(), true); -} - -pub async fn db_insert_fetch(db: impl TestStore) { - let test_row = Entry::new( - "category", - "name", - "value", - vec![ - EntryTag::Encrypted("t1".to_string(), "v1".to_string()), - EntryTag::Plaintext("t2".to_string(), "v2".to_string()), - ], - ); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - let row = conn - .fetch(&test_row.category, &test_row.name, false) - .await - .expect(ERR_FETCH) - .expect(ERR_REQ_ROW); - assert_eq!(row, test_row); - - let rows = conn - .fetch_all(&test_row.category, None, None, false) - .await - .expect(ERR_FETCH_ALL); - assert_eq!(rows.len(), 1); - assert_eq!(rows[0], test_row); -} - -pub async fn db_insert_duplicate(db: impl TestStore) { - let test_row = Entry::new("category", "name", "value", Vec::new()); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - let err = conn - .insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect_err(ERR_REQ_ERR); - assert_eq!(err.kind(), ErrorKind::Duplicate); -} - -pub async fn db_insert_remove(db: impl TestStore) { - let test_row = Entry::new("category", "name", "value", Vec::new()); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - conn.remove(&test_row.category, &test_row.name) - .await - .expect(ERR_REQ_ROW); -} - -pub async fn db_remove_missing(db: impl TestStore) { - let mut conn = db.session(None).await.expect(ERR_SESSION); - - let err = conn.remove("cat", "name").await.expect_err(ERR_REQ_ERR); - assert_eq!(err.kind(), ErrorKind::NotFound); -} - -pub async fn db_replace_fetch(db: impl TestStore) { - let test_row = Entry::new("category", "name", "value", Vec::new()); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - let mut replace_row = test_row.clone(); - replace_row.value = "new value".into(); - conn.replace( - &replace_row.category, - &replace_row.name, - &replace_row.value, - Some(replace_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_REPLACE); - - let row = conn - .fetch(&replace_row.category, &replace_row.name, false) - .await - .expect(ERR_FETCH) - .expect(ERR_REQ_ROW); - assert_eq!(row, replace_row); -} - -pub async fn db_replace_missing(db: impl TestStore) { - let test_row = Entry::new("category", "name", "value", Vec::new()); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - let err = conn - .replace( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect_err(ERR_REQ_ERR); - assert_eq!(err.kind(), ErrorKind::NotFound); -} - -pub async fn db_count(db: impl TestStore) { - let category = "category".to_string(); - let test_rows = vec![Entry::new(&category, "name", "value", Vec::new())]; - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - for upd in test_rows.iter() { - conn.insert( - &upd.category, - &upd.name, - &upd.value, - Some(upd.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - } - - let tag_filter = None; - let count = conn.count(&category, tag_filter).await.expect(ERR_COUNT); - assert_eq!(count, 1); - - let tag_filter = Some(TagFilter::is_eq("sometag", "someval")); - let count = conn.count(&category, tag_filter).await.expect(ERR_COUNT); - assert_eq!(count, 0); -} - -pub async fn db_count_exist(db: impl TestStore) { - let test_row = Entry::new( - "category", - "name", - "value", - vec![ - EntryTag::Encrypted("enc".to_string(), "v1".to_string()), - EntryTag::Plaintext("plain".to_string(), "v2".to_string()), - ], - ); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::exist(vec!["enc".to_string()])) - ) - .await - .expect(ERR_COUNT), - 1 - ); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::exist(vec!["~plain".to_string()])) - ) - .await - .expect(ERR_COUNT), - 1 - ); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::exist(vec!["~enc".to_string()])) - ) - .await - .expect(ERR_COUNT), - 0 - ); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::exist(vec!["plain".to_string()])) - ) - .await - .expect(ERR_COUNT), - 0 - ); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::exist(vec!["other".to_string()])) - ) - .await - .expect(ERR_COUNT), - 0 - ); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::exist(vec![ - "enc".to_string(), - "other".to_string() - ])) - ) - .await - .expect(ERR_COUNT), - 0 - ); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::all_of(vec![ - TagFilter::exist(vec!["enc".to_string()]), - TagFilter::exist(vec!["~plain".to_string()]) - ])) - ) - .await - .expect(ERR_COUNT), - 1 - ); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::any_of(vec![ - TagFilter::exist(vec!["~enc".to_string()]), - TagFilter::exist(vec!["~plain".to_string()]) - ])) - ) - .await - .expect(ERR_COUNT), - 1 - ); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::all_of(vec![ - TagFilter::exist(vec!["~enc".to_string()]), - TagFilter::exist(vec!["~plain".to_string()]) - ])) - ) - .await - .expect(ERR_COUNT), - 0 - ); - - assert_eq!( - conn.count( - &test_row.category, - Some(TagFilter::not(TagFilter::exist(vec![ - "enc".to_string(), - "other".to_string() - ]),)) - ) - .await - .expect(ERR_COUNT), - 0 - ); -} - -pub async fn db_scan(db: impl TestStore) { - let category = "category".to_string(); - let test_rows = vec![Entry::new( - &category, - "name", - "value", - vec![ - EntryTag::Encrypted("t1".to_string(), "v1".to_string()), - EntryTag::Plaintext("t2".to_string(), "v2".to_string()), - ], - )]; - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - for upd in test_rows.iter() { - conn.insert( - &upd.category, - &upd.name, - &upd.value, - Some(upd.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - } - drop(conn); - - let tag_filter = None; - let offset = None; - let limit = None; - let mut scan = db - .scan(None, category.clone(), tag_filter, offset, limit) - .await - .expect(ERR_SCAN); - let rows = scan.fetch_next().await.expect(ERR_SCAN_NEXT); - assert_eq!(rows, Some(test_rows)); - let rows = scan.fetch_next().await.expect(ERR_SCAN_NEXT); - assert_eq!(rows, None); - - let tag_filter = Some(TagFilter::is_eq("sometag", "someval")); - let mut scan = db - .scan(None, category.clone(), tag_filter, offset, limit) - .await - .expect(ERR_SCAN); - let rows = scan.fetch_next().await.expect(ERR_SCAN_NEXT); - assert_eq!(rows, None); -} - -pub async fn db_remove_all(db: impl TestStore) { - let test_rows = vec![ - Entry::new( - "category", - "item1", - "value", - vec![ - EntryTag::Encrypted("t1".to_string(), "del".to_string()), - EntryTag::Plaintext("t2".to_string(), "del".to_string()), - ], - ), - Entry::new( - "category", - "item2", - "value", - vec![ - EntryTag::Encrypted("t1".to_string(), "del".to_string()), - EntryTag::Plaintext("t2".to_string(), "del".to_string()), - ], - ), - Entry::new( - "category", - "item3", - "value", - vec![ - EntryTag::Encrypted("t1".to_string(), "keep".to_string()), - EntryTag::Plaintext("t2".to_string(), "keep".to_string()), - ], - ), - ]; - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - for test_row in test_rows.iter() { - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - } - - // could detect that a second transaction would block here? - // depends on the backend. just checking that no SQL errors occur for now. - let removed = conn - .remove_all( - "category", - Some(TagFilter::all_of(vec![ - TagFilter::is_eq("t1", "del"), - TagFilter::is_eq("~t2", "del"), - ])), - ) - .await - .expect(ERR_REMOVE_ALL); - assert_eq!(removed, 2); -} - -pub async fn db_keypair_insert_fetch(db: impl TestStore) { - let keypair = LocalKey::generate(KeyAlg::Ed25519, false).expect(ERR_CREATE_KEYPAIR); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - let key_name = "testkey"; - let metadata = "meta"; - conn.insert_key(&key_name, &keypair, Some(metadata), None, None) - .await - .expect(ERR_INSERT_KEY); - - let found = conn - .fetch_key(&key_name, false) - .await - .expect(ERR_FETCH_KEY) - .expect(ERR_REQ_ROW); - assert_eq!(found.algorithm(), Some(KeyAlg::Ed25519.as_str())); - assert_eq!(found.name(), key_name); - assert_eq!(found.metadata(), Some(metadata)); - assert_eq!(found.is_local(), true); - found.load_local_key().expect(ERR_LOAD_KEY); -} - -pub async fn db_txn_rollback(db: impl TestStore) { - let test_row = Entry::new("category", "name", "value", Vec::new()); - - let mut conn = db.transaction(None).await.expect(ERR_TRANSACTION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - conn.rollback() - .await - .expect("Error rolling back transaction"); - - let mut conn = db.session(None).await.expect("Error starting new session"); - - let row = conn - .fetch(&test_row.category, &test_row.name, false) - .await - .expect("Error fetching test row"); - assert_eq!(row, None); -} - -pub async fn db_txn_drop(db: impl TestStore) { - let test_row = Entry::new("category", "name", "value", Vec::new()); - - let mut conn = db - .transaction(None) - .await - .expect("Error starting new transaction"); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - drop(conn); - - let mut conn = db.session(None).await.expect("Error starting new session"); - - let row = conn - .fetch(&test_row.category, &test_row.name, false) - .await - .expect("Error fetching test row"); - assert_eq!(row, None); -} - -// test that session does NOT have transaction rollback behaviour -pub async fn db_session_drop(db: impl TestStore) { - let test_row = Entry::new("category", "name", "value", Vec::new()); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - drop(conn); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - let row = conn - .fetch(&test_row.category, &test_row.name, false) - .await - .expect(ERR_FETCH); - assert_eq!(row, Some(test_row)); -} - -pub async fn db_txn_commit(db: impl TestStore) { - let test_row = Entry::new("category", "name", "value", Vec::new()); - - let mut conn = db.transaction(None).await.expect(ERR_TRANSACTION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - conn.commit().await.expect(ERR_COMMIT); - - let mut conn = db.session(None).await.expect(ERR_SESSION); - - let row = conn - .fetch(&test_row.category, &test_row.name, false) - .await - .expect(ERR_FETCH); - assert_eq!(row, Some(test_row)); -} - -pub async fn db_txn_fetch_for_update(db: impl TestStore) { - let test_row = Entry::new("category", "name", "value", Vec::new()); - - let mut conn = db.transaction(None).await.expect(ERR_TRANSACTION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - // could detect that a second transaction would block here? - // depends on the backend. just checking that no SQL errors occur for now. - let row = conn - .fetch(&test_row.category, &test_row.name, true) - .await - .expect(ERR_FETCH) - .expect(ERR_REQ_ROW); - assert_eq!(row, test_row); - - let rows = conn - .fetch_all(&test_row.category, None, Some(2), true) - .await - .expect(ERR_FETCH_ALL); - assert_eq!(rows.len(), 1); - assert_eq!(rows[0], test_row); - - conn.commit().await.expect(ERR_COMMIT); -} - -pub async fn db_txn_contention(db: impl TestStore + 'static) { - let test_row = Entry::new( - "category", - "count", - "0", - vec![ - EntryTag::Encrypted("t1".to_string(), "v1".to_string()), - EntryTag::Plaintext("t2".to_string(), "v2".to_string()), - ], - ); - - let mut conn = db.transaction(None).await.expect(ERR_TRANSACTION); - - conn.insert( - &test_row.category, - &test_row.name, - &test_row.value, - Some(test_row.tags.as_slice()), - None, - ) - .await - .expect(ERR_INSERT); - - conn.commit().await.expect(ERR_COMMIT); - - const TASKS: usize = 10; - const INC: usize = 1000; - - async fn inc(db: impl TestStore, category: String, name: String) -> Result<(), &'static str> { - // try to avoid panics in this section, as they will be raised on a tokio worker thread - for _ in 0..INC { - let mut conn = db.transaction(None).await.expect(ERR_TRANSACTION); - let row = conn - .fetch(&category, &name, true) - .await - .map_err(|e| { - log::error!("{:?}", e); - ERR_FETCH - })? - .ok_or(ERR_REQ_ROW)?; - let val: usize = str::parse(row.value.as_opt_str().ok_or("Non-string counter value")?) - .map_err(|_| "Error parsing counter value")?; - conn.replace( - &category, - &name, - &format!("{}", val + 1).as_bytes(), - Some(row.tags.as_slice()), - None, - ) - .await - .map_err(|e| { - log::error!("{:?}", e); - ERR_REPLACE - })?; - conn.commit().await.map_err(|_| ERR_COMMIT)?; - } - Ok(()) - } - - let mut tasks = vec![]; - for _ in 0..TASKS { - tasks.push(spawn(inc( - db.clone(), - test_row.category.clone(), - test_row.name.clone(), - ))); - } - - // JoinSet is not stable yet, just await all the tasks - for task in tasks { - if let Err(s) = task.await.unwrap() { - panic!("Error in concurrent update task: {}", s); - } - } - - // check the total - let mut conn = db.session(None).await.expect(ERR_SESSION); - let row = conn - .fetch(&test_row.category, &test_row.name, false) - .await - .expect(ERR_FETCH) - .expect(ERR_REQ_ROW); - assert_eq!(row.value, format!("{}", TASKS * INC).as_bytes()); -} diff --git a/uniffi-kotlin-multiplatform-bindings/.gitignore b/uniffi-kotlin-multiplatform-bindings/.gitignore new file mode 100644 index 00000000..9b6f6d28 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/.gitignore @@ -0,0 +1,4 @@ +target +Cargo.lock +.idea +.DS_Store diff --git a/uniffi-kotlin-multiplatform-bindings/Cargo.toml b/uniffi-kotlin-multiplatform-bindings/Cargo.toml new file mode 100644 index 00000000..5584d89e --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "uniffi-kotlin-multiplatform" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.66" +askama = { version = "0.11.1", default-features = false, features = ["config"] } +camino = "1.1.1" +heck = "0.4" +include_dir = "0.7.3" +paste = "1.0" +serde = "1" +toml = "0.5" +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } diff --git a/uniffi-kotlin-multiplatform-bindings/LICENSE b/uniffi-kotlin-multiplatform-bindings/LICENSE new file mode 100644 index 00000000..55ad4cef --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Trixnity + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/uniffi-kotlin-multiplatform-bindings/README.md b/uniffi-kotlin-multiplatform-bindings/README.md new file mode 100644 index 00000000..a35b8e05 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/README.md @@ -0,0 +1,7 @@ +# uniffi-kotlin-multiplatform-bindings + +This project contains Kotlin Multiplatform bindings generation for [uniffi](https://github.com/mozilla/uniffi-rs). + +Currently only the Kotlin targets JVM and Native are supported. JS support would be awesome, but needs WASM support within uniffi. + +You can find examples on how to use the bindings in the [tests](./tests) directory. diff --git a/uniffi-kotlin-multiplatform-bindings/askama.toml b/uniffi-kotlin-multiplatform-bindings/askama.toml new file mode 100644 index 00000000..2ea10961 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/askama.toml @@ -0,0 +1,8 @@ +[general] +dirs = ["src/templates"] + +[[syntax]] +name = "kt" + +[[syntax]] +name = "c" \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/.gitignore b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/.gitignore new file mode 100644 index 00000000..3060674f --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/.gitignore @@ -0,0 +1,3 @@ +.gradle +build +.idea \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/build.gradle.kts b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/build.gradle.kts new file mode 100644 index 00000000..a9140a71 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/build.gradle.kts @@ -0,0 +1,83 @@ +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5") + } +} + +plugins { + kotlin("multiplatform") version "1.7.21" +} + +apply(plugin = "kotlinx-atomicfu") + +group = "me.benkuly" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +kotlin { + jvm { + compilations.all { + kotlinOptions.jvmTarget = "1.8" + } + withJava() + testRuns["test"].executionTask.configure { + useJUnitPlatform() + } + } +// js(BOTH) { +// browser { +// commonWebpackConfig { +// cssSupport.enabled = true +// } +// } +// } + val hostOs = System.getProperty("os.name") + val isMingwX64 = hostOs.startsWith("Windows") + val nativeTarget = when { + hostOs == "Mac OS X" -> macosX64("native") + hostOs == "Linux" -> linuxX64("native") + isMingwX64 -> mingwX64("native") + else -> throw GradleException("Host OS is not supported in Kotlin/Native.") + } + + nativeTarget.apply { + compilations.getByName("main") { + cinterops { + val uniffi by creating { + includeDirs(projectDir.resolve("src").resolve("nativeInterop").resolve("cinterop")) + } + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + implementation("com.squareup.okio:okio:3.2.0") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + val jvmMain by getting { + dependencies { + implementation("net.java.dev.jna:jna:5.12.1") + } + } + val jvmTest by getting +// val jsMain by getting +// val jsTest by getting + val nativeMain by getting + val nativeTest by getting + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradle.properties b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradle.properties new file mode 100644 index 00000000..b18e3adc --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradle.properties @@ -0,0 +1,4 @@ +kotlin.code.style=official +kotlin.mpp.enableGranularSourceSetsMetadata=true +kotlin.native.enableDependencyPropagation=false +kotlin.js.generate.executable.default=false diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradle/wrapper/gradle-wrapper.jar b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..41d9927a Binary files /dev/null and b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradle/wrapper/gradle-wrapper.jar differ diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradle/wrapper/gradle-wrapper.properties b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..aa991fce --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradlew b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradlew.bat b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/settings.gradle.kts b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/settings.gradle.kts new file mode 100644 index 00000000..e211f33b --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "kotlin-uniffi-base" \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/Disposable.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/Disposable.kt new file mode 100644 index 00000000..c399732c --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/Disposable.kt @@ -0,0 +1,20 @@ +interface Disposable { + fun destroy() + + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + this?.destroy() + } catch (_: Throwable) { + } + } \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FFIObject.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FFIObject.kt new file mode 100644 index 00000000..8592e319 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FFIObject.kt @@ -0,0 +1,40 @@ +import kotlinx.atomicfu.* + +abstract class FFIObject( + protected val pointer: Pointer +) : Disposable { + + private val wasDestroyed = atomic(false) + private val callCounter = atomic(1L) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + if (this.wasDestroyed.compareAndSet(expect = false, update = true)) { + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + do { + val c = this.callCounter.value + if (c == 0L) { + throw IllegalStateException("${this::class.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this::class.simpleName} call counter would overflow") + } + } while (!this.callCounter.compareAndSet(expect = c, update = c + 1L)) + try { + return block(this.pointer) + } finally { + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } +} \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverter.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverter.kt new file mode 100644 index 00000000..09fcff5b --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverter.kt @@ -0,0 +1,28 @@ +import okio.Buffer + +interface FfiConverter { + + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer { + val buffer = Buffer().apply { write(value, buffer) } + return allocRustBuffer(buffer) + } + + fun liftFromRustBuffer(rbuf: RustBuffer): KotlinType { + val byteBuf = rbuf.asSource() + try { + val item = read(byteBuf) + if (!byteBuf.exhausted()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + rbuf.free() + } + } + + fun lift(value: FfiType): KotlinType + fun lower(value: KotlinType): FfiType + fun read(source: NoCopySource): KotlinType + fun allocationSize(value: KotlinType): Int + fun write(value: KotlinType, buf: Buffer) +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterBoolean.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterBoolean.kt new file mode 100644 index 00000000..4df4b8cf --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterBoolean.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterBoolean : FfiConverter { + override fun lift(value: Byte): Boolean = value.toInt() != 0 + + override fun read(source: NoCopySource): Boolean = lift(source.readByte()) + + override fun lower(value: Boolean): Byte = if (value) 1.toByte() else 0.toByte() + + override fun allocationSize(value: Boolean) = 1 + + override fun write(value: Boolean, buf: Buffer) { + buf.writeByte(lower(value).toInt()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterByte.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterByte.kt new file mode 100644 index 00000000..9376390a --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterByte.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterByte : FfiConverter { + override fun lift(value: Byte): Byte = value + + override fun read(source: NoCopySource): Byte = source.readByte() + + override fun lower(value: Byte): Byte = value + + override fun allocationSize(value: Byte) = 1 + + override fun write(value: Byte, buf: Buffer) { + buf.writeByte(value.toInt()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterCallbackInterface.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterCallbackInterface.kt new file mode 100644 index 00000000..38692a51 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterCallbackInterface.kt @@ -0,0 +1,76 @@ +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.getAndUpdate +import kotlinx.atomicfu.locks.reentrantLock +import kotlinx.atomicfu.locks.withLock +import okio.Buffer + +internal class ConcurrentHandleMap( + private val leftMap: MutableMap = mutableMapOf(), + private val rightMap: MutableMap = mutableMapOf() +) { + private val lock = reentrantLock() + private val currentHandle = atomic(0L) + + fun insert(obj: T): Handle = + lock.withLock { + rightMap[obj] ?: currentHandle.getAndIncrement() + .let { it.toULong() } + .also { handle -> + leftMap[handle] = obj + rightMap[obj] = handle + } + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +internal const val IDX_CALLBACK_FREE = 0 +// Callback return codes +internal const val UNIFFI_CALLBACK_SUCCESS = 0 +internal const val UNIFFI_CALLBACK_ERROR = 1 +internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 + +abstract class FfiConverterCallbackInterface : FfiConverter { + private val handleMap = ConcurrentHandleMap() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + internal abstract fun register(lib: UniFFILib) + + fun drop(handle: Handle) { + handleMap.remove(handle) + } + + override fun lift(value: Handle): CallbackInterface { + return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") + } + + override fun read(source: NoCopySource) = lift(source.readLong().toULong()) + + override fun lower(value: CallbackInterface) = + handleMap.insert(value).also { + check(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + override fun allocationSize(value: CallbackInterface) = 8 + + override fun write(value: CallbackInterface, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterDouble.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterDouble.kt new file mode 100644 index 00000000..32a0c9b9 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterDouble.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterDouble : FfiConverter { + override fun lift(value: Double): Double = value + + override fun read(source: NoCopySource): Double = Double.fromBits(source.readLong()) + + override fun lower(value: Double): Double = value + + override fun allocationSize(value: Double) = 8 + + override fun write(value: Double, buf: Buffer) { + buf.writeLong(value.toRawBits()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterDuration.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterDuration.kt new file mode 100644 index 00000000..298b2b73 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterDuration.kt @@ -0,0 +1,26 @@ +import okio.Buffer +import kotlin.time.Duration +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +object FfiConverterDuration : FfiConverterRustBuffer { + override fun read(source: NoCopySource): Duration { + val seconds = source.readLong().seconds + val nanoseconds = source.readInt().nanoseconds + val duration = seconds + nanoseconds + if (duration < 0.nanoseconds) { + throw IllegalArgumentException("Duration nanoseconds exceed minimum or maximum supported by uniffi") + } + return duration + } + + override fun allocationSize(value: Duration) = 12 + + override fun write(value: Duration, buf: Buffer) { + if (value < 0.nanoseconds) { + throw IllegalArgumentException("Invalid duration, must be non-negative") + } + buf.writeLong(value.inWholeSeconds) + buf.writeInt(value.inWholeNanoseconds.toInt()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterFloat.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterFloat.kt new file mode 100644 index 00000000..712d6194 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterFloat.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterFloat : FfiConverter { + override fun lift(value: Float): Float = value + + override fun read(source: NoCopySource): Float = Float.fromBits(source.readInt()) + + override fun lower(value: Float): Float = value + + override fun allocationSize(value: Float) = 4 + + override fun write(value: Float, buf: Buffer) { + buf.writeInt(value.toRawBits()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterInstant.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterInstant.kt new file mode 100644 index 00000000..a61048b1 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterInstant.kt @@ -0,0 +1,27 @@ +import kotlinx.datetime.Instant +import okio.Buffer + +object FfiConverterInstant : FfiConverterRustBuffer { + override fun read(source: NoCopySource): Instant { + val seconds = source.readLong() + val nanoseconds = source.readInt() + val instant = Instant.fromEpochSeconds(seconds, nanoseconds) + if (nanoseconds < 0) { + throw IllegalArgumentException("Instant nanoseconds exceed minimum or maximum supported by uniffi") + } + return instant + } + + override fun allocationSize(value: Instant) = 12 + + override fun write(value: Instant, buf: Buffer) { + value.epochSeconds + + if (value.nanosecondsOfSecond < 0) { + throw IllegalArgumentException("Invalid timestamp, nano value must be non-negative") + } + + buf.writeLong(value.epochSeconds) + buf.writeInt(value.nanosecondsOfSecond) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterInt.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterInt.kt new file mode 100644 index 00000000..98b45b11 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterInt.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterInt : FfiConverter { + override fun lift(value: Int): Int = value + + override fun read(source: NoCopySource): Int = source.readInt() + + override fun lower(value: Int): Int = value + + override fun allocationSize(value: Int) = 4 + + override fun write(value: Int, buf: Buffer) { + buf.writeInt(value) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterLong.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterLong.kt new file mode 100644 index 00000000..cb2b3d40 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterLong.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterLong : FfiConverter { + override fun lift(value: Long): Long = value + + override fun read(source: NoCopySource): Long = source.readLong() + + override fun lower(value: Long): Long = value + + override fun allocationSize(value: Long) = 8 + + override fun write(value: Long, buf: Buffer) { + buf.writeLong(value) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterRustBuffer.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterRustBuffer.kt new file mode 100644 index 00000000..b625e63d --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterRustBuffer.kt @@ -0,0 +1,4 @@ +interface FfiConverterRustBuffer : FfiConverter { + override fun lift(value: RustBuffer) = liftFromRustBuffer(value) + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterShort.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterShort.kt new file mode 100644 index 00000000..e5a60da8 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterShort.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterShort : FfiConverter { + override fun lift(value: Short): Short = value + + override fun read(source: NoCopySource): Short = source.readShort() + + override fun lower(value: Short): Short = value + + override fun allocationSize(value: Short) = 2 + + override fun write(value: Short, buf: Buffer) { + buf.writeShort(value.toInt()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterString.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterString.kt new file mode 100644 index 00000000..f4737f3a --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterString.kt @@ -0,0 +1,35 @@ +import okio.Buffer + +object FfiConverterString : FfiConverter { + override fun lift(value: RustBuffer): String { + try { + val byteArr = value.asSource().readByteArray(value.dataSize.toLong()) + return byteArr.decodeToString() + } finally { + value.free() + } + } + + override fun read(source: NoCopySource): String { + val len = source.readInt() + val byteArr = source.readByteArray(len.toLong()) + return byteArr.decodeToString() + } + + override fun lower(value: String): RustBuffer { + val buffer = Buffer().write(value.encodeToByteArray()) + return allocRustBuffer(buffer) + } + + override fun allocationSize(value: String): Int { + val sizeForLength = 4 + val sizeForString = value.length * 3 + return sizeForLength + sizeForString + } + + override fun write(value: String, buf: Buffer) { + val byteArr = value.encodeToByteArray() + buf.writeInt(byteArr.size) + buf.write(byteArr) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterUByte.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterUByte.kt new file mode 100644 index 00000000..4c84b5c8 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterUByte.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterUByte : FfiConverter { + override fun lift(value: UByte): UByte = value + + override fun read(source: NoCopySource): UByte = lift(source.readByte().toUByte()) + + override fun lower(value: UByte): UByte = value + + override fun allocationSize(value: UByte) = 1 + + override fun write(value: UByte, buf: Buffer) { + buf.writeByte(value.toInt()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterUInt.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterUInt.kt new file mode 100644 index 00000000..fb79f30e --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterUInt.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterUInt : FfiConverter { + override fun lift(value: UInt): UInt = value + + override fun read(source: NoCopySource): UInt = lift(source.readInt().toUInt()) + + override fun lower(value: UInt): UInt = value + + override fun allocationSize(value: UInt) = 4 + + override fun write(value: UInt, buf: Buffer) { + buf.writeInt(value.toInt()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterULong.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterULong.kt new file mode 100644 index 00000000..0fdce5f2 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterULong.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterULong : FfiConverter { + override fun lift(value: ULong): ULong = value + + override fun read(source: NoCopySource): ULong = lift(source.readLong().toULong()) + + override fun lower(value: ULong): ULong = value + + override fun allocationSize(value: ULong) = 8 + + override fun write(value: ULong, buf: Buffer) { + buf.writeLong(value.toLong()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterUShort.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterUShort.kt new file mode 100644 index 00000000..2a97c0de --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/FfiConverterUShort.kt @@ -0,0 +1,15 @@ +import okio.Buffer + +object FfiConverterUShort : FfiConverter { + override fun lift(value: UShort): UShort = value + + override fun read(source: NoCopySource): UShort = lift(source.readShort().toUShort()) + + override fun lower(value: UShort): UShort = value + + override fun allocationSize(value: UShort) = 2 + + override fun write(value: UShort, buf: Buffer) { + buf.writeShort(value.toInt()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/ForeignBytes.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/ForeignBytes.kt new file mode 100644 index 00000000..c5f57269 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/ForeignBytes.kt @@ -0,0 +1,3 @@ +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class ForeignBytes \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/ForeignCallback.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/ForeignCallback.kt new file mode 100644 index 00000000..465414f7 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/ForeignCallback.kt @@ -0,0 +1,5 @@ +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class ForeignCallback + +typealias Handle = ULong diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/InternalException.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/InternalException.kt new file mode 100644 index 00000000..d0532bc8 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/InternalException.kt @@ -0,0 +1 @@ +class InternalException(message: String) : Exception(message) \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/Pointer.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/Pointer.kt new file mode 100644 index 00000000..489f0dae --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/Pointer.kt @@ -0,0 +1,24 @@ +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class Pointer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UBytePointer + +expect fun Long.toPointer(): Pointer + +expect fun Pointer.toLong(): Long + +expect fun UBytePointer.asSource(len: Long): NoCopySource + +interface NoCopySource { + fun exhausted(): Boolean + fun readByte(): Byte + fun readInt(): Int + fun readLong(): Long + fun readShort(): Short + fun readByteArray(): ByteArray + fun readByteArray(len: Long): ByteArray +} + diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/RustBuffer.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/RustBuffer.kt new file mode 100644 index 00000000..e794de63 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/RustBuffer.kt @@ -0,0 +1,21 @@ +import okio.Buffer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class RustBuffer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class RustBufferPointer + +expect fun RustBuffer.asSource(): NoCopySource + +expect val RustBuffer.dataSize: Int + +expect fun RustBuffer.free() + +expect fun allocRustBuffer(buffer: Buffer): RustBuffer + +expect fun RustBufferPointer.setValue(value: RustBuffer) + +expect fun emptyRustBuffer(): RustBuffer diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/RustCallStatus.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/RustCallStatus.kt new file mode 100644 index 00000000..d489fd06 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/RustCallStatus.kt @@ -0,0 +1,19 @@ +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class RustCallStatus + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class RustCallStatusByValue + +fun RustCallStatus.isSuccess(): Boolean = statusCode == 0.toByte() + +fun RustCallStatus.isError(): Boolean = statusCode == 1.toByte() + +fun RustCallStatus.isPanic(): Boolean = statusCode == 2.toByte() + +expect val RustCallStatus.statusCode: Byte + +expect val RustCallStatus.errorBuffer: RustBuffer + +expect fun withRustCallStatus(block: (RustCallStatus) -> T): T diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/UniffiHandleMap.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/UniffiHandleMap.kt new file mode 100644 index 00000000..96d7eaea --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/UniffiHandleMap.kt @@ -0,0 +1,16 @@ +// Map handles to objects +// +// This is used when the Rust code expects an opaque pointer to represent some foreign object. +// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an +// object reference , nor does it support leaking a reference to Rust. +// +// Instead, this class maps ULong values to objects so that we can pass a pointer-sized type to +// Rust when it needs an opaque pointer. +// +// TODO: refactor callbacks to use this class +expect class UniFfiHandleMap() { + val size: Int + fun insert(obj: T): ULong + fun get(handle: ULong): T? + fun remove(handle: ULong) +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/rustCall.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/rustCall.kt new file mode 100644 index 00000000..12bdc05a --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin/rustCall.kt @@ -0,0 +1,44 @@ +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +internal inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, crossinline callback: (RustCallStatus) -> U): U = + withRustCallStatus { status -> + val return_value = callback(status) + checkCallStatus(errorHandler, status) + return_value + } + + +// Check RustCallStatus and throw an error if the call wasn't successful +internal fun checkCallStatus(errorHandler: CallStatusErrorHandler, status: RustCallStatus) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.errorBuffer) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.errorBuffer.dataSize > 0) { + // TODO avoid additional copy + throw InternalException(FfiConverterString.lift(status.errorBuffer)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +interface CallStatusErrorHandler { + fun lift(errorBuffer: RustBuffer): E; +} + +object NullCallStatusErrorHandler : CallStatusErrorHandler { + override fun lift(errorBuffer: RustBuffer): InternalException { + errorBuffer.free() + return InternalException("Unexpected CALL_ERROR") + } +} + +internal inline fun rustCall(crossinline callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/ForeignBytes.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/ForeignBytes.kt new file mode 100644 index 00000000..122aa4d6 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/ForeignBytes.kt @@ -0,0 +1,11 @@ +import com.sun.jna.Pointer +import com.sun.jna.Structure + +@Structure.FieldOrder("len", "data") +actual open class ForeignBytes : Structure() { + @JvmField + var len: Int = 0 + + @JvmField + var data: Pointer? = null +} \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/ForeignCallbackJvm.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/ForeignCallbackJvm.kt new file mode 100644 index 00000000..5f0aceb0 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/ForeignCallbackJvm.kt @@ -0,0 +1,21 @@ +import com.sun.jna.Callback + +class NativeCallback( + private val invokeImpl: ( + handle: Handle, + method: Int, + argsData: UBytePointer, + argsLen: Int, + outBuf: RustBufferPointer // RustBufferByReference + ) -> Int +) : Callback { + fun invoke( + handle: Handle, + method: Int, + argsData: UBytePointer, + argsLen: Int, + outBuf: RustBufferPointer // RustBufferByReference + ): Int = invokeImpl(handle, method, argsData, argsLen, outBuf) +} + +actual typealias ForeignCallback = NativeCallback diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/Pointer.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/Pointer.kt new file mode 100644 index 00000000..a3b22bf1 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/Pointer.kt @@ -0,0 +1,38 @@ +import java.nio.ByteOrder + +actual typealias Pointer = com.sun.jna.Pointer + +actual typealias UBytePointer = com.sun.jna.Pointer + +actual fun Long.toPointer() = com.sun.jna.Pointer(this) + +actual fun Pointer.toLong(): Long = com.sun.jna.Pointer.nativeValue(this) + +actual fun UBytePointer.asSource(len: Long): NoCopySource = object : NoCopySource { + val buffer = getByteBuffer(0, len).also { + it.order(ByteOrder.BIG_ENDIAN) + } + + override fun exhausted(): Boolean = !buffer.hasRemaining() + + override fun readByte(): Byte = buffer.get() + + override fun readInt(): Int = buffer.getInt() + + override fun readLong(): Long = buffer.getLong() + + override fun readShort(): Short = buffer.getShort() + + override fun readByteArray(): ByteArray { + val remaining = buffer.remaining() + return readByteArray(remaining.toLong()) + } + + override fun readByteArray(len: Long): ByteArray { + val startIndex = buffer.position().toLong() + val indexAfterLast = (startIndex + len).toInt() + val byteArray = getByteArray(startIndex, len.toInt()) + buffer.position(indexAfterLast) + return byteArray + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/RustCallStatusJvm.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/RustCallStatusJvm.kt new file mode 100644 index 00000000..37dcd3d4 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/RustCallStatusJvm.kt @@ -0,0 +1,27 @@ +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.Structure.ByValue + +@Structure.FieldOrder("code", "error_buf") +actual open class RustCallStatus : Structure() { + @JvmField + var code: Byte = 0 + + @JvmField + var error_buf: RustBuffer = RustBuffer() +} + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +actual open class RustCallStatusByValue : RustCallStatus(), ByValue + +actual val RustCallStatus.statusCode: Byte + get() = code +actual val RustCallStatus.errorBuffer: RustBuffer + get() = error_buf + +actual fun withRustCallStatus(block: (RustCallStatus) -> T): T { + val rustCallStatus = RustCallStatus() + return block(rustCallStatus) +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/UniffiHandleMap.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/UniffiHandleMap.kt new file mode 100644 index 00000000..72321f45 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin/UniffiHandleMap.kt @@ -0,0 +1,27 @@ +import java.util.concurrent.ConcurrentHashMap + +actual class UniFfiHandleMap { + private val map = ConcurrentHashMap() + // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible + // values seems like enough. If somehow we generate 4 billion handles, then this will wrap + // around back to zero and we can assume the first handle generated will have been dropped by + // then. + private val counter = java.util.concurrent.atomic.AtomicInteger(0) + + actual val size: Int + get() = map.size + + actual fun insert(obj: T): ULong { + val handle = counter.getAndAdd(1).toULong() + map.put(handle, obj) + return handle + } + + actual fun get(handle: ULong): T? { + return map.get(handle) + } + + actual fun remove(handle: ULong) { + map.remove(handle) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeInterop/cinterop/uniffi.def b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeInterop/cinterop/uniffi.def new file mode 100644 index 00000000..e9630299 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeInterop/cinterop/uniffi.def @@ -0,0 +1,2 @@ +headers = uniffi/uniffi.h +headerFilter = uniffi/* \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeInterop/cinterop/uniffi/uniffi.h b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeInterop/cinterop/uniffi/uniffi.h new file mode 100644 index 00000000..47a16f5a --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeInterop/cinterop/uniffi/uniffi.h @@ -0,0 +1,63 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + int32_t capacity; + int32_t len; + uint8_t *_Nullable data; +} RustBuffer; + +// *_Nonnull is ignored by cinterop +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull); + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H + +RustBuffer ffi_vodozemac_9f49_rustbuffer_alloc( + int32_t size, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ffi_vodozemac_9f49_rustbuffer_from_bytes( + ForeignBytes bytes, + RustCallStatus *_Nonnull out_status + ); +void ffi_vodozemac_9f49_rustbuffer_free( + RustBuffer buf, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ffi_vodozemac_9f49_rustbuffer_reserve( + RustBuffer buf,int32_t additional, + RustCallStatus *_Nonnull out_status + ); diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin/ForeignCallback.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin/ForeignCallback.kt new file mode 100644 index 00000000..dfe3f3dc --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin/ForeignCallback.kt @@ -0,0 +1,8 @@ +import kotlinx.cinterop.CFunction +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.CValue + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") + +actual typealias ForeignCallback = CPointer Int>> diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin/Pointer.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin/Pointer.kt new file mode 100644 index 00000000..54c3629c --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin/Pointer.kt @@ -0,0 +1,98 @@ +import kotlinx.cinterop.* + +import okio.Buffer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_USE_SITE_VARIANCE") +actual typealias Pointer = CPointer + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_USE_SITE_VARIANCE", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UBytePointer = CPointer + +actual fun Long.toPointer(): Pointer = requireNotNull(this.toCPointer()) + +actual fun Pointer.toLong(): Long = this.rawValue.toLong() + +@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +internal inline infix fun Byte.and(other: Long): Long = toLong() and other + +@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +internal inline infix fun Byte.and(other: Int): Int = toInt() and other + +// byte twiddling was basically pasted from okio +actual fun UBytePointer.asSource(len: Long): NoCopySource = object : NoCopySource { + var readBytes: Int = 0 + var remaining: Long = len + + init { + if (len < 0) { + throw IllegalStateException("Trying to create NoCopySource with negative length") + } + } + + private fun requireLen(requiredLen: Long) { + if (remaining < requiredLen) { + throw IllegalStateException("Expected at least ${requiredLen} bytes in source but have only ${len}") + } + remaining -= requiredLen + } + + override fun exhausted(): Boolean = remaining == 0L + + override fun readByte(): Byte { + requireLen(1) + return reinterpret()[readBytes++] + } + + override fun readShort(): Short { + requireLen(2) + val data = reinterpret() + val s = data[readBytes++] and 0xff shl 8 or (data[readBytes++] and 0xff) + return s.toShort() + } + + override fun readInt(): Int { + requireLen(4) + val data = reinterpret() + val i = ( + data[readBytes++] and 0xff shl 24 + or (data[readBytes++] and 0xff shl 16) + or (data[readBytes++] and 0xff shl 8) + or (data[readBytes++] and 0xff) + ) + return i + } + + override fun readLong(): Long { + requireLen(8) + val data = reinterpret() + val v = ( + data[readBytes++] and 0xffL shl 56 + or (data[readBytes++] and 0xffL shl 48) + or (data[readBytes++] and 0xffL shl 40) + or (data[readBytes++] and 0xffL shl 32) + or (data[readBytes++] and 0xffL shl 24) + or (data[readBytes++] and 0xffL shl 16) + or (data[readBytes++] and 0xffL shl 8) // ktlint-disable no-multi-spaces + or (data[readBytes++] and 0xffL) + ) + return v + } + + override fun readByteArray(): ByteArray = readByteArray(len) + + override fun readByteArray(len: Long): ByteArray { + requireLen(len) + + val cast = reinterpret() + val intLen = len.toInt() + val byteArray = ByteArray(intLen) + + for (writeIdx in 0 until intLen) { + byteArray[writeIdx] = cast[readBytes++] + } + + return byteArray + } + +} diff --git a/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin/UniFfiHandleMap.kt b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin/UniFfiHandleMap.kt new file mode 100644 index 00000000..2bd0574e --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin/UniFfiHandleMap.kt @@ -0,0 +1,41 @@ +import kotlinx.atomicfu.getAndUpdate + +// This is actually common kotlin but inefficient because of the coarse granular locking... +// TODO either create some real implementation or at least measure if protecting the counter +// with the lock and using a plain Int wouldn't be faster +actual class UniFfiHandleMap { + private val mapLock = kotlinx.atomicfu.locks.ReentrantLock() + private val map = HashMap() + + // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible + // values seems like enough. If somehow we generate 4 billion handles, then this will wrap + // around back to zero and we can assume the first handle generated will have been dropped by + // then. + private val counter = kotlinx.atomicfu.atomic(0) + + actual val size: Int + get() = map.size + + actual fun insert(obj: T): ULong { + val handle = counter.getAndUpdate { it + 1 }.toULong() + synchronizedMapAccess { map.put(handle, obj) } + return handle + } + + actual fun get(handle: ULong): T? { + return synchronizedMapAccess { map.get(handle) } + } + + actual fun remove(handle: ULong) { + synchronizedMapAccess { map.remove(handle) } + } + + fun synchronizedMapAccess(block: () -> T): T { + mapLock.lock() + try { + return block() + } finally { + mapLock.unlock() + } + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/callback_interface.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/callback_interface.rs new file mode 100644 index 00000000..36599b0a --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/callback_interface.rs @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal}; + +pub struct CallbackInterfaceCodeType { + id: String, +} + +impl CallbackInterfaceCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for CallbackInterfaceCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.class_name(&self.id) + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String { + unreachable!(); + } + + fn initialization_fn(&self, oracle: &dyn CodeOracle) -> Option { + Some(format!("{}.register", self.ffi_converter_name(oracle))) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/compounds.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/compounds.rs new file mode 100644 index 00000000..d98deab9 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/compounds.rs @@ -0,0 +1,94 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use paste::paste; +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal, TypeIdentifier}; + +fn render_literal(oracle: &dyn CodeOracle, literal: &Literal, inner: &TypeIdentifier) -> String { + match literal { + Literal::Null => "null".into(), + Literal::EmptySequence => "listOf()".into(), + Literal::EmptyMap => "mapOf()".into(), + + // For optionals + _ => oracle.find(inner).literal(oracle, literal), + } +} + +macro_rules! impl_code_type_for_compound { + ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => { + paste! { + pub struct $T { + inner: TypeIdentifier, + } + + impl $T { + pub fn new(inner: TypeIdentifier) -> Self { + Self { inner } + } + fn inner(&self) -> &TypeIdentifier { + &self.inner + } + } + + impl CodeType for $T { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + format!($type_label_pattern, oracle.find(self.inner()).type_label(oracle)) + } + + fn canonical_name(&self, oracle: &dyn CodeOracle) -> String { + format!($canonical_name_pattern, oracle.find(self.inner()).canonical_name(oracle)) + } + + fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String { + render_literal(oracle, literal, self.inner()) + } + } + } + } + } + +impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}"); +impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}"); + +pub struct MapCodeType { + key: TypeIdentifier, + value: TypeIdentifier, +} + +impl MapCodeType { + pub fn new(key: TypeIdentifier, value: TypeIdentifier) -> Self { + Self { key, value } + } + + fn key(&self) -> &TypeIdentifier { + &self.key + } + + fn value(&self) -> &TypeIdentifier { + &self.value + } +} + +impl CodeType for MapCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + format!( + "Map<{}, {}>", + self.key().type_label(oracle), + self.value().type_label(oracle), + ) + } + + fn canonical_name(&self, oracle: &dyn CodeOracle) -> String { + format!( + "Map{}{}", + self.key().type_label(oracle), + self.value().type_label(oracle), + ) + } + + fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String { + render_literal(oracle, literal, &self.value) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/custom.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/custom.rs new file mode 100644 index 00000000..29291db5 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/custom.rs @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal}; + +pub struct CustomCodeType { + name: String, +} + +impl CustomCodeType { + pub fn new(name: String) -> Self { + CustomCodeType { name } + } +} + +impl CodeType for CustomCodeType { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + self.name.clone() + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.name) + } + + fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String { + unreachable!("Can't have a literal of a custom type"); + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/enum_.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/enum_.rs new file mode 100644 index 00000000..418284fc --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/enum_.rs @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal}; + +pub struct EnumCodeType { + id: String, +} + +impl EnumCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for EnumCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.class_name(&self.id) + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String { + if let Literal::Enum(v, _) = literal { + format!( + "{}.{}", + self.type_label(oracle), + oracle.enum_variant_name(v) + ) + } else { + unreachable!(); + } + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/error.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/error.rs new file mode 100644 index 00000000..86168a73 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/error.rs @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal}; + +pub struct ErrorCodeType { + id: String, +} + +impl ErrorCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ErrorCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.error_name(&self.id) + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String { + unreachable!(); + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/executor.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/executor.rs new file mode 100644 index 00000000..fc37ee3b --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/executor.rs @@ -0,0 +1,22 @@ +use uniffi_bindgen::backend::{CodeOracle, CodeType}; + +pub struct ForeignExecutorCodeType; + +impl CodeType for ForeignExecutorCodeType { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + // Kotlin uses a CoroutineScope for ForeignExecutor + "CoroutineScope".into() + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + "ForeignExecutor".into() + } + + fn initialization_fn(&self, _oracle: &dyn CodeOracle) -> Option { + // FfiConverterForeignExecutor is a Kotlin object generated from a template + // register calls lib.uniffi_foreign_executor_callback_set(UniFfiForeignExecutorCallback) where + // object UniFfiForeignExecutorCallback : com.sun.jna.Callback + // but that will not work in Kotlin/Native since we do not have access to JNA + Some("FfiConverterForeignExecutor.register".into()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/external.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/external.rs new file mode 100644 index 00000000..edf02c4b --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/external.rs @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal}; + +pub struct ExternalCodeType { + name: String, +} + +impl ExternalCodeType { + pub fn new(name: String) -> Self { + Self { name } + } +} + +impl CodeType for ExternalCodeType { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + self.name.clone() + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.name) + } + + fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String { + unreachable!("Can't have a literal of an external type"); + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/miscellany.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/miscellany.rs new file mode 100644 index 00000000..c0b3ee15 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/miscellany.rs @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use paste::paste; +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal}; + +macro_rules! impl_code_type_for_miscellany { + ($T:ty, $class_name:literal, $canonical_name:literal) => { + paste! { + pub struct $T; + + impl CodeType for $T { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + $class_name.into() + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + $canonical_name.into() + } + + fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String { + unreachable!() + } + } + } + }; +} + +impl_code_type_for_miscellany!(TimestampCodeType, "kotlinx.datetime.Instant", "Instant"); + +impl_code_type_for_miscellany!(DurationCodeType, "kotlin.time.Duration", "Duration"); diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/mod.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/mod.rs new file mode 100644 index 00000000..fc8715f5 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/mod.rs @@ -0,0 +1,851 @@ +use std::borrow::Borrow; +use std::collections::HashMap; + +use anyhow::{Context, Result}; +use askama::Template; +use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; +use uniffi_bindgen::backend::{CodeOracle, CodeType, TypeIdentifier}; +use uniffi_bindgen::interface::{Callable, CallbackInterface, Enum, Error, FfiType, Object, Record, Type}; +use uniffi_bindgen::ComponentInterface; + +use crate::{Config, KotlinMultiplatformBindings}; + +mod callback_interface; +mod compounds; +mod custom; +mod enum_; +mod error; +mod executor; +mod external; +mod miscellany; +mod object; +mod primitives; +mod record; + +macro_rules! kotlin_template { + ($KotlinTemplate:ident, $source_file:literal) => { + #[derive(Template)] + #[template(syntax = "kt", escape = "none", path = $source_file)] + pub struct $KotlinTemplate<'ci> { + config: Config, + ci: &'ci ComponentInterface, + } + + impl<'ci> $KotlinTemplate<'ci> { + pub fn new(config: Config, ci: &'ci ComponentInterface) -> Self { + Self { config, ci } + } + + pub fn initialization_fns(&self) -> Vec { + self.ci + .iter_types() + .filter_map(|t| t.initialization_fn(&KotlinCodeOracle)) + .collect() + } + } + }; +} + +// Dummy templates are copied as is. They are useful to reuse the existing logic +macro_rules! kotlin_dummy_template { + ($KotlinTemplate:ident, $source_file:literal) => { + #[derive(Template)] + #[template(syntax = "kt", escape = "none", path = $source_file)] + pub struct $KotlinTemplate {} + + impl $KotlinTemplate { + pub fn new() -> Self { + Self { } + } + } + }; +} + +macro_rules! kotlin_callback_interface_template { + ($KotlinTemplate:ident, $source_file:literal) => { + #[derive(Template)] + #[template(syntax = "kt", escape = "none", path = $source_file)] + pub struct $KotlinTemplate<'cbi> { + cbi: &'cbi CallbackInterface, + type_name: String, + foreign_callback_name: String, + ffi_converter_name: String, + } + + impl<'cbi> $KotlinTemplate<'cbi> { + pub fn new( + cbi: &'cbi CallbackInterface, + type_name: String, + foreign_callback_name: String, + ffi_converter_name: String, + ) -> Self { + Self { + cbi, + type_name, + foreign_callback_name, + ffi_converter_name, + } + } + } + }; +} + +#[derive(Template)] +#[template( + syntax = "kt", + escape = "none", + path = "common/CustomTypeTemplate.kt.j2" +)] +pub struct CustomTypeTemplateCommon { + config: Config, + name: String, + ffi_converter_name: String, + builtin: Box, +} + +impl CustomTypeTemplateCommon { + pub fn new( + config: Config, + name: String, + ffi_converter_name: String, + builtin: Box, + ) -> Self { + Self { + config, + ffi_converter_name, + name, + builtin, + } + } +} + +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "common/EnumTemplate.kt.j2")] +pub struct EnumTemplateCommon<'e> { + e: &'e Enum, + type_name: String, + contains_object_references: bool, +} + +impl<'e> EnumTemplateCommon<'e> { + pub fn new(e: &'e Enum, type_name: String, contains_object_references: bool) -> Self { + Self { + e, + type_name, + contains_object_references, + } + } +} + +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "common/ErrorTemplate.kt.j2")] +pub struct ErrorTemplateCommon<'e> { + e: &'e Error, + type_name: String, + contains_object_references: bool, +} + +impl<'e> ErrorTemplateCommon<'e> { + pub fn new(e: &'e Error, type_name: String, contains_object_references: bool) -> Self { + Self { + e, + type_name, + contains_object_references, + } + } +} + +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "common/MapTemplate.kt.j2")] +pub struct MapTemplateCommon { + key_type: Box, + value_type: Box, + ffi_converter_name: String, +} + +impl MapTemplateCommon { + pub fn new(key_type: Box, value_type: Box, ffi_converter_name: String) -> Self { + Self { + key_type, + value_type, + ffi_converter_name, + } + } +} + +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "common/ObjectTemplate.kt.j2")] +pub struct ObjectTemplateCommon<'e> { + obj: &'e Object, + type_name: String, +} + +impl<'e> ObjectTemplateCommon<'e> { + pub fn new(obj: &'e Object, type_name: String) -> Self { + Self { obj, type_name } + } +} + +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "common/OptionalTemplate.kt.j2")] +pub struct OptionalTemplateCommon { + ffi_converter_name: String, + inner_type_name: String, + inner_type: Box, +} + +impl OptionalTemplateCommon { + pub fn new(ffi_converter_name: String, inner_type_name: String, inner_type: Box) -> Self { + Self { + ffi_converter_name, + inner_type_name, + inner_type, + } + } +} + +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "common/RecordTemplate.kt.j2")] +pub struct RecordTemplateCommon<'rec> { + rec: &'rec Record, + type_name: String, + contains_object_references: bool, +} + +impl<'rec> RecordTemplateCommon<'rec> { + pub fn new(rec: &'rec Record, type_name: String, contains_object_references: bool) -> Self { + Self { + rec, + type_name, + contains_object_references, + } + } +} + +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "common/SequenceTemplate.kt.j2")] +pub struct SequenceTemplateCommon { + ffi_converter_name: String, + inner_type_name: String, + inner_type: Box, +} + +impl SequenceTemplateCommon { + pub fn new(ffi_converter_name: String, inner_type_name: String, inner_type: Box) -> Self { + Self { + ffi_converter_name, + inner_type_name, + inner_type, + } + } +} + +#[derive(Template)] +#[template( + syntax = "c", + escape = "none", + path = "headers/BridgingHeaderTemplate.h.j2" +)] +pub struct BridgingHeader<'ci> { + _config: Config, + ci: &'ci ComponentInterface, +} + +impl<'ci> BridgingHeader<'ci> { + pub fn new(config: Config, ci: &'ci ComponentInterface) -> Self { + Self { + _config: config, + ci, + } + } +} + +macro_rules! render_kotlin_template { + ($template:ident, $file_name:literal, $map:ident) => { + let file_name = $file_name.to_string(); + let context = format!("failed to render kotlin binding {}", stringify!($T)); + $map.insert(file_name, $template.render().context(context).unwrap()); + }; + + ($template:ident, $file_name:ident, $map:ident) => { + let file_name = $file_name; + let context = format!("failed to render kotlin binding {}", stringify!($T)); + $map.insert(file_name, $template.render().context(context).unwrap()); + }; +} + +kotlin_dummy_template!( + FfiConverterForeignExecutorTemplateCommon, + "common/FfiConverterForeignExecutor.kt.j2" +); +kotlin_dummy_template!( + UniFfiForeignExecutorCallbackTemplateCommon, + "common/UniFfiForeignExecutorCallback.kt.j2" +); +kotlin_template!( + AsyncTypesTemplateCommon, + "common/AsyncTypesTemplate.kt.j2" +); +kotlin_template!( + TopLevelFunctionsTemplateCommon, + "common/TopLevelFunctionsTemplate.kt.j2" +); +kotlin_template!(UniFFILibTemplateCommon, "common/UniFFILibTemplate.kt.j2"); +kotlin_callback_interface_template!( + CallbackInterfaceTemplateCommon, + "common/CallbackInterfaceTemplate.kt.j2" +); + +kotlin_dummy_template!( + UniFfiForeignExecutorCallbackTemplateJvm, + "jvm/UniFfiForeignExecutorCallback.kt.j2" +); +kotlin_template!( + AsyncTypesTemplateJvm, + "jvm/AsyncTypesTemplate.kt.j2" +); +kotlin_template!( + RustBufferTemplateJvm, + "jvm/RustBufferTemplate.kt.j2" +); +kotlin_template!( + UniFFILibTemplateJvm, + "jvm/UniFFILibTemplate.kt.j2" +); +kotlin_callback_interface_template!( + CallbackInterfaceTemplateJvm, + "jvm/CallbackInterfaceTemplate.kt.j2" +); + +kotlin_dummy_template!( + UniFfiForeignExecutorCallbackTemplateNative, + "native/UniFfiForeignExecutorCallback.kt.j2" +); +kotlin_template!( + AsyncTypesTemplateNative, + "native/AsyncTypesTemplate.kt.j2" +); +kotlin_template!( + ForeignBytesTemplateNative, + "native/ForeignBytesTemplate.kt.j2" +); +kotlin_template!(RustBufferTemplateNative, "native/RustBufferTemplate.kt.j2"); + +kotlin_template!( + RustCallStatusTemplateNative, + "native/RustCallStatusTemplate.kt.j2" +); +kotlin_template!(UniFFILibTemplateNative, "native/UniFFILibTemplate.kt.j2"); +kotlin_callback_interface_template!( + CallbackInterfaceTemplateNative, + "native/CallbackInterfaceTemplate.kt.j2" +); + +pub fn generate_bindings( + config: &Config, + ci: &ComponentInterface, +) -> Result { + let mut common_wrapper: HashMap = HashMap::new(); + let async_types_template_common = AsyncTypesTemplateCommon::new( + config.clone(), + ci + ); + render_kotlin_template!( + async_types_template_common, + "AsyncTypes.kt", + common_wrapper + ); + let top_level_functions_template_common = + TopLevelFunctionsTemplateCommon::new(config.clone(), ci); + render_kotlin_template!( + top_level_functions_template_common, + "TopLevelFunctions.kt", + common_wrapper + ); + let uniffilib_template_common = UniFFILibTemplateCommon::new(config.clone(), ci); + render_kotlin_template!(uniffilib_template_common, "UniFFILib.kt", common_wrapper); + for type_ in ci.iter_types() { + let canonical_type_name = filters::canonical_name(type_).unwrap(); + let ffi_converter_name = filters::ffi_converter_name(type_).unwrap(); + let contains_object_references = ci.item_contains_object_references(type_); + match type_ { + Type::CallbackInterface(name) => { + let cbi: &CallbackInterface = ci.get_callback_interface_definition(name).unwrap(); + let type_name = filters::type_name(cbi).unwrap(); + let template = CallbackInterfaceTemplateCommon::new( + cbi, + type_name.clone(), + format!("ForeignCallback{}", canonical_type_name), + ffi_converter_name, + ); + let file_name = format!("{}.kt", type_name); + render_kotlin_template!(template, file_name, common_wrapper); + } + + Type::Custom { name, builtin } => { + let template = CustomTypeTemplateCommon::new( + config.clone(), + name.clone(), + ffi_converter_name, + builtin.clone(), + ); + let file_name = format!("{}.kt", name); + render_kotlin_template!(template, file_name, common_wrapper); + } + + Type::Enum(name) => { + let e: &Enum = ci.get_enum_definition(name).unwrap(); + let type_name = filters::type_name(type_).unwrap(); + let template = + EnumTemplateCommon::new(e, type_name.clone(), contains_object_references); + let file_name = format!("{}.kt", type_name); + render_kotlin_template!(template, file_name, common_wrapper); + } + + Type::Error(name) => { + let e: &Error = ci.get_error_definition(name).unwrap(); + let type_name = filters::type_name(type_).unwrap(); + let template = + ErrorTemplateCommon::new(e, type_name.clone(), contains_object_references); + let file_name = format!("{}.kt", type_name); + render_kotlin_template!(template, file_name, common_wrapper); + } + + Type::External { name, crate_name, kind } => { + // TODO this need specific imports in some classes. + } + + Type::ForeignExecutor => { + // The presence of the ForeignExecutor type indicates that we need to add the async infrastructure + let executor_template_common = FfiConverterForeignExecutorTemplateCommon::new(); + render_kotlin_template!(executor_template_common, "FfiConverterForeignExecutor.kt", common_wrapper); + let callback_template_common = UniFfiForeignExecutorCallbackTemplateCommon::new(); + render_kotlin_template!(callback_template_common, "UniFfiForeignExecutorCallback.kt", common_wrapper); + } + + Type::Map(key_type, value_type) => { + let template = MapTemplateCommon::new( + key_type.clone(), + value_type.clone(), + ffi_converter_name.clone(), + ); + let file_name = format!("{}.kt", ffi_converter_name); + render_kotlin_template!(template, file_name, common_wrapper); + } + + Type::Object { name, .. } => { + let obj: &Object = ci.get_object_definition(name).unwrap(); + let type_name = filters::type_name(type_).unwrap(); + let template = ObjectTemplateCommon::new( + obj, type_name.clone(), + ); + let file_name = format!("{}.kt", type_name); + render_kotlin_template!(template, file_name, common_wrapper); + } + + Type::Optional(inner_type) => { + let inner_type_name = filters::type_name(inner_type).unwrap(); + let template = OptionalTemplateCommon::new( + ffi_converter_name.clone(), inner_type_name, inner_type.clone(), + ); + let file_name = format!("{}.kt", ffi_converter_name); + render_kotlin_template!(template, file_name, common_wrapper); + } + + Type::Record(name) => { + let rec: &Record = ci.get_record_definition(name).unwrap(); + let type_name = filters::type_name(type_).unwrap(); + let template = RecordTemplateCommon::new( + rec, type_name.clone(), contains_object_references, + ); + let file_name = format!("{}.kt", type_name); + render_kotlin_template!(template, file_name, common_wrapper); + } + + Type::Sequence(inner_type) => { + let inner_type_name = filters::type_name(inner_type).unwrap(); + let template = SequenceTemplateCommon::new( + ffi_converter_name.clone(), inner_type_name, inner_type.clone(), + ); + let file_name = format!("{}.kt", ffi_converter_name); + render_kotlin_template!(template, file_name, common_wrapper); + } + _ => {} + } + } + + let mut jvm_wrapper: HashMap = HashMap::new(); + let async_types_template_jvm = AsyncTypesTemplateJvm::new(config.clone(), ci); + render_kotlin_template!(async_types_template_jvm, "AsyncTypes.kt", jvm_wrapper); + let rust_buffer_template_jvm = RustBufferTemplateJvm::new(config.clone(), ci); + render_kotlin_template!(rust_buffer_template_jvm, "RustBuffer.kt", jvm_wrapper); + let uniffilib_template_jvm = UniFFILibTemplateJvm::new(config.clone(), ci); + render_kotlin_template!(uniffilib_template_jvm, "UniFFILib.kt", jvm_wrapper); + for type_ in ci.iter_types() { + let canonical_type_name = filters::canonical_name(type_).unwrap(); + let ffi_converter_name = filters::ffi_converter_name(type_).unwrap(); + match type_ { + Type::CallbackInterface(name) => { + let cbi: &CallbackInterface = ci.get_callback_interface_definition(name).unwrap(); + let type_name = filters::type_name(cbi).unwrap(); + let template = CallbackInterfaceTemplateJvm::new( + cbi, + type_name.clone(), + format!("ForeignCallback{}", canonical_type_name), + ffi_converter_name, + ); + let file_name = format!("{}.kt", type_name); + render_kotlin_template!(template, file_name, jvm_wrapper); + } + + Type::ForeignExecutor => { + // The presence of the ForeignExecutor type indicates that we need to add the async infrastructure + let template = UniFfiForeignExecutorCallbackTemplateJvm::new(); + render_kotlin_template!(template, "UniFfiForeignExecutorCallback.kt", jvm_wrapper); + } + + _ => {} + } + } + + let mut native_wrapper: HashMap = HashMap::new(); + let async_types_template_native = AsyncTypesTemplateNative::new(config.clone(), ci); + render_kotlin_template!( + async_types_template_native, + "AsyncTypes.kt", + native_wrapper + ); + let foreign_bytes_template_native = ForeignBytesTemplateNative::new(config.clone(), ci); + render_kotlin_template!( + foreign_bytes_template_native, + "ForeignBytes.kt", + native_wrapper + ); + let rust_buffer_template_native = RustBufferTemplateNative::new(config.clone(), ci); + render_kotlin_template!( + rust_buffer_template_native, + "RustBuffer.kt", + native_wrapper + ); + let rust_call_status_template_native = RustCallStatusTemplateNative::new(config.clone(), ci); + render_kotlin_template!( + rust_call_status_template_native, + "RustCallStatus.kt", + native_wrapper + ); + let uniffilib_template_native = UniFFILibTemplateNative::new(config.clone(), ci); + render_kotlin_template!( + uniffilib_template_native, + "UniFFILib.kt", + native_wrapper + ); + for type_ in ci.iter_types() { + let canonical_type_name = filters::canonical_name(type_).unwrap(); + let ffi_converter_name = filters::ffi_converter_name(type_).unwrap(); + match type_ { + Type::CallbackInterface(name) => { + let cbi: &CallbackInterface = ci.get_callback_interface_definition(name).unwrap(); + let type_name = filters::type_name(cbi).unwrap(); + let template = CallbackInterfaceTemplateNative::new( + cbi, + type_name.clone(), + format!("ForeignCallback{}", canonical_type_name), + ffi_converter_name, + ); + let file_name = format!("{}.kt", type_name); + render_kotlin_template!(template, file_name, native_wrapper); + } + + Type::ForeignExecutor => { + // The presence of the ForeignExecutor type indicates that we need to add the async infrastructure + let template = UniFfiForeignExecutorCallbackTemplateNative::new(); + render_kotlin_template!(template, "UniFfiForeignExecutorCallback.kt", native_wrapper); + } + + _ => {} + } + } + + let header = BridgingHeader::new(config.clone(), ci) + .render() + .context("failed to render Kotlin/Native bridging header")?; + + Ok(KotlinMultiplatformBindings { + common: common_wrapper, + jvm: jvm_wrapper, + native: native_wrapper, + header, + }) +} + +#[derive(Clone)] +pub struct KotlinCodeOracle; + +impl KotlinCodeOracle { + // Map `Type` instances to a `Box` for that type. + // + // There is a companion match in `templates/Types.kt` which performs a similar function for the + // template code. + // + // - When adding additional types here, make sure to also add a match arm to the `Types.kt` template. + // - To keep things managable, let's try to limit ourselves to these 2 mega-matches + fn create_code_type(&self, type_: TypeIdentifier) -> Box { + match type_ { + Type::UInt8 => Box::new(primitives::UInt8CodeType), + Type::Int8 => Box::new(primitives::Int8CodeType), + Type::UInt16 => Box::new(primitives::UInt16CodeType), + Type::Int16 => Box::new(primitives::Int16CodeType), + Type::UInt32 => Box::new(primitives::UInt32CodeType), + Type::Int32 => Box::new(primitives::Int32CodeType), + Type::UInt64 => Box::new(primitives::UInt64CodeType), + Type::Int64 => Box::new(primitives::Int64CodeType), + Type::Float32 => Box::new(primitives::Float32CodeType), + Type::Float64 => Box::new(primitives::Float64CodeType), + Type::Boolean => Box::new(primitives::BooleanCodeType), + Type::String => Box::new(primitives::StringCodeType), + + Type::Timestamp => Box::new(miscellany::TimestampCodeType), + Type::Duration => Box::new(miscellany::DurationCodeType), + + Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)), + Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)), + Type::Record(id) => Box::new(record::RecordCodeType::new(id)), + Type::Error(id) => Box::new(error::ErrorCodeType::new(id)), + Type::CallbackInterface(id) => { + Box::new(callback_interface::CallbackInterfaceCodeType::new(id)) + } + Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)), + Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)), + Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)), + Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), + Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), + Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), + } + } + + fn ffi_header_type_label(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::Int8 => "int8_t".into(), + FfiType::UInt8 => "uint8_t".into(), + FfiType::Int16 => "int16_t".into(), + FfiType::UInt16 => "uint16_t".into(), + FfiType::Int32 => "int32_t".into(), + FfiType::UInt32 => "uint32_t".into(), + FfiType::Int64 => "int64_t".into(), + FfiType::UInt64 => "uint64_t".into(), + FfiType::Float32 => "float".into(), + FfiType::Float64 => "double".into(), + FfiType::RustArcPtr(_) => "void*_Nonnull".into(), + FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::ForeignBytes => "ForeignBytes".into(), + FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(), + FfiType::ForeignExecutorHandle => "size_t".into(), + FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(), + FfiType::FutureCallback { return_type } => format!( + "UniFfiFutureCallback{} _Nonnull", + return_type.canonical_name() + ), + FfiType::FutureCallbackData => "void* _Nonnull".into(), + } + } +} + +impl CodeOracle for KotlinCodeOracle { + fn find(&self, type_: &TypeIdentifier) -> Box { + self.create_code_type(type_.clone()) + } + + /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc). + fn class_name(&self, nm: &str) -> String { + nm.to_string().to_upper_camel_case() + } + + /// Get the idiomatic Kotlin rendering of a function name. + fn fn_name(&self, nm: &str) -> String { + format!("`{}`", nm.to_string().to_lower_camel_case()) + } + + /// Get the idiomatic Kotlin rendering of a variable name. + fn var_name(&self, nm: &str) -> String { + format!("`{}`", nm.to_string().to_lower_camel_case()) + } + + /// Get the idiomatic Kotlin rendering of an individual enum variant. + fn enum_variant_name(&self, nm: &str) -> String { + nm.to_string().to_shouty_snake_case() + } + + /// Get the idiomatic Kotlin rendering of an exception name + /// + /// This replaces "Error" at the end of the name with "Exception". Rust code typically uses + /// "Error" for any type of error but in the Java world, "Error" means a non-recoverable error + /// and is distinguished from an "Exception". + fn error_name(&self, nm: &str) -> String { + // errors are a class in kotlin. + let name = self.class_name(nm); + match name.strip_suffix("Error") { + None => name, + Some(stripped) => format!("{stripped}Exception"), + } + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::Int8 => "Byte".to_string(), + FfiType::UInt8 => "UByte".to_string(), + FfiType::Int16 => "Short".to_string(), + FfiType::UInt16 => "UShort".to_string(), + FfiType::Int32 => "Int".to_string(), + FfiType::UInt32 => "UInt".to_string(), + FfiType::Int64 => "Long".to_string(), + FfiType::UInt64 => "ULong".to_string(), + FfiType::Float32 => "Float".to_string(), + FfiType::Float64 => "Double".to_string(), + FfiType::RustArcPtr(_) => "Pointer".to_string(), + FfiType::RustBuffer(_) => "RustBuffer".to_string(), + FfiType::ForeignBytes => "ForeignBytes".to_string(), + FfiType::ForeignCallback => "ForeignCallback".to_string(), + FfiType::ForeignExecutorHandle => "ULong".to_string(), + FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(), + FfiType::FutureCallback { return_type } => { + format!("UniFfiFutureCallback{}", return_type.canonical_name()) + } + FfiType::FutureCallbackData => "Pointer".to_string(), + } + } +} + +pub mod filters { + use uniffi_bindgen::backend::Literal; + use uniffi_bindgen::interface::ResultType; + + use super::*; + + fn oracle() -> &'static KotlinCodeOracle { + &KotlinCodeOracle + } + + pub fn type_name(codetype: &impl CodeType) -> Result { + Ok(codetype.type_label(oracle())) + } + + pub fn canonical_name(codetype: &impl CodeType) -> Result { + Ok(codetype.canonical_name(oracle())) + } + + pub fn ffi_converter_name(codetype: &impl CodeType) -> Result { + Ok(codetype.ffi_converter_name(oracle())) + } + + pub fn lower_fn(codetype: &impl CodeType) -> Result { + Ok(format!("{}.lower", codetype.ffi_converter_name(oracle()))) + } + + pub fn allocation_size_fn(codetype: &impl CodeType) -> Result { + Ok(format!( + "{}.allocationSize", + codetype.ffi_converter_name(oracle()) + )) + } + + pub fn write_fn(codetype: &impl CodeType) -> Result { + Ok(format!("{}.write", codetype.ffi_converter_name(oracle()))) + } + + pub fn lift_fn(codetype: &impl CodeType) -> Result { + Ok(format!("{}.lift", codetype.ffi_converter_name(oracle()))) + } + + pub fn read_fn(codetype: &impl CodeType) -> Result { + Ok(format!("{}.read", codetype.ffi_converter_name(oracle()))) + } + + pub fn error_handler(result_type: &ResultType) -> Result { + match &result_type.throws_type { + Some(error_type) => type_name(error_type), + None => Ok("NullCallStatusErrorHandler".into()), + } + } + + pub fn future_callback_handler(result_type: &ResultType) -> Result { + let return_component = match &result_type.return_type { + Some(return_type) => return_type.canonical_name(), + None => "Void".into(), + }; + let throws_component = match &result_type.throws_type { + Some(throws_type) => format!("_{}", throws_type.canonical_name()), + None => "".into(), + }; + Ok(format!( + "UniFfiFutureCallbackHandler{return_component}{throws_component}" + )) + } + + pub fn future_continuation_type(result_type: &ResultType) -> Result { + let return_type_name = match &result_type.return_type { + Some(t) => type_name(t)?, + None => "Unit".into(), + }; + Ok(format!("Continuation<{return_type_name}>")) + } + + pub fn render_literal( + literal: &Literal, + codetype: &impl CodeType, + ) -> Result { + Ok(codetype.literal(oracle(), literal)) + } + + /// Get the Kotlin syntax for representing a given low-level `FfiType`. + pub fn ffi_type_name(type_: &FfiType) -> Result { + Ok(oracle().ffi_type_label(type_)) + } + + pub fn ffi_header_type_name(type_: &FfiType) -> Result { + Ok(oracle().ffi_header_type_label(type_)) + } + + /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc). + pub fn class_name(nm: &str) -> Result { + Ok(oracle().class_name(nm)) + } + + /// Get the idiomatic Kotlin rendering of a function name. + pub fn fn_name(nm: &str) -> Result { + Ok(oracle().fn_name(nm)) + } + + /// Get the idiomatic Kotlin rendering of a variable name. + pub fn var_name(nm: &str) -> Result { + Ok(oracle().var_name(nm)) + } + + /// Get the idiomatic Kotlin rendering of an individual enum variant. + pub fn enum_variant(nm: &str) -> Result { + Ok(oracle().enum_variant_name(nm)) + } + + /// Get the idiomatic Kotlin rendering of an exception name, replacing + /// `Error` with `Exception`. + pub fn exception_name(nm: &str) -> Result { + Ok(oracle().error_name(nm)) + } + + /// Remove the "`" chars we put around function/variable names + /// + /// These are used to avoid name clashes with kotlin identifiers, but sometimes you want to + /// render the name unquoted. One example is the message property for errors where we want to + /// display the name for the user. + pub fn unquote(nm: &str) -> Result { + Ok(nm.trim_matches('`').to_string()) + } + + /// Transforms Pointer types to nullable pointer to work around cinterop not respecting + /// nullability annotations in header files. This leads to type mismatches. + pub fn nullify_pointer(type_name: &str) -> Result { + Ok(match type_name { + "Pointer" => "Pointer?".to_string(), + _ => type_name.to_string(), + }) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/object.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/object.rs new file mode 100644 index 00000000..9d54bab0 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/object.rs @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal}; + +pub struct ObjectCodeType { + id: String, +} + +impl ObjectCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ObjectCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.class_name(&self.id) + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String { + unreachable!(); + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/primitives.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/primitives.rs new file mode 100644 index 00000000..e581d4a7 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/primitives.rs @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use paste::paste; +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal}; +use uniffi_bindgen::interface::{types::Type, Radix}; + +fn render_literal(_oracle: &dyn CodeOracle, literal: &Literal) -> String { + fn typed_number(type_: &Type, num_str: String) -> String { + match type_ { + // Bytes, Shorts and Ints can all be inferred from the type. + Type::Int8 | Type::Int16 | Type::Int32 => num_str, + Type::Int64 => format!("{num_str}L"), + + Type::UInt8 | Type::UInt16 | Type::UInt32 => format!("{num_str}u"), + Type::UInt64 => format!("{num_str}uL"), + + Type::Float32 => format!("{num_str}f"), + Type::Float64 => num_str, + _ => panic!("Unexpected literal: {num_str} is not a number"), + } + } + + match literal { + Literal::Boolean(v) => format!("{v}"), + Literal::String(s) => format!("\"{s}\""), + Literal::Int(i, radix, type_) => typed_number( + type_, + match radix { + Radix::Octal => format!("{i:#x}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::UInt(i, radix, type_) => typed_number( + type_, + match radix { + Radix::Octal => format!("{i:#x}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::Float(string, type_) => typed_number(type_, string.clone()), + + _ => unreachable!("Literal"), + } +} + +macro_rules! impl_code_type_for_primitive { + ($T:ty, $class_name:literal) => { + paste! { + pub struct $T; + + impl CodeType for $T { + fn type_label(&self, _oracle: &dyn CodeOracle) -> String { + $class_name.into() + } + + fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String { + render_literal(oracle, &literal) + } + } + } + }; +} + +impl_code_type_for_primitive!(BooleanCodeType, "Boolean"); +impl_code_type_for_primitive!(StringCodeType, "String"); +impl_code_type_for_primitive!(Int8CodeType, "Byte"); +impl_code_type_for_primitive!(Int16CodeType, "Short"); +impl_code_type_for_primitive!(Int32CodeType, "Int"); +impl_code_type_for_primitive!(Int64CodeType, "Long"); +impl_code_type_for_primitive!(UInt8CodeType, "UByte"); +impl_code_type_for_primitive!(UInt16CodeType, "UShort"); +impl_code_type_for_primitive!(UInt32CodeType, "UInt"); +impl_code_type_for_primitive!(UInt64CodeType, "ULong"); +impl_code_type_for_primitive!(Float32CodeType, "Float"); +impl_code_type_for_primitive!(Float64CodeType, "Double"); diff --git a/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/record.rs b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/record.rs new file mode 100644 index 00000000..820a4b06 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/gen_kotlin_multiplatform/record.rs @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use uniffi_bindgen::backend::{CodeOracle, CodeType, Literal}; + +pub struct RecordCodeType { + id: String, +} + +impl RecordCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for RecordCodeType { + fn type_label(&self, oracle: &dyn CodeOracle) -> String { + oracle.class_name(&self.id) + } + + fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String { + unreachable!(); + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/lib.rs b/uniffi-kotlin-multiplatform-bindings/src/lib.rs new file mode 100644 index 00000000..38a65e91 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/lib.rs @@ -0,0 +1,159 @@ +use std::collections::HashMap; +use std::{fs}; +use std::fs::File; +use std::io::Write; + +use anyhow::Result; +use camino::{Utf8Path, Utf8PathBuf}; +use include_dir::{include_dir, Dir}; +use serde::{Deserialize, Serialize}; +use uniffi_bindgen::backend::TemplateExpression; +use uniffi_bindgen::{BindingGenerator, BindingGeneratorConfig, ComponentInterface, macro_metadata}; + +pub use gen_kotlin_multiplatform::generate_bindings; + +pub mod gen_kotlin_multiplatform; + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Config { + package_name: Option, + cdylib_name: Option, + #[serde(default)] + custom_types: HashMap, + #[serde(default)] + external_packages: HashMap, + library_file: Option +} + +// impl<'de> Deserialize<'de> for Config { +// fn deserialize(_deserializer: D) -> Result +// where D: serde::Deserializer<'de>, +// { +// _deserializer.deserialize_any() +// } +// } + +impl Config { + pub fn package_name(&self) -> String { + if let Some(package_name) = &self.package_name { + package_name.clone() + } else { + "uniffi".into() + } + } + + pub fn cdylib_name(&self) -> String { + if let Some(cdylib_name) = &self.cdylib_name { + cdylib_name.clone() + } else { + "uniffi".into() + } + } +} + +impl BindingGeneratorConfig for Config { + fn get_entry_from_bindings_table(_bindings: &toml::value::Value) -> Option { + if let Some(table) = _bindings.as_table() { + table.get("kotlin-native").map(|v| v.clone()) + } else { + None + } + } + + fn get_config_defaults(ci: &ComponentInterface) -> Vec<(String, toml::value::Value)> { + vec![ + ("package_name".to_string(), toml::value::Value::String(ci.namespace().to_string())), + ("cdylib_name".to_string(), toml::value::Value::String(ci.namespace().to_string())), + ] + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CustomTypeConfig { + imports: Option>, + type_name: Option, + into_custom: TemplateExpression, + from_custom: TemplateExpression, +} + +pub struct KotlinMultiplatformBindings { + common: HashMap, + jvm: HashMap, + native: HashMap, + header: String, +} + +pub struct KotlinBindingGenerator {} + +impl BindingGenerator for KotlinBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: ComponentInterface, + config: Self::Config, + out_dir: &Utf8Path, + ) -> Result<()> { + let mut ci = ci; + + if let Some(library_file) = &config.library_file { + + let target_triple = if cfg!(all(target_os = "macos", target_arch = "x86_64")) { + "x86_64-apple-darwin" + } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) { + "aarch64-apple-darwin" + } else if cfg!(all(target_os = "linux", target_arch = "x86_64")) { + "x86_64-unknown-linux-gnu" + } else { + "unknown-target-triple" + }; + + if target_triple != String::from("unknown-target-triple"){ + let effective_library_file = library_file.to_string().replace("", target_triple); + macro_metadata::add_to_ci_from_library(&mut ci, Utf8Path::new(&effective_library_file))?; + }else{ + let effective_library_file = library_file.to_string(); + macro_metadata::add_to_ci_from_library(&mut ci, Utf8Path::new(&effective_library_file))?; + } + } + + let bindings = generate_bindings(&config, &ci)?; + + create_target(&config, include_dir!("uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/commonMain/kotlin"), out_dir, "commonMain", bindings.common); + create_target(&config, include_dir!("uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/jvmMain/kotlin"), out_dir, "jvmMain", bindings.jvm); + create_target(&config, include_dir!("uniffi-kotlin-multiplatform-bindings/kotlin-uniffi-base/src/nativeMain/kotlin"), out_dir, "nativeMain", bindings.native); + + create_cinterop(&ci, out_dir, bindings.header); + + Ok(()) + } +} + +fn create_target(config: &Config, base_dir: Dir, out_dir: &Utf8Path, name: &str, files: HashMap) { + let mut all_files: HashMap = HashMap::new(); + for base_file in base_dir.files() { + let file_name = base_file.path().file_name().unwrap().to_str().unwrap().to_string(); + let file_content = base_file.contents_utf8().unwrap().to_string(); + all_files.insert(file_name, file_content); + } + all_files.extend(files); + + let package_path: Utf8PathBuf = config.package_name().split(".").collect(); + let dst_dir = Utf8PathBuf::from(out_dir).join(&name).join("kotlin").join(package_path); + fs::create_dir_all(&dst_dir).unwrap(); + for (file_name, file_content) in all_files { + let file_path = Utf8PathBuf::from(&dst_dir).join(file_name); + let mut f = File::create(&file_path).unwrap(); + writeln!(f, "package {}", config.package_name()).unwrap(); + writeln!(f, "").unwrap(); + write!(f, "{}", file_content).unwrap(); + } +} + +fn create_cinterop(ci: &ComponentInterface, out_dir: &Utf8Path, content: String) { + let dst_dir = Utf8PathBuf::from(out_dir).join("nativeInterop").join("cinterop").join("headers").join(ci.namespace()); + fs::create_dir_all(&dst_dir).unwrap(); + let file_path = Utf8PathBuf::from(dst_dir).join(format!("{}.h", ci.namespace())); + let mut f = File::create(&file_path).unwrap(); + write!(f, "{}", content).unwrap(); +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/AsyncTypesTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/AsyncTypesTemplate.kt.j2 new file mode 100644 index 00000000..79783574 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/AsyncTypesTemplate.kt.j2 @@ -0,0 +1,35 @@ +// Async return type handlers + +import kotlin.coroutines.Continuation + +// FFI type for callback handlers +{%- for callback_param in ci.iter_future_callback_params() %} +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallback{{ callback_param.canonical_name() }} +{%- endfor %} + +// Callback handlers for an async call. These are invoked by Rust when the future is ready. +// They lift the return value or error and resume the suspended function. +{%- for result_type in ci.iter_async_result_types() %} +{%- let callback_param = result_type.future_callback_param() %} +expect fun create{{ result_type|future_callback_handler }}Data(continuation: {{ result_type|future_continuation_type }}) + : {{ result_type|future_callback_handler }}Data + +data class {{ result_type|future_callback_handler }}Data( + val resultHandler: UniFfiFutureCallback{{ callback_param.canonical_name() }}, + val dropHandle: DropHandle< + {%- match result_type.return_type %} + {%- when Some(return_type) %} + {{ return_type|type_name }} + {%- when None %} + Unit + {%- endmatch %} + >, + val continuationRef: Pointer +) + +{%- endfor %} + +expect class DropHandle { + fun dropIt() +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/CallbackInterfaceTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/CallbackInterfaceTemplate.kt.j2 new file mode 100644 index 00000000..0c17463d --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/CallbackInterfaceTemplate.kt.j2 @@ -0,0 +1,103 @@ +{% import "helpers.j2" as helpers %} + +interface {{ type_name }} { + {% for meth in cbi.methods() -%} + fun {{ meth.name()|fn_name }}({% call helpers::arg_list_decl(meth) %}) + {%- match meth.return_type() -%} + {%- when Some with (return_type) %}: {{ return_type|type_name -}} + {%- else -%} + {%- endmatch %} + {% endfor %} +} + +object {{ foreign_callback_name }} { + @Suppress("TooGenericExceptionCaught") + fun invoke(handle: Handle, method: Int, argsData: UBytePointer, argsLen: Int, outBuf: RustBufferPointer): Int { + val cb = {{ ffi_converter_name }}.lift(handle) + return when (method) { + IDX_CALLBACK_FREE -> { + {{ ffi_converter_name }}.drop(handle) + UNIFFI_CALLBACK_SUCCESS + } + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} + {{ loop.index }} -> { + try { + {%- match meth.throws_type() %} + {%- when Some(error_type) %} + try { + val buffer = this.{{ method_name }}(cb, argsData, argsLen) + outBuf.setValue(buffer) + UNIFFI_CALLBACK_SUCCESS + } catch (e: {{ error_type|type_name }}) { + val buffer = {{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e) + outBuf.setValue(buffer) + UNIFFI_CALLBACK_ERROR + } + {%- else %} + val buffer = this.{{ method_name }}(cb, argsData, argsLen) + // Success + outBuf.setValue(buffer) + UNIFFI_CALLBACK_SUCCESS + {%- endmatch %} + } catch (e: Throwable) { + try { + outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString())) + } catch (e: Throwable) { + } + UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + } + {% endfor %} + else -> { + try { + outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index")) + } catch (e: Throwable) { + } + UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + } + } + + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name %} + private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, argsData: UBytePointer, argsLen: Int): RustBuffer { + {#- Unpacking args from the RustBuffer #} + {%- if meth.arguments().len() != 0 -%} + {#- Calling the concrete callback object #} + val source = argsData.asSource(argsLen.toLong()) + return kotlinCallbackInterface.{{ meth.name()|fn_name }}( + {% for arg in meth.arguments() -%} + {{ arg|read_fn }}(source) + {%- if !loop.last %}, {% endif %} + {% endfor -%} + ) + {% else %} + kotlinCallbackInterface.{{ meth.name()|fn_name }}() + {% endif -%} + + {#- Packing up the return value into a RustBuffer #} + {%- match meth.return_type() -%} + {%- when Some with (return_type) -%} + .let { + {{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(it) + } + {%- else -%} + .let { emptyRustBuffer() } + {% endmatch -%} + } + {% endfor %} +} + +object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>() { + // prevent the callback from being GC'ed + private val foreignCallback = {{ foreign_callback_name }}.toForeignCallback() + + override fun register(lib: UniFFILib) { + rustCall() { status -> + lib.{{ cbi.ffi_init_callback().name() }}(foreignCallback, status) + } + } +} + +expect fun {{ foreign_callback_name }}.toForeignCallback() : ForeignCallback diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/CustomTypeTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/CustomTypeTemplate.kt.j2 new file mode 100644 index 00000000..cf230f2b --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/CustomTypeTemplate.kt.j2 @@ -0,0 +1,54 @@ +{%- match config.custom_types.get(name.as_str()) %} +{%- when None %} +{#- Define the type using typealiases to the builtin #} + +typealias {{ name }} = {{ builtin|type_name }} +typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }} + +{%- when Some with (config) %} + +{%- let ffi_type_name=builtin.ffi_type().borrow()|ffi_type_name %} + +{# When the config specifies a different type name, create a typealias for it #} +{%- match config.type_name %} +{%- when Some(concrete_type_name) %} + +typealias {{ name }} = {{ concrete_type_name }} +{%- else %} +{%- endmatch %} + +{%- match config.imports %} +{%- when Some(imports) %} +{%- for import_name in imports %} +import {{ import_name }} +{%- endfor %} +{%- else %} +{%- endmatch %} + +object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_name }}> { + override fun lift(value: {{ ffi_type_name }}): {{ name }} { + val builtinValue = {{ builtin|lift_fn }}(value) + return {{ config.into_custom.render("builtinValue") }} + } + + override fun lower(value: {{ name }}): {{ ffi_type_name }} { + val builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|lower_fn }}(builtinValue) + } + + override fun read(source: NoCopySource): {{ name }} { + val builtinValue = {{ builtin|read_fn }}(source) + return {{ config.into_custom.render("builtinValue") }} + } + + override fun allocationSize(value: {{ name }}): Int { + val builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|allocation_size_fn }}(builtinValue) + } + + override fun write(value: {{ name }}, buf: Buffer) { + val builtinValue = {{ config.from_custom.render("value") }} + {{ builtin|write_fn }}(builtinValue, buf) + } +} +{%- endmatch %} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/EnumTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/EnumTemplate.kt.j2 new file mode 100644 index 00000000..b06cae95 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/EnumTemplate.kt.j2 @@ -0,0 +1,100 @@ +{% import "helpers.j2" as helpers %} +import okio.Buffer + +{%- if e.is_flat() %} + +enum class {{ type_name }} { + {% for variant in e.variants() -%} + {{ variant.name()|enum_variant }}{% if loop.last %};{% else %},{% endif %} + {%- endfor %} +} + +object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { + override fun read(source: NoCopySource) = try { + {{ type_name }}.values()[source.readInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: {{ type_name }}) = 4 + + override fun write(value: {{ type_name }}, buf: Buffer) { + buf.writeInt(value.ordinal + 1) + } +} + +{% else %} + +sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} { + {% for variant in e.variants() -%} + {% if !variant.has_fields() -%} + object {{ variant.name()|class_name }} : {{ type_name }}() + {% else -%} + data class {{ variant.name()|class_name }}( + {% for field in variant.fields() -%} + val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %} + {% endfor -%} + ) : {{ type_name }}() + {%- endif %} + {% endfor %} + + {% if contains_object_references %} + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + when(this) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant.name()|class_name }} -> { + {%- if variant.has_fields() %} + {% call helpers::destroy_fields(variant) %} + {% else -%} + {%- endif %} + } + {%- endfor %} + } + } + {% endif %} +} + +object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}>{ + override fun read(source: NoCopySource): {{ type_name }} { + return when(source.readInt()) { + {%- for variant in e.variants() %} + {{ loop.index }} -> {{ type_name }}.{{ variant.name()|class_name }}{% if variant.has_fields() %}( + {% for field in variant.fields() -%} + {{ field|read_fn }}(source), + {% endfor -%} + ){%- endif -%} + {%- endfor %} + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: {{ type_name }}) = when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant.name()|class_name }} -> { + ( + 4 + {%- for field in variant.fields() %} + + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + ) + } + {%- endfor %} + } + + override fun write(value: {{ type_name }}, buf: Buffer) { + when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant.name()|class_name }} -> { + buf.writeInt({{ loop.index }}) + {%- for field in variant.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + Unit + } + {%- endfor %} + } + } +} + +{% endif %} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/ErrorTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/ErrorTemplate.kt.j2 new file mode 100644 index 00000000..ea76e09f --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/ErrorTemplate.kt.j2 @@ -0,0 +1,105 @@ +{% import "helpers.j2" as helpers %} +import okio.Buffer + +{% if e.is_flat() %} +sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} { + {% for variant in e.variants() -%} + class {{ variant.name()|exception_name }}(message: String) : {{ type_name }}(message) + {% endfor %} + + companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + override fun lift(errorBuffer: RustBuffer): {{ type_name }} = {{ e|lift_fn }}(errorBuffer) + } +} +{%- else %} +sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} { + {% for variant in e.variants() -%} + {%- let variant_name = variant.name()|exception_name %} + class {{ variant_name }}( + {% for field in variant.fields() -%} + val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %} + {% endfor -%} + ) : {{ type_name }}() { + override val message + get() = "{%- for field in variant.fields() %}{{ field.name()|var_name|unquote }}=${ {{field.name()|var_name }} }{% if !loop.last %}, {% endif %}{% endfor %}" + } + {% endfor %} + + companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + override fun lift(errorBuffer: RustBuffer): {{ type_name }} = {{ e|lift_fn }}(errorBuffer) + } + + {% if contains_object_references %} + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + when(this) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant.name()|exception_name }} -> { + {%- if variant.has_fields() %} + {% call helpers::destroy_fields(variant) %} + {% else -%} + {%- endif %} + } + {%- endfor %} + } + } + {% endif %} +} +{%- endif %} + +object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}> { + override fun read(source: NoCopySource): {{ type_name }} { + {% if e.is_flat() %} + return when(source.readInt()) { + {%- for variant in e.variants() %} + {{ loop.index }} -> {{ type_name }}.{{ variant.name()|exception_name }}({{ TypeIdentifier::String.borrow()|read_fn }}(source)) + {%- endfor %} + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + {% else %} + + return when(source.readInt()) { + {%- for variant in e.variants() %} + {{ loop.index }} -> {{ type_name }}.{{ variant.name()|exception_name }}({% if variant.has_fields() %} + {% for field in variant.fields() -%} + {{ field|read_fn }}(source), + {% endfor -%} + {%- endif -%}) + {%- endfor %} + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + {%- endif %} + } + + override fun allocationSize(value: {{ type_name }}): Int { + {%- if e.is_flat() %} + return 4 + {%- else %} + return when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant.name()|exception_name }} -> ( + 4 + {%- for field in variant.fields() %} + + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + ) + {%- endfor %} + } + {%- endif %} + } + + override fun write(value: {{ type_name }}, buf: Buffer) { + when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant.name()|exception_name }} -> { + buf.writeInt({{ loop.index }}) + {%- for field in variant.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + Unit + } + {%- endfor %} + } + } + +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/FfiConverterForeignExecutor.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/FfiConverterForeignExecutor.kt.j2 new file mode 100644 index 00000000..a936ae22 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/FfiConverterForeignExecutor.kt.j2 @@ -0,0 +1,35 @@ +import kotlinx.coroutines.CoroutineScope +import okio.Buffer + +object FfiConverterForeignExecutor: FfiConverter { + internal val handleMap = UniFfiHandleMap() + internal val foreignExecutorCallback = createUniFfiForeignExecutorCallback() + + internal fun drop(handle: ULong) { + handleMap.remove(handle) + } + + internal fun register(lib: UniFFILib) { + lib.uniffi_foreign_executor_callback_set(foreignExecutorCallback) + } + + // Number of live handles, exposed so we can test the memory management + public fun handleCount() : Int { + return handleMap.size + } + + override fun allocationSize(value: CoroutineScope) = ULong.SIZE_BYTES + + override fun lift(value: ULong): CoroutineScope { + return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift") + } + + override fun read(source: NoCopySource): CoroutineScope = TODO("unused") + + override fun lower(value: CoroutineScope): ULong { + return handleMap.insert(value) + } + + override fun write(value: CoroutineScope, buf: Buffer) = TODO("unused") + +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/MapTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/MapTemplate.kt.j2 new file mode 100644 index 00000000..b025f1a8 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/MapTemplate.kt.j2 @@ -0,0 +1,33 @@ +import okio.Buffer + +{%- let key_type_name = key_type|type_name %} +{%- let value_type_name = value_type|type_name %} +object {{ ffi_converter_name }}: FfiConverterRustBuffer> { + override fun read(source: NoCopySource): Map<{{ key_type_name }}, {{ value_type_name }}> { + val items : MutableMap<{{ key_type_name }}, {{ value_type_name }}> = mutableMapOf() + val len = source.readInt() + repeat(len) { + val k = {{ key_type|read_fn }}(source) + val v = {{ value_type|read_fn }}(source) + items[k] = v + } + return items + } + + override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int { + val spaceForMapSize = 4 + val spaceForChildren = value.map { (k, v) -> + {{ key_type|allocation_size_fn }}(k) + + {{ value_type|allocation_size_fn }}(v) + }.sum() + return spaceForMapSize + spaceForChildren + } + + override fun write(value: Map<{{ key_type_name }}, {{ value_type_name }}>, buf: Buffer) { + buf.writeInt(value.size) + value.forEach { (k, v) -> + {{ key_type|write_fn }}(k, buf) + {{ value_type|write_fn }}(v, buf) + } + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/ObjectTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/ObjectTemplate.kt.j2 new file mode 100644 index 00000000..01c48c80 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/ObjectTemplate.kt.j2 @@ -0,0 +1,134 @@ +{% import "helpers.j2" as helpers %} +import kotlinx.coroutines.coroutineScope +import okio.Buffer +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +interface {{ type_name }}Interface { + {% for meth in obj.methods() -%} + {%- match meth.throws_type() -%} + {%- when Some with (throwable) %} + @Throws({{ throwable|type_name }}::class{%- if meth.is_async() -%}, CancellationException::class{%- endif -%}) + {%- else -%} + {%- endmatch %} + {% if meth.is_async() -%} + suspend fun {{ meth.name()|fn_name }}({% call helpers::arg_list_decl(meth) %}) + {%- else -%} + fun {{ meth.name()|fn_name }}({% call helpers::arg_list_decl(meth) %}) + {%- endif %} + {%- match meth.return_type() -%} + {%- when Some with (return_type) %}: {{ return_type|type_name -}} + {%- else -%} + {%- endmatch %} + {% endfor %} +} + +class {{ type_name }}( + pointer: Pointer +) : FFIObject(pointer), {{ type_name }}Interface { + + {%- match obj.primary_constructor() %} + {%- when Some with (cons) %} + constructor({% call helpers::arg_list_decl(cons) -%}) : + this({% call helpers::to_ffi_call(cons) %}) + {%- when None %} + {%- endmatch %} + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.{{ obj.ffi_object_free().name() }}(this.pointer, status) + } + } + + {% for meth in obj.methods() -%} + {%- match meth.throws_type() -%} + {%- when Some with (throwable) %} + @Throws({{ throwable|type_name }}::class{%- if meth.is_async() -%}, CancellationException::class{%- endif -%}) + {%- else -%} + {%- endmatch %} + {%- if meth.is_async() %} + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun {{ meth.name()|fn_name }}({%- call helpers::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name }}{% when None %}{%- endmatch %} { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: {{ func.result_type().borrow()|future_callback_handler }}Data? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = create{{ meth.result_type().borrow()|future_callback_handler }}Data(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.{{ meth.ffi_func().name() }}( + thisPtr, + // FIXME create macro that handles the comma + {% call helpers::_arg_list_ffi_call(meth) %}{% if func.arguments().len() > 0 %},{% endif %} + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + {%- else -%} + {%- match meth.return_type() -%} + + {%- when Some with (return_type) -%} + override fun {{ meth.name()|fn_name }}({% call helpers::arg_list_protocol(meth) %}): {{ return_type|type_name }} = + callWithPointer { + {%- call helpers::to_ffi_call_with_prefix("it", meth) %} + }.let { + {{ return_type|lift_fn }}(it) + } + + {%- when None -%} + override fun {{ meth.name()|fn_name }}({% call helpers::arg_list_protocol(meth) %}) = + callWithPointer { + {%- call helpers::to_ffi_call_with_prefix("it", meth) %} + } + {% endmatch %} + {% endif %} + {% endfor %} + + {% if !obj.alternate_constructors().is_empty() -%} + companion object { + {% for cons in obj.alternate_constructors() -%} + fun {{ cons.name()|fn_name }}({% call helpers::arg_list_decl(cons) %}): {{ type_name }} = + {{ type_name }}({% call helpers::to_ffi_call(cons) %}) + {% endfor %} + } + {% endif %} +} + +object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> { + override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): {{ type_name }} { + return {{ type_name }}(value) + } + + override fun read(source: NoCopySource): {{ type_name }} { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: {{ type_name }}) = 8 + + override fun write(value: {{ type_name }}, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/OptionalTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/OptionalTemplate.kt.j2 new file mode 100644 index 00000000..1fbfdc84 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/OptionalTemplate.kt.j2 @@ -0,0 +1,27 @@ +import okio.Buffer + +object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_name }}?> { + override fun read(source: NoCopySource): {{ inner_type_name }}? { + if (source.readByte().toInt() == 0) { + return null + } + return {{ inner_type|read_fn }}(source) + } + + override fun allocationSize(value: {{ inner_type_name }}?): Int { + if (value == null) { + return 1 + } else { + return 1 + {{ inner_type|allocation_size_fn }}(value) + } + } + + override fun write(value: {{ inner_type_name }}?, buf: Buffer) { + if (value == null) { + buf.writeByte(0) + } else { + buf.writeByte(1) + {{ inner_type|write_fn }}(value, buf) + } + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/RecordTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/RecordTemplate.kt.j2 new file mode 100644 index 00000000..7e45779c --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/RecordTemplate.kt.j2 @@ -0,0 +1,42 @@ +{% import "helpers.j2" as helpers %} +import okio.Buffer + +data class {{ type_name }} ( + {%- for field in rec.fields() %} + var {{ field.name()|var_name }}: {{ field|type_name -}} + {%- match field.default_value() %} + {%- when Some with(literal) %} = {{ literal|render_literal(field) }} + {%- else %} + {%- endmatch -%} + {% if !loop.last %}, {% endif %} + {%- endfor %} +) {% if contains_object_references %}: Disposable {% endif %}{ + {% if contains_object_references %} + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + {% call helpers::destroy_fields(rec) %} + } + {% endif %} +} + +object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { + override fun read(source: NoCopySource): {{ type_name }} { + return {{ type_name }}( + {%- for field in rec.fields() %} + {{ field|read_fn }}(source), + {%- endfor %} + ) + } + + override fun allocationSize(value: {{ type_name }}) = ( + {%- for field in rec.fields() %} + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%} + {%- endfor %} + ) + + override fun write(value: {{ type_name }}, buf: Buffer) { + {%- for field in rec.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/SequenceTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/SequenceTemplate.kt.j2 new file mode 100644 index 00000000..1c87be4f --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/SequenceTemplate.kt.j2 @@ -0,0 +1,25 @@ +import okio.Buffer + +{%- let inner_type_name = inner_type|type_name %} + +object {{ ffi_converter_name }}: FfiConverterRustBuffer> { + override fun read(source: NoCopySource): List<{{ inner_type_name }}> { + val len = source.readInt() + return List<{{ inner_type_name }}>(len) { + {{ inner_type|read_fn }}(source) + } + } + + override fun allocationSize(value: List<{{ inner_type_name }}>): Int { + val sizeForLength = 4 + val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List<{{ inner_type_name }}>, buf: Buffer) { + buf.writeInt(value.size) + value.forEach { + {{ inner_type|write_fn }}(it, buf) + } + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/TopLevelFunctionsTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/TopLevelFunctionsTemplate.kt.j2 new file mode 100644 index 00000000..53865485 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/TopLevelFunctionsTemplate.kt.j2 @@ -0,0 +1,73 @@ +{% import "helpers.j2" as helpers %} + +{# todo only import if async funs are present #} +import kotlinx.coroutines.coroutineScope +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +{%- for func in ci.function_definitions() %} + +{%- if func.is_async() %} +{%- match func.throws_type() -%} +{%- when Some with (throwable) %} +@Throws({{ throwable|type_name }}::class, CancellationException::class) +{%- else -%} +{%- endmatch %} + +@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") +suspend fun {{ func.name()|fn_name }}({%- call helpers::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name }}{% when None %}{%- endmatch %} { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: {{ func.result_type().borrow()|future_callback_handler }}Data? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = create{{ func.result_type().borrow()|future_callback_handler }}Data(continuation) + callbackDataHolder = callbackData + rustCall { status -> + UniFFILib.{{ func.ffi_func().name() }}( + {% call helpers::_arg_list_ffi_call(func) %}{% if func.arguments().len() > 0 %},{% endif %} + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } +} + +{%- else %} {# if func.is_async() #} + +{%- match func.throws_type() -%} +{%- when Some with (throwable) %} +@Throws({{ throwable|type_name }}::class) +{%- else -%} +{%- endmatch %} +{%- match func.return_type() -%} +{%- when Some with (return_type) %} + +fun {{ func.name()|fn_name }}({%- call helpers::arg_list_decl(func) -%}): {{ return_type|type_name }} { + return {{ return_type|lift_fn }}({% call helpers::to_ffi_call(func) %}) +} + +{% when None %} + +fun {{ func.name()|fn_name }}({% call helpers::arg_list_decl(func) %}) = + {% call helpers::to_ffi_call(func) %} +{% endmatch %} +{%- endif %} {# if func.is_async() #} + +{%- endfor %} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/UniFFILibTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/UniFFILibTemplate.kt.j2 new file mode 100644 index 00000000..c40338fc --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/UniFFILibTemplate.kt.j2 @@ -0,0 +1,10 @@ +{% import "helpers.j2" as helpers %} + +expect object UniFFILib { + {% for func in ci.iter_ffi_function_definitions() -%} + fun {{ func.name() }}( + {%- call helpers::arg_list_ffi_decl(func) %} + ){%- match func.return_type() -%}{%- when Some with (type_) %}: {{ type_.borrow()|ffi_type_name }}{% when None %}: Unit{% endmatch %} + + {% endfor %} +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/common/UniFfiForeignExecutorCallback.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/common/UniFfiForeignExecutorCallback.kt.j2 new file mode 100644 index 00000000..574a34f8 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/common/UniFfiForeignExecutorCallback.kt.j2 @@ -0,0 +1,8 @@ +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiForeignExecutorCallback + +expect fun createUniFfiForeignExecutorCallback(): UniFfiForeignExecutorCallback diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/headers/BridgingHeaderTemplate.h.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/headers/BridgingHeaderTemplate.h.j2 new file mode 100644 index 00000000..ca55a36c --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/headers/BridgingHeaderTemplate.h.j2 @@ -0,0 +1,82 @@ +{% import "helpers.j2" as helpers %} + +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + int32_t capacity; + int32_t len; + uint8_t *_Nullable data; +} RustBuffer; + +// *_Nonnull is ignored by cinterop +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull); + +// Task defined in Rust that the foreign language executes +// TODO why would that be nullable? +typedef void (*UniFfiRustTaskCallback)(const void * _Nullable); + +// Callback to execute Rust tasks using a foreign language Task +// +// Args: +// executor: ForeignExecutor lowered into a size_t value +// delay: Delay in MS +// task: UniFfiRustTaskCallback to call +// task_data: data to pass the task callback +typedef void (*UniFfiForeignExecutorCallback)(size_t, uint32_t, UniFfiRustTaskCallback _Nullable, const void * _Nullable); + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H + +{%- for ffi_type in ci.iter_future_callback_params() %} +typedef void (*UniFfiFutureCallback{{ ffi_type.canonical_name() }})(const void * _Nonnull, {{ ffi_type|ffi_header_type_name }}, RustCallStatus); +{%- endfor %} + +// Scaffolding functions +{%- for func in ci.iter_ffi_function_definitions() %} +{% match func.return_type() -%}{%- when Some with (type_) %}{{ type_|ffi_header_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}( + {%- if func.arguments().len() > 0 %} + {%- for arg in func.arguments() %} + {{- arg.type_().borrow()|ffi_header_type_name }} {{ arg.name() -}}{% if !loop.last || func.has_rust_call_status_arg() %}, {% endif %} + {%- endfor %} + {%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{% endif %} + {%- else %} + {%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{%- else %}void{% endif %} + {% endif %} +); +{%- endfor %} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/helpers.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/helpers.j2 new file mode 100644 index 00000000..252168a7 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/helpers.j2 @@ -0,0 +1,81 @@ +{%- macro to_ffi_call(func) -%} + {%- match func.throws_type() %} + {%- when Some with (e) %} + rustCallWithError({{ e|type_name}}) + {%- else %} + rustCall() + {%- endmatch %} { _status -> + UniFFILib.{{ func.ffi_func().name() }}({% call _arg_list_ffi_call(func) -%}{% if func.arguments().len() > 0 %},{% endif %} _status) +} +{%- endmacro -%} +{%- macro to_ffi_call_with_prefix(prefix, func) %} + {%- match func.throws_type() %} + {%- when Some with (e) %} + rustCallWithError({{ e|type_name}}) + {%- else %} + rustCall() + {%- endmatch %} { _status -> + UniFFILib.{{ func.ffi_func().name() }}( + {{- prefix }}, {% call _arg_list_ffi_call(func) %}{% if func.arguments().len() > 0 %}, {% endif %} _status) +} +{%- endmacro %} +{%- macro _arg_list_ffi_call(func) %} + {%- for arg in func.arguments() %} + {{- arg|lower_fn }}({{ arg.name()|var_name }}) + {%- if !loop.last %}, {% endif %} + {%- endfor %} +{%- endmacro -%} +{#- +// Arglist as used in kotlin declarations of methods, functions and constructors. +// Note the var_name and type_name filters. +-#} +{% macro arg_list_decl(func) %} + {%- for arg in func.arguments() -%} + {{ arg.name()|var_name }}: {{ arg|type_name -}} + {%- match arg.default_value() %} + {%- when Some with(literal) %} = {{ literal|render_literal(arg) }} + {%- else %} + {%- endmatch %} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} +{% macro arg_list_protocol(func) %} + {%- for arg in func.arguments() -%} + {{ arg.name()|var_name }}: {{ arg|type_name -}} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} +{#- +// Arglist as used in the UniFFILib function declations. +// Note unfiltered name but ffi_type_name filters. +-#} +{%- macro arg_list_ffi_decl(func) %} + {%- for arg in func.arguments() %} + {{- arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name -}}, + {%- endfor %} + {%- if func.has_rust_call_status_arg() %}_uniffi_out_err: RustCallStatus, {% endif %} +{%- endmacro -%} +{%- macro arg_list_ffi_call(func) %} + {%- for arg in func.arguments() %} + {{- arg.name()|var_name }}, + {%- endfor %} + {%- if func.has_rust_call_status_arg() %}_uniffi_out_err{% endif %} +{%- endmacro -%} +{%- macro arg_list_ffi_header_decl(func) %} + {%- for arg in func.arguments() %} + {{- arg.type_().borrow()|ffi_header_type_name }} {{ arg.name() -}}, + {%- endfor %} + RustCallStatus *_Nonnull out_status +{%- endmacro -%} +// Macro for destroying fields +{%- macro destroy_fields(member) %} + Disposable.destroy( + {%- for field in member.fields() %} + this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%} + {% endfor -%}) +{%- endmacro -%} +{%- macro ffi_function_definition(func) %} +fun {{ func.name()|fn_name }}( + {%- call arg_list_ffi_decl(func) %} +){%- match func.return_type() -%}{%- when Some with (type_) %}: {{ type_|ffi_type_name }}{% when None %}: Unit{% endmatch %} +{% endmacro %} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/AsyncTypesTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/AsyncTypesTemplate.kt.j2 new file mode 100644 index 00000000..e5baa546 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/AsyncTypesTemplate.kt.j2 @@ -0,0 +1,60 @@ +// Async return type handlers + +{# add imports that we use #} +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +{# We use these in the generated functions, which don't have access to add_import() -- might as well add it here #} +import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.coroutineScope + +// FFI type for callback handlers + +{%- for callback_param in ci.iter_future_callback_params() %} +actual class UniFfiFutureCallback{{ callback_param.canonical_name() }}(private val inner: (ULong, {{ callback_param|ffi_type_name|nullify_pointer }}, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: {{ callback_param|ffi_type_name|nullify_pointer }}, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +{%- endfor %} + +// Callback handlers for an async call. These are invoked by Rust when the future is ready. They +// lift the return value or error and resume the suspended function. +{%- for result_type in ci.iter_async_result_types() %} +{%- let callback_param = result_type.future_callback_param() %} +actual fun create{{ result_type|future_callback_handler }}Data(continuation: {{ result_type|future_continuation_type }}) + : {{ result_type|future_callback_handler }}Data = + {{ result_type|future_callback_handler }}Data( + UniFfiFutureCallback{{ callback_param.canonical_name() }} { _: ULong, returnValue: {{ callback_param|ffi_type_name|nullify_pointer }}, callStatus: RustCallStatusByValue -> + try { + checkCallStatus({{ result_type|error_handler }}, callStatus) + {%- match result_type.return_type %} + {%- when Some(return_type) %} + continuation.resume({{ return_type|lift_fn }}(returnValue!!)) + {%- when None %} + continuation.resume(Unit) + {%- endmatch %} + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) + +{%- endfor %} + +actual class DropHandle { + actual fun dropIt() { + // no-op on the JVM + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/CallbackInterfaceTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/CallbackInterfaceTemplate.kt.j2 new file mode 100644 index 00000000..9a1f3e28 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/CallbackInterfaceTemplate.kt.j2 @@ -0,0 +1,2 @@ +actual fun {{ foreign_callback_name }}.toForeignCallback() : ForeignCallback = + NativeCallback(this::invoke) \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/RustBufferTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/RustBufferTemplate.kt.j2 new file mode 100644 index 00000000..146b8d43 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/RustBufferTemplate.kt.j2 @@ -0,0 +1,51 @@ +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.ptr.ByReference +import okio.Buffer + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBufferStructure : Structure() { + @JvmField + var capacity: Int = 0 + + @JvmField + var len: Int = 0 + + @JvmField + var data: Pointer? = null +} + +actual class RustBuffer : RustBufferStructure(), Structure.ByValue + +actual class RustBufferPointer : ByReference(16) { + fun setValueInternal(value: RustBuffer) { + pointer.setInt(0, value.capacity) + pointer.setInt(4, value.len) + pointer.setPointer(8, value.data) + } +} + +actual fun RustBuffer.asSource(): NoCopySource = requireNotNull(data).asSource(len.toLong()) + +actual val RustBuffer.dataSize: Int + get() = len + +actual fun RustBuffer.free() = + rustCall { status -> + UniFFILib.{{ ci.ffi_rustbuffer_free().name() }}(this, status) + } + +actual fun allocRustBuffer(buffer: Buffer): RustBuffer = + rustCall { status -> + val size = buffer.size + UniFFILib.{{ ci.ffi_rustbuffer_alloc().name() }}(size.toInt(), status).also { rustBuffer: RustBuffer -> + val data = rustBuffer.data + ?: throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + rustBuffer.writeField("len", size.toInt()) + buffer.read(data.getByteBuffer(0, size)) + } + } + +actual fun RustBufferPointer.setValue(value: RustBuffer) = setValueInternal(value) + +actual fun emptyRustBuffer(): RustBuffer = RustBuffer() diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/UniFFILibTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/UniFFILibTemplate.kt.j2 new file mode 100644 index 00000000..adf66e78 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/UniFFILibTemplate.kt.j2 @@ -0,0 +1,34 @@ +{% import "helpers.j2" as helpers %} + +import com.sun.jna.Library +import com.sun.jna.Native + +@Synchronized +private fun findLibraryName(): String { + val componentName = "{{ ci.namespace() }}" + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "{{ config.cdylib_name() }}" +} + +actual object UniFFILib : Library { + init { + Native.register(UniFFILib::class.java, findLibraryName()) + {% let initialization_fns = self.initialization_fns() %} + {%- if !initialization_fns.is_empty() -%} + {% for fn in initialization_fns -%} + {{ fn }}(this) + {% endfor -%} + {% endif %} + } + + {% for func in ci.iter_ffi_function_definitions() -%} + @JvmName("{{ func.name() }}") + actual external fun {{ func.name() }}( + {%- call helpers::arg_list_ffi_decl(func) %} + ){%- match func.return_type() -%}{%- when Some with (type_) %}: {{ type_.borrow()|ffi_type_name }}{% when None %}: Unit{% endmatch %} + + {% endfor %} +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/UniFfiForeignExecutorCallback.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/UniFfiForeignExecutorCallback.kt.j2 new file mode 100644 index 00000000..6b4d37c7 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/jvm/UniFfiForeignExecutorCallback.kt.j2 @@ -0,0 +1,54 @@ +import com.sun.jna.Callback +import com.sun.jna.Pointer +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +// TODO unify this code with the native source set? See the comment in the corresponding file +interface UniFfiRustTaskCallback: Callback { + fun invoke(rustTaskData: Pointer?) +} + +class UniFfiForeignExecutorCallbackImpl( + private val invokeImpl: ( + handle: ULong, + delayMs: Int, + rustTask: UniFfiRustTaskCallback?, + rustTaskData: Pointer? + ) -> Unit +) : Callback { + fun invoke( + handle: ULong, + delayMs: Int, + rustTask: UniFfiRustTaskCallback?, + rustTaskData: Pointer? + ) = invokeImpl(handle, delayMs, rustTask, rustTaskData) +} + +fun createUniFfiForeignExecutorCallbackImpl( + block: (handle: ULong, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) -> Unit +): UniFfiForeignExecutorCallback = UniFfiForeignExecutorCallbackImpl(block) + +actual typealias UniFfiForeignExecutorCallback = UniFfiForeignExecutorCallbackImpl + +fun invokeUniFfiForeignExecutorCallback( + handle: ULong, + delayMs: Int, + rustTask: UniFfiRustTaskCallback?, + rustTaskData: Pointer? +) { + if (rustTask == null) { + FfiConverterForeignExecutor.drop(handle) + } else { + val coroutineScope = FfiConverterForeignExecutor.lift(handle) + coroutineScope.launch { + if (delayMs > 0) { + delay(delayMs.toLong()) + } + rustTask.invoke(rustTaskData) + } + } +} + +actual fun createUniFfiForeignExecutorCallback(): UniFfiForeignExecutorCallback = + createUniFfiForeignExecutorCallbackImpl(::invokeUniFfiForeignExecutorCallback) + diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/native/AsyncTypesTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/native/AsyncTypesTemplate.kt.j2 new file mode 100644 index 00000000..f5f0e8d7 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/native/AsyncTypesTemplate.kt.j2 @@ -0,0 +1,98 @@ +// Async return type handlers + +import kotlinx.cinterop.* +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.test.assertNotNull + +// For each result_type, there could be several handlers for different errors and one for the plain type without any error +// In upstream uniffi, UniFfiFutureCallback\{\{ callback_param.canonical_name() \}\} is a super interface that all the +// different handlers with and without error implement. +// This does not work well for Native where we have Pointer types typealiases for CPointer for several T +// We define a single typealias for the handler that expects no error since the pointer type is identical, anyway +{%- for callback_param in ci.iter_future_callback_params() %} +typealias UniFfiFutureCallback{{ callback_param.canonical_name() }} = CPointer Unit>> +{%- endfor %} + + +// Callback handlers for an async call. These are invoked by Rust when the future is ready. They +// lift the return value or error and resume the suspended function. +{%- for result_type in ci.iter_async_result_types() %} +{%- let callback_param = result_type.future_callback_param() %} + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias {{ result_type|future_callback_handler }} = CPointer Unit>> + +actual fun create{{ result_type|future_callback_handler }}Data(continuation: {{ result_type|future_continuation_type }}) + : {{ result_type|future_callback_handler }}Data { + val resultHandler = create{{ result_type|future_callback_handler }}() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return {{ result_type|future_callback_handler }}Data( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun create{{ result_type|future_callback_handler }}(): {{ result_type|future_callback_handler }} = + staticCFunction { callbackData: Pointer?, + {%- if result_type.return_type.is_some() %} returnValue{% else %} _{% endif %}: {{ callback_param|ffi_type_name|nullify_pointer }}, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef<{{ result_type|future_continuation_type }}>() + val continuation = stableRef.get() + try { + checkCallStatusByValue({{ result_type|error_handler }}, callStatus) + {%- match result_type.return_type %} + {%- when Some(return_type) %} + continuation.resume({{ return_type|lift_fn }}(requireNotNull(returnValue))) + {%- when None %} + continuation.resume(Unit) + {%- endmatch %} + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +{%- endfor %} + +actual class DropHandle(private val stableRef: StableRef>) { + actual fun dropIt() { + stableRef.dispose() + } +} + +private inline fun {{ config.package_name() }}.cinterop.RustCallStatus.isSuccess(): Boolean = code == 0.toByte() +private inline fun {{ config.package_name() }}.cinterop.RustCallStatus.isError(): Boolean = code == 1.toByte() +private inline fun {{ config.package_name() }}.cinterop.RustCallStatus.isPanic(): Boolean = code == 2.toByte() + +// TODO remove this crutch that is needed because on Native, RustCallStatusByValue does not extend RustCallStatus +// and "just getting a pointer to the value" apparently requires copying to a special place and *then* getting +// a pointer to that location +private fun checkCallStatusByValue(errorHandler: CallStatusErrorHandler, statusByValue: RustCallStatusByValue) { + // no need to clean up the call status since the caller received it as a c parameter by value + statusByValue.useContents { + if (this.isSuccess()) { + return + } else if (isError()) { + throw errorHandler.lift(errorBuf.readValue()) + } else if (isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + val errorBuffer = errorBuf.readValue() + if (errorBuffer.dataSize > 0) { + // TODO avoid additional copy + throw InternalException(FfiConverterString.lift(errorBuffer)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $statusByValue.code") + } + } +} + diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/native/CallbackInterfaceTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/native/CallbackInterfaceTemplate.kt.j2 new file mode 100644 index 00000000..3f66bc7a --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/native/CallbackInterfaceTemplate.kt.j2 @@ -0,0 +1,7 @@ +import kotlinx.cinterop.staticCFunction + +actual fun {{ foreign_callback_name }}.toForeignCallback() : ForeignCallback = + staticCFunction{ handle: Handle, method: Int, argsData: UBytePointer?, argLen: Int, outBuf: RustBufferPointer?-> + // *_Nonnull is ignored by cinterop + {{ foreign_callback_name }}.invoke(handle, method, requireNotNull(argsData), argLen, requireNotNull(outBuf)) + } diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/native/ForeignBytesTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/native/ForeignBytesTemplate.kt.j2 new file mode 100644 index 00000000..b4af04c6 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/native/ForeignBytesTemplate.kt.j2 @@ -0,0 +1,5 @@ +import kotlinx.cinterop.CValue + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias ForeignBytes = CValue<{{ config.package_name() }}.cinterop.ForeignBytes> \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/native/RustBufferTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/native/RustBufferTemplate.kt.j2 new file mode 100644 index 00000000..0e7e3bea --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/native/RustBufferTemplate.kt.j2 @@ -0,0 +1,46 @@ +import kotlinx.cinterop.* +import okio.Buffer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias RustBuffer = CValue<{{ config.package_name() }}.cinterop.RustBuffer> +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias RustBufferPointer = CPointer<{{ config.package_name() }}.cinterop.RustBuffer> + +actual fun RustBuffer.asSource(): NoCopySource { + val data = useContents { data } + val len = useContents { len } + return requireNotNull(data).asSource(len.toLong()) +} + +actual val RustBuffer.dataSize: Int + get() = useContents { len } + +actual fun RustBuffer.free(): Unit = + rustCall { status -> + UniFFILib.{{ ci.ffi_rustbuffer_free().name() }}(this, status) + } + +actual fun allocRustBuffer(buffer: Buffer): RustBuffer = + rustCall { status -> + val size = buffer.size + UniFFILib.{{ ci.ffi_rustbuffer_alloc().name() }}(size.toInt(), status).also { + it.useContents { + val notNullData = data + checkNotNull(notNullData) { "RustBuffer.alloc() returned null data pointer (size=${size})" } + buffer.readByteArray().forEachIndexed { index, byte -> + notNullData[index] = byte.toUByte() + } + } + } + } + +actual fun RustBufferPointer.setValue(value: RustBuffer) { + this.pointed.capacity = value.useContents { capacity } + this.pointed.len = value.useContents { len } + this.pointed.data = value.useContents { data } +} + +actual fun emptyRustBuffer(): RustBuffer { + return allocRustBuffer(Buffer()) +} diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/native/RustCallStatusTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/native/RustCallStatusTemplate.kt.j2 new file mode 100644 index 00000000..6bcc6a20 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/native/RustCallStatusTemplate.kt.j2 @@ -0,0 +1,22 @@ +import kotlinx.cinterop.* + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias RustCallStatus = CPointer<{{ config.package_name() }}.cinterop.RustCallStatus> + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias RustCallStatusByValue = CValue<{{ config.package_name() }}.cinterop.RustCallStatus> + +actual val RustCallStatus.statusCode: Byte + get() = pointed.code +actual val RustCallStatus.errorBuffer: RustBuffer + get() = pointed.errorBuf.readValue() + +actual fun withRustCallStatus(block: (RustCallStatus) -> T): T = + memScoped { + val allocated = alloc<{{ config.package_name() }}.cinterop.RustCallStatus>().ptr + block(allocated) + } + +val RustCallStatusByValue.statusCode: Byte + get() = useContents { code } diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/native/UniFFILibTemplate.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/native/UniFFILibTemplate.kt.j2 new file mode 100644 index 00000000..dc8f9ee4 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/native/UniFFILibTemplate.kt.j2 @@ -0,0 +1,22 @@ +{% import "helpers.j2" as helpers %} + +actual object UniFFILib { + init { + {% let initialization_fns = self.initialization_fns() %} + {%- if !initialization_fns.is_empty() -%} + {% for fn in initialization_fns -%} + {{ fn }}(this) + {% endfor -%} + {% endif %} + } + + {% for func in ci.iter_ffi_function_definitions() -%} + actual fun {{ func.name() }}( + {%- call helpers::arg_list_ffi_decl(func) %} + ){%- match func.return_type() -%}{%- when Some with (type_) %}: {{ type_.borrow()|ffi_type_name }}{% when None %}: Unit{% endmatch %} = + requireNotNull({{ config.package_name() }}.cinterop.{{ func.name() }}( + {%- call helpers::arg_list_ffi_call(func) %} + )) + + {% endfor %} +} \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/src/templates/native/UniFfiForeignExecutorCallback.kt.j2 b/uniffi-kotlin-multiplatform-bindings/src/templates/native/UniFfiForeignExecutorCallback.kt.j2 new file mode 100644 index 00000000..f412fc15 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/src/templates/native/UniFfiForeignExecutorCallback.kt.j2 @@ -0,0 +1,41 @@ +import kotlinx.cinterop.CFunction +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.invoke +import kotlinx.cinterop.staticCFunction +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +typealias UniFfiRustTaskCallback = CPointer Unit>> + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiForeignExecutorCallback = CPointer Unit>> + +// TODO find a way to unify this with the JVM source? The issue is that with JNA, UniFfiRustTaskCallback is (must be?) +// an interface implementing jna.Callback. On Native, it is (must be?) a CPointer which is a class, not an interface +// `expect typealias UniFfiRustTaskCallback` could be a solution but that is currently not allowed +fun invokeUniFfiForeignExecutorCallback( + handle: ULong, + delayMs: Int, + rustTask: UniFfiRustTaskCallback?, + rustTaskData: Pointer? +) { + if (rustTask == null) { + FfiConverterForeignExecutor.drop(handle) + } else { + val coroutineScope = FfiConverterForeignExecutor.lift(handle) + coroutineScope.launch { + if (delayMs > 0) { + delay(delayMs.toLong()) + } + rustTask.invoke(rustTaskData) + } + } +} + +actual fun createUniFfiForeignExecutorCallback(): UniFfiForeignExecutorCallback = + staticCFunction { handle, delayMs, rustTask, rustTaskData -> + // FIXME the rusty delayMs is an u32, so Int was probalby the wrong type all along? + // fix the java types so no negative values are created here + invokeUniFfiForeignExecutorCallback(handle, delayMs.toInt(), rustTask, rustTaskData) + } + diff --git a/uniffi-kotlin-multiplatform-bindings/tests/.gitignore b/uniffi-kotlin-multiplatform-bindings/tests/.gitignore new file mode 100644 index 00000000..3060674f --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/.gitignore @@ -0,0 +1,3 @@ +.gradle +build +.idea \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/tests/build.gradle.kts b/uniffi-kotlin-multiplatform-bindings/tests/build.gradle.kts new file mode 100644 index 00000000..aacbdecb --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/build.gradle.kts @@ -0,0 +1,175 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5") + } +} + +plugins { + kotlin("multiplatform") version "1.8.20" +} + +allprojects { + group = "net.folivo" + version = "0.0.1" + + repositories { + mavenCentral() + } +} + +kotlin { + jvm() // just a dummy +} + +subprojects { + apply(plugin = "kotlinx-atomicfu") + apply(plugin = "org.jetbrains.kotlin.multiplatform") + + val hostOs = System.getProperty("os.name") + val hostArch = System.getProperty("os.arch") + val isMingwX64 = hostOs.startsWith("Windows") + + val generatedDir = buildDir.resolve("generated").resolve("uniffi") + val crateDir = projectDir.resolve("uniffi") + val crateTargetDir = crateDir.resolve("target") + val crateTargetBindingsDir = crateDir.resolve("target").resolve("bindings") + val crateTargetLibDir = crateTargetDir.resolve("debug") + + val buildCrate = tasks.register("buildCrate", Exec::class) { + group = "uniffi" + workingDir(crateDir) + commandLine("cargo", "build") + } + + // Creating the bindings requires analysing the compiled library in order to get the metadata from + // uniffi's proc macro output + val createBindings = tasks.register("createBindings", Exec::class) { + group = "uniffi" + workingDir(crateDir) + commandLine("cargo", "run", "--bin", "create_bindings") + dependsOn(buildCrate) + } + + val copyBindings = tasks.register("copyBindings", Copy::class) { + group = "uniffi" + from(crateTargetBindingsDir) + into(generatedDir) + dependsOn(createBindings) + } + + val copyBinariesToProcessedRessources = tasks.register("copyBinaries", Copy::class) { + group = "uniffi" + + val sharedObjectPattern = when { + hostOs == "Mac OS X" -> "*.dylib" + hostOs == "Linux" -> "*.so" + else -> throw IllegalStateException() + } + val destinationResourceDirectory = when { + hostOs == "Mac OS X" && hostArch == "aarch64" -> "darwin-aarch64" + hostOs == "Mac OS X" && hostArch == "x86_64" -> "darwin-x86-64" + hostOs == "Linux" -> "linux-x86-64" + else -> throw IllegalStateException() + } + + from(crateTargetLibDir) + + include(sharedObjectPattern) + into( + buildDir.resolve("processedResources") + .resolve("jvm") + .resolve("main") + .resolve(destinationResourceDirectory) + ) + dependsOn(buildCrate) + } + + tasks.withType { + dependsOn(copyBinariesToProcessedRessources) + } + + tasks.withType { + dependsOn(copyBindings) + } + + kotlin { + jvm { + compilations.all { + kotlinOptions.jvmTarget = "1.8" + } + withJava() + testRuns["test"].executionTask.configure { + useJUnitPlatform() + } + } + + val nativeTarget = when { + hostOs == "Mac OS X" && hostArch == "aarch64" -> macosArm64("native") + hostOs == "Mac OS X" && hostArch == "x86_64" -> macosX64("native") + hostOs == "Linux" -> linuxX64("native") + isMingwX64 -> mingwX64("native") + else -> throw GradleException("Host OS is not supported in Kotlin/Native.") + } + + nativeTarget.apply { + compilations.getByName("main") { + cinterops { + val uniffi by creating { + val crate = this@subprojects.name + packageName("$crate.cinterop") + header( + generatedDir.resolve("nativeInterop").resolve("cinterop").resolve("headers") + .resolve(crate).resolve("$crate.h") + ) + tasks.named(interopProcessingTaskName) { + dependsOn(copyBinariesToProcessedRessources, copyBindings) + } + extraOpts("-libraryPath", crateTargetLibDir.absolutePath) + } + } + } + binaries { + executable { + entryPoint = "main" + } + } + } + + sourceSets { + val commonMain by getting { + kotlin.srcDir(generatedDir.resolve("commonMain").resolve("kotlin")) + dependencies { + implementation("com.squareup.okio:okio:3.2.0") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation("io.kotest:kotest-assertions-core:5.5.4") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") + } + } + val jvmMain by getting { + kotlin.srcDir(generatedDir.resolve("jvmMain").resolve("kotlin")) + dependencies { + implementation("net.java.dev.jna:jna:5.12.1") + } + } + val jvmTest by getting +// val jsMain by getting +// val jsTest by getting + val nativeMain by getting { + kotlin.srcDir(generatedDir.resolve("nativeMain").resolve("kotlin")) + } + val nativeTest by getting + } + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/build.gradle.kts b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/build.gradle.kts new file mode 100644 index 00000000..e69de29b diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/src/commonTest/kotlin/CallbacksTest.kt b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/src/commonTest/kotlin/CallbacksTest.kt new file mode 100644 index 00000000..0c275e1f --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/src/commonTest/kotlin/CallbacksTest.kt @@ -0,0 +1,165 @@ +import callbacks.* +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldInclude +import kotlinx.coroutines.runBlocking +import kotlin.test.Test + +class CallbacksTest { + + class KotlinGetters : ForeignGetters { + override fun getBool(v: Boolean, argumentTwo: Boolean): Boolean = v xor argumentTwo + override fun getString(v: String, arg2: Boolean): String { + if (v == "bad-argument") { + throw SimpleException.BadArgument("bad argument") + } + if (v == "unexpected-error") { + throw RuntimeException("something failed") + } + return if (arg2) "1234567890123" else v + } + + override fun getOption(v: String?, arg2: Boolean): String? { + if (v == "bad-argument") { + throw ComplexException.ReallyBadArgument(20) + } + if (v == "unexpected-error") { + throw RuntimeException("something failed") + } + return if (arg2) v?.uppercase() else v + } + + override fun getList(v: List, arg2: Boolean): List = if (arg2) v else listOf() + } + + @Test + fun callbackAsArgument() { + val rustGetters = RustGetters() + + val callback = KotlinGetters() + listOf(true, false).forEach { v -> + val flag = true + val expected = callback.getBool(v, flag) + val observed = rustGetters.getBool(callback, v, flag) + expected shouldBe observed + } + + listOf(listOf(1, 2), listOf(0, 1)).forEach { v -> + val flag = true + val expected = callback.getList(v, flag) + val observed = rustGetters.getList(callback, v, flag) + expected shouldBe observed + } + + listOf("Hello", "world").forEach { v -> + val flag = true + val expected = callback.getString(v, flag) + val observed = rustGetters.getString(callback, v, flag) + expected shouldBe observed + } + + listOf("Some", null).forEach { v -> + val flag = false + val expected = callback.getOption(v, flag) + val observed = rustGetters.getOption(callback, v, flag) + expected shouldBe observed + } + + rustGetters.getStringOptionalCallback(callback, "TestString", false) shouldBe "TestString" + rustGetters.getStringOptionalCallback(null, "TestString", false) shouldBe null + + shouldThrow { + rustGetters.getString(callback, "bad-argument", true) + } + shouldThrow { + rustGetters.getString(callback, "unexpected-error", true) + } + + shouldThrow { + rustGetters.getOption(callback, "bad-argument", true) + }.also { + it.code shouldBe 20 + } + + shouldThrow { + rustGetters.getOption(callback, "unexpected-error", true) + }.also { + it.reason shouldBe RuntimeException("something failed").toString() + } + rustGetters.destroy() + } + + class StoredKotlinStringifier : StoredForeignStringifier { + override fun fromSimpleType(value: Int): String = "kotlin: $value" + + // We don't test this, but we're checking that the arg type is included in the minimal list of types used + // in the UDL. + // If this doesn't compile, then look at TypeResolver. + override fun fromComplexType(values: List?): String = "kotlin: $values" + } + + @Test + fun callbackAsConstructorArgument() { + val kotlinStringifier = StoredKotlinStringifier() + val rustStringifier = RustStringifier(kotlinStringifier) + listOf(1, 2).forEach { v -> + val expected = kotlinStringifier.fromSimpleType(v) + val observed = rustStringifier.fromSimpleType(v) + expected shouldBe observed + } + rustStringifier.destroy() + } + + @Test + fun callbackReturningVoid() { + // the following block serves as a 'regression test' for foreign callbacks being GC'ed + runBlocking { + val secondAnswer = calcAsync(41u) + secondAnswer shouldBe 41u + println("secondAnswer: ${secondAnswer}") + } + + val answer = 42uL + + val callback = object : VoidCallback { + public var answer: ULong? = null; + + override fun callBack(newValue: ULong) { + this.answer = newValue + } + } + + VoidCallbackProcessor(answer).use { + it.process(callback) + } + + callback.answer shouldBe answer + } + + @Test + fun callBackReturningResultOfVoid() { + val answer = 42uL + val errorMessage = "That feels off" + + val throwingCallback = object : VoidCallbackWithError { + public var answer: ULong? = null; + + + override fun callBack(newValue: ULong) { + if (newValue != 42uL) { + throw ComplexException.UnexpectedErrorWithReason(errorMessage) + } + this.answer = answer + } + } + + VoidCallbackWithErrorProcessor(throwingCallback).use { + shouldThrow { it.process(7uL) } + .message shouldInclude errorMessage + + it.process(answer) + } + + throwingCallback.answer shouldBe answer + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/src/nativeInterop/cinterop/uniffi.def b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/src/nativeInterop/cinterop/uniffi.def new file mode 100644 index 00000000..037dd1a0 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/src/nativeInterop/cinterop/uniffi.def @@ -0,0 +1 @@ +staticLibraries = libcallbacks.a diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/src/nativeMain/kotlin/Main.kt b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/src/nativeMain/kotlin/Main.kt new file mode 100644 index 00000000..dee55120 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/src/nativeMain/kotlin/Main.kt @@ -0,0 +1,8 @@ +import callbacks.calcAsync +import kotlinx.coroutines.runBlocking + +// A toy executable that can be debugged externally after building it +fun main() { + val answer = runBlocking { calcAsync(42u) } + val secondAnswer = runBlocking { calcAsync(42u) } +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/Cargo.toml b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/Cargo.toml new file mode 100644 index 00000000..8b7b324b --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "callbacks" +version = "0.1.0" +edition = "2021" + +[lib] +name = "callbacks" +crate-type = ["cdylib", "staticlib"] + +[dependencies] +uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +once_cell = "1.12" +thiserror = "1.0" +# should go into create_bindings-specific dependencies +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "../../.." } +camino = "1.1.1" + +[build-dependencies] +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "../../.." } +camino = "1.1.1" + diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/build.rs b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/build.rs new file mode 100644 index 00000000..68c41faf --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi_build::generate_scaffolding("./src/callbacks.udl").unwrap(); +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/src/bin/create_bindings.rs b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/src/bin/create_bindings.rs new file mode 100644 index 00000000..1315f3b0 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/src/bin/create_bindings.rs @@ -0,0 +1,13 @@ +use camino::Utf8Path; + +use uniffi_kotlin_multiplatform::KotlinBindingGenerator; + +fn main() { + let out_dir = Utf8Path::new("target/bindings"); + uniffi_bindgen::generate_external_bindings( + KotlinBindingGenerator {}, + "./src/callbacks.udl", + None::<&Utf8Path>, + Some(out_dir), + ).unwrap(); +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/src/callbacks.udl b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/src/callbacks.udl new file mode 100644 index 00000000..2c24bf2f --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/src/callbacks.udl @@ -0,0 +1,86 @@ +namespace callbacks {}; + +[Error] +enum SimpleError { + "BadArgument", + "UnexpectedError", +}; + +[Error] +interface ComplexError { + ReallyBadArgument(i32 code); + UnexpectedErrorWithReason(string reason); +}; + +/// These objects are implemented by the foreign language and passed +/// to Rust. Rust then calls methods on it when it needs to. +/// +/// Some methods throw SimpleError and some throw ComplexError so that we can test both field-less and fielded errors +callback interface ForeignGetters { + [Throws=SimpleError] + boolean get_bool(boolean v, boolean argument_two); + [Throws=SimpleError] + string get_string(string v, boolean arg2); + [Throws=ComplexError] + string? get_option(string? v, boolean arg2); + [Throws=SimpleError] + sequence get_list(sequence v, boolean arg2); +}; + +/// These objects are implemented in Rust, and call out to `ForeignGetters` +/// to get the value. +interface RustGetters { + constructor(); + [Throws=SimpleError] + boolean get_bool(ForeignGetters callback, boolean v, boolean argument_two); + [Throws=SimpleError] + string get_string(ForeignGetters callback, string v, boolean arg2); + [Throws=ComplexError] + string? get_option(ForeignGetters callback, string? v, boolean arg2); + [Throws=SimpleError] + sequence get_list(ForeignGetters callback, sequence v, boolean arg2); + [Throws=SimpleError] + string? get_string_optional_callback(ForeignGetters? callback, string v, boolean arg2); +}; + +/// These objects are implemented by the foreign language and passed +/// to Rust. Rust then calls methods on it when it needs to. +/// Rust developers need to declare these traits extending `Send` so +/// they can be stored in Rust— i.e. not passed in as an argument to +/// be used immediately. +/// +/// These methods don't throw any error so that we can test older callback +//interfaces that don't support them. +callback interface StoredForeignStringifier { + string from_simple_type(i32 value); + // Test if types are collected from callback interfaces. + // kotlinc compile time error if not. + string from_complex_type(sequence? values); +}; + +/// Rust object that uses the StoredForeignStringifier to produce string representations +/// of passed arguments. +interface RustStringifier { + constructor(StoredForeignStringifier callback); + string from_simple_type(i32 value); +}; + +callback interface VoidCallback { + void call_back(u64 new_value); +}; + +interface VoidCallbackProcessor { + constructor(u64 new_value); + void process(VoidCallback void_callback); +}; + +callback interface VoidCallbackWithError { + [Throws=ComplexError] + void call_back(u64 new_value); +}; + +interface VoidCallbackWithErrorProcessor { + constructor(VoidCallbackWithError void_callback); + [Throws=ComplexError] + void process(u64 new_value); +}; diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/src/lib.rs b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/src/lib.rs new file mode 100644 index 00000000..c6ff71f7 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/src/lib.rs @@ -0,0 +1,167 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub struct VoidCallbackProcessor { + new_value: u64 +} + +pub trait VoidCallback: Send + Sync + std::fmt::Debug { + fn call_back(&self, new_value: u64); +} + +impl VoidCallbackProcessor { + pub fn new(new_value: u64) -> Self { + VoidCallbackProcessor { + new_value + } + } + + pub fn process(&self, void_callback: Box) { + void_callback.call_back(self.new_value); + } +} + +pub trait VoidCallbackWithError: Send + Sync + std::fmt::Debug { + fn call_back(&self, new_value: u64) -> Result<(), ComplexError>; +} + +pub struct VoidCallbackWithErrorProcessor { + callback: Box +} + +impl VoidCallbackWithErrorProcessor { + pub fn new(callback: Box) -> Self { + VoidCallbackWithErrorProcessor { + callback + } + } + + pub fn process(&self, new_value: u64) -> Result<(), ComplexError> { + self.callback.call_back(new_value) + } +} + +trait ForeignGetters { + fn get_bool(&self, v: bool, argument_two: bool) -> Result; + fn get_string(&self, v: String, arg2: bool) -> Result; + fn get_option(&self, v: Option, arg2: bool) -> Result, ComplexError>; + fn get_list(&self, v: Vec, arg2: bool) -> Result, SimpleError>; +} + +#[derive(Debug, thiserror::Error)] +pub enum SimpleError { + #[error("BadArgument")] + BadArgument, + #[error("InternalTelephoneError")] + UnexpectedError, +} + +#[derive(Debug, thiserror::Error)] +pub enum ComplexError { + #[error("ReallyBadArgument")] + ReallyBadArgument { code: i32 }, + #[error("InternalTelephoneError")] + UnexpectedErrorWithReason { reason: String }, +} + +impl From for SimpleError { + fn from(_: uniffi::UnexpectedUniFFICallbackError) -> SimpleError { + SimpleError::UnexpectedError + } +} + +impl From for ComplexError { + fn from(e: uniffi::UnexpectedUniFFICallbackError) -> ComplexError { + ComplexError::UnexpectedErrorWithReason { reason: e.reason } + } +} + +#[derive(Debug, Clone)] +pub struct RustGetters; + +impl RustGetters { + pub fn new() -> Self { + RustGetters + } + fn get_bool( + &self, + callback: Box, + v: bool, + argument_two: bool, + ) -> Result { + let ret = callback.get_bool(v, argument_two); + ret + } + fn get_string( + &self, + callback: Box, + v: String, + arg2: bool, + ) -> Result { + callback.get_string(v, arg2) + } + fn get_option( + &self, + callback: Box, + v: Option, + arg2: bool, + ) -> Result, ComplexError> { + callback.get_option(v, arg2) + } + fn get_list( + &self, + callback: Box, + v: Vec, + arg2: bool, + ) -> Result, SimpleError> { + callback.get_list(v, arg2) + } + + fn get_string_optional_callback( + &self, + callback: Option>, + v: String, + arg2: bool, + ) -> Result, SimpleError> { + callback.map(|c| c.get_string(v, arg2)).transpose() + } +} + +impl Default for RustGetters { + fn default() -> Self { + Self::new() + } +} + +// Use `Send+Send` because we want to store the callback in an exposed +// `Send+Sync` object. +#[allow(clippy::wrong_self_convention)] +trait StoredForeignStringifier: Send + Sync + std::fmt::Debug { + fn from_simple_type(&self, value: i32) -> String; + fn from_complex_type(&self, values: Option>>) -> String; +} + +#[derive(Debug)] +pub struct RustStringifier { + callback: Box, +} + +impl RustStringifier { + fn new(callback: Box) -> Self { + RustStringifier { callback } + } + + #[allow(clippy::wrong_self_convention)] + fn from_simple_type(&self, value: i32) -> String { + self.callback.from_simple_type(value) + } +} + +#[uniffi::export] +pub async fn calc_async(v: u64) -> u64 { + v +} + +uniffi::include_scaffolding!("callbacks"); +uniffi_reexport_scaffolding!(); diff --git a/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/uniffi.toml b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/uniffi.toml new file mode 100644 index 00000000..b964df1a --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/callbacks/uniffi/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.kotlin-native] +library_file = "target/debug/libcallbacks.a" diff --git a/uniffi-kotlin-multiplatform-bindings/tests/coverall/build.gradle.kts b/uniffi-kotlin-multiplatform-bindings/tests/coverall/build.gradle.kts new file mode 100644 index 00000000..e69de29b diff --git a/uniffi-kotlin-multiplatform-bindings/tests/coverall/src/commonTest/kotlin/CoverallTest.kt b/uniffi-kotlin-multiplatform-bindings/tests/coverall/src/commonTest/kotlin/CoverallTest.kt new file mode 100644 index 00000000..dc85333b --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/coverall/src/commonTest/kotlin/CoverallTest.kt @@ -0,0 +1,209 @@ +import coverall.* +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldBeIn +import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.* +import kotlinx.coroutines.test.runTest +import kotlinx.datetime.Clock +import kotlin.math.abs +import kotlin.test.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class CoverallTest { + + @Test + fun dict() { + createSomeDict().use { d -> + d.text shouldBe "text" + d.maybeText shouldBe "maybe_text" + d.aBool shouldBe true + d.maybeABool shouldBe false + d.unsigned8 shouldBe 1.toUByte() + d.maybeUnsigned8 shouldBe 2.toUByte() + d.unsigned16 shouldBe 3.toUShort() + d.maybeUnsigned16 shouldBe 4.toUShort() + d.unsigned64 shouldBe 18446744073709551615UL + d.maybeUnsigned64 shouldBe 0UL + d.signed8 shouldBe 8.toByte() + d.maybeSigned8 shouldBe 0.toByte() + d.signed64 shouldBe 9223372036854775807L + d.maybeSigned64 shouldBe 0L + + // floats should be "close enough". + infix fun Float.almostEquals(other: Float) = (abs(this - other) < 0.000001) shouldBe true + infix fun Double.almostEquals(other: Double) = (abs(this - other) < 0.000001) shouldBe true + + d.float32 almostEquals 1.2345F + d.maybeFloat32!! almostEquals (22.0F / 7.0F) + d.float64 almostEquals 0.0 + d.maybeFloat64!! almostEquals 1.0 + + d.coveralls!!.getName() shouldBe "some_dict" + } + } + + @Test + fun arcs() { + Coveralls("test_arcs").use { coveralls -> + getNumAlive() shouldBe 1UL + // One ref held by the foreign-language code, one created for this method call. + coveralls.strongCount() shouldBe 2UL + coveralls.getOther() shouldBe null + coveralls.takeOther(coveralls) + // Should now be a new strong ref, held by the object's reference to itself. + coveralls.strongCount() shouldBe 3UL + // But the same number of instances. + getNumAlive() shouldBe 1UL + // Careful, this makes a new Kotlin object which must be separately destroyed. + coveralls.getOther()!!.use { other -> + // It's the same Rust object. + other.getName() shouldBe "test_arcs" + } + shouldThrow { + coveralls.takeOtherFallible() + } + shouldThrow { + coveralls.takeOtherPanic("expected panic: with an arc!") + } + shouldThrow { + coveralls.falliblePanic("Expected panic in a fallible function!") + } + coveralls.takeOther(null) + coveralls.strongCount() shouldBe 2UL + } + getNumAlive() shouldBe 0UL + } + + @Test + fun returnObjects() { + Coveralls("test_return_objects").use { coveralls -> + getNumAlive() shouldBe 1UL + coveralls.strongCount() shouldBe 2UL + coveralls.cloneMe().use { c2 -> + c2.getName() shouldBe coveralls.getName() + getNumAlive() shouldBe 2UL + c2.strongCount() shouldBe 2UL + + coveralls.takeOther(c2) + // same number alive but `c2` has an additional ref count. + getNumAlive() shouldBe 2UL + coveralls.strongCount() shouldBe 2UL + c2.strongCount() shouldBe 3UL + } + // Here we've dropped Kotlin's reference to `c2`, but the rust struct will not + // be dropped as coveralls hold an `Arc<>` to it. + getNumAlive() shouldBe 2UL + } + // Destroying `coveralls` will kill both. + getNumAlive() shouldBe 0UL + + Coveralls("test_simple_errors").use { coveralls -> + shouldThrow { + coveralls.maybeThrow(true) + }.also { e -> + e.message shouldBe "The coverall has too many holes" + } + + shouldThrow { + coveralls.maybeThrowInto(true) + } + + shouldThrow { + coveralls.panic("oops") + }.also { e -> + e.message shouldBe "oops" + } + } + + Coveralls("test_complex_errors").use { coveralls -> + coveralls.maybeThrowComplex(0) shouldBe true + + shouldThrow { + coveralls.maybeThrowComplex(1) + }.also { e -> + e.code shouldBe 10.toShort() + e.extendedCode shouldBe 20.toShort() + e.toString() shouldBeIn setOf( + "coverall.ComplexException.OsException: code=10, extendedCode=20", + "coverall.ComplexException\$OsException: code=10, extendedCode=20" + ) + } + + shouldThrow { + coveralls.maybeThrowComplex(2) + }.also { e -> + e.reason shouldBe "Forbidden" + e.toString() shouldBeIn setOf( + "coverall.ComplexException.PermissionDenied: reason=Forbidden", + "coverall.ComplexException\$PermissionDenied: reason=Forbidden" + ) + } + + shouldThrow { + coveralls.maybeThrowComplex(3) + } + } + + Coveralls("test_interfaces_in_dicts").use { coveralls -> + coveralls.addPatch(Patch(Color.RED)) + coveralls.addRepair( + Repair(`when` = Clock.System.now(), patch = Patch(Color.BLUE)) + ) + coveralls.getRepairs().size shouldBe 2 + } + + Coveralls("test_regressions").use { coveralls -> + coveralls.getStatus("success") shouldBe "status: success" + } + } + + // This tests that the UniFFI-generated scaffolding doesn't introduce any unexpected locking. + // We have one thread busy-wait for a some period of time, while a second thread repeatedly + // increments the counter and then checks if the object is still busy. The second thread should + // not be blocked on the first, and should reliably observe the first thread being busy. + // If it does not, that suggests UniFFI is accidentally serializing the two threads on access + // to the shared counter object. + @Test + fun threadSafe() = runTest { + val scope = CoroutineScope(Dispatchers.Default) + try { + ThreadsafeCounter().use { counter -> + val busyWaiting = scope.launch { + // 300 ms should be long enough for the other thread to easily finish + // its loop, but not so long as to annoy the user with a slow test. + counter.busyWait(300) + } + val incrementing = scope.async { + var count = 0 + for (n in 1..100) { + // We exect most iterations of this loop to run concurrently + // with the busy-waiting thread. + count = counter.incrementIfBusy() + } + count + } + + busyWaiting.join() + val count = incrementing.await() + count shouldBeGreaterThan 0 + } + } finally { + scope.cancel() + } + } + + @Test + fun noRustCall() { + // This does not call Rust code. + var d = DictWithDefaults() + d.name shouldBe "default-value" + d.category shouldBe null + d.integer shouldBe 31UL + + d = DictWithDefaults(name = "this", category = "that", integer = 42UL) + d.name shouldBe "this" + d.category shouldBe "that" + d.integer shouldBe 42UL + } +} \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/tests/coverall/src/nativeInterop/cinterop/uniffi.def b/uniffi-kotlin-multiplatform-bindings/tests/coverall/src/nativeInterop/cinterop/uniffi.def new file mode 100644 index 00000000..40c53844 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/coverall/src/nativeInterop/cinterop/uniffi.def @@ -0,0 +1 @@ +staticLibraries = libcoverall.a \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/Cargo.toml b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/Cargo.toml new file mode 100644 index 00000000..26e80730 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "coverall" +version = "0.1.0" +edition = "2021" + +[lib] +name = "coverall" +crate-type = ["cdylib", "staticlib"] + +[dependencies] +uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +once_cell = "1.12" +thiserror = "1.0" +# should go into create_bindings-specific dependencies +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "../../.." } +camino = "1.1.1" + +[build-dependencies] +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "../../.." } +camino = "1.1.1" diff --git a/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/build.rs b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/build.rs new file mode 100644 index 00000000..5126e04b --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/build.rs @@ -0,0 +1,6 @@ +use camino::Utf8Path; +use uniffi_kotlin_multiplatform::KotlinBindingGenerator; + +fn main() { + uniffi_build::generate_scaffolding("./src/coverall.udl").unwrap(); +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/src/bin/create_bindings.rs b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/src/bin/create_bindings.rs new file mode 100644 index 00000000..3da36bff --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/src/bin/create_bindings.rs @@ -0,0 +1,13 @@ +use camino::Utf8Path; + +use uniffi_kotlin_multiplatform::KotlinBindingGenerator; + +fn main() { + let out_dir = Utf8Path::new("target/bindings"); + uniffi_bindgen::generate_external_bindings( + KotlinBindingGenerator {}, + "./src/coverall.udl", + None::<&Utf8Path>, + Some(out_dir), + ).unwrap(); +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/src/coverall.udl b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/src/coverall.udl new file mode 100644 index 00000000..277fbc07 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/src/coverall.udl @@ -0,0 +1,152 @@ +namespace coverall { + SimpleDict create_some_dict(); + SimpleDict create_none_dict(); + + u64 get_num_alive(); + + // void returning error throwing namespace function to catch clippy warnings (eg, #1330) + [Throws=CoverallError] + void println(string text); +}; + +dictionary SimpleDict { + string text; + string? maybe_text; + boolean a_bool; + boolean? maybe_a_bool; + u8 unsigned8; + u8? maybe_unsigned8; + u16 unsigned16; + u16? maybe_unsigned16; + u64 unsigned64; + u64? maybe_unsigned64; + i8 signed8; + i8? maybe_signed8; + i64 signed64; + i64? maybe_signed64; + float float32; + float? maybe_float32; + double float64; + double? maybe_float64; + Coveralls? coveralls; +}; + +dictionary DictWithDefaults { + string name = "default-value"; + string? category = null; + u64 integer = 31; +}; + +[Enum] +interface MaybeSimpleDict { + Yeah(SimpleDict d); + Nah(); +}; + +[Error] +enum CoverallError { + "TooManyHoles" +}; + +[Error] +interface ComplexError { + OsError(i16 code, i16 extended_code); + PermissionDenied(string reason); +}; + +interface Coveralls { + constructor(string name); + + // Either constructs a new object or throws an error. + [Throws=CoverallError, Name="fallible_new"] + constructor(string name, boolean should_fail); + + // Always panics, just to test panics in ctors are handled. + [Name="panicing_new"] + constructor(string message); + + string get_name(); + + [Throws=CoverallError] + boolean maybe_throw(boolean should_throw); + + /// Throws something that impls `Into`, + /// rather than directly throwing `CoverallError`. + [Throws=CoverallError] + boolean maybe_throw_into(boolean should_throw); + + [Throws=ComplexError] + boolean maybe_throw_complex(i8 input); + + void panic(string message); + + [Throws=CoverallError] + void fallible_panic(string message); + + // *** Test functions which take either `self` or other params as `Arc` *** + + /// Calls `Arc::strong_count()` on the `Arc` containing `self`. + [Self=ByArc] + u64 strong_count(); + + /// Takes an `Arc` and stores it in `self`, dropping the existing + /// reference. Note you can create circular references by passing `self`. + void take_other(Coveralls? other); + + /// Returns what was previously set via `take_other()`, or null. + Coveralls? get_other(); + + /// Same signature as `take_other` but always fails. + [Self=ByArc, Throws=CoverallError] + void take_other_fallible(); + + /// Same signature as `take_other` but with an extra string arg - always + /// panics with that message.. + [Self=ByArc] + void take_other_panic(string message); + + // can't name it `clone` as it conflicts with the Clone trait and ours has a different signature + Coveralls clone_me(); + + // regression test: using a parameter name that was also used by UniFFI runtime code + string get_status(string status); + + /// Simple string->integer dictionary, using the legacy `DOMString` type. + record get_dict(string key, u64 value); + + /// Simple string->integer dictionary, using the classic string type + record get_dict2(string key, u64 value); + + /// integer->integer dictionary + record get_dict3(u32 key, u64 value); + + /// Adds a new repair at the current time. + void add_patch(Patch patch); + + /// Adds a new repair at the specified time. + void add_repair(Repair repair); + + /// Returns all repairs made. + sequence get_repairs(); +}; + +// coveralls keep track of their repairs (an interface in a dict) +dictionary Repair { + timestamp when; + Patch patch; +}; + +// All coveralls end up with a patch. +enum Color {"Red", "Blue", "Green"}; + +interface Patch { + constructor(Color color); + + Color get_color(); +}; + +interface ThreadsafeCounter { + constructor(); + void busy_wait(i32 ms); + i32 increment_if_busy(); +}; diff --git a/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/src/lib.rs b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/src/lib.rs new file mode 100644 index 00000000..a2c55188 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/src/lib.rs @@ -0,0 +1,353 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use std::sync::{Arc, Mutex, RwLock}; +use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; +use std::time::SystemTime; + +use once_cell::sync::Lazy; + +static NUM_ALIVE: Lazy> = Lazy::new(|| RwLock::new(0)); + +#[derive(Debug, thiserror::Error)] +pub enum CoverallError { + #[error("The coverall has too many holes")] + TooManyHoles, +} + +/// This error doesn't appear in the interface, instead +/// we rely on an `Into` impl to surface it to consumers. +#[derive(Debug, thiserror::Error)] +pub enum InternalCoverallError { + #[error("The coverall has an excess of holes")] + ExcessiveHoles, +} + +impl From for CoverallError { + fn from(err: InternalCoverallError) -> CoverallError { + match err { + InternalCoverallError::ExcessiveHoles => CoverallError::TooManyHoles, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ComplexError { + #[error("OsError: {code} ({extended_code})")] + OsError { code: i16, extended_code: i16 }, + #[error("PermissionDenied: {reason}")] + PermissionDenied { reason: String }, +} + +#[derive(Debug, Clone)] +pub struct SimpleDict { + text: String, + maybe_text: Option, + a_bool: bool, + maybe_a_bool: Option, + unsigned8: u8, + maybe_unsigned8: Option, + unsigned16: u16, + maybe_unsigned16: Option, + unsigned64: u64, + maybe_unsigned64: Option, + signed8: i8, + maybe_signed8: Option, + signed64: i64, + maybe_signed64: Option, + float32: f32, + maybe_float32: Option, + float64: f64, + maybe_float64: Option, + coveralls: Option>, +} + +#[derive(Debug, Clone)] +pub struct DictWithDefaults { + name: String, + category: Option, + integer: u64, +} + +#[derive(Debug, Clone)] +pub enum MaybeSimpleDict { + Yeah { d: SimpleDict }, + Nah, +} + +fn create_some_dict() -> SimpleDict { + SimpleDict { + text: "text".to_string(), + maybe_text: Some("maybe_text".to_string()), + a_bool: true, + maybe_a_bool: Some(false), + unsigned8: 1, + maybe_unsigned8: Some(2), + unsigned16: 3, + maybe_unsigned16: Some(4), + unsigned64: u64::MAX, + maybe_unsigned64: Some(u64::MIN), + signed8: 8, + maybe_signed8: Some(0), + signed64: i64::MAX, + maybe_signed64: Some(0), + float32: 1.2345, + maybe_float32: Some(22.0 / 7.0), + float64: 0.0, + maybe_float64: Some(1.0), + coveralls: Some(Arc::new(Coveralls::new("some_dict".to_string()))), + } +} + +fn create_none_dict() -> SimpleDict { + SimpleDict { + text: "text".to_string(), + maybe_text: None, + a_bool: true, + maybe_a_bool: None, + unsigned8: 1, + maybe_unsigned8: None, + unsigned16: 3, + maybe_unsigned16: None, + unsigned64: u64::MAX, + maybe_unsigned64: None, + signed8: 8, + maybe_signed8: None, + signed64: i64::MAX, + maybe_signed64: None, + float32: 1.2345, + maybe_float32: None, + float64: 0.0, + maybe_float64: None, + coveralls: None, + } +} + +fn get_num_alive() -> u64 { + *NUM_ALIVE.read().unwrap() +} + +type Result = std::result::Result; +type ComplexResult = std::result::Result; + +fn println(text: String) -> Result<()> { + println!("coveralls println: {text}"); + Ok(()) +} + +#[derive(Debug)] +pub struct Coveralls { + name: String, + // A reference to another Coveralls. Currently will be only a reference + // to `self`, so will create a circular reference. + other: Mutex>>, + // Repairs we've made to this coverall. + repairs: Mutex>, +} + +impl Coveralls { + fn new(name: String) -> Self { + *NUM_ALIVE.write().unwrap() += 1; + Self { + name, + other: Mutex::new(None), + repairs: Mutex::new(Vec::new()), + } + } + + fn fallible_new(name: String, should_fail: bool) -> Result { + if should_fail { + Err(CoverallError::TooManyHoles) + } else { + Ok(Self::new(name)) + } + } + + fn fallible_panic(&self, message: String) -> Result<()> { + panic!("{message}"); + } + + fn get_name(&self) -> String { + self.name.clone() + } + + fn panicing_new(message: String) -> Self { + panic!("{message}"); + } + + fn maybe_throw(&self, should_throw: bool) -> Result { + if should_throw { + Err(CoverallError::TooManyHoles) + } else { + Ok(true) + } + } + + fn maybe_throw_into(&self, should_throw: bool) -> Result { + if should_throw { + Err(InternalCoverallError::ExcessiveHoles) + } else { + Ok(true) + } + } + + fn maybe_throw_complex(&self, input: i8) -> ComplexResult { + match input { + 0 => Ok(true), + 1 => Err(ComplexError::OsError { + code: 10, + extended_code: 20, + }), + 2 => Err(ComplexError::PermissionDenied { + reason: "Forbidden".to_owned(), + }), + _ => panic!("Invalid input"), + } + } + + fn panic(&self, message: String) { + panic!("{message}"); + } + + fn strong_count(self: Arc) -> u64 { + Arc::strong_count(&self) as u64 + } + + fn take_other(&self, other: Option>) { + *self.other.lock().unwrap() = other.map(|arc| Arc::clone(&arc)) + } + + fn get_other(&self) -> Option> { + (*self.other.lock().unwrap()).as_ref().map(Arc::clone) + } + + fn take_other_fallible(self: Arc) -> Result<()> { + Err(CoverallError::TooManyHoles) + } + + fn take_other_panic(self: Arc, message: String) { + panic!("{message}"); + } + + fn clone_me(&self) -> Arc { + let other = self.other.lock().unwrap(); + let new_other = Mutex::new(other.clone()); + *NUM_ALIVE.write().unwrap() += 1; + Arc::new(Self { + name: self.name.clone(), + other: new_other, + repairs: Mutex::new(Vec::new()), + }) + } + + fn get_status(&self, status: String) -> String { + format!("status: {status}") + } + + fn get_dict(&self, key: String, value: u64) -> HashMap { + let mut map = HashMap::new(); + map.insert(key, value); + map + } + + fn get_dict2(&self, key: String, value: u64) -> HashMap { + let mut map = HashMap::new(); + map.insert(key, value); + map + } + + fn get_dict3(&self, key: u32, value: u64) -> HashMap { + let mut map = HashMap::new(); + map.insert(key, value); + map + } + + fn add_patch(&self, patch: Arc) { + let repair = Repair { + when: SystemTime::now(), + patch, + }; + let mut repairs = self.repairs.lock().unwrap(); + repairs.push(repair); + } + + fn add_repair(&self, repair: Repair) { + let mut repairs = self.repairs.lock().unwrap(); + repairs.push(repair); + } + + fn get_repairs(&self) -> Vec { + let repairs = self.repairs.lock().unwrap(); + repairs.clone() + } +} + +impl Drop for Coveralls { + fn drop(&mut self) { + *NUM_ALIVE.write().unwrap() -= 1; + } +} + +#[derive(Debug, Clone)] +pub struct Repair { + when: SystemTime, + patch: Arc, +} + +#[derive(Debug, Clone, Copy)] +pub enum Color { + Red, + Blue, + Green, +} + +#[derive(Debug, Clone)] +struct Patch { + color: Color, +} + +impl Patch { + fn new(color: Color) -> Self { + Self { color } + } + + fn get_color(&self) -> Color { + self.color + } +} + +// This is a small implementation of a counter that allows waiting on one thread, +// and counting on another thread. We use it to test that the UniFFI generated scaffolding +// doesn't introduce unexpected locking behaviour between threads. +struct ThreadsafeCounter { + is_busy: AtomicBool, + count: AtomicI32, +} + +impl ThreadsafeCounter { + fn new() -> Self { + Self { + is_busy: AtomicBool::new(false), + count: AtomicI32::new(0), + } + } + + fn busy_wait(&self, ms: i32) { + self.is_busy.store(true, Ordering::SeqCst); + // Pretend to do some work in a blocking fashion. + std::thread::sleep(std::time::Duration::from_millis(ms as u64)); + self.is_busy.store(false, Ordering::SeqCst); + } + + fn increment_if_busy(&self) -> i32 { + if self.is_busy.load(Ordering::SeqCst) { + self.count.fetch_add(1, Ordering::SeqCst) + 1 + } else { + self.count.load(Ordering::SeqCst) + } + } +} + +include!(concat!(env!("OUT_DIR"), "/coverall.uniffi.rs")); diff --git a/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/uniffi.toml b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/uniffi.toml new file mode 100644 index 00000000..aad96981 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/coverall/uniffi/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.kotlin-native] +library_file = "target/debug/libcoverall.a" diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/build.gradle.kts b/uniffi-kotlin-multiplatform-bindings/tests/external_types/build.gradle.kts new file mode 100644 index 00000000..e69de29b diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/src/commonTest/kotlin/ExternalTypesTest.kt b/uniffi-kotlin-multiplatform-bindings/tests/external_types/src/commonTest/kotlin/ExternalTypesTest.kt new file mode 100644 index 00000000..5f9b9f4a --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/src/commonTest/kotlin/ExternalTypesTest.kt @@ -0,0 +1,24 @@ +import external_types.CombinedType +import external_types.CrateOneType +import external_types.CrateTwoType +import external_types.getCombinedType +import io.kotest.matchers.shouldBe +import kotlin.test.Test + +class ExternalTypesTest { + @Test + fun test() { + val ct = getCombinedType( + CombinedType( + CrateOneType("test"), + CrateTwoType(42), + ) + ); + ct.cot.sval shouldBe "test" + ct.ctt.ival shouldBe 42 + + val ct2 = getCombinedType(null) + ct2.cot.sval shouldBe "hello" + ct2.ctt.ival shouldBe 1 + } +} \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/src/nativeInterop/cinterop/uniffi.def b/uniffi-kotlin-multiplatform-bindings/tests/external_types/src/nativeInterop/cinterop/uniffi.def new file mode 100644 index 00000000..5bcfa8fd --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/src/nativeInterop/cinterop/uniffi.def @@ -0,0 +1 @@ +staticLibraries = libexternal_types.a \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/Cargo.toml b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/Cargo.toml new file mode 100644 index 00000000..b753ecb5 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "external_types" +version = "0.1.0" +edition = "2021" + +[lib] +name = "external_types" +crate-type = ["cdylib", "staticlib"] + +[dependencies] +uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +once_cell = "1.12" +thiserror = "1.0" +crate_one = { path = "crate_one" } +crate_two = { path = "crate_two" } +# should go into create_bindings-specific dependencies +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "../../.." } +camino = "1.1.1" + +[build-dependencies] +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "../../.." } +camino = "1.1.1" diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/build.rs b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/build.rs new file mode 100644 index 00000000..9fcb8a84 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/build.rs @@ -0,0 +1,14 @@ +use std::fs; +use std::process::Command; + +use camino::Utf8Path; + +use uniffi_kotlin_multiplatform::KotlinBindingGenerator; + +fn main() { + Command::new("cargo").args(["build", "--manifest-path", "./crate_one/Cargo.toml"]).output().unwrap(); + Command::new("cargo").args(["build", "--manifest-path", "./crate_two/Cargo.toml"]).output().unwrap(); + fs::copy("crate_one/target/debug/libcrate_one.a", "target/debug/libcrate_one.a").unwrap(); + fs::copy("crate_two/target/debug/libcrate_two.a", "target/debug/libcrate_two.a").unwrap(); + uniffi_build::generate_scaffolding("./src/external_types.udl").unwrap(); +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_one/Cargo.toml b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_one/Cargo.toml new file mode 100644 index 00000000..c4b0b7fb --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_one/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "crate_one" +version = "0.1.0" +edition = "2021" + +[lib] +name = "crate_one" +crate-type = ["cdylib", "staticlib", "rlib"] diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_one/src/lib.rs b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_one/src/lib.rs new file mode 100644 index 00000000..28631274 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_one/src/lib.rs @@ -0,0 +1,3 @@ +pub struct CrateOneType { + pub sval: String, +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_two/Cargo.toml b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_two/Cargo.toml new file mode 100644 index 00000000..ef6e4721 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_two/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "crate_two" +version = "0.1.0" +edition = "2021" + +[lib] +name = "crate_two" +crate-type = ["cdylib", "staticlib", "rlib"] + +[dependencies] +crate_one = { path = "../crate_one" } diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_two/src/lib.rs b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_two/src/lib.rs new file mode 100644 index 00000000..32fff6c3 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/crate_two/src/lib.rs @@ -0,0 +1,3 @@ +pub struct CrateTwoType { + pub ival: i32, +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/src/bin/create_bindings.rs b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/src/bin/create_bindings.rs new file mode 100644 index 00000000..3b5e1ed6 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/src/bin/create_bindings.rs @@ -0,0 +1,13 @@ +use camino::Utf8Path; + +use uniffi_kotlin_multiplatform::KotlinBindingGenerator; + +fn main() { + let out_dir = Utf8Path::new("target/bindings"); + uniffi_bindgen::generate_external_bindings( + KotlinBindingGenerator {}, + "./src/external_types.udl", + None::<&Utf8Path>, + Some(out_dir), + ).unwrap(); +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/src/external_types.udl b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/src/external_types.udl new file mode 100644 index 00000000..f85ba46c --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/src/external_types.udl @@ -0,0 +1,16 @@ +namespace external_types { + CombinedType get_combined_type(optional CombinedType? cval); +}; + +dictionary CrateOneType { + string sval; +}; + +dictionary CrateTwoType { + i32 ival; +}; + +dictionary CombinedType { + CrateOneType cot; + CrateTwoType ctt; +}; diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/src/lib.rs b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/src/lib.rs new file mode 100644 index 00000000..ab5c2aa2 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/src/lib.rs @@ -0,0 +1,18 @@ +use crate_one::CrateOneType; +use crate_two::CrateTwoType; + +pub struct CombinedType { + pub cot: CrateOneType, + pub ctt: CrateTwoType, +} + +fn get_combined_type(existing: Option) -> CombinedType { + existing.unwrap_or_else(|| CombinedType { + cot: CrateOneType { + sval: "hello".to_string(), + }, + ctt: CrateTwoType { ival: 1 }, + }) +} + +include!(concat!(env!("OUT_DIR"), "/external_types.uniffi.rs")); diff --git a/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/uniffi.toml b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/uniffi.toml new file mode 100644 index 00000000..9dfc9ca6 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/external_types/uniffi/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.kotlin-native] +library_file = "target/debug/libexternal_types.a" diff --git a/uniffi-kotlin-multiplatform-bindings/tests/futures/build.gradle.kts b/uniffi-kotlin-multiplatform-bindings/tests/futures/build.gradle.kts new file mode 100644 index 00000000..e69de29b diff --git a/uniffi-kotlin-multiplatform-bindings/tests/futures/src/commonTest/kotlin/FuturesTest.kt b/uniffi-kotlin-multiplatform-bindings/tests/futures/src/commonTest/kotlin/FuturesTest.kt new file mode 100644 index 00000000..9f2257f0 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/futures/src/commonTest/kotlin/FuturesTest.kt @@ -0,0 +1,187 @@ + import futures.* + import kotlinx.coroutines.* + import kotlin.test.* + import kotlin.time.* + import kotlin.time.Duration.Companion.milliseconds + + fun assertReturnsImmediately(actualTime: Duration, testName: String) { + assertTrue( + // increased the margin by 1.millisecond compared to upstream + actualTime <= 5.milliseconds, "unexpected $testName time: ${actualTime.inWholeMilliseconds}ms" + ) + } + + fun assertApproximateTime(actualTime: Duration, expectedTime: Duration, testName: String) { + assertTrue( + actualTime >= expectedTime && actualTime <= expectedTime + 100.milliseconds, + "unexpected $testName time: ${actualTime.inWholeMilliseconds}ms" + ) + } + + fun assertSmall(duration: Duration) = assertTrue(duration < 100.milliseconds) + + @OptIn(ExperimentalTime::class) + class FuturesTest { + init { + // init UniFFI to get good measurements after that + runBlocking { + measureTime { + alwaysReady() + } + } + } + + @Test + fun testAlwaysReady() = runBlocking { + val result = measureTimedValue { + alwaysReady() + } + + assertTrue(result.value) + + assertReturnsImmediately(result.duration, "always_ready") + } + + @Test + fun testVoid() = runBlocking { + val result: TimedValue = measureTimedValue { + void() + } + + assertReturnsImmediately(result.duration, "void") + } + + @Test + fun testSleep() = runBlocking { + val duration = measureTime { + sleep(200u) + } + + assertApproximateTime(duration, 200.milliseconds, "sleep") + } + + @Test + fun testSequentialFutures() = runBlocking { + val result = measureTimedValue { + val resultAlice = sayAfter(100u, "Alice") + val resultBob = sayAfter(200u, "Bob") + + Pair(resultAlice, resultBob) + } + + assertEquals(Pair("Hello, Alice!", "Hello, Bob!"), result.value) + assertApproximateTime(result.duration, 300.milliseconds, "sequential futures") + } + + @Test + fun testConcurrentFutures() = runBlocking { + val result = measureTimedValue { + val resultAlice = async { sayAfter(100u, "Alice") } + val resultBob = async { sayAfter(200u, "Bob") } + + Pair(resultAlice.await(), resultBob.await()) + } + + assertEquals(Pair("Hello, Alice!", "Hello, Bob!"), result.value) + assertApproximateTime(result.duration, 200.milliseconds, "concurrent futures") + } + + @Test + fun testAsyncMethods() = runBlocking { + val megaphone = newMegaphone() + val result = measureTimedValue { megaphone.sayAfter(200u, "Alice") } + + assertEquals(result.value, "HELLO, ALICE!") + assertApproximateTime(result.duration, 200.milliseconds, "async methods") + } + + @Test + fun testAsyncMaybeNewMegaphone() = runBlocking { + val megaphone = asyncMaybeNewMegaphone(true) + assertNotNull(megaphone) + + val notMegaphone = asyncMaybeNewMegaphone(false) + assertNull(notMegaphone) + } + + @Test + fun testWithTokioRuntime() = runBlocking { + val result = measureTimedValue { + sayAfterWithTokio(200u, "Alice") + } + + assertEquals("Hello, Alice (with Tokio)!", result.value) + assertApproximateTime(result.duration, 200.milliseconds, "with tokio runtime") + } + + @Test + fun testFalliblefunctions() = runBlocking { + val success = measureTimedValue { + fallibleMe(false) + } + assertEquals(42u, success.value) + + val successDuration = success.duration + println("fallible function (with result): ${successDuration.inWholeMilliseconds}ms") + assertSmall(successDuration) + + val failDuration = measureTime { + assertFailsWith { fallibleMe(true) } + } + println("fallible function (with exception): ${failDuration.inWholeMilliseconds}ms") + assertSmall(failDuration) + } + + @Test + fun testFallibleMethods() = runBlocking { + newMegaphone().use { + val success = measureTimedValue { + it.fallibleMe(false) + } + + assertEquals(42u, success.value) + val successDuration = success.duration + print("fallible method (with result): ${successDuration.inWholeMilliseconds}ms") + assertSmall(successDuration) + + val failDuration = measureTime { + assertFailsWith { it.fallibleMe(true) } + } + + print("fallible method (with exception): ${failDuration.inWholeMilliseconds}ms") + assertSmall(failDuration) + } + } + + @Test + fun testFallibleStruct() { + runBlocking { + assertFailsWith { fallibleStruct(true) } + } + } + + @Test + fun testRecord() = runBlocking { + val (result, duration) = measureTimedValue { + newMyRecord("foo", 42u) + } + + assertEquals("foo", result.a) + assertEquals(42u, result.b) + print("record: ${duration.inWholeMilliseconds}ms") + assertSmall(duration) + } + + @Test + fun testBrokenSleep() = runBlocking { + val duration = measureTime { + brokenSleep(100u, 0u) // calls the waker twice immediately + sleep(100u) // wait for a possible failure + + brokenSleep(100u, 100u) // calls the waker a second time after 1s + sleep(200u) // wait for a possible faillure + } + + assertApproximateTime(duration, 500.milliseconds, "broken sleep") + } + } diff --git a/uniffi-kotlin-multiplatform-bindings/tests/futures/src/nativeInterop/cinterop/uniffi.def b/uniffi-kotlin-multiplatform-bindings/tests/futures/src/nativeInterop/cinterop/uniffi.def new file mode 100644 index 00000000..efc7199f --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/futures/src/nativeInterop/cinterop/uniffi.def @@ -0,0 +1 @@ +staticLibraries = libfutures.a diff --git a/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/Cargo.toml b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/Cargo.toml new file mode 100644 index 00000000..8a51fa05 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "futures" +version = "0.1.0" +edition = "2021" + +[lib] +name = "futures" +crate-type = ["cdylib", "staticlib"] + +[dependencies] +uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e", features = ["tokio"] } +uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +tokio = { version = "1.24.1", features = ["time"] } + +# should go into create_bindings-specific dependencies +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "../../.." } +camino = "1.1.1" + +[build-dependencies] +uniffi_build = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs.git", rev= "22b8c21f117faa95c6d398baad463ef7f108749e" } +uniffi-kotlin-multiplatform = { path = "../../.." } +camino = "1.1.1" diff --git a/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/build.rs b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/build.rs new file mode 100644 index 00000000..a44c2f43 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi_build::generate_scaffolding("./src/futures.udl").unwrap(); +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/src/bin/create_bindings.rs b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/src/bin/create_bindings.rs new file mode 100644 index 00000000..f82997f8 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/src/bin/create_bindings.rs @@ -0,0 +1,13 @@ +use camino::Utf8Path; + +use uniffi_kotlin_multiplatform::KotlinBindingGenerator; + +fn main() { + let out_dir = Utf8Path::new("target/bindings"); + uniffi_bindgen::generate_external_bindings( + KotlinBindingGenerator {}, + "./src/futures.udl", + None::<&Utf8Path>, + Some(out_dir), + ).unwrap(); +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/src/futures.udl b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/src/futures.udl new file mode 100644 index 00000000..ad8cb841 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/src/futures.udl @@ -0,0 +1 @@ +namespace futures {}; diff --git a/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/src/lib.rs b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/src/lib.rs new file mode 100644 index 00000000..0c1cbd47 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/src/lib.rs @@ -0,0 +1,280 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{ + future::Future, + pin::Pin, + sync::{Arc, Mutex, MutexGuard}, + task::{Context, Poll, Waker}, + thread, + time::Duration, +}; + +/// Non-blocking timer future. +pub struct TimerFuture { + shared_state: Arc>, +} + +struct SharedState { + completed: bool, + waker: Option, +} + +impl Future for TimerFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut shared_state = self.shared_state.lock().unwrap(); + + if shared_state.completed { + Poll::Ready(()) + } else { + shared_state.waker = Some(cx.waker().clone()); + Poll::Pending + } + } +} + +impl TimerFuture { + pub fn new(duration: Duration) -> Self { + let shared_state = Arc::new(Mutex::new(SharedState { + completed: false, + waker: None, + })); + + let thread_shared_state = shared_state.clone(); + + // Let's mimic an event coming from somewhere else, like the system. + thread::spawn(move || { + thread::sleep(duration); + + let mut shared_state: MutexGuard<_> = thread_shared_state.lock().unwrap(); + shared_state.completed = true; + + if let Some(waker) = shared_state.waker.take() { + waker.wake(); + } + }); + + Self { shared_state } + } +} + +/// Sync function. +#[uniffi::export] +pub fn greet(who: String) -> String { + format!("Hello, {who}") +} + +/// Async function that is immediatly ready. +#[uniffi::export] +pub async fn always_ready() -> bool { + true +} + +#[uniffi::export] +pub async fn void() {} + +/// Async function that says something after 2s. +#[uniffi::export] +pub async fn say() -> String { + TimerFuture::new(Duration::from_secs(2)).await; + + "Hello, Future!".to_string() +} + +/// Async function that says something after a certain time. +#[uniffi::export] +pub async fn say_after(ms: u16, who: String) -> String { + TimerFuture::new(Duration::from_millis(ms.into())).await; + + format!("Hello, {who}!") +} + +/// Async function that sleeps! +#[uniffi::export] +pub async fn sleep(ms: u16) -> bool { + TimerFuture::new(Duration::from_millis(ms.into())).await; + + true +} + +// Our error. +#[derive(uniffi::Error, Debug)] +pub enum MyError { + Foo, +} + +// An async function that can throw. +#[uniffi::export] +pub async fn fallible_me(do_fail: bool) -> Result { + if do_fail { + Err(MyError::Foo) + } else { + Ok(42) + } +} + +// An async function returning a struct that can throw. +#[uniffi::export] +pub async fn fallible_struct(do_fail: bool) -> Result, MyError> { + if do_fail { + Err(MyError::Foo) + } else { + Ok(new_megaphone()) + } +} + +/// Sync function that generates a new `Megaphone`. +/// +/// It builds a `Megaphone` which has async methods on it. +#[uniffi::export] +pub fn new_megaphone() -> Arc { + Arc::new(Megaphone) +} + +/// Async function that generates a new `Megaphone`. +#[uniffi::export] +pub async fn async_new_megaphone() -> Arc { + new_megaphone() +} + +/// Async function that possibly generates a new `Megaphone`. +#[uniffi::export] +pub async fn async_maybe_new_megaphone(y: bool) -> Option> { + if y { + Some(new_megaphone()) + } else { + None + } +} + +/// A megaphone. Be careful with the neighbours. +#[derive(uniffi::Object)] +pub struct Megaphone; + +#[uniffi::export] +impl Megaphone { + /// An async method that yells something after a certain time. + pub async fn say_after(self: Arc, ms: u16, who: String) -> String { + say_after(ms, who).await.to_uppercase() + } + + // An async method that can throw. + pub async fn fallible_me(self: Arc, do_fail: bool) -> Result { + if do_fail { + Err(MyError::Foo) + } else { + Ok(42) + } + } +} + +// TODO depends on https://github.com/mozilla/uniffi-rs/commit/b7d7bbd4a22733e1856a346af97e7bc974c44e61 +// The async_runtime attribute used to error when *any* function in the impl block was not async, +// now it should work as long as at least one function *is* async. +// #[uniffi::export(async_runtime = "tokio")] +// impl Megaphone { +// /// A sync method that yells something immediately. +// pub fn say_now(&self, who: String) -> String { +// format!("Hello, {who}!").to_uppercase() +// } +// +// /// An async method that yells something after a certain time. +// /// +// /// Uses tokio's timer functionality. +// pub async fn say_after_with_tokio(self: Arc, ms: u16, who: String) -> String { +// say_after_with_tokio(ms, who).await.to_uppercase() +// } +// } + +// Say something after a certain amount of time, by using `tokio::time::sleep` +// instead of our own `TimerFuture`. +#[uniffi::export(async_runtime = "tokio")] +pub async fn say_after_with_tokio(ms: u16, who: String) -> String { + tokio::time::sleep(Duration::from_millis(ms.into())).await; + + format!("Hello, {who} (with Tokio)!") +} + +#[derive(uniffi::Record)] +pub struct MyRecord { + pub a: String, + pub b: u32, +} + +#[uniffi::export] +pub async fn new_my_record(a: String, b: u32) -> MyRecord { + MyRecord { a, b } +} + +/// Non-blocking timer future. +pub struct BrokenTimerFuture { + shared_state: Arc>, +} + +impl Future for BrokenTimerFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut shared_state = self.shared_state.lock().unwrap(); + + if shared_state.completed { + Poll::Ready(()) + } else { + shared_state.waker = Some(cx.waker().clone()); + Poll::Pending + } + } +} + +impl BrokenTimerFuture { + pub fn new(duration: Duration, fail_after: Duration) -> Self { + let shared_state = Arc::new(Mutex::new(SharedState { + completed: false, + waker: None, + })); + + let thread_shared_state = shared_state.clone(); + + // Let's mimic an event coming from somewhere else, like the system. + thread::spawn(move || { + thread::sleep(duration); + + let mut shared_state: MutexGuard<_> = thread_shared_state.lock().unwrap(); + shared_state.completed = true; + + if let Some(waker) = shared_state.waker.take() { + // Do not consume `waker`. + waker.wake_by_ref(); + + // And this is the important part. We are going to call + // `wake()` a second time. That's incorrect, but that's on + // purpose, to see how foreign languages will react. + if fail_after.is_zero() { + waker.wake(); + } else { + thread::spawn(move || { + thread::sleep(fail_after); + waker.wake(); + }); + } + } + }); + + Self { shared_state } + } +} + +/// Async function that sleeps! +#[uniffi::export] +pub async fn broken_sleep(ms: u16, fail_after: u16) { + BrokenTimerFuture::new( + Duration::from_millis(ms.into()), + Duration::from_millis(fail_after.into()), + ) + .await; +} + +uniffi::include_scaffolding!("futures"); diff --git a/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/uniffi.toml b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/uniffi.toml new file mode 100644 index 00000000..ff61fea5 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/futures/uniffi/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.kotlin-native] +library_file = "target/debug/libfutures.a" diff --git a/uniffi-kotlin-multiplatform-bindings/tests/gradle.properties b/uniffi-kotlin-multiplatform-bindings/tests/gradle.properties new file mode 100644 index 00000000..ce40ca38 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/gradle.properties @@ -0,0 +1,4 @@ +kotlin.code.style=official +kotlin.mpp.enableGranularSourceSetsMetadata=true +kotlin.native.enableDependencyPropagation=false +kotlin.native.cacheKind.linuxX64=none diff --git a/uniffi-kotlin-multiplatform-bindings/tests/gradle/wrapper/gradle-wrapper.jar b/uniffi-kotlin-multiplatform-bindings/tests/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..41d9927a Binary files /dev/null and b/uniffi-kotlin-multiplatform-bindings/tests/gradle/wrapper/gradle-wrapper.jar differ diff --git a/uniffi-kotlin-multiplatform-bindings/tests/gradle/wrapper/gradle-wrapper.properties b/uniffi-kotlin-multiplatform-bindings/tests/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..aa991fce --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/uniffi-kotlin-multiplatform-bindings/tests/gradlew b/uniffi-kotlin-multiplatform-bindings/tests/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/uniffi-kotlin-multiplatform-bindings/tests/gradlew.bat b/uniffi-kotlin-multiplatform-bindings/tests/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/uniffi-kotlin-multiplatform-bindings/tests/keywords/build.gradle.kts b/uniffi-kotlin-multiplatform-bindings/tests/keywords/build.gradle.kts new file mode 100644 index 00000000..e69de29b diff --git a/uniffi-kotlin-multiplatform-bindings/tests/keywords/src/commonTest/kotlin/KeywordsTest.kt b/uniffi-kotlin-multiplatform-bindings/tests/keywords/src/commonTest/kotlin/KeywordsTest.kt new file mode 100644 index 00000000..a780f6dc --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/keywords/src/commonTest/kotlin/KeywordsTest.kt @@ -0,0 +1,11 @@ +import keywords.* +import kotlin.test.Test + +class KeywordsTest { + + @Test + fun test() { + // able to import is the real test, but might as well call something. + `if`(1.toUByte()) + } +} \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/tests/keywords/src/nativeInterop/cinterop/uniffi.def b/uniffi-kotlin-multiplatform-bindings/tests/keywords/src/nativeInterop/cinterop/uniffi.def new file mode 100644 index 00000000..5f4be9d9 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/keywords/src/nativeInterop/cinterop/uniffi.def @@ -0,0 +1 @@ +staticLibraries = libkeywords.a \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/Cargo.toml b/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/Cargo.toml new file mode 100644 index 00000000..18cca1ff --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "keywords" +version = "0.1.0" +edition = "2021" + +[lib] +name = "keywords" +crate-type = ["cdylib", "staticlib"] + +[dependencies] +uniffi = "0.21.0" +uniffi_macros = "0.21.0" +once_cell = "1.12" +thiserror = "1.0" + +[build-dependencies] +uniffi_build = { version = "0.21.0", features = ["builtin-bindgen"] } +uniffi_bindgen = "0.21.0" +uniffi-kotlin-multiplatform = { path = "../../.." } +camino = "1.1.1" \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/build.rs b/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/build.rs new file mode 100644 index 00000000..e2089260 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/build.rs @@ -0,0 +1,18 @@ +use camino::Utf8Path; +use uniffi_kotlin_multiplatform::KotlinBindingGenerator; + +fn main() { + let tests = vec!["keywords"]; + let out_dir = Utf8Path::new("target/bindings"); + for test in tests.iter() { + let udl_file_path = format!("./src/{}.udl", test); + let udl_file = Utf8Path::new(&udl_file_path); + uniffi_build::generate_scaffolding(udl_file).unwrap(); + uniffi_bindgen::generate_external_bindings( + KotlinBindingGenerator {}, + udl_file, + None::<&Utf8Path>, + Some(out_dir), + ).unwrap(); + } +} diff --git a/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/src/keywords.udl b/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/src/keywords.udl new file mode 100644 index 00000000..b7dabe84 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/src/keywords.udl @@ -0,0 +1,43 @@ +namespace keywords { + void if(u8 break); +}; + +interface break { + void class(u8 object); + void object(u8? class); +}; + +callback interface continue { + return return(return v); + continue? continue(sequence continue); + record break(break? v); + while while(sequence while); + record>? class(record> v); +}; + +dictionary return { + u8 class; + u8? object; +}; + +dictionary while { + return class; + return? fun; + sequence object; + record break; +}; + +[Enum] +interface false { + true(u8 object); +}; + +[Error] +enum class { + "object", +}; + +[Error] +interface fun { + class(u8 object); +}; diff --git a/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/src/lib.rs b/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/src/lib.rs new file mode 100644 index 00000000..7c99bcfe --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/keywords/uniffi/src/lib.rs @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{collections::HashMap, sync::Arc}; + +pub fn r#if(_object: u8) {} + +#[allow(non_camel_case_types)] +pub struct r#break {} + +impl r#break { + pub fn class(&self, _object: u8) {} + pub fn object(&self, _class: Option) {} +} + +#[allow(non_camel_case_types)] +trait r#continue { + fn r#return(&self, v: r#return) -> r#return; + fn r#continue(&self, v: Vec>) -> Option>; + fn r#break(&self, _v: Option>) -> HashMap>; + fn r#while(&self, _v: Vec) -> r#while; + fn class(&self, _v: HashMap>) -> Option>>; +} + +#[allow(non_camel_case_types)] +pub struct r#return { + class: u8, + object: Option, +} + +#[allow(non_camel_case_types)] +pub struct r#while { + class: r#return, + fun: Option, + object: Vec, + r#break: HashMap, +} + +#[allow(non_camel_case_types)] +pub enum r#false { + #[allow(non_camel_case_types)] + r#true { object: u8 }, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, thiserror::Error)] +pub enum class { + #[error("object error")] + object, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, thiserror::Error)] +pub enum fun { + #[error("class?")] + class { object: u8 }, +} + +include!(concat!(env!("OUT_DIR"), "/keywords.uniffi.rs")); \ No newline at end of file diff --git a/uniffi-kotlin-multiplatform-bindings/tests/settings.gradle.kts b/uniffi-kotlin-multiplatform-bindings/tests/settings.gradle.kts new file mode 100644 index 00000000..98f10692 --- /dev/null +++ b/uniffi-kotlin-multiplatform-bindings/tests/settings.gradle.kts @@ -0,0 +1,8 @@ +rootProject.name = "tests" +include( + "callbacks", + "coverall" , + "external_types", + "futures" +// "keywords", // fails on native: https://youtrack.jetbrains.com/issue/KT-55154/cinterop-function-paramter-is-not-backquoted +) diff --git a/uniffi.toml b/uniffi.toml new file mode 100644 index 00000000..33428aa3 --- /dev/null +++ b/uniffi.toml @@ -0,0 +1,2 @@ +[bindings.kotlin-native] +library_file = "target/aarch64-linux-android/debug/libaries_askar.a" diff --git a/uniffi/askar.udl b/uniffi/askar.udl new file mode 100644 index 00000000..e2723534 --- /dev/null +++ b/uniffi/askar.udl @@ -0,0 +1,24 @@ +interface AskarEntry {}; +interface AskarKeyEntry {}; +interface AskarScan {}; +interface AskarSession {}; +interface AskarStore {}; +interface AskarLocalKey {}; +interface EncryptedBuffer {}; +interface LocalKeyFactory { + constructor(); +}; +interface AskarStoreManager { + constructor(); +}; +interface AskarCrypto { + constructor(); +}; +interface AskarEcdhEs { + constructor(string alg_id, string apu, string apv); +}; +interface AskarEcdh1PU { + constructor(string alg_id, string apu, string apv); +}; + +namespace aries_askar {}; diff --git a/uniffi/uniffi-bindgen.rs b/uniffi/uniffi-bindgen.rs new file mode 100644 index 00000000..1ba7728c --- /dev/null +++ b/uniffi/uniffi-bindgen.rs @@ -0,0 +1,13 @@ +use uniffi_kotlin_multiplatform::KotlinBindingGenerator; +use camino::Utf8Path; + +fn main() { + let out_dir = Utf8Path::new("target/kotlin"); + let config = Utf8Path::new("./uniffi.toml"); + uniffi_bindgen::generate_external_bindings( + KotlinBindingGenerator {}, + "./uniffi/askar.udl", + None::<&Utf8Path>, + Some(out_dir) + ).unwrap(); +} diff --git a/wrappers/javascript/.eslintignore b/wrappers/javascript/.eslintignore new file mode 100644 index 00000000..830ded42 --- /dev/null +++ b/wrappers/javascript/.eslintignore @@ -0,0 +1,4 @@ +react-native-example/metro.config.js +react-native-example/babel.config.js +**/build/** +build/** \ No newline at end of file diff --git a/wrappers/javascript/.eslintrc.js b/wrappers/javascript/.eslintrc.js new file mode 100644 index 00000000..f3e5b276 --- /dev/null +++ b/wrappers/javascript/.eslintrc.js @@ -0,0 +1,86 @@ +module.exports = { + parser: '@typescript-eslint/parser', + extends: [ + 'eslint:recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:prettier/recommended', + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: [ + './tsconfig.eslint.json', + './aries-askar-shared/tsconfig.json', + './aries-askar-react-native/tsconfig.json', + './aries-askar-nodejs/tsconfig.json', + ], + }, + settings: { + 'import/extensions': ['.js', '.ts', '.jsx', '.tsx'], + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { + typescript: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + project: [ + 'aries-askar-shared/tsconfig.json', + 'aries-askar-react-native/tsconfig.json', + 'aries-askar-nodejs/tsconfig.json', + ], + alwaysTryTypes: true, + }, + node: { + project: [ + 'aries-askar-shared/tsconfig.json', + 'aries-askar-react-native/tsconfig.json', + 'aries-askar-nodejs/tsconfig.json', + ], + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + }, + }, + rules: { + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-use-before-define': ['error', { functions: false, classes: false, variables: true }], + '@typescript-eslint/explicit-member-accessibility': 'error', + 'no-console': 'error', + '@typescript-eslint/consistent-type-imports': 'error', + 'import/no-cycle': 'error', + 'import/newline-after-import': ['error', { count: 1 }], + 'import/order': [ + 'error', + { + groups: ['type', ['builtin', 'external'], 'parent', 'sibling', 'index'], + alphabetize: { + order: 'asc', + }, + 'newlines-between': 'always', + }, + ], + '@typescript-eslint/no-non-null-assertion': 'error', + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: false, + }, + ], + }, + overrides: [ + { + files: ['.eslintrc.js', 'babel.config.js'], + env: { + node: true, + }, + rules: { + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + }, + }, + ], +} diff --git a/wrappers/javascript/.gitignore b/wrappers/javascript/.gitignore new file mode 100644 index 00000000..ff49519a --- /dev/null +++ b/wrappers/javascript/.gitignore @@ -0,0 +1,63 @@ +.expo/ +jsconfig.json + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +.classpath +.cxx +.gradle +.idea +.project +.settings +local.properties +android.iml + + +# node.js +node_modules/ +npm-debug.log +yarn-debug.log +yarn-error.log +.yarn +.yarnrc + +# BUCK +buck-out/ +\.buckd/ +android/app/libs +android/keystores/debug.keystore + +# Expo +.expo/* +.expo-shared/* + +# Native Libraries +aries-askar-react-native/ios/Frameworks +aries-askar-react-native/android/libs + +# Test wallet +aries-askar-nodejs/tests/indy_wallet_sqlite_upgraded.db + +# Example app +react-native-example + +# Test +tmp diff --git a/wrappers/javascript/.prettierrc b/wrappers/javascript/.prettierrc new file mode 100644 index 00000000..cbe842ac --- /dev/null +++ b/wrappers/javascript/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 120, + "semi": false, + "singleQuote": true +} diff --git a/wrappers/javascript/aries-askar-nodejs/README.md b/wrappers/javascript/aries-askar-nodejs/README.md new file mode 100644 index 00000000..daae85ef --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/README.md @@ -0,0 +1,34 @@ +# Aries Askar NodeJS + +Wrapper for Nodejs around Aries Askar + +## Requirements + +This library requires (and has been tested extensively with) Node.js version `18.x`. Newer versions might also work, but they have not been tested. + +## Installation + +```sh +yarn add @hyperledger/aries-askar-nodejs +``` + +## Setup + +You can import all types and classes from the `@hyperledger/aries-askar-nodejs` library: + +```typescript +import { Key, KeyAlgs } from '@hyperledger/aries-askar-nodejs' + +const seed = Uint8Array.from(Buffer.from('testseed000000000000000000000001')) +const key = Key.fromSeed({ algorithm: KeyAlgs.Bls12381G1, seed }) +``` + +> **Note**: If you want to use this library in a cross-platform environment you need to import methods from the `@hyperledger/aries-askar-shared` package instead. This is a platform independent package that allows to register the native bindings. The `@hyperledger/aries-askar-nodejs` package uses this package under the hood. See the [Aries Askar Shared README](https://github.com/hyperledger/aries-askar/tree/main/wrappers/javascript/aries-askar-shared/README.md) for documentation on how to use this package. + +## Version Compatibility + +The JavaScript wrapper is versioned independently from the native bindings. The following table shows the compatibility between the different versions: + +| Aries Askar | JavaScript Wrapper | +| ----------- | ------------------ | +| v0.2.9 | v0.1.0, v0.1.1 | diff --git a/wrappers/javascript/aries-askar-nodejs/jest.config.ts b/wrappers/javascript/aries-askar-nodejs/jest.config.ts new file mode 100644 index 00000000..8cabdf60 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/jest.config.ts @@ -0,0 +1,14 @@ +import type { Config } from '@jest/types' + +import base from '../jest.config.base' + +import packageJson from './package.json' + +const config: Config.InitialOptions = { + ...base, + displayName: packageJson.name, + verbose: true, + testTimeout: 120000, +} + +export default config diff --git a/wrappers/javascript/aries-askar-nodejs/native/aries_askar.node b/wrappers/javascript/aries-askar-nodejs/native/aries_askar.node new file mode 100644 index 00000000..5179558d --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/native/aries_askar.node @@ -0,0 +1,3 @@ +# This is a placeholder file to prevent node-pre-gyp from installing the aries_askar binary when cloning +# this repository. It won't be published to NPM, meaning when you download this package from npm it +# will try to download the binary. \ No newline at end of file diff --git a/wrappers/javascript/aries-askar-nodejs/package.json b/wrappers/javascript/aries-askar-nodejs/package.json new file mode 100644 index 00000000..37808cb8 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/package.json @@ -0,0 +1,69 @@ +{ + "name": "@hyperledger/aries-askar-nodejs", + "version": "0.1.1", + "license": "Apache-2.0", + "description": "Nodejs wrapper for Aries Askar", + "source": "src/index", + "main": "build/index", + "homepage": "https://github.com/hyperledger/aries-askar/tree/main/wrappers/javascript/aries-askar-nodejs", + "author": "Hyperledger (https://github.com/hyperledger)", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-askar", + "directory": "wrappers/javascript/aries-askar-nodejs" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "build", + "scripts" + ], + "scripts": { + "lint": "eslint .", + "check-types": "tsc --noEmit -p tsconfig.build.json", + "check-format": "yarn prettier --check", + "format": "yarn prettier --write", + "prettier": "prettier --ignore-path .gitignore '**/*+(.js|json|ts|md|yml|yaml)'", + "build": "yarn clean && yarn compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "example": "yarn --cwd example", + "release": "release-it", + "test": "jest", + "install": "node-pre-gyp install --target_arch=$(node scripts/arch.js) --target_platform=$(node scripts/platform.js)" + }, + "devDependencies": { + "@types/2060.io__ffi-napi": "npm:@types/ffi-napi", + "@types/2060.io__ref-napi": "npm:@types/ref-napi", + "@types/jest": "^27.4.1", + "@types/node": "^17.0.31", + "@types/ref-array-di": "^1.2.3", + "@types/ref-struct-di": "^1.1.6", + "base64url": "^3.0.1", + "eslint-plugin-import": "^2.26.0", + "jest": "^28.0.2", + "prettier": "2.6.2", + "ts-node": "^10.8.1", + "typescript": "4.5.5" + }, + "dependencies": { + "@hyperledger/aries-askar-shared": "0.1.1", + "@mapbox/node-pre-gyp": "^1.0.10", + "@2060.io/ffi-napi": "4.0.5", + "@2060.io/ref-napi": "3.0.4", + "node-cache": "^5.1.2", + "ref-array-di": "^1.2.2", + "ref-struct-di": "^1.1.1" + }, + "binary": { + "module_name": "aries_askar", + "module_path": "native", + "remote_path": "v0.2.9", + "host": "https://github.com/hyperledger/aries-askar/releases/download/", + "package_name": "library-{platform}-{arch}.tar.gz" + }, + "engines": { + "node": ">= 18" + } +} diff --git a/wrappers/javascript/aries-askar-nodejs/scripts/arch.js b/wrappers/javascript/aries-askar-nodejs/scripts/arch.js new file mode 100644 index 00000000..6a2386a4 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/scripts/arch.js @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable no-undef */ +const os = require('os') + +// Find appropriate target architecture for retrieving the anoncreds library +const arch = os.arch() +const platform = os.platform() + +// Architecture mapping +// This is used because node-pre-gyp uses `os.arch()` for +// architecture detection, but our library uses a different +// naming convention +const archTable = { + x64: 'x86_64', + arm64: 'aarch64', +} + +const targetArchitecture = platform === 'darwin' ? 'universal' : archTable[arch] + +if (targetArchitecture) { + // We console.log here because when we use the `yarn install` script + // er evaluate this script and use the output as an argument to + // node-pre-gyp as `--arch=$(node -e arch.js)` + console.log(targetArchitecture) +} diff --git a/wrappers/javascript/aries-askar-nodejs/scripts/platform.js b/wrappers/javascript/aries-askar-nodejs/scripts/platform.js new file mode 100644 index 00000000..feea7aa9 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/scripts/platform.js @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable no-undef */ +const os = require('os') + +// Find appropriate target architecture for retrieving the anoncreds library +const platform = os.platform() + +// We swap win32 with windows as that is the key that we use +const targetPlatform = platform == 'win32' ? 'windows' : platform + +console.log(targetPlatform) diff --git a/wrappers/javascript/aries-askar-nodejs/src/NodeJSAriesAskar.ts b/wrappers/javascript/aries-askar-nodejs/src/NodeJSAriesAskar.ts new file mode 100644 index 00000000..9a961bdc --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/NodeJSAriesAskar.ts @@ -0,0 +1,992 @@ +import type { + ByteBufferType, + EncryptedBufferType, + NativeCallback, + NativeCallbackWithResponse, + SecretBufferType, +} from './ffi' +import type { + AriesAskar, + EntryListCountOptions, + EntryListFreeOptions, + EntryListGetCategoryOptions, + EntryListGetNameOptions, + EntryListGetTagsOptions, + EntryListGetValueOptions, + KeyAeadDecryptOptions, + KeyAeadEncryptOptions, + KeyAeadGetPaddingOptions, + KeyAeadGetParamsOptions, + KeyAeadRandomNonceOptions, + KeyConvertOptions, + KeyCryptoBoxOpenOptions, + KeyCryptoBoxOptions, + KeyCryptoBoxSealOpenOptions, + KeyCryptoBoxSealOptions, + KeyDeriveEcdh1puOptions, + KeyDeriveEcdhEsOptions, + KeyEntryListCountOptions, + KeyEntryListFreeOptions, + KeyEntryListGetAlgorithmOptions, + KeyEntryListGetMetadataOptions, + KeyEntryListGetNameOptions, + KeyEntryListGetTagsOptions, + KeyEntryListLoadLocalOptions, + KeyFreeOptions, + KeyFromJwkOptions, + KeyFromKeyExchangeOptions, + KeyFromPublicBytesOptions, + KeyFromSecretBytesOptions, + KeyFromSeedOptions, + KeyGenerateOptions, + KeyGetAlgorithmOptions, + KeyGetEphemeralOptions, + KeyGetJwkPublicOptions, + KeyGetJwkSecretOptions, + KeyGetJwkThumbprintOptions, + KeyGetPublicBytesOptions, + KeyGetSecretBytesOptions, + KeySignMessageOptions, + KeyUnwrapKeyOptions, + KeyVerifySignatureOptions, + KeyWrapKeyOptions, + ScanFreeOptions, + ScanNextOptions, + ScanStartOptions, + SessionCloseOptions, + SessionCountOptions, + SessionFetchAllKeysOptions, + SessionFetchAllOptions, + SessionFetchKeyOptions, + SessionFetchOptions, + SessionInsertKeyOptions, + SessionRemoveAllOptions, + SessionRemoveKeyOptions, + SessionStartOptions, + SessionUpdateKeyOptions, + SessionUpdateOptions, + SetCustomLoggerOptions, + SetMaxLogLevelOptions, + StoreCloseOptions, + StoreCreateProfileOptions, + StoreGenerateRawKeyOptions, + StoreGetProfileNameOptions, + StoreOpenOptions, + StoreProvisionOptions, + StoreRekeyOptions, + StoreRemoveOptions, + StoreRemoveProfileOptions, + EncryptedBuffer, + AriesAskarErrorObject, + AeadParamsOptions, + MigrateIndySdkOptions, +} from '@hyperledger/aries-askar-shared' + +import { + handleInvalidNullResponse, + AriesAskarError, + ScanHandle, + EntryListHandle, + StoreHandle, + LocalKeyHandle, + AeadParams, + SessionHandle, + KeyEntryListHandle, +} from '@hyperledger/aries-askar-shared' + +import { + serializeArguments, + encryptedBufferStructToClass, + deallocateCallbackBuffer, + toNativeCallback, + FFI_STRING, + allocateStringBuffer, + toNativeCallbackWithResponse, + toNativeLogCallback, + allocateInt32Buffer, + allocateSecretBuffer, + secretBufferToBuffer, + allocateEncryptedBuffer, + allocateAeadParams, + allocatePointer, + allocateInt8Buffer, + FFI_ENTRY_LIST_HANDLE, + FFI_SCAN_HANDLE, + FFI_INT64, + FFI_KEY_ENTRY_LIST_HANDLE, + FFI_SESSION_HANDLE, + FFI_STORE_HANDLE, + FFI_INT8, +} from './ffi' +import { getNativeAriesAskar } from './library' + +function handleNullableReturnPointer(returnValue: Buffer): Return | null { + if (returnValue.address() === 0) return null + return returnValue.deref() as Return +} + +function handleReturnPointer(returnValue: Buffer): Return { + if (returnValue.address() === 0) { + throw AriesAskarError.customError({ message: 'Unexpected null pointer' }) + } + + return returnValue.deref() as Return +} + +export class NodeJSAriesAskar implements AriesAskar { + private promisify = async (method: (nativeCallbackPtr: Buffer, id: number) => void): Promise => { + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const cb: NativeCallback = (id, _) => { + deallocateCallbackBuffer(id) + + try { + this.handleError() + } catch (e) { + reject(e) + } + + resolve() + } + const { nativeCallback, id } = toNativeCallback(cb) + method(nativeCallback, +id) + }) + } + + private promisifyWithResponse = async ( + method: (nativeCallbackWithResponsePtr: Buffer, id: number) => void, + responseFfiType = FFI_STRING + ): Promise => { + return new Promise((resolve, reject) => { + const cb: NativeCallbackWithResponse = (id, errorCode, response) => { + deallocateCallbackBuffer(id) + + if (errorCode) { + const nativeError = allocateStringBuffer() + this.nativeAriesAskar.askar_get_current_error(nativeError) + return reject(new AriesAskarError(JSON.parse(nativeError.deref() as string) as AriesAskarErrorObject)) + } + + if (typeof response === 'string') { + if (responseFfiType === FFI_STRING) resolve(response as unknown as Return) + try { + resolve(JSON.parse(response) as Return) + } catch (error) { + reject(error) + } + } else if (typeof response === 'number') { + resolve(response as unknown as Return) + } else if (response instanceof Buffer) { + if (response.address() === 0) return resolve(null) + + resolve(response as unknown as Return) + } + + reject(`could not parse return type properly (type: ${typeof response})`) + } + const { nativeCallback, id } = toNativeCallbackWithResponse(cb, responseFfiType) + method(nativeCallback, +id) + }) + } + + private handleError() { + const nativeError = allocateStringBuffer() + this.nativeAriesAskar.askar_get_current_error(nativeError) + + const ariesAskarErrorObject = JSON.parse(nativeError.deref() as string) as AriesAskarErrorObject + + if (ariesAskarErrorObject.code === 0) return + + throw new AriesAskarError(ariesAskarErrorObject) + } + public get nativeAriesAskar() { + return getNativeAriesAskar() + } + + public version(): string { + return this.nativeAriesAskar.askar_version() + } + + public getCurrentError(): string { + const error = allocateStringBuffer() + this.nativeAriesAskar.askar_get_current_error(error) + this.handleError() + return handleReturnPointer(error) + } + + public clearCustomLogger(): void { + this.nativeAriesAskar.askar_clear_custom_logger() + this.handleError() + } + + // TODO: the id has to be deallocated when its done, but how? + public setCustomLogger({ logLevel, flush = false, enabled = false, logger }: SetCustomLoggerOptions): void { + const { id, nativeCallback } = toNativeLogCallback(logger) + + // TODO: flush and enabled are just guessed + this.nativeAriesAskar.askar_set_custom_logger(0, nativeCallback, +enabled, +flush, logLevel) + this.handleError() + deallocateCallbackBuffer(+id) + } + + public setDefaultLogger(): void { + this.nativeAriesAskar.askar_set_default_logger() + this.handleError() + } + + public setMaxLogLevel(options: SetMaxLogLevelOptions): void { + const { logLevel } = serializeArguments(options) + + this.nativeAriesAskar.askar_set_max_log_level(logLevel) + this.handleError() + } + + public entryListCount(options: EntryListCountOptions): number { + const { entryListHandle } = serializeArguments(options) + const ret = allocateInt32Buffer() + + this.nativeAriesAskar.askar_entry_list_count(entryListHandle, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public entryListFree(options: EntryListFreeOptions): void { + const { entryListHandle } = serializeArguments(options) + + this.nativeAriesAskar.askar_entry_list_free(entryListHandle) + this.handleError() + } + + public entryListGetCategory(options: EntryListGetCategoryOptions): string { + const { entryListHandle, index } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_entry_list_get_category(entryListHandle, index, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public entryListGetName(options: EntryListGetNameOptions): string { + const { entryListHandle, index } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_entry_list_get_name(entryListHandle, index, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public entryListGetTags(options: EntryListGetTagsOptions): string | null { + const { entryListHandle, index } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_entry_list_get_tags(entryListHandle, index, ret) + this.handleError() + + return handleNullableReturnPointer(ret) + } + + public entryListGetValue(options: EntryListGetValueOptions): Uint8Array { + const { entryListHandle, index } = serializeArguments(options) + + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_entry_list_get_value(entryListHandle, index, ret) + this.handleError() + + const byteBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(byteBuffer)) + } + + public keyAeadDecrypt(options: KeyAeadDecryptOptions): Uint8Array { + const { aad, ciphertext, localKeyHandle, nonce, tag } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_aead_decrypt(localKeyHandle, ciphertext, nonce, tag, aad, ret) + this.handleError() + + const byteBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(byteBuffer)) + } + + public keyAeadEncrypt(options: KeyAeadEncryptOptions): EncryptedBuffer { + const { localKeyHandle, aad, nonce, message } = serializeArguments(options) + const ret = allocateEncryptedBuffer() + + this.nativeAriesAskar.askar_key_aead_encrypt(localKeyHandle, message, nonce, aad, ret) + this.handleError() + + const encryptedBuffer = handleReturnPointer(ret) + return encryptedBufferStructToClass(encryptedBuffer) + } + + public keyAeadGetPadding(options: KeyAeadGetPaddingOptions): number { + const { localKeyHandle, msgLen } = serializeArguments(options) + const ret = allocateInt32Buffer() + + this.nativeAriesAskar.askar_key_aead_get_padding(localKeyHandle, msgLen, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public keyAeadGetParams(options: KeyAeadGetParamsOptions): AeadParams { + const { localKeyHandle } = serializeArguments(options) + const ret = allocateAeadParams() + + this.nativeAriesAskar.askar_key_aead_get_params(localKeyHandle, ret) + this.handleError() + + return new AeadParams(handleReturnPointer(ret)) + } + + public keyAeadRandomNonce(options: KeyAeadRandomNonceOptions): Uint8Array { + const { localKeyHandle } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_aead_random_nonce(localKeyHandle, ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keyConvert(options: KeyConvertOptions): LocalKeyHandle { + const { localKeyHandle, algorithm } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_convert(localKeyHandle, algorithm, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyCryptoBox(options: KeyCryptoBoxOptions): Uint8Array { + const { nonce, message, recipientKey, senderKey } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_crypto_box(recipientKey, senderKey, message, nonce, ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keyCryptoBoxOpen(options: KeyCryptoBoxOpenOptions): Uint8Array { + const { nonce, message, senderKey, recipientKey } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_crypto_box_open(recipientKey, senderKey, message, nonce, ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keyCryptoBoxRandomNonce(): Uint8Array { + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_crypto_box_random_nonce(ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keyCryptoBoxSeal(options: KeyCryptoBoxSealOptions): Uint8Array { + const { message, localKeyHandle } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_crypto_box_seal(localKeyHandle, message, ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keyCryptoBoxSealOpen(options: KeyCryptoBoxSealOpenOptions): Uint8Array { + const { ciphertext, localKeyHandle } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_crypto_box_seal_open(localKeyHandle, ciphertext, ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keyDeriveEcdh1pu(options: KeyDeriveEcdh1puOptions): LocalKeyHandle { + const { senderKey, recipientKey, algorithm, algId, apu, apv, ccTag, ephemeralKey, receive } = + serializeArguments(options) + + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_derive_ecdh_1pu( + algorithm, + ephemeralKey, + senderKey, + recipientKey, + algId, + apu, + apv, + ccTag, + receive, + ret + ) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyDeriveEcdhEs(options: KeyDeriveEcdhEsOptions): LocalKeyHandle { + const { receive, apv, apu, algId, recipientKey, ephemeralKey, algorithm } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_derive_ecdh_es(algorithm, ephemeralKey, recipientKey, algId, apu, apv, receive, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyEntryListCount(options: KeyEntryListCountOptions): number { + const { keyEntryListHandle } = serializeArguments(options) + const ret = allocateInt32Buffer() + + this.nativeAriesAskar.askar_key_entry_list_count(keyEntryListHandle, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public keyEntryListFree(options: KeyEntryListFreeOptions): void { + const { keyEntryListHandle } = serializeArguments(options) + + this.nativeAriesAskar.askar_key_entry_list_free(keyEntryListHandle) + this.handleError() + } + + public keyEntryListGetAlgorithm(options: KeyEntryListGetAlgorithmOptions): string { + const { keyEntryListHandle, index } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_key_entry_list_get_algorithm(keyEntryListHandle, index, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public keyEntryListGetMetadata(options: KeyEntryListGetMetadataOptions): string | null { + const { keyEntryListHandle, index } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_key_entry_list_get_metadata(keyEntryListHandle, index, ret) + this.handleError() + + return handleNullableReturnPointer(ret) + } + + public keyEntryListGetName(options: KeyEntryListGetNameOptions): string { + const { keyEntryListHandle, index } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_key_entry_list_get_name(keyEntryListHandle, index, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public keyEntryListGetTags(options: KeyEntryListGetTagsOptions): string | null { + const { keyEntryListHandle, index } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_key_entry_list_get_tags(keyEntryListHandle, index, ret) + + return handleNullableReturnPointer(ret) + } + + public keyEntryListLoadLocal(options: KeyEntryListLoadLocalOptions): LocalKeyHandle { + const { index, keyEntryListHandle } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_entry_list_load_local(keyEntryListHandle, index, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyFree(options: KeyFreeOptions): void { + const { localKeyHandle } = serializeArguments(options) + + this.nativeAriesAskar.askar_key_free(localKeyHandle) + this.handleError() + } + + public keyFromJwk(options: KeyFromJwkOptions): LocalKeyHandle { + const { jwk } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_from_jwk(jwk, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyFromKeyExchange(options: KeyFromKeyExchangeOptions): LocalKeyHandle { + const { algorithm, pkHandle, skHandle } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_from_key_exchange(algorithm, skHandle, pkHandle, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyFromPublicBytes(options: KeyFromPublicBytesOptions): LocalKeyHandle { + const { publicKey, algorithm } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_from_public_bytes(algorithm, publicKey, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyFromSecretBytes(options: KeyFromSecretBytesOptions): LocalKeyHandle { + const { secretKey, algorithm } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_from_secret_bytes(algorithm, secretKey, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyFromSeed(options: KeyFromSeedOptions): LocalKeyHandle { + const { algorithm, method, seed } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_from_seed(algorithm, seed, method, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyGenerate(options: KeyGenerateOptions): LocalKeyHandle { + const { algorithm, ephemeral } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_generate(algorithm, ephemeral, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyGetAlgorithm(options: KeyGetAlgorithmOptions): string { + const { localKeyHandle } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_key_get_algorithm(localKeyHandle, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public keyGetEphemeral(options: KeyGetEphemeralOptions): number { + const { localKeyHandle } = serializeArguments(options) + const ret = allocateInt32Buffer() + + this.nativeAriesAskar.askar_key_get_ephemeral(localKeyHandle, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public keyGetJwkPublic(options: KeyGetJwkPublicOptions): string { + const { localKeyHandle, algorithm } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_key_get_jwk_public(localKeyHandle, algorithm, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public keyGetJwkSecret(options: KeyGetJwkSecretOptions): Uint8Array { + const { localKeyHandle } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_get_jwk_secret(localKeyHandle, ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keyGetJwkThumbprint(options: KeyGetJwkThumbprintOptions): string { + const { localKeyHandle, algorithm } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_key_get_jwk_thumbprint(localKeyHandle, algorithm, ret) + this.handleError() + + return handleReturnPointer(ret) + } + + public keyGetPublicBytes(options: KeyGetPublicBytesOptions): Uint8Array { + const { localKeyHandle } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_get_public_bytes(localKeyHandle, ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keyGetSecretBytes(options: KeyGetSecretBytesOptions): Uint8Array { + const { localKeyHandle } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_get_secret_bytes(localKeyHandle, ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keySignMessage(options: KeySignMessageOptions): Uint8Array { + const { localKeyHandle, message, sigType } = serializeArguments(options) + const ret = allocateSecretBuffer() + + this.nativeAriesAskar.askar_key_sign_message(localKeyHandle, message, sigType, ret) + this.handleError() + + const secretBuffer = handleReturnPointer(ret) + return new Uint8Array(secretBufferToBuffer(secretBuffer)) + } + + public keyUnwrapKey(options: KeyUnwrapKeyOptions): LocalKeyHandle { + const { localKeyHandle, algorithm, ciphertext, nonce, tag } = serializeArguments(options) + const ret = allocatePointer() + + this.nativeAriesAskar.askar_key_unwrap_key(localKeyHandle, algorithm, ciphertext, nonce, tag, ret) + this.handleError() + + const handle = handleReturnPointer(ret) + return new LocalKeyHandle(handle) + } + + public keyVerifySignature(options: KeyVerifySignatureOptions): boolean { + const { localKeyHandle, sigType, message, signature } = serializeArguments(options) + const ret = allocateInt8Buffer() + + this.nativeAriesAskar.askar_key_verify_signature(localKeyHandle, message, signature, sigType, ret) + this.handleError() + + return Boolean(handleReturnPointer(ret)) + } + + public keyWrapKey(options: KeyWrapKeyOptions): EncryptedBuffer { + const { localKeyHandle, nonce, other } = serializeArguments(options) + const ret = allocateEncryptedBuffer() + + this.nativeAriesAskar.askar_key_wrap_key(localKeyHandle, other, nonce, ret) + this.handleError() + + const encryptedBuffer = handleReturnPointer(ret) + return encryptedBufferStructToClass(encryptedBuffer) + } + + public scanFree(options: ScanFreeOptions): void { + const { scanHandle } = serializeArguments(options) + + this.nativeAriesAskar.askar_scan_free(scanHandle) + this.handleError() + } + + public async scanNext(options: ScanNextOptions): Promise { + const { scanHandle } = serializeArguments(options) + + const handle = await this.promisifyWithResponse( + (cb, cbId) => this.nativeAriesAskar.askar_scan_next(scanHandle, cb, cbId), + FFI_ENTRY_LIST_HANDLE + ) + + return EntryListHandle.fromHandle(handle) + } + + public async scanStart(options: ScanStartOptions): Promise { + const { category, limit, offset, profile, storeHandle, tagFilter } = serializeArguments(options) + const handle = await this.promisifyWithResponse( + (cb, cbId) => + this.nativeAriesAskar.askar_scan_start( + storeHandle, + profile, + category, + tagFilter, + +offset || 0, + +limit || -1, + cb, + cbId + ), + FFI_SCAN_HANDLE + ) + + return ScanHandle.fromHandle(handle) + } + + public async sessionClose(options: SessionCloseOptions): Promise { + const { commit, sessionHandle } = serializeArguments(options) + + return await this.promisify((cb, cbId) => + this.nativeAriesAskar.askar_session_close(sessionHandle, commit, cb, cbId) + ) + } + + public async sessionCount(options: SessionCountOptions): Promise { + const { sessionHandle, tagFilter, category } = serializeArguments(options) + const response = await this.promisifyWithResponse( + (cb, cbId) => this.nativeAriesAskar.askar_session_count(sessionHandle, category, tagFilter, cb, cbId), + FFI_INT64 + ) + + return handleInvalidNullResponse(response) + } + + public async sessionFetch(options: SessionFetchOptions): Promise { + const { name, category, sessionHandle, forUpdate } = serializeArguments(options) + const handle = await this.promisifyWithResponse( + (cb, cbId) => this.nativeAriesAskar.askar_session_fetch(sessionHandle, category, name, forUpdate, cb, cbId), + FFI_ENTRY_LIST_HANDLE + ) + + return EntryListHandle.fromHandle(handle) + } + + public async sessionFetchAll(options: SessionFetchAllOptions): Promise { + const { forUpdate, sessionHandle, tagFilter, limit, category } = serializeArguments(options) + + const handle = await this.promisifyWithResponse( + (cb, cbId) => + this.nativeAriesAskar.askar_session_fetch_all( + sessionHandle, + category, + tagFilter, + +limit || -1, + forUpdate, + cb, + cbId + ), + FFI_ENTRY_LIST_HANDLE + ) + + return EntryListHandle.fromHandle(handle) + } + + public async sessionFetchAllKeys(options: SessionFetchAllKeysOptions): Promise { + const { forUpdate, limit, tagFilter, sessionHandle, algorithm, thumbprint } = serializeArguments(options) + + const handle = await this.promisifyWithResponse( + (cb, cbId) => + this.nativeAriesAskar.askar_session_fetch_all_keys( + sessionHandle, + algorithm, + thumbprint, + tagFilter, + +limit || -1, + forUpdate, + cb, + cbId + ), + FFI_KEY_ENTRY_LIST_HANDLE + ) + + return KeyEntryListHandle.fromHandle(handle) + } + + public async sessionFetchKey(options: SessionFetchKeyOptions): Promise { + const { forUpdate, sessionHandle, name } = serializeArguments(options) + + const handle = await this.promisifyWithResponse( + (cb, cbId) => this.nativeAriesAskar.askar_session_fetch_key(sessionHandle, name, forUpdate, cb, cbId), + FFI_KEY_ENTRY_LIST_HANDLE + ) + + return KeyEntryListHandle.fromHandle(handle) + } + + public async sessionInsertKey(options: SessionInsertKeyOptions): Promise { + const { name, sessionHandle, expiryMs, localKeyHandle, metadata, tags } = serializeArguments(options) + + return this.promisify((cb, cbId) => + this.nativeAriesAskar.askar_session_insert_key( + sessionHandle, + localKeyHandle, + name, + metadata, + tags, + +expiryMs || -1, + cb, + cbId + ) + ) + } + + public async sessionRemoveAll(options: SessionRemoveAllOptions): Promise { + const { sessionHandle, tagFilter, category } = serializeArguments(options) + const response = await this.promisifyWithResponse( + (cb, cbId) => this.nativeAriesAskar.askar_session_remove_all(sessionHandle, category, tagFilter, cb, cbId), + FFI_INT64 + ) + + return handleInvalidNullResponse(response) + } + + public async sessionRemoveKey(options: SessionRemoveKeyOptions): Promise { + const { sessionHandle, name } = serializeArguments(options) + + return this.promisify((cb, cbId) => this.nativeAriesAskar.askar_session_remove_key(sessionHandle, name, cb, cbId)) + } + + public async sessionStart(options: SessionStartOptions): Promise { + const { storeHandle, profile, asTransaction } = serializeArguments(options) + + const handle = await this.promisifyWithResponse((cb, cbId) => { + this.nativeAriesAskar.askar_session_start(storeHandle, profile, asTransaction, cb, cbId) + }, FFI_SESSION_HANDLE) + + return SessionHandle.fromHandle(handle) + } + + public async sessionUpdate(options: SessionUpdateOptions): Promise { + const { name, sessionHandle, category, expiryMs, tags, operation, value } = serializeArguments(options) + + return this.promisify((cb, cbId) => + this.nativeAriesAskar.askar_session_update( + sessionHandle, + operation, + category, + name, + value, + tags, + +expiryMs || -1, + cb, + cbId + ) + ) + } + + public async sessionUpdateKey(options: SessionUpdateKeyOptions): Promise { + const { expiryMs, tags, name, sessionHandle, metadata } = serializeArguments(options) + + return this.promisify((cb, cbId) => + this.nativeAriesAskar.askar_session_update_key(sessionHandle, name, metadata, tags, +expiryMs || -1, cb, cbId) + ) + } + + public storeClose(options: StoreCloseOptions): Promise { + const { storeHandle } = serializeArguments(options) + + return this.promisify((cb, cbId) => this.nativeAriesAskar.askar_store_close(storeHandle, cb, cbId)) + } + + public async storeCreateProfile(options: StoreCreateProfileOptions): Promise { + const { storeHandle, profile } = serializeArguments(options) + const response = await this.promisifyWithResponse( + (cb, cbId) => this.nativeAriesAskar.askar_store_create_profile(storeHandle, profile, cb, cbId), + FFI_STRING + ) + + return handleInvalidNullResponse(response) + } + + public storeGenerateRawKey(options: StoreGenerateRawKeyOptions): string { + const { seed } = serializeArguments(options) + const ret = allocateStringBuffer() + + this.nativeAriesAskar.askar_store_generate_raw_key(seed, ret) + this.handleError() + + return ret.deref() as string + } + + public async storeGetProfileName(options: StoreGetProfileNameOptions): Promise { + const { storeHandle } = serializeArguments(options) + const response = await this.promisifyWithResponse((cb, cbId) => + this.nativeAriesAskar.askar_store_get_profile_name(storeHandle, cb, cbId) + ) + + return handleInvalidNullResponse(response) + } + + public async storeOpen(options: StoreOpenOptions): Promise { + const { profile, keyMethod, passKey, specUri } = serializeArguments(options) + + const handle = await this.promisifyWithResponse( + (cb, cbId) => this.nativeAriesAskar.askar_store_open(specUri, keyMethod, passKey, profile, cb, cbId), + FFI_STORE_HANDLE + ) + + return StoreHandle.fromHandle(handle) + } + + public async storeProvision(options: StoreProvisionOptions): Promise { + const { profile, passKey, keyMethod, specUri, recreate } = serializeArguments(options) + + const handle = await this.promisifyWithResponse( + (cb, cbId) => + this.nativeAriesAskar.askar_store_provision(specUri, keyMethod, passKey, profile, recreate, cb, cbId), + FFI_STORE_HANDLE + ) + + return StoreHandle.fromHandle(handle) + } + + public async storeRekey(options: StoreRekeyOptions): Promise { + const { passKey, keyMethod, storeHandle } = serializeArguments(options) + + return this.promisify((cb, cbId) => + this.nativeAriesAskar.askar_store_rekey(storeHandle, keyMethod, passKey, cb, cbId) + ) + } + + public async storeRemove(options: StoreRemoveOptions): Promise { + const { specUri } = serializeArguments(options) + const response = await this.promisifyWithResponse( + (cb, cbId) => this.nativeAriesAskar.askar_store_remove(specUri, cb, cbId), + FFI_INT8 + ) + + return handleInvalidNullResponse(response) + } + + public async storeRemoveProfile(options: StoreRemoveProfileOptions): Promise { + const { storeHandle, profile } = serializeArguments(options) + + const response = await this.promisifyWithResponse( + (cb, cbId) => this.nativeAriesAskar.askar_store_remove_profile(storeHandle, profile, cb, cbId), + FFI_INT8 + ) + + return handleInvalidNullResponse(response) + } + + public async migrateIndySdk(options: MigrateIndySdkOptions): Promise { + const { specUri, kdfLevel, walletKey, walletName } = serializeArguments(options) + await this.promisify((cb, cbId) => + this.nativeAriesAskar.askar_migrate_indy_sdk(specUri, walletName, walletKey, kdfLevel, cb, cbId) + ) + } +} diff --git a/wrappers/javascript/aries-askar-nodejs/src/ffi/alloc.ts b/wrappers/javascript/aries-askar-nodejs/src/ffi/alloc.ts new file mode 100644 index 00000000..415760c4 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/ffi/alloc.ts @@ -0,0 +1,24 @@ +import { alloc } from '@2060.io/ref-napi' + +import { FFI_POINTER, FFI_STRING, FFI_INT32, FFI_INT8 } from './primitives' +import { SecretBufferStruct, EncryptedBufferStruct, AeadParamsStruct } from './structures' + +export const allocatePointer = (): Buffer => alloc(FFI_POINTER) + +export const allocateStringBuffer = (): Buffer => alloc(FFI_STRING) + +export const allocateInt32Buffer = (): Buffer => alloc(FFI_INT32) + +export const allocateInt8Buffer = (): Buffer => alloc(FFI_INT8) + +export const allocateSecretBuffer = (): Buffer => alloc(SecretBufferStruct) + +export const allocateEncryptedBuffer = (): Buffer => alloc(EncryptedBufferStruct) + +export const allocateAeadParams = (): Buffer => alloc(AeadParamsStruct) + +export const allocateLocalKeyHandle = allocatePointer + +export const allocateCallbackBuffer = (callback: Buffer) => setTimeout(() => callback, 1000000) + +export const deallocateCallbackBuffer = (id: number) => clearTimeout(id as unknown as NodeJS.Timeout) diff --git a/wrappers/javascript/aries-askar-nodejs/src/ffi/callback.ts b/wrappers/javascript/aries-askar-nodejs/src/ffi/callback.ts new file mode 100644 index 00000000..0feea6b7 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/ffi/callback.ts @@ -0,0 +1,38 @@ +import { Callback } from '@2060.io/ffi-napi' +import { refType } from '@2060.io/ref-napi' + +import { allocateCallbackBuffer } from './alloc' +import { FFI_VOID, FFI_CALLBACK_ID, FFI_ERROR_CODE, FFI_STRING, FFI_INT32 } from './primitives' + +export type NativeCallback = (id: number, errorCode: number) => void +export const toNativeCallback = (cb: NativeCallback) => { + const nativeCallback = Callback(FFI_VOID, [FFI_CALLBACK_ID, FFI_ERROR_CODE], cb) + const id = allocateCallbackBuffer(nativeCallback) + return { nativeCallback, id } +} + +export type NativeCallbackWithResponse = (id: number, errorCode: number, response: R) => void +export const toNativeCallbackWithResponse = (cb: NativeCallbackWithResponse, responseFfiType = FFI_STRING) => { + const nativeCallback = Callback(FFI_VOID, [FFI_CALLBACK_ID, FFI_ERROR_CODE, responseFfiType], cb) + const id = allocateCallbackBuffer(nativeCallback) + return { nativeCallback, id } +} + +export type NativeLogCallback = ( + context: unknown, + level: number, + target: string, + message: string, + modulePath: string, + file: string, + line: number +) => void +export const toNativeLogCallback = (cb: NativeLogCallback) => { + const nativeCallback = Callback( + FFI_VOID, + [refType(FFI_VOID), FFI_INT32, FFI_STRING, FFI_STRING, FFI_STRING, FFI_STRING, FFI_INT32], + cb + ) + const id = allocateCallbackBuffer(nativeCallback) + return { nativeCallback, id } +} diff --git a/wrappers/javascript/aries-askar-nodejs/src/ffi/conversion.ts b/wrappers/javascript/aries-askar-nodejs/src/ffi/conversion.ts new file mode 100644 index 00000000..2e26df32 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/ffi/conversion.ts @@ -0,0 +1,31 @@ +import type { ByteBufferType, EncryptedBufferType } from './structures' +import type { Pointer } from '@2060.io/ref-napi' +import type { TypedArray } from 'ref-array-di' + +import { reinterpret } from '@2060.io/ref-napi' +import { EncryptedBuffer } from '@hyperledger/aries-askar-shared' + +import { ByteBufferStruct } from './structures' + +export const byteBufferClassToStruct = ({ len, data }: ByteBufferType) => { + return ByteBufferStruct({ + len, + data: data as Pointer>, + }) +} + +export const secretBufferClassToStruct = byteBufferClassToStruct + +export const uint8arrayToByteBufferStruct = (buf: Buffer) => { + return byteBufferClassToStruct({ data: buf, len: buf.length }) +} + +export const byteBufferToBuffer = ({ data, len }: ByteBufferType) => reinterpret(data, len) + +export const secretBufferToBuffer = byteBufferToBuffer + +export const encryptedBufferStructToClass = ({ secretBuffer, tagPos, noncePos }: EncryptedBufferType) => { + const buffer = Uint8Array.from(secretBufferToBuffer(secretBuffer)) + + return new EncryptedBuffer({ tagPos, noncePos, buffer }) +} diff --git a/wrappers/javascript/aries-askar-nodejs/src/ffi/index.ts b/wrappers/javascript/aries-askar-nodejs/src/ffi/index.ts new file mode 100644 index 00000000..e3a8ef94 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/ffi/index.ts @@ -0,0 +1,6 @@ +export * from './alloc' +export * from './callback' +export * from './conversion' +export * from './structures' +export * from './primitives' +export * from './serialize' diff --git a/wrappers/javascript/aries-askar-nodejs/src/ffi/primitives.ts b/wrappers/javascript/aries-askar-nodejs/src/ffi/primitives.ts new file mode 100644 index 00000000..32c8fbba --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/ffi/primitives.ts @@ -0,0 +1,35 @@ +import { refType } from '@2060.io/ref-napi' + +// Primitives + +export const FFI_UINT8 = 'uint8' +export const FFI_UINT64 = 'uint64' +export const FFI_USIZE = 'size_t' +export const FFI_INT8 = 'int8' +export const FFI_INT32 = 'int32' +export const FFI_INT64 = 'int64' +export const FFI_STRING = 'string' +export const FFI_VOID = 'void' +export const FFI_POINTER = 'pointer' + +// Pointers + +export const FFI_INT8_PTR = refType(FFI_INT8) +export const FFI_STRING_PTR = refType(FFI_STRING) +export const FFI_INT32_PTR = refType(FFI_INT32) + +// Custom + +export const FFI_CALLBACK_ID = FFI_INT64 +export const FFI_CALLBACK_PTR = FFI_POINTER +export const FFI_ERROR_CODE = FFI_INT64 + +// Handles + +const FFI_ARC_HANDLE = FFI_POINTER +export const FFI_ENTRY_LIST_HANDLE = FFI_ARC_HANDLE +export const FFI_KEY_ENTRY_LIST_HANDLE = FFI_ARC_HANDLE +export const FFI_LOCAL_KEY_HANDLE = FFI_ARC_HANDLE +export const FFI_SESSION_HANDLE = FFI_USIZE +export const FFI_SCAN_HANDLE = FFI_USIZE +export const FFI_STORE_HANDLE = FFI_USIZE diff --git a/wrappers/javascript/aries-askar-nodejs/src/ffi/serialize.ts b/wrappers/javascript/aries-askar-nodejs/src/ffi/serialize.ts new file mode 100644 index 00000000..a1a53a13 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/ffi/serialize.ts @@ -0,0 +1,113 @@ +import type { ByteBufferStruct, SecretBufferStruct } from './structures' + +import { NULL } from '@2060.io/ref-napi' +import { Key, ArcHandle, StoreHandle, SessionHandle, ScanHandle, Jwk } from '@hyperledger/aries-askar-shared' + +import { uint8arrayToByteBufferStruct } from './conversion' + +export type Callback = (err: number) => void +export type CallbackWithResponse = (err: number, response: string) => void + +type Argument = + | Record + | ArcHandle + | StoreHandle + | SessionHandle + | ScanHandle + | Array + | Date + | Uint8Array + | SerializedArgument + | boolean + | Jwk + | Key + +type SerializedArgument = + | string + | number + | Callback + | CallbackWithResponse + | ArrayBuffer + | typeof ByteBufferStruct + | typeof SecretBufferStruct + | Buffer + +type SerializedArguments = Record + +export type SerializedOptions = Required<{ + [Property in keyof Type]: Type[Property] extends string + ? string + : Type[Property] extends number | boolean | Date | StoreHandle | SessionHandle | ScanHandle + ? number + : Type[Property] extends Record | Array + ? string + : Type[Property] extends Buffer | Uint8Array | Key | ArcHandle | Jwk + ? Buffer + : Type[Property] extends Callback + ? Callback + : Type[Property] extends CallbackWithResponse + ? CallbackWithResponse + : Type[Property] extends boolean | undefined + ? number + : Type[Property] extends Array | undefined + ? string + : Type[Property] extends Record | undefined + ? string + : Type[Property] extends Date | undefined + ? number + : Type[Property] extends string | undefined + ? string + : Type[Property] extends number | undefined + ? number + : Type[Property] extends Uint8Array | undefined + ? Buffer + : unknown +}> + +const serialize = (arg: Argument): SerializedArgument => { + switch (typeof arg) { + case 'undefined': + return NULL + case 'boolean': + return +arg + case 'string': + return arg + case 'number': + return arg + case 'function': + return arg + case 'object': + if (arg instanceof Date) { + return arg.valueOf() + } else if ( + arg instanceof ArcHandle || + arg instanceof StoreHandle || + arg instanceof SessionHandle || + arg instanceof ScanHandle + ) { + return arg.handle + } else if (arg instanceof Key) { + return arg.handle.handle + } else if (arg instanceof Buffer) { + return arg + } else if (arg instanceof Uint8Array) { + return uint8arrayToByteBufferStruct(Buffer.from(arg)) as unknown as Buffer + } else if (arg instanceof Jwk) { + return uint8arrayToByteBufferStruct(Buffer.from(arg.toUint8Array())) as unknown as Buffer + } else { + return JSON.stringify(arg) + } + default: + throw new Error('could not serialize value') + } +} + +const serializeArguments = = Record>( + args: T +): SerializedOptions => { + const retVal: SerializedArguments = {} + Object.entries(args).forEach(([key, val]) => (retVal[key] = serialize(val))) + return retVal as SerializedOptions +} + +export { serializeArguments } diff --git a/wrappers/javascript/aries-askar-nodejs/src/ffi/structures.ts b/wrappers/javascript/aries-askar-nodejs/src/ffi/structures.ts new file mode 100644 index 00000000..b225f95e --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/ffi/structures.ts @@ -0,0 +1,41 @@ +import { default as ref, refType } from '@2060.io/ref-napi' +import refArray from 'ref-array-di' +import refStruct from 'ref-struct-di' + +import { FFI_INT32, FFI_INT64, FFI_UINT8 } from './primitives' + +const CStruct = refStruct(ref) +const CArray = refArray(ref) + +export const ByteBufferArray = CArray(FFI_UINT8) +export const ByteBufferArrayPtr = refType(ByteBufferArray) + +export const ByteBufferStruct = CStruct({ + len: FFI_INT64, + data: ByteBufferArrayPtr, +}) + +const ByteBufferStructPtr = ref.refType(ByteBufferStruct) + +export const SecretBufferStruct = ByteBufferStruct + +export const SecretBufferStructPtr = ByteBufferStructPtr + +export const EncryptedBufferStruct = CStruct({ + secretBuffer: SecretBufferStruct, + tagPos: FFI_INT64, + noncePos: FFI_INT64, +}) + +export const EncryptedBufferStructPtr = ref.refType(EncryptedBufferStruct) + +export const AeadParamsStruct = CStruct({ + nonceLength: FFI_INT32, + tagLength: FFI_INT32, +}) + +export const AeadParamsStructPtr = ref.refType(AeadParamsStruct) + +export type EncryptedBufferType = { secretBuffer: SecretBufferType; tagPos: number; noncePos: number } +export type ByteBufferType = { data: Buffer; len: number } +export type SecretBufferType = ByteBufferType diff --git a/wrappers/javascript/aries-askar-nodejs/src/index.ts b/wrappers/javascript/aries-askar-nodejs/src/index.ts new file mode 100644 index 00000000..34b78aff --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/index.ts @@ -0,0 +1,8 @@ +import { registerAriesAskar } from '@hyperledger/aries-askar-shared' + +import { NodeJSAriesAskar } from './NodeJSAriesAskar' + +export const ariesAskarNodeJS = new NodeJSAriesAskar() +registerAriesAskar({ askar: ariesAskarNodeJS }) + +export * from '@hyperledger/aries-askar-shared' diff --git a/wrappers/javascript/aries-askar-nodejs/src/library/NativeBindingInterface.ts b/wrappers/javascript/aries-askar-nodejs/src/library/NativeBindingInterface.ts new file mode 100644 index 00000000..632cbf9d --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/library/NativeBindingInterface.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import type { nativeBindings } from './bindings' + +// We need a mapping from string type value => type (property 'string' maps to type string) +interface StringTypeMapping { + pointer: Buffer + 'char*': Buffer + string: string + int64: number + int32: number + int8: number + int: number + size_t: number +} + +// Needed so TS stops complaining about index signatures +type ShapeOf = { + [Property in keyof T]: T[Property] +} +type StringTypeArrayToTypes> = { + [Item in keyof List]: List[Item] extends keyof StringTypeMapping ? StringTypeMapping[List[Item]] : Buffer +} + +type TypedMethods = { + [Property in keyof Base]: ( + ...args: StringTypeArrayToTypes extends any[] ? StringTypeArrayToTypes : [] + ) => StringTypeMapping[Base[Property][0]] +} +type Mutable = { + -readonly [K in keyof T]: Mutable +} + +export type NativeMethods = TypedMethods>> diff --git a/wrappers/javascript/aries-askar-nodejs/src/library/bindings.ts b/wrappers/javascript/aries-askar-nodejs/src/library/bindings.ts new file mode 100644 index 00000000..bf2bcbb0 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/library/bindings.ts @@ -0,0 +1,205 @@ +import { refType } from '@2060.io/ref-napi' + +import { + FFI_CALLBACK_ID, + FFI_CALLBACK_PTR, + FFI_ENTRY_LIST_HANDLE, + FFI_ERROR_CODE, + FFI_INT32, + FFI_INT32_PTR, + FFI_INT64, + FFI_INT8, + FFI_INT8_PTR, + FFI_KEY_ENTRY_LIST_HANDLE, + FFI_LOCAL_KEY_HANDLE, + FFI_POINTER, + FFI_SCAN_HANDLE, + FFI_SESSION_HANDLE, + FFI_STORE_HANDLE, + FFI_STRING, + FFI_STRING_PTR, + SecretBufferStruct, + SecretBufferStructPtr, + ByteBufferStruct, + AeadParamsStructPtr, + EncryptedBufferStructPtr, +} from '../ffi' + +export const nativeBindings = { + // first element is method return type, second element is list of method argument types + askar_version: [FFI_STRING, []], + askar_get_current_error: [FFI_ERROR_CODE, [FFI_STRING_PTR]], + askar_buffer_free: [FFI_ERROR_CODE, [SecretBufferStruct]], + askar_clear_custom_logger: [FFI_ERROR_CODE, []], + askar_set_custom_logger: [FFI_ERROR_CODE, [FFI_INT32, FFI_CALLBACK_PTR, FFI_INT32, FFI_INT32, FFI_INT32]], + askar_set_default_logger: [FFI_ERROR_CODE, []], + askar_set_max_log_level: [FFI_ERROR_CODE, [FFI_INT32]], + + askar_entry_list_count: [FFI_ERROR_CODE, [FFI_ENTRY_LIST_HANDLE, FFI_INT32_PTR]], + askar_entry_list_free: [FFI_ERROR_CODE, [FFI_ENTRY_LIST_HANDLE]], + askar_entry_list_get_category: [FFI_ERROR_CODE, [FFI_ENTRY_LIST_HANDLE, FFI_INT32, FFI_STRING_PTR]], + askar_entry_list_get_name: [FFI_ERROR_CODE, [FFI_ENTRY_LIST_HANDLE, FFI_INT32, FFI_STRING_PTR]], + askar_entry_list_get_tags: [FFI_ERROR_CODE, [FFI_ENTRY_LIST_HANDLE, FFI_INT32, FFI_STRING_PTR]], + askar_entry_list_get_value: [FFI_ERROR_CODE, [FFI_ENTRY_LIST_HANDLE, FFI_INT32, SecretBufferStructPtr]], + + askar_key_aead_decrypt: [ + FFI_ERROR_CODE, + [FFI_POINTER, ByteBufferStruct, ByteBufferStruct, ByteBufferStruct, ByteBufferStruct, SecretBufferStructPtr], + ], + askar_key_aead_encrypt: [ + FFI_ERROR_CODE, + [FFI_POINTER, ByteBufferStruct, ByteBufferStruct, ByteBufferStruct, EncryptedBufferStructPtr], + ], + askar_key_aead_get_padding: [FFI_ERROR_CODE, [FFI_POINTER, FFI_INT64, FFI_INT32_PTR]], + askar_key_aead_get_params: [FFI_ERROR_CODE, [FFI_POINTER, AeadParamsStructPtr]], + askar_key_aead_random_nonce: [FFI_ERROR_CODE, [FFI_POINTER, SecretBufferStructPtr]], + askar_key_convert: [FFI_ERROR_CODE, [FFI_POINTER, FFI_STRING, refType(FFI_POINTER)]], + askar_key_crypto_box: [ + FFI_ERROR_CODE, + [FFI_POINTER, FFI_POINTER, ByteBufferStruct, ByteBufferStruct, SecretBufferStructPtr], + ], + askar_key_crypto_box_open: [ + FFI_ERROR_CODE, + [FFI_POINTER, FFI_POINTER, ByteBufferStruct, ByteBufferStruct, SecretBufferStructPtr], + ], + askar_key_crypto_box_random_nonce: [FFI_ERROR_CODE, [SecretBufferStructPtr]], + askar_key_crypto_box_seal: [FFI_ERROR_CODE, [FFI_POINTER, ByteBufferStruct, SecretBufferStructPtr]], + askar_key_crypto_box_seal_open: [FFI_ERROR_CODE, [FFI_POINTER, ByteBufferStruct, SecretBufferStructPtr]], + askar_key_derive_ecdh_1pu: [ + FFI_ERROR_CODE, + [ + FFI_STRING, + FFI_POINTER, + FFI_POINTER, + FFI_POINTER, + ByteBufferStruct, + ByteBufferStruct, + ByteBufferStruct, + ByteBufferStruct, + FFI_INT8, + refType(FFI_POINTER), + ], + ], + askar_key_derive_ecdh_es: [ + FFI_ERROR_CODE, + [ + FFI_STRING, + FFI_POINTER, + FFI_POINTER, + ByteBufferStruct, + ByteBufferStruct, + ByteBufferStruct, + FFI_INT8, + refType(FFI_POINTER), + ], + ], + askar_key_entry_list_count: [FFI_ERROR_CODE, [FFI_KEY_ENTRY_LIST_HANDLE, FFI_INT32_PTR]], + askar_key_entry_list_free: [FFI_ERROR_CODE, [FFI_KEY_ENTRY_LIST_HANDLE]], + askar_key_entry_list_get_algorithm: [FFI_ERROR_CODE, [FFI_KEY_ENTRY_LIST_HANDLE, FFI_INT32, FFI_STRING_PTR]], + askar_key_entry_list_get_metadata: [FFI_ERROR_CODE, [FFI_KEY_ENTRY_LIST_HANDLE, FFI_INT32, FFI_STRING_PTR]], + askar_key_entry_list_get_name: [FFI_ERROR_CODE, [FFI_KEY_ENTRY_LIST_HANDLE, FFI_INT32, FFI_STRING_PTR]], + askar_key_entry_list_get_tags: [FFI_ERROR_CODE, [FFI_KEY_ENTRY_LIST_HANDLE, FFI_INT32, FFI_STRING_PTR]], + askar_key_entry_list_load_local: [FFI_ERROR_CODE, [FFI_KEY_ENTRY_LIST_HANDLE, FFI_INT32, refType(FFI_POINTER)]], + askar_key_free: [FFI_ERROR_CODE, [FFI_POINTER]], + askar_key_from_jwk: [FFI_ERROR_CODE, [ByteBufferStruct, refType(FFI_POINTER)]], + askar_key_from_key_exchange: [FFI_ERROR_CODE, [FFI_STRING, FFI_POINTER, FFI_POINTER, FFI_LOCAL_KEY_HANDLE]], + askar_key_from_public_bytes: [FFI_ERROR_CODE, [FFI_STRING, ByteBufferStruct, FFI_LOCAL_KEY_HANDLE]], + askar_key_from_secret_bytes: [FFI_ERROR_CODE, [FFI_STRING, ByteBufferStruct, refType(FFI_POINTER)]], + askar_key_from_seed: [FFI_ERROR_CODE, [FFI_STRING, ByteBufferStruct, FFI_STRING, FFI_LOCAL_KEY_HANDLE]], + askar_key_generate: [FFI_ERROR_CODE, [FFI_STRING, FFI_INT8, FFI_LOCAL_KEY_HANDLE]], + askar_key_get_algorithm: [FFI_ERROR_CODE, [FFI_POINTER, FFI_STRING_PTR]], + askar_key_get_ephemeral: [FFI_ERROR_CODE, [FFI_POINTER, FFI_INT8_PTR]], + askar_key_get_jwk_public: [FFI_ERROR_CODE, [FFI_POINTER, FFI_STRING, FFI_STRING_PTR]], + askar_key_get_jwk_secret: [FFI_ERROR_CODE, [FFI_POINTER, SecretBufferStructPtr]], + askar_key_get_jwk_thumbprint: [FFI_ERROR_CODE, [FFI_POINTER, FFI_STRING, FFI_STRING_PTR]], + askar_key_get_public_bytes: [FFI_ERROR_CODE, [FFI_POINTER, SecretBufferStructPtr]], + askar_key_get_secret_bytes: [FFI_ERROR_CODE, [FFI_POINTER, SecretBufferStructPtr]], + askar_key_sign_message: [FFI_ERROR_CODE, [FFI_POINTER, ByteBufferStruct, FFI_STRING, SecretBufferStructPtr]], + askar_key_unwrap_key: [ + FFI_ERROR_CODE, + [FFI_POINTER, FFI_STRING, ByteBufferStruct, ByteBufferStruct, ByteBufferStruct, refType(FFI_POINTER)], + ], + askar_key_verify_signature: [ + FFI_ERROR_CODE, + [FFI_POINTER, ByteBufferStruct, ByteBufferStruct, FFI_STRING, FFI_INT8_PTR], + ], + askar_key_wrap_key: [FFI_ERROR_CODE, [FFI_POINTER, FFI_POINTER, ByteBufferStruct, EncryptedBufferStructPtr]], + + askar_scan_free: [FFI_ERROR_CODE, [FFI_SCAN_HANDLE]], + askar_scan_next: [FFI_ERROR_CODE, [FFI_SCAN_HANDLE, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + askar_scan_start: [ + FFI_ERROR_CODE, + [FFI_STORE_HANDLE, FFI_STRING, FFI_STRING, FFI_STRING, FFI_INT64, FFI_INT64, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + + askar_session_close: [FFI_ERROR_CODE, [FFI_SESSION_HANDLE, FFI_INT8, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + askar_session_count: [ + FFI_ERROR_CODE, + [FFI_SESSION_HANDLE, FFI_STRING, FFI_STRING, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + askar_session_fetch: [ + FFI_ERROR_CODE, + [FFI_SESSION_HANDLE, FFI_STRING, FFI_STRING, FFI_INT8, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + askar_session_fetch_all: [ + FFI_ERROR_CODE, + [FFI_SESSION_HANDLE, FFI_STRING, FFI_STRING, FFI_INT64, FFI_INT8, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + askar_session_fetch_all_keys: [ + FFI_ERROR_CODE, + [FFI_SESSION_HANDLE, FFI_STRING, FFI_STRING, FFI_STRING, FFI_INT64, FFI_INT8, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + askar_session_fetch_key: [ + FFI_ERROR_CODE, + [FFI_SESSION_HANDLE, FFI_STRING, FFI_INT8, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + askar_session_insert_key: [ + FFI_ERROR_CODE, + [FFI_SESSION_HANDLE, FFI_POINTER, FFI_STRING, FFI_STRING, FFI_STRING, FFI_INT64, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + askar_session_remove_all: [ + FFI_ERROR_CODE, + [FFI_SESSION_HANDLE, FFI_STRING, FFI_STRING, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + askar_session_remove_key: [FFI_ERROR_CODE, [FFI_SESSION_HANDLE, FFI_STRING, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + askar_session_start: [FFI_ERROR_CODE, [FFI_STORE_HANDLE, FFI_STRING, FFI_INT8, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + askar_session_update: [ + FFI_ERROR_CODE, + [ + FFI_SESSION_HANDLE, + FFI_INT8, + FFI_STRING, + FFI_STRING, + ByteBufferStruct, + FFI_STRING, + FFI_INT64, + FFI_CALLBACK_PTR, + FFI_CALLBACK_ID, + ], + ], + askar_session_update_key: [ + FFI_ERROR_CODE, + [FFI_SESSION_HANDLE, FFI_STRING, FFI_STRING, FFI_STRING, FFI_INT64, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + + askar_store_close: [FFI_ERROR_CODE, [FFI_STORE_HANDLE, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + askar_store_create_profile: [FFI_ERROR_CODE, [FFI_STORE_HANDLE, FFI_STRING, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + askar_store_generate_raw_key: [FFI_ERROR_CODE, [ByteBufferStruct, FFI_STRING_PTR]], + askar_store_get_profile_name: [FFI_ERROR_CODE, [FFI_STORE_HANDLE, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + askar_store_open: [ + FFI_ERROR_CODE, + [FFI_STRING, FFI_STRING, FFI_STRING, FFI_STRING, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + askar_store_provision: [ + FFI_ERROR_CODE, + [FFI_STRING, FFI_STRING, FFI_STRING, FFI_STRING, FFI_INT8, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], + askar_store_rekey: [FFI_ERROR_CODE, [FFI_STORE_HANDLE, FFI_STRING, FFI_STRING, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + askar_store_remove: [FFI_ERROR_CODE, [FFI_STRING, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + askar_store_remove_profile: [FFI_ERROR_CODE, [FFI_STORE_HANDLE, FFI_STRING, FFI_CALLBACK_PTR, FFI_CALLBACK_ID]], + + askar_migrate_indy_sdk: [ + FFI_ERROR_CODE, + [FFI_STRING, FFI_STRING, FFI_STRING, FFI_STRING, FFI_CALLBACK_PTR, FFI_CALLBACK_ID], + ], +} as const diff --git a/wrappers/javascript/aries-askar-nodejs/src/library/index.ts b/wrappers/javascript/aries-askar-nodejs/src/library/index.ts new file mode 100644 index 00000000..df4ece64 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/library/index.ts @@ -0,0 +1,3 @@ +export * from './NativeBindingInterface' +export * from './bindings' +export * from './register' diff --git a/wrappers/javascript/aries-askar-nodejs/src/library/register.ts b/wrappers/javascript/aries-askar-nodejs/src/library/register.ts new file mode 100644 index 00000000..4408af75 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/src/library/register.ts @@ -0,0 +1,76 @@ +import type { NativeMethods } from './NativeBindingInterface' + +import { Library } from '@2060.io/ffi-napi' +import fs from 'fs' +import os from 'os' +import path from 'path' + +import { nativeBindings } from './bindings' + +const LIBNAME = 'aries_askar' +const ENV_VAR = 'LIB_ARIES_ASKAR_PATH' + +type Platform = 'darwin' | 'linux' | 'win32' + +type ExtensionMap = Record + +const extensions: ExtensionMap = { + darwin: { prefix: 'lib', extension: '.dylib' }, + linux: { prefix: 'lib', extension: '.so' }, + win32: { extension: '.dll' }, +} + +const libPaths: Record> = { + darwin: ['/usr/local/lib/', '/usr/lib/', '/opt/homebrew/opt/'], + linux: ['/usr/lib/', '/usr/local/lib/'], + win32: ['c:\\windows\\system32\\'], +} + +// Alias for a simple function to check if the path exists +const doesPathExist = fs.existsSync + +const getLibrary = () => { + // Detect OS; darwin, linux and windows are only supported + const platform = os.platform() + + if (platform !== 'linux' && platform !== 'win32' && platform !== 'darwin') + throw new Error(`Unsupported platform: ${platform}. linux, win32 and darwin are supported.`) + + // Get a potential path from the environment variable + const pathFromEnvironment = process.env[ENV_VAR] + + // Get the paths specific to the users operating system + const platformPaths = libPaths[platform] + + // Look for the file in the native directory of the package. + // node-pre-gyp will download the binaries to this directory after installing the package + platformPaths.unshift(path.join(__dirname, '../../native')) + + // Check if the path from the environment variable is supplied and add it + // We use unshift here so that when we want to get a valid library path this will be the first to resolve + if (pathFromEnvironment) platformPaths.unshift(pathFromEnvironment) + + // Create the path + file + const libaries = platformPaths.map((p) => + path.join(p, `${extensions[platform].prefix ?? ''}${LIBNAME}${extensions[platform].extension}`) + ) + + // Gaurd so we quit if there is no valid path for the library + if (!libaries.some(doesPathExist)) + throw new Error(`Could not find ${LIBNAME} with these paths: ${libaries.join(' ')}`) + + // Get the first valid library + // Casting here as a string because there is a guard of none of the paths + const validLibraryPath = libaries.find((l) => doesPathExist(l)) as string + + // TODO fix the typing conversion + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return Library(validLibraryPath, nativeBindings) +} + +let nativeAriesAskar: NativeMethods | undefined = undefined +export const getNativeAriesAskar = () => { + if (!nativeAriesAskar) nativeAriesAskar = getLibrary() as unknown as NativeMethods + return nativeAriesAskar +} diff --git a/wrappers/javascript/aries-askar-nodejs/tests/cryptoBox.test.ts b/wrappers/javascript/aries-askar-nodejs/tests/cryptoBox.test.ts new file mode 100644 index 00000000..2bc5bd3b --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tests/cryptoBox.test.ts @@ -0,0 +1,18 @@ +import { CryptoBox, Key, KeyAlgs } from '@hyperledger/aries-askar-shared' + +describe('CryptoBox', () => { + beforeAll(() => { + require('@hyperledger/aries-askar-nodejs') + }) + + test('seal', () => { + const x25519Key = Key.generate(KeyAlgs.X25519) + + const message = Uint8Array.from(Buffer.from('foobar')) + const sealed = CryptoBox.seal({ recipientKey: x25519Key, message }) + + const opened = CryptoBox.sealOpen({ recipientKey: x25519Key, ciphertext: sealed }) + expect(opened).toStrictEqual(message) + x25519Key.handle.free() + }) +}) diff --git a/wrappers/javascript/aries-askar-nodejs/tests/indy_wallet_sqlite.db b/wrappers/javascript/aries-askar-nodejs/tests/indy_wallet_sqlite.db new file mode 100644 index 00000000..97c3f25a Binary files /dev/null and b/wrappers/javascript/aries-askar-nodejs/tests/indy_wallet_sqlite.db differ diff --git a/wrappers/javascript/aries-askar-nodejs/tests/joseEcdh.test.ts b/wrappers/javascript/aries-askar-nodejs/tests/joseEcdh.test.ts new file mode 100644 index 00000000..84e7d1f9 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tests/joseEcdh.test.ts @@ -0,0 +1,338 @@ +import { Ecdh1PU, EcdhEs, Jwk, Key, KeyAlgs } from '@hyperledger/aries-askar-shared' + +import { base64url } from './utils' + +describe('jose ecdh', () => { + beforeAll(() => { + require('@hyperledger/aries-askar-nodejs') + }) + + test('ecdh es direct', () => { + const bobKey = Key.generate(KeyAlgs.EcSecp256r1) + const bobJwk = bobKey.jwkPublic + const ephemeralKey = Key.generate(KeyAlgs.EcSecp256r1) + const ephemeralJwk = ephemeralKey.jwkPublic + const messageString = 'Helo there' + const message = Uint8Array.from(Buffer.from(messageString)) + const alg = 'ECDH-ES' + const apu = 'Alice' + const apv = 'Bob' + const encAlg = KeyAlgs.AesA256Gcm + + const protectedJson = { + alg, + enc: encAlg, + apu: base64url(apu), + apv: base64url(apv), + epk: ephemeralKey, + } + const protectedB64 = base64url(JSON.stringify(protectedJson)) + + const encryptedMessage = new EcdhEs({ + algId: Uint8Array.from(Buffer.from(encAlg)), + apu: Uint8Array.from(Buffer.from(apu)), + apv: Uint8Array.from(Buffer.from(apv)), + }).encryptDirect({ + encAlg, + ephemeralKey, + message, + recipientKey: bobJwk, + aad: Uint8Array.from(Buffer.from(protectedB64)), + }) + + const { nonce, tag, ciphertext } = encryptedMessage.parts + + const messageReceived = new EcdhEs({ + algId: Uint8Array.from(Buffer.from(encAlg)), + apu: Uint8Array.from(Buffer.from(apu)), + apv: Uint8Array.from(Buffer.from(apv)), + }).decryptDirect({ + encAlg, + ephemeralKey: ephemeralJwk, + recipientKey: bobKey, + ciphertext, + nonce, + tag, + aad: Uint8Array.from(Buffer.from(protectedB64)), + }) + expect(Buffer.from(messageReceived).toString()).toStrictEqual(messageString) + ephemeralKey.handle.free() + bobKey.handle.free() + }) + + test('ecdh es wrapped', () => { + const bobKey = Key.generate(KeyAlgs.X25519) + const bobJwk = bobKey.jwkPublic + const ephemeralKey = Key.generate(KeyAlgs.X25519) + const ephemeralJwk = ephemeralKey.jwkPublic + const message = Uint8Array.from(Buffer.from('Hello there')) + const alg = 'ECDH-ES+A128KW' + const enc = 'A256GCM' + const apu = 'Alice' + const apv = 'bob' + + const protectedJson = { + alg, + enc, + apu: base64url(apu), + apv: base64url(apv), + epk: ephemeralJwk, + } + const protectedString = JSON.stringify(protectedJson) + const protectedB64 = Buffer.from(protectedString).toString('base64url') + const protectedB64Bytes = Uint8Array.from(Buffer.from(protectedB64)) + + const cek = Key.generate(KeyAlgs.AesA256Gcm) + + const encryptedMessage = cek.aeadEncrypt({ message, aad: protectedB64Bytes }) + const { tag, nonce, ciphertext } = encryptedMessage.parts + const encryptedKey = new EcdhEs({ + algId: Uint8Array.from(Buffer.from(alg)), + apu: Uint8Array.from(Buffer.from(apu)), + apv: Uint8Array.from(Buffer.from(apv)), + }).senderWrapKey({ + wrapAlg: KeyAlgs.AesA128Kw, + ephemeralKey, + recipientKey: Key.fromJwk({ jwk: bobJwk }), + cek, + }).ciphertext + + const cekReceiver = new EcdhEs({ + algId: Uint8Array.from(Buffer.from(alg)), + apu: Uint8Array.from(Buffer.from(apu)), + apv: Uint8Array.from(Buffer.from(apv)), + }).receiverUnwrapKey({ + wrapAlg: KeyAlgs.AesA128Kw, + encAlg: KeyAlgs.AesA256Gcm, + ephemeralKey: Key.fromJwk({ jwk: ephemeralJwk }), + recipientKey: bobKey, + ciphertext: encryptedKey, + }) + + const messageReceived = cekReceiver.aeadDecrypt({ ciphertext, tag, nonce, aad: protectedB64Bytes }) + + expect(messageReceived).toStrictEqual(message) + ephemeralKey.handle.free() + bobKey.handle.free() + cek.handle.free() + cekReceiver.handle.free() + }) + + test('ecdh 1pu direct', () => { + const aliceKey = Key.generate(KeyAlgs.EcSecp256r1) + const aliceJwk = aliceKey.jwkPublic + const bobKey = Key.generate(KeyAlgs.EcSecp256r1) + const bobJwk = bobKey.jwkPublic + const ephemeralKey = Key.generate(KeyAlgs.EcSecp256r1) + const ephemeralJwk = ephemeralKey.jwkPublic + const message = Uint8Array.from(Buffer.from('Hello there')) + const alg = 'ECDH-1PU' + const enc = 'A256GCM' + const apu = 'Alice' + const apv = 'Bob' + const protectedJson = { + alg, + enc, + apu: base64url(apu), + apv: base64url(apv), + epk: ephemeralJwk, + } + const protectedString = JSON.stringify(protectedJson) + const protectedB64 = Buffer.from(protectedString).toString('base64url') + const protectedB64Bytes = Uint8Array.from(Buffer.from(protectedB64)) + + const encrypedMessage = new Ecdh1PU({ + algId: Uint8Array.from(Buffer.from(enc)), + apu: Uint8Array.from(Buffer.from(apu)), + apv: Uint8Array.from(Buffer.from(apv)), + }).encryptDirect({ + encAlg: KeyAlgs.AesA256Gcm, + ephemeralKey, + message, + senderKey: aliceKey, + recipientKey: Key.fromJwk({ jwk: bobJwk }), + aad: protectedB64Bytes, + }) + + const { nonce, tag, ciphertext } = encrypedMessage.parts + + const messageReceived = new Ecdh1PU({ + algId: Uint8Array.from(Buffer.from(enc)), + apu: Uint8Array.from(Buffer.from(apu)), + apv: Uint8Array.from(Buffer.from(apv)), + }).decryptDirect({ + encAlg: KeyAlgs.AesA256Gcm, + ephemeralKey, + senderKey: Key.fromJwk({ jwk: aliceJwk }), + recipientKey: bobKey, + ciphertext, + nonce, + tag, + aad: protectedB64Bytes, + }) + + expect(messageReceived).toStrictEqual(message) + aliceKey.handle.free() + bobKey.handle.free() + ephemeralKey.handle.free() + }) + + /** + * + * These tests have been implemented as a copy from the python wapper. + * The test vectores have been taken from: + * https://www.ietf.org/archive/id/draft-madden-jose-ecdh-1pu-04.txt + */ + test('ecdh 1pu wrapped expected', () => { + const ephemeral = Key.fromJwk({ + jwk: Jwk.fromJson({ + crv: 'X25519', + kty: 'OKP', + d: 'x8EVZH4Fwk673_mUujnliJoSrLz0zYzzCWp5GUX2fc8', + x: 'k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc', + }), + }) + + const alice = Key.fromJwk({ + jwk: Jwk.fromJson({ + crv: 'X25519', + kty: 'OKP', + d: 'i9KuFhSzEBsiv3PKVL5115OCdsqQai5nj_Flzfkw5jU', + x: 'Knbm_BcdQr7WIoz-uqit9M0wbcfEr6y-9UfIZ8QnBD4', + }), + }) + + const bob = Key.fromJwk({ + jwk: Jwk.fromJson({ + crv: 'X25519', + kty: 'OKP', + d: '1gDirl_r_Y3-qUa3WXHgEXrrEHngWThU3c9zj9A2uBg', + x: 'BT7aR0ItXfeDAldeeOlXL_wXqp-j5FltT0vRSG16kRw', + }), + }) + + const alg = 'ECDH-1PU+A128KW' + const apu = 'Alice' + const apv = 'Bob and Charlie' + const base64urlApu = base64url(apu) + const base64urlApv = base64url(apv) + + expect(base64urlApu).toStrictEqual('QWxpY2U') + expect(base64urlApv).toStrictEqual('Qm9iIGFuZCBDaGFybGll') + + const protectedJson = { + alg: 'ECDH-1PU+A128KW', + enc: 'A256CBC-HS512', + apu: 'QWxpY2U', + apv: 'Qm9iIGFuZCBDaGFybGll', + epk: { kty: 'OKP', crv: 'X25519', x: 'k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc' }, + } + const protectedString = JSON.stringify(protectedJson) + const protectedB64 = Buffer.from(protectedString).toString('base64url') + const protectedB64Bytes = Uint8Array.from(Buffer.from(protectedB64)) + + const cek = Key.fromSecretBytes({ + algorithm: KeyAlgs.AesA256CbcHs512, + secretKey: Uint8Array.from( + Buffer.from( + 'fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0', + 'hex' + ) + ), + }) + + const iv = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) + const message = Uint8Array.from(Buffer.from('Three is a magic number.')) + + const enc = cek.aeadEncrypt({ message, nonce: iv, aad: protectedB64Bytes }) + + const { ciphertext, tag: ccTag } = enc.parts + + expect(Buffer.from(ciphertext).toString('base64url')).toStrictEqual('Az2IWsISEMDJvyc5XRL-3-d-RgNBOGolCsxFFoUXFYw') + expect(Buffer.from(ccTag).toString('base64url')).toStrictEqual('HLb4fTlm8spGmij3RyOs2gJ4DpHM4hhVRwdF_hGb3WQ') + + const derived = new Ecdh1PU({ + apv: Uint8Array.from(Buffer.from(apv)), + apu: Uint8Array.from(Buffer.from(apu)), + algId: Uint8Array.from(Buffer.from(alg)), + }).deriveKey({ + encAlg: KeyAlgs.AesA128Kw, + recipientKey: bob, + senderKey: alice, + ccTag, + ephemeralKey: ephemeral, + receive: false, + }) + + expect(derived.secretBytes).toStrictEqual(Uint8Array.from(Buffer.from('df4c37a0668306a11e3d6b0074b5d8df', 'hex'))) + + const encryptedKey = derived.wrapKey({ other: cek }).ciphertextWithTag + expect(encryptedKey).toStrictEqual( + Uint8Array.from( + Buffer.from( + 'pOMVA9_PtoRe7xXW1139NzzN1UhiFoio8lGto9cf0t8PyU-sjNXH8-LIRLycq8CHJQbDwvQeU1cSl55cQ0hGezJu2N9IY0QN', + 'base64url' + ) + ) + ) + + const encryptedKey2 = new Ecdh1PU({ + apv: Uint8Array.from(Buffer.from(apv)), + apu: Uint8Array.from(Buffer.from(apu)), + algId: Uint8Array.from(Buffer.from(alg)), + }).senderWrapKey({ + wrapAlg: KeyAlgs.AesA128Kw, + cek, + ephemeralKey: ephemeral, + ccTag, + senderKey: alice, + recipientKey: bob, + }) + + expect(encryptedKey2.ciphertextWithTag).toStrictEqual(encryptedKey) + + const derivedReceiver = new Ecdh1PU({ + apv: Uint8Array.from(Buffer.from(apv)), + apu: Uint8Array.from(Buffer.from(apu)), + algId: Uint8Array.from(Buffer.from(alg)), + }).deriveKey({ + encAlg: KeyAlgs.AesA128Kw, + ephemeralKey: ephemeral, + senderKey: alice, + recipientKey: bob, + ccTag, + receive: true, + }) + + const cekReceiver = derivedReceiver.unwrapKey({ algorithm: KeyAlgs.AesA256CbcHs512, ciphertext: encryptedKey }) + + const messageReceived = cekReceiver.aeadDecrypt({ + ciphertext, + nonce: iv, + aad: protectedB64Bytes, + tag: ccTag, + }) + + expect(messageReceived).toStrictEqual(message) + + const cekReceiver2 = new Ecdh1PU({ + apv: Uint8Array.from(Buffer.from(apv)), + apu: Uint8Array.from(Buffer.from(apu)), + algId: Uint8Array.from(Buffer.from(alg)), + }).receiverUnwrapKey({ + wrapAlg: KeyAlgs.AesA128Kw, + encAlg: KeyAlgs.AesA256CbcHs512, + ephemeralKey: ephemeral, + senderKey: alice, + recipientKey: bob, + ciphertext: encryptedKey, + ccTag, + }) + + expect(cekReceiver2.jwkSecret).toStrictEqual(cek.jwkSecret) + cek.handle.free() + cekReceiver.handle.free() + cekReceiver2.handle.free() + derived.handle.free() + }) +}) diff --git a/wrappers/javascript/aries-askar-nodejs/tests/keys.test.ts b/wrappers/javascript/aries-askar-nodejs/tests/keys.test.ts new file mode 100644 index 00000000..dd0069f7 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tests/keys.test.ts @@ -0,0 +1,79 @@ +import { Key, KeyAlgs, KeyMethod } from '@hyperledger/aries-askar-shared' + +describe('keys', () => { + beforeAll(() => { + require('@hyperledger/aries-askar-nodejs') + }) + + test('aes cbc hmac', () => { + const key = Key.generate(KeyAlgs.AesA128CbcHs256) + expect(key.algorithm).toStrictEqual(KeyAlgs.AesA128CbcHs256) + + const messageString = 'test message' + const message = Uint8Array.from(Buffer.from(messageString)) + const aeadNonce = key.aeadRandomNonce + const params = key.aeadParams + expect(params.nonceLength).toStrictEqual(16) + expect(params.tagLength).toStrictEqual(16) + const enc = key.aeadEncrypt({ message, nonce: aeadNonce }) + const dec = key.aeadDecrypt(enc.parts) + expect(dec).toStrictEqual(message) + }) + + test('Bls G2 Keygen', () => { + const seed = Uint8Array.from(Buffer.from('testseed000000000000000000000001')) + const key = Key.fromSeed({ algorithm: KeyAlgs.Bls12381G2, seed }) + + expect(key.jwkPublic).toMatchObject({ + crv: 'BLS12381_G2', + kty: 'OKP', + x: 'lH6hIRPzjlKW6LvPm0sHqyEbGqf8ag7UWpA_GFfefwq_kzDXSHmls9Yoza_be23zEw-pSOmKI_MGR1DahBa7Jbho2BGwDNV_QmyhxMYBwTH12Ltk_GLyPD4AP6pQVgge', + }) + }) + + test('Bls G1 Keygen', () => { + const seed = Uint8Array.from(Buffer.from('testseed000000000000000000000001')) + const key = Key.fromSeed({ algorithm: KeyAlgs.Bls12381G1, seed }) + + expect(key.jwkPublic).toMatchObject({ + crv: 'BLS12381_G1', + kty: 'OKP', + x: 'hsjb9FSBUJXuB1fCluEcUBLeAPgIbnZGfxPKyeN3LVjQaKFWzXfNtMFAY8VL-eu-', + }) + }) + + test('Bls G1G2 Keygen', () => { + const seed = Uint8Array.from(Buffer.from('testseed000000000000000000000001')) + const key = Key.fromSeed({ algorithm: KeyAlgs.Bls12381G1G2, seed, method: KeyMethod.BlsKeygen }) + + expect(key.jwkPublic).toMatchObject({ + crv: 'BLS12381_G1G2', + kty: 'OKP', + x: 'h56eYI8Qkq5hitICb-ik8wRTzcn6Fd4iY8aDNVc9q1xoPS3lh4DB_B4wNtar1HrViZIOsO6BgLV72zCrBE2ym3DEhDYcghnUMO4O8IVVD8yS-C_zu6OA3L-ny-AO4rbkAo-WuApZEjn83LY98UtoKpTufn4PCUFVQZzJNH_gXWHR3oDspJaCbOajBfm5qj6d', + }) + }) + + test('ed25519', () => { + const key = Key.generate(KeyAlgs.Ed25519) + expect(key.algorithm).toStrictEqual(KeyAlgs.Ed25519) + const message = Uint8Array.from(Buffer.from('test message')) + const signature = key.signMessage({ message }) + expect(key.verifySignature({ message, signature })).toStrictEqual(true) + + const x25519Key = key.convertkey({ algorithm: KeyAlgs.X25519 }) + const x25519Key2 = Key.generate(KeyAlgs.X25519) + + const kex = x25519Key.keyFromKeyExchange({ algorithm: KeyAlgs.Chacha20XC20P, publicKey: x25519Key2 }) + expect(kex).toBeInstanceOf(Key) + + expect(key.jwkPublic).toMatchObject({ + kty: 'OKP', + crv: 'Ed25519', + }) + + expect(key.jwkSecret).toMatchObject({ + kty: 'OKP', + crv: 'Ed25519', + }) + }) +}) diff --git a/wrappers/javascript/aries-askar-nodejs/tests/migration.test.ts b/wrappers/javascript/aries-askar-nodejs/tests/migration.test.ts new file mode 100644 index 00000000..8b3404d7 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tests/migration.test.ts @@ -0,0 +1,50 @@ +import { Migration } from '@hyperledger/aries-askar-shared' +import fs from 'fs' +import path from 'path' + +const DB_TEMPLATE_PATH = path.join(__dirname, 'indy_wallet_sqlite.db') +const DB_UPGRADE_PATH = path.join(__dirname, 'indy_wallet_sqlite_upgraded.db') + +describe('migration', () => { + beforeAll(() => { + require('@hyperledger/aries-askar-nodejs') + }) + + beforeEach(() => { + const tplPaths = [DB_TEMPLATE_PATH, `${DB_TEMPLATE_PATH}-shm`, `${DB_TEMPLATE_PATH}-wal`] + + const updPaths = [DB_UPGRADE_PATH, `${DB_UPGRADE_PATH}-shm`, `${DB_UPGRADE_PATH}-wal`] + + for (let i = 0; i <= 3; i++) { + const tplPath = tplPaths[i] + const updPath = updPaths[i] + + if (fs.existsSync(tplPath)) { + fs.copyFileSync(tplPath, updPath) + } else { + fs.existsSync(updPath) && fs.rmSync(updPath) + } + } + }) + + test('migrate', async () => { + await expect( + Migration.migrate({ + specUri: DB_UPGRADE_PATH, + kdfLevel: 'RAW', + walletName: 'walletwallet.0', + walletKey: 'GfwU1DC7gEZNs3w41tjBiZYj7BNToDoFEqKY6wZXqs1A', + }) + ).resolves.toBeUndefined() + + // Double migrate should not work + await expect( + Migration.migrate({ + specUri: DB_UPGRADE_PATH, + kdfLevel: 'RAW', + walletName: 'walletwallet.0', + walletKey: 'GfwU1DC7gEZNs3w41tjBiZYj7BNToDoFEqKY6wZXqs1A', + }) + ).rejects.toThrowError('Database is already migrated') + }) +}) diff --git a/wrappers/javascript/aries-askar-nodejs/tests/store.test.ts b/wrappers/javascript/aries-askar-nodejs/tests/store.test.ts new file mode 100644 index 00000000..d8bcbefe --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tests/store.test.ts @@ -0,0 +1,277 @@ +import { Store, StoreKeyMethod, Key, KeyAlgs, AriesAskarError, KdfMethod } from '@hyperledger/aries-askar-shared' +import { promises } from 'fs' + +import { firstEntry, getRawKey, secondEntry, setupWallet, testStoreUri } from './utils' + +describe('Store and Session', () => { + let store: Store + + beforeAll(() => { + require('@hyperledger/aries-askar-nodejs') + }) + + beforeEach(async () => { + store = await setupWallet() + }) + + afterEach(async () => { + await store.close(true) + }) + + test('argon2i mod', async () => { + const argon2iModStore = await Store.provision({ + recreate: true, + passKey: 'abc', + uri: testStoreUri, + keyMethod: new StoreKeyMethod(KdfMethod.Argon2IMod), + }) + + const session = await argon2iModStore.openSession() + await expect(session.fetch({ name: 'unknownKey', category: 'unknownCategory' })).resolves.toBeNull() + + await argon2iModStore.close() + }) + + test('argon2i int', async () => { + const argon2iIntStore = await Store.provision({ + recreate: true, + passKey: 'abc', + uri: testStoreUri, + keyMethod: new StoreKeyMethod(KdfMethod.Argon2IInt), + }) + + const session = await argon2iIntStore.openSession() + await expect(session.fetch({ name: 'unknownKey', category: 'unknownCategory' })).resolves.toBeNull() + + await argon2iIntStore.close() + }) + + test('Rekey', async () => { + const initialKey = Store.generateRawKey() + + // Make sure db directory exists + const storagePath = './tmp' + try { + await promises.access(storagePath) + } catch { + await promises.mkdir(storagePath) + } + + let newStore = await Store.provision({ + recreate: true, + profile: 'rekey', + uri: `sqlite://${storagePath}/rekey.db`, + keyMethod: new StoreKeyMethod(KdfMethod.Raw), + passKey: initialKey, + }) + + const newKey = Store.generateRawKey() + await newStore.rekey({ keyMethod: new StoreKeyMethod(KdfMethod.Raw), passKey: newKey }) + + await newStore.close() + + await expect( + Store.open({ + profile: 'rekey', + uri: `sqlite://${storagePath}/rekey.db`, + keyMethod: new StoreKeyMethod(KdfMethod.Raw), + passKey: initialKey, + }) + ).rejects.toThrowError(AriesAskarError) + + newStore = await Store.open({ + profile: 'rekey', + uri: `sqlite://${storagePath}/rekey.db`, + keyMethod: new StoreKeyMethod(KdfMethod.Raw), + passKey: newKey, + }) + + await newStore.close(true) + }) + + test('Insert', async () => { + const session = await store.openSession() + + await session.insert(firstEntry) + + await expect(session.count(firstEntry)).resolves.toStrictEqual(1) + + await session.close() + }) + + test('Replace', async () => { + const session = await store.openSession() + + await session.insert(firstEntry) + + await expect(session.count(firstEntry)).resolves.toStrictEqual(1) + + const updatedEntry = { ...firstEntry, value: 'bar', tags: { update: 'baz' } } + + await session.replace(updatedEntry) + + await expect(session.count(updatedEntry)).resolves.toStrictEqual(1) + + await session.close() + }) + + test('Remove', async () => { + const session = await store.openSession() + + await session.insert(firstEntry) + + await expect(session.count(firstEntry)).resolves.toStrictEqual(1) + + await session.remove(firstEntry) + + await expect(session.count(firstEntry)).resolves.toStrictEqual(0) + + await session.close() + }) + + test('Remove all', async () => { + const session = await store.openSession() + + await session.insert(firstEntry) + await session.insert(secondEntry) + + await expect(session.count(firstEntry)).resolves.toStrictEqual(2) + + await session.removeAll({ category: firstEntry.category }) + + await expect(session.count(firstEntry)).resolves.toStrictEqual(0) + + await session.close() + }) + + test('Scan', async () => { + const session = await store.openSession() + + await session.insert(firstEntry) + await session.insert(secondEntry) + const found = await store.scan({ category: firstEntry.category }).fetchAll() + expect(found.length).toBe(2) + // value is converted to string, so we expect it as string at this level + expect(found).toEqual( + expect.arrayContaining([firstEntry, { ...secondEntry, value: JSON.stringify(secondEntry.value) }]) + ) + + await session.close() + }) + + test('Transaction basic', async () => { + const txn = await store.openSession(true) + + await txn.insert(firstEntry) + + await expect(txn.count(firstEntry)).resolves.toStrictEqual(1) + + await expect(txn.fetch(firstEntry)).resolves.toMatchObject(firstEntry) + + const found = await txn.fetchAll(firstEntry) + + expect(found[0]).toMatchObject(firstEntry) + + await txn.commit() + + const session = await store.openSession() + + await expect(session.fetch(firstEntry)).resolves.toMatchObject(firstEntry) + }) + + test('Key store', async () => { + const session = await store.openSession() + + const key = Key.generate(KeyAlgs.Ed25519) + + const keyName = 'testKey' + + await session.insertKey({ key, name: keyName, metadata: 'metadata', tags: { a: 'b' } }) + + const fetchedKey1 = await session.fetchKey({ name: keyName }) + expect(fetchedKey1).toMatchObject({ + name: keyName, + tags: { a: 'b' }, + metadata: 'metadata', + }) + + await session.updateKey({ name: keyName, metadata: 'updated metadata', tags: { a: 'c' } }) + const fetchedKey2 = await session.fetchKey({ name: keyName }) + expect(fetchedKey2).toMatchObject({ + name: keyName, + tags: { a: 'c' }, + metadata: 'updated metadata', + }) + + expect(key.jwkThumbprint === fetchedKey1?.key.jwkThumbprint).toBeTruthy() + + const found = await session.fetchAllKeys({ + algorithm: KeyAlgs.Ed25519, + thumbprint: key.jwkThumbprint, + tagFilter: { a: 'c' }, + }) + + expect(found[0]).toMatchObject({ name: keyName, metadata: 'updated metadata', tags: { a: 'c' } }) + + await session.removeKey({ name: keyName }) + + await expect(session.fetchKey({ name: keyName })).resolves.toBeNull() + + await session.close() + + // Clear objects + fetchedKey1?.key.handle.free() + fetchedKey2?.key.handle.free() + key.handle.free() + found.forEach((entry) => entry.key.handle.free()) + }) + + test('profile', async () => { + const session = await store.openSession() + await session.insert(firstEntry) + await session.close() + + const profile = await store.createProfile() + + const session2 = await store.session(profile).open() + //Should not find previously stored record + await expect(session2.count(firstEntry)).resolves.toStrictEqual(0) + await session2.insert(firstEntry) + await expect(session2.count(firstEntry)).resolves.toStrictEqual(1) + await session2.close() + + if (!store.uri.includes(':memory:')) { + // Test accessing profile after re-opening + const key = getRawKey() + const store2 = await Store.open({ uri: testStoreUri, keyMethod: new StoreKeyMethod(KdfMethod.Raw), passKey: key }) + const session3 = await store2.openSession() + //Should not find previously stored record + await expect(session3.count(firstEntry)).resolves.toStrictEqual(0) + await session3.close() + await store2.close() + } + + await expect(store.createProfile(profile)).rejects.toThrowError(AriesAskarError) + + // Check if profile is still usable + const session4 = await store.session(profile).open() + await expect(session4.count(firstEntry)).resolves.toStrictEqual(1) + await session4.close() + + await store.removeProfile(profile) + + // Profile key is cached + const session5 = await store.session(profile).open() + await expect(session5.count(firstEntry)).resolves.toStrictEqual(0) + await session5.close() + + // Unknown profile + const session6 = await store.session('unknown profile').open() + await expect(session6.count(firstEntry)).rejects.toThrowError(AriesAskarError) + await session6.close() + + const session7 = await store.session(profile).open() + await expect(session7.count(firstEntry)).resolves.toStrictEqual(0) + await session7.close() + }) +}) diff --git a/wrappers/javascript/aries-askar-nodejs/tests/utils/fixtures.ts b/wrappers/javascript/aries-askar-nodejs/tests/utils/fixtures.ts new file mode 100644 index 00000000..7135f81f --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tests/utils/fixtures.ts @@ -0,0 +1,20 @@ +export const firstEntry = { + category: 'category-one', + name: 'testEntry', + value: 'foobar', + tags: { '~plaintag': 'a', enctag: 'b' }, +} + +export const secondEntry = { + category: 'category-one', + name: 'secondEntry', + value: { foo: 'bar' }, + tags: { '~plaintag': 'a', enctag: 'b' }, +} + +export const thirdEntry = { + category: 'category-one', + name: 'thirdEntry', + value: { foo: 'baz' }, + tags: { '~plaintag': 'a', enctag: 'b' }, +} diff --git a/wrappers/javascript/aries-askar-nodejs/tests/utils/index.ts b/wrappers/javascript/aries-askar-nodejs/tests/utils/index.ts new file mode 100644 index 00000000..6630a3d2 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tests/utils/index.ts @@ -0,0 +1,2 @@ +export * from './initialize' +export * from './fixtures' diff --git a/wrappers/javascript/aries-askar-nodejs/tests/utils/initialize.ts b/wrappers/javascript/aries-askar-nodejs/tests/utils/initialize.ts new file mode 100644 index 00000000..fb783127 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tests/utils/initialize.ts @@ -0,0 +1,18 @@ +import '@hyperledger/aries-askar-nodejs' +import { Store, StoreKeyMethod, KdfMethod } from '@hyperledger/aries-askar-shared' + +export const getRawKey = () => Store.generateRawKey(Buffer.from('00000000000000000000000000000My1')) +export const testStoreUri = process.env.URI || 'sqlite://:memory:' + +export const setupWallet = async () => { + const key = getRawKey() + + return await Store.provision({ + recreate: true, + uri: testStoreUri, + keyMethod: new StoreKeyMethod(KdfMethod.Raw), + passKey: key, + }) +} + +export const base64url = (str: string) => Buffer.from(str).toString('base64url') diff --git a/wrappers/javascript/aries-askar-nodejs/tsconfig.build.json b/wrappers/javascript/aries-askar-nodejs/tsconfig.build.json new file mode 100644 index 00000000..647179f9 --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build", + "types": ["node"] + }, + "include": ["src/**/*"] +} diff --git a/wrappers/javascript/aries-askar-nodejs/tsconfig.json b/wrappers/javascript/aries-askar-nodejs/tsconfig.json new file mode 100644 index 00000000..7a66af8f --- /dev/null +++ b/wrappers/javascript/aries-askar-nodejs/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["node", "jest"] + } +} diff --git a/wrappers/javascript/aries-askar-react-native/README.md b/wrappers/javascript/aries-askar-react-native/README.md new file mode 100644 index 00000000..2fda5e8a --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/README.md @@ -0,0 +1,36 @@ +# Aries Askar React Native + +Wrapper for React Native around Aries Askar + +## Requirements + +This module uses the new React Native Turbo Modules. These are faster than the +previous Native Modules, and can be completely synchronous. A React Native +version of `>= 0.66.0` is required for this package to work. + +## Installation + +```sh +yarn add @hyperledger/aries-askar-react-native +``` + +## Setup + +You can import all types and classes from the `@hyperledger/aries-askar-react-native` library: + +```typescript +import { Key, KeyAlgs } from '@hyperledger/aries-askar-react-native' + +const seed = Uint8Array.from(Buffer.from('testseed000000000000000000000001')) +const key = Key.fromSeed({ algorithm: KeyAlgs.Bls12381G1, seed }) +``` + +> **Note**: If you want to use this library in a cross-platform environment you need to import methods from the `@hyperledger/aries-askar-shared` package instead. This is a platform independent package that allows to register the native bindings. The `@hyperledger/aries-askar-react-native` package uses this package under the hood. See the [Aries Askar Shared README](https://github.com/hyperledger/aries-askar/tree/main/wrappers/javascript/aries-askar-shared/README.md) for documentation on how to use this package. + +## Version Compatibility + +The JavaScript wrapper is versioned independently from the native bindings. The following table shows the compatibility between the different versions: + +| Aries Askar | JavaScript Wrapper | +| ----------- | ------------------ | +| v0.2.9 | v0.1.0, v0.1.1 | diff --git a/wrappers/javascript/aries-askar-react-native/android/CMakeLists.txt b/wrappers/javascript/aries-askar-react-native/android/CMakeLists.txt new file mode 100644 index 00000000..74b6aff9 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/android/CMakeLists.txt @@ -0,0 +1,142 @@ +project(ariesaskarreactnative) +cmake_minimum_required(VERSION 3.4.1) + +set (PACKAGE_NAME "ariesaskarreactnative") +set (LIB_NAME "ariesaskar") +file (GLOB LIBASKAR_DIR "${CMAKE_SOURCE_DIR}/../native/mobile/android/${ANDROID_ABI}") + +set(CMAKE_VERBOSE_MAKEFILE ON) +set(CMAKE_CXX_STANDARD 14) + +set (BUILD_DIR ${CMAKE_SOURCE_DIR}/build) +if(${REACT_NATIVE_VERSION} GREATER_EQUAL 71) + find_package(fbjni REQUIRED CONFIG) + find_package(ReactAndroid REQUIRED CONFIG) +else() + set (RN_SO_DIR ${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/first-party/react/jni) +endif() + +if(${REACT_NATIVE_VERSION} LESS 66) + set (INCLUDE_JSI_CPP "${NODE_MODULES_DIR}/react-native/ReactCommon/jsi/jsi/jsi.cpp") + set (INCLUDE_JSIDYNAMIC_CPP "${NODE_MODULES_DIR}/react-native/ReactCommon/jsi/jsi/JSIDynamic.cpp") +endif() + +include_directories( + ../cpp + ../cpp/include + "${LIBASKAR_DIR}" +) + +add_library( + ${PACKAGE_NAME} + SHARED + cpp-adapter.cpp + ../cpp/HostObject.cpp + ../cpp/turboModuleUtility.cpp + ../cpp/ariesAskar.cpp +) + +if(${REACT_NATIVE_VERSION} GREATER_EQUAL 71) + target_include_directories( + ${PACKAGE_NAME} + PRIVATE + "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule" + "${NODE_MODULES_DIR}/react-native/ReactCommon" + "${NODE_MODULES_DIR}/react-native/ReactCommon/callinvoker" + "${NODE_MODULES_DIR}/react-native/ReactCommon/jsi" + "${NODE_MODULES_DIR}/react-native/ReactCommon/react/renderer/graphics/platform/cxx" + "${NODE_MODULES_DIR}/react-native/ReactCommon/runtimeexecutor" + "${NODE_MODULES_DIR}/react-native/ReactCommon/yoga" + ) +else() + file (GLOB LIBFBJNI_INCLUDE_DIR "${BUILD_DIR}/fbjni-*-headers.jar/") + + target_include_directories( + ${PACKAGE_NAME} + PRIVATE + "${LIBFBJNI_INCLUDE_DIR}" + "${NODE_MODULES_DIR}/react-native/React" + "${NODE_MODULES_DIR}/react-native/React/Base" + "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni" + "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni" + "${NODE_MODULES_DIR}/react-native/ReactCommon" + "${NODE_MODULES_DIR}/react-native/ReactCommon/callinvoker" + "${NODE_MODULES_DIR}/react-native/ReactCommon/jsi" + ${INCLUDE_JSI_CPP} # only on older RN versions + ${INCLUDE_JSIDYNAMIC_CPP} # only on older RN versions + ) +endif() + +file (GLOB LIBRN_DIR "${BUILD_DIR}/react-native-0*/jni/${ANDROID_ABI}") + +if(${REACT_NATIVE_VERSION} LESS 71) + find_library( + FBJNI_LIB + fbjni + PATHS ${LIBRN_DIR} + NO_CMAKE_FIND_ROOT_PATH + ) +endif() + +if(${REACT_NATIVE_VERSION} LESS 71) + find_library( + REACT_NATIVE_JNI_LIB + reactnativejni + PATHS ${LIBRN_DIR} + NO_CMAKE_FIND_ROOT_PATH + ) +endif() + +if(${REACT_NATIVE_VERSION} GREATER_EQUAL 71) + target_link_libraries( + ${PACKAGE_NAME} + ReactAndroid::jsi + ReactAndroid::reactnativejni + fbjni::fbjni + ) +elseif(${REACT_NATIVE_VERSION} LESS 66) + # JSI lib didn't exist on RN 0.65 and before. Simply omit it. + set (JSI_LIB "") +else() + # RN 0.66 distributes libjsi.so, can be used instead of compiling jsi.cpp manually. + find_library( + JSI_LIB + jsi + PATHS ${LIBRN_DIR} + NO_CMAKE_FIND_ROOT_PATH + ) +endif() + +find_library( + REANIMATED_LIB + reanimated + PATHS ${LIBREANIMATED_DIR} + NO_CMAKE_FIND_ROOT_PATH +) + +find_library( + LOG_LIB + log +) + + +find_library( + ASKAR_LIB + aries_askar + paths ${LIBASKAR_DIR} + NO_CMAKE_FIND_ROOT_PATH +) + +if (NOT ASKAR_LIB) + message(FATAL_ERROR "Could not find ASKAR_LIB at: ${LIBASKAR_DIR}") +endif() + +target_link_libraries( + ${PACKAGE_NAME} + ${ASKAR_LIB} + ${LOG_LIB} + ${JSI_LIB} + ${REACT_NATIVE_JNI_LIB} + ${FBJNI_LIB} + android +) diff --git a/wrappers/javascript/aries-askar-react-native/android/build.gradle b/wrappers/javascript/aries-askar-react-native/android/build.gradle new file mode 100644 index 00000000..2cc06db3 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/android/build.gradle @@ -0,0 +1,282 @@ +import org.apache.tools.ant.filters.ReplaceTokens +import java.nio.file.Paths + +buildscript { + repositories { + google() + jcenter() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.2.2' + classpath 'de.undercouch:gradle-download-task:4.1.2' + } +} + +static def findNodeModules(baseDir) { + def basePath = baseDir.toPath().normalize() + while (basePath) { + def nodeModulesPath = Paths.get(basePath.toString(), "node_modules") + def reactNativePath = Paths.get(nodeModulesPath.toString(), "react-native") + if (nodeModulesPath.toFile().exists() && reactNativePath.toFile().exists()) { + return nodeModulesPath.toString() + } + basePath = basePath.getParent() + } + throw new GradleException("aries-askar: Failed to find node_modules/ path!") +} + +static def findNodeModulePath(baseDir, packageName) { + def basePath = baseDir.toPath().normalize() + while (basePath) { + def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName) + if (candidatePath.toFile().exists()) { + return candidatePath.toString() + } + basePath = basePath.getParent() + } + return null +} + +def isNewArchitectureEnabled() { + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} + +def nodeModules = findNodeModules(projectDir) + +def reactNative = new File("$nodeModules/react-native") +def CMAKE_NODE_MODULES_DIR = project.getProjectDir().getParentFile().getParent() +def reactProperties = new Properties() +file("$nodeModules/react-native/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) } +def REACT_NATIVE_FULL_VERSION = reactProperties.getProperty("VERSION_NAME") +def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME").split("\\.")[1].toInteger() + +apply plugin: 'com.android.library' +apply plugin: 'de.undercouch.download' + +def getExt(name) { + return rootProject.ext.get(name) +} + +def resolveBuildType() { + def buildType = "debug" + tasks.all({ task -> + if (task.name == "buildCMakeRelease") { + buildType = "release" + } + }) + return buildType +} + +android { + compileSdkVersion getExt('compileSdkVersion') + buildToolsVersion getExt('buildToolsVersion') + ndkVersion getExt('ndkVersion') + + if (REACT_NATIVE_VERSION >= 71) { + buildFeatures { + prefab true + } + } + + defaultConfig { + minSdkVersion 21 + targetSdkVersion getExt('targetSdkVersion') + + externalNativeBuild { + cmake { + cppFlags "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID" + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + arguments "-DANDROID_STL=c++_shared", + "-DREACT_NATIVE_VERSION=${REACT_NATIVE_VERSION}", + "-DNODE_MODULES_DIR=${nodeModules}" + } + } + + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + packagingOptions { + excludes = ["**/libc++_shared.so", + "**/libfbjni.so", + "**/libjsi.so", + "**/libreactnativejni.so", + "**/libjscexecutor.so", + ] + } + + buildTypes { + release { + minifyEnabled false + } + } + + lintOptions { + disable 'GradleCompatible' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + configurations { + extractHeaders + extractJNI + } + + sourceSets { + main { + jniLibs.srcDirs = ['../native/mobile/android'] + } + } +} + +repositories { + mavenCentral() + google() + + def found = false + def defaultDir = null + def androidSourcesName = 'React Native sources' + + if (rootProject.ext.has('reactNativeAndroidRoot')) { + defaultDir = rootProject.ext.get('reactNativeAndroidRoot') + } else { + defaultDir = new File( + projectDir, + '/../../../node_modules/react-native/android' + ) + } + + if (defaultDir.exists()) { + maven { + url defaultDir.toString() + name androidSourcesName + } + + logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}") + found = true + } else { + def parentDir = rootProject.projectDir + + 1.upto(5, { + if (found) return true + parentDir = parentDir.parentFile + + def androidSourcesDir = new File( + parentDir, + 'node_modules/react-native' + ) + + def androidPrebuiltBinaryDir = new File( + parentDir, + 'node_modules/react-native/android' + ) + + if (androidPrebuiltBinaryDir.exists()) { + maven { + url androidPrebuiltBinaryDir.toString() + name androidSourcesName + } + + logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}") + found = true + } else if (androidSourcesDir.exists()) { + maven { + url androidSourcesDir.toString() + name androidSourcesName + } + + logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}") + found = true + } + }) + } + + if (!found) { + throw new GradleException( + "${project.name}: unable to locate React Native android sources. " + + "Ensure you have you installed React Native as a dependency in your project and try again." + ) + } +} + + +dependencies { + if (REACT_NATIVE_VERSION >= 71) { + implementation "com.facebook.react:react-android:" + } else { + // noinspection GradleDynamicVersion + implementation 'com.facebook.react:react-native:' + REACT_NATIVE_FULL_VERSION + } + + if (REACT_NATIVE_VERSION < 71) { + //noinspection GradleDynamicVersion + extractHeaders("com.facebook.fbjni:fbjni:+:headers") + //noinspection GradleDynamicVersion + extractJNI("com.facebook.fbjni:fbjni:+") + + def rnAarMatcher = "**/react-native/**/*${resolveBuildType()}.aar" + if (REACT_NATIVE_VERSION < 69) { + rnAarMatcher = "**/**/*.aar" + } + + def rnAAR = fileTree("$reactNative/android").matching({ it.include rnAarMatcher }).singleFile + def jscAAR = fileTree("${nodeModules}/jsc-android/dist/org/webkit/android-jsc").matching({ it.include "**/**/*.aar" }).singleFile + extractJNI(files(rnAAR, jscAAR)) + } +} + +def downloadsDir = new File("$buildDir/downloads") + +task createNativeDepsDirectories { + doLast { + downloadsDir.mkdirs() + } +} + + +task extractAARHeaders { + doLast { + configurations.extractHeaders.files.each { + def file = it.absoluteFile + copy { + from zipTree(file) + into "$buildDir/$file.name" + include "**/*.h" + } + } + } +} + +extractAARHeaders.mustRunAfter createNativeDepsDirectories + +task extractJNIFiles { + doLast { + configurations.extractJNI.files.each { + def file = it.absoluteFile + + copy { + from zipTree(file) + into "$buildDir/$file.name" + include "jni/**/*" + } + } + } +} + +extractJNIFiles.mustRunAfter extractAARHeaders + +tasks.whenTaskAdded { task -> + if (!task.name.contains('Clean') && (task.name.contains('externalNative') || task.name.contains('CMake'))) { + task.dependsOn(extractAARHeaders) + task.dependsOn(extractJNIFiles) + } +} diff --git a/wrappers/javascript/aries-askar-react-native/android/cpp-adapter.cpp b/wrappers/javascript/aries-askar-react-native/android/cpp-adapter.cpp new file mode 100644 index 00000000..64a8463f --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/android/cpp-adapter.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +#include + +using namespace facebook; + +struct AriesAskarModule : jni::JavaClass { +public: + __unused static constexpr auto kJavaDescriptor = "Lorg/hyperledger/ariesaskar/AriesAskarModule;"; + + static constexpr auto TAG = "AriesAskar"; + + static void registerNatives() { + javaClassStatic()->registerNatives({ makeNativeMethod("installNative", AriesAskarModule::installNative) }); + } + +private: + static void installNative(jni::alias_ref, + jlong jsiRuntimePointer, + jni::alias_ref jsCallInvokerHolder) { + + auto runtime = reinterpret_cast(jsiRuntimePointer); + auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker(); + + ariesAskarTurboModuleUtility::registerTurboModule(*runtime, jsCallInvoker); + } +}; + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return facebook::jni::initialize(vm, [] { + AriesAskarModule::registerNatives(); + }); +} diff --git a/wrappers/javascript/aries-askar-react-native/android/src/main/AndroidManifest.xml b/wrappers/javascript/aries-askar-react-native/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a4d6521e --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/wrappers/javascript/aries-askar-react-native/android/src/main/java/org/hyperledger/ariesaskar/AriesAskarModule.java b/wrappers/javascript/aries-askar-react-native/android/src/main/java/org/hyperledger/ariesaskar/AriesAskarModule.java new file mode 100644 index 00000000..b7653ae8 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/android/src/main/java/org/hyperledger/ariesaskar/AriesAskarModule.java @@ -0,0 +1,56 @@ +package org.hyperledger.ariesaskar; + +import android.util.Log; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.facebook.proguard.annotations.DoNotStrip; + +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.turbomodule.core.CallInvokerHolderImpl; +import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder; + +@Keep +@DoNotStrip +public class AriesAskarModule extends ReactContextBaseJavaModule { + static { + System.loadLibrary("ariesaskarreactnative"); + } + + public static final String NAME = "AriesAskar"; + + static String TAG = "AriesAskar"; + + public AriesAskarModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + @NonNull + public String getName() { + return TAG; + } + + private static native void installNative(long jsiRuntimePointer, CallInvokerHolderImpl jsCallInvokerHolder); + + @ReactMethod(isBlockingSynchronousMethod = true) + public boolean install() { + try { + System.loadLibrary("ariesaskarreactnative"); + ReactContext context = getReactApplicationContext(); + long jsContextPointer = context.getJavaScriptContextHolder().get(); + CallInvokerHolderImpl holder = (CallInvokerHolderImpl) context.getCatalystInstance().getJSCallInvokerHolder(); + installNative(jsContextPointer, holder); + return true; + } catch (Exception exception) { + Log.e(NAME, "Failed to install JSI Bindings!", exception); + return false; + } + } +} diff --git a/wrappers/javascript/aries-askar-react-native/android/src/main/java/org/hyperledger/ariesaskar/AriesAskarPackage.java b/wrappers/javascript/aries-askar-react-native/android/src/main/java/org/hyperledger/ariesaskar/AriesAskarPackage.java new file mode 100644 index 00000000..d07714d2 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/android/src/main/java/org/hyperledger/ariesaskar/AriesAskarPackage.java @@ -0,0 +1,25 @@ +package org.hyperledger.ariesaskar; + +import androidx.annotation.NonNull; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.Collections; +import java.util.List; + +public class AriesAskarPackage implements ReactPackage { + @NonNull + @Override + public List createNativeModules(@NonNull ReactApplicationContext reactContext) { + return Collections.singletonList(new AriesAskarModule(reactContext)); + } + + @NonNull + @Override + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/wrappers/javascript/aries-askar-react-native/aries-askar.podspec b/wrappers/javascript/aries-askar-react-native/aries-askar.podspec new file mode 100644 index 00000000..655864ca --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/aries-askar.podspec @@ -0,0 +1,29 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +Pod::Spec.new do |s| + s.name = "aries-askar" + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.platforms = { :ios => "12.0" } + s.source = { :git => "https://github.com/hyperledger/aries-askar", :tag => "#{s.version}" } + + s.header_mappings_dir = "cpp" + + s.pod_target_xcconfig = { + :USE_HEADERMAP => "No" + } + + s.ios.vendored_frameworks = "native/mobile/ios/aries_askar.xcframework" + + s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}" + + s.dependency "React-Core" + s.dependency "React-callinvoker" + s.dependency "React" +end diff --git a/wrappers/javascript/aries-askar-react-native/babel.config.js b/wrappers/javascript/aries-askar-react-native/babel.config.js new file mode 100644 index 00000000..f424a0f7 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/babel.config.js @@ -0,0 +1,18 @@ +const path = require('path') + +const ariesAskarShared = require('../aries-askar-shared/package.json') + +module.exports = { + presets: ['module:metro-react-native-babel-preset'], + plugins: [ + [ + 'module-resolver', + { + extensions: ['.tsx', '.ts', '.js', '.json'], + alias: { + [ariesAskarShared.name]: path.join(__dirname, '../aries-askar-shared', ariesAskarShared.source), + }, + }, + ], + ], +} diff --git a/wrappers/javascript/aries-askar-react-native/cpp/HostObject.cpp b/wrappers/javascript/aries-askar-react-native/cpp/HostObject.cpp new file mode 100644 index 00000000..ef70ab02 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/cpp/HostObject.cpp @@ -0,0 +1,190 @@ +#include +#include +#include + +AriesAskarTurboModuleHostObject::AriesAskarTurboModuleHostObject( + jsi::Runtime &rt) { + return; +} +FunctionMap AriesAskarTurboModuleHostObject::functionMapping(jsi::Runtime &rt) { + FunctionMap fMap; + + fMap.insert(std::make_tuple("version", &ariesAskar::version)); + fMap.insert(std::make_tuple("getCurrentError", &ariesAskar::getCurrentError)); + fMap.insert( + std::make_tuple("setDefaultLogger", &ariesAskar::setDefaultLogger)); + + fMap.insert(std::make_tuple("storeOpen", &ariesAskar::storeOpen)); + fMap.insert( + std::make_tuple("storeGenerateRawKey", &ariesAskar::storeGenerateRawKey)); + fMap.insert(std::make_tuple("storeProvision", &ariesAskar::storeProvision)); + fMap.insert(std::make_tuple("storeOpen", &ariesAskar::storeOpen)); + fMap.insert(std::make_tuple("storeClose", &ariesAskar::storeClose)); + fMap.insert( + std::make_tuple("storeCreateProfile", &ariesAskar::storeCreateProfile)); + fMap.insert( + std::make_tuple("storeGenerateRawKey", &ariesAskar::storeGenerateRawKey)); + fMap.insert( + std::make_tuple("storeGetProfileName", &ariesAskar::storeGetProfileName)); + fMap.insert(std::make_tuple("storeProvision", &ariesAskar::storeProvision)); + fMap.insert(std::make_tuple("storeRekey", &ariesAskar::storeRekey)); + fMap.insert(std::make_tuple("storeRemove", &ariesAskar::storeRemove)); + fMap.insert( + std::make_tuple("storeRemoveProfile", &ariesAskar::storeRemoveProfile)); + + fMap.insert(std::make_tuple("sessionClose", &ariesAskar::sessionClose)); + fMap.insert(std::make_tuple("sessionCount", &ariesAskar::sessionCount)); + fMap.insert(std::make_tuple("sessionFetch", &ariesAskar::sessionFetch)); + fMap.insert(std::make_tuple("sessionFetchAll", &ariesAskar::sessionFetchAll)); + fMap.insert( + std::make_tuple("sessionFetchAllKeys", &ariesAskar::sessionFetchAllKeys)); + fMap.insert(std::make_tuple("sessionFetchKey", &ariesAskar::sessionFetchKey)); + fMap.insert( + std::make_tuple("sessionInsertKey", &ariesAskar::sessionInsertKey)); + fMap.insert( + std::make_tuple("sessionRemoveAll", &ariesAskar::sessionRemoveAll)); + fMap.insert( + std::make_tuple("sessionRemoveKey", &ariesAskar::sessionRemoveKey)); + fMap.insert(std::make_tuple("sessionStart", &ariesAskar::sessionStart)); + fMap.insert(std::make_tuple("sessionUpdate", &ariesAskar::sessionUpdate)); + fMap.insert( + std::make_tuple("sessionUpdateKey", &ariesAskar::sessionUpdateKey)); + + fMap.insert( + std::make_tuple("entryListGetName", &ariesAskar::entryListGetName)); + fMap.insert( + std::make_tuple("entryListGetValue", &ariesAskar::entryListGetValue)); + fMap.insert(std::make_tuple("entryListGetCategory", + &ariesAskar::entryListGetCategory)); + fMap.insert( + std::make_tuple("entryListGetTags", &ariesAskar::entryListGetTags)); + fMap.insert(std::make_tuple("entryListCount", &ariesAskar::entryListCount)); + fMap.insert(std::make_tuple("entryListFree", &ariesAskar::entryListFree)); + + fMap.insert(std::make_tuple("scanFree", &ariesAskar::scanFree)); + fMap.insert(std::make_tuple("scanNext", &ariesAskar::scanNext)); + fMap.insert(std::make_tuple("scanStart", &ariesAskar::scanStart)); + + fMap.insert(std::make_tuple("keyFromJwk", &ariesAskar::keyFromJwk)); + fMap.insert( + std::make_tuple("keyFromKeyExchange", &ariesAskar::keyFromKeyExchange)); + fMap.insert( + std::make_tuple("keyFromPublicBytes", &ariesAskar::keyFromPublicBytes)); + fMap.insert( + std::make_tuple("keyFromSecretBytes", &ariesAskar::keyFromSecretBytes)); + fMap.insert(std::make_tuple("keyFromSeed", &ariesAskar::keyFromSeed)); + fMap.insert(std::make_tuple("keyGenerate", &ariesAskar::keyGenerate)); + fMap.insert(std::make_tuple("keyGetAlgorithm", &ariesAskar::keyGetAlgorithm)); + fMap.insert(std::make_tuple("keyGetEphemeral", &ariesAskar::keyGetEphemeral)); + fMap.insert(std::make_tuple("keyGetJwkPublic", &ariesAskar::keyGetJwkPublic)); + fMap.insert(std::make_tuple("keyGetJwkSecret", &ariesAskar::keyGetJwkSecret)); + fMap.insert( + std::make_tuple("keyGetJwkThumbprint", &ariesAskar::keyGetJwkThumbprint)); + fMap.insert( + std::make_tuple("keyGetPublicBytes", &ariesAskar::keyGetPublicBytes)); + fMap.insert( + std::make_tuple("keyGetSecretBytes", &ariesAskar::keyGetSecretBytes)); + fMap.insert(std::make_tuple("keySignMessage", &ariesAskar::keySignMessage)); + fMap.insert(std::make_tuple("keyUnwrapKey", &ariesAskar::keyUnwrapKey)); + fMap.insert( + std::make_tuple("keyVerifySignature", &ariesAskar::keyVerifySignature)); + fMap.insert(std::make_tuple("keyWrapKey", &ariesAskar::keyWrapKey)); + + fMap.insert(std::make_tuple("keyConvert", &ariesAskar::keyConvert)); + fMap.insert(std::make_tuple("keyFree", &ariesAskar::keyFree)); + + fMap.insert(std::make_tuple("keyCryptoBox", &ariesAskar::keyCryptoBox)); + fMap.insert( + std::make_tuple("keyCryptoBoxOpen", &ariesAskar::keyCryptoBoxOpen)); + fMap.insert(std::make_tuple("keyCryptoBoxRandomNonce", + &ariesAskar::keyCryptoBoxRandomNonce)); + fMap.insert( + std::make_tuple("keyCryptoBoxSeal", &ariesAskar::keyCryptoBoxSeal)); + fMap.insert(std::make_tuple("keyCryptoBoxSealOpen", + &ariesAskar::keyCryptoBoxSealOpen)); + + fMap.insert( + std::make_tuple("keyDeriveEcdh1pu", &ariesAskar::keyDeriveEcdh1pu)); + fMap.insert(std::make_tuple("keyDeriveEcdhEs", &ariesAskar::keyDeriveEcdhEs)); + + fMap.insert(std::make_tuple("keyAeadDecrypt", &ariesAskar::keyAeadDecrypt)); + fMap.insert(std::make_tuple("keyAeadEncrypt", &ariesAskar::keyAeadEncrypt)); + fMap.insert( + std::make_tuple("keyAeadGetPadding", &ariesAskar::keyAeadGetPadding)); + fMap.insert( + std::make_tuple("keyAeadGetParams", &ariesAskar::keyAeadGetParams)); + fMap.insert( + std::make_tuple("keyAeadRandomNonce", &ariesAskar::keyAeadRandomNonce)); + + fMap.insert( + std::make_tuple("keyEntryListCount", &ariesAskar::keyEntryListCount)); + fMap.insert( + std::make_tuple("keyEntryListFree", &ariesAskar::keyEntryListFree)); + fMap.insert(std::make_tuple("keyEntryListGetAlgorithm", + &ariesAskar::keyEntryListGetAlgorithm)); + fMap.insert(std::make_tuple("keyEntryListGetMetadata", + &ariesAskar::keyEntryListGetMetadata)); + fMap.insert( + std::make_tuple("keyEntryListGetName", &ariesAskar::keyEntryListGetName)); + fMap.insert( + std::make_tuple("keyEntryListGetTags", &ariesAskar::keyEntryListGetTags)); + fMap.insert(std::make_tuple("keyEntryListLoadLocal", + &ariesAskar::keyEntryListLoadLocal)); + + fMap.insert(std::make_tuple("migrateIndySdk", &ariesAskar::migrateIndySdk)); + + return fMap; +} + +jsi::Function AriesAskarTurboModuleHostObject::call(jsi::Runtime &rt, + const char *name, Cb cb) { + return jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, name), 1, + [this, cb](jsi::Runtime &rt, const jsi::Value &thisValue, + const jsi::Value *arguments, size_t count) -> jsi::Value { + const jsi::Value *val = &arguments[0]; + ariesAskarTurboModuleUtility::assertValueIsObject(rt, val); + return (*cb)(rt, val->getObject(rt)); + }); +}; + +std::vector +AriesAskarTurboModuleHostObject::getPropertyNames(jsi::Runtime &rt) { + auto fMap = AriesAskarTurboModuleHostObject::functionMapping(rt); + std::vector result; + for (FunctionMap::iterator it = fMap.begin(); it != fMap.end(); ++it) { + result.push_back(jsi::PropNameID::forUtf8(rt, it->first)); + } + + return result; +} + +jsi::Value +AriesAskarTurboModuleHostObject::get(jsi::Runtime &rt, + const jsi::PropNameID &propNameId) { + auto propName = propNameId.utf8(rt); + auto fMap = AriesAskarTurboModuleHostObject::functionMapping(rt); + for (FunctionMap::iterator it = fMap.begin(); it != fMap.end(); ++it) { + if (it->first == propName) { + return AriesAskarTurboModuleHostObject::call(rt, it->first, it->second); + } + } + + /* + * https://overreacted.io/why-do-react-elements-have-typeof-property/ + * + * This is a special React key on the object that `React.createElement()` + * returns. + * + * This function is called under-the-hood to see if this React element is + * renderable. + * + * When we return undefined, instead of `Symbol.for('react.element'), we tell + * React that this element is not renderable. + * + */ + if (propName == "$$typeof") + return jsi::Value::undefined(); + + throw jsi::JSError(rt, "Function: " + propName + " is not defined"); +} diff --git a/wrappers/javascript/aries-askar-react-native/cpp/HostObject.h b/wrappers/javascript/aries-askar-react-native/cpp/HostObject.h new file mode 100644 index 00000000..66f04daf --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/cpp/HostObject.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +#include +#include + +using namespace facebook; + +typedef jsi::Value (*Cb)(jsi::Runtime &rt, jsi::Object options); +typedef std::map FunctionMap; + +class JSI_EXPORT AriesAskarTurboModuleHostObject : public jsi::HostObject { +public: + AriesAskarTurboModuleHostObject(jsi::Runtime &rt); + jsi::Function call(jsi::Runtime &rt, const char *name, Cb cb); + FunctionMap functionMapping(jsi::Runtime &rt); + +public: + jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name) override; + std::vector getPropertyNames(jsi::Runtime &rt) override; +}; diff --git a/wrappers/javascript/aries-askar-react-native/cpp/ariesAskar.cpp b/wrappers/javascript/aries-askar-react-native/cpp/ariesAskar.cpp new file mode 100644 index 00000000..12e172a9 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/cpp/ariesAskar.cpp @@ -0,0 +1,981 @@ +#include + +#include + +using namespace ariesAskarTurboModuleUtility; + +namespace ariesAskar { + +jsi::Value version(jsi::Runtime &rt, jsi::Object options) { + return jsi::String::createFromAscii(rt, askar_version()); +}; + +jsi::Value getCurrentError(jsi::Runtime &rt, jsi::Object options) { + const char *error; + askar_get_current_error(&error); + return jsi::String::createFromAscii(rt, error); +}; + +jsi::Value setDefaultLogger(jsi::Runtime &rt, jsi::Object options) { + ErrorCode code = askar_set_default_logger(); + + return createReturnValue(rt, code, nullptr); +}; + +jsi::Value entryListCount(jsi::Runtime &rt, jsi::Object options) { + auto entryListHandle = + jsiToValue(rt, options, "entryListHandle"); + + int32_t out; + + ErrorCode code = askar_entry_list_count(entryListHandle, &out); + + return createReturnValue(rt, code, &out); +}; +jsi::Value entryListFree(jsi::Runtime &rt, jsi::Object options) { + auto entryListHandle = + jsiToValue(rt, options, "entryListHandle"); + + askar_entry_list_free(entryListHandle); + + return createReturnValue(rt, ErrorCode::Success, nullptr); +} + +jsi::Value entryListGetCategory(jsi::Runtime &rt, jsi::Object options) { + auto entryListHandle = + jsiToValue(rt, options, "entryListHandle"); + auto index = jsiToValue(rt, options, "index"); + + const char *out; + + ErrorCode code = askar_entry_list_get_category(entryListHandle, index, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value entryListGetTags(jsi::Runtime &rt, jsi::Object options) { + auto entryListHandle = + jsiToValue(rt, options, "entryListHandle"); + auto index = jsiToValue(rt, options, "index"); + + const char *out; + + ErrorCode code = askar_entry_list_get_tags(entryListHandle, index, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value entryListGetValue(jsi::Runtime &rt, jsi::Object options) { + auto entryListHandle = + jsiToValue(rt, options, "entryListHandle"); + auto index = jsiToValue(rt, options, "index"); + + SecretBuffer out; + + ErrorCode code = askar_entry_list_get_value(entryListHandle, index, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value entryListGetName(jsi::Runtime &rt, jsi::Object options) { + auto entryListHandle = + jsiToValue(rt, options, "entryListHandle"); + auto index = jsiToValue(rt, options, "index"); + + const char *out; + + ErrorCode code = askar_entry_list_get_name(entryListHandle, index, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value storeOpen(jsi::Runtime &rt, jsi::Object options) { + auto specUri = jsiToValue(rt, options, "specUri"); + auto keyMethod = jsiToValue(rt, options, "keyMethod", true); + auto passKey = jsiToValue(rt, options, "passKey", true); + auto profile = jsiToValue(rt, options, "profile", true); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_store_open( + specUri.c_str(), keyMethod.length() ? keyMethod.c_str() : nullptr, + passKey.length() ? passKey.c_str() : nullptr, + profile.length() ? profile.c_str() : nullptr, callbackWithResponse, + CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value storeProvision(jsi::Runtime &rt, jsi::Object options) { + auto specUri = jsiToValue(rt, options, "specUri"); + auto keyMethod = jsiToValue(rt, options, "keyMethod", true); + auto passKey = jsiToValue(rt, options, "passKey", true); + auto profile = jsiToValue(rt, options, "profile", true); + auto recreate = jsiToValue(rt, options, "recreate"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_store_provision( + specUri.c_str(), keyMethod.length() ? keyMethod.c_str() : nullptr, + passKey.length() ? passKey.c_str() : nullptr, + profile.length() ? profile.c_str() : nullptr, recreate, + callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value storeGenerateRawKey(jsi::Runtime &rt, jsi::Object options) { + auto seed = jsiToValue(rt, options, "seed", true); + + const char *out; + ErrorCode code = askar_store_generate_raw_key(seed, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value storeClose(jsi::Runtime &rt, jsi::Object options) { + auto storeHandle = jsiToValue(rt, options, "storeHandle"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_store_close(storeHandle, callback, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value storeCreateProfile(jsi::Runtime &rt, jsi::Object options) { + auto storeHandle = jsiToValue(rt, options, "storeHandle"); + auto profile = jsiToValue(rt, options, "profile", true); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_store_create_profile( + storeHandle, profile.length() ? profile.c_str() : nullptr, + callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value storeGetProfileName(jsi::Runtime &rt, jsi::Object options) { + auto storeHandle = jsiToValue(rt, options, "storeHandle"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_store_get_profile_name( + storeHandle, callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value storeRekey(jsi::Runtime &rt, jsi::Object options) { + auto storeHandle = jsiToValue(rt, options, "storeHandle"); + auto keyMethod = jsiToValue(rt, options, "keyMethod", true); + auto passKey = jsiToValue(rt, options, "passKey"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_store_rekey(storeHandle, + keyMethod.length() ? keyMethod.c_str() : nullptr, + passKey.c_str(), callback, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value storeRemove(jsi::Runtime &rt, jsi::Object options) { + auto specUri = jsiToValue(rt, options, "specUri"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_store_remove(specUri.c_str(), callbackWithResponse, + CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value storeRemoveProfile(jsi::Runtime &rt, jsi::Object options) { + auto storeHandle = jsiToValue(rt, options, "storeHandle"); + auto profile = jsiToValue(rt, options, "profile"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_store_remove_profile( + storeHandle, profile.c_str(), callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionClose(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + int8_t commit = jsiToValue(rt, options, "commit"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = + askar_session_close(sessionHandle, commit, callback, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionCount(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + auto category = jsiToValue(rt, options, "category"); + auto tagFilter = jsiToValue(rt, options, "tagFilter", true); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = + askar_session_count(sessionHandle, category.c_str(), + tagFilter.length() ? tagFilter.c_str() : nullptr, + callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionFetch(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + auto category = jsiToValue(rt, options, "category"); + auto name = jsiToValue(rt, options, "name"); + int8_t forUpdate = jsiToValue(rt, options, "forUpdate"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = + askar_session_fetch(sessionHandle, category.c_str(), name.c_str(), + forUpdate, callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionFetchAll(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + auto category = jsiToValue(rt, options, "category"); + auto tagFilter = jsiToValue(rt, options, "tagFilter", true); + int64_t limit = jsiToValue(rt, options, "limit", true); + int8_t forUpdate = jsiToValue(rt, options, "forUpdate"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_session_fetch_all( + sessionHandle, category.c_str(), + tagFilter.length() ? tagFilter.c_str() : nullptr, limit, forUpdate, + callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionFetchAllKeys(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + auto algorithm = jsiToValue(rt, options, "algorithm", true); + auto thumbprint = jsiToValue(rt, options, "thumbprint", true); + auto tagFilter = jsiToValue(rt, options, "tagFilter", true); + auto limit = jsiToValue(rt, options, "limit", true); + auto forUpdate = jsiToValue(rt, options, "forUpdate"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_session_fetch_all_keys( + sessionHandle, algorithm.length() ? algorithm.c_str() : nullptr, + thumbprint.length() ? thumbprint.c_str() : nullptr, + tagFilter.length() ? tagFilter.c_str() : nullptr, limit, forUpdate, + callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionFetchKey(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + auto name = jsiToValue(rt, options, "name"); + auto forUpdate = jsiToValue(rt, options, "forUpdate"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = + askar_session_fetch_key(sessionHandle, name.c_str(), forUpdate, + callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionInsertKey(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto name = jsiToValue(rt, options, "name"); + auto metadata = jsiToValue(rt, options, "metadata", true); + auto tags = jsiToValue(rt, options, "tags", true); + auto expiryMs = jsiToValue(rt, options, "expiryMs", true); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = + askar_session_insert_key(sessionHandle, localKeyHandle, name.c_str(), + metadata.length() ? metadata.c_str() : nullptr, + tags.length() ? tags.c_str() : nullptr, expiryMs, + callback, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionRemoveAll(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + auto category = jsiToValue(rt, options, "category"); + auto tagFilter = jsiToValue(rt, options, "tagFilter", true); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = + askar_session_remove_all(sessionHandle, category.c_str(), + tagFilter.length() ? tagFilter.c_str() : nullptr, + callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionRemoveKey(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + auto name = jsiToValue(rt, options, "name"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_session_remove_key(sessionHandle, name.c_str(), + callback, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionStart(jsi::Runtime &rt, jsi::Object options) { + auto storeHandle = jsiToValue(rt, options, "storeHandle"); + auto profile = jsiToValue(rt, options, "profile", true); + int8_t asTransaction = jsiToValue(rt, options, "asTransaction"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_session_start( + storeHandle, profile.length() ? profile.c_str() : nullptr, asTransaction, + callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionUpdate(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + int8_t operation = jsiToValue(rt, options, "operation"); + auto category = jsiToValue(rt, options, "category"); + auto name = jsiToValue(rt, options, "name"); + auto tags = jsiToValue(rt, options, "tags", true); + auto value = jsiToValue(rt, options, "value", true); + int64_t expiryMs = jsiToValue(rt, options, "expiryMs", true); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_session_update(sessionHandle, operation, + category.c_str(), name.c_str(), value, + tags.length() ? tags.c_str() : nullptr, + expiryMs, callback, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value sessionUpdateKey(jsi::Runtime &rt, jsi::Object options) { + auto sessionHandle = jsiToValue(rt, options, "sessionHandle"); + auto name = jsiToValue(rt, options, "name"); + auto tags = jsiToValue(rt, options, "tags", true); + auto metadata = jsiToValue(rt, options, "metadata", true); + auto expiryMs = jsiToValue(rt, options, "expiryMs", true); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = + askar_session_update_key(sessionHandle, name.c_str(), + metadata.length() ? metadata.c_str() : nullptr, + tags.length() ? tags.c_str() : nullptr, expiryMs, + callback, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +jsi::Value scanStart(jsi::Runtime &rt, jsi::Object options) { + auto storeHandle = jsiToValue(rt, options, "storeHandle"); + auto category = jsiToValue(rt, options, "category"); + + auto tagFilter = jsiToValue(rt, options, "tagFilter", true); + auto profile = jsiToValue(rt, options, "profile", true); + auto offset = jsiToValue(rt, options, "offset", true); + auto limit = jsiToValue(rt, options, "limit", true); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_scan_start( + storeHandle, profile.length() ? profile.c_str() : nullptr, + category.c_str(), tagFilter.length() ? tagFilter.c_str() : nullptr, + offset, limit, callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +}; + +jsi::Value scanNext(jsi::Runtime &rt, jsi::Object options) { + auto scanHandle = jsiToValue(rt, options, "scanHandle"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = + askar_scan_next(scanHandle, callbackWithResponse, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +}; + +jsi::Value scanFree(jsi::Runtime &rt, jsi::Object options) { + auto scanHandle = jsiToValue(rt, options, "scanHandle"); + + ErrorCode code = askar_scan_free(scanHandle); + + return createReturnValue(rt, code, nullptr); +}; + +jsi::Value keyFromJwk(jsi::Runtime &rt, jsi::Object options) { + auto jwk = jsiToValue(rt, options, "jwk"); + + LocalKeyHandle out; + + ErrorCode code = askar_key_from_jwk(jwk, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyFromKeyExchange(jsi::Runtime &rt, jsi::Object options) { + auto algorithm = jsiToValue(rt, options, "algorithm"); + auto skHandle = jsiToValue(rt, options, "skHandle"); + auto pkHandle = jsiToValue(rt, options, "pkHandle"); + + LocalKeyHandle out; + + ErrorCode code = + askar_key_from_key_exchange(algorithm.c_str(), skHandle, pkHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyFromPublicBytes(jsi::Runtime &rt, jsi::Object options) { + auto algorithm = jsiToValue(rt, options, "algorithm"); + auto publicKey = jsiToValue(rt, options, "publicKey"); + + LocalKeyHandle out; + + ErrorCode code = + askar_key_from_public_bytes(algorithm.c_str(), publicKey, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyFromSecretBytes(jsi::Runtime &rt, jsi::Object options) { + auto algorithm = jsiToValue(rt, options, "algorithm"); + auto secretKey = jsiToValue(rt, options, "secretKey"); + + LocalKeyHandle out; + + ErrorCode code = + askar_key_from_secret_bytes(algorithm.c_str(), secretKey, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyFromSeed(jsi::Runtime &rt, jsi::Object options) { + auto algorithm = jsiToValue(rt, options, "algorithm"); + auto seed = jsiToValue(rt, options, "seed"); + auto method = jsiToValue(rt, options, "method"); + + LocalKeyHandle out; + + ErrorCode code = + askar_key_from_seed(algorithm.c_str(), seed, method.c_str(), &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyGenerate(jsi::Runtime &rt, jsi::Object options) { + auto algorithm = jsiToValue(rt, options, "algorithm"); + auto ephemeral = jsiToValue(rt, options, "ephemeral"); + + LocalKeyHandle out; + + ErrorCode code = askar_key_generate(algorithm.c_str(), ephemeral, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyGetAlgorithm(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + + const char *out; + + ErrorCode code = askar_key_get_algorithm(localKeyHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyGetEphemeral(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + + int8_t out; + + ErrorCode code = askar_key_get_ephemeral(localKeyHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyGetJwkPublic(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto algorithm = jsiToValue(rt, options, "algorithm"); + + const char *out; + + ErrorCode code = + askar_key_get_jwk_public(localKeyHandle, algorithm.c_str(), &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyGetJwkSecret(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + + SecretBuffer out; + + ErrorCode code = askar_key_get_jwk_secret(localKeyHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyGetJwkThumbprint(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto algorithm = jsiToValue(rt, options, "algorithm"); + + const char *out; + + ErrorCode code = + askar_key_get_jwk_thumbprint(localKeyHandle, algorithm.c_str(), &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyGetPublicBytes(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + + SecretBuffer out; + + ErrorCode code = askar_key_get_public_bytes(localKeyHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyGetSecretBytes(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + + SecretBuffer out; + + ErrorCode code = askar_key_get_secret_bytes(localKeyHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keySignMessage(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto message = jsiToValue(rt, options, "message"); + auto sigType = jsiToValue(rt, options, "sigType", true); + + SecretBuffer out; + + ErrorCode code = askar_key_sign_message( + localKeyHandle, message, sigType.length() ? sigType.c_str() : nullptr, + &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyUnwrapKey(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto algorithm = jsiToValue(rt, options, "algorithm"); + auto ciphertext = jsiToValue(rt, options, "ciphertext"); + auto nonce = jsiToValue(rt, options, "nonce", true); + auto tag = jsiToValue(rt, options, "tag", true); + + LocalKeyHandle out; + + ErrorCode code = askar_key_unwrap_key(localKeyHandle, algorithm.c_str(), + ciphertext, nonce, tag, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyVerifySignature(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto message = jsiToValue(rt, options, "message"); + auto signature = jsiToValue(rt, options, "signature"); + auto sigType = jsiToValue(rt, options, "sigType", true); + + int8_t out; + + ErrorCode code = askar_key_verify_signature( + localKeyHandle, message, signature, + sigType.length() ? sigType.c_str() : nullptr, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyWrapKey(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto other = jsiToValue(rt, options, "other"); + auto nonce = jsiToValue(rt, options, "nonce", true); + + EncryptedBuffer out; + + ErrorCode code = askar_key_wrap_key(localKeyHandle, other, nonce, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyConvert(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto algorithm = jsiToValue(rt, options, "algorithm"); + + LocalKeyHandle out; + + ErrorCode code = askar_key_convert(localKeyHandle, algorithm.c_str(), &out); + + return createReturnValue(rt, code, &out); +}; + +jsi::Value keyFree(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + + askar_key_free(localKeyHandle); + + return createReturnValue(rt, ErrorCode::Success, nullptr); +}; + +jsi::Value keyCryptoBox(jsi::Runtime &rt, jsi::Object options) { + auto recipientKey = jsiToValue(rt, options, "recipientKey"); + auto senderKey = jsiToValue(rt, options, "senderKey"); + auto message = jsiToValue(rt, options, "message"); + auto nonce = jsiToValue(rt, options, "nonce"); + + SecretBuffer out; + + ErrorCode code = + askar_key_crypto_box(recipientKey, senderKey, message, nonce, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyCryptoBoxOpen(jsi::Runtime &rt, jsi::Object options) { + auto recipientKey = jsiToValue(rt, options, "recipientKey"); + auto senderKey = jsiToValue(rt, options, "senderKey"); + auto message = jsiToValue(rt, options, "message"); + auto nonce = jsiToValue(rt, options, "nonce"); + + SecretBuffer out; + + ErrorCode code = + askar_key_crypto_box_open(recipientKey, senderKey, message, nonce, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyCryptoBoxRandomNonce(jsi::Runtime &rt, jsi::Object options) { + SecretBuffer out; + + ErrorCode code = askar_key_crypto_box_random_nonce(&out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyCryptoBoxSeal(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto message = jsiToValue(rt, options, "message"); + + SecretBuffer out; + + ErrorCode code = askar_key_crypto_box_seal(localKeyHandle, message, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyCryptoBoxSealOpen(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto ciphertext = jsiToValue(rt, options, "ciphertext"); + + SecretBuffer out; + + ErrorCode code = + askar_key_crypto_box_seal_open(localKeyHandle, ciphertext, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyDeriveEcdh1pu(jsi::Runtime &rt, jsi::Object options) { + auto ephemeralKey = jsiToValue(rt, options, "ephemeralKey"); + auto senderKey = jsiToValue(rt, options, "senderKey"); + auto recipientKey = jsiToValue(rt, options, "recipientKey"); + auto algorithm = jsiToValue(rt, options, "algorithm"); + auto algId = jsiToValue(rt, options, "algId"); + auto apu = jsiToValue(rt, options, "apu"); + auto apv = jsiToValue(rt, options, "apv"); + auto ccTag = jsiToValue(rt, options, "ccTag", true); + auto receive = jsiToValue(rt, options, "receive"); + + LocalKeyHandle out; + + ErrorCode code = askar_key_derive_ecdh_1pu(algorithm.c_str(), ephemeralKey, + senderKey, recipientKey, algId, + apu, apv, ccTag, receive, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyDeriveEcdhEs(jsi::Runtime &rt, jsi::Object options) { + auto ephemeralKey = jsiToValue(rt, options, "ephemeralKey"); + auto recipientKey = jsiToValue(rt, options, "recipientKey"); + auto algorithm = jsiToValue(rt, options, "algorithm"); + auto algId = jsiToValue(rt, options, "algId"); + auto apu = jsiToValue(rt, options, "apu"); + auto apv = jsiToValue(rt, options, "apv"); + auto receive = jsiToValue(rt, options, "receive"); + + LocalKeyHandle out; + + ErrorCode code = + askar_key_derive_ecdh_es(algorithm.c_str(), ephemeralKey, recipientKey, + algId, apu, apv, receive, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyAeadDecrypt(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto ciphertext = jsiToValue(rt, options, "ciphertext"); + auto nonce = jsiToValue(rt, options, "nonce"); + auto tag = jsiToValue(rt, options, "tag", true); + auto aad = jsiToValue(rt, options, "aad", true); + + SecretBuffer out; + + ErrorCode code = + askar_key_aead_decrypt(localKeyHandle, ciphertext, nonce, tag, aad, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyAeadEncrypt(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto message = jsiToValue(rt, options, "message"); + auto nonce = jsiToValue(rt, options, "nonce", true); + auto aad = jsiToValue(rt, options, "aad", true); + + EncryptedBuffer out; + + ErrorCode code = + askar_key_aead_encrypt(localKeyHandle, message, nonce, aad, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyAeadGetPadding(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + auto messageLength = jsiToValue(rt, options, "msgLen"); + + int32_t out; + + ErrorCode code = + askar_key_aead_get_padding(localKeyHandle, messageLength, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyAeadGetParams(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + + AeadParams out; + + ErrorCode code = askar_key_aead_get_params(localKeyHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyAeadRandomNonce(jsi::Runtime &rt, jsi::Object options) { + auto localKeyHandle = + jsiToValue(rt, options, "localKeyHandle"); + + SecretBuffer out; + + ErrorCode code = askar_key_aead_random_nonce(localKeyHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyEntryListCount(jsi::Runtime &rt, jsi::Object options) { + auto keyEntryListHandle = + jsiToValue(rt, options, "keyEntryListHandle"); + + int32_t out; + + ErrorCode code = askar_key_entry_list_count(keyEntryListHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyEntryListFree(jsi::Runtime &rt, jsi::Object options) { + auto keyEntryListHandle = + jsiToValue(rt, options, "keyEntryListHandle"); + + int32_t out; + + ErrorCode code = askar_key_entry_list_count(keyEntryListHandle, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyEntryListGetAlgorithm(jsi::Runtime &rt, jsi::Object options) { + auto keyEntryListHandle = + jsiToValue(rt, options, "keyEntryListHandle"); + auto index = jsiToValue(rt, options, "index"); + + const char *out; + + ErrorCode code = + askar_key_entry_list_get_algorithm(keyEntryListHandle, index, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyEntryListGetMetadata(jsi::Runtime &rt, jsi::Object options) { + auto keyEntryListHandle = + jsiToValue(rt, options, "keyEntryListHandle"); + auto index = jsiToValue(rt, options, "index"); + + const char *out; + + ErrorCode code = + askar_key_entry_list_get_metadata(keyEntryListHandle, index, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyEntryListGetName(jsi::Runtime &rt, jsi::Object options) { + auto keyEntryListHandle = + jsiToValue(rt, options, "keyEntryListHandle"); + auto index = jsiToValue(rt, options, "index"); + + const char *out; + + ErrorCode code = + askar_key_entry_list_get_name(keyEntryListHandle, index, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyEntryListGetTags(jsi::Runtime &rt, jsi::Object options) { + auto keyEntryListHandle = + jsiToValue(rt, options, "keyEntryListHandle"); + auto index = jsiToValue(rt, options, "index"); + + const char *out; + + ErrorCode code = + askar_key_entry_list_get_tags(keyEntryListHandle, index, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value keyEntryListLoadLocal(jsi::Runtime &rt, jsi::Object options) { + auto keyEntryListHandle = + jsiToValue(rt, options, "keyEntryListHandle"); + auto index = jsiToValue(rt, options, "index"); + + LocalKeyHandle out; + + ErrorCode code = + askar_key_entry_list_load_local(keyEntryListHandle, index, &out); + + return createReturnValue(rt, code, &out); +} + +jsi::Value migrateIndySdk(jsi::Runtime &rt, jsi::Object options) { + auto specUri = jsiToValue(rt, options, "specUri"); + auto walletName = jsiToValue(rt, options, "walletName"); + auto walletKey = jsiToValue(rt, options, "walletKey"); + auto kdfLevel = jsiToValue(rt, options, "kdfLevel"); + + jsi::Function cb = options.getPropertyAsFunction(rt, "cb"); + State *state = new State(&cb); + state->rt = &rt; + + ErrorCode code = askar_migrate_indy_sdk(specUri.c_str(), walletName.c_str(), + walletKey.c_str(), kdfLevel.c_str(), + callback, CallbackId(state)); + + return createReturnValue(rt, code, nullptr); +} + +} // namespace ariesAskar diff --git a/wrappers/javascript/aries-askar-react-native/cpp/ariesAskar.h b/wrappers/javascript/aries-askar-react-native/cpp/ariesAskar.h new file mode 100644 index 00000000..879c72f6 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/cpp/ariesAskar.h @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include +#include + +using namespace facebook; + +namespace ariesAskar { + +jsi::Value version(jsi::Runtime &rt, jsi::Object options); +jsi::Value getCurrentError(jsi::Runtime &rt, jsi::Object options); +jsi::Value setDefaultLogger(jsi::Runtime &rt, jsi::Object options); + +// TODO: not implemented yet +// jsi::Value setCustomLogger(jsi::Runtime &rt, jsi::Object options); +// jsi::Value setMaxLogLevel(jsi::Runtime &rt, jsi::Object options); +// jsi::Value clearCustomLogger(jsi::Runtime &rt, jsi::Object options); + +jsi::Value entryListCount(jsi::Runtime &rt, jsi::Object options); +jsi::Value entryListFree(jsi::Runtime &rt, jsi::Object options); +jsi::Value entryListGetCategory(jsi::Runtime &rt, jsi::Object options); +jsi::Value entryListGetName(jsi::Runtime &rt, jsi::Object options); +jsi::Value entryListGetTags(jsi::Runtime &rt, jsi::Object options); +jsi::Value entryListGetValue(jsi::Runtime &rt, jsi::Object options); + +jsi::Value keyAeadDecrypt(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyAeadEncrypt(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyAeadGetPadding(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyAeadGetParams(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyAeadRandomNonce(jsi::Runtime &rt, jsi::Object options); + +jsi::Value keyConvert(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyFree(jsi::Runtime &rt, jsi::Object options); + +jsi::Value keyCryptoBox(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyCryptoBoxOpen(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyCryptoBoxRandomNonce(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyCryptoBoxSeal(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyCryptoBoxSealOpen(jsi::Runtime &rt, jsi::Object options); + +jsi::Value keyDeriveEcdh1pu(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyDeriveEcdhEs(jsi::Runtime &rt, jsi::Object options); + +jsi::Value keyEntryListCount(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyEntryListFree(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyEntryListGetAlgorithm(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyEntryListGetMetadata(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyEntryListGetName(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyEntryListGetTags(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyEntryListLoadLocal(jsi::Runtime &rt, jsi::Object options); + +jsi::Value keyFromJwk(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyFromKeyExchange(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyFromPublicBytes(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyFromSecretBytes(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyFromSeed(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyGenerate(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyGetAlgorithm(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyGetEphemeral(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyGetJwkPublic(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyGetJwkSecret(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyGetJwkThumbprint(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyGetPublicBytes(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyGetSecretBytes(jsi::Runtime &rt, jsi::Object options); +jsi::Value keySignMessage(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyUnwrapKey(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyVerifySignature(jsi::Runtime &rt, jsi::Object options); +jsi::Value keyWrapKey(jsi::Runtime &rt, jsi::Object options); + +jsi::Value scanFree(jsi::Runtime &rt, jsi::Object options); +jsi::Value scanNext(jsi::Runtime &rt, jsi::Object options); +jsi::Value scanStart(jsi::Runtime &rt, jsi::Object options); + +jsi::Value sessionClose(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionCount(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionFetch(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionFetchAll(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionFetchAllKeys(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionFetchKey(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionInsertKey(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionRemoveAll(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionRemoveKey(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionStart(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionUpdate(jsi::Runtime &rt, jsi::Object options); +jsi::Value sessionUpdateKey(jsi::Runtime &rt, jsi::Object options); + +jsi::Value storeOpen(jsi::Runtime &rt, jsi::Object options); +jsi::Value storeClose(jsi::Runtime &rt, jsi::Object options); +jsi::Value storeCreateProfile(jsi::Runtime &rt, jsi::Object options); +jsi::Value storeGenerateRawKey(jsi::Runtime &rt, jsi::Object options); +jsi::Value storeGetProfileName(jsi::Runtime &rt, jsi::Object options); +jsi::Value storeProvision(jsi::Runtime &rt, jsi::Object options); +jsi::Value storeRekey(jsi::Runtime &rt, jsi::Object options); +jsi::Value storeRemove(jsi::Runtime &rt, jsi::Object options); +jsi::Value storeRemoveProfile(jsi::Runtime &rt, jsi::Object options); + +jsi::Value migrateIndySdk(jsi::Runtime &rt, jsi::Object options); + +} // namespace ariesAskar diff --git a/wrappers/javascript/aries-askar-react-native/cpp/include/libaries_askar.h b/wrappers/javascript/aries-askar-react-native/cpp/include/libaries_askar.h new file mode 100644 index 00000000..3dae491f --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/cpp/include/libaries_askar.h @@ -0,0 +1,574 @@ +#pragma once + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include +#include + +typedef size_t ScanHandle; +typedef size_t StoreHandle; +typedef size_t SessionHandle; + + +enum ErrorCode +#ifdef __cplusplus + : int64_t +#endif // __cplusplus + { + Success = 0, + Backend = 1, + Busy = 2, + Duplicate = 3, + Encryption = 4, + Input = 5, + NotFound = 6, + Unexpected = 7, + Unsupported = 8, + Custom = 100, +}; +#ifndef __cplusplus +typedef int64_t ErrorCode; +#endif // __cplusplus + +typedef struct FfiResultList_Entry FfiResultList_Entry; + +typedef struct FfiResultList_KeyEntry FfiResultList_KeyEntry; + +/** + * A stored key entry + */ +typedef struct LocalKey LocalKey; + +typedef struct Option_EnabledCallback Option_EnabledCallback; + +typedef struct Option_FlushCallback Option_FlushCallback; + +typedef struct SecretBuffer { + int64_t len; + uint8_t *data; +} SecretBuffer; + +typedef struct FfiResultList_Entry FfiEntryList; + +typedef struct ArcHandle_FfiEntryList { + const FfiEntryList *_0; +} ArcHandle_FfiEntryList; + +typedef struct ArcHandle_FfiEntryList EntryListHandle; + +typedef struct ArcHandle_LocalKey { + const struct LocalKey *_0; +} ArcHandle_LocalKey; + +typedef struct ArcHandle_LocalKey LocalKeyHandle; + +/** + * ByteBuffer is a struct that represents an array of bytes to be sent over the FFI boundaries. + * There are several cases when you might want to use this, but the primary one for us + * is for returning protobuf-encoded data to Swift and Java. The type is currently rather + * limited (implementing almost no functionality), however in the future it may be + * more expanded. + * + * ## Caveats + * + * Note that the order of the fields is `len` (an i64) then `data` (a `*mut u8`), getting + * this wrong on the other side of the FFI will cause memory corruption and crashes. + * `i64` is used for the length instead of `u64` and `usize` because JNA has interop + * issues with both these types. + * + * ### `Drop` is not implemented + * + * ByteBuffer does not implement Drop. This is intentional. Memory passed into it will + * be leaked if it is not explicitly destroyed by calling [`ByteBuffer::destroy`], or + * [`ByteBuffer::destroy_into_vec`]. This is for two reasons: + * + * 1. In the future, we may allow it to be used for data that is not managed by + * the Rust allocator\*, and `ByteBuffer` assuming it's okay to automatically + * deallocate this data with the Rust allocator. + * + * 2. Automatically running destructors in unsafe code is a + * [frequent footgun](https://without.boats/blog/two-memory-bugs-from-ringbahn/) + * (among many similar issues across many crates). + * + * Note that calling `destroy` manually is often not needed, as usually you should + * be passing these to the function defined by [`define_bytebuffer_destructor!`] from + * the other side of the FFI. + * + * Because this type is essentially *only* useful in unsafe or FFI code (and because + * the most common usage pattern does not require manually managing the memory), it + * does not implement `Drop`. + * + * \* Note: in the case of multiple Rust shared libraries loaded at the same time, + * there may be multiple instances of "the Rust allocator" (one per shared library), + * in which case we're referring to whichever instance is active for the code using + * the `ByteBuffer`. Note that this doesn't occur on all platforms or build + * configurations, but treating allocators in different shared libraries as fully + * independent is always safe. + * + * ## Layout/fields + * + * This struct's field are not `pub` (mostly so that we can soundly implement `Send`, but also so + * that we can verify rust users are constructing them appropriately), the fields, their types, and + * their order are *very much* a part of the public API of this type. Consumers on the other side + * of the FFI will need to know its layout. + * + * If this were a C struct, it would look like + * + * ```c,no_run + * struct ByteBuffer { + * // Note: This should never be negative, but values above + * // INT64_MAX / i64::MAX are not allowed. + * int64_t len; + * // Note: nullable! + * uint8_t *data; + * }; + * ``` + * + * In rust, there are two fields, in this order: `len: i64`, and `data: *mut u8`. + * + * For clarity, the fact that the data pointer is nullable means that `Option` is not + * the same size as ByteBuffer, and additionally is not FFI-safe (the latter point is not + * currently guaranteed anyway as of the time of writing this comment). + * + * ### Description of fields + * + * `data` is a pointer to an array of `len` bytes. Note that data can be a null pointer and therefore + * should be checked. + * + * The bytes array is allocated on the heap and must be freed on it as well. Critically, if there + * are multiple rust shared libraries using being used in the same application, it *must be freed + * on the same heap that allocated it*, or you will corrupt both heaps. + * + * Typically, this object is managed on the other side of the FFI (on the "FFI consumer"), which + * means you must expose a function to release the resources of `data` which can be done easily + * using the [`define_bytebuffer_destructor!`] macro provided by this crate. + */ +typedef struct ByteBuffer { + int64_t len; + uint8_t *data; +} ByteBuffer; + +typedef struct EncryptedBuffer { + struct SecretBuffer buffer; + int64_t tag_pos; + int64_t nonce_pos; +} EncryptedBuffer; + +typedef struct AeadParams { + int32_t nonce_length; + int32_t tag_length; +} AeadParams; + +/** + * `FfiStr<'a>` is a safe (`#[repr(transparent)]`) wrapper around a + * nul-terminated `*const c_char` (e.g. a C string). Conceptually, it is + * similar to [`std::ffi::CStr`], except that it may be used in the signatures + * of extern "C" functions. + * + * Functions accepting strings should use this instead of accepting a C string + * directly. This allows us to write those functions using safe code without + * allowing safe Rust to cause memory unsafety. + * + * A single function for constructing these from Rust ([`FfiStr::from_raw`]) + * has been provided. Most of the time, this should not be necessary, and users + * should accept `FfiStr` in the parameter list directly. + * + * ## Caveats + * + * An effort has been made to make this struct hard to misuse, however it is + * still possible, if the `'static` lifetime is manually specified in the + * struct. E.g. + * + * ```rust,no_run + * # use ffi_support::FfiStr; + * // NEVER DO THIS + * #[no_mangle] + * extern "C" fn never_do_this(s: FfiStr<'static>) { + * // save `s` somewhere, and access it after this + * // function returns. + * } + * ``` + * + * Instead, one of the following patterns should be used: + * + * ``` + * # use ffi_support::FfiStr; + * #[no_mangle] + * extern "C" fn valid_use_1(s: FfiStr<'_>) { + * // Use of `s` after this function returns is impossible + * } + * // Alternative: + * #[no_mangle] + * extern "C" fn valid_use_2(s: FfiStr) { + * // Use of `s` after this function returns is impossible + * } + * ``` + */ +typedef const char *FfiStr; + +typedef struct FfiResultList_KeyEntry FfiKeyEntryList; + +typedef struct ArcHandle_FfiKeyEntryList { + const FfiKeyEntryList *_0; +} ArcHandle_FfiKeyEntryList; + +typedef struct ArcHandle_FfiKeyEntryList KeyEntryListHandle; + +typedef int64_t CallbackId; + +typedef void (*LogCallback)(const void *context, int32_t level, const char *target, const char *message, const char *module_path, const char *file, int32_t line); + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void askar_buffer_free(struct SecretBuffer buffer); + +void askar_clear_custom_logger(void); + +ErrorCode askar_entry_list_count(EntryListHandle handle, int32_t *count); + +void askar_entry_list_free(EntryListHandle handle); + +ErrorCode askar_entry_list_get_category(EntryListHandle handle, + int32_t index, + const char **category); + +ErrorCode askar_entry_list_get_name(EntryListHandle handle, int32_t index, const char **name); + +ErrorCode askar_entry_list_get_tags(EntryListHandle handle, int32_t index, const char **tags); + +ErrorCode askar_entry_list_get_value(EntryListHandle handle, + int32_t index, + struct SecretBuffer *value); + +ErrorCode askar_get_current_error(const char **error_json_p); + +ErrorCode askar_key_aead_decrypt(LocalKeyHandle handle, + struct ByteBuffer ciphertext, + struct ByteBuffer nonce, + struct ByteBuffer tag, + struct ByteBuffer aad, + struct SecretBuffer *out); + +ErrorCode askar_key_aead_encrypt(LocalKeyHandle handle, + struct ByteBuffer message, + struct ByteBuffer nonce, + struct ByteBuffer aad, + struct EncryptedBuffer *out); + +ErrorCode askar_key_aead_get_padding(LocalKeyHandle handle, int64_t msg_len, int32_t *out); + +ErrorCode askar_key_aead_get_params(LocalKeyHandle handle, struct AeadParams *out); + +ErrorCode askar_key_aead_random_nonce(LocalKeyHandle handle, struct SecretBuffer *out); + +ErrorCode askar_key_convert(LocalKeyHandle handle, FfiStr alg, LocalKeyHandle *out); + +ErrorCode askar_key_crypto_box(LocalKeyHandle recip_key, + LocalKeyHandle sender_key, + struct ByteBuffer message, + struct ByteBuffer nonce, + struct SecretBuffer *out); + +ErrorCode askar_key_crypto_box_open(LocalKeyHandle recip_key, + LocalKeyHandle sender_key, + struct ByteBuffer message, + struct ByteBuffer nonce, + struct SecretBuffer *out); + +ErrorCode askar_key_crypto_box_random_nonce(struct SecretBuffer *out); + +ErrorCode askar_key_crypto_box_seal(LocalKeyHandle handle, + struct ByteBuffer message, + struct SecretBuffer *out); + +ErrorCode askar_key_crypto_box_seal_open(LocalKeyHandle handle, + struct ByteBuffer ciphertext, + struct SecretBuffer *out); + +ErrorCode askar_key_derive_ecdh_1pu(FfiStr alg, + LocalKeyHandle ephem_key, + LocalKeyHandle sender_key, + LocalKeyHandle recip_key, + struct ByteBuffer alg_id, + struct ByteBuffer apu, + struct ByteBuffer apv, + struct ByteBuffer cc_tag, + int8_t receive, + LocalKeyHandle *out); + +ErrorCode askar_key_derive_ecdh_es(FfiStr alg, + LocalKeyHandle ephem_key, + LocalKeyHandle recip_key, + struct ByteBuffer alg_id, + struct ByteBuffer apu, + struct ByteBuffer apv, + int8_t receive, + LocalKeyHandle *out); + +ErrorCode askar_key_entry_list_count(KeyEntryListHandle handle, int32_t *count); + +void askar_key_entry_list_free(KeyEntryListHandle handle); + +ErrorCode askar_key_entry_list_get_algorithm(KeyEntryListHandle handle, + int32_t index, + const char **alg); + +ErrorCode askar_key_entry_list_get_metadata(KeyEntryListHandle handle, + int32_t index, + const char **metadata); + +ErrorCode askar_key_entry_list_get_name(KeyEntryListHandle handle, + int32_t index, + const char **name); + +ErrorCode askar_key_entry_list_get_tags(KeyEntryListHandle handle, + int32_t index, + const char **tags); + +ErrorCode askar_key_entry_list_load_local(KeyEntryListHandle handle, + int32_t index, + LocalKeyHandle *out); + +void askar_key_free(LocalKeyHandle handle); + +ErrorCode askar_key_from_jwk(struct ByteBuffer jwk, LocalKeyHandle *out); + +ErrorCode askar_key_from_key_exchange(FfiStr alg, + LocalKeyHandle sk_handle, + LocalKeyHandle pk_handle, + LocalKeyHandle *out); + +ErrorCode askar_key_from_public_bytes(FfiStr alg, struct ByteBuffer public_, LocalKeyHandle *out); + +ErrorCode askar_key_from_secret_bytes(FfiStr alg, struct ByteBuffer secret, LocalKeyHandle *out); + +ErrorCode askar_key_from_seed(FfiStr alg, + struct ByteBuffer seed, + FfiStr method, + LocalKeyHandle *out); + +ErrorCode askar_key_generate(FfiStr alg, int8_t ephemeral, LocalKeyHandle *out); + +ErrorCode askar_key_get_algorithm(LocalKeyHandle handle, const char **out); + +ErrorCode askar_key_get_ephemeral(LocalKeyHandle handle, int8_t *out); + +ErrorCode askar_key_get_jwk_public(LocalKeyHandle handle, FfiStr alg, const char **out); + +ErrorCode askar_key_get_jwk_secret(LocalKeyHandle handle, struct SecretBuffer *out); + +ErrorCode askar_key_get_jwk_thumbprint(LocalKeyHandle handle, FfiStr alg, const char **out); + +ErrorCode askar_key_get_public_bytes(LocalKeyHandle handle, struct SecretBuffer *out); + +ErrorCode askar_key_get_secret_bytes(LocalKeyHandle handle, struct SecretBuffer *out); + +ErrorCode askar_key_sign_message(LocalKeyHandle handle, + struct ByteBuffer message, + FfiStr sig_type, + struct SecretBuffer *out); + +ErrorCode askar_key_unwrap_key(LocalKeyHandle handle, + FfiStr alg, + struct ByteBuffer ciphertext, + struct ByteBuffer nonce, + struct ByteBuffer tag, + LocalKeyHandle *out); + +ErrorCode askar_key_verify_signature(LocalKeyHandle handle, + struct ByteBuffer message, + struct ByteBuffer signature, + FfiStr sig_type, + int8_t *out); + +ErrorCode askar_key_wrap_key(LocalKeyHandle handle, + LocalKeyHandle other, + struct ByteBuffer nonce, + struct EncryptedBuffer *out); + +/** + * Migrate an sqlite wallet from an indy-sdk structure to an aries-askar structure. + * It is important to note that this does not do any post-processing. If the record values, tags, + * names, etc. have changed, it must be processed manually afterwards. This script does the following: + * + * 1. Create and rename the required tables + * 2. Fetch the indy key from the wallet + * 3. Create a new configuration + * 4. Initialize a profile + * 5. Update the items from the indy-sdk + * 6. Clean up (drop tables and add a version of "1") + */ +ErrorCode askar_migrate_indy_sdk(FfiStr spec_uri, + FfiStr wallet_name, + FfiStr wallet_key, + FfiStr kdf_level, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_scan_free(ScanHandle handle); + +ErrorCode askar_scan_next(ScanHandle handle, + void (*cb)(CallbackId cb_id, ErrorCode err, EntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_scan_start(StoreHandle handle, + FfiStr profile, + FfiStr category, + FfiStr tag_filter, + int64_t offset, + int64_t limit, + void (*cb)(CallbackId cb_id, ErrorCode err, ScanHandle handle), + CallbackId cb_id); + +ErrorCode askar_session_close(SessionHandle handle, + int8_t commit, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_session_count(SessionHandle handle, + FfiStr category, + FfiStr tag_filter, + void (*cb)(CallbackId cb_id, ErrorCode err, int64_t count), + CallbackId cb_id); + +ErrorCode askar_session_fetch(SessionHandle handle, + FfiStr category, + FfiStr name, + int8_t for_update, + void (*cb)(CallbackId cb_id, ErrorCode err, EntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_session_fetch_all(SessionHandle handle, + FfiStr category, + FfiStr tag_filter, + int64_t limit, + int8_t for_update, + void (*cb)(CallbackId cb_id, ErrorCode err, EntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_session_fetch_all_keys(SessionHandle handle, + FfiStr alg, + FfiStr thumbprint, + FfiStr tag_filter, + int64_t limit, + int8_t for_update, + void (*cb)(CallbackId cb_id, ErrorCode err, KeyEntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_session_fetch_key(SessionHandle handle, + FfiStr name, + int8_t for_update, + void (*cb)(CallbackId cb_id, ErrorCode err, KeyEntryListHandle results), + CallbackId cb_id); + +ErrorCode askar_session_insert_key(SessionHandle handle, + LocalKeyHandle key_handle, + FfiStr name, + FfiStr metadata, + FfiStr tags, + int64_t expiry_ms, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_session_remove_all(SessionHandle handle, + FfiStr category, + FfiStr tag_filter, + void (*cb)(CallbackId cb_id, ErrorCode err, int64_t removed), + CallbackId cb_id); + +ErrorCode askar_session_remove_key(SessionHandle handle, + FfiStr name, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_session_start(StoreHandle handle, + FfiStr profile, + int8_t as_transaction, + void (*cb)(CallbackId cb_id, ErrorCode err, SessionHandle handle), + CallbackId cb_id); + +ErrorCode askar_session_update(SessionHandle handle, + int8_t operation, + FfiStr category, + FfiStr name, + struct ByteBuffer value, + FfiStr tags, + int64_t expiry_ms, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_session_update_key(SessionHandle handle, + FfiStr name, + FfiStr metadata, + FfiStr tags, + int64_t expiry_ms, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_set_custom_logger(const void *context, + LogCallback log, + struct Option_EnabledCallback enabled, + struct Option_FlushCallback flush, + int32_t max_level); + +ErrorCode askar_set_default_logger(void); + +ErrorCode askar_set_max_log_level(int32_t max_level); + +ErrorCode askar_store_close(StoreHandle handle, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_store_create_profile(StoreHandle handle, + FfiStr profile, + void (*cb)(CallbackId cb_id, ErrorCode err, const char *result_p), + CallbackId cb_id); + +ErrorCode askar_store_generate_raw_key(struct ByteBuffer seed, const char **out); + +ErrorCode askar_store_get_profile_name(StoreHandle handle, + void (*cb)(CallbackId cb_id, ErrorCode err, const char *name), + CallbackId cb_id); + +ErrorCode askar_store_open(FfiStr spec_uri, + FfiStr key_method, + FfiStr pass_key, + FfiStr profile, + void (*cb)(CallbackId cb_id, ErrorCode err, StoreHandle handle), + CallbackId cb_id); + +ErrorCode askar_store_provision(FfiStr spec_uri, + FfiStr key_method, + FfiStr pass_key, + FfiStr profile, + int8_t recreate, + void (*cb)(CallbackId cb_id, ErrorCode err, StoreHandle handle), + CallbackId cb_id); + +ErrorCode askar_store_rekey(StoreHandle handle, + FfiStr key_method, + FfiStr pass_key, + void (*cb)(CallbackId cb_id, ErrorCode err), + CallbackId cb_id); + +ErrorCode askar_store_remove(FfiStr spec_uri, + void (*cb)(CallbackId cb_id, ErrorCode err, int8_t), + CallbackId cb_id); + +ErrorCode askar_store_remove_profile(StoreHandle handle, + FfiStr profile, + void (*cb)(CallbackId cb_id, ErrorCode err, int8_t removed), + CallbackId cb_id); + +void askar_terminate(void); + +char *askar_version(void); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/wrappers/javascript/aries-askar-react-native/cpp/turboModuleUtility.cpp b/wrappers/javascript/aries-askar-react-native/cpp/turboModuleUtility.cpp new file mode 100644 index 00000000..7968168d --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/cpp/turboModuleUtility.cpp @@ -0,0 +1,564 @@ +#include + +#include + +namespace ariesAskarTurboModuleUtility { + +using byteVector = std::vector; + +std::shared_ptr invoker; + +void registerTurboModule(jsi::Runtime &rt, + std::shared_ptr jsCallInvoker) { + // Setting the callInvoker for async code + invoker = jsCallInvoker; + // Create a TurboModuleRustHostObject + auto instance = std::make_shared(rt); + // Create a JS equivalent object of the instance + jsi::Object jsInstance = jsi::Object::createFromHostObject(rt, instance); + // Register the object on global + rt.global().setProperty(rt, "_aries_askar", std::move(jsInstance)); +} + +void assertValueIsObject(jsi::Runtime &rt, const jsi::Value *val) { + val->asObject(rt); +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + const char **value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + auto isNullptr = value == nullptr || *value == nullptr; + auto valueWithoutNullptr = isNullptr + ? jsi::Value::null() + : jsi::String::createFromAscii(rt, *value); + object.setProperty(rt, "value", valueWithoutNullptr); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + const char *const *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + auto isNullptr = value == nullptr || *value == nullptr; + auto valueWithoutNullptr = isNullptr + ? jsi::Value::null() + : jsi::String::createFromAscii(rt, *value); + object.setProperty(rt, "value", valueWithoutNullptr); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + nullptr_t value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + object.setProperty(rt, "value", jsi::Value::null()); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, int8_t *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + auto valueWithoutNullptr = + value == nullptr ? jsi::Value::null() : jsi::Value(rt, int(*value)); + object.setProperty(rt, "value", valueWithoutNullptr); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + int8_t const *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + auto valueWithoutNullptr = + value == nullptr ? jsi::Value::null() : jsi::Value(rt, int(*value)); + object.setProperty(rt, "value", valueWithoutNullptr); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, int32_t *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + auto valueWithoutNullptr = + value == nullptr ? jsi::Value::null() : jsi::Value(rt, int(*value)); + object.setProperty(rt, "value", valueWithoutNullptr); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, int64_t *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + auto valueWithoutNullptr = + value == nullptr ? jsi::Value::null() : jsi::Value(rt, int(*value)); + object.setProperty(rt, "value", valueWithoutNullptr); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + int64_t const *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + auto valueWithoutNullptr = + value == nullptr ? jsi::Value::null() : jsi::Value(rt, int(*value)); + object.setProperty(rt, "value", valueWithoutNullptr); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + size_t const *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + auto valueWithoutNullptr = + value == nullptr ? jsi::Value::null() : jsi::Value(rt, int(*value)); + object.setProperty(rt, "value", valueWithoutNullptr); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + std::string value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + object.setProperty(rt, "value", jsi::String::createFromAscii(rt, value)); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + SecretBuffer *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + auto valueWithoutNullptr = value == nullptr + ? jsi::Value::null() + : secretBufferToArrayBuffer(rt, *value); + object.setProperty(rt, "value", valueWithoutNullptr); + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + LocalKeyHandle *value) { + auto isNullptr = value == nullptr || value->_0 == nullptr; + return isNullptr + ? createReturnValue(rt, code, nullptr) + : createReturnValue(rt, code, std::to_string(int64_t(intptr_t(value->_0)))); +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + EntryListHandle const *value) { + auto isNullptr = value == nullptr || value->_0 == nullptr; + return isNullptr + ? createReturnValue(rt, code, nullptr) + : createReturnValue(rt, code, std::to_string(int64_t(intptr_t(value->_0)))); +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + KeyEntryListHandle const *value) { + auto isNullptr = value == nullptr || value->_0 == nullptr; + return isNullptr + ? createReturnValue(rt, code, nullptr) + : createReturnValue(rt, code, std::to_string(int64_t(intptr_t(value->_0)))); +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + EncryptedBuffer *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + if (value == nullptr) { + object.setProperty(rt, "value", jsi::Value::null()); + } else { + auto valueObject = jsi::Object(rt); + + valueObject.setProperty(rt, "buffer", + secretBufferToArrayBuffer(rt, value->buffer)); + valueObject.setProperty(rt, "tagPos", int(value->tag_pos)); + valueObject.setProperty(rt, "noncePos", int(value->nonce_pos)); + + object.setProperty(rt, "value", valueObject); + } + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +template <> +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, + AeadParams *value) { + auto object = jsi::Object(rt); + + if (code == ErrorCode::Success) { + if (value == nullptr) { + object.setProperty(rt, "value", jsi::Value::null()); + } else { + auto valueObject = jsi::Object(rt); + + valueObject.setProperty(rt, "nonceLength", int(value->nonce_length)); + valueObject.setProperty(rt, "tagLength", int(value->tag_length)); + + object.setProperty(rt, "value", object); + } + } + + object.setProperty(rt, "errorCode", int(code)); + + return object; +} + +void callback(CallbackId result, ErrorCode code) { + invoker->invokeAsync([result, code]() { + State *_state = reinterpret_cast(result); + State *state = static_cast(_state); + jsi::Function *cb = &state->cb; + jsi::Runtime *rt = reinterpret_cast(state->rt); + + auto object = jsi::Object(*rt); + object.setProperty(*rt, "errorCode", int(code)); + cb->call(*rt, object); + }); + // delete state; +} + +// Session, Store and Scan Handle +template <> +void callbackWithResponse(CallbackId result, ErrorCode code, size_t response) { + invoker->invokeAsync([result, code, response]() { + State *_state = reinterpret_cast(result); + State *state = static_cast(_state); + jsi::Function *cb = &state->cb; + jsi::Runtime *rt = reinterpret_cast(state->rt); + + auto out = createReturnValue(*rt, code, &response); + cb->call(*rt, out); + }); +} + +template <> +void callbackWithResponse(CallbackId result, ErrorCode code, + const char *response) { + invoker->invokeAsync([result, code, response]() { + State *_state = reinterpret_cast(result); + State *state = static_cast(_state); + jsi::Function *cb = &state->cb; + jsi::Runtime *rt = reinterpret_cast(state->rt); + + auto out = createReturnValue(*rt, code, &response); + cb->call(*rt, out); + }); +} + +template <> +void callbackWithResponse(CallbackId result, ErrorCode code, + EntryListHandle response) { + invoker->invokeAsync([result, code, response]() { + State *_state = reinterpret_cast(result); + State *state = static_cast(_state); + jsi::Function *cb = &state->cb; + jsi::Runtime *rt = reinterpret_cast(state->rt); + + auto out = createReturnValue(*rt, code, &response); + cb->call(*rt, out); + }); +} + +template <> +void callbackWithResponse(CallbackId result, ErrorCode code, + KeyEntryListHandle response) { + invoker->invokeAsync([result, code, response]() { + State *_state = reinterpret_cast(result); + State *state = static_cast(_state); + jsi::Function *cb = &state->cb; + jsi::Runtime *rt = reinterpret_cast(state->rt); + + auto out = createReturnValue(*rt, code, &response); + cb->call(*rt, out); + }); +} + +template <> +void callbackWithResponse(CallbackId result, ErrorCode code, int8_t response) { + invoker->invokeAsync([result, code, response]() { + State *_state = reinterpret_cast(result); + State *state = static_cast(_state); + jsi::Function *cb = &state->cb; + jsi::Runtime *rt = reinterpret_cast(state->rt); + + auto out = createReturnValue(*rt, code, &response); + cb->call(*rt, out); + }); +} + +template <> +void callbackWithResponse(CallbackId result, ErrorCode code, int64_t response) { + invoker->invokeAsync([result, code, response]() { + State *_state = reinterpret_cast(result); + State *state = static_cast(_state); + jsi::Function *cb = &state->cb; + jsi::Runtime *rt = reinterpret_cast(state->rt); + + auto out = createReturnValue(*rt, code, &response); + cb->call(*rt, out); + }); +} + +template <> +uint8_t jsiToValue(jsi::Runtime &rt, jsi::Object &options, const char *name, + bool optional) { + jsi::Value value = options.getProperty(rt, name); + if ((value.isNull() || value.isUndefined()) && optional) + return 0; + + if (value.isNumber()) + return value.asNumber(); + + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "number"); +}; + +template <> +int8_t jsiToValue(jsi::Runtime &rt, jsi::Object &options, const char *name, + bool optional) { + jsi::Value value = options.getProperty(rt, name); + if ((value.isNull() || value.isUndefined()) && optional) + return 0; + + if (value.isNumber()) + return value.asNumber(); + + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "number"); +}; + +template <> +std::string jsiToValue(jsi::Runtime &rt, jsi::Object &options, + const char *name, bool optional) { + jsi::Value value = options.getProperty(rt, name); + + if ((value.isNull() || value.isUndefined()) && optional) + return std::string(); + + if (value.isString()) { + auto x = value.asString(rt).utf8(rt); + return x; + } + + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "string"); +} + +template <> +int64_t jsiToValue(jsi::Runtime &rt, jsi::Object &options, const char *name, + bool optional) { + jsi::Value value = options.getProperty(rt, name); + if ((value.isNull() || value.isUndefined()) && optional) + return 0; + + if (value.isNumber()) + return value.asNumber(); + + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "number"); +}; + +template <> +uint64_t jsiToValue(jsi::Runtime &rt, jsi::Object &options, const char *name, + bool optional) { + jsi::Value value = options.getProperty(rt, name); + if ((value.isNull() || value.isUndefined()) && optional) + return 0; + + if (value.isNumber()) + return value.asNumber(); + + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "number"); +}; + +template <> +int32_t jsiToValue(jsi::Runtime &rt, jsi::Object &options, const char *name, + bool optional) { + jsi::Value value = options.getProperty(rt, name); + if ((value.isNull() || value.isUndefined()) && optional) + return 0; + + if (value.isNumber()) + return value.asNumber(); + + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "number"); +}; + +template <> +uint32_t jsiToValue(jsi::Runtime &rt, jsi::Object &options, const char *name, + bool optional) { + jsi::Value value = options.getProperty(rt, name); + if ((value.isNull() || value.isUndefined()) && optional) + return 0; + + if (value.isNumber()) + return value.asNumber(); + + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "number"); +}; + +template <> +KeyEntryListHandle jsiToValue(jsi::Runtime &rt, jsi::Object &options, + const char *name, bool optional) { + std::string handle = jsiToValue(rt, options, name, optional); + FfiKeyEntryList *ffiKeyEntryListPtr = + reinterpret_cast(std::stol(handle)); + KeyEntryListHandle keyEntryListHandle = + KeyEntryListHandle{._0 = ffiKeyEntryListPtr}; + + return keyEntryListHandle; +}; + +template <> +EntryListHandle jsiToValue(jsi::Runtime &rt, jsi::Object &options, + const char *name, bool optional) { + std::string handle = jsiToValue(rt, options, name, optional); + FfiEntryList *ffiEntryListPtr = + reinterpret_cast(std::stol(handle)); + EntryListHandle entryListHandle = EntryListHandle{._0 = ffiEntryListPtr}; + + return entryListHandle; +}; + +template <> +LocalKeyHandle jsiToValue(jsi::Runtime &rt, jsi::Object &options, + const char *name, bool optional) { + std::string handle = jsiToValue(rt, options, name, optional); + LocalKey *localKeyPtr = reinterpret_cast(std::stol(handle)); + LocalKeyHandle localKeyHandle = LocalKeyHandle{._0 = localKeyPtr}; + + return localKeyHandle; +}; + +template <> +std::vector +jsiToValue>(jsi::Runtime &rt, jsi::Object &options, + const char *name, bool optional) { + jsi::Value value = options.getProperty(rt, name); + + if (value.isObject() && value.asObject(rt).isArray(rt)) { + std::vector vec = {}; + jsi::Array arr = value.asObject(rt).asArray(rt); + size_t length = arr.length(rt); + for (int i = 0; i < length; i++) { + jsi::Value element = arr.getValueAtIndex(rt, i); + if (element.isNumber()) { + vec.push_back(element.asNumber()); + } else { + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "number"); + } + } + return vec; + } + + if (optional) + return {}; + + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "Array"); +} + +template <> +ByteBuffer jsiToValue(jsi::Runtime &rt, jsi::Object &options, + const char *name, bool optional) { + jsi::Value value = options.getProperty(rt, name); + + if (value.isObject() && value.asObject(rt).isArrayBuffer(rt)) { + jsi::ArrayBuffer arrayBuffer = value.getObject(rt).getArrayBuffer(rt); + return ByteBuffer{int(arrayBuffer.size(rt)), arrayBuffer.data(rt)}; + } + + if (optional) + return ByteBuffer{0, 0}; + + throw jsi::JSError(rt, errorPrefix + name + errorInfix + "Uint8Array"); +} + +jsi::ArrayBuffer byteBufferToArrayBuffer(jsi::Runtime &rt, ByteBuffer bb) { + jsi::ArrayBuffer arrayBuffer = rt.global() + .getPropertyAsFunction(rt, "ArrayBuffer") + .callAsConstructor(rt, int(bb.len)) + .getObject(rt) + .getArrayBuffer(rt); + + memcpy(arrayBuffer.data(rt), bb.data, bb.len); + return arrayBuffer; +} + +jsi::ArrayBuffer secretBufferToArrayBuffer(jsi::Runtime &rt, SecretBuffer sb) { + jsi::ArrayBuffer arrayBuffer = rt.global() + .getPropertyAsFunction(rt, "ArrayBuffer") + .callAsConstructor(rt, int(sb.len)) + .getObject(rt) + .getArrayBuffer(rt); + + memcpy(arrayBuffer.data(rt), sb.data, sb.len); + return arrayBuffer; +} + +} // namespace ariesAskarTurboModuleUtility diff --git a/wrappers/javascript/aries-askar-react-native/cpp/turboModuleUtility.h b/wrappers/javascript/aries-askar-react-native/cpp/turboModuleUtility.h new file mode 100644 index 00000000..f97db934 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/cpp/turboModuleUtility.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include +#include + +using namespace facebook; + +namespace ariesAskarTurboModuleUtility { + +static const std::string errorPrefix = "Value `"; +static const std::string errorInfix = "` is not of type "; + +// state of a callback function +struct State { + jsi::Function cb; + jsi::Runtime *rt; + + State(jsi::Function *cb_) : cb(std::move(*cb_)) {} +}; + +// Install the Turbomodule +void registerTurboModule(jsi::Runtime &rt, + std::shared_ptr jsCallInvoker); + +// Asserts that a jsi::Value is an object and can be safely transformed +void assertValueIsObject(jsi::Runtime &rt, const jsi::Value *val); + +// Converts jsi values to regular cpp values +template +T jsiToValue(jsi::Runtime &rt, jsi::Object &options, const char *name, + bool optional = false); + +// Callback function that makes the host function async +void callback(CallbackId result, ErrorCode code); + +// Callback function that makes the host function async with response from rust +// side +template +void callbackWithResponse(CallbackId result, ErrorCode code, T response); + +// Instantiate a return object for JS side. +// ```typescript +// type ReturnObject = { +// errorCode: number +// value?: unknown | null +// } +// ``` +// +// Value will be defined if there is no error. +// Value will be `null` if there is no value to return +// Value will be undefined if there is an error, e.g. error code != 0 +template +jsi::Value createReturnValue(jsi::Runtime &rt, ErrorCode code, T out); + +jsi::ArrayBuffer byteBufferToArrayBuffer(jsi::Runtime &rt, ByteBuffer bb); +jsi::ArrayBuffer secretBufferToArrayBuffer(jsi::Runtime &rt, SecretBuffer sb); + +} // namespace ariesAskarTurboModuleUtility diff --git a/wrappers/javascript/aries-askar-react-native/ios/AriesAskar.h b/wrappers/javascript/aries-askar-react-native/ios/AriesAskar.h new file mode 100644 index 00000000..a2bf7d3d --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/ios/AriesAskar.h @@ -0,0 +1,5 @@ +#import + +@interface AriesAskar : NSObject + +@end diff --git a/wrappers/javascript/aries-askar-react-native/ios/AriesAskar.mm b/wrappers/javascript/aries-askar-react-native/ios/AriesAskar.mm new file mode 100644 index 00000000..635876de --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/ios/AriesAskar.mm @@ -0,0 +1,33 @@ +#import "turboModuleUtility.h" + +#import "AriesAskar.h" +#import +#import +#import +#import + +using namespace facebook; + +@implementation AriesAskar + +RCT_EXPORT_MODULE() + +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) +{ + RCTBridge* bridge = [RCTBridge currentBridge]; + RCTCxxBridge* cxxBridge = (RCTCxxBridge*)bridge; + if (cxxBridge == nil) { + return @false; + } + + jsi::Runtime* jsiRuntime = (jsi::Runtime*) cxxBridge.runtime; + if (jsiRuntime == nil) { + return @false; + } + + auto callInvoker = bridge.jsCallInvoker; + ariesAskarTurboModuleUtility::registerTurboModule(*jsiRuntime, callInvoker); + return @true; +} + +@end diff --git a/wrappers/javascript/aries-askar-react-native/ios/AriesAskar.xcodeproj/project.pbxproj b/wrappers/javascript/aries-askar-react-native/ios/AriesAskar.xcodeproj/project.pbxproj new file mode 100644 index 00000000..616424c3 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/ios/AriesAskar.xcodeproj/project.pbxproj @@ -0,0 +1,276 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5E46D8CD2428F78900513E24 /* example.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E46D8CB2428F78900513E24 /* example.cpp */; }; + 5E555C0D2413F4C50049A1A2 /* AriesAskarReactNative.mm in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* AriesAskarReactNative.mm */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 58B511D91A9E6C8500147676 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 134814201AA4EA6300B7C361 /* libAriesAskarReactNative.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAriesAskarReactNative.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5E46D8CB2428F78900513E24 /* example.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = example.cpp; path = ../cpp/example.cpp; sourceTree = ""; }; + 5E46D8CC2428F78900513E24 /* example.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = example.h; path = ../cpp/example.h; sourceTree = ""; }; + B3E7B5891CC2AC0600A0062D /* AriesAskarReactNative.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AriesAskarReactNative.mm; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58B511D81A9E6C8500147676 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 134814211AA4EA7D00B7C361 /* Products */ = { + isa = PBXGroup; + children = ( + 134814201AA4EA6300B7C361 /* libAriesAskarReactNative.a */, + ); + name = Products; + sourceTree = ""; + }; + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + 5E46D8CB2428F78900513E24 /* example.cpp */, + 5E46D8CC2428F78900513E24 /* example.h */, + B3E7B5891CC2AC0600A0062D /* AriesAskarReactNative.mm */, + 134814211AA4EA7D00B7C361 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 58B511DA1A9E6C8500147676 /* AriesAskarReactNative */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "AriesAskarReactNative" */; + buildPhases = ( + 58B511D71A9E6C8500147676 /* Sources */, + 58B511D81A9E6C8500147676 /* Frameworks */, + 58B511D91A9E6C8500147676 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AriesAskarReactNative; + productName = RCTDataManager; + productReference = 134814201AA4EA6300B7C361 /* libAriesAskarReactNative.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 58B511DA1A9E6C8500147676 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "AriesAskar" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58B511DA1A9E6C8500147676 /* AriesAskarReactNative */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 58B511D71A9E6C8500147676 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E46D8CD2428F78900513E24 /* example.cpp in Sources */, + 5E555C0D2413F4C50049A1A2 /* AriesAskarReactNative.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 58B511F01A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = AriesAskarReactNative; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 58B511F11A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = AriesAskarReactNative; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "AriesAskar" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "AriesAskarReactNative" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511F01A9E6C8500147676 /* Debug */, + 58B511F11A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/wrappers/javascript/aries-askar-react-native/native/aries_askar.node b/wrappers/javascript/aries-askar-react-native/native/aries_askar.node new file mode 100644 index 00000000..ee9b5b34 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/native/aries_askar.node @@ -0,0 +1,4 @@ +# This is a placeholder file to prevent node-pre-gyp from installing the +# aries-askar binary when cloning this repository. It won't be published to +# NPM, meaning when you download this package from npm it will try to download +# the binary. \ No newline at end of file diff --git a/wrappers/javascript/aries-askar-react-native/package.json b/wrappers/javascript/aries-askar-react-native/package.json new file mode 100644 index 00000000..4a0dff57 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/package.json @@ -0,0 +1,62 @@ +{ + "name": "@hyperledger/aries-askar-react-native", + "version": "0.1.1", + "license": "Apache-2.0", + "description": "React Native wrapper for Aries Askar", + "main": "build/index", + "source": "src/index", + "homepage": "https://github.com/hyperledger/aries-askar/tree/main/wrappers/javascript/aries-askar-react-native", + "author": "Hyperledger (https://github.com/hyperledger)", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-askar", + "directory": "wrappers/javascript/aries-askar-react-native" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "build", + "android/src", + "android/build.gradle", + "android/CMakeLists.txt", + "android/cpp-adapter.cpp", + "ios/**/*.h", + "ios/**/*.mm", + "ios/AriesAskar.xcodeproj/project.pbxproj", + "cpp/**/*.cpp", + "cpp/**/*.h", + "aries-askar.podspec" + ], + "scripts": { + "build": "yarn clean && yarn compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "install": "node-pre-gyp install" + }, + "dependencies": { + "@hyperledger/aries-askar-shared": "0.1.1", + "@mapbox/node-pre-gyp": "^1.0.10" + }, + "devDependencies": { + "@types/react": "^16.9.19", + "@types/react-native": "^0.67.0", + "babel-jest": "^28.1.1", + "babel-plugin-module-resolver": "^4.0.0", + "prettier": "2.6.2", + "react": "17.0.2", + "react-native": "0.67.2", + "typescript": "4.5.5" + }, + "peerDependencies": { + "react": ">= 16", + "react-native": ">= 0.66.0" + }, + "binary": { + "module_name": "aries_askar", + "module_path": "native", + "remote_path": "v0.2.9", + "host": "https://github.com/hyperledger/aries-askar/releases/download/", + "package_name": "library-ios-android.tar.gz" + } +} diff --git a/wrappers/javascript/aries-askar-react-native/src/NativeBindings.ts b/wrappers/javascript/aries-askar-react-native/src/NativeBindings.ts new file mode 100644 index 00000000..ff6a1ef3 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/src/NativeBindings.ts @@ -0,0 +1,169 @@ +import type { CallbackWithResponse, ReturnObject } from './serialize' + +type LocalKeyHandle = string + +// TODO: convert all unknown types +export interface NativeBindings { + version(options: Record): string + getCurrentError(options: Record): string + + entryListCount(options: { entryListHandle: string }): ReturnObject + entryListFree(options: { entryListHandle: string }): ReturnObject + entryListGetCategory(options: { entryListHandle: string; index: number }): ReturnObject + entryListGetName(options: { entryListHandle: string; index: number }): ReturnObject + entryListGetTags(options: { entryListHandle: string; index: number }): ReturnObject + entryListGetValue(options: { entryListHandle: string; index: number }): ReturnObject + + keyAeadDecrypt(options: { + localKeyHandle: string + ciphertext: ArrayBuffer + nonce: ArrayBuffer + tag?: ArrayBuffer + aad?: ArrayBuffer + }): ReturnObject + + keyAeadEncrypt(options: { + localKeyHandle: string + message: ArrayBuffer + nonce?: ArrayBuffer + aad?: ArrayBuffer + }): ReturnObject<{ + noncePos: number + tagPos: number + buffer: ArrayBuffer + }> + + keyAeadGetPadding(options: { localKeyHandle: string; msgLen: number }): ReturnObject + + keyAeadGetParams(options: { localKeyHandle: string }): ReturnObject<{ nonceLength: number; tagLength: number }> + + keyAeadRandomNonce(options: unknown): ReturnObject + + keyConvert(options: unknown): ReturnObject + + keyCryptoBox(options: unknown): ReturnObject + + keyCryptoBoxOpen(options: unknown): ReturnObject + + keyCryptoBoxRandomNonce(options: Record): ReturnObject + + keyCryptoBoxSeal(options: unknown): ReturnObject + + keyCryptoBoxSealOpen(options: unknown): ReturnObject + + keyDeriveEcdh1pu(options: unknown): ReturnObject + + keyDeriveEcdhEs(options: unknown): ReturnObject + + keyEntryListCount(options: unknown): ReturnObject + + keyEntryListFree(options: unknown): ReturnObject + + keyEntryListGetAlgorithm(options: unknown): ReturnObject + + keyEntryListGetMetadata(options: unknown): ReturnObject + + keyEntryListGetName(options: unknown): ReturnObject + + keyEntryListGetTags(options: unknown): ReturnObject + + keyEntryListLoadLocal(options: unknown): ReturnObject + + keyFree(options: unknown): ReturnObject + + keyFromJwk(options: unknown): ReturnObject + + keyFromKeyExchange(options: unknown): ReturnObject + + keyFromPublicBytes(options: unknown): ReturnObject + + keyFromSecretBytes(options: unknown): ReturnObject + + keyFromSeed(options: unknown): ReturnObject + + keyGenerate(options: unknown): ReturnObject + + keyGetAlgorithm(options: unknown): ReturnObject + + keyGetEphemeral(options: unknown): ReturnObject + + keyGetJwkPublic(options: unknown): ReturnObject + + keyGetJwkSecret(options: unknown): ReturnObject + + keyGetJwkThumbprint(options: unknown): ReturnObject + + keyGetPublicBytes(options: unknown): ReturnObject + + keyGetSecretBytes(options: unknown): ReturnObject + + keySignMessage(options: unknown): ReturnObject + + keyUnwrapKey(options: unknown): ReturnObject + + keyVerifySignature(options: unknown): ReturnObject + + keyWrapKey(options: unknown): ReturnObject<{ buffer: ArrayBuffer; tagPos: number; noncePos: number }> + + scanFree(options: unknown): ReturnObject + + scanNext(options: unknown): ReturnObject + + scanStart(options: unknown): ReturnObject + + sessionClose(options: unknown): ReturnObject + + sessionCount(options: unknown): ReturnObject + + sessionFetch(options: unknown): ReturnObject + + sessionFetchAll(options: unknown): ReturnObject + + sessionFetchAllKeys(options: unknown): ReturnObject + + sessionFetchKey(options: unknown): ReturnObject + + sessionInsertKey(options: unknown): ReturnObject + + sessionRemoveAll(options: unknown): ReturnObject + + sessionRemoveKey(options: unknown): ReturnObject + + sessionStart(options: unknown): ReturnObject + + sessionUpdate(options: unknown): ReturnObject + + sessionUpdateKey(options: unknown): ReturnObject + + setCustomLogger(options: unknown): ReturnObject + + setDefaultLogger(options: unknown): ReturnObject + + setMaxLogLevel(options: unknown): ReturnObject + + storeClose(options: unknown): ReturnObject + + storeCreateProfile(options: unknown): ReturnObject + + storeGenerateRawKey(options: { seed?: ArrayBuffer }): ReturnObject + + storeGetProfileName(options: unknown): ReturnObject + + storeOpen(options: { + specUri: string + keyMethod?: string + passKey?: string + profile?: string + cb: CallbackWithResponse + }): ReturnObject + + storeProvision(options: unknown): ReturnObject + + storeRekey(options: unknown): ReturnObject + + storeRemove(options: unknown): ReturnObject + + storeRemoveProfile(options: unknown): ReturnObject + + migrateIndySdk(options: unknown): ReturnObject +} diff --git a/wrappers/javascript/aries-askar-react-native/src/ReactNativeAriesAskar.ts b/wrappers/javascript/aries-askar-react-native/src/ReactNativeAriesAskar.ts new file mode 100644 index 00000000..97757db9 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/src/ReactNativeAriesAskar.ts @@ -0,0 +1,672 @@ +import type { NativeBindings } from './NativeBindings' +import type { Callback, CallbackWithResponse, ReturnObject } from './serialize' +import type { + AriesAskar, + AriesAskarErrorObject, + EntryListCountOptions, + EntryListFreeOptions, + EntryListGetCategoryOptions, + EntryListGetNameOptions, + EntryListGetTagsOptions, + EntryListGetValueOptions, + KeyAeadDecryptOptions, + KeyAeadEncryptOptions, + KeyAeadGetPaddingOptions, + KeyAeadGetParamsOptions, + KeyAeadRandomNonceOptions, + KeyConvertOptions, + KeyCryptoBoxOpenOptions, + KeyCryptoBoxOptions, + KeyCryptoBoxSealOpenOptions, + KeyCryptoBoxSealOptions, + KeyDeriveEcdh1puOptions, + KeyDeriveEcdhEsOptions, + KeyEntryListCountOptions, + KeyEntryListFreeOptions, + KeyEntryListGetAlgorithmOptions, + KeyEntryListGetMetadataOptions, + KeyEntryListGetNameOptions, + KeyEntryListGetTagsOptions, + KeyEntryListLoadLocalOptions, + KeyFreeOptions, + KeyFromJwkOptions, + KeyFromKeyExchangeOptions, + KeyFromPublicBytesOptions, + KeyFromSecretBytesOptions, + KeyFromSeedOptions, + KeyGenerateOptions, + KeyGetAlgorithmOptions, + KeyGetEphemeralOptions, + KeyGetJwkPublicOptions, + KeyGetJwkSecretOptions, + KeyGetJwkThumbprintOptions, + KeyGetPublicBytesOptions, + KeyGetSecretBytesOptions, + KeySignMessageOptions, + KeyUnwrapKeyOptions, + KeyVerifySignatureOptions, + KeyWrapKeyOptions, + MigrateIndySdkOptions, + ScanFreeOptions, + ScanNextOptions, + ScanStartOptions, + SessionCloseOptions, + SessionCountOptions, + SessionFetchAllKeysOptions, + SessionFetchAllOptions, + SessionFetchKeyOptions, + SessionFetchOptions, + SessionInsertKeyOptions, + SessionRemoveAllOptions, + SessionRemoveKeyOptions, + SessionStartOptions, + SessionUpdateKeyOptions, + SessionUpdateOptions, + SetCustomLoggerOptions, + SetMaxLogLevelOptions, + StoreCloseOptions, + StoreCreateProfileOptions, + StoreGenerateRawKeyOptions, + StoreGetProfileNameOptions, + StoreOpenOptions, + StoreProvisionOptions, + StoreRekeyOptions, + StoreRemoveOptions, + StoreRemoveProfileOptions, +} from '@hyperledger/aries-askar-shared' + +import { + AriesAskarError, + handleInvalidNullResponse, + AeadParams, + EncryptedBuffer, + LocalKeyHandle, + EntryListHandle, + StoreHandle, + SessionHandle, + ScanHandle, + KeyEntryListHandle, +} from '@hyperledger/aries-askar-shared' + +import { serializeArguments } from './serialize' + +export class ReactNativeAriesAskar implements AriesAskar { + private ariesAskar: NativeBindings + + public constructor(bindings: NativeBindings) { + this.ariesAskar = bindings + } + + private handleError({ errorCode, value }: ReturnObject): T { + if (errorCode !== 0) { + throw new AriesAskarError(JSON.parse(this.getCurrentError()) as AriesAskarErrorObject) + } + + return value as T + } + + private promisify = (method: (cb: Callback) => void): Promise => { + return new Promise((resolve, reject) => { + const _cb: Callback = ({ errorCode }) => { + if (errorCode !== 0) { + reject(new AriesAskarError(JSON.parse(this.getCurrentError()) as AriesAskarErrorObject)) + } else { + resolve() + } + } + + method(_cb) + }) + } + + private promisifyWithResponse = ( + method: (cb: CallbackWithResponse) => void + ): Promise => { + return new Promise((resolve, reject) => { + const _cb: CallbackWithResponse = ({ errorCode, value }) => { + if (errorCode !== 0) { + reject(new AriesAskarError(JSON.parse(this.getCurrentError()) as AriesAskarErrorObject)) + } else { + if (value === undefined) { + reject( + AriesAskarError.customError({ message: 'error code was 0 but no value found. This should not occur.' }) + ) + } else { + resolve(value) + } + } + } + method(_cb) + }) + } + + public version(): string { + return handleInvalidNullResponse(this.ariesAskar.version({})) + } + + public getCurrentError(): string { + return handleInvalidNullResponse(this.ariesAskar.getCurrentError({})) + } + + public clearCustomLogger(): void { + throw new Error('Method not implemented. clearCustomLogger') + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public setCustomLogger(_: SetCustomLoggerOptions): void { + throw new Error('Method not implemented. setCustomLogger') + } + + public setDefaultLogger(): void { + this.ariesAskar.setDefaultLogger({}) + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public setMaxLogLevel(_: SetMaxLogLevelOptions): void { + throw new Error('Method not implemented. setMaxLogLevel') + } + + public entryListCount(options: EntryListCountOptions): number { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.entryListCount(serializedOptions))) + } + + public entryListFree(options: EntryListFreeOptions): void { + const serializedOptions = serializeArguments(options) + + // null resopnse is expected as we're freeing the object + this.handleError(this.ariesAskar.entryListFree(serializedOptions)) + } + + public entryListGetCategory(options: EntryListGetCategoryOptions): string { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.entryListGetCategory(serializedOptions))) + } + + public entryListGetName(options: EntryListGetNameOptions): string { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.entryListGetName(serializedOptions))) + } + + public entryListGetTags(options: EntryListGetTagsOptions): string | null { + const serializedOptions = serializeArguments(options) + return this.handleError(this.ariesAskar.entryListGetTags(serializedOptions)) + } + + public entryListGetValue(options: EntryListGetValueOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.entryListGetValue(serializedOptions))) + return new Uint8Array(buf) + } + + public keyAeadDecrypt(options: KeyAeadDecryptOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyAeadDecrypt(serializedOptions))) + return new Uint8Array(buf) + } + + public keyAeadEncrypt(options: KeyAeadEncryptOptions): EncryptedBuffer { + const serializedOptions = serializeArguments(options) + const ret = this.handleError(this.ariesAskar.keyAeadEncrypt(serializedOptions)) + + const { buffer, noncePos, tagPos } = handleInvalidNullResponse(ret) + + return new EncryptedBuffer({ tagPos, noncePos, buffer: new Uint8Array(buffer) }) + } + + public keyAeadGetPadding(options: KeyAeadGetPaddingOptions): number { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.keyAeadGetPadding(serializedOptions))) + } + + public keyAeadGetParams(options: KeyAeadGetParamsOptions): AeadParams { + const serializedOptions = serializeArguments(options) + const ret = this.handleError(this.ariesAskar.keyAeadGetParams(serializedOptions)) + + const { tagLength, nonceLength } = handleInvalidNullResponse(ret) + + return new AeadParams({ nonceLength, tagLength }) + } + + public keyAeadRandomNonce(options: KeyAeadRandomNonceOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyAeadRandomNonce(serializedOptions))) + return new Uint8Array(buf) + } + + public keyConvert(options: KeyConvertOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyConvert(serializedOptions))) + + return new LocalKeyHandle(handle) + } + + public keyCryptoBox(options: KeyCryptoBoxOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyCryptoBox(serializedOptions))) + return new Uint8Array(buf) + } + + public keyCryptoBoxOpen(options: KeyCryptoBoxOpenOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyCryptoBoxOpen(serializedOptions))) + return new Uint8Array(buf) + } + + public keyCryptoBoxRandomNonce(): Uint8Array { + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyCryptoBoxRandomNonce({}))) + return new Uint8Array(buf) + } + + public keyCryptoBoxSeal(options: KeyCryptoBoxSealOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyCryptoBoxSeal(serializedOptions))) + return new Uint8Array(buf) + } + + public keyCryptoBoxSealOpen(options: KeyCryptoBoxSealOpenOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyCryptoBoxSealOpen(serializedOptions))) + return new Uint8Array(buf) + } + + public keyDeriveEcdh1pu(options: KeyDeriveEcdh1puOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyDeriveEcdh1pu(serializedOptions))) + return new LocalKeyHandle(handle) + } + + public keyDeriveEcdhEs(options: KeyDeriveEcdhEsOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyDeriveEcdhEs(serializedOptions))) + return new LocalKeyHandle(handle) + } + + public keyEntryListCount(options: KeyEntryListCountOptions): number { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.keyEntryListCount(serializedOptions))) + } + + public keyEntryListFree(options: KeyEntryListFreeOptions): void { + const serializedOptions = serializeArguments(options) + + // null resopnse is expected as we're freeing the object + this.handleError(this.ariesAskar.keyEntryListFree(serializedOptions)) + } + + public keyEntryListGetAlgorithm(options: KeyEntryListGetAlgorithmOptions): string { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.keyEntryListGetAlgorithm(serializedOptions))) + } + + public keyEntryListGetMetadata(options: KeyEntryListGetMetadataOptions): string | null { + const serializedOptions = serializeArguments(options) + return this.handleError(this.ariesAskar.keyEntryListGetMetadata(serializedOptions)) + } + + public keyEntryListGetName(options: KeyEntryListGetNameOptions): string { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.keyEntryListGetName(serializedOptions))) + } + + public keyEntryListGetTags(options: KeyEntryListGetTagsOptions): string | null { + const serializedOptions = serializeArguments(options) + return this.handleError(this.ariesAskar.keyEntryListGetTags(serializedOptions)) + } + + public keyEntryListLoadLocal(options: KeyEntryListLoadLocalOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyEntryListLoadLocal(serializedOptions))) + + return new LocalKeyHandle(handle) + } + + public keyFree(options: KeyFreeOptions): void { + const serializedOptions = serializeArguments(options) + + // null resopnse is expected as we're freeing the object + this.handleError(this.ariesAskar.keyFree(serializedOptions)) + } + + public keyFromJwk(options: KeyFromJwkOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyFromJwk(serializedOptions))) + + return new LocalKeyHandle(handle) + } + + public keyFromKeyExchange(options: KeyFromKeyExchangeOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyFromKeyExchange(serializedOptions))) + + return new LocalKeyHandle(handle) + } + + public keyFromPublicBytes(options: KeyFromPublicBytesOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyFromPublicBytes(serializedOptions))) + + return new LocalKeyHandle(handle) + } + + public keyFromSecretBytes(options: KeyFromSecretBytesOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyFromSecretBytes(serializedOptions))) + + return new LocalKeyHandle(handle) + } + + public keyFromSeed(options: KeyFromSeedOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyFromSeed(serializedOptions))) + + return new LocalKeyHandle(handle) + } + + public keyGenerate(options: KeyGenerateOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyGenerate(serializedOptions))) + + return new LocalKeyHandle(handleInvalidNullResponse(handle)) + } + + public keyGetAlgorithm(options: KeyGetAlgorithmOptions): string { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.keyGetAlgorithm(serializedOptions))) + } + + public keyGetEphemeral(options: KeyGetEphemeralOptions): number { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.keyGetEphemeral(serializedOptions))) + } + + public keyGetJwkPublic(options: KeyGetJwkPublicOptions): string { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.keyGetJwkPublic(serializedOptions))) + } + + public keyGetJwkSecret(options: KeyGetJwkSecretOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyGetJwkSecret(serializedOptions))) + return new Uint8Array(buf) + } + + public keyGetJwkThumbprint(options: KeyGetJwkThumbprintOptions): string { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.keyGetJwkThumbprint(serializedOptions))) + } + + public keyGetPublicBytes(options: KeyGetPublicBytesOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyGetPublicBytes(serializedOptions))) + return new Uint8Array(buf) + } + + public keyGetSecretBytes(options: KeyGetSecretBytesOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyGetSecretBytes(serializedOptions))) + return new Uint8Array(buf) + } + + public keySignMessage(options: KeySignMessageOptions): Uint8Array { + const serializedOptions = serializeArguments(options) + const buf = handleInvalidNullResponse(this.handleError(this.ariesAskar.keySignMessage(serializedOptions))) + return new Uint8Array(buf) + } + + public keyUnwrapKey(options: KeyUnwrapKeyOptions): LocalKeyHandle { + const serializedOptions = serializeArguments(options) + const handle = handleInvalidNullResponse(this.handleError(this.ariesAskar.keyUnwrapKey(serializedOptions))) + + return new LocalKeyHandle(handle) + } + + public keyVerifySignature(options: KeyVerifySignatureOptions): boolean { + const serializedOptions = serializeArguments(options) + const result = this.handleError(this.ariesAskar.keyVerifySignature(serializedOptions)) + + return !!result + } + + public keyWrapKey(options: KeyWrapKeyOptions): EncryptedBuffer { + const serializedOptions = serializeArguments(options) + const ret = this.handleError(this.ariesAskar.keyWrapKey(serializedOptions)) + + const { buffer, noncePos, tagPos } = handleInvalidNullResponse(ret) + + return new EncryptedBuffer({ tagPos, noncePos, buffer: new Uint8Array(buffer) }) + } + + public scanFree(options: ScanFreeOptions): void { + const serializedOptions = serializeArguments(options) + + // null resopnse is expected as we're freeing the object + this.handleError(this.ariesAskar.scanFree(serializedOptions)) + } + + public async scanNext(options: ScanNextOptions) { + const serializedOptions = serializeArguments(options) + const handle = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.scanNext({ cb, ...serializedOptions })) + ) + + return EntryListHandle.fromHandle(handle) + } + + public async scanStart(options: ScanStartOptions): Promise { + const { category, storeHandle, limit, offset, profile, tagFilter } = serializeArguments(options) + const handle = await this.promisifyWithResponse((cb) => + this.handleError( + this.ariesAskar.scanStart({ + cb, + category, + storeHandle, + offset: offset || 0, + limit: limit || -1, + profile, + tagFilter, + }) + ) + ) + + return ScanHandle.fromHandle(handle) + } + + public sessionClose(options: SessionCloseOptions): Promise { + const serializedOptions = serializeArguments(options) + return this.promisify((cb) => this.handleError(this.ariesAskar.sessionClose({ cb, ...serializedOptions }))) + } + + public async sessionCount(options: SessionCountOptions): Promise { + const serializedOptions = serializeArguments(options) + const response = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.sessionCount({ cb, ...serializedOptions })) + ) + + return handleInvalidNullResponse(response) + } + + public async sessionFetch(options: SessionFetchOptions) { + const serializedOptions = serializeArguments(options) + const handle = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.sessionFetch({ cb, ...serializedOptions })) + ) + + return EntryListHandle.fromHandle(handle) + } + + public async sessionFetchAll(options: SessionFetchAllOptions) { + const { category, sessionHandle, forUpdate, limit, tagFilter } = serializeArguments(options) + const handle = await this.promisifyWithResponse((cb) => + this.handleError( + this.ariesAskar.sessionFetchAll({ cb, category, sessionHandle, forUpdate, limit: limit || -1, tagFilter }) + ) + ) + + return EntryListHandle.fromHandle(handle) + } + + public async sessionFetchAllKeys(options: SessionFetchAllKeysOptions) { + const { sessionHandle, algorithm, forUpdate, limit, thumbprint, tagFilter } = serializeArguments(options) + const handle = await this.promisifyWithResponse((cb) => + this.handleError( + this.ariesAskar.sessionFetchAllKeys({ + cb, + sessionHandle, + algorithm, + forUpdate: forUpdate || -1, + limit: limit || -1, + thumbprint, + tagFilter, + }) + ) + ) + + return KeyEntryListHandle.fromHandle(handle) + } + public async sessionFetchKey(options: SessionFetchKeyOptions) { + const serializedOptions = serializeArguments(options) + const handle = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.sessionFetchKey({ cb, ...serializedOptions })) + ) + + return KeyEntryListHandle.fromHandle(handle) + } + + public sessionInsertKey(options: SessionInsertKeyOptions): Promise { + const { sessionHandle, name, localKeyHandle, expiryMs, metadata, tags } = serializeArguments(options) + return this.promisify((cb) => + this.handleError( + this.ariesAskar.sessionInsertKey({ + cb, + sessionHandle, + name, + localKeyHandle, + expiryMs: expiryMs || -1, + metadata, + tags, + }) + ) + ) + } + + public async sessionRemoveAll(options: SessionRemoveAllOptions): Promise { + const serializedOptions = serializeArguments(options) + const response = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.sessionRemoveAll({ cb, ...serializedOptions })) + ) + + return handleInvalidNullResponse(response) + } + + public sessionRemoveKey(options: SessionRemoveKeyOptions): Promise { + const serializedOptions = serializeArguments(options) + return this.promisify((cb) => this.handleError(this.ariesAskar.sessionRemoveKey({ cb, ...serializedOptions }))) + } + + public async sessionStart(options: SessionStartOptions): Promise { + const serializedOptions = serializeArguments(options) + const handle = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.sessionStart({ cb, ...serializedOptions })) + ) + + return SessionHandle.fromHandle(handle) + } + + public sessionUpdate(options: SessionUpdateOptions): Promise { + const { category, name, operation, sessionHandle, expiryMs, tags, value } = serializeArguments(options) + return this.promisify((cb) => + this.handleError( + this.ariesAskar.sessionUpdate({ + cb, + category, + name, + operation, + sessionHandle, + expiryMs: expiryMs || -1, + tags, + value, + }) + ) + ) + } + + public sessionUpdateKey(options: SessionUpdateKeyOptions): Promise { + const serializedOptions = serializeArguments(options) + return this.promisify((cb) => this.handleError(this.ariesAskar.sessionUpdateKey({ cb, ...serializedOptions }))) + } + + public storeClose(options: StoreCloseOptions): Promise { + const serializedOptions = serializeArguments(options) + return this.promisify((cb) => this.handleError(this.ariesAskar.storeClose({ cb, ...serializedOptions }))) + } + + public async storeCreateProfile(options: StoreCreateProfileOptions): Promise { + const serializedOptions = serializeArguments(options) + const response = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.storeCreateProfile({ cb, ...serializedOptions })) + ) + + return handleInvalidNullResponse(response) + } + + public storeGenerateRawKey(options: StoreGenerateRawKeyOptions): string { + const serializedOptions = serializeArguments(options) + return handleInvalidNullResponse(this.handleError(this.ariesAskar.storeGenerateRawKey(serializedOptions))) + } + + public async storeGetProfileName(options: StoreGetProfileNameOptions): Promise { + const serializedOptions = serializeArguments(options) + const response = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.storeGetProfileName({ cb, ...serializedOptions })) + ) + + return handleInvalidNullResponse(response) + } + + public async storeOpen(options: StoreOpenOptions): Promise { + const serializedOptions = serializeArguments(options) + const handle = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.storeOpen({ cb, ...serializedOptions })) + ) + + return StoreHandle.fromHandle(handle) + } + + public async storeProvision(options: StoreProvisionOptions): Promise { + const serializedOptions = serializeArguments(options) + const handle = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.storeProvision({ cb, ...serializedOptions })) + ) + + return StoreHandle.fromHandle(handle) + } + + public storeRekey(options: StoreRekeyOptions): Promise { + const serializedOptions = serializeArguments(options) + return this.promisify((cb) => this.handleError(this.ariesAskar.storeRekey({ cb, ...serializedOptions }))) + } + + public async storeRemove(options: StoreRemoveOptions): Promise { + const serializedOptions = serializeArguments(options) + const response = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.storeRemove({ cb, ...serializedOptions })) + ) + + return handleInvalidNullResponse(response) + } + + public async storeRemoveProfile(options: StoreRemoveProfileOptions): Promise { + const serializedOptions = serializeArguments(options) + const response = await this.promisifyWithResponse((cb) => + this.handleError(this.ariesAskar.storeRemoveProfile({ cb, ...serializedOptions })) + ) + + return handleInvalidNullResponse(response) + } + + public async migrateIndySdk(options: MigrateIndySdkOptions): Promise { + const serializedOptions = serializeArguments(options) + return this.promisify((cb) => this.handleError(this.ariesAskar.migrateIndySdk({ cb, ...serializedOptions }))) + } +} diff --git a/wrappers/javascript/aries-askar-react-native/src/index.ts b/wrappers/javascript/aries-askar-react-native/src/index.ts new file mode 100644 index 00000000..c0c64dcd --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/src/index.ts @@ -0,0 +1,22 @@ +import type { NativeBindings } from './NativeBindings' + +import { registerAriesAskar } from '@hyperledger/aries-askar-shared' +import { NativeModules } from 'react-native' + +import { ReactNativeAriesAskar } from './ReactNativeAriesAskar' + +// Reexport everything from shared +export * from '@hyperledger/aries-askar-shared' + +const module = NativeModules.AriesAskar as { install: () => boolean } +if (!module.install()) throw Error('Unable to install the turboModule: ariesAskar') + +// This can already check whether `_aries_askar` exists on global +// eslint-disable-next-line @typescript-eslint/no-use-before-define +if (!_aries_askar) { + throw Error('_aries_askar has not been exposed on global. Something went wrong while installing the turboModule') +} + +declare let _aries_askar: NativeBindings + +registerAriesAskar({ askar: new ReactNativeAriesAskar(_aries_askar) }) diff --git a/wrappers/javascript/aries-askar-react-native/src/serialize.ts b/wrappers/javascript/aries-askar-react-native/src/serialize.ts new file mode 100644 index 00000000..6c221e28 --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/src/serialize.ts @@ -0,0 +1,116 @@ +import { ArcHandle, Jwk, ScanHandle, SessionHandle, StoreHandle, Key } from '@hyperledger/aries-askar-shared' + +export type ReturnObject = { + errorCode: number + value?: null | T +} + +export type Callback = (o: ReturnObject) => void +export type CallbackWithResponse = (o: ReturnObject) => void + +type Argument = + | Record + | Array + | Date + | Uint8Array + | SerializedArgument + | boolean + | StoreHandle + | SessionHandle + | ScanHandle + | ArcHandle + | Key + | Jwk + +type SerializedArgument = string | number | Callback | CallbackWithResponse | ArrayBuffer + +type SerializedArguments = Record + +export type SerializedOptions = { + [Property in keyof Type]: Type[Property] extends string + ? string + : Type[Property] extends number + ? number + : Type[Property] extends Record + ? string + : Type[Property] extends Array + ? string + : Type[Property] extends Array | undefined + ? string + : Type[Property] extends Record | undefined + ? string | undefined + : Type[Property] extends Date + ? number + : Type[Property] extends Date | undefined + ? number | undefined + : Type[Property] extends string | undefined + ? undefined | string + : Type[Property] extends number | undefined + ? undefined | number + : Type[Property] extends Callback + ? Callback + : Type[Property] extends CallbackWithResponse + ? CallbackWithResponse + : Type[Property] extends Uint8Array + ? ArrayBuffer + : Type[Property] extends Uint8Array | undefined + ? ArrayBuffer + : Type[Property] extends StoreHandle + ? number + : Type[Property] extends SessionHandle + ? number + : Type[Property] extends ScanHandle + ? number + : Type[Property] extends ArcHandle + ? string + : Type[Property] extends Jwk + ? string + : unknown +} + +const serialize = (arg: Argument): SerializedArgument => { + switch (typeof arg) { + case 'undefined': + return arg + case 'string': + return arg + case 'boolean': + return Number(arg) + case 'number': + return arg + case 'function': + return arg + case 'object': + if (arg instanceof Date) { + return arg.valueOf() + } else if (arg instanceof Uint8Array) { + return arg.buffer + } else if (arg instanceof Jwk) { + return arg.toUint8Array().buffer + } else if (arg instanceof Key) { + return arg.handle.handle + } else if ( + arg instanceof StoreHandle || + arg instanceof SessionHandle || + arg instanceof ScanHandle || + arg instanceof ArcHandle + ) { + return arg.handle + } else { + return JSON.stringify(arg) + } + default: + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + throw new Error(`Could not serialize value ${arg}`) + } +} + +const serializeArguments = = Record>( + args: T +): SerializedOptions => { + const retVal: SerializedArguments = {} + Object.entries(args).forEach(([key, val]) => (retVal[key] = serialize(val))) + return retVal as SerializedOptions +} + +export { serializeArguments } diff --git a/wrappers/javascript/aries-askar-react-native/tsconfig.build.json b/wrappers/javascript/aries-askar-react-native/tsconfig.build.json new file mode 100644 index 00000000..30ac275e --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build", + "types": ["react-native"] + }, + "include": ["src/**/*"] +} diff --git a/wrappers/javascript/aries-askar-react-native/tsconfig.json b/wrappers/javascript/aries-askar-react-native/tsconfig.json new file mode 100644 index 00000000..5394a4ef --- /dev/null +++ b/wrappers/javascript/aries-askar-react-native/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["react-native"] + } +} diff --git a/wrappers/javascript/aries-askar-shared/README.md b/wrappers/javascript/aries-askar-shared/README.md new file mode 100644 index 00000000..8b883b63 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/README.md @@ -0,0 +1,43 @@ +# Aries Askar Shared + +This package does not contain any functionality, just the classes and types that wrap around the native NodeJS / React Native functionality + +## Platform independent setup + +If you would like to leverage the Aries Askar libraries for JavaScript in a platform independent way you need to add the `@hyperledger/aries-askar-shared` package to your project. This package exports all public methods. + +Before calling any methods you then need to make sure you register the platform specific native bindings. You can do this by importing the platform specific package. You can do this by having separate files that register the package, which allows the React Native bundler to import a different package: + +```typescript +// register.ts +import '@hyperledger/aries-askar-nodejs' +``` + +```typescript +// register.native.ts +import '@hyperledger/aries-askar-react-native' +``` + +An alterative approach is to first try to require the Node.JS package, and otherwise require the React Native package: + +```typescript +try { + require('@hyperledger/aries-askar-nodejs') +} catch (error) { + try { + require('@hyperledger/aries-askar-react-native') + } catch (error) { + throw new Error('Could not load Aries Askar bindings') + } +} +``` + +How you approach it is up to you, as long as the native binding are called before any actions are performed on the Aries Askar library. + +## Version Compatibility + +The JavaScript wrapper is versioned independently from the native bindings. The following table shows the compatibility between the different versions: + +| Aries Askar | JavaScript Wrapper | +| ----------- | ------------------ | +| v0.2.9 | v0.1.0, v0.1.1 | diff --git a/wrappers/javascript/aries-askar-shared/package.json b/wrappers/javascript/aries-askar-shared/package.json new file mode 100644 index 00000000..a819ed04 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/package.json @@ -0,0 +1,36 @@ +{ + "name": "@hyperledger/aries-askar-shared", + "version": "0.1.1", + "license": "Apache-2.0", + "description": "Shared library for using Aries Askar with NodeJS and React Native", + "main": "build/index", + "source": "src/index", + "types": "build/index", + "homepage": "https://github.com/hyperledger/aries-askar/tree/main/wrappers/javascript/aries-askar-shared", + "repository": { + "url": "https://github.com/hyperledger/aries-askar", + "type": "git", + "directory": "wrappers/javascript/aries-askar-shared" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "build" + ], + "scripts": { + "build": "yarn run clean && yarn run compile", + "clean": "rimraf -rf ./build", + "compile": "tsc -p tsconfig.build.json", + "prepublishOnly": "yarn run build" + }, + "devDependencies": { + "@types/fast-text-encoding": "^1.0.1", + "prettier": "^2.6.2", + "rimraf": "^3.0.2", + "typescript": "^4.5.5" + }, + "dependencies": { + "fast-text-encoding": "^1.0.3" + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/ariesAskar/AriesAskar.ts b/wrappers/javascript/aries-askar-shared/src/ariesAskar/AriesAskar.ts new file mode 100644 index 00000000..bec3c26a --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/ariesAskar/AriesAskar.ts @@ -0,0 +1,309 @@ +import type { + EntryListHandle, + Jwk, + Key, + KeyEntryListHandle, + LocalKeyHandle, + ScanHandle, + SessionHandle, + StoreHandle, +} from '../crypto' +import type { KeyAlgs, LogLevel, SigAlgs } from '../enums' +import type { AeadParams, EncryptedBuffer } from '../types' + +export type ErrorCode = number + +export type NativeLogCallback = ( + context: unknown, + level: number, + target: string, + message: string, + modulePath: string, + file: string, + line: number +) => void + +export type SetCustomLoggerOptions = { + logLevel: LogLevel + flush?: boolean + enabled?: boolean + logger: NativeLogCallback +} +export type SetMaxLogLevelOptions = { logLevel: number } + +export type EntryListCountOptions = { entryListHandle: EntryListHandle } +export type EntryListFreeOptions = { entryListHandle: EntryListHandle } +export type EntryListGetCategoryOptions = { entryListHandle: EntryListHandle; index: number } +export type EntryListGetNameOptions = { entryListHandle: EntryListHandle; index: number } +export type EntryListGetTagsOptions = { entryListHandle: EntryListHandle; index: number } +export type EntryListGetValueOptions = { entryListHandle: EntryListHandle; index: number } + +export type KeyAeadDecryptOptions = { + localKeyHandle: LocalKeyHandle + ciphertext: Uint8Array + nonce: Uint8Array + tag?: Uint8Array + aad?: Uint8Array +} +export type KeyAeadEncryptOptions = { + localKeyHandle: LocalKeyHandle + message: Uint8Array + nonce?: Uint8Array + aad?: Uint8Array +} +export type KeyAeadGetPaddingOptions = { localKeyHandle: LocalKeyHandle; msgLen: number } +export type KeyAeadGetParamsOptions = { localKeyHandle: LocalKeyHandle } +export type KeyAeadRandomNonceOptions = { localKeyHandle: LocalKeyHandle } +export type KeyConvertOptions = { localKeyHandle: LocalKeyHandle; algorithm: KeyAlgs } +export type KeyCryptoBoxOptions = { + recipientKey: Key + senderKey: Key + message: Uint8Array + nonce: Uint8Array +} +export type KeyCryptoBoxOpenOptions = { + recipientKey: Key + senderKey: Key + message: Uint8Array + nonce: Uint8Array +} +export type KeyCryptoBoxSealOptions = { localKeyHandle: LocalKeyHandle; message: Uint8Array } +export type KeyCryptoBoxSealOpenOptions = { localKeyHandle: LocalKeyHandle; ciphertext: Uint8Array } +export type KeyDeriveEcdh1puOptions = { + algorithm: KeyAlgs + ephemeralKey: Key + senderKey: Key + recipientKey: Key + algId: Uint8Array + apu: Uint8Array + apv: Uint8Array + ccTag?: Uint8Array + receive: boolean +} +export type KeyDeriveEcdhEsOptions = { + algorithm: KeyAlgs + ephemeralKey: Key + recipientKey: Key + algId: Uint8Array + apu: Uint8Array + apv: Uint8Array + receive: boolean +} +export type KeyEntryListCountOptions = { keyEntryListHandle: KeyEntryListHandle } +export type KeyEntryListFreeOptions = { keyEntryListHandle: KeyEntryListHandle } +export type KeyEntryListGetAlgorithmOptions = { keyEntryListHandle: KeyEntryListHandle; index: number } +export type KeyEntryListGetMetadataOptions = { keyEntryListHandle: KeyEntryListHandle; index: number } +export type KeyEntryListGetNameOptions = { keyEntryListHandle: KeyEntryListHandle; index: number } +export type KeyEntryListGetTagsOptions = { keyEntryListHandle: KeyEntryListHandle; index: number } +export type KeyEntryListLoadLocalOptions = { keyEntryListHandle: KeyEntryListHandle; index: number } +export type KeyFreeOptions = { localKeyHandle: LocalKeyHandle } +export type KeyFromJwkOptions = { jwk: Jwk } +export type KeyFromKeyExchangeOptions = { + algorithm: KeyAlgs + skHandle: LocalKeyHandle + pkHandle: LocalKeyHandle +} +export type KeyFromPublicBytesOptions = { algorithm: KeyAlgs; publicKey: Uint8Array } +export type KeyFromSecretBytesOptions = { algorithm: KeyAlgs; secretKey: Uint8Array } +export type KeyFromSeedOptions = { algorithm: KeyAlgs; seed: Uint8Array; method: string } +export type KeyGenerateOptions = { algorithm: KeyAlgs; ephemeral: boolean } +export type KeyGetAlgorithmOptions = { localKeyHandle: LocalKeyHandle } +export type KeyGetEphemeralOptions = { localKeyHandle: LocalKeyHandle } +export type KeyGetJwkPublicOptions = { localKeyHandle: LocalKeyHandle; algorithm: string } +export type KeyGetJwkSecretOptions = { localKeyHandle: LocalKeyHandle } +export type KeyGetJwkThumbprintOptions = { localKeyHandle: LocalKeyHandle; algorithm: string } +export type KeyGetPublicBytesOptions = { localKeyHandle: LocalKeyHandle } +export type KeyGetSecretBytesOptions = { localKeyHandle: LocalKeyHandle } +export type KeySignMessageOptions = { localKeyHandle: LocalKeyHandle; message: Uint8Array; sigType?: SigAlgs } +export type KeyUnwrapKeyOptions = { + localKeyHandle: LocalKeyHandle + algorithm: KeyAlgs + ciphertext: Uint8Array + nonce?: Uint8Array + tag?: Uint8Array +} +export type KeyVerifySignatureOptions = { + localKeyHandle: LocalKeyHandle + message: Uint8Array + signature: Uint8Array + sigType?: SigAlgs +} +export type KeyWrapKeyOptions = { + localKeyHandle: LocalKeyHandle + other: LocalKeyHandle + nonce?: Uint8Array +} + +export type ScanFreeOptions = { scanHandle: ScanHandle } +export type ScanNextOptions = { scanHandle: ScanHandle } +export type ScanStartOptions = { + storeHandle: StoreHandle + category: string + profile?: string + tagFilter?: Record + offset?: number + limit?: number +} + +export type SessionCloseOptions = { sessionHandle: SessionHandle; commit: boolean } +export type SessionCountOptions = { + sessionHandle: SessionHandle + category: string + tagFilter?: Record +} +export type SessionFetchOptions = { + sessionHandle: SessionHandle + category: string + name: string + forUpdate: boolean +} +export type SessionFetchAllOptions = { + sessionHandle: SessionHandle + category: string + tagFilter?: Record + limit?: number + forUpdate: boolean +} +export type SessionFetchAllKeysOptions = { + sessionHandle: SessionHandle + forUpdate: boolean + algorithm?: string + thumbprint?: string + tagFilter?: Record + limit?: number +} +export type SessionFetchKeyOptions = { sessionHandle: SessionHandle; name: string; forUpdate: boolean } +export type SessionInsertKeyOptions = { + sessionHandle: SessionHandle + localKeyHandle: LocalKeyHandle + name: string + metadata?: string + tags?: Record + expiryMs?: number +} +export type SessionRemoveAllOptions = { + sessionHandle: SessionHandle + category: string + tagFilter?: Record +} +export type SessionRemoveKeyOptions = { sessionHandle: SessionHandle; name: string } +export type SessionStartOptions = { storeHandle: StoreHandle; profile?: string; asTransaction: boolean } +export type SessionUpdateOptions = { + sessionHandle: SessionHandle + operation: number + category: string + name: string + value?: Uint8Array + tags?: Record + expiryMs?: number +} +export type SessionUpdateKeyOptions = { + sessionHandle: SessionHandle + name: string + metadata?: string + tags?: Record + expiryMs?: number +} + +export type StoreCloseOptions = { storeHandle: StoreHandle } +export type StoreCreateProfileOptions = { storeHandle: StoreHandle; profile?: string } +export type StoreGenerateRawKeyOptions = { seed?: Uint8Array } +export type StoreGetProfileNameOptions = { storeHandle: StoreHandle } +export type StoreOpenOptions = { specUri: string; keyMethod?: string; passKey?: string; profile?: string } +export type StoreProvisionOptions = { + specUri: string + keyMethod?: string + passKey?: string + profile?: string + recreate: boolean +} +export type StoreRekeyOptions = { storeHandle: StoreHandle; keyMethod?: string; passKey: string } +export type StoreRemoveOptions = { specUri: string } +export type StoreRemoveProfileOptions = { storeHandle: StoreHandle; profile: string } + +export type MigrateIndySdkOptions = { specUri: string; walletName: string; walletKey: string; kdfLevel: string } + +export type AriesAskar = { + version(): string + getCurrentError(): string + clearCustomLogger(): void + + setCustomLogger(options: SetCustomLoggerOptions): void + setDefaultLogger(): void + setMaxLogLevel(options: SetMaxLogLevelOptions): void + + entryListCount(options: EntryListCountOptions): number + entryListFree(options: EntryListFreeOptions): void + entryListGetCategory(options: EntryListGetCategoryOptions): string + entryListGetName(options: EntryListGetNameOptions): string + entryListGetTags(options: EntryListGetTagsOptions): string | null + entryListGetValue(options: EntryListGetValueOptions): Uint8Array + + keyAeadDecrypt(options: KeyAeadDecryptOptions): Uint8Array + keyAeadEncrypt(options: KeyAeadEncryptOptions): EncryptedBuffer + keyAeadGetPadding(options: KeyAeadGetPaddingOptions): number + keyAeadGetParams(options: KeyAeadGetParamsOptions): AeadParams + keyAeadRandomNonce(options: KeyAeadRandomNonceOptions): Uint8Array + keyConvert(options: KeyConvertOptions): LocalKeyHandle + keyCryptoBox(options: KeyCryptoBoxOptions): Uint8Array + keyCryptoBoxOpen(options: KeyCryptoBoxOpenOptions): Uint8Array + keyCryptoBoxRandomNonce(): Uint8Array + keyCryptoBoxSeal(options: KeyCryptoBoxSealOptions): Uint8Array + keyCryptoBoxSealOpen(options: KeyCryptoBoxSealOpenOptions): Uint8Array + keyDeriveEcdh1pu(options: KeyDeriveEcdh1puOptions): LocalKeyHandle + keyDeriveEcdhEs(options: KeyDeriveEcdhEsOptions): LocalKeyHandle + keyEntryListCount(options: KeyEntryListCountOptions): number + keyEntryListFree(options: KeyEntryListFreeOptions): void + keyEntryListGetAlgorithm(options: KeyEntryListGetAlgorithmOptions): string + keyEntryListGetMetadata(options: KeyEntryListGetMetadataOptions): string | null + keyEntryListGetName(options: KeyEntryListGetNameOptions): string + keyEntryListGetTags(options: KeyEntryListGetTagsOptions): string | null + keyEntryListLoadLocal(options: KeyEntryListLoadLocalOptions): LocalKeyHandle + keyFree(options: KeyFreeOptions): void + keyFromJwk(options: KeyFromJwkOptions): LocalKeyHandle + keyFromKeyExchange(options: KeyFromKeyExchangeOptions): LocalKeyHandle + keyFromPublicBytes(options: KeyFromPublicBytesOptions): LocalKeyHandle + keyFromSecretBytes(options: KeyFromSecretBytesOptions): LocalKeyHandle + keyFromSeed(options: KeyFromSeedOptions): LocalKeyHandle + keyGenerate(options: KeyGenerateOptions): LocalKeyHandle + keyGetAlgorithm(options: KeyGetAlgorithmOptions): string + keyGetEphemeral(options: KeyGetEphemeralOptions): number + keyGetJwkPublic(options: KeyGetJwkPublicOptions): string + keyGetJwkSecret(options: KeyGetJwkSecretOptions): Uint8Array + keyGetJwkThumbprint(options: KeyGetJwkThumbprintOptions): string + keyGetPublicBytes(options: KeyGetPublicBytesOptions): Uint8Array + keyGetSecretBytes(options: KeyGetSecretBytesOptions): Uint8Array + keySignMessage(options: KeySignMessageOptions): Uint8Array + keyUnwrapKey(options: KeyUnwrapKeyOptions): LocalKeyHandle + keyVerifySignature(options: KeyVerifySignatureOptions): boolean + keyWrapKey(options: KeyWrapKeyOptions): EncryptedBuffer + + scanFree(options: ScanFreeOptions): void + scanNext(options: ScanNextOptions): Promise + scanStart(options: ScanStartOptions): Promise + + sessionClose(options: SessionCloseOptions): Promise + sessionCount(options: SessionCountOptions): Promise + sessionFetch(options: SessionFetchOptions): Promise + sessionFetchAll(options: SessionFetchAllOptions): Promise + sessionFetchAllKeys(options: SessionFetchAllKeysOptions): Promise + sessionFetchKey(options: SessionFetchKeyOptions): Promise + sessionInsertKey(options: SessionInsertKeyOptions): Promise + sessionRemoveAll(options: SessionRemoveAllOptions): Promise + sessionRemoveKey(options: SessionRemoveKeyOptions): Promise + sessionStart(options: SessionStartOptions): Promise + sessionUpdate(options: SessionUpdateOptions): Promise + sessionUpdateKey(options: SessionUpdateKeyOptions): Promise + + storeClose(options: StoreCloseOptions): Promise + storeCreateProfile(options: StoreCreateProfileOptions): Promise + storeGenerateRawKey(options: StoreGenerateRawKeyOptions): string + storeGetProfileName(options: StoreGetProfileNameOptions): Promise + storeOpen(options: StoreOpenOptions): Promise + storeProvision(options: StoreProvisionOptions): Promise + storeRekey(options: StoreRekeyOptions): Promise + storeRemove(options: StoreRemoveOptions): Promise + storeRemoveProfile(options: StoreRemoveProfileOptions): Promise + + migrateIndySdk(options: MigrateIndySdkOptions): Promise +} diff --git a/wrappers/javascript/aries-askar-shared/src/ariesAskar/index.ts b/wrappers/javascript/aries-askar-shared/src/ariesAskar/index.ts new file mode 100644 index 00000000..6b01402f --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/ariesAskar/index.ts @@ -0,0 +1,2 @@ +export * from './register' +export * from './AriesAskar' diff --git a/wrappers/javascript/aries-askar-shared/src/ariesAskar/register.ts b/wrappers/javascript/aries-askar-shared/src/ariesAskar/register.ts new file mode 100644 index 00000000..2d1799ec --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/ariesAskar/register.ts @@ -0,0 +1,5 @@ +import type { AriesAskar } from './AriesAskar' + +export let ariesAskar: AriesAskar + +export const registerAriesAskar = ({ askar }: { askar: AriesAskar }) => (ariesAskar = askar) diff --git a/wrappers/javascript/aries-askar-shared/src/crypto/CryptoBox.ts b/wrappers/javascript/aries-askar-shared/src/crypto/CryptoBox.ts new file mode 100644 index 00000000..771048eb --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/crypto/CryptoBox.ts @@ -0,0 +1,45 @@ +import type { Key } from './Key' + +import { ariesAskar } from '../ariesAskar' + +export class CryptoBox { + public static randomNonce() { + return ariesAskar.keyCryptoBoxRandomNonce() + } + + public static cryptoBox({ + recipientKey, + senderKey, + message, + nonce, + }: { + recipientKey: Key + senderKey: Key + message: Uint8Array + nonce: Uint8Array + }) { + return ariesAskar.keyCryptoBox({ nonce, message, senderKey, recipientKey }) + } + + public static open({ + recipientKey, + senderKey, + message, + nonce, + }: { + recipientKey: Key + senderKey: Key + message: Uint8Array + nonce: Uint8Array + }) { + return ariesAskar.keyCryptoBoxOpen({ nonce, message, senderKey, recipientKey }) + } + + public static seal({ recipientKey, message }: { recipientKey: Key; message: Uint8Array }) { + return ariesAskar.keyCryptoBoxSeal({ message, localKeyHandle: recipientKey.handle }) + } + + public static sealOpen({ recipientKey, ciphertext }: { recipientKey: Key; ciphertext: Uint8Array }) { + return ariesAskar.keyCryptoBoxSealOpen({ ciphertext, localKeyHandle: recipientKey.handle }) + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/crypto/Ecdh1PU.ts b/wrappers/javascript/aries-askar-shared/src/crypto/Ecdh1PU.ts new file mode 100644 index 00000000..6c2a9b20 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/crypto/Ecdh1PU.ts @@ -0,0 +1,158 @@ +import type { KeyAlgs } from '../enums' + +import { ariesAskar } from '../ariesAskar' + +import { Key } from './Key' + +// Tests +export class Ecdh1PU { + private algId: Uint8Array + private apu: Uint8Array + private apv: Uint8Array + + public constructor({ apv, apu, algId }: { algId: Uint8Array; apu: Uint8Array; apv: Uint8Array }) { + this.algId = algId + this.apu = apu + this.apv = apv + } + + public deriveKey({ + encAlg, + ephemeralKey, + recipientKey, + senderKey, + receive, + ccTag, + }: { + encAlg: KeyAlgs + ephemeralKey: Key + recipientKey: Key + senderKey: Key + receive: boolean + ccTag?: Uint8Array + }): Key { + return new Key( + ariesAskar.keyDeriveEcdh1pu({ + algId: this.algId, + receive, + apv: this.apv, + apu: this.apu, + algorithm: encAlg, + ephemeralKey: ephemeralKey, + recipientKey: recipientKey, + senderKey: senderKey, + ccTag, + }) + ) + } + + public encryptDirect({ + encAlg, + recipientKey, + ephemeralKey, + senderKey, + message, + aad, + nonce, + }: { + encAlg: KeyAlgs + ephemeralKey: Key + recipientKey: Key + senderKey: Key + message: Uint8Array + aad?: Uint8Array + nonce?: Uint8Array + }) { + const derived = this.deriveKey({ encAlg, ephemeralKey, recipientKey, senderKey, receive: false }) + const encryptedBuffer = derived.aeadEncrypt({ message, aad, nonce }) + derived.handle.free() + return encryptedBuffer + } + + public decryptDirect({ + nonce, + encAlg, + recipientKey, + ephemeralKey, + senderKey, + ciphertext, + tag, + aad, + }: { + encAlg: KeyAlgs + ephemeralKey: Key + recipientKey: Key + senderKey: Key + ciphertext: Uint8Array + nonce: Uint8Array + tag: Uint8Array + aad?: Uint8Array + }) { + const derived = this.deriveKey({ encAlg, ephemeralKey, recipientKey, senderKey, receive: true }) + const encryptedBuffer = derived.aeadDecrypt({ tag, nonce, ciphertext, aad }) + derived.handle.free() + return encryptedBuffer + } + + public senderWrapKey({ + wrapAlg, + ephemeralKey, + recipientKey, + senderKey, + cek, + ccTag, + }: { + wrapAlg: KeyAlgs + ephemeralKey: Key + recipientKey: Key + senderKey: Key + cek: Key + ccTag: Uint8Array + }) { + const derived = this.deriveKey({ + encAlg: wrapAlg, + ephemeralKey, + recipientKey, + senderKey, + receive: false, + ccTag, + }) + const encryptedBuffer = derived.wrapKey({ other: cek }) + derived.handle.free() + return encryptedBuffer + } + + public receiverUnwrapKey({ + wrapAlg, + encAlg, + recipientKey, + ephemeralKey, + senderKey, + ciphertext, + nonce, + tag, + ccTag, + }: { + wrapAlg: KeyAlgs + encAlg: KeyAlgs + ephemeralKey: Key + recipientKey: Key + senderKey: Key + ciphertext: Uint8Array + nonce?: Uint8Array + tag?: Uint8Array + ccTag: Uint8Array + }) { + const derived = this.deriveKey({ + encAlg: wrapAlg, + ephemeralKey, + recipientKey, + receive: true, + senderKey, + ccTag, + }) + const encryptedBuffer = derived.unwrapKey({ tag, nonce, ciphertext, algorithm: encAlg }) + derived.handle.free() + return encryptedBuffer + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/crypto/EcdhEs.ts b/wrappers/javascript/aries-askar-shared/src/crypto/EcdhEs.ts new file mode 100644 index 00000000..74bda5a3 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/crypto/EcdhEs.ts @@ -0,0 +1,131 @@ +import type { KeyAlgs } from '../enums' + +import { ariesAskar } from '../ariesAskar' + +import { Jwk } from './Jwk' +import { Key } from './Key' + +// Tests +export class EcdhEs { + private algId: Uint8Array + private apu: Uint8Array + private apv: Uint8Array + + public constructor({ apv, apu, algId }: { algId: Uint8Array; apu: Uint8Array; apv: Uint8Array }) { + this.algId = algId + this.apu = apu + this.apv = apv + } + + private deriveKey({ + encAlg, + ephemeralKey, + recipientKey, + receive, + }: { + encAlg: KeyAlgs + ephemeralKey: Key + recipientKey: Key + receive: boolean + }): Key { + return new Key( + ariesAskar.keyDeriveEcdhEs({ + algId: this.algId, + receive, + apv: this.apv, + apu: this.apu, + algorithm: encAlg, + ephemeralKey, + recipientKey, + }) + ) + } + + public encryptDirect({ + encAlg, + recipientKey, + ephemeralKey, + message, + aad, + nonce, + }: { + encAlg: KeyAlgs + ephemeralKey: Key | Jwk + recipientKey: Key | Jwk + message: Uint8Array + aad?: Uint8Array + nonce?: Uint8Array + }) { + const eKey = ephemeralKey instanceof Jwk ? Key.fromJwk({ jwk: ephemeralKey }) : ephemeralKey + const rKey = recipientKey instanceof Jwk ? Key.fromJwk({ jwk: recipientKey }) : recipientKey + const derived = this.deriveKey({ encAlg, ephemeralKey: eKey, recipientKey: rKey, receive: false }) + const encryptedBuffer = derived.aeadEncrypt({ message, aad, nonce }) + derived.handle.free() + return encryptedBuffer + } + + public decryptDirect({ + nonce, + encAlg, + recipientKey, + ciphertext, + ephemeralKey, + tag, + aad, + }: { + encAlg: KeyAlgs + ephemeralKey: Key | Jwk + recipientKey: Key | Jwk + ciphertext: Uint8Array + nonce: Uint8Array + tag: Uint8Array + aad?: Uint8Array + }) { + const eKey = ephemeralKey instanceof Jwk ? Key.fromJwk({ jwk: ephemeralKey }) : ephemeralKey + const rKey = recipientKey instanceof Jwk ? Key.fromJwk({ jwk: recipientKey }) : recipientKey + const derived = this.deriveKey({ encAlg, ephemeralKey: eKey, recipientKey: rKey, receive: true }) + const encryptedBuffer = derived.aeadDecrypt({ tag, nonce, ciphertext, aad }) + derived.handle.free() + return encryptedBuffer + } + + public senderWrapKey({ + wrapAlg, + ephemeralKey, + recipientKey, + cek, + }: { + wrapAlg: KeyAlgs + ephemeralKey: Key + recipientKey: Key + cek: Key + }) { + const derived = this.deriveKey({ encAlg: wrapAlg, ephemeralKey, recipientKey, receive: false }) + const encryptedBuffer = derived.wrapKey({ other: cek }) + derived.handle.free() + return encryptedBuffer + } + + public receiverUnwrapKey({ + recipientKey, + wrapAlg, + ephemeralKey, + encAlg, + ciphertext, + nonce, + tag, + }: { + wrapAlg: KeyAlgs + encAlg: KeyAlgs + ephemeralKey: Key + recipientKey: Key + ciphertext: Uint8Array + nonce?: Uint8Array + tag?: Uint8Array + }) { + const derived = this.deriveKey({ encAlg: wrapAlg, ephemeralKey, recipientKey, receive: true }) + const encryptedBuffer = derived.unwrapKey({ tag, nonce, ciphertext, algorithm: encAlg }) + derived.handle.free() + return encryptedBuffer + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/crypto/Jwk.ts b/wrappers/javascript/aries-askar-shared/src/crypto/Jwk.ts new file mode 100644 index 00000000..fb6f28d7 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/crypto/Jwk.ts @@ -0,0 +1,41 @@ +export type JwkProps = { + kty: string + crv: string + x: string + d?: string + y?: string +} + +export class Jwk { + public kty: string + public crv: string + public x: string + public d?: string + public y?: string + + public constructor({ kty, crv, x, d, y }: JwkProps) { + this.kty = kty + this.crv = crv + this.x = x + this.d = d + this.y = y + } + + public static fromJson(jwk: JwkProps) { + return new Jwk(jwk) + } + + public static fromString(str: string) { + return new Jwk(JSON.parse(str) as JwkProps) + } + + public toUint8Array() { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const encoder = new TextEncoder() + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + const encoded = encoder.encode(JSON.stringify(this)) as Uint8Array + return encoded + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/crypto/Key.ts b/wrappers/javascript/aries-askar-shared/src/crypto/Key.ts new file mode 100644 index 00000000..7411c360 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/crypto/Key.ts @@ -0,0 +1,122 @@ +import type { LocalKeyHandle } from './handles' +import type { KeyAlgs, SigAlgs } from '../enums' + +import { ariesAskar } from '../ariesAskar' +import { KeyMethod, keyAlgFromString } from '../enums' + +import { Jwk } from './Jwk' + +export class Key { + private localKeyHandle: LocalKeyHandle + + public constructor(handle: LocalKeyHandle) { + this.localKeyHandle = handle + } + + public static generate(algorithm: KeyAlgs, ephemeral = false) { + return new Key(ariesAskar.keyGenerate({ algorithm, ephemeral })) + } + + public static fromSeed({ + method = KeyMethod.None, + algorithm, + seed, + }: { + algorithm: KeyAlgs + seed: Uint8Array + method?: KeyMethod + }) { + return new Key(ariesAskar.keyFromSeed({ algorithm, method, seed })) + } + + public static fromSecretBytes(options: { algorithm: KeyAlgs; secretKey: Uint8Array }) { + return new Key(ariesAskar.keyFromSecretBytes(options)) + } + + public static fromPublicBytes(options: { algorithm: KeyAlgs; publicKey: Uint8Array }) { + return new Key(ariesAskar.keyFromPublicBytes(options)) + } + + public static fromJwk(options: { jwk: Jwk }) { + return new Key(ariesAskar.keyFromJwk(options)) + } + + public convertkey(options: { algorithm: KeyAlgs }) { + return new Key(ariesAskar.keyConvert({ localKeyHandle: this.handle, ...options })) + } + + public keyFromKeyExchange({ algorithm, publicKey }: { algorithm: KeyAlgs; publicKey: Key }) { + return new Key(ariesAskar.keyFromKeyExchange({ skHandle: this.handle, pkHandle: publicKey.handle, algorithm })) + } + + public get handle() { + return this.localKeyHandle + } + + public get algorithm() { + const alg = ariesAskar.keyGetAlgorithm({ localKeyHandle: this.handle }) + return keyAlgFromString(alg) + } + + public get ephemeral() { + return Boolean(ariesAskar.keyGetEphemeral({ localKeyHandle: this.handle })) + } + + public get publicBytes() { + return ariesAskar.keyGetPublicBytes({ localKeyHandle: this.handle }) + } + + public get secretBytes() { + return ariesAskar.keyGetSecretBytes({ localKeyHandle: this.handle }) + } + + public get jwkPublic(): Jwk { + return Jwk.fromString(ariesAskar.keyGetJwkPublic({ localKeyHandle: this.handle, algorithm: this.algorithm })) + } + + public get jwkSecret() { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const decoder = new TextDecoder() + const secretBytes = ariesAskar.keyGetJwkSecret({ localKeyHandle: this.handle }) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + return Jwk.fromString(decoder.decode(secretBytes)) + } + + public get jwkThumbprint() { + return ariesAskar.keyGetJwkThumbprint({ localKeyHandle: this.handle, algorithm: this.algorithm }) + } + + public get aeadParams() { + return ariesAskar.keyAeadGetParams({ localKeyHandle: this.handle }) + } + + public get aeadRandomNonce() { + return ariesAskar.keyAeadRandomNonce({ localKeyHandle: this.handle }) + } + + public aeadEncrypt(options: { message: Uint8Array; nonce?: Uint8Array; aad?: Uint8Array }) { + return ariesAskar.keyAeadEncrypt({ localKeyHandle: this.handle, ...options }) + } + + public aeadDecrypt(options: { ciphertext: Uint8Array; nonce: Uint8Array; tag?: Uint8Array; aad?: Uint8Array }) { + return ariesAskar.keyAeadDecrypt({ localKeyHandle: this.handle, ...options }) + } + + public signMessage(options: { message: Uint8Array; sigType?: SigAlgs }) { + return ariesAskar.keySignMessage({ localKeyHandle: this.handle, ...options }) + } + + public verifySignature(options: { message: Uint8Array; signature: Uint8Array; sigType?: SigAlgs }) { + return ariesAskar.keyVerifySignature({ localKeyHandle: this.handle, ...options }) + } + + public wrapKey({ other, nonce }: { other: Key; nonce?: Uint8Array }) { + return ariesAskar.keyWrapKey({ localKeyHandle: this.handle, other: other.handle, nonce }) + } + + public unwrapKey(options: { algorithm: KeyAlgs; tag?: Uint8Array; ciphertext: Uint8Array; nonce?: Uint8Array }) { + return new Key(ariesAskar.keyUnwrapKey({ localKeyHandle: this.handle, ...options })) + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/crypto/handles.ts b/wrappers/javascript/aries-askar-shared/src/crypto/handles.ts new file mode 100644 index 00000000..c781f27b --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/crypto/handles.ts @@ -0,0 +1,157 @@ +import { ariesAskar } from '../ariesAskar' +import { AriesAskarError } from '../error' + +type ArcHandleType = Uint8Array | string | null + +export class ArcHandle { + public handle: Uint8Array | string + + public constructor(handle: Uint8Array | string) { + if (handle === '0') { + throw AriesAskarError.customError({ + message: 'Invalid handle. This means that the function call succeeded but none was found.', + }) + } + this.handle = handle + } + + public static fromHandle(handle: ArcHandleType) { + return fromPointerHandle(this, handle) + } +} + +export class StoreHandle { + public handle: number + + public constructor(handle: number) { + this.handle = handle + } + + public async close() { + await ariesAskar.storeClose({ storeHandle: this }) + } + + public static fromHandle(handle: number | null) { + return fromSequenceHandle(this, handle) + } +} + +export class ScanHandle { + public handle: number + + public constructor(handle: number) { + this.handle = handle + } + + public free() { + ariesAskar.scanFree({ scanHandle: this }) + } + + public static fromHandle(handle: number | null) { + return fromSequenceHandle(this, handle) + } +} + +export class SessionHandle { + public handle: number + + public constructor(handle: number) { + this.handle = handle + } + + public async close(commit: boolean) { + await ariesAskar.sessionClose({ commit, sessionHandle: this }) + } + + public static fromHandle(handle: number | null) { + return fromSequenceHandle(this, handle) + } +} + +export class EntryListHandle extends ArcHandle { + public getCategory(index: number) { + return ariesAskar.entryListGetCategory({ index, entryListHandle: this }) + } + + public getName(index: number) { + return ariesAskar.entryListGetName({ index, entryListHandle: this }) + } + + public getValue(index: number) { + return ariesAskar.entryListGetValue({ index, entryListHandle: this }) + } + + public getTags(index: number) { + return ariesAskar.entryListGetTags({ index, entryListHandle: this }) + } + + public free() { + ariesAskar.entryListFree({ entryListHandle: this }) + } + + public static fromHandle(handle: ArcHandleType) { + return fromPointerHandle(this, handle) + } +} + +export class KeyEntryListHandle extends ArcHandle { + public getAlgorithm(index: number) { + return ariesAskar.keyEntryListGetAlgorithm({ index, keyEntryListHandle: this }) + } + + public getName(index: number) { + return ariesAskar.keyEntryListGetName({ index, keyEntryListHandle: this }) + } + + public getTags(index: number) { + return ariesAskar.keyEntryListGetTags({ index, keyEntryListHandle: this }) + } + + public getMetadata(index: number) { + return ariesAskar.keyEntryListGetMetadata({ index, keyEntryListHandle: this }) + } + + public loadKey(index: number) { + return ariesAskar.keyEntryListLoadLocal({ index, keyEntryListHandle: this }) + } + + public free() { + ariesAskar.keyEntryListFree({ keyEntryListHandle: this }) + } + + public static fromHandle(handle: ArcHandleType) { + return fromPointerHandle(this, handle) + } +} + +export class LocalKeyHandle extends ArcHandle { + public free() { + ariesAskar.keyFree({ localKeyHandle: this }) + } + + public static fromHandle(handle: ArcHandleType) { + return fromPointerHandle(this, handle) + } +} + +/** + * Instantiate an handle class based on a received handle. If the handle has a value + * of null, the handle class won't be instantiated but rather null will be returned. + */ +function fromPointerHandle( + HandleClass: HC, + handle: H +): H extends null ? null : InstanceType { + return (handle ? (new HandleClass(handle) as InstanceType) : null) as H extends null ? null : InstanceType +} + +function fromSequenceHandle< + HC extends typeof StoreHandle | typeof ScanHandle | typeof SessionHandle, + H extends number | null +>(HandleClass: HC, handle: H): InstanceType { + if (handle === null) { + throw AriesAskarError.customError({ message: 'Invalid handle' }) + } + + return new HandleClass(handle) as InstanceType +} diff --git a/wrappers/javascript/aries-askar-shared/src/crypto/index.ts b/wrappers/javascript/aries-askar-shared/src/crypto/index.ts new file mode 100644 index 00000000..d1bcf598 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/crypto/index.ts @@ -0,0 +1,6 @@ +export * from './handles' +export * from './Key' +export * from './EcdhEs' +export * from './Ecdh1PU' +export * from './CryptoBox' +export * from './Jwk' diff --git a/wrappers/javascript/aries-askar-shared/src/enums/EntryOperation.ts b/wrappers/javascript/aries-askar-shared/src/enums/EntryOperation.ts new file mode 100644 index 00000000..5abdff30 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/enums/EntryOperation.ts @@ -0,0 +1,5 @@ +export enum EntryOperation { + Insert, + Replace, + Remove, +} diff --git a/wrappers/javascript/aries-askar-shared/src/enums/KeyAlgs.ts b/wrappers/javascript/aries-askar-shared/src/enums/KeyAlgs.ts new file mode 100644 index 00000000..1c6ee0fb --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/enums/KeyAlgs.ts @@ -0,0 +1,34 @@ +import { AriesAskarError } from '../error' + +export enum KeyAlgs { + AesA128Gcm = 'a128gcm', + AesA256Gcm = 'a256gcm', + AesA128CbcHs256 = 'a128cbchs256', + AesA256CbcHs512 = 'a256cbchs512', + AesA128Kw = 'a128kw', + AesA256Kw = 'a256kw', + Bls12381G1 = 'bls12381g1', + Bls12381G2 = 'bls12381g2', + Bls12381G1G2 = 'bls12381g1g2', + Chacha20C20P = 'c20p', + Chacha20XC20P = 'xc20p', + Ed25519 = 'ed25519', + X25519 = 'x25519', + EcSecp256k1 = 'k256', + EcSecp256r1 = 'p256', + EcSecp384r1 = 'p384', +} + +export const keyAlgFromString = (alg: string): KeyAlgs => { + const keyAlg = Object.entries(KeyAlgs).find(([, value]) => value === alg) + if (keyAlg) return keyAlg[1] + + throw AriesAskarError.customError({ message: `Algorithm: ${alg} is not supported!` }) +} + +export const keyAlgToString = (alg: KeyAlgs): string => { + const keyAlg = Object.entries(KeyAlgs).find(([key]) => key === alg) + if (keyAlg) return keyAlg[0] + + throw AriesAskarError.customError({ message: `Algorithm: ${alg} is not supported!` }) +} diff --git a/wrappers/javascript/aries-askar-shared/src/enums/KeyMethod.ts b/wrappers/javascript/aries-askar-shared/src/enums/KeyMethod.ts new file mode 100644 index 00000000..ae07d0b8 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/enums/KeyMethod.ts @@ -0,0 +1,4 @@ +export enum KeyMethod { + None = '', + BlsKeygen = 'bls_keygen', +} diff --git a/wrappers/javascript/aries-askar-shared/src/enums/LogLevel.ts b/wrappers/javascript/aries-askar-shared/src/enums/LogLevel.ts new file mode 100644 index 00000000..7abb2ba9 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/enums/LogLevel.ts @@ -0,0 +1,9 @@ +export enum LogLevel { + RUST_LOG = -1, + Off = 0, + Error = 1, + Warn = 2, + Info = 3, + Debug = 4, + Trace = 5, +} diff --git a/wrappers/javascript/aries-askar-shared/src/enums/SigAlgs.ts b/wrappers/javascript/aries-askar-shared/src/enums/SigAlgs.ts new file mode 100644 index 00000000..261b06e0 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/enums/SigAlgs.ts @@ -0,0 +1,5 @@ +export enum SigAlgs { + EdDSA = 'eddsa', + ES256 = 'es256', + ES256K = 'es256k', +} diff --git a/wrappers/javascript/aries-askar-shared/src/enums/index.ts b/wrappers/javascript/aries-askar-shared/src/enums/index.ts new file mode 100644 index 00000000..04644176 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/enums/index.ts @@ -0,0 +1,4 @@ +export * from './KeyAlgs' +export * from './KeyMethod' +export * from './SigAlgs' +export * from './LogLevel' diff --git a/wrappers/javascript/aries-askar-shared/src/error.ts b/wrappers/javascript/aries-askar-shared/src/error.ts new file mode 100644 index 00000000..efb042f1 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/error.ts @@ -0,0 +1,28 @@ +export type AriesAskarErrorObject = { + code: number + extra?: string + message: string +} + +export class AriesAskarError extends Error { + public readonly code: number + public readonly extra?: string + + public constructor({ code, message, extra }: AriesAskarErrorObject) { + super(message) + this.code = code + this.extra = extra + } + + public static customError({ message }: { message: string }) { + return new AriesAskarError({ message, code: 100 }) + } +} + +export function handleInvalidNullResponse(response: T): Exclude { + if (response === null) { + throw AriesAskarError.customError({ message: 'Invalid response. Expected value but received null pointer' }) + } + + return response as Exclude +} diff --git a/wrappers/javascript/aries-askar-shared/src/index.ts b/wrappers/javascript/aries-askar-shared/src/index.ts new file mode 100644 index 00000000..47697a83 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/index.ts @@ -0,0 +1,9 @@ +import 'fast-text-encoding' + +export * from './error' +export * from './ariesAskar' +export * from './types' +export * from './crypto' +export * from './enums' +export * from './store' +export * from './migration' diff --git a/wrappers/javascript/aries-askar-shared/src/migration.ts b/wrappers/javascript/aries-askar-shared/src/migration.ts new file mode 100644 index 00000000..7d85faa0 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/migration.ts @@ -0,0 +1,14 @@ +import { ariesAskar } from './ariesAskar' + +type MigrationOptions = { + walletName: string + walletKey: string + specUri: string + kdfLevel: string +} + +export class Migration { + public static async migrate(options: MigrationOptions): Promise { + await ariesAskar.migrateIndySdk(options) + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/Entry.ts b/wrappers/javascript/aries-askar-shared/src/store/Entry.ts new file mode 100644 index 00000000..cf05c7fd --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/Entry.ts @@ -0,0 +1,55 @@ +import type { EntryListHandle } from '../crypto' + +export type EntryObject = { + name: string + value: Record | string + tags: Record + category: string +} + +export class Entry { + private _list: EntryListHandle + private _pos: number + + public constructor({ list, position }: { list: EntryListHandle; position: number }) { + this._list = list + this._pos = position + } + + public get category() { + return this._list.getCategory(this._pos) + } + + public get name() { + return this._list.getName(this._pos) + } + + public get value(): string { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const decoder = new TextDecoder() + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + return decoder.decode(this.rawValue) + } + + private get rawValue() { + return this._list.getValue(this._pos) + } + + public get tags() { + const tags = this._list.getTags(this._pos) + + if (!tags) return {} + return JSON.parse(tags) as Record + } + + public toJson(shouldParseValueToJson = false): EntryObject { + return { + name: this.name, + value: shouldParseValueToJson ? (JSON.parse(this.value) as Record) : this.value, + tags: this.tags, + category: this.category, + } + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/EntryList.ts b/wrappers/javascript/aries-askar-shared/src/store/EntryList.ts new file mode 100644 index 00000000..6b484c2c --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/EntryList.ts @@ -0,0 +1,47 @@ +import type { EntryObject } from '.' +import type { EntryListHandle } from '../crypto' + +import { ariesAskar } from '../ariesAskar' + +import { Entry } from './Entry' + +export class EntryList { + private _handle: EntryListHandle + private _length = 0 + + public constructor({ handle, length }: { handle: EntryListHandle; length?: number }) { + this._handle = handle + this._length = length || ariesAskar.entryListCount({ entryListHandle: handle }) + } + + public get handle() { + return this._handle + } + + public get length() { + return this._length + } + + public getEntryByIndex(index: number) { + return new Entry({ list: this.handle, position: index }) + } + + private forEach(cb: (entry: Entry, index?: number) => unknown) { + for (let i = 0; i < this.length; i++) { + cb(this.getEntryByIndex(i), i) + } + } + + public find(cb: (entry: Entry, index?: number) => boolean): Entry | undefined { + for (let i = 0; i < this.length; i++) { + const item = this.getEntryByIndex(i) + if (cb(item)) return item + } + } + + public toArray(valuesAreJson?: boolean): Array { + const list: Array = [] + this.forEach((entry) => list.push(entry.toJson(valuesAreJson))) + return list + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/KeyEntry.ts b/wrappers/javascript/aries-askar-shared/src/store/KeyEntry.ts new file mode 100644 index 00000000..8d90f9bf --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/KeyEntry.ts @@ -0,0 +1,56 @@ +import type { KeyEntryListHandle } from '../crypto' + +import { Key } from '../crypto' + +export type KeyEntryObject = { + algorithm: string + name: string + metadata: string | null + tags: Record + key: Key +} + +export class KeyEntry { + private _list: KeyEntryListHandle + private _pos: number + + public constructor({ list, pos }: { list: KeyEntryListHandle; pos: number }) { + this._list = list + this._pos = pos + } + + public get algorithm() { + return this._list.getAlgorithm(this._pos) + } + + public get name() { + return this._list.getName(this._pos) + } + + public get metadata() { + const metadata = this._list.getMetadata(this._pos) + + return metadata + } + + public get tags() { + const tags = this._list.getTags(this._pos) + + if (!tags) return {} + return JSON.parse(tags) as Record + } + + public get key() { + return new Key(this._list.loadKey(this._pos)) + } + + public toJson(): KeyEntryObject { + return { + algorithm: this.algorithm, + name: this.name, + metadata: this.metadata, + tags: this.tags, + key: this.key, + } + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/KeyEntryList.ts b/wrappers/javascript/aries-askar-shared/src/store/KeyEntryList.ts new file mode 100644 index 00000000..1847fdef --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/KeyEntryList.ts @@ -0,0 +1,40 @@ +import type { KeyEntryObject } from '.' +import type { KeyEntryListHandle } from '../crypto' + +import { ariesAskar } from '../ariesAskar' + +import { KeyEntry } from './KeyEntry' + +export class KeyEntryList { + private _handle: KeyEntryListHandle + private _len = 0 + + public constructor({ handle }: { handle: KeyEntryListHandle }) { + this._handle = handle + this._len = ariesAskar.keyEntryListCount({ keyEntryListHandle: handle }) + } + + public get handle() { + return this._handle + } + + public get length() { + return this._len + } + + public getEntryByIndex(index: number) { + return new KeyEntry({ list: this.handle, pos: index }) + } + + public forEach(cb: (entry: KeyEntry, index?: number) => unknown) { + for (let i = 0; i < this.length; i++) { + cb(this.getEntryByIndex(i), i) + } + } + + public toArray(): Array { + const list: Array = [] + this.forEach((key) => list.push(key.toJson())) + return list + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/OpenSession.ts b/wrappers/javascript/aries-askar-shared/src/store/OpenSession.ts new file mode 100644 index 00000000..62c6f052 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/OpenSession.ts @@ -0,0 +1,31 @@ +import type { StoreHandle } from '../crypto' + +import { ariesAskar } from '../ariesAskar' +import { AriesAskarError } from '../error' + +import { Session } from './Session' + +export class OpenSession { + private store: StoreHandle + private profile?: string + private isTxn: boolean + // TODO: implement session + private session?: Session + + public constructor({ store, isTxn, profile }: { store: StoreHandle; profile?: string; isTxn: boolean }) { + this.store = store + this.isTxn = isTxn + this.profile = profile + } + + public async open() { + if (!this.store) throw AriesAskarError.customError({ message: 'Cannot start session from closed store' }) + if (this.session) throw AriesAskarError.customError({ message: 'Session already opened' }) + const sessionHandle = await ariesAskar.sessionStart({ + profile: this.profile, + asTransaction: this.isTxn, + storeHandle: this.store, + }) + return new Session({ isTxn: this.isTxn, handle: sessionHandle }) + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/Scan.ts b/wrappers/javascript/aries-askar-shared/src/store/Scan.ts new file mode 100644 index 00000000..e01ab9c6 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/Scan.ts @@ -0,0 +1,88 @@ +import type { Entry, EntryObject } from './Entry' +import type { Store } from './Store' +import type { EntryListHandle, ScanHandle } from '../crypto' + +import { ariesAskar } from '../ariesAskar' +import { AriesAskarError } from '../error' + +import { EntryList } from './EntryList' + +export class Scan { + private _handle?: ScanHandle + private _listHandle?: EntryListHandle + private store: Store + private profile?: string + private category: string + private tagFilter?: Record + private offset?: number + private limit?: number + + public constructor({ + category, + limit, + offset, + profile, + tagFilter, + store, + }: { + profile?: string + category: string + tagFilter?: Record + offset?: number + limit?: number + store: Store + }) { + this.category = category + this.profile = profile + this.tagFilter = tagFilter + this.offset = offset + this.limit = limit + this.store = store + } + + public get handle() { + return this._handle + } + + private async forEach(cb: (row: Entry, index?: number) => void) { + if (!this.handle) { + if (!this.store?.handle) throw AriesAskarError.customError({ message: 'Cannot scan from closed store' }) + this._handle = await ariesAskar.scanStart({ + storeHandle: this.store.handle, + limit: this.limit, + offset: this.offset, + tagFilter: this.tagFilter, + profile: this.profile, + category: this.category, + }) + } + + try { + let recordCount = 0 + // Loop while limit not reached (or no limit specified) + while (!this.limit || recordCount < this.limit) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const listHandle = await ariesAskar.scanNext({ scanHandle: this._handle! }) + if (!listHandle) break + + this._listHandle = listHandle + const list = new EntryList({ handle: this._listHandle }) + + recordCount = recordCount + list.length + for (let index = 0; index < list.length; index++) { + const entry = list.getEntryByIndex(index) + cb(entry) + } + } + } finally { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ariesAskar.scanFree({ scanHandle: this._handle! }) + } + } + + public async fetchAll() { + const rows: Array = [] + await this.forEach((row) => rows.push(row.toJson())) + return rows + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/Session.ts b/wrappers/javascript/aries-askar-shared/src/store/Session.ts new file mode 100644 index 00000000..f4dcbd79 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/Session.ts @@ -0,0 +1,287 @@ +import type { Key, SessionHandle } from '../crypto' +import type { KeyAlgs } from '../enums' + +import { ariesAskar } from '../ariesAskar' +import { EntryOperation } from '../enums/EntryOperation' +import { AriesAskarError } from '../error' + +import { Entry } from './Entry' +import { EntryList } from './EntryList' +import { KeyEntryList } from './KeyEntryList' + +export class Session { + private _handle?: SessionHandle + private isTxn: boolean + + public constructor({ handle, isTxn }: { handle?: SessionHandle; isTxn: boolean }) { + this._handle = handle + this.isTxn = isTxn + } + + public get isTransaction() { + return this.isTxn + } + + public get handle() { + return this._handle + } + + public async count({ category, tagFilter }: { category: string; tagFilter?: Record }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot count from closed session' }) + return await ariesAskar.sessionCount({ tagFilter, category, sessionHandle: this.handle }) + } + + public async fetch({ + category, + name, + forUpdate = false, + isJson, + }: { + category: string + name: string + forUpdate?: boolean + isJson?: boolean + }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot fetch from a closed session' }) + + const handle = await ariesAskar.sessionFetch({ forUpdate, name, category, sessionHandle: this.handle }) + if (!handle) return null + + const entry = new Entry({ list: handle, position: 0 }) + const entryObject = entry.toJson(isJson) + + handle.free() + + return entryObject + } + + public async fetchAll({ + category, + forUpdate = false, + limit, + tagFilter, + isJson, + }: { + category: string + tagFilter?: Record + limit?: number + forUpdate?: boolean + isJson?: boolean + }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot fetch all from a closed session' }) + const handle = await ariesAskar.sessionFetchAll({ + forUpdate, + limit, + tagFilter, + sessionHandle: this.handle, + category, + }) + if (!handle) return [] + + const entryList = new EntryList({ handle }) + const entryObjects = entryList.toArray(isJson) + + entryList.handle.free() + + return entryObjects + } + + public async insert({ + category, + name, + expiryMs, + tags, + value, + }: { + category: string + name: string + value: string | Record + tags?: Record + expiryMs?: number + }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot insert with a closed session' }) + const serializedValue = typeof value === 'string' ? value : JSON.stringify(value) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const encoder = new TextEncoder() + + await ariesAskar.sessionUpdate({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + value: new Uint8Array(encoder.encode(serializedValue)), + expiryMs, + tags, + name, + category, + sessionHandle: this.handle, + operation: EntryOperation.Insert, + }) + } + + public async replace({ + category, + name, + expiryMs, + tags, + value, + }: { + category: string + name: string + value: string | Record + tags?: Record + expiryMs?: number + }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot replace with a closed session' }) + const serializedValue = typeof value === 'string' ? value : JSON.stringify(value) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call + const encoder = new TextEncoder() + + await ariesAskar.sessionUpdate({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + value: new Uint8Array(encoder.encode(serializedValue)), + expiryMs, + tags, + name, + category, + sessionHandle: this.handle, + operation: EntryOperation.Replace, + }) + } + + public async remove({ category, name }: { category: string; name: string }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot remove with a closed session' }) + + await ariesAskar.sessionUpdate({ + name, + category, + sessionHandle: this.handle, + operation: EntryOperation.Remove, + }) + } + + public async removeAll({ category, tagFilter }: { category: string; tagFilter?: Record }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot remove all with a closed session' }) + + await ariesAskar.sessionRemoveAll({ + category, + sessionHandle: this.handle, + tagFilter, + }) + } + + public async insertKey({ + name, + key, + expiryMs, + metadata, + tags, + }: { + name: string + key: Key + metadata?: string + tags?: Record + expiryMs?: number + }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot insert a key with a closed session' }) + + await ariesAskar.sessionInsertKey({ + expiryMs, + tags, + metadata, + name, + sessionHandle: this.handle, + localKeyHandle: key.handle, + }) + } + + public async fetchKey({ name, forUpdate = false }: { name: string; forUpdate?: boolean }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot fetch a key with a closed session' }) + + const handle = await ariesAskar.sessionFetchKey({ forUpdate, name, sessionHandle: this.handle }) + if (!handle) return null + + const keyEntryList = new KeyEntryList({ handle }) + const keyEntryObject = keyEntryList.getEntryByIndex(0).toJson() + keyEntryList.handle.free() + + return keyEntryObject + } + + public async fetchAllKeys({ + forUpdate = false, + algorithm, + limit, + tagFilter, + thumbprint, + }: { + algorithm?: KeyAlgs + thumbprint?: string + tagFilter?: Record + limit?: number + forUpdate?: boolean + }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot fetch all keys with a closed session' }) + const handle = await ariesAskar.sessionFetchAllKeys({ + forUpdate, + limit, + tagFilter, + thumbprint, + algorithm, + sessionHandle: this.handle, + }) + if (!handle) return [] + + const keyEntryList = new KeyEntryList({ handle }) + const keyEntryObjects = keyEntryList.toArray() + keyEntryList.handle.free() + + return keyEntryObjects + } + + public async updateKey({ + name, + expiryMs, + metadata, + tags, + }: { + name: string + metadata?: string + tags?: Record + expiryMs?: number + }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot update a key with a closed session' }) + await ariesAskar.sessionUpdateKey({ expiryMs, tags, metadata, name, sessionHandle: this.handle }) + } + + public async removeKey({ name }: { name: string }) { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot remove a key with a closed session' }) + await ariesAskar.sessionRemoveKey({ name, sessionHandle: this.handle }) + } + + /** + * @note also closes the session + */ + public async commit() { + if (!this.isTxn) throw AriesAskarError.customError({ message: 'Session is not a transaction' }) + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot commit a closed session' }) + await this.handle.close(true) + this._handle = undefined + } + + public async rollback() { + if (!this.isTxn) throw AriesAskarError.customError({ message: 'Session is not a transaction' }) + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot rollback a closed session' }) + await this.handle.close(false) + this._handle = undefined + } + + public async close() { + if (!this.handle) throw AriesAskarError.customError({ message: 'Cannot close a closed session' }) + await this.handle.close(false) + this._handle = undefined + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/Store.ts b/wrappers/javascript/aries-askar-shared/src/store/Store.ts new file mode 100644 index 00000000..bdcd1f86 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/Store.ts @@ -0,0 +1,115 @@ +import type { StoreKeyMethod } from './StoreKeyMethod' +import type { StoreHandle } from '../crypto' + +import { ariesAskar } from '../ariesAskar' + +import { OpenSession } from './OpenSession' +import { Scan } from './Scan' + +export class Store { + private _handle: StoreHandle + private _opener?: OpenSession + private _uri: string + + public constructor({ handle, uri }: { handle: StoreHandle; uri: string }) { + this._handle = handle + this._uri = uri + } + + public get handle() { + return this._handle + } + + public static generateRawKey(seed?: Uint8Array) { + return ariesAskar.storeGenerateRawKey({ seed }) + } + + public get uri() { + return this._uri + } + + public async createProfile(name?: string) { + return ariesAskar.storeCreateProfile({ storeHandle: this.handle, profile: name }) + } + + public async removeProfile(name: string) { + return await ariesAskar.storeRemoveProfile({ profile: name, storeHandle: this.handle }) + } + + public async rekey({ keyMethod, passKey }: { keyMethod?: StoreKeyMethod; passKey: string }) { + return await ariesAskar.storeRekey({ keyMethod: keyMethod?.toUri(), passKey, storeHandle: this.handle }) + } + + public static async provision({ + uri, + recreate, + keyMethod, + passKey, + profile, + }: { + uri: string + keyMethod?: StoreKeyMethod + passKey?: string + profile?: string + recreate: boolean + }) { + const handle = await ariesAskar.storeProvision({ + specUri: uri, + keyMethod: keyMethod?.toUri(), + profile, + passKey, + recreate, + }) + return new Store({ handle, uri }) + } + + public static async open({ + uri, + keyMethod, + passKey, + profile, + }: { + uri: string + keyMethod?: StoreKeyMethod + passKey?: string + profile?: string + }) { + const handle = await ariesAskar.storeOpen({ profile, passKey, keyMethod: keyMethod?.toUri(), specUri: uri }) + return new Store({ uri, handle }) + } + + public async close(remove = false) { + this._opener = undefined + + if (this.handle) await this.handle.close() + + return remove ? await Store.remove(this.uri) : false + } + + public static async remove(uri: string) { + return await ariesAskar.storeRemove({ specUri: uri }) + } + + public session(profile?: string) { + return new OpenSession({ store: this.handle, profile, isTxn: false }) + } + + public transaction(profile?: string) { + return new OpenSession({ store: this.handle, profile, isTxn: true }) + } + + public async openSession(isTransaction = false) { + this._opener ??= new OpenSession({ store: this.handle, isTxn: isTransaction }) + return await this._opener.open() + } + + public scan(options: { + category: string + tagFilter?: Record + offset?: number + limit?: number + profile?: string + }) { + return new Scan({ ...options, store: this }) + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/StoreKeyMethod.ts b/wrappers/javascript/aries-askar-shared/src/store/StoreKeyMethod.ts new file mode 100644 index 00000000..c8acf73a --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/StoreKeyMethod.ts @@ -0,0 +1,18 @@ +export enum KdfMethod { + Raw = 'raw', + None = 'none', + Argon2IMod = 'kdf:argon2i:mod', + Argon2IInt = 'kdf:argon2i:int', +} + +export class StoreKeyMethod { + private method: KdfMethod + + public constructor(method: KdfMethod) { + this.method = method + } + + public toUri() { + return this.method.toString() + } +} diff --git a/wrappers/javascript/aries-askar-shared/src/store/index.ts b/wrappers/javascript/aries-askar-shared/src/store/index.ts new file mode 100644 index 00000000..7a69f4d5 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/store/index.ts @@ -0,0 +1,9 @@ +export * from './Entry' +export * from './EntryList' +export * from './KeyEntry' +export * from './KeyEntryList' +export * from './Scan' +export * from './Store' +export * from './OpenSession' +export * from './Session' +export * from './StoreKeyMethod' diff --git a/wrappers/javascript/aries-askar-shared/src/types.ts b/wrappers/javascript/aries-askar-shared/src/types.ts new file mode 100644 index 00000000..e54eb048 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/src/types.ts @@ -0,0 +1,61 @@ +export type EncryptedBufferOptions = { + buffer: Uint8Array + tagPos: number + noncePos: number +} + +export type AeadParamsOptions = { + nonceLength: number + tagLength: number +} + +export class EncryptedBuffer { + private buffer: Uint8Array + private tagPos: number + private noncePos: number + + public constructor({ noncePos, tagPos, buffer }: EncryptedBufferOptions) { + this.buffer = buffer + this.tagPos = tagPos + this.noncePos = noncePos + } + + public get ciphertextWithTag() { + const p = this.noncePos + return this.buffer.slice(0, p) + } + + public get ciphertext() { + const p = this.tagPos + return this.buffer.slice(0, p) + } + + public get nonce() { + const p = this.noncePos + return this.buffer.slice(p) + } + + public get tag() { + const p1 = this.tagPos + const p2 = this.noncePos + return this.buffer.slice(p1, p2) + } + + public get parts() { + return { + ciphertext: this.ciphertext, + tag: this.tag, + nonce: this.nonce, + } + } +} + +export class AeadParams { + public nonceLength: number + public tagLength: number + + public constructor({ nonceLength, tagLength }: AeadParamsOptions) { + this.nonceLength = nonceLength + this.tagLength = tagLength + } +} diff --git a/wrappers/javascript/aries-askar-shared/tsconfig.build.json b/wrappers/javascript/aries-askar-shared/tsconfig.build.json new file mode 100644 index 00000000..e36cfe29 --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.build.json", + "compilerOptions": { + "outDir": "./build" + }, + "include": ["src/**/*"] +} diff --git a/wrappers/javascript/aries-askar-shared/tsconfig.json b/wrappers/javascript/aries-askar-shared/tsconfig.json new file mode 100644 index 00000000..3c43903c --- /dev/null +++ b/wrappers/javascript/aries-askar-shared/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json" +} diff --git a/wrappers/javascript/jest.config.base.ts b/wrappers/javascript/jest.config.base.ts new file mode 100644 index 00000000..c0d4f696 --- /dev/null +++ b/wrappers/javascript/jest.config.base.ts @@ -0,0 +1,19 @@ +import type { Config } from '@jest/types' + +const config: Config.InitialOptions = { + preset: 'ts-jest', + testEnvironment: 'node', + verbose: true, + testMatch: ['**/?(*.)+(spec|test).[tj]s?(x)'], + moduleNameMapper: { + '@hyperledger/aries-askar-shared': ['/../aries-askar-shared/src'], + '@hyperledger/aries-askar-nodejs': ['/src'], + }, + globals: { + 'ts-jest': { + isolatedModules: true, + }, + }, +} + +export default config diff --git a/wrappers/javascript/jest.config.ts b/wrappers/javascript/jest.config.ts new file mode 100644 index 00000000..4af805f2 --- /dev/null +++ b/wrappers/javascript/jest.config.ts @@ -0,0 +1,11 @@ +import type { Config } from '@jest/types' + +import base from './jest.config.base' + +const config: Config.InitialOptions = { + ...base, + roots: [''], + projects: ['/*/jest.config.ts'], +} + +export default config diff --git a/wrappers/javascript/lerna.json b/wrappers/javascript/lerna.json new file mode 100644 index 00000000..b20f93ff --- /dev/null +++ b/wrappers/javascript/lerna.json @@ -0,0 +1,10 @@ +{ + "version": "0.1.1", + "useWorkspaces": true, + "npmClient": "yarn", + "command": { + "version": { + "allowBranch": ["main"] + } + } +} diff --git a/wrappers/javascript/package.json b/wrappers/javascript/package.json new file mode 100644 index 00000000..b8317329 --- /dev/null +++ b/wrappers/javascript/package.json @@ -0,0 +1,50 @@ +{ + "name": "aries-askar-js", + "private": true, + "license": "Apache-2.0", + "workspaces": { + "packages": [ + "aries-askar-shared", + "aries-askar-react-native", + "aries-askar-nodejs" + ] + }, + "scripts": { + "check-types": "yarn workspaces run tsc --noEmit -p tsconfig.json", + "build": "yarn workspaces run build", + "clean": "yarn workspaces run clean", + "prettier": "prettier --ignore-path .gitignore '**/*.+(js|json|ts|md|yml|yaml)'", + "format": "yarn prettier --write", + "check-format": "yarn prettier --list-different", + "lint": "eslint .", + "validate": "yarn lint && yarn check-types && yarn check-format", + "reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock", + "example": "yarn --cwd react-native-example", + "react-native": "yarn --cwd aries-askar-react-native", + "shared": "yarn --cwd aries-askar-shared", + "nodejs": "yarn --cwd aries-askar-nodejs", + "test": "cross-env yarn nodejs test", + "test:local-build": "cross-env LIB_ARIES_ASKAR_PATH=../../../target/release yarn test", + "set-version": "npx lerna version --exact --no-git-tag-version --no-push --yes" + }, + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/aries-askar", + "directory": "wrappers/javascript" + }, + "devDependencies": { + "@types/eslint": "^8.4.1", + "@typescript-eslint/eslint-plugin": "^5.11.0", + "@typescript-eslint/parser": "^5.11.0", + "cross-env": "^7.0.3", + "eslint": "^8.8.0", + "eslint-config-prettier": "^8.3.0", + "eslint-import-resolver-typescript": "^2.5.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-prettier": "^4.0.0", + "lerna": "^6.4.1", + "prettier": "^2.5.1", + "ts-jest": "^29.0.5", + "typescript": "^4.5.5" + } +} diff --git a/wrappers/javascript/tsconfig.build.json b/wrappers/javascript/tsconfig.build.json new file mode 100644 index 00000000..a37eb494 --- /dev/null +++ b/wrappers/javascript/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2017", + "declaration": true, + "sourceMap": true, + "strict": true, + "noEmitOnError": true, + "lib": ["ESNext"], + "types": [], + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true + }, + "exclude": ["node_modules", "**/build"] +} diff --git a/wrappers/javascript/tsconfig.eslint.json b/wrappers/javascript/tsconfig.eslint.json new file mode 100644 index 00000000..14b10f46 --- /dev/null +++ b/wrappers/javascript/tsconfig.eslint.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@hyperledger/aries-askar-shared": ["aries-askar-shared/src"], + "@hyperledger/aries-askar-react-native": ["aries-askar-react-native/src"], + "@hyperledger/aries-askar-nodejs": ["aries-askar-nodejs/src"] + } + }, + "include": [".eslintrc.js", "jest.config.ts"], + "exclude": ["node_modules", "build"] +} diff --git a/wrappers/javascript/tsconfig.json b/wrappers/javascript/tsconfig.json new file mode 100644 index 00000000..91fed3a1 --- /dev/null +++ b/wrappers/javascript/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@hyperledger/aries-askar-shared": ["aries-askar-shared/src"], + "@hyperledger/aries-askar-react-native": ["aries-askar-react-native/src"], + "@hyperledger/aries-askar-nodejs": ["aries-askar-nodejs/src"] + } + }, + "exclude": ["node_modules", "**/build/**", "build"] +} diff --git a/wrappers/javascript/yarn.lock b/wrappers/javascript/yarn.lock new file mode 100644 index 00000000..222e3fa6 --- /dev/null +++ b/wrappers/javascript/yarn.lock @@ -0,0 +1,10540 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@2060.io/ffi-napi@4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@2060.io/ffi-napi/-/ffi-napi-4.0.5.tgz#52fd369f25cba85319eb5625e2d598925a1713ce" + integrity sha512-CdV4y/gXYI7yzSiSHWvY/JdW06Li57Gdc3ZKzNesXrCQg4IoHL0S+zIxEr6Mbr0fCNVvj1GNSh8Uz9vSQkCXYA== + dependencies: + "@2060.io/ref-napi" "3.0.4" + debug "^4.1.1" + get-uv-event-loop-napi-h "^1.0.5" + node-addon-api "^3.0.0" + node-gyp-build "^4.2.1" + ref-struct-di "^1.1.0" + +"@2060.io/ref-napi@3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@2060.io/ref-napi/-/ref-napi-3.0.4.tgz#6a78093b36e8f4ffeb750f706433869382e73eb1" + integrity sha512-Aqf699E+DKs2xANx8bSkuzXyG9gcZ2iFkAk1kUTA8KbV5BSPQtIcBJexzohSRi9QWDhP4X54NLm7xwGA0UNCdQ== + dependencies: + debug "^4.1.1" + get-symbol-from-current-process-h "^1.0.2" + node-addon-api "^3.0.0" + node-gyp-build "^4.2.1" + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" + integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== + dependencies: + "@babel/highlight" "^7.22.5" + +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.5.tgz#b1f6c86a02d85d2dd3368a2b67c09add8cd0c255" + integrity sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA== + +"@babel/core@^7.1.6", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.14.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.5.tgz#d67d9747ecf26ee7ecd3ebae1ee22225fe902a89" + integrity sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helpers" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + +"@babel/generator@^7.14.0", "@babel/generator@^7.22.5", "@babel/generator@^7.7.2": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7" + integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA== + dependencies: + "@babel/types" "^7.22.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz#a3f4758efdd0190d8927fcffd261755937c71878" + integrity sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz#fc7319fc54c5e2fa14b2909cf3c5fd3046813e02" + integrity sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw== + dependencies: + "@babel/compat-data" "^7.22.5" + "@babel/helper-validator-option" "^7.22.5" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz#2192a1970ece4685fbff85b48da2c32fcb130b7c" + integrity sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.5" + semver "^6.3.0" + +"@babel/helper-create-regexp-features-plugin@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz#bb2bf0debfe39b831986a4efbf4066586819c6e4" + integrity sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.0" + +"@babel/helper-define-polyfill-provider@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8" + integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-environment-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" + integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== + +"@babel/helper-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== + dependencies: + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-member-expression-to-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" + integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" + integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-transforms@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef" + integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/helper-remap-async-to-generator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz#14a38141a7bf2165ad38da61d61cf27b43015da2" + integrity sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-wrap-function" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/helper-replace-supers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz#71bc5fb348856dea9fdc4eafd7e2e49f585145dc" + integrity sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg== + dependencies: + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08" + integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + +"@babel/helper-validator-identifier@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" + integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== + +"@babel/helper-validator-option@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" + integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== + +"@babel/helper-wrap-function@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz#44d205af19ed8d872b4eefb0d2fa65f45eb34f06" + integrity sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw== + dependencies: + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/helpers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.5.tgz#74bb4373eb390d1ceed74a15ef97767e63120820" + integrity sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q== + dependencies: + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/highlight@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" + integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== + dependencies: + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea" + integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q== + +"@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-export-default-from@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.22.5.tgz#825924eda1fad382c3de4db6fe1711b6fa03362f" + integrity sha512-UCe1X/hplyv6A5g2WnQ90tnHRvYL29dabCWww92lO7VdfMVTVReBTRrhiMrKQejHD9oVkdnRdwYuzUZkBVQisg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-export-default-from" "^7.22.5" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.0.0", "@babel/plugin-proposal-nullish-coalescing-operator@^7.1.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-object-rest-spread@^7.0.0": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" + integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== + dependencies: + "@babel/compat-data" "^7.20.5" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.20.7" + +"@babel/plugin-proposal-optional-catch-binding@^7.0.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.0.0", "@babel/plugin-proposal-optional-chaining@^7.1.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" + integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.0.0", "@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-dynamic-import@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-default-from@^7.0.0", "@babel/plugin-syntax-export-default-from@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.22.5.tgz#ac3a24b362a04415a017ab96b9b4483d0e2a6e44" + integrity sha512-ODAqWWXB/yReh/jVQDag/3/tl6lgBueQkk/TcfW/59Oykm4c8a55XloX0CTk2k2VJiFWMgHby9xNX29IbCv9dQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.2.0", "@babel/plugin-syntax-flow@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.22.5.tgz#163b820b9e7696ce134df3ee716d9c0c98035859" + integrity sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" + integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.0.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.0.0", "@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.22.5", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" + integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-arrow-functions@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" + integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-async-to-generator@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" + integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== + dependencies: + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.5" + +"@babel/plugin-transform-block-scoped-functions@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" + integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-block-scoping@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz#8bfc793b3a4b2742c0983fadc1480d843ecea31b" + integrity sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-classes@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz#635d4e98da741fad814984639f4c0149eb0135e1" + integrity sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.5" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" + integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/template" "^7.22.5" + +"@babel/plugin-transform-destructuring@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz#d3aca7438f6c26c78cdd0b0ba920a336001b27cc" + integrity sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-exponentiation-operator@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" + integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-flow-strip-types@^7.0.0", "@babel/plugin-transform-flow-strip-types@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.22.5.tgz#0bb17110c7bf5b35a60754b2f00c58302381dee2" + integrity sha512-tujNbZdxdG0/54g/oua8ISToaXTFBf8EnSb5PgQSciIXWOWKX3S4+JR7ZE9ol8FZwf9kxitzkGQ+QWeov/mCiA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-flow" "^7.22.5" + +"@babel/plugin-transform-for-of@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" + integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-function-name@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" + integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== + dependencies: + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-literals@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" + integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-member-expression-literals@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" + integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.1.0", "@babel/plugin-transform-modules-commonjs@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" + integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== + dependencies: + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + +"@babel/plugin-transform-object-assign@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.22.5.tgz#290c1b9555dcea48bb2c29ad94237777600d04f9" + integrity sha512-iDhx9ARkXq4vhZ2CYOSnQXkmxkDgosLi3J8Z17mKz7LyzthtkdVchLD7WZ3aXeCuvJDOW3+1I5TpJmwIbF9MKQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-object-super@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" + integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" + +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" + integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-property-literals@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" + integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-display-name@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b" + integrity sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-jsx-self@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz#ca2fdc11bc20d4d46de01137318b13d04e481d8e" + integrity sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-jsx-source@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz#49af1615bfdf6ed9d3e9e43e425e0b2b65d15b6c" + integrity sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz#932c291eb6dd1153359e2a90cb5e557dcf068416" + integrity sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/plugin-transform-regenerator@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz#cd8a68b228a5f75fa01420e8cc2fc400f0fc32aa" + integrity sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + regenerator-transform "^0.15.1" + +"@babel/plugin-transform-runtime@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.5.tgz#ca975fb5e260044473c8142e1b18b567d33c2a3b" + integrity sha512-bg4Wxd1FWeFx3daHFTWk1pkSWK/AyQuiyAoeZAOkAOUBjnZPH6KT7eMxouV47tQ6hl6ax2zyAWBdWZXbrvXlaw== + dependencies: + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + babel-plugin-polyfill-corejs2 "^0.4.3" + babel-plugin-polyfill-corejs3 "^0.8.1" + babel-plugin-polyfill-regenerator "^0.5.0" + semver "^6.3.0" + +"@babel/plugin-transform-shorthand-properties@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" + integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-spread@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" + integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-sticky-regex@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" + integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-template-literals@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" + integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-typescript@^7.22.5", "@babel/plugin-transform-typescript@^7.5.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz#5c0f7adfc1b5f38c4dbc8f79b1f0f8074134bd7d" + integrity sha512-SMubA9S7Cb5sGSFFUlqxyClTA9zWJ8qGQrppNUm05LtFuN1ELRFNndkix4zUJrC9F+YivWwa1dHMSyo0e0N9dA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-typescript" "^7.22.5" + +"@babel/plugin-transform-unicode-regex@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" + integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/preset-flow@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.22.5.tgz#876f24ab6b38bd79703a93f32020ca2162312784" + integrity sha512-ta2qZ+LSiGCrP5pgcGt8xMnnkXQrq8Sa4Ulhy06BOlF5QbLw9q5hIx7bn5MrsvyTGAfh6kTOo07Q+Pfld/8Y5Q== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.5" + "@babel/plugin-transform-flow-strip-types" "^7.22.5" + +"@babel/preset-typescript@^7.1.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz#16367d8b01d640e9a507577ed4ee54e0101e51c8" + integrity sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.5" + "@babel/plugin-transform-typescript" "^7.22.5" + +"@babel/register@^7.0.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.22.5.tgz#e4d8d0f615ea3233a27b5c6ada6750ee59559939" + integrity sha512-vV6pm/4CijSQ8Y47RH5SopXzursN35RQINfGJkmOlcpAtGuf94miFvIPhCKGQN7WGIcsgG1BHEX2KVdTYwTwUQ== + dependencies: + clone-deep "^4.0.1" + find-cache-dir "^2.0.0" + make-dir "^2.1.0" + pirates "^4.0.5" + source-map-support "^0.5.16" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + +"@babel/runtime@^7.8.4": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" + integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== + dependencies: + regenerator-runtime "^0.13.11" + +"@babel/template@^7.0.0", "@babel/template@^7.22.5", "@babel/template@^7.3.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" + integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/traverse@^7.14.0", "@babel/traverse@^7.22.5", "@babel/traverse@^7.7.2": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.5.tgz#44bd276690db6f4940fdb84e1cb4abd2f729ccd1" + integrity sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/types" "^7.22.5" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" + integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== + +"@eslint/eslintrc@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" + integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.5.2" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.43.0": + version "8.43.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.43.0.tgz#559ca3d9ddbd6bf907ad524320a0d14b85586af0" + integrity sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg== + +"@gar/promisify@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + +"@hapi/hoek@^9.0.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@humanwhocodes/config-array@^0.11.10": + version "0.11.10" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" + integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@hutson/parse-repository-url@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" + integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@isaacs/string-locale-compare@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.3.tgz#2030606ec03a18c31803b8a36382762e447655df" + integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + +"@jest/core@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.3.tgz#0ebf2bd39840f1233cd5f2d1e6fc8b71bd5a1ac7" + integrity sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/reporters" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^28.1.3" + jest-config "^28.1.3" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-resolve-dependencies "^28.1.3" + jest-runner "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + jest-watcher "^28.1.3" + micromatch "^4.0.4" + pretty-format "^28.1.3" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/create-cache-key-function@^27.0.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz#7448fae15602ea95c828f5eceed35c202a820b31" + integrity sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ== + dependencies: + "@jest/types" "^27.5.1" + +"@jest/environment@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.3.tgz#abed43a6b040a4c24fdcb69eab1f97589b2d663e" + integrity sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA== + dependencies: + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + +"@jest/expect-utils@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.3.tgz#58561ce5db7cd253a7edddbc051fb39dda50f525" + integrity sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA== + dependencies: + jest-get-type "^28.0.2" + +"@jest/expect@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.3.tgz#9ac57e1d4491baca550f6bdbd232487177ad6a72" + integrity sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw== + dependencies: + expect "^28.1.3" + jest-snapshot "^28.1.3" + +"@jest/fake-timers@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.3.tgz#230255b3ad0a3d4978f1d06f70685baea91c640e" + integrity sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw== + dependencies: + "@jest/types" "^28.1.3" + "@sinonjs/fake-timers" "^9.1.2" + "@types/node" "*" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +"@jest/globals@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.3.tgz#a601d78ddc5fdef542728309894895b4a42dc333" + integrity sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/types" "^28.1.3" + +"@jest/reporters@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.3.tgz#9adf6d265edafc5fc4a434cfb31e2df5a67a369a" + integrity sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + jest-worker "^28.1.3" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + terminal-link "^2.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905" + integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== + dependencies: + "@sinclair/typebox" "^0.24.1" + +"@jest/schemas@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" + integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== + dependencies: + "@sinclair/typebox" "^0.25.16" + +"@jest/source-map@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24" + integrity sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww== + dependencies: + "@jridgewell/trace-mapping" "^0.3.13" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.3.tgz#5eae945fd9f4b8fcfce74d239e6f725b6bf076c5" + integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== + dependencies: + "@jest/console" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz#9d0c283d906ac599c74bde464bc0d7e6a82886c3" + integrity sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw== + dependencies: + "@jest/test-result" "^28.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + slash "^3.0.0" + +"@jest/transform@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.3.tgz#59d8098e50ab07950e0f2fc0fc7ec462371281b0" + integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + +"@jest/types@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" + integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== + dependencies: + "@jest/schemas" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jest/types@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" + integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== + dependencies: + "@jest/schemas" "^29.4.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@1.4.14": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@lerna/child-process@6.6.2": + version "6.6.2" + resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-6.6.2.tgz#5d803c8dee81a4e013dc428292e77b365cba876c" + integrity sha512-QyKIWEnKQFnYu2ey+SAAm1A5xjzJLJJj3bhIZd3QKyXKKjaJ0hlxam/OsWSltxTNbcyH1jRJjC6Cxv31usv0Ag== + dependencies: + chalk "^4.1.0" + execa "^5.0.0" + strong-log-transformer "^2.1.0" + +"@lerna/create@6.6.2": + version "6.6.2" + resolved "https://registry.yarnpkg.com/@lerna/create/-/create-6.6.2.tgz#39a36d80cddb355340c297ed785aa76f4498177f" + integrity sha512-xQ+1Y7D+9etvUlE+unhG/TwmM6XBzGIdFBaNoW8D8kyOa9M2Jf3vdEtAxVa7mhRz66CENfhL/+I/QkVaa7pwbQ== + dependencies: + "@lerna/child-process" "6.6.2" + dedent "^0.7.0" + fs-extra "^9.1.0" + init-package-json "^3.0.2" + npm-package-arg "8.1.1" + p-reduce "^2.1.0" + pacote "15.1.1" + pify "^5.0.0" + semver "^7.3.4" + slash "^3.0.0" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^4.0.0" + yargs-parser "20.2.4" + +"@lerna/legacy-package-management@6.6.2": + version "6.6.2" + resolved "https://registry.yarnpkg.com/@lerna/legacy-package-management/-/legacy-package-management-6.6.2.tgz#411c395e72e563ab98f255df77e4068627a85bb0" + integrity sha512-0hZxUPKnHwehUO2xC4ldtdX9bW0W1UosxebDIQlZL2STnZnA2IFmIk2lJVUyFW+cmTPQzV93jfS0i69T9Z+teg== + dependencies: + "@npmcli/arborist" "6.2.3" + "@npmcli/run-script" "4.1.7" + "@nrwl/devkit" ">=15.5.2 < 16" + "@octokit/rest" "19.0.3" + byte-size "7.0.0" + chalk "4.1.0" + clone-deep "4.0.1" + cmd-shim "5.0.0" + columnify "1.6.0" + config-chain "1.1.12" + conventional-changelog-core "4.2.4" + conventional-recommended-bump "6.1.0" + cosmiconfig "7.0.0" + dedent "0.7.0" + dot-prop "6.0.1" + execa "5.0.0" + file-url "3.0.0" + find-up "5.0.0" + fs-extra "9.1.0" + get-port "5.1.1" + get-stream "6.0.0" + git-url-parse "13.1.0" + glob-parent "5.1.2" + globby "11.1.0" + graceful-fs "4.2.10" + has-unicode "2.0.1" + inquirer "8.2.4" + is-ci "2.0.0" + is-stream "2.0.0" + libnpmpublish "7.1.4" + load-json-file "6.2.0" + make-dir "3.1.0" + minimatch "3.0.5" + multimatch "5.0.0" + node-fetch "2.6.7" + npm-package-arg "8.1.1" + npm-packlist "5.1.1" + npm-registry-fetch "14.0.3" + npmlog "6.0.2" + p-map "4.0.0" + p-map-series "2.1.0" + p-queue "6.6.2" + p-waterfall "2.1.1" + pacote "15.1.1" + pify "5.0.0" + pretty-format "29.4.3" + read-cmd-shim "3.0.0" + read-package-json "5.0.1" + resolve-from "5.0.0" + semver "7.3.8" + signal-exit "3.0.7" + slash "3.0.0" + ssri "9.0.1" + strong-log-transformer "2.1.0" + tar "6.1.11" + temp-dir "1.0.0" + tempy "1.0.0" + upath "2.0.1" + uuid "8.3.2" + write-file-atomic "4.0.1" + write-pkg "4.0.0" + yargs "16.2.0" + +"@mapbox/node-pre-gyp@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" + integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@npmcli/arborist@6.2.3": + version "6.2.3" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-6.2.3.tgz#31f8aed2588341864d3811151d929c01308f8e71" + integrity sha512-lpGOC2ilSJXcc2zfW9QtukcCTcMbl3fVI0z4wvFB2AFIl0C+Q6Wv7ccrpdrQa8rvJ1ZVuc6qkX7HVTyKlzGqKA== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/fs" "^3.1.0" + "@npmcli/installed-package-contents" "^2.0.0" + "@npmcli/map-workspaces" "^3.0.2" + "@npmcli/metavuln-calculator" "^5.0.0" + "@npmcli/name-from-folder" "^2.0.0" + "@npmcli/node-gyp" "^3.0.0" + "@npmcli/package-json" "^3.0.0" + "@npmcli/query" "^3.0.0" + "@npmcli/run-script" "^6.0.0" + bin-links "^4.0.1" + cacache "^17.0.4" + common-ancestor-path "^1.0.1" + hosted-git-info "^6.1.1" + json-parse-even-better-errors "^3.0.0" + json-stringify-nice "^1.1.4" + minimatch "^6.1.6" + nopt "^7.0.0" + npm-install-checks "^6.0.0" + npm-package-arg "^10.1.0" + npm-pick-manifest "^8.0.1" + npm-registry-fetch "^14.0.3" + npmlog "^7.0.1" + pacote "^15.0.8" + parse-conflict-json "^3.0.0" + proc-log "^3.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^1.0.1" + read-package-json-fast "^3.0.2" + semver "^7.3.7" + ssri "^10.0.1" + treeverse "^3.0.0" + walk-up-path "^1.0.0" + +"@npmcli/fs@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" + integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== + dependencies: + "@gar/promisify" "^1.1.3" + semver "^7.3.5" + +"@npmcli/fs@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.0.tgz#233d43a25a91d68c3a863ba0da6a3f00924a173e" + integrity sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w== + dependencies: + semver "^7.3.5" + +"@npmcli/git@^4.0.0", "@npmcli/git@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-4.1.0.tgz#ab0ad3fd82bc4d8c1351b6c62f0fa56e8fe6afa6" + integrity sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ== + dependencies: + "@npmcli/promise-spawn" "^6.0.0" + lru-cache "^7.4.4" + npm-pick-manifest "^8.0.0" + proc-log "^3.0.0" + promise-inflight "^1.0.1" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^3.0.0" + +"@npmcli/installed-package-contents@^2.0.0", "@npmcli/installed-package-contents@^2.0.1": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz#bfd817eccd9e8df200919e73f57f9e3d9e4f9e33" + integrity sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ== + dependencies: + npm-bundled "^3.0.0" + npm-normalize-package-bin "^3.0.0" + +"@npmcli/map-workspaces@^3.0.2": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-3.0.4.tgz#15ad7d854292e484f7ba04bc30187a8320dba799" + integrity sha512-Z0TbvXkRbacjFFLpVpV0e2mheCh+WzQpcqL+4xp49uNJOxOnIAPZyXtUxZ5Qn3QBTGKA11Exjd9a5411rBrhDg== + dependencies: + "@npmcli/name-from-folder" "^2.0.0" + glob "^10.2.2" + minimatch "^9.0.0" + read-package-json-fast "^3.0.0" + +"@npmcli/metavuln-calculator@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-5.0.1.tgz#426b3e524c2008bcc82dbc2ef390aefedd643d76" + integrity sha512-qb8Q9wIIlEPj3WeA1Lba91R4ZboPL0uspzV0F9uwP+9AYMVB2zOoa7Pbk12g6D2NHAinSbHh6QYmGuRyHZ874Q== + dependencies: + cacache "^17.0.0" + json-parse-even-better-errors "^3.0.0" + pacote "^15.0.0" + semver "^7.3.5" + +"@npmcli/move-file@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" + integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@npmcli/name-from-folder@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz#c44d3a7c6d5c184bb6036f4d5995eee298945815" + integrity sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg== + +"@npmcli/node-gyp@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz#8c20e53e34e9078d18815c1d2dda6f2420d75e35" + integrity sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A== + +"@npmcli/node-gyp@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz#101b2d0490ef1aa20ed460e4c0813f0db560545a" + integrity sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA== + +"@npmcli/package-json@^3.0.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-3.1.1.tgz#5628332aac90fa1b4d6f98e03988c5958b35e0c5" + integrity sha512-+UW0UWOYFKCkvszLoTwrYGrjNrT8tI5Ckeb/h+Z1y1fsNJEctl7HmerA5j2FgmoqFaLI2gsA1X9KgMFqx/bRmA== + dependencies: + "@npmcli/git" "^4.1.0" + glob "^10.2.2" + json-parse-even-better-errors "^3.0.0" + normalize-package-data "^5.0.0" + npm-normalize-package-bin "^3.0.1" + proc-log "^3.0.0" + +"@npmcli/promise-spawn@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz#53283b5f18f855c6925f23c24e67c911501ef573" + integrity sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g== + dependencies: + infer-owner "^1.0.4" + +"@npmcli/promise-spawn@^6.0.0", "@npmcli/promise-spawn@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz#c8bc4fa2bd0f01cb979d8798ba038f314cfa70f2" + integrity sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg== + dependencies: + which "^3.0.0" + +"@npmcli/query@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/query/-/query-3.0.0.tgz#51a0dfb85811e04f244171f164b6bc83b36113a7" + integrity sha512-MFNDSJNgsLZIEBVZ0Q9w9K7o07j5N4o4yjtdz2uEpuCZlXGMuPENiRaFYk0vRqAA64qVuUQwC05g27fRtfUgnA== + dependencies: + postcss-selector-parser "^6.0.10" + +"@npmcli/run-script@4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-4.1.7.tgz#b1a2f57568eb738e45e9ea3123fb054b400a86f7" + integrity sha512-WXr/MyM4tpKA4BotB81NccGAv8B48lNH0gRoILucbcAhTQXLCoi6HflMV3KdXubIqvP9SuLsFn68Z7r4jl+ppw== + dependencies: + "@npmcli/node-gyp" "^2.0.0" + "@npmcli/promise-spawn" "^3.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^2.0.3" + which "^2.0.2" + +"@npmcli/run-script@^6.0.0": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-6.0.2.tgz#a25452d45ee7f7fb8c16dfaf9624423c0c0eb885" + integrity sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA== + dependencies: + "@npmcli/node-gyp" "^3.0.0" + "@npmcli/promise-spawn" "^6.0.0" + node-gyp "^9.0.0" + read-package-json-fast "^3.0.0" + which "^3.0.0" + +"@nrwl/cli@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-15.9.4.tgz#63b600dff1cdc126f234d16978a888f72c22a00c" + integrity sha512-FoiGFCLpb/r4HXCM3KYqT0xteP+MRV6bIHjz3bdPHIDLmBNQQnRRaV2K47jtJ6zjh1eOU5UHKyDtDDYf80Idpw== + dependencies: + nx "15.9.4" + +"@nrwl/devkit@>=15.5.2 < 16": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-15.9.4.tgz#3f0a43a9637fcd0a46c06df2a9c36012b27f006b" + integrity sha512-mUX1kXTuPMdTzFxIzH+MsSNvdppOmstPDOEtiGFZJTuJ625ki0HhNJILO3N2mJ7MeMrLqIlAiNdvelQaObxYsQ== + dependencies: + ejs "^3.1.7" + ignore "^5.0.4" + semver "7.3.4" + tmp "~0.2.1" + tslib "^2.3.0" + +"@nrwl/nx-darwin-arm64@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/nx-darwin-arm64/-/nx-darwin-arm64-15.9.4.tgz#e5a2f39d42a60397a01140a251f894788f5d1fda" + integrity sha512-XnvrnT9BJsgThY/4xUcYtE077ERq/img8CkRj7MOOBNOh0/nVcR4LGbBKDHtwE3HPk0ikyS/SxRyNa9msvi3QQ== + +"@nrwl/nx-darwin-x64@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/nx-darwin-x64/-/nx-darwin-x64-15.9.4.tgz#97a810d4ff6b4bf395a43e4740890c0def2372da" + integrity sha512-WKSfSlpVMLchpXkax0geeUNyhvNxwO7qUz/s0/HJWBekt8fizwKDwDj1gP7fOu+YWb/tHiSscbR1km8PtdjhQw== + +"@nrwl/nx-linux-arm-gnueabihf@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-15.9.4.tgz#b8dd23b8c755b7e640d744945ab2dec3fd3eda65" + integrity sha512-a/b4PP7lP/Cgrh0LjC4O2YTt5pyf4DQTGtuE8qlo8o486UiofCtk4QGJX72q80s23L0ejCaKY2ULKx/3zMLjuA== + +"@nrwl/nx-linux-arm64-gnu@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-15.9.4.tgz#5bc150c2bdb2e0a2eaf8721b3c5fdb2eb93f8739" + integrity sha512-ibBV8fMhSfLVd/2WzcDuUm32BoZsattuKkvMmOoyU6Pzoznc3AqyDjJR4xCIoAn5Rf+Nu1oeQONr5FAtb1Ugow== + +"@nrwl/nx-linux-arm64-musl@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-arm64-musl/-/nx-linux-arm64-musl-15.9.4.tgz#df2f18f813828000dc52f1b7668339947b1a0862" + integrity sha512-iIjvVYd7+uM4jVD461+PvU5XTALgSvJOODUaMRGOoDl0KlMuTe6pQZlw0eXjl5rcTd6paKaVFWT5j6awr8kj7w== + +"@nrwl/nx-linux-x64-gnu@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-x64-gnu/-/nx-linux-x64-gnu-15.9.4.tgz#55547b07e6aeb0c36a43e05bd07c15b013f2de9f" + integrity sha512-q4OyH72mdrE4KellBWtwpr5EwfxHKNoFP9//7FAILO68ROh0rpMd7YQMlTB7T04UEUHjKEEsFGTlVXIee3Viwg== + +"@nrwl/nx-linux-x64-musl@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/nx-linux-x64-musl/-/nx-linux-x64-musl-15.9.4.tgz#29cd644736f643566d9c0e1a1171c49a62a08c09" + integrity sha512-67+/XNMR1CgLPyeGX8jqSG6l8yYD0iiwUgcu1Vaxq6N05WwnqVisIW8XzLSRUtKt4WyVQgOWk3aspImpMVOG3Q== + +"@nrwl/nx-win32-arm64-msvc@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-15.9.4.tgz#55a38bf5dc201e9088729fb03e505dc63caf8b3a" + integrity sha512-2rEsq3eOGVCYpYJn2tTJkOGNJm/U8rP/FmqtZXYa6VJv/00XP3Gl00IXFEDaYV6rZo7SWqLxtEPUbjK5LwPzZA== + +"@nrwl/nx-win32-x64-msvc@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/nx-win32-x64-msvc/-/nx-win32-x64-msvc-15.9.4.tgz#56bb859bfe47d08d14f8d5822d9a31d9098d95a9" + integrity sha512-bogVju4Z/hy1jbppqaTNbmV1R4Kg0R5fKxXAXC2LaL7FL0dup31wPumdV+mXttXBNOBDjV8V/Oz1ZqdmxpOJUw== + +"@nrwl/tao@15.9.4": + version "15.9.4" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-15.9.4.tgz#5e384af06d1fb68e326eda2c6a5d8f99ce1583b8" + integrity sha512-m90iz8UsXx1rgPm1dxsBQjSrCViWYZIrp8bpwjSCW24j3kifyilYSXGuKaRwZwUn7eNmH/kZcI9/8qeGIPF4Sg== + dependencies: + nx "15.9.4" + +"@octokit/auth-token@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.4.tgz#70e941ba742bdd2b49bdb7393e821dea8520a3db" + integrity sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ== + +"@octokit/core@^4.0.0": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.2.4.tgz#d8769ec2b43ff37cc3ea89ec4681a20ba58ef907" + integrity sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ== + dependencies: + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^9.0.0" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^7.0.0": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.6.tgz#791f65d3937555141fb6c08f91d618a7d645f1e2" + integrity sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg== + dependencies: + "@octokit/types" "^9.0.0" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^5.0.0": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.6.tgz#9eac411ac4353ccc5d3fca7d76736e6888c5d248" + integrity sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw== + dependencies: + "@octokit/request" "^6.0.0" + "@octokit/types" "^9.0.0" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^12.11.0": + version "12.11.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-12.11.0.tgz#da5638d64f2b919bca89ce6602d059f1b52d3ef0" + integrity sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ== + +"@octokit/openapi-types@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-14.0.0.tgz#949c5019028c93f189abbc2fb42f333290f7134a" + integrity sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw== + +"@octokit/openapi-types@^18.0.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-18.0.0.tgz#f43d765b3c7533fd6fb88f3f25df079c24fccf69" + integrity sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw== + +"@octokit/plugin-enterprise-rest@6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" + integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== + +"@octokit/plugin-paginate-rest@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-3.1.0.tgz#86f8be759ce2d6d7c879a31490fd2f7410b731f0" + integrity sha512-+cfc40pMzWcLkoDcLb1KXqjX0jTGYXjKuQdFQDc6UAknISJHnZTiBqld6HDwRJvD4DsouDKrWXNbNV0lE/3AXA== + dependencies: + "@octokit/types" "^6.41.0" + +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@^6.0.0": + version "6.8.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.8.1.tgz#97391fda88949eb15f68dc291957ccbe1d3e8ad1" + integrity sha512-QrlaTm8Lyc/TbU7BL/8bO49vp+RZ6W3McxxmmQTgYxf2sWkO8ZKuj4dLhPNJD6VCUW1hetCmeIM0m6FTVpDiEg== + dependencies: + "@octokit/types" "^8.1.1" + deprecation "^2.3.1" + +"@octokit/request-error@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.3.tgz#ef3dd08b8e964e53e55d471acfe00baa892b9c69" + integrity sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ== + dependencies: + "@octokit/types" "^9.0.0" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^6.0.0": + version "6.2.8" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-6.2.8.tgz#aaf480b32ab2b210e9dadd8271d187c93171d8eb" + integrity sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw== + dependencies: + "@octokit/endpoint" "^7.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^9.0.0" + is-plain-object "^5.0.0" + node-fetch "^2.6.7" + universal-user-agent "^6.0.0" + +"@octokit/rest@19.0.3": + version "19.0.3" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.3.tgz#b9a4e8dc8d53e030d611c053153ee6045f080f02" + integrity sha512-5arkTsnnRT7/sbI4fqgSJ35KiFaN7zQm0uQiQtivNQLI8RQx8EHwJCajcTUwmaCMNDg7tdCvqAnc7uvHHPxrtQ== + dependencies: + "@octokit/core" "^4.0.0" + "@octokit/plugin-paginate-rest" "^3.0.0" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^6.0.0" + +"@octokit/types@^6.41.0": + version "6.41.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.41.0.tgz#e58ef78d78596d2fb7df9c6259802464b5f84a04" + integrity sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg== + dependencies: + "@octokit/openapi-types" "^12.11.0" + +"@octokit/types@^8.1.1": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-8.2.1.tgz#a6de091ae68b5541f8d4fcf9a12e32836d4648aa" + integrity sha512-8oWMUji8be66q2B9PmEIUyQm00VPDPun07umUWSaCwxmeaquFBro4Hcc3ruVoDo3zkQyZBlRvhIMEYS3pBhanw== + dependencies: + "@octokit/openapi-types" "^14.0.0" + +"@octokit/types@^9.0.0": + version "9.3.2" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-9.3.2.tgz#3f5f89903b69f6a2d196d78ec35f888c0013cac5" + integrity sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA== + dependencies: + "@octokit/openapi-types" "^18.0.0" + +"@parcel/watcher@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" + integrity sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg== + dependencies: + node-addon-api "^3.2.1" + node-gyp-build "^4.3.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@react-native-community/cli-debugger-ui@^6.0.0-rc.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-6.0.0.tgz#ef9eb1268d85c1bd3caf2c4d36dc350bb080f254" + integrity sha512-onf6vtvqSzOr6bNEWhPzgcJP2UQhA0VY6c8tXwNczIONC/ahnN93LPBB/uXDbn9d/kLMvE7oUJiqRadZWHk6aA== + dependencies: + serve-static "^1.13.1" + +"@react-native-community/cli-hermes@^6.3.0": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-6.3.1.tgz#a4c3b4b07a1775f7012baf6d5a0b059da2ffac00" + integrity sha512-+tMJsEsVX0WyylnoFE7uPoMu1aTAChaA62Y32dwWgAa1Fx6YrpPkC9d6wvYSBe9md/4mTtRher+ooBcuov6JHw== + dependencies: + "@react-native-community/cli-platform-android" "^6.3.1" + "@react-native-community/cli-tools" "^6.2.1" + chalk "^4.1.2" + hermes-profile-transformer "^0.0.6" + ip "^1.1.5" + +"@react-native-community/cli-platform-android@^6.0.0", "@react-native-community/cli-platform-android@^6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-6.3.1.tgz#8d35c809ddaf3b6c5d4ef9ff9c814a25a55259aa" + integrity sha512-n5A64RI1ty4ScZCel/3JYY9Anl857dPsUZ86Dwc1GxrbflSB5/+hcCMg5DCNcnJRa4Hdv95SAR5pMmtAjOXApA== + dependencies: + "@react-native-community/cli-tools" "^6.2.1" + chalk "^4.1.2" + execa "^1.0.0" + fs-extra "^8.1.0" + glob "^7.1.3" + jetifier "^1.6.2" + lodash "^4.17.15" + logkitty "^0.7.1" + slash "^3.0.0" + xmldoc "^1.1.2" + +"@react-native-community/cli-platform-ios@^6.0.0": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-6.2.1.tgz#852a7dc520071ac55fead3c78daa6a0060dde72f" + integrity sha512-5vwLRfTbIVUsO86AUPmR5vkp+7t4gTH2+SwRo0DKqBGBQ3hraA3dlWu0nzh99eQKQhCiFLB1WJPAi3zY03lK4w== + dependencies: + "@react-native-community/cli-tools" "^6.2.1" + chalk "^4.1.2" + glob "^7.1.3" + js-yaml "^3.13.1" + lodash "^4.17.15" + ora "^3.4.0" + plist "^3.0.2" + xcode "^2.0.0" + +"@react-native-community/cli-plugin-metro@^6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-6.4.0.tgz#70b7908d6b548481f37ed58167f9460d325ae21c" + integrity sha512-lcrNODvHd3ZVhiEshXAjyBoqP44rjhkytkOSUpnZHAcmMLiguxDmvhWeWqbwu3XqSX/f0gVKmfj81t+opI1bSw== + dependencies: + "@react-native-community/cli-server-api" "^6.4.0" + "@react-native-community/cli-tools" "^6.2.0" + chalk "^4.1.2" + metro "^0.66.1" + metro-config "^0.66.1" + metro-core "^0.66.1" + metro-react-native-babel-transformer "^0.66.1" + metro-resolver "^0.66.1" + metro-runtime "^0.66.1" + readline "^1.3.0" + +"@react-native-community/cli-server-api@^6.4.0": + version "6.4.3" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-6.4.3.tgz#b52444454f40bfb54a84ab52bf42e9f8002917f5" + integrity sha512-Ywy2x+PhIUZXgE74YiCYXylSVnuEBcq5cNfYLR3AwOvrILjh03smXfCca8s2V2LWUlzmWN6+L85FJGsT92MUJA== + dependencies: + "@react-native-community/cli-debugger-ui" "^6.0.0-rc.0" + "@react-native-community/cli-tools" "^6.2.0" + compression "^1.7.1" + connect "^3.6.5" + errorhandler "^1.5.0" + nocache "^2.1.0" + pretty-format "^26.6.2" + serve-static "^1.13.1" + ws "^1.1.0" + +"@react-native-community/cli-tools@^6.2.0", "@react-native-community/cli-tools@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-6.2.1.tgz#6f7ada6559846b49fb9fcfed522399b232976ada" + integrity sha512-7RbOkZLT/3YG8CAYYM70ajRKIOgVxK/b4t9KNsPq+2uen99MGezfeglC8s1cs3vBNVVxCo0a2JbXg18bUd8eqA== + dependencies: + appdirsjs "^1.2.4" + chalk "^4.1.2" + lodash "^4.17.15" + mime "^2.4.1" + node-fetch "^2.6.0" + open "^6.2.0" + semver "^6.3.0" + shell-quote "^1.7.3" + +"@react-native-community/cli-types@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-6.0.0.tgz#90269fbdc7229d5e3b8f2f3e029a94083551040d" + integrity sha512-K493Fk2DMJC0ZM8s8gnfseKxGasIhuDaCUDeLZcoCSFlrjKEuEs1BKKEJiev0CARhKEXKOyyp/uqYM9nWhisNw== + dependencies: + ora "^3.4.0" + +"@react-native-community/cli@^6.0.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-6.4.0.tgz#0b61a4b5f11293b0b79a0e78f80777387a9529a9" + integrity sha512-UNvYnWaALa4mJEaWdLY3fVK+csZzx/Ja/FGvXISPJ9W9lrKvGtyXkidViUCPbPtMsJUi7teA4uIShHn0mbGmnQ== + dependencies: + "@react-native-community/cli-debugger-ui" "^6.0.0-rc.0" + "@react-native-community/cli-hermes" "^6.3.0" + "@react-native-community/cli-plugin-metro" "^6.4.0" + "@react-native-community/cli-server-api" "^6.4.0" + "@react-native-community/cli-tools" "^6.2.0" + "@react-native-community/cli-types" "^6.0.0" + appdirsjs "^1.2.4" + chalk "^4.1.2" + command-exists "^1.2.8" + commander "^2.19.0" + cosmiconfig "^5.1.0" + deepmerge "^3.2.0" + envinfo "^7.7.2" + execa "^1.0.0" + find-up "^4.1.0" + fs-extra "^8.1.0" + glob "^7.1.3" + graceful-fs "^4.1.3" + joi "^17.2.1" + leven "^3.1.0" + lodash "^4.17.15" + minimist "^1.2.0" + node-stream-zip "^1.9.1" + ora "^3.4.0" + pretty-format "^26.6.2" + prompts "^2.4.0" + semver "^6.3.0" + serve-static "^1.13.1" + strip-ansi "^5.2.0" + sudo-prompt "^9.0.0" + wcwidth "^1.0.1" + +"@react-native/assets@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@react-native/assets/-/assets-1.0.0.tgz#c6f9bf63d274bafc8e970628de24986b30a55c8e" + integrity sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ== + +"@react-native/normalize-color@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-2.0.0.tgz#da955909432474a9a0fe1cbffc66576a0447f567" + integrity sha512-Wip/xsc5lw8vsBlmY2MO/gFLp3MvuZ2baBZjDeTjjndMgM0h5sxz7AZR62RDPGgstp8Np7JzjvVqVT7tpFZqsw== + +"@react-native/polyfills@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@react-native/polyfills/-/polyfills-2.0.0.tgz#4c40b74655c83982c8cf47530ee7dc13d957b6aa" + integrity sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ== + +"@sideway/address@^4.1.3": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@sigstore/protobuf-specs@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz#957cb64ea2f5ce527cc9cf02a096baeb0d2b99b4" + integrity sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ== + +"@sigstore/tuf@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@sigstore/tuf/-/tuf-1.0.0.tgz#13b69323e7bf8de458cd6c952c57acd1169772a5" + integrity sha512-bLzi9GeZgMCvjJeLUIfs8LJYCxrPRA8IXQkzUtaFKKVPTz0mucRyqFcV2U20yg9K+kYAD0YSitzGfRZCFLjdHQ== + dependencies: + "@sigstore/protobuf-specs" "^0.1.0" + make-fetch-happen "^11.0.1" + tuf-js "^1.1.3" + +"@sinclair/typebox@^0.24.1": + version "0.24.51" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" + integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== + +"@sinclair/typebox@^0.25.16": + version "0.25.24" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" + integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== + +"@sinonjs/commons@^1.7.0": + version "1.8.6" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" + integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@tufjs/canonical-json@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz#eade9fd1f537993bc1f0949f3aea276ecc4fab31" + integrity sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ== + +"@tufjs/models@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tufjs/models/-/models-1.0.4.tgz#5a689630f6b9dbda338d4b208019336562f176ef" + integrity sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A== + dependencies: + "@tufjs/canonical-json" "1.0.0" + minimatch "^9.0.0" + +"@types/2060.io__ffi-napi@npm:@types/ffi-napi": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@types/ffi-napi/-/ffi-napi-4.0.7.tgz#b3a9beeae160c74adca801ca1c9defb1ec0a1a32" + integrity sha512-2CvLfgxCUUSj7qVab6/uFLyVpgVd2gEV4H/TQEHHn6kZTV8iTesz9uo0bckhwzsh71atutOv8P3JmvRX2ZvpZg== + dependencies: + "@types/node" "*" + "@types/ref-napi" "*" + "@types/ref-struct-di" "*" + +"@types/2060.io__ref-napi@npm:@types/ref-napi", "@types/ref-napi@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.7.tgz#20adc93a7a2f9f992dfb17409fd748e6f4bf403d" + integrity sha512-CzPwr36VkezSpaJGdQX/UrczMSDsDgsWQQFEfQkS799Ft7n/s183a53lsql7RwVq+Ik4yLEgI84pRnLC0XXRlA== + dependencies: + "@types/node" "*" + +"@types/babel__core@^7.1.14": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" + integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" + integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/eslint@^8.4.1": + version "8.40.2" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.40.2.tgz#2833bc112d809677864a4b0e7d1de4f04d7dac2d" + integrity sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" + integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== + +"@types/fast-text-encoding@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/fast-text-encoding/-/fast-text-encoding-1.0.1.tgz#7a9e6d8dd2ac1fa70772b500a307c2620cec1fe3" + integrity sha512-DRFqoqjXfvuX3f5jIQKAQr/QzuFdNoHOtou0KQ9bzUJOreCciO7T/kFJHgEote7QusmgE1fTxsQSqNvXVR0GBw== + +"@types/graceful-fs@^4.1.2", "@types/graceful-fs@^4.1.3": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^27.4.1": + version "27.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" + integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.9": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/minimatch@^3.0.3": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + +"@types/minimist@^1.2.0": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" + integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== + +"@types/node@*": + version "20.3.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" + integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== + +"@types/node@^17.0.31": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + +"@types/normalize-package-data@^2.4.0": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/prettier@^2.1.5": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== + +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + +"@types/react-native@^0.67.0": + version "0.67.21" + resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.67.21.tgz#d7a46cb1a1e5c9cdc8d83efefe9e54dc179d7126" + integrity sha512-p++6s9efGcIPjDJFxlfXS9zCb2ZVMhDM3eaEUqjmn9InVM1NhquyQlDABn6yZUAhBMqqoor62CXNKR0wC6sPKA== + dependencies: + "@types/react" "^17" + +"@types/react@^16.9.19": + version "16.14.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.43.tgz#bc6e7a0e99826809591d38ddf1193955de32c446" + integrity sha512-7zdjv7jvoLLQg1tTvpQsm+hyNUMT2mPlNV1+d0I8fbGhkJl82spopMyBlu4wb1dviZAxpGdk5eHu/muacknnfw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/react@^17": + version "17.0.62" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.62.tgz#2efe8ddf8533500ec44b1334dd1a97caa2f860e3" + integrity sha512-eANCyz9DG8p/Vdhr0ZKST8JV12PhH2ACCDYlFw6DIO+D+ca+uP4jtEDEpVqXZrh/uZdXQGwk7whJa3ah5DtyLw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/ref-array-di@^1.2.3": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/ref-array-di/-/ref-array-di-1.2.5.tgz#822b9be5b934398fafd5c26264d8de80d487747d" + integrity sha512-dA/Himb7ca/Tf5vqLOhi7LewAAoOXghlocw7gAqvNrmLybAtu+w2BLzEsbFWAtx5ElNzMEHDaRybueYViFROjQ== + dependencies: + "@types/ref-napi" "*" + +"@types/ref-napi@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/ref-napi/-/ref-napi-3.0.7.tgz#20adc93a7a2f9f992dfb17409fd748e6f4bf403d" + integrity sha512-CzPwr36VkezSpaJGdQX/UrczMSDsDgsWQQFEfQkS799Ft7n/s183a53lsql7RwVq+Ik4yLEgI84pRnLC0XXRlA== + dependencies: + "@types/node" "*" + +"@types/ref-struct-di@*", "@types/ref-struct-di@^1.1.6": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@types/ref-struct-di/-/ref-struct-di-1.1.9.tgz#cdca2cefbb8a907ac9eb9d6a7f19cfae00bfa092" + integrity sha512-B1FsB1BhG1VLx0+IqBaAPXEPH0wCOb+Glaaw/i+nRUwDKFtSqWOziGnTRw05RyrBbrDsMiM0tVWmaujrs016Sw== + dependencies: + "@types/ref-napi" "*" + +"@types/scheduler@*": + version "0.16.3" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== + +"@types/semver@^7.3.12": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^15.0.0": + version "15.0.15" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.15.tgz#e609a2b1ef9e05d90489c2f5f45bbfb2be092158" + integrity sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg== + dependencies: + "@types/yargs-parser" "*" + +"@types/yargs@^16.0.0": + version "16.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3" + integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ== + dependencies: + "@types/yargs-parser" "*" + +"@types/yargs@^17.0.8": + version "17.0.24" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.11.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.0.tgz#2f4bea6a3718bed2ba52905358d0f45cd3620d31" + integrity sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.60.0" + "@typescript-eslint/type-utils" "5.60.0" + "@typescript-eslint/utils" "5.60.0" + debug "^4.3.4" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.11.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.60.0.tgz#08f4daf5fc6548784513524f4f2f359cebb4068a" + integrity sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ== + dependencies: + "@typescript-eslint/scope-manager" "5.60.0" + "@typescript-eslint/types" "5.60.0" + "@typescript-eslint/typescript-estree" "5.60.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.60.0.tgz#ae511967b4bd84f1d5e179bb2c82857334941c1c" + integrity sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ== + dependencies: + "@typescript-eslint/types" "5.60.0" + "@typescript-eslint/visitor-keys" "5.60.0" + +"@typescript-eslint/type-utils@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.60.0.tgz#69b09087eb12d7513d5b07747e7d47f5533aa228" + integrity sha512-X7NsRQddORMYRFH7FWo6sA9Y/zbJ8s1x1RIAtnlj6YprbToTiQnM6vxcMu7iYhdunmoC0rUWlca13D5DVHkK2g== + dependencies: + "@typescript-eslint/typescript-estree" "5.60.0" + "@typescript-eslint/utils" "5.60.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.60.0.tgz#3179962b28b4790de70e2344465ec97582ce2558" + integrity sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA== + +"@typescript-eslint/typescript-estree@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz#4ddf1a81d32a850de66642d9b3ad1e3254fb1600" + integrity sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ== + dependencies: + "@typescript-eslint/types" "5.60.0" + "@typescript-eslint/visitor-keys" "5.60.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.60.0.tgz#4667c5aece82f9d4f24a667602f0f300864b554c" + integrity sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.60.0" + "@typescript-eslint/types" "5.60.0" + "@typescript-eslint/typescript-estree" "5.60.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.0.tgz#b48b29da3f5f31dd1656281727004589d2722a66" + integrity sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw== + dependencies: + "@typescript-eslint/types" "5.60.0" + eslint-visitor-keys "^3.3.0" + +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + +"@yarnpkg/parsers@^3.0.0-rc.18": + version "3.0.0-rc.46" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz#03f8363111efc0ea670e53b0282cd3ef62de4e01" + integrity sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q== + dependencies: + js-yaml "^3.10.0" + tslib "^2.4.0" + +"@zkochan/js-yaml@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826" + integrity sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg== + dependencies: + argparse "^2.0.1" + +JSONStream@^1.0.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +abbrev@1, abbrev@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +abbrev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" + integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +absolute-path@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/absolute-path/-/absolute-path-0.0.0.tgz#a78762fbdadfb5297be99b15d35a785b2f095bf7" + integrity sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA== + +accepts@^1.3.7, accepts@~1.3.5, accepts@~1.3.7: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1, acorn@^8.8.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" + integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== + +add-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" + integrity sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ== + +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.2.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" + integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== + dependencies: + debug "^4.1.0" + depd "^2.0.0" + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +anser@^1.4.9: + version "1.4.10" + resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b" + integrity sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww== + +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-fragments@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansi-fragments/-/ansi-fragments-0.2.1.tgz#24409c56c4cc37817c3d7caa99d8969e2de5a05e" + integrity sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w== + dependencies: + colorette "^1.0.7" + slice-ansi "^2.0.0" + strip-ansi "^5.0.0" + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.0, ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +appdirsjs@^1.2.4: + version "1.2.7" + resolved "https://registry.yarnpkg.com/appdirsjs/-/appdirsjs-1.2.7.tgz#50b4b7948a26ba6090d4aede2ae2dc2b051be3b3" + integrity sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw== + +"aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +are-we-there-yet@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-4.0.0.tgz#3ff397dc14f08b52dd8b2a64d3cee154ab8760d2" + integrity sha512-nSXlV+u3vtVjRgihdTzbfWYzxPWGo424zPgQbHD0ZqIla3jqYAewDcvee0Ua2hjS5IfTAmjGlx1Jf0PKwjZDEw== + dependencies: + delegates "^1.0.0" + readable-stream "^4.1.0" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== + +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + +array-differ@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" + integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== + +array-ify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" + integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== + +array-includes@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" + integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + get-intrinsic "^1.1.3" + is-string "^1.0.7" + +array-index@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-index/-/array-index-1.0.0.tgz#ec56a749ee103e4e08c790b9c353df16055b97f9" + integrity sha512-jesyNbBkLQgGZMSwA1FanaFjalb1mZUGxGeUEkSDidzgrbjBGhvizJkaItdhkt8eIHFOJC7nDsrXk+BaehTdRw== + dependencies: + debug "^2.2.0" + es6-symbol "^3.0.2" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== + +array.prototype.flat@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" + integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" + integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== + +arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== + +ast-types@0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd" + integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA== + dependencies: + tslib "^2.0.1" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +async@^2.4.0: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + +async@^3.2.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +axios@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" + integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +babel-core@^7.0.0-bridge.0: + version "7.0.0-bridge.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" + integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== + +babel-jest@^28.1.1, babel-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5" + integrity sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q== + dependencies: + "@jest/transform" "^28.1.3" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^28.1.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz#1952c4d0ea50f2d6d794353762278d1d8cca3fbe" + integrity sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-module-resolver@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz#22a4f32f7441727ec1fbf4967b863e1e3e9f33e2" + integrity sha512-MlX10UDheRr3lb3P0WcaIdtCSRlxdQsB1sBqL7W0raF070bGl1HQQq5K3T2vf2XAYie+ww+5AKC/WrkjRO2knA== + dependencies: + find-babel-config "^1.2.0" + glob "^7.1.6" + pkg-up "^3.1.0" + reselect "^4.0.0" + resolve "^1.13.1" + +babel-plugin-polyfill-corejs2@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd" + integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.4.0" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a" + integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.0" + core-js-compat "^3.30.1" + +babel-plugin-polyfill-regenerator@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380" + integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.4.0" + +babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf" + integrity sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ== + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-fbjs@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz#38a14e5a7a3b285a3f3a86552d650dca5cf6111c" + integrity sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow== + dependencies: + "@babel/plugin-proposal-class-properties" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-syntax-class-properties" "^7.0.0" + "@babel/plugin-syntax-flow" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-block-scoped-functions" "^7.0.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.0.0" + "@babel/plugin-transform-flow-strip-types" "^7.0.0" + "@babel/plugin-transform-for-of" "^7.0.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-member-expression-literals" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-object-super" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-property-literals" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-template-literals" "^7.0.0" + babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" + +babel-preset-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz#5dfc20b99abed5db994406c2b9ab94c73aaa419d" + integrity sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A== + dependencies: + babel-plugin-jest-hoist "^28.1.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.1.2, base64-js@^1.3.1, base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +base64url@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +before-after-hook@^2.2.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== + +big-integer@1.6.x: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + +bin-links@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-4.0.1.tgz#afeb0549e642f61ff889b58ea2f8dca78fb9d8d3" + integrity sha512-bmFEM39CyX336ZGGRsGPlc6jZHriIoHacOQcTt72MktIjpPhZoP4te2jOyUXF3BLILmJ8aNLncoPVeIIFlrDeA== + dependencies: + cmd-shim "^6.0.0" + npm-normalize-package-bin "^3.0.0" + read-cmd-shim "^4.0.0" + write-file-atomic "^5.0.0" + +bl@^4.0.3, bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bplist-creator@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.1.0.tgz#018a2d1b587f769e379ef5519103730f8963ba1e" + integrity sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg== + dependencies: + stream-buffers "2.2.x" + +bplist-parser@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.1.tgz#e1c90b2ca2a9f9474cc72f6862bbf3fee8341fd1" + integrity sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA== + dependencies: + big-integer "1.6.x" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.21.3, browserslist@^4.21.5: + version "4.21.9" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" + integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== + dependencies: + caniuse-lite "^1.0.30001503" + electron-to-chromium "^1.4.431" + node-releases "^2.0.12" + update-browserslist-db "^1.0.11" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +builtins@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" + integrity sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ== + +builtins@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" + integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== + dependencies: + semver "^7.0.0" + +byte-size@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-7.0.0.tgz#36528cd1ca87d39bd9abd51f5715dc93b6ceb032" + integrity sha512-NNiBxKgxybMBtWdmvx7ZITJi4ZG+CYUgwOSZTfqB1qogkRHrhbQE/R2r5Fh94X+InN5MCYz6SvB/ejHMj/HbsQ== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +cacache@^16.1.0: + version "16.1.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" + integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== + dependencies: + "@npmcli/fs" "^2.1.0" + "@npmcli/move-file" "^2.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + glob "^8.0.1" + infer-owner "^1.0.4" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + unique-filename "^2.0.0" + +cacache@^17.0.0, cacache@^17.0.4: + version "17.1.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-17.1.3.tgz#c6ac23bec56516a7c0c52020fd48b4909d7c7044" + integrity sha512-jAdjGxmPxZh0IipMdR7fK/4sDSrHMLUV0+GvVUsjwyGNKHsh79kW/otg+GkbXwl6Uzvy9wsvHOX4nUoWldeZMg== + dependencies: + "@npmcli/fs" "^3.1.0" + fs-minipass "^3.0.0" + glob "^10.2.2" + lru-cache "^7.7.1" + minipass "^5.0.0" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^4.0.0" + ssri "^10.0.0" + tar "^6.1.11" + unique-filename "^3.0.0" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.0.0, camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001503: + version "1.0.30001507" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001507.tgz#fae53f6286e7564783eadea9b447819410a59534" + integrity sha512-SFpUDoSLCaE5XYL2jfqe9ova/pbQHEmbheDf5r4diNwbAgR3qxM9NQtfsiSscjqoya5K7kFcHPUQ+VsUkIJR4A== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +chalk@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +ci-info@^3.2.0, ci-info@^3.6.1: + version "3.8.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== + +cjs-module-lexer@^1.0.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@3.1.0, cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + +cli-spinners@^2.0.0, cli-spinners@^2.5.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.0.tgz#5881d0ad96381e117bbe07ad91f2008fe6ffd8db" + integrity sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g== + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone-deep@4.0.1, clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clone@2.x: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + +cmd-shim@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-5.0.0.tgz#8d0aaa1a6b0708630694c4dbde070ed94c707724" + integrity sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw== + dependencies: + mkdirp-infer-owner "^2.0.0" + +cmd-shim@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" + integrity sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q== + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.2, color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +colorette@^1.0.7: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +columnify@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" + integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== + dependencies: + strip-ansi "^6.0.1" + wcwidth "^1.0.0" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +command-exists@^1.2.8: + version "1.2.9" + resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" + integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== + +commander@^2.19.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@~2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" + integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== + +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +compare-func@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" + integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== + dependencies: + array-ify "^1.0.0" + dot-prop "^5.1.0" + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.1: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concat-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + +config-chain@1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" + integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +connect@^3.6.5: + version "3.7.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" + integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +conventional-changelog-angular@5.0.12: + version "5.0.12" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz#c979b8b921cbfe26402eb3da5bbfda02d865a2b9" + integrity sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw== + dependencies: + compare-func "^2.0.0" + q "^1.5.1" + +conventional-changelog-core@4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz#e50d047e8ebacf63fac3dc67bf918177001e1e9f" + integrity sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg== + dependencies: + add-stream "^1.0.0" + conventional-changelog-writer "^5.0.0" + conventional-commits-parser "^3.2.0" + dateformat "^3.0.0" + get-pkg-repo "^4.0.0" + git-raw-commits "^2.0.8" + git-remote-origin-url "^2.0.0" + git-semver-tags "^4.1.1" + lodash "^4.17.15" + normalize-package-data "^3.0.0" + q "^1.5.1" + read-pkg "^3.0.0" + read-pkg-up "^3.0.0" + through2 "^4.0.0" + +conventional-changelog-preset-loader@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz#14a855abbffd59027fd602581f1f34d9862ea44c" + integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== + +conventional-changelog-writer@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz#e0757072f045fe03d91da6343c843029e702f359" + integrity sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ== + dependencies: + conventional-commits-filter "^2.0.7" + dateformat "^3.0.0" + handlebars "^4.7.7" + json-stringify-safe "^5.0.1" + lodash "^4.17.15" + meow "^8.0.0" + semver "^6.0.0" + split "^1.0.0" + through2 "^4.0.0" + +conventional-commits-filter@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" + integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== + dependencies: + lodash.ismatch "^4.4.0" + modify-values "^1.0.0" + +conventional-commits-parser@^3.2.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz#a7d3b77758a202a9b2293d2112a8d8052c740972" + integrity sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q== + dependencies: + JSONStream "^1.0.4" + is-text-path "^1.0.1" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +conventional-recommended-bump@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz#cfa623285d1de554012f2ffde70d9c8a22231f55" + integrity sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw== + dependencies: + concat-stream "^2.0.0" + conventional-changelog-preset-loader "^2.3.4" + conventional-commits-filter "^2.0.7" + conventional-commits-parser "^3.2.0" + git-raw-commits "^2.0.8" + git-semver-tags "^4.1.1" + meow "^8.0.0" + q "^1.5.1" + +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== + +core-js-compat@^3.30.1: + version "3.31.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1" + integrity sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw== + dependencies: + browserslist "^4.21.5" + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csstype@^3.0.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +dargs@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" + integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== + +dateformat@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" + integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== + +dayjs@^1.8.15: + version "1.11.8" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.8.tgz#4282f139c8c19dd6d0c7bd571e30c2d0ba7698ea" + integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ== + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^3.1.0, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decamelize-keys@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" + integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +decode-uri-component@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + +dedent@0.7.0, dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.3.0.tgz#d3c47fd6f3a93d517b14426b0628a17b0125f5f7" + integrity sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +del@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" + integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +denodeify@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" + integrity sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg== + +depd@2.0.0, depd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== + +detect-libc@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + +diff-sequences@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" + integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dot-prop@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== + dependencies: + is-obj "^2.0.0" + +dot-prop@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dotenv@~10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +duplexer@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +ejs@^3.1.7: + version "3.1.9" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" + integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== + dependencies: + jake "^10.8.5" + +electron-to-chromium@^1.4.431: + version "1.4.440" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.440.tgz#d3b1eeb36b717eb479a240c0406ac1fa67901762" + integrity sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw== + +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enquirer@~2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +envinfo@^7.7.2, envinfo@^7.7.4: + version "7.9.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.9.0.tgz#47594a13081be0d9be6e513534e8c58dbb26c7a1" + integrity sha512-RODB4txU+xImYDemN5DqaKC0CHk05XSVkOX4pq0hK26Qx+1LChkuOyUDlGEjYb3ACr0n9qBhFjg37hQuJvpkRQ== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + +errorhandler@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/errorhandler/-/errorhandler-1.5.1.tgz#b9ba5d17cf90744cd1e851357a6e75bf806a9a91" + integrity sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A== + dependencies: + accepts "~1.3.7" + escape-html "~1.0.3" + +es-abstract@^1.19.0, es-abstract@^1.20.4: + version "1.21.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" + integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== + dependencies: + array-buffer-byte-length "^1.0.0" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.2.0" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.10" + is-weakref "^1.0.2" + object-inspect "^1.12.3" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.4.3" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.7" + string.prototype.trimend "^1.0.6" + string.prototype.trimstart "^1.0.6" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.9" + +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.0.2, es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^8.3.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== + +eslint-import-resolver-node@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" + integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== + dependencies: + debug "^3.2.7" + is-core-module "^2.11.0" + resolve "^1.22.1" + +eslint-import-resolver-typescript@^2.5.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.1.tgz#a90a4a1c80da8d632df25994c4c5fdcdd02b8751" + integrity sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ== + dependencies: + debug "^4.3.4" + glob "^7.2.0" + is-glob "^4.0.3" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" + +eslint-module-utils@^2.7.4: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.25.4, eslint-plugin-import@^2.26.0: + version "2.27.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" + integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.7.4" + has "^1.0.3" + is-core-module "^2.11.0" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.values "^1.1.6" + resolve "^1.22.1" + semver "^6.3.0" + tsconfig-paths "^3.14.1" + +eslint-plugin-prettier@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" + integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" + integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== + +eslint@^8.8.0: + version "8.43.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.43.0.tgz#3e8c6066a57097adfd9d390b8fc93075f257a094" + integrity sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.0.3" + "@eslint/js" "8.43.0" + "@humanwhocodes/config-array" "^0.11.10" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.1" + espree "^9.5.2" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + +espree@^9.5.2: + version "9.5.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" + integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0, esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +event-target-shim@^5.0.0, event-target-shim@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +eventemitter3@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +exec-sh@^0.3.2: + version "0.3.6" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" + integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== + +execa@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" + integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.3.tgz#90a7c1a124f1824133dd4533cce2d2bdcb6603ec" + integrity sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g== + dependencies: + "@jest/expect-utils" "^28.1.3" + jest-get-type "^28.0.2" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + +exponential-backoff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" + integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== + +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-glob@^3.2.9: + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-text-encoding@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" + integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +figures@3.2.0, figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +file-url@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/file-url/-/file-url-3.0.0.tgz#247a586a746ce9f7a8ed05560290968afc262a77" + integrity sha512-g872QGsHexznxkIAdK8UiZRe7SkE6kvylShU4Nsj8NvfvZag7S0QuQ4IgvPDkk75HxgjIVDwycFTDAgIiO4nDA== + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-babel-config@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2" + integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA== + dependencies: + json5 "^0.5.1" + path-exists "^3.0.0" + +find-cache-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-up@5.0.0, find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + +flow-parser@0.*: + version "0.209.1" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.209.1.tgz#b3ea7566469113abc660b4833a38ae8c599f0719" + integrity sha512-7YdhWfCsLRn31or7oK9M7Svd4WFk1qfj6VIFY/9S6HRyzBBiLlobNbUbitZHfdi0nhkik5S498UmF3phdzM6ug== + +flow-parser@^0.121.0: + version "0.121.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.121.0.tgz#9f9898eaec91a9f7c323e9e992d81ab5c58e618f" + integrity sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg== + +follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== + +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@9.1.0, fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" + integrity sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + +fs-extra@^11.1.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" + integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-minipass@^2.0.0, fs-minipass@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-minipass@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.2.tgz#5b383858efa8c1eb8c33b39e994f7e8555b8b3a3" + integrity sha512-2GAfyfoaCDRrM6jaOS3UsBts8yJ55VioXdWcOL7dK9zdAuKT71+WBA4ifnNYqVjYv+4SsPxjK0JT4yIIn4cA/g== + dependencies: + minipass "^5.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.1.2, fsevents@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + +functions-have-names@^1.2.2, functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +gauge@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-5.0.1.tgz#1efc801b8ff076b86ef3e9a7a280a975df572112" + integrity sha512-CmykPMJGuNan/3S4kZOpvvPYSNqSHANiWnh9XcMU2pSjtBfF0XzZ2p1bFAxTbnFxyBuPxQYHhzwaoOmUdqzvxQ== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^4.0.1" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1, get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-proto "^1.0.1" + has-symbols "^1.0.3" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-pkg-repo@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz#75973e1c8050c73f48190c52047c4cee3acbf385" + integrity sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA== + dependencies: + "@hutson/parse-repository-url" "^3.0.0" + hosted-git-info "^4.0.0" + through2 "^2.0.0" + yargs "^16.2.0" + +get-port@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + +get-stream@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" + integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +get-symbol-from-current-process-h@^1.0.1, get-symbol-from-current-process-h@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz#510af52eaef873f7028854c3377f47f7bb200265" + integrity sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw== + +get-uv-event-loop-napi-h@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz#42b0b06b74c3ed21fbac8e7c72845fdb7a200208" + integrity sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg== + dependencies: + get-symbol-from-current-process-h "^1.0.1" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== + +git-raw-commits@^2.0.8: + version "2.0.11" + resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723" + integrity sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A== + dependencies: + dargs "^7.0.0" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +git-remote-origin-url@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" + integrity sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw== + dependencies: + gitconfiglocal "^1.0.0" + pify "^2.3.0" + +git-semver-tags@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-4.1.1.tgz#63191bcd809b0ec3e151ba4751c16c444e5b5780" + integrity sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA== + dependencies: + meow "^8.0.0" + semver "^6.0.0" + +git-up@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/git-up/-/git-up-7.0.0.tgz#bace30786e36f56ea341b6f69adfd83286337467" + integrity sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ== + dependencies: + is-ssh "^1.4.0" + parse-url "^8.1.0" + +git-url-parse@13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-13.1.0.tgz#07e136b5baa08d59fabdf0e33170de425adf07b4" + integrity sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA== + dependencies: + git-up "^7.0.0" + +gitconfiglocal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" + integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ== + dependencies: + ini "^1.3.2" + +glob-parent@5.1.2, glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^10.2.2: + version "10.3.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.0.tgz#763d02a894f3cdfc521b10bbbbc8e0309e750cce" + integrity sha512-AQ1/SB9HH0yCx1jXAT4vmCbTOPe5RQ+kCurjbel5xSCGhebumUv+GJZfa1rEqor3XIViqwSEmlkZCQD43RWrBg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.0.3" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2" + path-scurry "^1.7.0" + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +glob@^9.2.0: + version "9.3.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" + integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== + dependencies: + fs.realpath "^1.0.0" + minimatch "^8.0.2" + minipass "^4.2.4" + path-scurry "^1.6.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@11.1.0, globby@^11.0.1, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@4.2.10: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +handlebars@^4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-unicode@2.0.1, has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hermes-engine@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/hermes-engine/-/hermes-engine-0.9.0.tgz#84d9cfe84e8f6b1b2020d6e71b350cec84ed982f" + integrity sha512-r7U+Y4P2Qg/igFVZN+DpT7JFfXUn1MM4dFne8aW+cCrF6RRymof+VqrUHs1kl07j8h8V2CNesU19RKgWbr3qPw== + +hermes-parser@0.4.7: + version "0.4.7" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.4.7.tgz#410f5129d57183784d205a0538e6fbdcf614c9ea" + integrity sha512-jc+zCtXbtwTiXoMAoXOHepxAaGVFIp89wwE9qcdwnMd/uGVEtPoY8FaFSsx0ThPvyKirdR2EsIIDVrpbSXz1Ag== + +hermes-profile-transformer@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz#bd0f5ecceda80dd0ddaae443469ab26fb38fc27b" + integrity sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ== + dependencies: + source-map "^0.7.3" + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hosted-git-info@^3.0.6: + version "3.0.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" + integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== + dependencies: + lru-cache "^6.0.0" + +hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== + dependencies: + lru-cache "^6.0.0" + +hosted-git-info@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-5.2.1.tgz#0ba1c97178ef91f3ab30842ae63d6a272341156f" + integrity sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw== + dependencies: + lru-cache "^7.5.1" + +hosted-git-info@^6.0.0, hosted-git-info@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-6.1.1.tgz#629442c7889a69c05de604d52996b74fe6f26d58" + integrity sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w== + dependencies: + lru-cache "^7.5.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13, ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== + dependencies: + minimatch "^5.0.1" + +ignore-walk@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.3.tgz#0fcdb6decaccda35e308a7b0948645dd9523b7bb" + integrity sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA== + dependencies: + minimatch "^9.0.0" + +ignore@^5.0.4, ignore@^5.2.0: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +image-size@^0.6.0: + version "0.6.3" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.6.3.tgz#e7e5c65bb534bd7cdcedd6cb5166272a85f75fb2" + integrity sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA== + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg== + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.2, ini@^1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +init-package-json@3.0.2, init-package-json@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-3.0.2.tgz#f5bc9bac93f2bdc005778bc2271be642fecfcd69" + integrity sha512-YhlQPEjNFqlGdzrBfDNRLhvoSgX7iQRgSxgsNknRQ9ITXFT7UMfVMWhBTOh2Y+25lRnGrv5Xz8yZwQ3ACR6T3A== + dependencies: + npm-package-arg "^9.0.1" + promzard "^0.3.0" + read "^1.0.7" + read-package-json "^5.0.0" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^4.0.0" + +inquirer@8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" + integrity sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^7.0.0" + +inquirer@^8.2.4: + version "8.2.5" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.5.tgz#d8654a7542c35a9b9e069d27e2df4858784d54f8" + integrity sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^7.0.0" + +internal-slot@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== + dependencies: + get-intrinsic "^1.2.0" + has "^1.0.3" + side-channel "^1.0.4" + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +ip@^1.1.5: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" + integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== + +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-ci@2.0.0, is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-core-module@^2.11.0, is-core-module@^2.5.0, is-core-module@^2.8.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" + integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== + dependencies: + has "^1.0.3" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.2, is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-ssh@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" + integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== + dependencies: + protocols "^2.0.1" + +is-stream@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-text-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" + integrity sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w== + dependencies: + text-extensions "^1.0.0" + +is-typed-array@^1.1.10, is-typed-array@^1.1.9: + version "1.1.10" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" + integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^2.0.3: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.1.tgz#655e8cf025d872c9c03d3eb63e8f0c024fef16a6" + integrity sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jake@^10.8.5: + version "10.8.7" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f" + integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +jest-changed-files@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.1.3.tgz#d9aeee6792be3686c47cb988a8eaf82ff4238831" + integrity sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA== + dependencies: + execa "^5.0.0" + p-limit "^3.1.0" + +jest-circus@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.3.tgz#d14bd11cf8ee1a03d69902dc47b6bd4634ee00e4" + integrity sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + p-limit "^3.1.0" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.3.tgz#558b33c577d06de55087b8448d373b9f654e46b2" + integrity sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ== + dependencies: + "@jest/core" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.3.tgz#e315e1f73df3cac31447eed8b8740a477392ec60" + integrity sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^28.1.3" + "@jest/types" "^28.1.3" + babel-jest "^28.1.3" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^28.1.3" + jest-environment-node "^28.1.3" + jest-get-type "^28.0.2" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-runner "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^28.1.3" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-diff@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.3.tgz#948a192d86f4e7a64c5264ad4da4877133d8792f" + integrity sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw== + dependencies: + chalk "^4.0.0" + diff-sequences "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-docblock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" + integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== + dependencies: + detect-newline "^3.0.0" + +jest-each@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.3.tgz#bdd1516edbe2b1f3569cfdad9acd543040028f81" + integrity sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g== + dependencies: + "@jest/types" "^28.1.3" + chalk "^4.0.0" + jest-get-type "^28.0.2" + jest-util "^28.1.3" + pretty-format "^28.1.3" + +jest-environment-node@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.3.tgz#7e74fe40eb645b9d56c0c4b70ca4357faa349be5" + integrity sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + +jest-get-type@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" + integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== + +jest-haste-map@^26.5.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== + dependencies: + "@jest/types" "^26.6.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + +jest-haste-map@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.3.tgz#abd5451129a38d9841049644f34b034308944e2b" + integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== + dependencies: + "@jest/types" "^28.1.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + jest-worker "^28.1.3" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz#a6685d9b074be99e3adee816ce84fd30795e654d" + integrity sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA== + dependencies: + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-matcher-utils@^27.0.0: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-matcher-utils@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz#5a77f1c129dd5ba3b4d7fc20728806c78893146e" + integrity sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw== + dependencies: + chalk "^4.0.0" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-message-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.3.tgz#232def7f2e333f1eecc90649b5b94b0055e7c43d" + integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^28.1.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.3.tgz#d4e9b1fc838bea595c77ab73672ebf513ab249da" + integrity sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + +jest-regex-util@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" + integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== + +jest-resolve-dependencies@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz#8c65d7583460df7275c6ea2791901fa975c1fe66" + integrity sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA== + dependencies: + jest-regex-util "^28.0.2" + jest-snapshot "^28.1.3" + +jest-resolve@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.3.tgz#cfb36100341ddbb061ec781426b3c31eb51aa0a8" + integrity sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-pnp-resolver "^1.2.2" + jest-util "^28.1.3" + jest-validate "^28.1.3" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.3.tgz#5eee25febd730b4713a2cdfd76bdd5557840f9a1" + integrity sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/environment" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.10.2" + graceful-fs "^4.2.9" + jest-docblock "^28.1.1" + jest-environment-node "^28.1.3" + jest-haste-map "^28.1.3" + jest-leak-detector "^28.1.3" + jest-message-util "^28.1.3" + jest-resolve "^28.1.3" + jest-runtime "^28.1.3" + jest-util "^28.1.3" + jest-watcher "^28.1.3" + jest-worker "^28.1.3" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.3.tgz#a57643458235aa53e8ec7821949e728960d0605f" + integrity sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/globals" "^28.1.3" + "@jest/source-map" "^28.1.2" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + +jest-snapshot@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.3.tgz#17467b3ab8ddb81e2f605db05583d69388fc0668" + integrity sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/babel__traverse" "^7.0.6" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^28.1.3" + graceful-fs "^4.2.9" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + jest-haste-map "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + natural-compare "^1.4.0" + pretty-format "^28.1.3" + semver "^7.3.5" + +jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + +jest-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" + integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-util@^29.0.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" + integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== + dependencies: + "@jest/types" "^29.5.0" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^26.5.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== + dependencies: + "@jest/types" "^26.6.2" + camelcase "^6.0.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" + leven "^3.1.0" + pretty-format "^26.6.2" + +jest-validate@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.3.tgz#e322267fd5e7c64cea4629612c357bbda96229df" + integrity sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA== + dependencies: + "@jest/types" "^28.1.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^28.0.2" + leven "^3.1.0" + pretty-format "^28.1.3" + +jest-watcher@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.3.tgz#c6023a59ba2255e3b4c57179fc94164b3e73abd4" + integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== + dependencies: + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.10.2" + jest-util "^28.1.3" + string-length "^4.0.1" + +jest-worker@^26.0.0, jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest-worker@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" + integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^28.0.2: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.3.tgz#e9c6a7eecdebe3548ca2b18894a50f45b36dfc6b" + integrity sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA== + dependencies: + "@jest/core" "^28.1.3" + "@jest/types" "^28.1.3" + import-local "^3.0.2" + jest-cli "^28.1.3" + +jetifier@^1.6.2: + version "1.6.8" + resolved "https://registry.yarnpkg.com/jetifier/-/jetifier-1.6.8.tgz#e88068697875cbda98c32472902c4d3756247798" + integrity sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw== + +joi@^17.2.1: + version "17.9.2" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.9.2.tgz#8b2e4724188369f55451aebd1d0b1d9482470690" + integrity sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.3" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +js-yaml@^3.10.0, js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsc-android@^250230.2.1: + version "250230.2.1" + resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-250230.2.1.tgz#3790313a970586a03ab0ad47defbc84df54f1b83" + integrity sha512-KmxeBlRjwoqCnBBKGsihFtvsBHyUFlBxJPK4FzeYcIuBfdjv6jFys44JITAgSTbQD+vIdwMEfyZklsuQX0yI1Q== + +jscodeshift@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/jscodeshift/-/jscodeshift-0.11.0.tgz#4f95039408f3f06b0e39bb4d53bc3139f5330e2f" + integrity sha512-SdRK2C7jjs4k/kT2mwtO07KJN9RnjxtKn03d9JVj6c3j9WwaLcFYsICYDnLAzY0hp+wG2nxl+Cm2jWLiNVYb8g== + dependencies: + "@babel/core" "^7.1.6" + "@babel/parser" "^7.1.6" + "@babel/plugin-proposal-class-properties" "^7.1.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.1.0" + "@babel/plugin-proposal-optional-chaining" "^7.1.0" + "@babel/plugin-transform-modules-commonjs" "^7.1.0" + "@babel/preset-flow" "^7.0.0" + "@babel/preset-typescript" "^7.1.0" + "@babel/register" "^7.0.0" + babel-core "^7.0.0-bridge.0" + colors "^1.1.2" + flow-parser "0.*" + graceful-fs "^4.2.4" + micromatch "^3.1.10" + neo-async "^2.5.0" + node-dir "^0.1.17" + recast "^0.20.3" + temp "^0.8.1" + write-file-atomic "^2.3.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-parse-even-better-errors@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz#2cb2ee33069a78870a0c7e3da560026b89669cf7" + integrity sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc-parser@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + integrity sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonparse@^1.2.0, jsonparse@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +just-diff-apply@^5.2.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-5.5.0.tgz#771c2ca9fa69f3d2b54e7c3f5c1dfcbcc47f9f0f" + integrity sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw== + +just-diff@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-6.0.2.tgz#03b65908543ac0521caf6d8eb85035f7d27ea285" + integrity sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA== + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + integrity sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw== + optionalDependencies: + graceful-fs "^4.1.9" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +lerna@^6.4.1: + version "6.6.2" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-6.6.2.tgz#ad921f913aca4e7307123a598768b6f15ca5804f" + integrity sha512-W4qrGhcdutkRdHEaDf9eqp7u4JvI+1TwFy5woX6OI8WPe4PYBdxuILAsvhp614fUG41rKSGDKlOh+AWzdSidTg== + dependencies: + "@lerna/child-process" "6.6.2" + "@lerna/create" "6.6.2" + "@lerna/legacy-package-management" "6.6.2" + "@npmcli/arborist" "6.2.3" + "@npmcli/run-script" "4.1.7" + "@nrwl/devkit" ">=15.5.2 < 16" + "@octokit/plugin-enterprise-rest" "6.0.1" + "@octokit/rest" "19.0.3" + byte-size "7.0.0" + chalk "4.1.0" + clone-deep "4.0.1" + cmd-shim "5.0.0" + columnify "1.6.0" + config-chain "1.1.12" + conventional-changelog-angular "5.0.12" + conventional-changelog-core "4.2.4" + conventional-recommended-bump "6.1.0" + cosmiconfig "7.0.0" + dedent "0.7.0" + dot-prop "6.0.1" + envinfo "^7.7.4" + execa "5.0.0" + fs-extra "9.1.0" + get-port "5.1.1" + get-stream "6.0.0" + git-url-parse "13.1.0" + glob-parent "5.1.2" + globby "11.1.0" + graceful-fs "4.2.10" + has-unicode "2.0.1" + import-local "^3.0.2" + init-package-json "3.0.2" + inquirer "^8.2.4" + is-ci "2.0.0" + is-stream "2.0.0" + js-yaml "^4.1.0" + libnpmaccess "^6.0.3" + libnpmpublish "7.1.4" + load-json-file "6.2.0" + make-dir "3.1.0" + minimatch "3.0.5" + multimatch "5.0.0" + node-fetch "2.6.7" + npm-package-arg "8.1.1" + npm-packlist "5.1.1" + npm-registry-fetch "^14.0.3" + npmlog "^6.0.2" + nx ">=15.5.2 < 16" + p-map "4.0.0" + p-map-series "2.1.0" + p-pipe "3.1.0" + p-queue "6.6.2" + p-reduce "2.1.0" + p-waterfall "2.1.1" + pacote "15.1.1" + pify "5.0.0" + read-cmd-shim "3.0.0" + read-package-json "5.0.1" + resolve-from "5.0.0" + rimraf "^4.4.1" + semver "^7.3.8" + signal-exit "3.0.7" + slash "3.0.0" + ssri "9.0.1" + strong-log-transformer "2.1.0" + tar "6.1.11" + temp-dir "1.0.0" + typescript "^3 || ^4" + upath "^2.0.1" + uuid "8.3.2" + validate-npm-package-license "3.0.4" + validate-npm-package-name "4.0.0" + write-file-atomic "4.0.1" + write-pkg "4.0.0" + yargs "16.2.0" + yargs-parser "20.2.4" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +libnpmaccess@^6.0.3: + version "6.0.4" + resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-6.0.4.tgz#2dd158bd8a071817e2207d3b201d37cf1ad6ae6b" + integrity sha512-qZ3wcfIyUoW0+qSFkMBovcTrSGJ3ZeyvpR7d5N9pEYv/kXs8sHP2wiqEIXBKLFrZlmM0kR0RJD7mtfLngtlLag== + dependencies: + aproba "^2.0.0" + minipass "^3.1.1" + npm-package-arg "^9.0.1" + npm-registry-fetch "^13.0.0" + +libnpmpublish@7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-7.1.4.tgz#a0d138e00e52a0c71ffc82273acf0082fc2dfb36" + integrity sha512-mMntrhVwut5prP4rJ228eEbEyvIzLWhqFuY90j5QeXBCTT2pWSMno7Yo2S2qplPUr02zPurGH4heGLZ+wORczg== + dependencies: + ci-info "^3.6.1" + normalize-package-data "^5.0.0" + npm-package-arg "^10.1.0" + npm-registry-fetch "^14.0.3" + proc-log "^3.0.0" + semver "^7.3.7" + sigstore "^1.4.0" + ssri "^10.0.1" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lines-and-columns@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-2.0.3.tgz#b2f0badedb556b747020ab8ea7f0373e22efac1b" + integrity sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w== + +load-json-file@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" + integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== + dependencies: + graceful-fs "^4.1.15" + parse-json "^5.0.0" + strip-bom "^4.0.0" + type-fest "^0.6.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.ismatch@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" + integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g== + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== + +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +logkitty@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/logkitty/-/logkitty-0.7.1.tgz#8e8d62f4085a826e8d38987722570234e33c6aa7" + integrity sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ== + dependencies: + ansi-fragments "^0.2.1" + dayjs "^1.8.15" + yargs "^15.1.0" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +lru-cache@^9.1.1: + version "9.1.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.2.tgz#255fdbc14b75589d6d0e73644ca167a8db506835" + integrity sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ== + +make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-dir@^2.0.0, make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-error@1.x, make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +make-fetch-happen@^10.0.6: + version "10.2.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" + integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== + dependencies: + agentkeepalive "^4.2.1" + cacache "^16.1.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-fetch "^2.0.3" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + socks-proxy-agent "^7.0.0" + ssri "^9.0.0" + +make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1, make-fetch-happen@^11.0.3, make-fetch-happen@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz#85ceb98079584a9523d4bf71d32996e7e208549f" + integrity sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w== + dependencies: + agentkeepalive "^4.2.1" + cacache "^17.0.0" + http-cache-semantics "^4.1.1" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^7.7.1" + minipass "^5.0.0" + minipass-fetch "^3.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + socks-proxy-agent "^7.0.0" + ssri "^10.0.0" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== + +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== + +map-obj@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" + integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== + dependencies: + object-visit "^1.0.0" + +meow@^8.0.0: + version "8.1.2" + resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" + integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +metro-babel-register@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-babel-register/-/metro-babel-register-0.66.2.tgz#c6bbe36c7a77590687ccd74b425dc020d17d05af" + integrity sha512-3F+vsVubUPJYKfVMeol8/7pd8CC287Rw92QYzJD8LEmI980xcgwMUEVBZ0UIAUwlLgiJG/f4Mwhuji2EeBXrPg== + dependencies: + "@babel/core" "^7.14.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-proposal-optional-chaining" "^7.0.0" + "@babel/plugin-syntax-class-properties" "^7.0.0" + "@babel/plugin-transform-flow-strip-types" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/register" "^7.0.0" + escape-string-regexp "^1.0.5" + +metro-babel-transformer@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.66.2.tgz#fce0a3e314d28a5e7141c135665e1cc9b8e7ce86" + integrity sha512-aJ/7fc/Xkofw8Fqa51OTDhBzBz26mmpIWrXAZcPdQ8MSTt883EWncxeCEjasc79NJ89BRi7sOkkaWZo2sXlKvw== + dependencies: + "@babel/core" "^7.14.0" + hermes-parser "0.4.7" + metro-source-map "0.66.2" + nullthrows "^1.1.1" + +metro-cache-key@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.66.2.tgz#d6463d2a53e887a38419d523962cc24ea0e780b4" + integrity sha512-WtkNmRt41qOpHh1MkNA4nLiQ/m7iGL90ysSKD+fcLqlUnOBKJptPQm0ZUv8Kfqk18ddWX2KmsSbq+Sf3I6XohQ== + +metro-cache@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.66.2.tgz#e0af4e0a319898f7d42a980f7ee5da153fcfd019" + integrity sha512-5QCYJtJOHoBSbL3H4/Fpl36oA697C3oYHqsce+Hk/dh2qtODUGpS3gOBhvP1B8iB+H8jJMyR75lZq129LJEsIQ== + dependencies: + metro-core "0.66.2" + mkdirp "^0.5.1" + rimraf "^2.5.4" + +metro-config@0.66.2, metro-config@^0.66.1: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.66.2.tgz#e365acdb66ad0cda0182b9c9910760a97ee4293b" + integrity sha512-0C+PrKKIBNNzLZUKN/8ZDJS2U5FLMOTXDWbvBHIdqb6YXz8WplXR2+xlSlaSCCi5b+GR7cWFWUNeKA4GQS1/AQ== + dependencies: + cosmiconfig "^5.0.5" + jest-validate "^26.5.2" + metro "0.66.2" + metro-cache "0.66.2" + metro-core "0.66.2" + metro-runtime "0.66.2" + +metro-core@0.66.2, metro-core@^0.66.1: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.66.2.tgz#ead776a17b3e5a307e6dc22259db30bf5c7e8490" + integrity sha512-JieLZkef/516yxXYvQxWnf3OWw5rcgWRy76K8JV/wr/i8LGVGulPAXlIi445/QZzXVydzRVASKAEVqyxM5F4mA== + dependencies: + jest-haste-map "^26.5.2" + lodash.throttle "^4.1.1" + metro-resolver "0.66.2" + +metro-hermes-compiler@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-hermes-compiler/-/metro-hermes-compiler-0.66.2.tgz#30290748f83805faa601aa487632444915795823" + integrity sha512-nCVL1g9uR6vrw5+X1wjwZruRyMkndnzGRMqjqoljf+nGEqBTD607CR7elXw4fMWn/EM+1y0Vdq5altUu9LdgCA== + +metro-inspector-proxy@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-inspector-proxy/-/metro-inspector-proxy-0.66.2.tgz#a83c76bd2f2fd7b9240be92acf9a8b1d1404547a" + integrity sha512-gnLc9121eznwP0iiA9tCBW8qZjwIsCgwHWMF1g1Qaki9le9tzeJv3dK4/lFNGxyfSaLO7vahQEhsEYsiRnTROg== + dependencies: + connect "^3.6.5" + debug "^2.2.0" + ws "^1.1.5" + yargs "^15.3.1" + +metro-minify-uglify@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.66.2.tgz#6061dbee4f61e6d5bb3c100e4379ff6f2e16e42b" + integrity sha512-7TUK+L5CmB5x1PVnFbgmjzHW4CUadq9H5jgp0HfFoWT1skXAyEsx0DHkKDXwnot0khnNhBOEfl62ctQOnE110Q== + dependencies: + uglify-es "^3.1.9" + +metro-react-native-babel-preset@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.66.2.tgz#fddebcf413ad4ea617d4f47f7c1da401052de734" + integrity sha512-H/nLBAz0MgfDloSe1FjyH4EnbokHFdncyERvLPXDACY3ROVRCeUyFNo70ywRGXW2NMbrV4H7KUyU4zkfWhC2HQ== + dependencies: + "@babel/core" "^7.14.0" + "@babel/plugin-proposal-class-properties" "^7.0.0" + "@babel/plugin-proposal-export-default-from" "^7.0.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" + "@babel/plugin-proposal-optional-chaining" "^7.0.0" + "@babel/plugin-syntax-dynamic-import" "^7.0.0" + "@babel/plugin-syntax-export-default-from" "^7.0.0" + "@babel/plugin-syntax-flow" "^7.2.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-syntax-optional-chaining" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-async-to-generator" "^7.0.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.0.0" + "@babel/plugin-transform-exponentiation-operator" "^7.0.0" + "@babel/plugin-transform-flow-strip-types" "^7.0.0" + "@babel/plugin-transform-for-of" "^7.0.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-object-assign" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + "@babel/plugin-transform-regenerator" "^7.0.0" + "@babel/plugin-transform-runtime" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-sticky-regex" "^7.0.0" + "@babel/plugin-transform-template-literals" "^7.0.0" + "@babel/plugin-transform-typescript" "^7.5.0" + "@babel/plugin-transform-unicode-regex" "^7.0.0" + "@babel/template" "^7.0.0" + react-refresh "^0.4.0" + +metro-react-native-babel-transformer@0.66.2, metro-react-native-babel-transformer@^0.66.1: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.66.2.tgz#768f341e7c3d3d1c38189799c9884b90d1c32eb7" + integrity sha512-z1ab7ihIT0pJrwgi9q2IH+LcW/xUWMQ0hH+Mrk7wbKQB0RnJdXFoxphrfoVHBHMUu+TBPetUcEkKawkK1e7Cng== + dependencies: + "@babel/core" "^7.14.0" + babel-preset-fbjs "^3.4.0" + hermes-parser "0.4.7" + metro-babel-transformer "0.66.2" + metro-react-native-babel-preset "0.66.2" + metro-source-map "0.66.2" + nullthrows "^1.1.1" + +metro-resolver@0.66.2, metro-resolver@^0.66.1: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.66.2.tgz#f743ddbe7a12dd137d1f7a555732cafcaea421f8" + integrity sha512-pXQAJR/xauRf4kWFj2/hN5a77B4jLl0Fom5I3PHp6Arw/KxSBp0cnguXpGLwNQ6zQC0nxKCoYGL9gQpzMnN7Hw== + dependencies: + absolute-path "^0.0.0" + +metro-runtime@0.66.2, metro-runtime@^0.66.1: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.66.2.tgz#3409ee957b949b6c7b72ef6ed2b9af9a4f4a910e" + integrity sha512-vFhKBk2ot9FS4b+2v0OTa/guCF/QDAOJubY0CNg7PzCS5+w4y3IvZIcPX4SSS1t8pYEZBLvtdtTDarlDl81xmg== + +metro-source-map@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.66.2.tgz#b5304a282a5d55fa67b599265e9cf3217175cdd7" + integrity sha512-038tFmB7vSh73VQcDWIbr5O1m+WXWyYafDaOy+1A/2K308YP0oj33gbEgDnZsLZDwcJ+xt1x6KUEBIzlX4YGeQ== + dependencies: + "@babel/traverse" "^7.14.0" + "@babel/types" "^7.0.0" + invariant "^2.2.4" + metro-symbolicate "0.66.2" + nullthrows "^1.1.1" + ob1 "0.66.2" + source-map "^0.5.6" + vlq "^1.0.0" + +metro-symbolicate@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.66.2.tgz#addd095ce5f77e73ca21ddb5dfb396ff5d4fa041" + integrity sha512-u+DeQHyAFXVD7mVP+GST/894WHJ3i/U8oEJFnT7U3P52ZuLgX8n4tMNxhqZU12RcLR6etF8143aP0Ktx1gFLEQ== + dependencies: + invariant "^2.2.4" + metro-source-map "0.66.2" + nullthrows "^1.1.1" + source-map "^0.5.6" + through2 "^2.0.1" + vlq "^1.0.0" + +metro-transform-plugins@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.66.2.tgz#39dd044a23b1343e4f2d2ec34d08128cdf255ed4" + integrity sha512-KTvqplh0ut7oDKovvDG6yzXM02R6X+9b2oVG+qYq8Zd3aCGTi51ASx4ThCNkAHyEvCuJdYg9fxXTL+j+wvhB5w== + dependencies: + "@babel/core" "^7.14.0" + "@babel/generator" "^7.14.0" + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.14.0" + nullthrows "^1.1.1" + +metro-transform-worker@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.66.2.tgz#0a8455992132c479721accd52c9bd47deb77769e" + integrity sha512-dO4PtYOMGB7Vzte8aIzX39xytODhmbJrBYPu+zYzlDjyefJZT7BkZ0LkPIThtyJi96xWcGqi9JBSo0CeRupAHw== + dependencies: + "@babel/core" "^7.14.0" + "@babel/generator" "^7.14.0" + "@babel/parser" "^7.14.0" + "@babel/types" "^7.0.0" + babel-preset-fbjs "^3.4.0" + metro "0.66.2" + metro-babel-transformer "0.66.2" + metro-cache "0.66.2" + metro-cache-key "0.66.2" + metro-hermes-compiler "0.66.2" + metro-source-map "0.66.2" + metro-transform-plugins "0.66.2" + nullthrows "^1.1.1" + +metro@0.66.2, metro@^0.66.1: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro/-/metro-0.66.2.tgz#f21759bf00995470e7577b5b88a5277963f24492" + integrity sha512-uNsISfcQ3iKKSHoN5Q+LAh0l3jeeg7ZcNZ/4BAHGsk02erA0OP+l2m+b5qYVoPptHz9Oc3KyG5oGJoTu41pWjg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/core" "^7.14.0" + "@babel/generator" "^7.14.0" + "@babel/parser" "^7.14.0" + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.14.0" + "@babel/types" "^7.0.0" + absolute-path "^0.0.0" + accepts "^1.3.7" + async "^2.4.0" + chalk "^4.0.0" + ci-info "^2.0.0" + connect "^3.6.5" + debug "^2.2.0" + denodeify "^1.2.1" + error-stack-parser "^2.0.6" + fs-extra "^1.0.0" + graceful-fs "^4.1.3" + hermes-parser "0.4.7" + image-size "^0.6.0" + invariant "^2.2.4" + jest-haste-map "^26.5.2" + jest-worker "^26.0.0" + lodash.throttle "^4.1.1" + metro-babel-register "0.66.2" + metro-babel-transformer "0.66.2" + metro-cache "0.66.2" + metro-cache-key "0.66.2" + metro-config "0.66.2" + metro-core "0.66.2" + metro-hermes-compiler "0.66.2" + metro-inspector-proxy "0.66.2" + metro-minify-uglify "0.66.2" + metro-react-native-babel-preset "0.66.2" + metro-resolver "0.66.2" + metro-runtime "0.66.2" + metro-source-map "0.66.2" + metro-symbolicate "0.66.2" + metro-transform-plugins "0.66.2" + metro-transform-worker "0.66.2" + mime-types "^2.1.27" + mkdirp "^0.5.1" + node-fetch "^2.2.0" + nullthrows "^1.1.1" + rimraf "^2.5.4" + serialize-error "^2.1.0" + source-map "^0.5.6" + strip-ansi "^6.0.0" + temp "0.8.3" + throat "^5.0.0" + ws "^1.1.5" + yargs "^15.3.1" + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.4.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +minimatch@3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3" + integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^6.1.6: + version "6.2.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-6.2.0.tgz#2b70fd13294178c69c04dfc05aebdb97a4e79e42" + integrity sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^8.0.2: + version "8.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" + integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.0, minimatch@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.2.tgz#397e387fff22f6795844d00badc903a3d5de7057" + integrity sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg== + dependencies: + brace-expansion "^2.0.1" + +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" + integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== + dependencies: + minipass "^3.1.6" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + +minipass-fetch@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.3.tgz#d9df70085609864331b533c960fd4ffaa78d15ce" + integrity sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ== + dependencies: + minipass "^5.0.0" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-json-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" + integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== + dependencies: + jsonparse "^1.3.1" + minipass "^3.0.0" + +minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^4.0.0, minipass@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +"minipass@^5.0.0 || ^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-6.0.2.tgz#542844b6c4ce95b202c0995b0a471f1229de4c81" + integrity sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w== + +minizlib@^2.1.1, minizlib@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp-infer-owner@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" + integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== + dependencies: + chownr "^2.0.0" + infer-owner "^1.0.4" + mkdirp "^1.0.3" + +mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +modify-values@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" + integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.0.0, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multimatch@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-5.0.0.tgz#932b800963cea7a31a033328fa1e0c3a1874dbe6" + integrity sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA== + dependencies: + "@types/minimatch" "^3.0.3" + array-differ "^3.0.0" + array-union "^2.1.0" + arrify "^2.0.1" + minimatch "^3.0.4" + +mute-stream@0.0.8, mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@0.6.3, negotiator@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.5.0, neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +nocache@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f" + integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q== + +node-addon-api@^3.0.0, node-addon-api@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== + +node-cache@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d" + integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg== + dependencies: + clone "2.x" + +node-dir@^0.1.17: + version "0.1.17" + resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" + integrity sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg== + dependencies: + minimatch "^3.0.2" + +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.7: + version "2.6.11" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" + integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== + dependencies: + whatwg-url "^5.0.0" + +node-gyp-build@^4.2.1, node-gyp-build@^4.3.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" + integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== + +node-gyp@^9.0.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.0.tgz#2a7a91c7cba4eccfd95e949369f27c9ba704f369" + integrity sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg== + dependencies: + env-paths "^2.2.0" + exponential-backoff "^3.1.1" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^11.0.3" + nopt "^6.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.12: + version "2.0.12" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039" + integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ== + +node-stream-zip@^1.9.1: + version "1.15.0" + resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" + integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +nopt@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" + integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== + dependencies: + abbrev "^1.0.0" + +nopt@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.0.tgz#067378c68116f602f552876194fd11f1292503d7" + integrity sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA== + dependencies: + abbrev "^2.0.0" + +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-4.0.1.tgz#b46b24e0616d06cadf9d5718b29b6d445a82a62c" + integrity sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg== + dependencies: + hosted-git-info "^5.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + +normalize-package-data@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-5.0.0.tgz#abcb8d7e724c40d88462b84982f7cbf6859b4588" + integrity sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q== + dependencies: + hosted-git-info "^6.0.0" + is-core-module "^2.8.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-bundled@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-bundled@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-3.0.0.tgz#7e8e2f8bb26b794265028491be60321a25a39db7" + integrity sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ== + dependencies: + npm-normalize-package-bin "^3.0.0" + +npm-install-checks@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-6.1.1.tgz#b459b621634d06546664207fde16810815808db1" + integrity sha512-dH3GmQL4vsPtld59cOn8uY0iOqRmqKvV+DLGwNXV/Q7MDgD2QfOADWd/mFXcIE5LVhYYGjA3baz6W9JneqnuCw== + dependencies: + semver "^7.1.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== + +npm-normalize-package-bin@^3.0.0, npm-normalize-package-bin@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz#25447e32a9a7de1f51362c61a559233b89947832" + integrity sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ== + +npm-package-arg@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.1.tgz#00ebf16ac395c63318e67ce66780a06db6df1b04" + integrity sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg== + dependencies: + hosted-git-info "^3.0.6" + semver "^7.0.0" + validate-npm-package-name "^3.0.0" + +npm-package-arg@^10.0.0, npm-package-arg@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-10.1.0.tgz#827d1260a683806685d17193073cc152d3c7e9b1" + integrity sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA== + dependencies: + hosted-git-info "^6.0.0" + proc-log "^3.0.0" + semver "^7.3.5" + validate-npm-package-name "^5.0.0" + +npm-package-arg@^9.0.1: + version "9.1.2" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-9.1.2.tgz#fc8acecb00235f42270dda446f36926ddd9ac2bc" + integrity sha512-pzd9rLEx4TfNJkovvlBSLGhq31gGu2QDexFPWT19yCDh0JgnRhlBLNo5759N0AJmBk+kQ9Y/hXoLnlgFD+ukmg== + dependencies: + hosted-git-info "^5.0.0" + proc-log "^2.0.1" + semver "^7.3.5" + validate-npm-package-name "^4.0.0" + +npm-packlist@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.1.tgz#79bcaf22a26b6c30aa4dd66b976d69cc286800e0" + integrity sha512-UfpSvQ5YKwctmodvPPkK6Fwk603aoVsf8AEbmVKAEECrfvL8SSe1A2YIwrJ6xmTHAITKPwwZsWo7WwEbNk0kxw== + dependencies: + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^1.1.2" + npm-normalize-package-bin "^1.0.1" + +npm-packlist@^7.0.0: + version "7.0.4" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-7.0.4.tgz#033bf74110eb74daf2910dc75144411999c5ff32" + integrity sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q== + dependencies: + ignore-walk "^6.0.0" + +npm-pick-manifest@^8.0.0, npm-pick-manifest@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz#c6acd97d1ad4c5dbb80eac7b386b03ffeb289e5f" + integrity sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA== + dependencies: + npm-install-checks "^6.0.0" + npm-normalize-package-bin "^3.0.0" + npm-package-arg "^10.0.0" + semver "^7.3.5" + +npm-registry-fetch@14.0.3: + version "14.0.3" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.3.tgz#8545e321c2b36d2c6fe6e009e77e9f0e527f547b" + integrity sha512-YaeRbVNpnWvsGOjX2wk5s85XJ7l1qQBGAp724h8e2CZFFhMSuw9enom7K1mWVUtvXO1uUSFIAPofQK0pPN0ZcA== + dependencies: + make-fetch-happen "^11.0.0" + minipass "^4.0.0" + minipass-fetch "^3.0.0" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^10.0.0" + proc-log "^3.0.0" + +npm-registry-fetch@^13.0.0: + version "13.3.1" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz#bb078b5fa6c52774116ae501ba1af2a33166af7e" + integrity sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw== + dependencies: + make-fetch-happen "^10.0.6" + minipass "^3.1.6" + minipass-fetch "^2.0.3" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^9.0.1" + proc-log "^2.0.0" + +npm-registry-fetch@^14.0.0, npm-registry-fetch@^14.0.3: + version "14.0.5" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz#fe7169957ba4986a4853a650278ee02e568d115d" + integrity sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA== + dependencies: + make-fetch-happen "^11.0.0" + minipass "^5.0.0" + minipass-fetch "^3.0.0" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^10.0.0" + proc-log "^3.0.0" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npmlog@6.0.2, npmlog@^6.0.0, npmlog@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +npmlog@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-7.0.1.tgz#7372151a01ccb095c47d8bf1d0771a4ff1f53ac8" + integrity sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg== + dependencies: + are-we-there-yet "^4.0.0" + console-control-strings "^1.1.0" + gauge "^5.0.0" + set-blocking "^2.0.0" + +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + +nx@15.9.4, "nx@>=15.5.2 < 16": + version "15.9.4" + resolved "https://registry.yarnpkg.com/nx/-/nx-15.9.4.tgz#1075bc33fe8ee6c6546c21ec6ffcfd2e000946c6" + integrity sha512-P1G4t59UvE/lkHyruLeSOB5ZuNyh01IwU0tTUOi8f9s/NbP7+OQ8MYVwDV74JHTr6mQgjlS+n+4Eox8tVm9itA== + dependencies: + "@nrwl/cli" "15.9.4" + "@nrwl/tao" "15.9.4" + "@parcel/watcher" "2.0.4" + "@yarnpkg/lockfile" "^1.1.0" + "@yarnpkg/parsers" "^3.0.0-rc.18" + "@zkochan/js-yaml" "0.0.6" + axios "^1.0.0" + chalk "^4.1.0" + cli-cursor "3.1.0" + cli-spinners "2.6.1" + cliui "^7.0.2" + dotenv "~10.0.0" + enquirer "~2.3.6" + fast-glob "3.2.7" + figures "3.2.0" + flat "^5.0.2" + fs-extra "^11.1.0" + glob "7.1.4" + ignore "^5.0.4" + js-yaml "4.1.0" + jsonc-parser "3.2.0" + lines-and-columns "~2.0.3" + minimatch "3.0.5" + npm-run-path "^4.0.1" + open "^8.4.0" + semver "7.3.4" + string-width "^4.2.3" + strong-log-transformer "^2.1.0" + tar-stream "~2.2.0" + tmp "~0.2.1" + tsconfig-paths "^4.1.2" + tslib "^2.3.0" + v8-compile-cache "2.3.0" + yargs "^17.6.2" + yargs-parser "21.1.1" + optionalDependencies: + "@nrwl/nx-darwin-arm64" "15.9.4" + "@nrwl/nx-darwin-x64" "15.9.4" + "@nrwl/nx-linux-arm-gnueabihf" "15.9.4" + "@nrwl/nx-linux-arm64-gnu" "15.9.4" + "@nrwl/nx-linux-arm64-musl" "15.9.4" + "@nrwl/nx-linux-x64-gnu" "15.9.4" + "@nrwl/nx-linux-x64-musl" "15.9.4" + "@nrwl/nx-win32-arm64-msvc" "15.9.4" + "@nrwl/nx-win32-x64-msvc" "15.9.4" + +ob1@0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.66.2.tgz#8caf548202cf2688944bae47db405a08bca17a61" + integrity sha512-RFewnL/RjE0qQBOuM+2bbY96zmJPIge/aDtsiDbLSb+MOiK8CReAhBHDgL+zrA3F1hQk00lMWpUwYcep750plA== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.12.3, object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== + dependencies: + isobject "^3.0.1" + +object.values@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" + integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== + dependencies: + mimic-fn "^1.0.0" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^6.2.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" + integrity sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg== + dependencies: + is-wsl "^1.1.0" + +open@^8.4.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +options@>=0.0.5: + version "0.0.6" + resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" + integrity sha512-bOj3L1ypm++N+n7CEbbe473A414AB7z+amKYshRb//iuL3MpdDCLhPnw6aVTdKB9g5ZRVHIEp8eUln6L2NUStg== + +ora@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" + integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== + dependencies: + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-spinners "^2.0.0" + log-symbols "^2.2.0" + strip-ansi "^5.2.0" + wcwidth "^1.0.1" + +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map-series@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-2.1.0.tgz#7560d4c452d9da0c07e692fdbfe6e2c81a2a91f2" + integrity sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q== + +p-map@4.0.0, p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-pipe@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" + integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== + +p-queue@6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + +p-reduce@2.1.0, p-reduce@^2.0.0, p-reduce@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" + integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== + +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +p-waterfall@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-2.1.1.tgz#63153a774f472ccdc4eb281cdb2967fcf158b2ee" + integrity sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw== + dependencies: + p-reduce "^2.0.0" + +pacote@15.1.1: + version "15.1.1" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-15.1.1.tgz#94d8c6e0605e04d427610b3aacb0357073978348" + integrity sha512-eeqEe77QrA6auZxNHIp+1TzHQ0HBKf5V6c8zcaYZ134EJe1lCi+fjXATkNiEEfbG+e50nu02GLvUtmZcGOYabQ== + dependencies: + "@npmcli/git" "^4.0.0" + "@npmcli/installed-package-contents" "^2.0.1" + "@npmcli/promise-spawn" "^6.0.1" + "@npmcli/run-script" "^6.0.0" + cacache "^17.0.0" + fs-minipass "^3.0.0" + minipass "^4.0.0" + npm-package-arg "^10.0.0" + npm-packlist "^7.0.0" + npm-pick-manifest "^8.0.0" + npm-registry-fetch "^14.0.0" + proc-log "^3.0.0" + promise-retry "^2.0.1" + read-package-json "^6.0.0" + read-package-json-fast "^3.0.0" + sigstore "^1.0.0" + ssri "^10.0.0" + tar "^6.1.11" + +pacote@^15.0.0, pacote@^15.0.8: + version "15.2.0" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-15.2.0.tgz#0f0dfcc3e60c7b39121b2ac612bf8596e95344d3" + integrity sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA== + dependencies: + "@npmcli/git" "^4.0.0" + "@npmcli/installed-package-contents" "^2.0.1" + "@npmcli/promise-spawn" "^6.0.1" + "@npmcli/run-script" "^6.0.0" + cacache "^17.0.0" + fs-minipass "^3.0.0" + minipass "^5.0.0" + npm-package-arg "^10.0.0" + npm-packlist "^7.0.0" + npm-pick-manifest "^8.0.0" + npm-registry-fetch "^14.0.0" + proc-log "^3.0.0" + promise-retry "^2.0.1" + read-package-json "^6.0.0" + read-package-json-fast "^3.0.0" + sigstore "^1.3.0" + ssri "^10.0.0" + tar "^6.1.11" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-conflict-json@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz#67dc55312781e62aa2ddb91452c7606d1969960c" + integrity sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw== + dependencies: + json-parse-even-better-errors "^3.0.0" + just-diff "^6.0.0" + just-diff-apply "^5.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-path@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-7.0.0.tgz#605a2d58d0a749c8594405d8cc3a2bf76d16099b" + integrity sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog== + dependencies: + protocols "^2.0.0" + +parse-url@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" + integrity sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w== + dependencies: + parse-path "^7.0.0" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.6.1, path-scurry@^1.7.0: + version "1.9.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.9.2.tgz#90f9d296ac5e37e608028e28a447b11d385b3f63" + integrity sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg== + dependencies: + lru-cache "^9.1.1" + minipass "^5.0.0 || ^6.0.2" + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@5.0.0, pify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" + integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pirates@^4.0.4, pirates@^4.0.5: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +plist@^3.0.2, plist@^3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.6.tgz#7cfb68a856a7834bca6dbfe3218eb9c7740145d3" + integrity sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA== + dependencies: + base64-js "^1.5.1" + xmlbuilder "^15.1.1" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== + +postcss-selector-parser@^6.0.10: + version "6.0.13" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" + integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" + integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== + +prettier@^2.5.1, prettier@^2.6.2: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +pretty-format@29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.3.tgz#25500ada21a53c9e8423205cf0337056b201244c" + integrity sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA== + dependencies: + "@jest/schemas" "^29.4.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +pretty-format@^26.5.2, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" + +pretty-format@^27.0.0, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +pretty-format@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.3.tgz#c9fba8cedf99ce50963a11b27d982a9ae90970d5" + integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== + dependencies: + "@jest/schemas" "^28.1.3" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +proc-log@^2.0.0, proc-log@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685" + integrity sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw== + +proc-log@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8" + integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/promise-call-limit/-/promise-call-limit-1.0.2.tgz#f64b8dd9ef7693c9c7613e7dfe8d6d24de3031ea" + integrity sha512-1vTUnfI2hzui8AEIixbdAJlFY4LFDXqQswy/2eOlThAscXCY4It8FdVuI0fMJGAB2aWGbdQf/gv0skKYXmdrHA== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +promise@^8.0.3: + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" + integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== + dependencies: + asap "~2.0.6" + +prompts@^2.0.1, prompts@^2.4.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +promzard@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" + integrity sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw== + dependencies: + read "1" + +prop-types@^15.7.2: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + +protocols@^2.0.0, protocols@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" + integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +q@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +react-devtools-core@4.19.1: + version "4.19.1" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.19.1.tgz#bc37c2ef2f48f28c6af4c7292be9dca1b63deace" + integrity sha512-2wJiGffPWK0KggBjVwnTaAk+Z3MSxKInHmdzPTrBh1mAarexsa93Kw+WMX88+XjN+TtYgAiLe9xeTqcO5FfJTw== + dependencies: + shell-quote "^1.6.1" + ws "^7" + +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +react-native-codegen@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.0.8.tgz#b7796a54074139d956fff2862cf1285db43c891b" + integrity sha512-k/944+0XD+8l7zDaiKfYabyEKmAmyZgS1mj+4LcSRPyHnrjgCHKrh/Y6jM6kucQ6xU1+1uyMmF/dSkikxK8i+Q== + dependencies: + flow-parser "^0.121.0" + jscodeshift "^0.11.0" + nullthrows "^1.1.1" + +react-native@0.67.2: + version "0.67.2" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.67.2.tgz#312224bc2271c3cecd374d4bc425619cff4ea5dc" + integrity sha512-grEtpOLLvtSg8Bivg0ffVRCjTkresqMt7Jdog/geF6VAYhb4RnLaaUCWvyrfyB9buf135FKnqg5BIuve/XQNXA== + dependencies: + "@jest/create-cache-key-function" "^27.0.1" + "@react-native-community/cli" "^6.0.0" + "@react-native-community/cli-platform-android" "^6.0.0" + "@react-native-community/cli-platform-ios" "^6.0.0" + "@react-native/assets" "1.0.0" + "@react-native/normalize-color" "2.0.0" + "@react-native/polyfills" "2.0.0" + abort-controller "^3.0.0" + anser "^1.4.9" + base64-js "^1.1.2" + event-target-shim "^5.0.1" + hermes-engine "~0.9.0" + invariant "^2.2.4" + jsc-android "^250230.2.1" + metro-react-native-babel-transformer "0.66.2" + metro-runtime "0.66.2" + metro-source-map "0.66.2" + nullthrows "^1.1.1" + pretty-format "^26.5.2" + promise "^8.0.3" + prop-types "^15.7.2" + react-devtools-core "4.19.1" + react-native-codegen "^0.0.8" + react-refresh "^0.4.0" + regenerator-runtime "^0.13.2" + scheduler "^0.20.2" + stacktrace-parser "^0.1.3" + use-subscription "^1.0.0" + whatwg-fetch "^3.0.0" + ws "^6.1.4" + +react-refresh@^0.4.0: + version "0.4.3" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.4.3.tgz#966f1750c191672e76e16c2efa569150cc73ab53" + integrity sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA== + +react@17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +read-cmd-shim@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-3.0.0.tgz#62b8c638225c61e6cc607f8f4b779f3b8238f155" + integrity sha512-KQDVjGqhZk92PPNRj9ZEXEuqg8bUobSKRw+q0YQ3TKI5xkce7bUJobL4Z/OtiEbAAv70yEpYIXp4iQ9L8oPVog== + +read-cmd-shim@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz#640a08b473a49043e394ae0c7a34dd822c73b9bb" + integrity sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q== + +read-package-json-fast@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" + integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== + dependencies: + json-parse-even-better-errors "^2.3.0" + npm-normalize-package-bin "^1.0.1" + +read-package-json-fast@^3.0.0, read-package-json-fast@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz#394908a9725dc7a5f14e70c8e7556dff1d2b1049" + integrity sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw== + dependencies: + json-parse-even-better-errors "^3.0.0" + npm-normalize-package-bin "^3.0.0" + +read-package-json@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-5.0.1.tgz#1ed685d95ce258954596b13e2e0e76c7d0ab4c26" + integrity sha512-MALHuNgYWdGW3gKzuNMuYtcSSZbGQm94fAp16xt8VsYTLBjUSc55bLMKe6gzpWue0Tfi6CBgwCSdDAqutGDhMg== + dependencies: + glob "^8.0.1" + json-parse-even-better-errors "^2.3.1" + normalize-package-data "^4.0.0" + npm-normalize-package-bin "^1.0.1" + +read-package-json@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-5.0.2.tgz#b8779ccfd169f523b67208a89cc912e3f663f3fa" + integrity sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q== + dependencies: + glob "^8.0.1" + json-parse-even-better-errors "^2.3.1" + normalize-package-data "^4.0.0" + npm-normalize-package-bin "^2.0.0" + +read-package-json@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.4.tgz#90318824ec456c287437ea79595f4c2854708836" + integrity sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw== + dependencies: + glob "^10.2.2" + json-parse-even-better-errors "^3.0.0" + normalize-package-data "^5.0.0" + npm-normalize-package-bin "^3.0.0" + +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + integrity sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw== + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA== + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +read@1, read@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== + dependencies: + mute-stream "~0.0.4" + +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^4.1.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.0.tgz#55ce132d60a988c460d75c631e9ccf6a7229b468" + integrity sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + +readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readline@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/readline/-/readline-1.3.0.tgz#c580d77ef2cfc8752b132498060dc9793a7ac01c" + integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== + +recast@^0.20.3: + version "0.20.5" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.20.5.tgz#8e2c6c96827a1b339c634dd232957d230553ceae" + integrity sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ== + dependencies: + ast-types "0.14.2" + esprima "~4.0.0" + source-map "~0.6.1" + tslib "^2.0.1" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +ref-array-di@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ref-array-di/-/ref-array-di-1.2.2.tgz#ceee9d667d9c424b5a91bb813457cc916fb1f64d" + integrity sha512-jhCmhqWa7kvCVrWhR/d7RemkppqPUdxEil1CtTtm7FkZV8LcHHCK3Or9GinUiFP5WY3k0djUkMvhBhx49Jb2iA== + dependencies: + array-index "^1.0.0" + debug "^3.1.0" + +ref-struct-di@^1.1.0, ref-struct-di@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ref-struct-di/-/ref-struct-di-1.1.1.tgz#5827b1d3b32372058f177547093db1fe1602dc10" + integrity sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g== + dependencies: + debug "^3.1.0" + +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +regenerator-transform@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" + integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== + dependencies: + "@babel/runtime" "^7.8.4" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexp.prototype.flags@^1.4.3: + version "1.5.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" + integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + functions-have-names "^1.2.3" + +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== + +repeat-element@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" + integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +reselect@^4.0.0: + version "4.1.8" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" + integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@5.0.0, resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== + +resolve.exports@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999" + integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ== + +resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1: + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + dependencies: + is-core-module "^2.11.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^2.5.4: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rimraf@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755" + integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og== + dependencies: + glob "^9.2.0" + +rimraf@~2.2.6: + version "2.2.8" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" + integrity sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg== + +rimraf@~2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^7.5.5: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + +semver@7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-error@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" + integrity sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw== + +serve-static@^1.13.1: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.6.1, shell-quote@^1.7.3: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@3.0.7, signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" + integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== + +sigstore@^1.0.0, sigstore@^1.3.0, sigstore@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/sigstore/-/sigstore-1.6.0.tgz#887a4007c6ee83f3ef3fd844be1a0840e849c301" + integrity sha512-QODKff/qW/TXOZI6V/Clqu74xnInAS6it05mufj4/fSewexLtfEntgLZZcBtUK44CDQyUE5TUXYy1ARYzlfG9g== + dependencies: + "@sigstore/protobuf-specs" "^0.1.0" + "@sigstore/tuf" "^1.0.0" + make-fetch-happen "^11.0.1" + tuf-js "^1.1.3" + +simple-plist@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017" + integrity sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw== + dependencies: + bplist-creator "0.1.0" + bplist-parser "0.3.1" + plist "^3.0.5" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@3.0.0, slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +socks-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" + integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + integrity sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg== + dependencies: + is-plain-obj "^1.0.0" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@^0.5.16: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.13" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5" + integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +split2@^3.0.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + +split@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +ssri@9.0.1, ssri@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" + integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== + dependencies: + minipass "^3.1.1" + +ssri@^10.0.0, ssri@^10.0.1: + version "10.0.4" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.4.tgz#5a20af378be586df139ddb2dfb3bf992cf0daba6" + integrity sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ== + dependencies: + minipass "^5.0.0" + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +stacktrace-parser@^0.1.3: + version "0.1.10" + resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" + integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg== + dependencies: + type-fest "^0.7.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +stream-buffers@2.2.x: + version "2.2.0" + resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" + integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.trim@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" + integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +string.prototype.trimend@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" + integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +string.prototype.trimstart@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" + integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^5.0.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strong-log-transformer@2.1.0, strong-log-transformer@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" + integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA== + dependencies: + duplexer "^0.1.1" + minimist "^1.2.0" + through "^2.3.4" + +sudo-prompt@^9.0.0: + version "9.2.1" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd" + integrity sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tar-stream@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@6.1.11: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tar@^6.1.11, tar@^6.1.2: + version "6.1.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" + integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +temp-dir@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" + integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== + +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + +temp@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" + integrity sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw== + dependencies: + os-tmpdir "^1.0.0" + rimraf "~2.2.6" + +temp@^0.8.1: + version "0.8.4" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2" + integrity sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg== + dependencies: + rimraf "~2.6.2" + +tempy@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-1.0.0.tgz#4f192b3ee3328a2684d0e3fc5c491425395aab65" + integrity sha512-eLXG5B1G0mRPHmgH2WydPl5v4jH35qEn3y/rA/aahKhIa91Pn119SsU7n7v/433gtT9ONzC8ISvNHIh2JSTm0w== + dependencies: + del "^6.0.0" + is-stream "^2.0.0" + temp-dir "^2.0.0" + type-fest "^0.16.0" + unique-string "^2.0.0" + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-extensions@^1.0.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" + integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + +through2@^2.0.0, through2@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through2@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== + dependencies: + readable-stream "3" + +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +treeverse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/treeverse/-/treeverse-3.0.0.tgz#dd82de9eb602115c6ebd77a574aae67003cb48c8" + integrity sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ== + +trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + +ts-jest@^29.0.5: + version "29.1.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.0.tgz#4a9db4104a49b76d2b368ea775b6c9535c603891" + integrity sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "^21.0.1" + +ts-node@^10.8.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tsconfig-paths@^3.14.1: + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsconfig-paths@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0: + version "2.5.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" + integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +tuf-js@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/tuf-js/-/tuf-js-1.1.7.tgz#21b7ae92a9373015be77dfe0cb282a80ec3bbe43" + integrity sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg== + dependencies: + "@tufjs/models" "1.0.4" + debug "^4.3.4" + make-fetch-happen "^11.1.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" + integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== + +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8" + integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" + integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + +typescript@4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== + +"typescript@^3 || ^4", typescript@^4.5.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +uglify-es@^3.1.9: + version "3.3.9" + resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" + integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== + dependencies: + commander "~2.13.0" + source-map "~0.6.1" + +uglify-js@^3.1.4: + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + +ultron@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" + integrity sha512-QMpnpVtYaWEeY+MwKDN/UdKlE/LsFZXM5lO1u7GaZzNgmIbGixHEmVMIKT+vqYOALu3m5GYQy9kz4Xu4IVn7Ow== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unique-filename@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" + integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== + dependencies: + unique-slug "^3.0.0" + +unique-filename@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea" + integrity sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g== + dependencies: + unique-slug "^4.0.0" + +unique-slug@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" + integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== + dependencies: + imurmurhash "^0.1.4" + +unique-slug@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3" + integrity sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ== + dependencies: + imurmurhash "^0.1.4" + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@2.0.1, upath@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" + integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== + +update-browserslist-db@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== + +use-subscription@^1.0.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.8.0.tgz#f118938c29d263c2bce12fc5585d3fe694d4dbce" + integrity sha512-LISuG0/TmmoDoCRmV5XAqYkd3UCBNM0ML3gGBndze65WITcsExCD3DTvXXTLyNcOC0heFQZzluW88bN/oC1DQQ== + dependencies: + use-sync-external-store "^1.2.0" + +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-compile-cache@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +v8-to-istanbul@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" + integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + +validate-npm-package-license@3.0.4, validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validate-npm-package-name@4.0.0, validate-npm-package-name@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz#fe8f1c50ac20afdb86f177da85b3600f0ac0d747" + integrity sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q== + dependencies: + builtins "^5.0.0" + +validate-npm-package-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" + integrity sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw== + dependencies: + builtins "^1.0.3" + +validate-npm-package-name@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz#f16afd48318e6f90a1ec101377fa0384cfc8c713" + integrity sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ== + dependencies: + builtins "^5.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vlq@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468" + integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== + +walk-up-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== + +walker@^1.0.7, walker@^1.0.8, walker@~1.0.5: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +wcwidth@^1.0.0, wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-fetch@^3.0.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + +which-typed-array@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" + integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.10" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +which@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/which/-/which-3.0.1.tgz#89f1cd0c23f629a8105ffe69b8172791c87b4be1" + integrity sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.2, wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +word-wrap@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" + integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.1.tgz#9faa33a964c1c85ff6f849b80b42a88c2c537c8f" + integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +write-file-atomic@^2.3.0, write-file-atomic@^2.4.2: + version "2.4.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" + integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write-file-atomic@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +write-file-atomic@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + +write-json-file@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-3.2.0.tgz#65bbdc9ecd8a1458e15952770ccbadfcff5fe62a" + integrity sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ== + dependencies: + detect-indent "^5.0.0" + graceful-fs "^4.1.15" + make-dir "^2.1.0" + pify "^4.0.1" + sort-keys "^2.0.0" + write-file-atomic "^2.4.2" + +write-pkg@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-4.0.0.tgz#675cc04ef6c11faacbbc7771b24c0abbf2a20039" + integrity sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA== + dependencies: + sort-keys "^2.0.0" + type-fest "^0.4.1" + write-json-file "^3.2.0" + +ws@^1.1.0, ws@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51" + integrity sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w== + dependencies: + options ">=0.0.5" + ultron "1.0.x" + +ws@^6.1.4: + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== + dependencies: + async-limiter "~1.0.0" + +ws@^7: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +xcode@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/xcode/-/xcode-2.1.0.tgz#bab64a7e954bb50ca8d19da7e09531c65a43ecfe" + integrity sha512-uCrmPITrqTEzhn0TtT57fJaNaw8YJs1aCzs+P/QqxsDbvPZSv7XMPPwXrKvHtD6pLjBM/NaVwraWJm8q83Y4iQ== + dependencies: + simple-plist "^1.0.0" + uuid "^3.3.2" + +xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== + +xmldoc@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.3.0.tgz#7823225b096c74036347c9ec5924d06b6a3cebab" + integrity sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng== + dependencies: + sax "^1.2.4" + +xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^20.2.2, yargs-parser@^20.2.3: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@16.2.0, yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^15.1.0, yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + +yargs@^17.3.1, yargs@^17.6.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/wrappers/kotlin/.gitignore b/wrappers/kotlin/.gitignore new file mode 100644 index 00000000..6338703c --- /dev/null +++ b/wrappers/kotlin/.gitignore @@ -0,0 +1,16 @@ +local.properties +build +.gradle +.DS_STORE +.idea/ +.gradle/ +*.iml +*.hprof +.cxx/ + +*.db* +*.db +*.db-shm +*.db-wal +target +src/androidMain/jnilibs \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AeadParams.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AeadParams.kt new file mode 100644 index 00000000..3b1bc63f --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AeadParams.kt @@ -0,0 +1,30 @@ +package aries_askar + + +import okio.Buffer + +data class AeadParams ( + var `nonceLength`: Int, + var `tagLength`: Int +) { + +} + +object FfiConverterTypeAeadParams: FfiConverterRustBuffer { + override fun read(source: NoCopySource): AeadParams { + return AeadParams( + FfiConverterInt.read(source), + FfiConverterInt.read(source), + ) + } + + override fun allocationSize(value: AeadParams) = ( + FfiConverterInt.allocationSize(value.`nonceLength`) + + FfiConverterInt.allocationSize(value.`tagLength`) + ) + + override fun write(value: AeadParams, buf: Buffer) { + FfiConverterInt.write(value.`nonceLength`, buf) + FfiConverterInt.write(value.`tagLength`, buf) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarCrypto.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarCrypto.kt new file mode 100644 index 00000000..2d8f4d44 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarCrypto.kt @@ -0,0 +1,111 @@ +package aries_askar + + +import okio.Buffer + +interface AskarCryptoInterface { + + @Throws(ErrorCode::class) + fun `randomNonce`(): List + + @Throws(ErrorCode::class) + fun `cryptoBox`(`receiverKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `message`: List, `nonce`: List): List + + @Throws(ErrorCode::class) + fun `boxOpen`(`receiverKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `message`: List, `nonce`: List): List + + @Throws(ErrorCode::class) + fun `boxSeal`(`receiverKey`: AskarLocalKey, `message`: List): List + + @Throws(ErrorCode::class) + fun `boxSealOpen`(`receiverKey`: AskarLocalKey, `ciphertext`: List): List + +} + +class AskarCrypto( + pointer: Pointer +) : FFIObject(pointer), AskarCryptoInterface { + constructor() : + this( + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_constructor_askarcrypto_new( _status) +}) + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarcrypto(this.pointer, status) + } + } + + + @Throws(ErrorCode::class)override fun `randomNonce`(): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarcrypto_random_nonce(it, _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `cryptoBox`(`receiverKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `message`: List, `nonce`: List): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarcrypto_crypto_box(it, FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterTypeAskarLocalKey.lower(`senderKey`), FfiConverterSequenceUByte.lower(`message`), FfiConverterSequenceUByte.lower(`nonce`), _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `boxOpen`(`receiverKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `message`: List, `nonce`: List): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarcrypto_box_open(it, FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterTypeAskarLocalKey.lower(`senderKey`), FfiConverterSequenceUByte.lower(`message`), FfiConverterSequenceUByte.lower(`nonce`), _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `boxSeal`(`receiverKey`: AskarLocalKey, `message`: List): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarcrypto_box_seal(it, FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterSequenceUByte.lower(`message`), _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `boxSealOpen`(`receiverKey`: AskarLocalKey, `ciphertext`: List): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarcrypto_box_seal_open(it, FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterSequenceUByte.lower(`ciphertext`), _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + + +} + +object FfiConverterTypeAskarCrypto: FfiConverter { + override fun lower(value: AskarCrypto): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarCrypto { + return AskarCrypto(value) + } + + override fun read(source: NoCopySource): AskarCrypto { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarCrypto) = 8 + + override fun write(value: AskarCrypto, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEcdh1Pu.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEcdh1Pu.kt new file mode 100644 index 00000000..cac5a9ba --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEcdh1Pu.kt @@ -0,0 +1,111 @@ +package aries_askar + + +import okio.Buffer + +interface AskarEcdh1PuInterface { + + @Throws(ErrorCode::class) + fun `deriveKey`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ccTag`: List, `receive`: Boolean): AskarLocalKey + + @Throws(ErrorCode::class) + fun `encryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `message`: List, `nonce`: List?, `aad`: List?): EncryptedBuffer + + @Throws(ErrorCode::class) + fun `decryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: List, `tag`: List?, `nonce`: List, `aad`: List?): List + + @Throws(ErrorCode::class) + fun `senderWrapKey`(`wrapAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `cek`: AskarLocalKey, `ccTag`: List): EncryptedBuffer + + @Throws(ErrorCode::class) + fun `receiverUnwrapKey`(`wrapAlg`: AskarKeyAlg, `encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: List, `ccTag`: List, `nonce`: List?, `tag`: List?): AskarLocalKey + +} + +class AskarEcdh1Pu( + pointer: Pointer +) : FFIObject(pointer), AskarEcdh1PuInterface { + constructor(`algId`: String, `apu`: String, `apv`: String) : + this( + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_constructor_askarecdh1pu_new(FfiConverterString.lower(`algId`), FfiConverterString.lower(`apu`), FfiConverterString.lower(`apv`), _status) +}) + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarecdh1pu(this.pointer, status) + } + } + + + @Throws(ErrorCode::class)override fun `deriveKey`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ccTag`: List, `receive`: Boolean): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdh1pu_derive_key(it, FfiConverterTypeAskarKeyAlg.lower(`encAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`senderKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterSequenceUByte.lower(`ccTag`), FfiConverterBoolean.lower(`receive`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + @Throws(ErrorCode::class)override fun `encryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `message`: List, `nonce`: List?, `aad`: List?): EncryptedBuffer = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdh1pu_encrypt_direct(it, FfiConverterTypeAskarKeyAlg.lower(`encAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`senderKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterSequenceUByte.lower(`message`), FfiConverterOptionalSequenceUByte.lower(`nonce`), FfiConverterOptionalSequenceUByte.lower(`aad`), _status) +} + }.let { + FfiConverterTypeEncryptedBuffer.lift(it) + } + + + @Throws(ErrorCode::class)override fun `decryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: List, `tag`: List?, `nonce`: List, `aad`: List?): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdh1pu_decrypt_direct(it, FfiConverterTypeAskarKeyAlg.lower(`encAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`senderKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterSequenceUByte.lower(`ciphertext`), FfiConverterOptionalSequenceUByte.lower(`tag`), FfiConverterSequenceUByte.lower(`nonce`), FfiConverterOptionalSequenceUByte.lower(`aad`), _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `senderWrapKey`(`wrapAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `cek`: AskarLocalKey, `ccTag`: List): EncryptedBuffer = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdh1pu_sender_wrap_key(it, FfiConverterTypeAskarKeyAlg.lower(`wrapAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`senderKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterTypeAskarLocalKey.lower(`cek`), FfiConverterSequenceUByte.lower(`ccTag`), _status) +} + }.let { + FfiConverterTypeEncryptedBuffer.lift(it) + } + + + @Throws(ErrorCode::class)override fun `receiverUnwrapKey`(`wrapAlg`: AskarKeyAlg, `encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: List, `ccTag`: List, `nonce`: List?, `tag`: List?): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdh1pu_receiver_unwrap_key(it, FfiConverterTypeAskarKeyAlg.lower(`wrapAlg`), FfiConverterTypeAskarKeyAlg.lower(`encAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`senderKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterSequenceUByte.lower(`ciphertext`), FfiConverterSequenceUByte.lower(`ccTag`), FfiConverterOptionalSequenceUByte.lower(`nonce`), FfiConverterOptionalSequenceUByte.lower(`tag`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + + +} + +object FfiConverterTypeAskarEcdh1Pu: FfiConverter { + override fun lower(value: AskarEcdh1Pu): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarEcdh1Pu { + return AskarEcdh1Pu(value) + } + + override fun read(source: NoCopySource): AskarEcdh1Pu { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarEcdh1Pu) = 8 + + override fun write(value: AskarEcdh1Pu, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEcdhEs.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEcdhEs.kt new file mode 100644 index 00000000..bc19a469 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEcdhEs.kt @@ -0,0 +1,111 @@ +package aries_askar + + +import okio.Buffer + +interface AskarEcdhEsInterface { + + @Throws(ErrorCode::class) + fun `deriveKey`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `receive`: Boolean): AskarLocalKey + + @Throws(ErrorCode::class) + fun `encryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `message`: List, `nonce`: List?, `aad`: List?): EncryptedBuffer + + @Throws(ErrorCode::class) + fun `decryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: List, `tag`: List?, `nonce`: List, `aad`: List?): List + + @Throws(ErrorCode::class) + fun `senderWrapKey`(`wrapAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `cek`: AskarLocalKey): EncryptedBuffer + + @Throws(ErrorCode::class) + fun `receiverUnwrapKey`(`wrapAlg`: AskarKeyAlg, `encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: List, `nonce`: List?, `tag`: List?): AskarLocalKey + +} + +class AskarEcdhEs( + pointer: Pointer +) : FFIObject(pointer), AskarEcdhEsInterface { + constructor(`algId`: String, `apu`: String, `apv`: String) : + this( + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_constructor_askarecdhes_new(FfiConverterString.lower(`algId`), FfiConverterString.lower(`apu`), FfiConverterString.lower(`apv`), _status) +}) + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarecdhes(this.pointer, status) + } + } + + + @Throws(ErrorCode::class)override fun `deriveKey`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `receive`: Boolean): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdhes_derive_key(it, FfiConverterTypeAskarKeyAlg.lower(`encAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterBoolean.lower(`receive`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + @Throws(ErrorCode::class)override fun `encryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `message`: List, `nonce`: List?, `aad`: List?): EncryptedBuffer = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdhes_encrypt_direct(it, FfiConverterTypeAskarKeyAlg.lower(`encAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterSequenceUByte.lower(`message`), FfiConverterOptionalSequenceUByte.lower(`nonce`), FfiConverterOptionalSequenceUByte.lower(`aad`), _status) +} + }.let { + FfiConverterTypeEncryptedBuffer.lift(it) + } + + + @Throws(ErrorCode::class)override fun `decryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: List, `tag`: List?, `nonce`: List, `aad`: List?): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdhes_decrypt_direct(it, FfiConverterTypeAskarKeyAlg.lower(`encAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterSequenceUByte.lower(`ciphertext`), FfiConverterOptionalSequenceUByte.lower(`tag`), FfiConverterSequenceUByte.lower(`nonce`), FfiConverterOptionalSequenceUByte.lower(`aad`), _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `senderWrapKey`(`wrapAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `cek`: AskarLocalKey): EncryptedBuffer = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdhes_sender_wrap_key(it, FfiConverterTypeAskarKeyAlg.lower(`wrapAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterTypeAskarLocalKey.lower(`cek`), _status) +} + }.let { + FfiConverterTypeEncryptedBuffer.lift(it) + } + + + @Throws(ErrorCode::class)override fun `receiverUnwrapKey`(`wrapAlg`: AskarKeyAlg, `encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: List, `nonce`: List?, `tag`: List?): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarecdhes_receiver_unwrap_key(it, FfiConverterTypeAskarKeyAlg.lower(`wrapAlg`), FfiConverterTypeAskarKeyAlg.lower(`encAlg`), FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), FfiConverterTypeAskarLocalKey.lower(`receiverKey`), FfiConverterSequenceUByte.lower(`ciphertext`), FfiConverterOptionalSequenceUByte.lower(`nonce`), FfiConverterOptionalSequenceUByte.lower(`tag`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + + +} + +object FfiConverterTypeAskarEcdhEs: FfiConverter { + override fun lower(value: AskarEcdhEs): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarEcdhEs { + return AskarEcdhEs(value) + } + + override fun read(source: NoCopySource): AskarEcdhEs { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarEcdhEs) = 8 + + override fun write(value: AskarEcdhEs, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEntry.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEntry.kt new file mode 100644 index 00000000..ffd8010c --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEntry.kt @@ -0,0 +1,85 @@ +package aries_askar + + +import okio.Buffer + +interface AskarEntryInterface { + + fun `category`(): String + + fun `name`(): String + + fun `tags`(): Map + + fun `value`(): List + +} + +class AskarEntry( + pointer: Pointer +) : FFIObject(pointer), AskarEntryInterface { + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarentry(this.pointer, status) + } + } + + override fun `category`(): String = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarentry_category(it, _status) + } + }.let { + FfiConverterString.lift(it) + } + + override fun `name`(): String = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarentry_name(it, _status) + } + }.let { + FfiConverterString.lift(it) + } + + override fun `tags`(): Map = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarentry_tags(it, _status) + } + }.let { + for( i in 0 until it.dataSize) { + } + FfiConverterMapStringString.lift(it) + } + + override fun `value`(): List = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarentry_value(it, _status) + } + }.let { + FfiConverterSequenceUByte.lift(it) + } + + +} + +object FfiConverterTypeAskarEntry : FfiConverter { + override fun lower(value: AskarEntry): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarEntry { + return AskarEntry(value) + } + + override fun read(source: NoCopySource): AskarEntry { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarEntry) = 8 + + override fun write(value: AskarEntry, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEntryOperation.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEntryOperation.kt new file mode 100644 index 00000000..3422828c --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarEntryOperation.kt @@ -0,0 +1,23 @@ +package aries_askar + + +import okio.Buffer + +enum class AskarEntryOperation { + INSERT,REPLACE,REMOVE; +} + +object FfiConverterTypeAskarEntryOperation: FfiConverterRustBuffer { + override fun read(source: NoCopySource) = try { + AskarEntryOperation.values()[source.readInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: AskarEntryOperation) = 4 + + override fun write(value: AskarEntryOperation, buf: Buffer) { + buf.writeInt(value.ordinal + 1) + } +} + diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarKeyAlg.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarKeyAlg.kt new file mode 100644 index 00000000..39184c36 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarKeyAlg.kt @@ -0,0 +1,23 @@ +package aries_askar + + +import okio.Buffer + +enum class AskarKeyAlg { + A128_GCM,A256_GCM,A128_CBC_HS256,A256_CBC_HS512,A128_KW,A256_KW,BLS12_381G1,BLS12_381G2,BLS12_381G1G2,C20P,XC20P,ED25519,X25519,K256,P256,P384; +} + +object FfiConverterTypeAskarKeyAlg: FfiConverterRustBuffer { + override fun read(source: NoCopySource) = try { + AskarKeyAlg.values()[source.readInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: AskarKeyAlg) = 4 + + override fun write(value: AskarKeyAlg, buf: Buffer) { + buf.writeInt(value.ordinal + 1) + } +} + diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarKeyEntry.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarKeyEntry.kt new file mode 100644 index 00000000..50503300 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarKeyEntry.kt @@ -0,0 +1,109 @@ +package aries_askar + + +import okio.Buffer + +interface AskarKeyEntryInterface { + + fun `algorithm`(): String? + + fun `metadata`(): String? + + fun `name`(): String + + fun `isLocal`(): Boolean + + fun `tags`(): Map + + @Throws(ErrorCode::class) + fun `loadLocalKey`(): AskarLocalKey + +} + +class AskarKeyEntry( + pointer: Pointer +) : FFIObject(pointer), AskarKeyEntryInterface { + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarkeyentry(this.pointer, status) + } + } + + override fun `algorithm`(): String? = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarkeyentry_algorithm(it, _status) +} + }.let { + FfiConverterOptionalString.lift(it) + } + + override fun `metadata`(): String? = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarkeyentry_metadata(it, _status) +} + }.let { + FfiConverterOptionalString.lift(it) + } + + override fun `name`(): String = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarkeyentry_name(it, _status) +} + }.let { + FfiConverterString.lift(it) + } + + override fun `isLocal`(): Boolean = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarkeyentry_is_local(it, _status) +} + }.let { + FfiConverterBoolean.lift(it) + } + + override fun `tags`(): Map = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarkeyentry_tags(it, _status) +} + }.let { + FfiConverterMapStringString.lift(it) + } + + + @Throws(ErrorCode::class)override fun `loadLocalKey`(): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarkeyentry_load_local_key(it, _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + + +} + +object FfiConverterTypeAskarKeyEntry: FfiConverter { + override fun lower(value: AskarKeyEntry): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarKeyEntry { + return AskarKeyEntry(value) + } + + override fun read(source: NoCopySource): AskarKeyEntry { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarKeyEntry) = 8 + + override fun write(value: AskarKeyEntry, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarLocalKey.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarLocalKey.kt new file mode 100644 index 00000000..dffbd69d --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarLocalKey.kt @@ -0,0 +1,271 @@ +package aries_askar + + +import okio.Buffer + +interface AskarLocalKeyInterface { + + @Throws(ErrorCode::class) + fun `toPublicBytes`(): List + + @Throws(ErrorCode::class) + fun `toSecretBytes`(): List + + @Throws(ErrorCode::class) + fun `toKeyExchange`(`alg`: AskarKeyAlg, `pk`: AskarLocalKey): AskarLocalKey + + fun `algorithm`(): AskarKeyAlg + + @Throws(ErrorCode::class) + fun `toJwkPublic`(`alg`: AskarKeyAlg?): String + + @Throws(ErrorCode::class) + fun `toJwkSecret`(): List + + @Throws(ErrorCode::class) + fun `toJwkThumbprint`(`alg`: AskarKeyAlg?): String + + @Throws(ErrorCode::class) + fun `toJwkThumbprints`(): List + + @Throws(ErrorCode::class) + fun `convertKey`(`alg`: AskarKeyAlg): AskarLocalKey + + @Throws(ErrorCode::class) + fun `aeadParams`(): AeadParams + + fun `aeadPadding`(`msgLen`: Int): Int + + @Throws(ErrorCode::class) + fun `aeadRandomNonce`(): List + + @Throws(ErrorCode::class) + fun `aeadEncrypt`(`message`: List, `nonce`: List?, `aad`: List?): EncryptedBuffer + + @Throws(ErrorCode::class) + fun `aeadDecrypt`(`ciphertext`: List, `tag`: List?, `nonce`: List, `aad`: List?): List + + @Throws(ErrorCode::class) + fun `signMessage`(`message`: List, `sigType`: String?): List + + @Throws(ErrorCode::class) + fun `verifySignature`(`message`: List, `signature`: List, `sigType`: String?): Boolean + + @Throws(ErrorCode::class) + fun `wrapKey`(`key`: AskarLocalKey, `nonce`: List?): EncryptedBuffer + + @Throws(ErrorCode::class) + fun `unwrapKey`(`alg`: AskarKeyAlg, `ciphertext`: List, `tag`: List?, `nonce`: List?): AskarLocalKey + +} + +class AskarLocalKey( + pointer: Pointer +) : FFIObject(pointer), AskarLocalKeyInterface { + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarlocalkey(this.pointer, status) + } + } + + + @Throws(ErrorCode::class)override fun `toPublicBytes`(): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_to_public_bytes(it, _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `toSecretBytes`(): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_to_secret_bytes(it, _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `toKeyExchange`(`alg`: AskarKeyAlg, `pk`: AskarLocalKey): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_to_key_exchange(it, FfiConverterTypeAskarKeyAlg.lower(`alg`), FfiConverterTypeAskarLocalKey.lower(`pk`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + override fun `algorithm`(): AskarKeyAlg = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_algorithm(it, _status) +} + }.let { + FfiConverterTypeAskarKeyAlg.lift(it) + } + + + @Throws(ErrorCode::class)override fun `toJwkPublic`(`alg`: AskarKeyAlg?): String = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_public(it, FfiConverterOptionalTypeAskarKeyAlg.lower(`alg`), _status) +} + }.let { + FfiConverterString.lift(it) + } + + + @Throws(ErrorCode::class)override fun `toJwkSecret`(): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_secret(it, _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `toJwkThumbprint`(`alg`: AskarKeyAlg?): String = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprint(it, FfiConverterOptionalTypeAskarKeyAlg.lower(`alg`), _status) +} + }.let { + FfiConverterString.lift(it) + } + + + @Throws(ErrorCode::class)override fun `toJwkThumbprints`(): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprints(it, _status) +} + }.let { + FfiConverterSequenceString.lift(it) + } + + + @Throws(ErrorCode::class)override fun `convertKey`(`alg`: AskarKeyAlg): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_convert_key(it, FfiConverterTypeAskarKeyAlg.lower(`alg`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + @Throws(ErrorCode::class)override fun `aeadParams`(): AeadParams = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_aead_params(it, _status) +} + }.let { + FfiConverterTypeAeadParams.lift(it) + } + + override fun `aeadPadding`(`msgLen`: Int): Int = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_aead_padding(it, FfiConverterInt.lower(`msgLen`), _status) +} + }.let { + FfiConverterInt.lift(it) + } + + + @Throws(ErrorCode::class)override fun `aeadRandomNonce`(): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_aead_random_nonce(it, _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `aeadEncrypt`(`message`: List, `nonce`: List?, `aad`: List?): EncryptedBuffer = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_aead_encrypt(it, FfiConverterSequenceUByte.lower(`message`), FfiConverterOptionalSequenceUByte.lower(`nonce`), FfiConverterOptionalSequenceUByte.lower(`aad`), _status) +} + }.let { + FfiConverterTypeEncryptedBuffer.lift(it) + } + + + @Throws(ErrorCode::class)override fun `aeadDecrypt`(`ciphertext`: List, `tag`: List?, `nonce`: List, `aad`: List?): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_aead_decrypt(it, FfiConverterSequenceUByte.lower(`ciphertext`), FfiConverterOptionalSequenceUByte.lower(`tag`), FfiConverterSequenceUByte.lower(`nonce`), FfiConverterOptionalSequenceUByte.lower(`aad`), _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `signMessage`(`message`: List, `sigType`: String?): List = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_sign_message(it, FfiConverterSequenceUByte.lower(`message`), FfiConverterOptionalString.lower(`sigType`), _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + @Throws(ErrorCode::class)override fun `verifySignature`(`message`: List, `signature`: List, `sigType`: String?): Boolean = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_verify_signature(it, FfiConverterSequenceUByte.lower(`message`), FfiConverterSequenceUByte.lower(`signature`), FfiConverterOptionalString.lower(`sigType`), _status) +} + }.let { + FfiConverterBoolean.lift(it) + } + + + @Throws(ErrorCode::class)override fun `wrapKey`(`key`: AskarLocalKey, `nonce`: List?): EncryptedBuffer = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_wrap_key(it, FfiConverterTypeAskarLocalKey.lower(`key`), FfiConverterOptionalSequenceUByte.lower(`nonce`), _status) +} + }.let { + FfiConverterTypeEncryptedBuffer.lift(it) + } + + + @Throws(ErrorCode::class)override fun `unwrapKey`(`alg`: AskarKeyAlg, `ciphertext`: List, `tag`: List?, `nonce`: List?): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarlocalkey_unwrap_key(it, FfiConverterTypeAskarKeyAlg.lower(`alg`), FfiConverterSequenceUByte.lower(`ciphertext`), FfiConverterOptionalSequenceUByte.lower(`tag`), FfiConverterOptionalSequenceUByte.lower(`nonce`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + + +} + +object FfiConverterTypeAskarLocalKey: FfiConverter { + override fun lower(value: AskarLocalKey): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarLocalKey { + return AskarLocalKey(value) + } + + override fun read(source: NoCopySource): AskarLocalKey { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarLocalKey) = 8 + + override fun write(value: AskarLocalKey, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarScan.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarScan.kt new file mode 100644 index 00000000..e3e6b8fa --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarScan.kt @@ -0,0 +1,129 @@ +package aries_askar + + +import kotlinx.coroutines.coroutineScope +import okio.Buffer +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +interface AskarScanInterface { + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `next`(): List? + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `fetchAll`(): List + +} + +class AskarScan( + pointer: Pointer +) : FFIObject(pointer), AskarScanInterface { + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarscan(this.pointer, status) + } + } + + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `next`() : List? { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarscan_next( + thisPtr, + // FIXME create macro that handles the comma + + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `fetchAll`() : List { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarscan_fetch_all( + thisPtr, + // FIXME create macro that handles the comma + + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + + +} + +object FfiConverterTypeAskarScan: FfiConverter { + override fun lower(value: AskarScan): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarScan { + return AskarScan(value) + } + + override fun read(source: NoCopySource): AskarScan { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarScan) = 8 + + override fun write(value: AskarScan, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarSession.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarSession.kt new file mode 100644 index 00000000..98ccb4e0 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarSession.kt @@ -0,0 +1,507 @@ +package aries_askar + + +import kotlinx.coroutines.coroutineScope +import okio.Buffer +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +interface AskarSessionInterface { + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `close`() + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `count`(`category`: String, `tagFilter`: String?): Long + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `fetch`(`category`: String, `name`: String, `forUpdate`: Boolean): AskarEntry? + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `fetchAll`(`category`: String, `tagFilter`: String?, `limit`: Long?, `forUpdate`: Boolean): List + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `update`(`operation`: AskarEntryOperation, `category`: String, `name`: String, `value`: List, `tags`: String?, `expiryMs`: Long?) + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `removeAll`(`category`: String, `tagFilter`: String?): Long + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `insertKey`(`name`: String, `key`: AskarLocalKey, `metadata`: String?, `tags`: String?, `expiryMs`: Long?) + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `fetchKey`(`name`: String, `forUpdate`: Boolean): AskarKeyEntry? + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `fetchAllKeys`(`algorithm`: String?, `thumbprint`: String?, `tagFilter`: String?, `limit`: Long?, `forUpdate`: Boolean): List + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `removeKey`(`name`: String) + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `updateKey`(`name`: String, `metadata`: String?, `tags`: String?, `expiryMs`: Long?) + +} + +class AskarSession( + pointer: Pointer +) : FFIObject(pointer), AskarSessionInterface { + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarsession(this.pointer, status) + } + } + + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `close`() { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_close( + thisPtr, + // FIXME create macro that handles the comma + + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `count`(`category`: String, `tagFilter`: String?) : Long { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandleri64_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandleri64_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_count( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`category`), FfiConverterOptionalString.lower(`tagFilter`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `fetch`(`category`: String, `name`: String, `forUpdate`: Boolean) : AskarEntry? { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_fetch( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`category`), FfiConverterString.lower(`name`), FfiConverterBoolean.lower(`forUpdate`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `fetchAll`(`category`: String, `tagFilter`: String?, `limit`: Long?, `forUpdate`: Boolean) : List { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_fetch_all( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`category`), FfiConverterOptionalString.lower(`tagFilter`), FfiConverterOptionalLong.lower(`limit`), FfiConverterBoolean.lower(`forUpdate`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `update`(`operation`: AskarEntryOperation, `category`: String, `name`: String, `value`: List, `tags`: String?, `expiryMs`: Long?) { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_update( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterTypeAskarEntryOperation.lower(`operation`), FfiConverterString.lower(`category`), FfiConverterString.lower(`name`), FfiConverterSequenceUByte.lower(`value`), FfiConverterOptionalString.lower(`tags`), FfiConverterOptionalLong.lower(`expiryMs`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `removeAll`(`category`: String, `tagFilter`: String?) : Long { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandleri64_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandleri64_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_remove_all( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`category`), FfiConverterOptionalString.lower(`tagFilter`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `insertKey`(`name`: String, `key`: AskarLocalKey, `metadata`: String?, `tags`: String?, `expiryMs`: Long?) { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_insert_key( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`name`), FfiConverterTypeAskarLocalKey.lower(`key`), FfiConverterOptionalString.lower(`metadata`), FfiConverterOptionalString.lower(`tags`), FfiConverterOptionalLong.lower(`expiryMs`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `fetchKey`(`name`: String, `forUpdate`: Boolean) : AskarKeyEntry? { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_fetch_key( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`name`), FfiConverterBoolean.lower(`forUpdate`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `fetchAllKeys`(`algorithm`: String?, `thumbprint`: String?, `tagFilter`: String?, `limit`: Long?, `forUpdate`: Boolean) : List { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_fetch_all_keys( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterOptionalString.lower(`algorithm`), FfiConverterOptionalString.lower(`thumbprint`), FfiConverterOptionalString.lower(`tagFilter`), FfiConverterOptionalLong.lower(`limit`), FfiConverterBoolean.lower(`forUpdate`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `removeKey`(`name`: String) { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_remove_key( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`name`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `updateKey`(`name`: String, `metadata`: String?, `tags`: String?, `expiryMs`: Long?) { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarsession_update_key( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`name`), FfiConverterOptionalString.lower(`metadata`), FfiConverterOptionalString.lower(`tags`), FfiConverterOptionalLong.lower(`expiryMs`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + + +} + +object FfiConverterTypeAskarSession: FfiConverter { + override fun lower(value: AskarSession): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarSession { + return AskarSession(value) + } + + override fun read(source: NoCopySource): AskarSession { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarSession) = 8 + + override fun write(value: AskarSession, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarStore.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarStore.kt new file mode 100644 index 00000000..ea9717fe --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarStore.kt @@ -0,0 +1,339 @@ +package aries_askar + + +import kotlinx.coroutines.coroutineScope +import okio.Buffer +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +interface AskarStoreInterface { + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `getProfileName`(): String + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `createProfile`(`profile`: String?): String + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `removeProfile`(`profile`: String): Boolean + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `rekey`(`keyMethod`: String?, `passKey`: String?) + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `close`() + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `scan`(`profile`: String?, `categogy`: String, `tagFilter`: String?, `offset`: Long?, `limit`: Long?): AskarScan + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `session`(`profile`: String?): AskarSession + +} + +class AskarStore( + pointer: Pointer +) : FFIObject(pointer), AskarStoreInterface { + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarstore(this.pointer, status) + } + } + + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `getProfileName`() : String { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerstring_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerstring_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstore_get_profile_name( + thisPtr, + // FIXME create macro that handles the comma + + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `createProfile`(`profile`: String?) : String { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerstring_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerstring_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstore_create_profile( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterOptionalString.lower(`profile`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `removeProfile`(`profile`: String) : Boolean { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerbool_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerbool_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstore_remove_profile( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`profile`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `rekey`(`keyMethod`: String?, `passKey`: String?) { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstore_rekey( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterOptionalString.lower(`keyMethod`), FfiConverterOptionalString.lower(`passKey`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `close`() { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstore_close( + thisPtr, + // FIXME create macro that handles the comma + + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `scan`(`profile`: String?, `categogy`: String, `tagFilter`: String?, `offset`: Long?, `limit`: Long?) : AskarScan { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstore_scan( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterOptionalString.lower(`profile`), FfiConverterString.lower(`categogy`), FfiConverterOptionalString.lower(`tagFilter`), FfiConverterOptionalLong.lower(`offset`), FfiConverterOptionalLong.lower(`limit`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `session`(`profile`: String?) : AskarSession { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstore_session( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterOptionalString.lower(`profile`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + + +} + +object FfiConverterTypeAskarStore: FfiConverter { + override fun lower(value: AskarStore): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarStore { + return AskarStore(value) + } + + override fun read(source: NoCopySource): AskarStore { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarStore) = 8 + + override fun write(value: AskarStore, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarStoreManager.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarStoreManager.kt new file mode 100644 index 00000000..83ce6e21 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AskarStoreManager.kt @@ -0,0 +1,201 @@ +package aries_askar + + +import kotlinx.coroutines.coroutineScope +import okio.Buffer +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +interface AskarStoreManagerInterface { + + @Throws(ErrorCode::class) + fun `generateRawStoreKey`(`seed`: String?): String + + @Throws(ErrorCode::class) + fun `setDefaultLogger`() + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `provision`(`specUri`: String, `keyMethod`: String?, `passKey`: String?, `profile`: String?, `recreate`: Boolean): AskarStore + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `open`(`specUri`: String, `keyMethod`: String?, `passKey`: String?, `profile`: String?): AskarStore + + @Throws(ErrorCode::class, CancellationException::class) + suspend fun `remove`(`specUri`: String): Boolean + +} + +class AskarStoreManager( + pointer: Pointer +) : FFIObject(pointer), AskarStoreManagerInterface { + constructor() : + this( + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_constructor_askarstoremanager_new( _status) +}) + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_askarstoremanager(this.pointer, status) + } + } + + + @Throws(ErrorCode::class)override fun `generateRawStoreKey`(`seed`: String?): String = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstoremanager_generate_raw_store_key(it, FfiConverterOptionalString.lower(`seed`), _status) +} + }.let { + FfiConverterString.lift(it) + } + + + @Throws(ErrorCode::class)override fun `setDefaultLogger`() = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstoremanager_set_default_logger(it, _status) +} + } + + + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `provision`(`specUri`: String, `keyMethod`: String?, `passKey`: String?, `profile`: String?, `recreate`: Boolean) : AskarStore { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstoremanager_provision( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`specUri`), FfiConverterOptionalString.lower(`keyMethod`), FfiConverterOptionalString.lower(`passKey`), FfiConverterOptionalString.lower(`profile`), FfiConverterBoolean.lower(`recreate`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `open`(`specUri`: String, `keyMethod`: String?, `passKey`: String?, `profile`: String?) : AskarStore { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstoremanager_open( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`specUri`), FfiConverterOptionalString.lower(`keyMethod`), FfiConverterOptionalString.lower(`passKey`), FfiConverterOptionalString.lower(`profile`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + @Throws(ErrorCode::class, CancellationException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `remove`(`specUri`: String) : Boolean { + // Create a new `CoroutineScope` for this operation, suspend the coroutine, and call the + // scaffolding function, passing it one of the callback handlers from `AsyncTypes.kt`. + // + // Make sure to retain a reference to the callback handler to ensure that it's not GCed before + // it's invoked + var callbackDataHolder: UniFfiFutureCallbackHandlerbool_TypeErrorCodeData? = null + try { + return coroutineScope { + val scope = this + return@coroutineScope suspendCoroutine { continuation -> + try { + val callbackData = createUniFfiFutureCallbackHandlerbool_TypeErrorCodeData(continuation) + callbackDataHolder = callbackData + callWithPointer { thisPtr -> + rustCall { status -> + UniFFILib.uniffi_aries_askar_fn_method_askarstoremanager_remove( + thisPtr, + // FIXME create macro that handles the comma + FfiConverterString.lower(`specUri`), + FfiConverterForeignExecutor.lower(scope), + callbackData.resultHandler, + callbackData.continuationRef, + status, + ) + } + } + } catch (e: Exception) { + continuation.resumeWithException(e) + } + } + } + } finally { + callbackDataHolder?.dropHandle?.dropIt() + } + } + + + +} + +object FfiConverterTypeAskarStoreManager: FfiConverter { + override fun lower(value: AskarStoreManager): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): AskarStoreManager { + return AskarStoreManager(value) + } + + override fun read(source: NoCopySource): AskarStoreManager { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: AskarStoreManager) = 8 + + override fun write(value: AskarStoreManager, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AsyncTypes.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AsyncTypes.kt new file mode 100644 index 00000000..83c0b63a --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/AsyncTypes.kt @@ -0,0 +1,334 @@ +package aries_askar + +// Async return type handlers + +import kotlin.coroutines.Continuation + +// FFI type for callback handlers +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackUInt8 +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackInt8 +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackInt32 +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackInt64 +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrAskarCrypto +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrAskarEcdh1PU +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrAskarEcdhEs +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrAskarLocalKey +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrAskarScan +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrAskarSession +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrAskarStore +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrAskarStoreManager +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrEncryptedBuffer +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustArcPtrLocalKeyFactory +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiFutureCallbackRustBuffer + +// Callback handlers for an async call. These are invoked by Rust when the future is ready. +// They lift the return value or error and resume the suspended function. +expect fun createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackUInt8, + val dropHandle: DropHandle< + Unit + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandleri32Data(continuation: Continuation) + : UniFfiFutureCallbackHandleri32Data + +data class UniFfiFutureCallbackHandleri32Data( + val resultHandler: UniFfiFutureCallbackInt32, + val dropHandle: DropHandle< + Int + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandleri64_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandleri64_TypeErrorCodeData + +data class UniFfiFutureCallbackHandleri64_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackInt64, + val dropHandle: DropHandle< + Long + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerboolData(continuation: Continuation) + : UniFfiFutureCallbackHandlerboolData + +data class UniFfiFutureCallbackHandlerboolData( + val resultHandler: UniFfiFutureCallbackInt8, + val dropHandle: DropHandle< + Boolean + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerbool_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerbool_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerbool_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackInt8, + val dropHandle: DropHandle< + Boolean + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerstringData(continuation: Continuation) + : UniFfiFutureCallbackHandlerstringData + +data class UniFfiFutureCallbackHandlerstringData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + String + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerstring_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerstring_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerstring_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + String + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAskarCryptoData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarCryptoData + +data class UniFfiFutureCallbackHandlerTypeAskarCryptoData( + val resultHandler: UniFfiFutureCallbackRustArcPtrAskarCrypto, + val dropHandle: DropHandle< + AskarCrypto + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAskarEcdh1PUData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarEcdh1PUData + +data class UniFfiFutureCallbackHandlerTypeAskarEcdh1PUData( + val resultHandler: UniFfiFutureCallbackRustArcPtrAskarEcdh1PU, + val dropHandle: DropHandle< + AskarEcdh1Pu + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAskarEcdhEsData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarEcdhEsData + +data class UniFfiFutureCallbackHandlerTypeAskarEcdhEsData( + val resultHandler: UniFfiFutureCallbackRustArcPtrAskarEcdhEs, + val dropHandle: DropHandle< + AskarEcdhEs + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustArcPtrAskarLocalKey, + val dropHandle: DropHandle< + AskarLocalKey + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustArcPtrAskarScan, + val dropHandle: DropHandle< + AskarScan + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustArcPtrAskarSession, + val dropHandle: DropHandle< + AskarSession + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustArcPtrAskarStore, + val dropHandle: DropHandle< + AskarStore + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAskarStoreManagerData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarStoreManagerData + +data class UniFfiFutureCallbackHandlerTypeAskarStoreManagerData( + val resultHandler: UniFfiFutureCallbackRustArcPtrAskarStoreManager, + val dropHandle: DropHandle< + AskarStoreManager + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustArcPtrEncryptedBuffer, + val dropHandle: DropHandle< + EncryptedBuffer + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeLocalKeyFactoryData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeLocalKeyFactoryData + +data class UniFfiFutureCallbackHandlerTypeLocalKeyFactoryData( + val resultHandler: UniFfiFutureCallbackRustArcPtrLocalKeyFactory, + val dropHandle: DropHandle< + LocalKeyFactory + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + AeadParams + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerTypeAskarKeyAlgData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarKeyAlgData + +data class UniFfiFutureCallbackHandlerTypeAskarKeyAlgData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + AskarKeyAlg + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerOptionalstringData(continuation: Continuation) + : UniFfiFutureCallbackHandlerOptionalstringData + +data class UniFfiFutureCallbackHandlerOptionalstringData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + String? + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + AskarEntry? + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + AskarKeyEntry? + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData(continuation: Continuation?>) + : UniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + List? + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerSequenceu8Data(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceu8Data + +data class UniFfiFutureCallbackHandlerSequenceu8Data( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + List + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerSequenceu8_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceu8_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerSequenceu8_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + List + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerSequencestring_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequencestring_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerSequencestring_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + List + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + List + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData + +data class UniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + List + >, + val continuationRef: Pointer +) +expect fun createUniFfiFutureCallbackHandlerMapStringStringData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerMapStringStringData + +data class UniFfiFutureCallbackHandlerMapStringStringData( + val resultHandler: UniFfiFutureCallbackRustBuffer, + val dropHandle: DropHandle< + Map + >, + val continuationRef: Pointer +) + +expect class DropHandle { + fun dropIt() +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/Disposable.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/Disposable.kt new file mode 100644 index 00000000..36322f52 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/Disposable.kt @@ -0,0 +1,22 @@ +package aries_askar + +interface Disposable { + fun destroy() + + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance() + .forEach(Disposable::destroy) + } + } +} + +inline fun T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + this?.destroy() + } catch (_: Throwable) { + } + } \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/EncryptedBuffer.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/EncryptedBuffer.kt new file mode 100644 index 00000000..08292787 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/EncryptedBuffer.kt @@ -0,0 +1,85 @@ +package aries_askar + + +import okio.Buffer + +interface EncryptedBufferInterface { + + fun `ciphertext`(): List + + fun `nonce`(): List + + fun `tag`(): List + + fun `ciphertextTag`(): List + +} + +class EncryptedBuffer( + pointer: Pointer +) : FFIObject(pointer), EncryptedBufferInterface { + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_encryptedbuffer(this.pointer, status) + } + } + + override fun `ciphertext`(): List = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext(it, _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + override fun `nonce`(): List = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_encryptedbuffer_nonce(it, _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + override fun `tag`(): List = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_encryptedbuffer_tag(it, _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + override fun `ciphertextTag`(): List = + callWithPointer { + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext_tag(it, _status) +} + }.let { + FfiConverterSequenceUByte.lift(it) + } + + + + +} + +object FfiConverterTypeEncryptedBuffer: FfiConverter { + override fun lower(value: EncryptedBuffer): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): EncryptedBuffer { + return EncryptedBuffer(value) + } + + override fun read(source: NoCopySource): EncryptedBuffer { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: EncryptedBuffer) = 8 + + override fun write(value: EncryptedBuffer, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/ErrorCode.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/ErrorCode.kt new file mode 100644 index 00000000..e7370f1e --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/ErrorCode.kt @@ -0,0 +1,197 @@ +package aries_askar + + +import okio.Buffer + + +sealed class ErrorCode(message: String): Exception(message) { + + + class Success( + message: String + ) : ErrorCode(message) + + class Backend( + message: String + ) : ErrorCode(message) + + class Busy( + message: String + ) : ErrorCode(message) + + class Duplicate( + message: String + ) : ErrorCode(message) + + class Encryption( + message: String + ) : ErrorCode(message) + + class Input( + message: String + ) : ErrorCode(message) + + class NotFound( + message: String + ) : ErrorCode(message) + + class Unexpected( + message: String + ) : ErrorCode(message) + + class Unsupported( + message: String + ) : ErrorCode(message) + + class Custom( + message: String + ) : ErrorCode(message) + + + companion object ErrorHandler : CallStatusErrorHandler { + override fun lift(errorBuffer: RustBuffer): ErrorCode = FfiConverterTypeErrorCode.lift(errorBuffer) + } + + +} + +object FfiConverterTypeErrorCode : FfiConverterRustBuffer { + override fun read(source: NoCopySource): ErrorCode { + + + return when(source.readInt()) { + 1 -> ErrorCode.Success( + FfiConverterString.read(source), + ) + 2 -> ErrorCode.Backend( + FfiConverterString.read(source), + ) + 3 -> ErrorCode.Busy( + FfiConverterString.read(source), + ) + 4 -> ErrorCode.Duplicate( + FfiConverterString.read(source), + ) + 5 -> ErrorCode.Encryption( + FfiConverterString.read(source), + ) + 6 -> ErrorCode.Input( + FfiConverterString.read(source), + ) + 7 -> ErrorCode.NotFound( + FfiConverterString.read(source), + ) + 8 -> ErrorCode.Unexpected( + FfiConverterString.read(source), + ) + 9 -> ErrorCode.Unsupported( + FfiConverterString.read(source), + ) + 10 -> ErrorCode.Custom( + FfiConverterString.read(source), + ) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: ErrorCode): Int { + return when(value) { + is ErrorCode.Success -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + is ErrorCode.Backend -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + is ErrorCode.Busy -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + is ErrorCode.Duplicate -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + is ErrorCode.Encryption -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + is ErrorCode.Input -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + is ErrorCode.NotFound -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + is ErrorCode.Unexpected -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + is ErrorCode.Unsupported -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + is ErrorCode.Custom -> ( + 4 + + FfiConverterString.allocationSize(value.message!!) + ) + } + } + + override fun write(value: ErrorCode, buf: Buffer) { + when(value) { + is ErrorCode.Success -> { + buf.writeInt(1) + FfiConverterString.write(value.message!!, buf) + Unit + } + is ErrorCode.Backend -> { + buf.writeInt(2) + FfiConverterString.write(value.message!!, buf) + Unit + } + is ErrorCode.Busy -> { + buf.writeInt(3) + FfiConverterString.write(value.message!!, buf) + Unit + } + is ErrorCode.Duplicate -> { + buf.writeInt(4) + FfiConverterString.write(value.message!!, buf) + Unit + } + is ErrorCode.Encryption -> { + buf.writeInt(5) + FfiConverterString.write(value.message!!, buf) + Unit + } + is ErrorCode.Input -> { + buf.writeInt(6) + FfiConverterString.write(value.message!!, buf) + Unit + } + is ErrorCode.NotFound -> { + buf.writeInt(7) + FfiConverterString.write(value.message!!, buf) + Unit + } + is ErrorCode.Unexpected -> { + buf.writeInt(8) + FfiConverterString.write(value.message!!, buf) + Unit + } + is ErrorCode.Unsupported -> { + buf.writeInt(9) + FfiConverterString.write(value.message!!, buf) + Unit + } + is ErrorCode.Custom -> { + buf.writeInt(10) + FfiConverterString.write(value.message!!, buf) + Unit + } + } + } + +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FFIObject.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FFIObject.kt new file mode 100644 index 00000000..9bed2ee8 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FFIObject.kt @@ -0,0 +1,42 @@ +package aries_askar + +import kotlinx.atomicfu.* + +abstract class FFIObject( + protected val pointer: Pointer +) : Disposable { + + private val wasDestroyed = atomic(false) + private val callCounter = atomic(1L) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + if (this.wasDestroyed.compareAndSet(expect = false, update = true)) { + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + do { + val c = this.callCounter.value + if (c == 0L) { + throw IllegalStateException("${this::class.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this::class.simpleName} call counter would overflow") + } + } while (!this.callCounter.compareAndSet(expect = c, update = c + 1L)) + try { + return block(this.pointer) + } finally { + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverter.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverter.kt new file mode 100644 index 00000000..3e10c4ee --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverter.kt @@ -0,0 +1,30 @@ +package aries_askar + +import okio.Buffer + +interface FfiConverter { + + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer { + val buffer = Buffer().apply { write(value, buffer) } + return allocRustBuffer(buffer) + } + + fun liftFromRustBuffer(rbuf: RustBuffer): KotlinType { + val byteBuf = rbuf.asSource() + try { + val item = read(byteBuf) + if (!byteBuf.exhausted()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + rbuf.free() + } + } + + fun lift(value: FfiType): KotlinType + fun lower(value: KotlinType): FfiType + fun read(source: NoCopySource): KotlinType + fun allocationSize(value: KotlinType): Int + fun write(value: KotlinType, buf: Buffer) +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterBoolean.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterBoolean.kt new file mode 100644 index 00000000..b2f7e6ee --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterBoolean.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterBoolean : FfiConverter { + override fun lift(value: Byte): Boolean = value.toInt() != 0 + + override fun read(source: NoCopySource): Boolean = lift(source.readByte()) + + override fun lower(value: Boolean): Byte = if (value) 1.toByte() else 0.toByte() + + override fun allocationSize(value: Boolean) = 1 + + override fun write(value: Boolean, buf: Buffer) { + buf.writeByte(lower(value).toInt()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterByte.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterByte.kt new file mode 100644 index 00000000..75f3eaaa --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterByte.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterByte : FfiConverter { + override fun lift(value: Byte): Byte = value + + override fun read(source: NoCopySource): Byte = source.readByte() + + override fun lower(value: Byte): Byte = value + + override fun allocationSize(value: Byte) = 1 + + override fun write(value: Byte, buf: Buffer) { + buf.writeByte(value.toInt()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterCallbackInterface.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterCallbackInterface.kt new file mode 100644 index 00000000..64a4096d --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterCallbackInterface.kt @@ -0,0 +1,77 @@ +package aries_askar + +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.locks.reentrantLock +import kotlinx.atomicfu.locks.withLock +import okio.Buffer + +internal class ConcurrentHandleMap( + private val leftMap: MutableMap = mutableMapOf(), + private val rightMap: MutableMap = mutableMapOf() +) { + private val lock = reentrantLock() + private val currentHandle = atomic(0L) + + fun insert(obj: T): Handle = + lock.withLock { + rightMap[obj] ?: currentHandle.getAndIncrement() + .let { it.toULong() } + .also { handle -> + leftMap[handle] = obj + rightMap[obj] = handle + } + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +internal const val IDX_CALLBACK_FREE = 0 +// Callback return codes +internal const val UNIFFI_CALLBACK_SUCCESS = 0 +internal const val UNIFFI_CALLBACK_ERROR = 1 +internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 + +abstract class FfiConverterCallbackInterface : FfiConverter { + private val handleMap = ConcurrentHandleMap() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + internal abstract fun register(lib: UniFFILib) + + fun drop(handle: Handle) { + handleMap.remove(handle) + } + + override fun lift(value: Handle): CallbackInterface { + return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") + } + + override fun read(source: NoCopySource) = lift(source.readLong().toULong()) + + override fun lower(value: CallbackInterface) = + handleMap.insert(value).also { + check(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + override fun allocationSize(value: CallbackInterface) = 8 + + override fun write(value: CallbackInterface, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterDouble.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterDouble.kt new file mode 100644 index 00000000..36f9f8d6 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterDouble.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterDouble : FfiConverter { + override fun lift(value: Double): Double = value + + override fun read(source: NoCopySource): Double = Double.fromBits(source.readLong()) + + override fun lower(value: Double): Double = value + + override fun allocationSize(value: Double) = 8 + + override fun write(value: Double, buf: Buffer) { + buf.writeLong(value.toRawBits()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterDuration.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterDuration.kt new file mode 100644 index 00000000..b74a8f07 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterDuration.kt @@ -0,0 +1,28 @@ +package aries_askar + +import okio.Buffer +import kotlin.time.Duration +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +object FfiConverterDuration : FfiConverterRustBuffer { + override fun read(source: NoCopySource): Duration { + val seconds = source.readLong().seconds + val nanoseconds = source.readInt().nanoseconds + val duration = seconds + nanoseconds + if (duration < 0.nanoseconds) { + throw IllegalArgumentException("Duration nanoseconds exceed minimum or maximum supported by uniffi") + } + return duration + } + + override fun allocationSize(value: Duration) = 12 + + override fun write(value: Duration, buf: Buffer) { + if (value < 0.nanoseconds) { + throw IllegalArgumentException("Invalid duration, must be non-negative") + } + buf.writeLong(value.inWholeSeconds) + buf.writeInt(value.inWholeNanoseconds.toInt()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterFloat.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterFloat.kt new file mode 100644 index 00000000..efe96708 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterFloat.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterFloat : FfiConverter { + override fun lift(value: Float): Float = value + + override fun read(source: NoCopySource): Float = Float.fromBits(source.readInt()) + + override fun lower(value: Float): Float = value + + override fun allocationSize(value: Float) = 4 + + override fun write(value: Float, buf: Buffer) { + buf.writeInt(value.toRawBits()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterForeignExecutor.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterForeignExecutor.kt new file mode 100644 index 00000000..5c8f0504 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterForeignExecutor.kt @@ -0,0 +1,37 @@ +package aries_askar + +import kotlinx.coroutines.CoroutineScope +import okio.Buffer + +object FfiConverterForeignExecutor: FfiConverter { + internal val handleMap = UniFfiHandleMap() + internal val foreignExecutorCallback = createUniFfiForeignExecutorCallback() + + internal fun drop(handle: ULong) { + handleMap.remove(handle) + } + + internal fun register(lib: UniFFILib) { + lib.uniffi_foreign_executor_callback_set(foreignExecutorCallback) + } + + // Number of live handles, exposed so we can test the memory management + public fun handleCount() : Int { + return handleMap.size + } + + override fun allocationSize(value: CoroutineScope) = ULong.SIZE_BYTES + + override fun lift(value: ULong): CoroutineScope { + return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift") + } + + override fun read(source: NoCopySource): CoroutineScope = TODO("unused") + + override fun lower(value: CoroutineScope): ULong { + return handleMap.insert(value) + } + + override fun write(value: CoroutineScope, buf: Buffer) = TODO("unused") + +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterInstant.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterInstant.kt new file mode 100644 index 00000000..e026f751 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterInstant.kt @@ -0,0 +1,29 @@ +package aries_askar + +import kotlinx.datetime.Instant +import okio.Buffer + +object FfiConverterInstant : FfiConverterRustBuffer { + override fun read(source: NoCopySource): Instant { + val seconds = source.readLong() + val nanoseconds = source.readInt() + val instant = Instant.fromEpochSeconds(seconds, nanoseconds) + if (nanoseconds < 0) { + throw IllegalArgumentException("Instant nanoseconds exceed minimum or maximum supported by uniffi") + } + return instant + } + + override fun allocationSize(value: Instant) = 12 + + override fun write(value: Instant, buf: Buffer) { + value.epochSeconds + + if (value.nanosecondsOfSecond < 0) { + throw IllegalArgumentException("Invalid timestamp, nano value must be non-negative") + } + + buf.writeLong(value.epochSeconds) + buf.writeInt(value.nanosecondsOfSecond) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterInt.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterInt.kt new file mode 100644 index 00000000..cc2e8a78 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterInt.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterInt : FfiConverter { + override fun lift(value: Int): Int = value + + override fun read(source: NoCopySource): Int = source.readInt() + + override fun lower(value: Int): Int = value + + override fun allocationSize(value: Int) = 4 + + override fun write(value: Int, buf: Buffer) { + buf.writeInt(value) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterLong.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterLong.kt new file mode 100644 index 00000000..b82f7922 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterLong.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterLong : FfiConverter { + override fun lift(value: Long): Long = value + + override fun read(source: NoCopySource): Long = source.readLong() + + override fun lower(value: Long): Long = value + + override fun allocationSize(value: Long) = 8 + + override fun write(value: Long, buf: Buffer) { + buf.writeLong(value) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterMapStringString.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterMapStringString.kt new file mode 100644 index 00000000..2890b901 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterMapStringString.kt @@ -0,0 +1,32 @@ +package aries_askar + +import okio.Buffer +object FfiConverterMapStringString: FfiConverterRustBuffer> { + override fun read(source: NoCopySource): Map { + val items : MutableMap = mutableMapOf() + val len = source.readInt() + repeat(len) { + val k = FfiConverterString.read(source) + val v = FfiConverterString.read(source) + items[k] = v + } + return items + } + + override fun allocationSize(value: Map): Int { + val spaceForMapSize = 4 + val spaceForChildren = value.map { (k, v) -> + FfiConverterString.allocationSize(k) + + FfiConverterString.allocationSize(v) + }.sum() + return spaceForMapSize + spaceForChildren + } + + override fun write(value: Map, buf: Buffer) { + buf.writeInt(value.size) + value.forEach { (k, v) -> + FfiConverterString.write(k, buf) + FfiConverterString.write(v, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalLong.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalLong.kt new file mode 100644 index 00000000..3c123632 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalLong.kt @@ -0,0 +1,29 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterOptionalLong: FfiConverterRustBuffer { + override fun read(source: NoCopySource): Long? { + if (source.readByte().toInt() == 0) { + return null + } + return FfiConverterLong.read(source) + } + + override fun allocationSize(value: Long?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterLong.allocationSize(value) + } + } + + override fun write(value: Long?, buf: Buffer) { + if (value == null) { + buf.writeByte(0) + } else { + buf.writeByte(1) + FfiConverterLong.write(value, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalSequenceTypeAskarEntry.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalSequenceTypeAskarEntry.kt new file mode 100644 index 00000000..ed9b3dc5 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalSequenceTypeAskarEntry.kt @@ -0,0 +1,29 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterOptionalSequenceTypeAskarEntry: FfiConverterRustBuffer?> { + override fun read(source: NoCopySource): List? { + if (source.readByte().toInt() == 0) { + return null + } + return FfiConverterSequenceTypeAskarEntry.read(source) + } + + override fun allocationSize(value: List?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterSequenceTypeAskarEntry.allocationSize(value) + } + } + + override fun write(value: List?, buf: Buffer) { + if (value == null) { + buf.writeByte(0) + } else { + buf.writeByte(1) + FfiConverterSequenceTypeAskarEntry.write(value, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalSequenceUByte.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalSequenceUByte.kt new file mode 100644 index 00000000..ba8e017e --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalSequenceUByte.kt @@ -0,0 +1,29 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterOptionalSequenceUByte: FfiConverterRustBuffer?> { + override fun read(source: NoCopySource): List? { + if (source.readByte().toInt() == 0) { + return null + } + return FfiConverterSequenceUByte.read(source) + } + + override fun allocationSize(value: List?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterSequenceUByte.allocationSize(value) + } + } + + override fun write(value: List?, buf: Buffer) { + if (value == null) { + buf.writeByte(0) + } else { + buf.writeByte(1) + FfiConverterSequenceUByte.write(value, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalString.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalString.kt new file mode 100644 index 00000000..cdb8b427 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalString.kt @@ -0,0 +1,29 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterOptionalString: FfiConverterRustBuffer { + override fun read(source: NoCopySource): String? { + if (source.readByte().toInt() == 0) { + return null + } + return FfiConverterString.read(source) + } + + override fun allocationSize(value: String?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterString.allocationSize(value) + } + } + + override fun write(value: String?, buf: Buffer) { + if (value == null) { + buf.writeByte(0) + } else { + buf.writeByte(1) + FfiConverterString.write(value, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeAskarEntry.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeAskarEntry.kt new file mode 100644 index 00000000..64358563 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeAskarEntry.kt @@ -0,0 +1,29 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterOptionalTypeAskarEntry: FfiConverterRustBuffer { + override fun read(source: NoCopySource): AskarEntry? { + if (source.readByte().toInt() == 0) { + return null + } + return FfiConverterTypeAskarEntry.read(source) + } + + override fun allocationSize(value: AskarEntry?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterTypeAskarEntry.allocationSize(value) + } + } + + override fun write(value: AskarEntry?, buf: Buffer) { + if (value == null) { + buf.writeByte(0) + } else { + buf.writeByte(1) + FfiConverterTypeAskarEntry.write(value, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeAskarKeyAlg.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeAskarKeyAlg.kt new file mode 100644 index 00000000..e6e5a809 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeAskarKeyAlg.kt @@ -0,0 +1,29 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterOptionalTypeAskarKeyAlg: FfiConverterRustBuffer { + override fun read(source: NoCopySource): AskarKeyAlg? { + if (source.readByte().toInt() == 0) { + return null + } + return FfiConverterTypeAskarKeyAlg.read(source) + } + + override fun allocationSize(value: AskarKeyAlg?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterTypeAskarKeyAlg.allocationSize(value) + } + } + + override fun write(value: AskarKeyAlg?, buf: Buffer) { + if (value == null) { + buf.writeByte(0) + } else { + buf.writeByte(1) + FfiConverterTypeAskarKeyAlg.write(value, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeAskarKeyEntry.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeAskarKeyEntry.kt new file mode 100644 index 00000000..2018235f --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeAskarKeyEntry.kt @@ -0,0 +1,29 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterOptionalTypeAskarKeyEntry: FfiConverterRustBuffer { + override fun read(source: NoCopySource): AskarKeyEntry? { + if (source.readByte().toInt() == 0) { + return null + } + return FfiConverterTypeAskarKeyEntry.read(source) + } + + override fun allocationSize(value: AskarKeyEntry?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterTypeAskarKeyEntry.allocationSize(value) + } + } + + override fun write(value: AskarKeyEntry?, buf: Buffer) { + if (value == null) { + buf.writeByte(0) + } else { + buf.writeByte(1) + FfiConverterTypeAskarKeyEntry.write(value, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeSeedMethod.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeSeedMethod.kt new file mode 100644 index 00000000..d585c624 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterOptionalTypeSeedMethod.kt @@ -0,0 +1,29 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterOptionalTypeSeedMethod: FfiConverterRustBuffer { + override fun read(source: NoCopySource): SeedMethod? { + if (source.readByte().toInt() == 0) { + return null + } + return FfiConverterTypeSeedMethod.read(source) + } + + override fun allocationSize(value: SeedMethod?): Int { + if (value == null) { + return 1 + } else { + return 1 + FfiConverterTypeSeedMethod.allocationSize(value) + } + } + + override fun write(value: SeedMethod?, buf: Buffer) { + if (value == null) { + buf.writeByte(0) + } else { + buf.writeByte(1) + FfiConverterTypeSeedMethod.write(value, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterRustBuffer.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterRustBuffer.kt new file mode 100644 index 00000000..40b8ecf9 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterRustBuffer.kt @@ -0,0 +1,6 @@ +package aries_askar + +interface FfiConverterRustBuffer : FfiConverter { + override fun lift(value: RustBuffer) = liftFromRustBuffer(value) + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceString.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceString.kt new file mode 100644 index 00000000..466f55aa --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceString.kt @@ -0,0 +1,25 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterSequenceString: FfiConverterRustBuffer> { + override fun read(source: NoCopySource): List { + val len = source.readInt() + return List(len) { + FfiConverterString.read(source) + } + } + + override fun allocationSize(value: List): Int { + val sizeForLength = 4 + val sizeForItems = value.map { FfiConverterString.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: Buffer) { + buf.writeInt(value.size) + value.forEach { + FfiConverterString.write(it, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceTypeAskarEntry.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceTypeAskarEntry.kt new file mode 100644 index 00000000..00f31361 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceTypeAskarEntry.kt @@ -0,0 +1,25 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterSequenceTypeAskarEntry: FfiConverterRustBuffer> { + override fun read(source: NoCopySource): List { + val len = source.readInt() + return List(len) { + FfiConverterTypeAskarEntry.read(source) + } + } + + override fun allocationSize(value: List): Int { + val sizeForLength = 4 + val sizeForItems = value.map { FfiConverterTypeAskarEntry.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: Buffer) { + buf.writeInt(value.size) + value.forEach { + FfiConverterTypeAskarEntry.write(it, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceTypeAskarKeyEntry.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceTypeAskarKeyEntry.kt new file mode 100644 index 00000000..6b8e7cbc --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceTypeAskarKeyEntry.kt @@ -0,0 +1,25 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterSequenceTypeAskarKeyEntry: FfiConverterRustBuffer> { + override fun read(source: NoCopySource): List { + val len = source.readInt() + return List(len) { + FfiConverterTypeAskarKeyEntry.read(source) + } + } + + override fun allocationSize(value: List): Int { + val sizeForLength = 4 + val sizeForItems = value.map { FfiConverterTypeAskarKeyEntry.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: Buffer) { + buf.writeInt(value.size) + value.forEach { + FfiConverterTypeAskarKeyEntry.write(it, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceUByte.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceUByte.kt new file mode 100644 index 00000000..6a0c7c32 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterSequenceUByte.kt @@ -0,0 +1,25 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterSequenceUByte: FfiConverterRustBuffer> { + override fun read(source: NoCopySource): List { + val len = source.readInt() + return List(len) { + FfiConverterUByte.read(source) + } + } + + override fun allocationSize(value: List): Int { + val sizeForLength = 4 + val sizeForItems = value.map { FfiConverterUByte.allocationSize(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List, buf: Buffer) { + buf.writeInt(value.size) + value.forEach { + FfiConverterUByte.write(it, buf) + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterShort.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterShort.kt new file mode 100644 index 00000000..16655ce3 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterShort.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterShort : FfiConverter { + override fun lift(value: Short): Short = value + + override fun read(source: NoCopySource): Short = source.readShort() + + override fun lower(value: Short): Short = value + + override fun allocationSize(value: Short) = 2 + + override fun write(value: Short, buf: Buffer) { + buf.writeShort(value.toInt()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterString.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterString.kt new file mode 100644 index 00000000..22e5b97a --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterString.kt @@ -0,0 +1,37 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterString : FfiConverter { + override fun lift(value: RustBuffer): String { + try { + val byteArr = value.asSource().readByteArray(value.dataSize.toLong()) + return byteArr.decodeToString() + } finally { + value.free() + } + } + + override fun read(source: NoCopySource): String { + val len = source.readInt() + val byteArr = source.readByteArray(len.toLong()) + return byteArr.decodeToString() + } + + override fun lower(value: String): RustBuffer { + val buffer = Buffer().write(value.encodeToByteArray()) + return allocRustBuffer(buffer) + } + + override fun allocationSize(value: String): Int { + val sizeForLength = 4 + val sizeForString = value.length * 3 + return sizeForLength + sizeForString + } + + override fun write(value: String, buf: Buffer) { + val byteArr = value.encodeToByteArray() + buf.writeInt(byteArr.size) + buf.write(byteArr) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterUByte.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterUByte.kt new file mode 100644 index 00000000..6554d7a4 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterUByte.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterUByte : FfiConverter { + override fun lift(value: UByte): UByte = value + + override fun read(source: NoCopySource): UByte = lift(source.readByte().toUByte()) + + override fun lower(value: UByte): UByte = value + + override fun allocationSize(value: UByte) = 1 + + override fun write(value: UByte, buf: Buffer) { + buf.writeByte(value.toInt()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterUInt.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterUInt.kt new file mode 100644 index 00000000..91cee997 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterUInt.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterUInt : FfiConverter { + override fun lift(value: UInt): UInt = value + + override fun read(source: NoCopySource): UInt = lift(source.readInt().toUInt()) + + override fun lower(value: UInt): UInt = value + + override fun allocationSize(value: UInt) = 4 + + override fun write(value: UInt, buf: Buffer) { + buf.writeInt(value.toInt()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterULong.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterULong.kt new file mode 100644 index 00000000..9e1bcbf8 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterULong.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterULong : FfiConverter { + override fun lift(value: ULong): ULong = value + + override fun read(source: NoCopySource): ULong = lift(source.readLong().toULong()) + + override fun lower(value: ULong): ULong = value + + override fun allocationSize(value: ULong) = 8 + + override fun write(value: ULong, buf: Buffer) { + buf.writeLong(value.toLong()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterUShort.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterUShort.kt new file mode 100644 index 00000000..17036fa0 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/FfiConverterUShort.kt @@ -0,0 +1,17 @@ +package aries_askar + +import okio.Buffer + +object FfiConverterUShort : FfiConverter { + override fun lift(value: UShort): UShort = value + + override fun read(source: NoCopySource): UShort = lift(source.readShort().toUShort()) + + override fun lower(value: UShort): UShort = value + + override fun allocationSize(value: UShort) = 2 + + override fun write(value: UShort, buf: Buffer) { + buf.writeShort(value.toInt()) + } +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/ForeignBytes.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/ForeignBytes.kt new file mode 100644 index 00000000..57a19088 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/ForeignBytes.kt @@ -0,0 +1,5 @@ +package aries_askar + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class ForeignBytes \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/ForeignCallback.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/ForeignCallback.kt new file mode 100644 index 00000000..b8038111 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/ForeignCallback.kt @@ -0,0 +1,7 @@ +package aries_askar + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class ForeignCallback + +typealias Handle = ULong diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/InternalException.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/InternalException.kt new file mode 100644 index 00000000..3b88182f --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/InternalException.kt @@ -0,0 +1,3 @@ +package aries_askar + +class InternalException(message: String) : Exception(message) \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/LocalKeyFactory.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/LocalKeyFactory.kt new file mode 100644 index 00000000..6e4143dc --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/LocalKeyFactory.kt @@ -0,0 +1,124 @@ +package aries_askar + + +import okio.Buffer + +interface LocalKeyFactoryInterface { + + @Throws(ErrorCode::class) + fun `generate`(`alg`: AskarKeyAlg, `ephemeral`: Boolean): AskarLocalKey + + @Throws(ErrorCode::class) + fun `fromSeed`(`alg`: AskarKeyAlg, `seed`: List, `method`: SeedMethod?): AskarLocalKey + + @Throws(ErrorCode::class) + fun `fromJwkSlice`(`jwk`: List): AskarLocalKey + + @Throws(ErrorCode::class) + fun `fromJwk`(`jwk`: String): AskarLocalKey + + @Throws(ErrorCode::class) + fun `fromPublicBytes`(`alg`: AskarKeyAlg, `bytes`: List): AskarLocalKey + + @Throws(ErrorCode::class) + fun `fromSecretBytes`(`alg`: AskarKeyAlg, `bytes`: List): AskarLocalKey + +} + +class LocalKeyFactory( + pointer: Pointer +) : FFIObject(pointer), LocalKeyFactoryInterface { + constructor() : + this( + rustCall() { _status -> + UniFFILib.uniffi_aries_askar_fn_constructor_localkeyfactory_new( _status) +}) + + override protected fun freeRustArcPtr() { + rustCall() { status -> + UniFFILib.uniffi_aries_askar_fn_free_localkeyfactory(this.pointer, status) + } + } + + + @Throws(ErrorCode::class)override fun `generate`(`alg`: AskarKeyAlg, `ephemeral`: Boolean): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_localkeyfactory_generate(it, FfiConverterTypeAskarKeyAlg.lower(`alg`), FfiConverterBoolean.lower(`ephemeral`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + @Throws(ErrorCode::class)override fun `fromSeed`(`alg`: AskarKeyAlg, `seed`: List, `method`: SeedMethod?): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_localkeyfactory_from_seed(it, FfiConverterTypeAskarKeyAlg.lower(`alg`), FfiConverterSequenceUByte.lower(`seed`), FfiConverterOptionalTypeSeedMethod.lower(`method`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + @Throws(ErrorCode::class)override fun `fromJwkSlice`(`jwk`: List): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_localkeyfactory_from_jwk_slice(it, FfiConverterSequenceUByte.lower(`jwk`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + @Throws(ErrorCode::class)override fun `fromJwk`(`jwk`: String): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_localkeyfactory_from_jwk(it, FfiConverterString.lower(`jwk`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + @Throws(ErrorCode::class)override fun `fromPublicBytes`(`alg`: AskarKeyAlg, `bytes`: List): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_localkeyfactory_from_public_bytes(it, FfiConverterTypeAskarKeyAlg.lower(`alg`), FfiConverterSequenceUByte.lower(`bytes`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + @Throws(ErrorCode::class)override fun `fromSecretBytes`(`alg`: AskarKeyAlg, `bytes`: List): AskarLocalKey = + callWithPointer { + rustCallWithError(ErrorCode) { _status -> + UniFFILib.uniffi_aries_askar_fn_method_localkeyfactory_from_secret_bytes(it, FfiConverterTypeAskarKeyAlg.lower(`alg`), FfiConverterSequenceUByte.lower(`bytes`), _status) +} + }.let { + FfiConverterTypeAskarLocalKey.lift(it) + } + + + + +} + +object FfiConverterTypeLocalKeyFactory: FfiConverter { + override fun lower(value: LocalKeyFactory): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): LocalKeyFactory { + return LocalKeyFactory(value) + } + + override fun read(source: NoCopySource): LocalKeyFactory { + return lift(source.readLong().toPointer()) + } + + override fun allocationSize(value: LocalKeyFactory) = 8 + + override fun write(value: LocalKeyFactory, buf: Buffer) { + buf.writeLong(lower(value).toLong()) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/Pointer.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/Pointer.kt new file mode 100644 index 00000000..4a3acd5c --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/Pointer.kt @@ -0,0 +1,26 @@ +package aries_askar + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class Pointer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UBytePointer + +expect fun Long.toPointer(): Pointer + +expect fun Pointer.toLong(): Long + +expect fun UBytePointer.asSource(len: Long): NoCopySource + +interface NoCopySource { + fun exhausted(): Boolean + fun readByte(): Byte + fun readInt(): Int + fun readLong(): Long + fun readShort(): Short + fun readByteArray(): ByteArray + fun readByteArray(len: Long): ByteArray +} + diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/RustBuffer.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/RustBuffer.kt new file mode 100644 index 00000000..65c83212 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/RustBuffer.kt @@ -0,0 +1,23 @@ +package aries_askar + +import okio.Buffer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class RustBuffer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class RustBufferPointer + +expect fun RustBuffer.asSource(): NoCopySource + +expect val RustBuffer.dataSize: Int + +expect fun RustBuffer.free() + +expect fun allocRustBuffer(buffer: Buffer): RustBuffer + +expect fun RustBufferPointer.setValue(value: RustBuffer) + +expect fun emptyRustBuffer(): RustBuffer diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/RustCallStatus.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/RustCallStatus.kt new file mode 100644 index 00000000..4031d4c6 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/RustCallStatus.kt @@ -0,0 +1,21 @@ +package aries_askar + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class RustCallStatus + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class RustCallStatusByValue + +fun RustCallStatus.isSuccess(): Boolean = statusCode == 0.toByte() + +fun RustCallStatus.isError(): Boolean = statusCode == 1.toByte() + +fun RustCallStatus.isPanic(): Boolean = statusCode == 2.toByte() + +expect val RustCallStatus.statusCode: Byte + +expect val RustCallStatus.errorBuffer: RustBuffer + +expect fun withRustCallStatus(block: (RustCallStatus) -> T): T diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/SeedMethod.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/SeedMethod.kt new file mode 100644 index 00000000..edabc041 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/SeedMethod.kt @@ -0,0 +1,23 @@ +package aries_askar + + +import okio.Buffer + +enum class SeedMethod { + BLS_KEY_GEN; +} + +object FfiConverterTypeSeedMethod: FfiConverterRustBuffer { + override fun read(source: NoCopySource) = try { + SeedMethod.values()[source.readInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: SeedMethod) = 4 + + override fun write(value: SeedMethod, buf: Buffer) { + buf.writeInt(value.ordinal + 1) + } +} + diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/TopLevelFunctions.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/TopLevelFunctions.kt new file mode 100644 index 00000000..5346fcd0 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/TopLevelFunctions.kt @@ -0,0 +1,9 @@ +package aries_askar + + + + +import kotlinx.coroutines.coroutineScope +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/UniFFILib.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/UniFFILib.kt new file mode 100644 index 00000000..6d2e0c6c --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/UniFFILib.kt @@ -0,0 +1,559 @@ +package aries_askar + + + +expect object UniFFILib { + fun uniffi_aries_askar_fn_free_askarentry(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarentry_category(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarentry_name(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarentry_tags(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarentry_value(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_free_askarkeyentry(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarkeyentry_algorithm(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarkeyentry_metadata(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarkeyentry_name(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarkeyentry_is_local(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Byte + + fun uniffi_aries_askar_fn_method_askarkeyentry_tags(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarkeyentry_load_local_key(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_free_askarscan(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarscan_next(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarscan_fetch_all(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_free_askarsession(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_close(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_count(`ptr`: Pointer,`category`: RustBuffer,`tagFilter`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackInt64,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_fetch(`ptr`: Pointer,`category`: RustBuffer,`name`: RustBuffer,`forUpdate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_fetch_all(`ptr`: Pointer,`category`: RustBuffer,`tagFilter`: RustBuffer,`limit`: RustBuffer,`forUpdate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_update(`ptr`: Pointer,`operation`: RustBuffer,`category`: RustBuffer,`name`: RustBuffer,`value`: RustBuffer,`tags`: RustBuffer,`expiryMs`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_remove_all(`ptr`: Pointer,`category`: RustBuffer,`tagFilter`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackInt64,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_insert_key(`ptr`: Pointer,`name`: RustBuffer,`key`: Pointer,`metadata`: RustBuffer,`tags`: RustBuffer,`expiryMs`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_fetch_key(`ptr`: Pointer,`name`: RustBuffer,`forUpdate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_fetch_all_keys(`ptr`: Pointer,`algorithm`: RustBuffer,`thumbprint`: RustBuffer,`tagFilter`: RustBuffer,`limit`: RustBuffer,`forUpdate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_remove_key(`ptr`: Pointer,`name`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarsession_update_key(`ptr`: Pointer,`name`: RustBuffer,`metadata`: RustBuffer,`tags`: RustBuffer,`expiryMs`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_free_askarstore(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstore_get_profile_name(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstore_create_profile(`ptr`: Pointer,`profile`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstore_remove_profile(`ptr`: Pointer,`profile`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstore_rekey(`ptr`: Pointer,`keyMethod`: RustBuffer,`passKey`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstore_close(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstore_scan(`ptr`: Pointer,`profile`: RustBuffer,`categogy`: RustBuffer,`tagFilter`: RustBuffer,`offset`: RustBuffer,`limit`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarScan,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstore_session(`ptr`: Pointer,`profile`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarSession,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_free_askarlocalkey(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarlocalkey_to_public_bytes(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_to_secret_bytes(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_to_key_exchange(`ptr`: Pointer,`alg`: RustBuffer,`pk`: Pointer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarlocalkey_algorithm(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_public(`ptr`: Pointer,`alg`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_secret(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprint(`ptr`: Pointer,`alg`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprints(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_convert_key(`ptr`: Pointer,`alg`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarlocalkey_aead_params(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_aead_padding(`ptr`: Pointer,`msgLen`: Int,_uniffi_out_err: RustCallStatus, + ): Int + + fun uniffi_aries_askar_fn_method_askarlocalkey_aead_random_nonce(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_aead_encrypt(`ptr`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarlocalkey_aead_decrypt(`ptr`: Pointer,`ciphertext`: RustBuffer,`tag`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_sign_message(`ptr`: Pointer,`message`: RustBuffer,`sigType`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarlocalkey_verify_signature(`ptr`: Pointer,`message`: RustBuffer,`signature`: RustBuffer,`sigType`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Byte + + fun uniffi_aries_askar_fn_method_askarlocalkey_wrap_key(`ptr`: Pointer,`key`: Pointer,`nonce`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarlocalkey_unwrap_key(`ptr`: Pointer,`alg`: RustBuffer,`ciphertext`: RustBuffer,`tag`: RustBuffer,`nonce`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_free_encryptedbuffer(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_encryptedbuffer_nonce(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_encryptedbuffer_tag(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext_tag(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_free_localkeyfactory(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_constructor_localkeyfactory_new(_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_localkeyfactory_generate(`ptr`: Pointer,`alg`: RustBuffer,`ephemeral`: Byte,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_localkeyfactory_from_seed(`ptr`: Pointer,`alg`: RustBuffer,`seed`: RustBuffer,`method`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_localkeyfactory_from_jwk_slice(`ptr`: Pointer,`jwk`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_localkeyfactory_from_jwk(`ptr`: Pointer,`jwk`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_localkeyfactory_from_public_bytes(`ptr`: Pointer,`alg`: RustBuffer,`bytes`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_localkeyfactory_from_secret_bytes(`ptr`: Pointer,`alg`: RustBuffer,`bytes`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_free_askarstoremanager(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_constructor_askarstoremanager_new(_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarstoremanager_generate_raw_store_key(`ptr`: Pointer,`seed`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarstoremanager_set_default_logger(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstoremanager_provision(`ptr`: Pointer,`specUri`: RustBuffer,`keyMethod`: RustBuffer,`passKey`: RustBuffer,`profile`: RustBuffer,`recreate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarStore,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstoremanager_open(`ptr`: Pointer,`specUri`: RustBuffer,`keyMethod`: RustBuffer,`passKey`: RustBuffer,`profile`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarStore,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_method_askarstoremanager_remove(`ptr`: Pointer,`specUri`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_free_askarcrypto(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_constructor_askarcrypto_new(_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarcrypto_random_nonce(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarcrypto_crypto_box(`ptr`: Pointer,`receiverKey`: Pointer,`senderKey`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarcrypto_box_open(`ptr`: Pointer,`receiverKey`: Pointer,`senderKey`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarcrypto_box_seal(`ptr`: Pointer,`receiverKey`: Pointer,`message`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarcrypto_box_seal_open(`ptr`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_free_askarecdhes(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_constructor_askarecdhes_new(`algId`: RustBuffer,`apu`: RustBuffer,`apv`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarecdhes_derive_key(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`receive`: Byte,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarecdhes_encrypt_direct(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarecdhes_decrypt_direct(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,`tag`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarecdhes_sender_wrap_key(`ptr`: Pointer,`wrapAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`cek`: Pointer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarecdhes_receiver_unwrap_key(`ptr`: Pointer,`wrapAlg`: RustBuffer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,`nonce`: RustBuffer,`tag`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_free_askarecdh1pu(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun uniffi_aries_askar_fn_constructor_askarecdh1pu_new(`algId`: RustBuffer,`apu`: RustBuffer,`apv`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarecdh1pu_derive_key(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`ccTag`: RustBuffer,`receive`: Byte,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarecdh1pu_encrypt_direct(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarecdh1pu_decrypt_direct(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,`tag`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_fn_method_askarecdh1pu_sender_wrap_key(`ptr`: Pointer,`wrapAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`cek`: Pointer,`ccTag`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun uniffi_aries_askar_fn_method_askarecdh1pu_receiver_unwrap_key(`ptr`: Pointer,`wrapAlg`: RustBuffer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,`ccTag`: RustBuffer,`nonce`: RustBuffer,`tag`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer + + fun ffi_aries_askar_rustbuffer_alloc(`size`: Int,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun ffi_aries_askar_rustbuffer_from_bytes(`bytes`: ForeignBytes,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun ffi_aries_askar_rustbuffer_free(`buf`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Unit + + fun ffi_aries_askar_rustbuffer_reserve(`buf`: RustBuffer,`additional`: Int,_uniffi_out_err: RustCallStatus, + ): RustBuffer + + fun uniffi_aries_askar_checksum_method_askarentry_category( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarentry_name( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarentry_tags( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarentry_value( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarkeyentry_algorithm( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarkeyentry_metadata( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarkeyentry_name( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarkeyentry_is_local( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarkeyentry_tags( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarkeyentry_load_local_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarscan_next( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarscan_fetch_all( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_close( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_count( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_fetch( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_fetch_all( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_update( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_remove_all( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_insert_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_fetch_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_fetch_all_keys( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_remove_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarsession_update_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstore_get_profile_name( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstore_create_profile( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstore_remove_profile( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstore_rekey( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstore_close( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstore_scan( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstore_session( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_to_public_bytes( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_to_secret_bytes( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_to_key_exchange( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_algorithm( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_public( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_secret( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprint( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprints( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_convert_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_params( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_padding( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_random_nonce( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_encrypt( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_decrypt( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_sign_message( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_verify_signature( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_wrap_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarlocalkey_unwrap_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext( + ): UShort + + fun uniffi_aries_askar_checksum_method_encryptedbuffer_nonce( + ): UShort + + fun uniffi_aries_askar_checksum_method_encryptedbuffer_tag( + ): UShort + + fun uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext_tag( + ): UShort + + fun uniffi_aries_askar_checksum_method_localkeyfactory_generate( + ): UShort + + fun uniffi_aries_askar_checksum_method_localkeyfactory_from_seed( + ): UShort + + fun uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk_slice( + ): UShort + + fun uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk( + ): UShort + + fun uniffi_aries_askar_checksum_method_localkeyfactory_from_public_bytes( + ): UShort + + fun uniffi_aries_askar_checksum_method_localkeyfactory_from_secret_bytes( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstoremanager_generate_raw_store_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstoremanager_set_default_logger( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstoremanager_provision( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstoremanager_open( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarstoremanager_remove( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarcrypto_random_nonce( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarcrypto_crypto_box( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarcrypto_box_open( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarcrypto_box_seal( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarcrypto_box_seal_open( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdhes_derive_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdhes_encrypt_direct( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdhes_decrypt_direct( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdhes_sender_wrap_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdhes_receiver_unwrap_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdh1pu_derive_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdh1pu_encrypt_direct( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdh1pu_decrypt_direct( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdh1pu_sender_wrap_key( + ): UShort + + fun uniffi_aries_askar_checksum_method_askarecdh1pu_receiver_unwrap_key( + ): UShort + + fun uniffi__checksum_constructor_localkeyfactory_new( + ): UShort + + fun uniffi__checksum_constructor_askarstoremanager_new( + ): UShort + + fun uniffi__checksum_constructor_askarcrypto_new( + ): UShort + + fun uniffi__checksum_constructor_askarecdhes_new( + ): UShort + + fun uniffi__checksum_constructor_askarecdh1pu_new( + ): UShort + + fun uniffi_foreign_executor_callback_set(`callback`: UniFfiForeignExecutorCallback, + ): Unit + + fun ffi_aries_askar_uniffi_contract_version( + ): UInt + + +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/UniFfiForeignExecutorCallback.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/UniFfiForeignExecutorCallback.kt new file mode 100644 index 00000000..d393995b --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/UniFfiForeignExecutorCallback.kt @@ -0,0 +1,7 @@ +package aries_askar + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +expect class UniFfiForeignExecutorCallback + +expect fun createUniFfiForeignExecutorCallback(): UniFfiForeignExecutorCallback \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/UniffiHandleMap.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/UniffiHandleMap.kt new file mode 100644 index 00000000..121b38c9 --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/UniffiHandleMap.kt @@ -0,0 +1,18 @@ +package aries_askar + +// Map handles to objects +// +// This is used when the Rust code expects an opaque pointer to represent some foreign object. +// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an +// object reference , nor does it support leaking a reference to Rust. +// +// Instead, this class maps ULong values to objects so that we can pass a pointer-sized type to +// Rust when it needs an opaque pointer. +// +// TODO: refactor callbacks to use this class +expect class UniFfiHandleMap() { + val size: Int + fun insert(obj: T): ULong + fun get(handle: ULong): T? + fun remove(handle: ULong) +} diff --git a/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/rustCall.kt b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/rustCall.kt new file mode 100644 index 00000000..37669a5e --- /dev/null +++ b/wrappers/kotlin/askarBindings/commonMain/kotlin/aries_askar/rustCall.kt @@ -0,0 +1,46 @@ +package aries_askar + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +internal inline fun rustCallWithError(errorHandler: CallStatusErrorHandler, crossinline callback: (RustCallStatus) -> U): U = + withRustCallStatus { status -> + val return_value = callback(status) + checkCallStatus(errorHandler, status) + return_value + } + + +// Check RustCallStatus and throw an error if the call wasn't successful +internal fun checkCallStatus(errorHandler: CallStatusErrorHandler, status: RustCallStatus) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.errorBuffer) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.errorBuffer.dataSize > 0) { + // TODO avoid additional copy + throw InternalException(FfiConverterString.lift(status.errorBuffer)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +interface CallStatusErrorHandler { + fun lift(errorBuffer: RustBuffer): E; +} + +object NullCallStatusErrorHandler : CallStatusErrorHandler { + override fun lift(errorBuffer: RustBuffer): InternalException { + errorBuffer.free() + return InternalException("Unexpected CALL_ERROR") + } +} + +internal inline fun rustCall(crossinline callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); +} diff --git a/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/AsyncTypes.kt b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/AsyncTypes.kt new file mode 100644 index 00000000..4263e9ee --- /dev/null +++ b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/AsyncTypes.kt @@ -0,0 +1,662 @@ +package aries_askar + +// Async return type handlers + + +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +// FFI type for callback handlers +actual class UniFfiFutureCallbackUInt8(private val inner: (ULong, UByte, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: UByte, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackInt8(private val inner: (ULong, Byte, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Byte, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackInt32(private val inner: (ULong, Int, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Int, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackInt64(private val inner: (ULong, Long, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Long, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrAskarCrypto(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrAskarEcdh1PU(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrAskarEcdhEs(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrAskarLocalKey(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrAskarScan(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrAskarSession(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrAskarStore(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrAskarStoreManager(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrEncryptedBuffer(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustArcPtrLocalKeyFactory(private val inner: (ULong, Pointer?, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } +actual class UniFfiFutureCallbackRustBuffer(private val inner: (ULong, RustBuffer, RustCallStatusByValue) -> Unit) : com.sun.jna.Callback { + // Note: callbackData does not contain a valid pointer. We could pass Rust a pointer/usize to represent the + // continuation, but with JNA it's easier to just store it in the callback handler. + // FIXME nullifying pointers does not seem right, but otherwise it crashes (witnessed for a nullable UByte callback parameter) + // on the other hand, nullifying pointers could be all that is needed because this covers all structs etc and + // primitive types implement Default which is hopefully used + // upstream, this reads \{\{ callback_param|ffi_type_name_by_value \}\}? + fun invoke(_callbackData: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue) { + inner(_callbackData, returnValue, callStatus) +} } + +// Callback handlers for an async call. These are invoked by Rust when the future is ready. They +// lift the return value or error and resume the suspended function. +actual fun createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData = + UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData( + UniFfiFutureCallbackUInt8 { _: ULong, returnValue: UByte, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(Unit) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandleri32Data(continuation: Continuation) + : UniFfiFutureCallbackHandleri32Data = + UniFfiFutureCallbackHandleri32Data( + UniFfiFutureCallbackInt32 { _: ULong, returnValue: Int, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterInt.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandleri64_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandleri64_TypeErrorCodeData = + UniFfiFutureCallbackHandleri64_TypeErrorCodeData( + UniFfiFutureCallbackInt64 { _: ULong, returnValue: Long, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterLong.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerboolData(continuation: Continuation) + : UniFfiFutureCallbackHandlerboolData = + UniFfiFutureCallbackHandlerboolData( + UniFfiFutureCallbackInt8 { _: ULong, returnValue: Byte, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterBoolean.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerbool_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerbool_TypeErrorCodeData = + UniFfiFutureCallbackHandlerbool_TypeErrorCodeData( + UniFfiFutureCallbackInt8 { _: ULong, returnValue: Byte, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterBoolean.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerstringData(continuation: Continuation) + : UniFfiFutureCallbackHandlerstringData = + UniFfiFutureCallbackHandlerstringData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterString.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerstring_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerstring_TypeErrorCodeData = + UniFfiFutureCallbackHandlerstring_TypeErrorCodeData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterString.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAskarCryptoData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarCryptoData = + UniFfiFutureCallbackHandlerTypeAskarCryptoData( + UniFfiFutureCallbackRustArcPtrAskarCrypto { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarCrypto.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAskarEcdh1PUData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarEcdh1PUData = + UniFfiFutureCallbackHandlerTypeAskarEcdh1PUData( + UniFfiFutureCallbackRustArcPtrAskarEcdh1PU { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarEcdh1Pu.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAskarEcdhEsData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarEcdhEsData = + UniFfiFutureCallbackHandlerTypeAskarEcdhEsData( + UniFfiFutureCallbackRustArcPtrAskarEcdhEs { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarEcdhEs.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCodeData = + UniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCodeData( + UniFfiFutureCallbackRustArcPtrAskarLocalKey { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAskarLocalKey.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData = + UniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData( + UniFfiFutureCallbackRustArcPtrAskarScan { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAskarScan.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData = + UniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData( + UniFfiFutureCallbackRustArcPtrAskarSession { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAskarSession.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData = + UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData( + UniFfiFutureCallbackRustArcPtrAskarStore { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAskarStore.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAskarStoreManagerData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarStoreManagerData = + UniFfiFutureCallbackHandlerTypeAskarStoreManagerData( + UniFfiFutureCallbackRustArcPtrAskarStoreManager { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarStoreManager.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCodeData = + UniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCodeData( + UniFfiFutureCallbackRustArcPtrEncryptedBuffer { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeEncryptedBuffer.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeLocalKeyFactoryData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeLocalKeyFactoryData = + UniFfiFutureCallbackHandlerTypeLocalKeyFactoryData( + UniFfiFutureCallbackRustArcPtrLocalKeyFactory { _: ULong, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeLocalKeyFactory.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCodeData = + UniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCodeData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAeadParams.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerTypeAskarKeyAlgData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarKeyAlgData = + UniFfiFutureCallbackHandlerTypeAskarKeyAlgData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarKeyAlg.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerOptionalstringData(continuation: Continuation) + : UniFfiFutureCallbackHandlerOptionalstringData = + UniFfiFutureCallbackHandlerOptionalstringData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterOptionalString.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData = + UniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterOptionalTypeAskarEntry.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData = + UniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterOptionalTypeAskarKeyEntry.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData(continuation: Continuation?>) + : UniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData = + UniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterOptionalSequenceTypeAskarEntry.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerSequenceu8Data(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceu8Data = + UniFfiFutureCallbackHandlerSequenceu8Data( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterSequenceUByte.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerSequenceu8_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceu8_TypeErrorCodeData = + UniFfiFutureCallbackHandlerSequenceu8_TypeErrorCodeData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterSequenceUByte.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerSequencestring_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequencestring_TypeErrorCodeData = + UniFfiFutureCallbackHandlerSequencestring_TypeErrorCodeData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterSequenceString.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData = + UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterSequenceTypeAskarEntry.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData = + UniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(ErrorCode, callStatus) + continuation.resume(FfiConverterSequenceTypeAskarKeyEntry.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) +actual fun createUniFfiFutureCallbackHandlerMapStringStringData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerMapStringStringData = + UniFfiFutureCallbackHandlerMapStringStringData( + UniFfiFutureCallbackRustBuffer { _: ULong, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + try { + checkCallStatus(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterMapStringString.lift(returnValue!!)) + } catch (e: Throwable) { + continuation.resumeWithException(e) + } + }, + DropHandle(), + // on the JVM, the continuation is held by UniFfiFutureCallbackHandleru64 so we do not need handle native + // pointers. The JVM does not let us pass a dummy Pointer.NULL, so we insert some garbage that + // must not be touched instead + Pointer.createConstant(0xCAFED00DL) + ) + +actual class DropHandle { + actual fun dropIt() { + // no-op on the JVM + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/ForeignBytes.kt b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/ForeignBytes.kt new file mode 100644 index 00000000..569720f1 --- /dev/null +++ b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/ForeignBytes.kt @@ -0,0 +1,12 @@ +package aries_askar + +import com.sun.jna.Structure + +@Structure.FieldOrder("len", "data") +actual open class ForeignBytes : Structure() { + @JvmField + var len: Int = 0 + + @JvmField + var data: Pointer? = null +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/ForeignCallbackJvm.kt b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/ForeignCallbackJvm.kt new file mode 100644 index 00000000..b4576d81 --- /dev/null +++ b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/ForeignCallbackJvm.kt @@ -0,0 +1,23 @@ +package aries_askar + +import com.sun.jna.Callback + +class NativeCallback( + private val invokeImpl: ( + handle: Handle, + method: Int, + argsData: UBytePointer, + argsLen: Int, + outBuf: RustBufferPointer // RustBufferByReference + ) -> Int +) : Callback { + fun invoke( + handle: Handle, + method: Int, + argsData: UBytePointer, + argsLen: Int, + outBuf: RustBufferPointer // RustBufferByReference + ): Int = invokeImpl(handle, method, argsData, argsLen, outBuf) +} + +actual typealias ForeignCallback = NativeCallback diff --git a/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/Pointer.kt b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/Pointer.kt new file mode 100644 index 00000000..751422ca --- /dev/null +++ b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/Pointer.kt @@ -0,0 +1,40 @@ +package aries_askar + +import java.nio.ByteOrder + +actual typealias Pointer = com.sun.jna.Pointer + +actual typealias UBytePointer = com.sun.jna.Pointer + +actual fun Long.toPointer() = com.sun.jna.Pointer(this) + +actual fun Pointer.toLong(): Long = com.sun.jna.Pointer.nativeValue(this) + +actual fun UBytePointer.asSource(len: Long): NoCopySource = object : NoCopySource { + val buffer = getByteBuffer(0, len).also { + it.order(ByteOrder.BIG_ENDIAN) + } + + override fun exhausted(): Boolean = !buffer.hasRemaining() + + override fun readByte(): Byte = buffer.get() + + override fun readInt(): Int = buffer.getInt() + + override fun readLong(): Long = buffer.getLong() + + override fun readShort(): Short = buffer.getShort() + + override fun readByteArray(): ByteArray { + val remaining = buffer.remaining() + return readByteArray(remaining.toLong()) + } + + override fun readByteArray(len: Long): ByteArray { + val startIndex = buffer.position().toLong() + val indexAfterLast = (startIndex + len).toInt() + val byteArray = getByteArray(startIndex, len.toInt()) + buffer.position(indexAfterLast) + return byteArray + } +} diff --git a/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/RustBuffer.kt b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/RustBuffer.kt new file mode 100644 index 00000000..42f86cfe --- /dev/null +++ b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/RustBuffer.kt @@ -0,0 +1,52 @@ +package aries_askar + +import com.sun.jna.Structure +import com.sun.jna.ptr.ByReference +import okio.Buffer + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBufferStructure : Structure() { + @JvmField + var capacity: Int = 0 + + @JvmField + var len: Int = 0 + + @JvmField + var data: Pointer? = null +} + +actual class RustBuffer : RustBufferStructure(), Structure.ByValue + +actual class RustBufferPointer : ByReference(16) { + fun setValueInternal(value: RustBuffer) { + pointer.setInt(0, value.capacity) + pointer.setInt(4, value.len) + pointer.setPointer(8, value.data) + } +} + +actual fun RustBuffer.asSource(): NoCopySource = requireNotNull(data).asSource(len.toLong()) + +actual val RustBuffer.dataSize: Int + get() = len + +actual fun RustBuffer.free() = + rustCall { status -> + UniFFILib.ffi_aries_askar_rustbuffer_free(this, status) + } + +actual fun allocRustBuffer(buffer: Buffer): RustBuffer = + rustCall { status -> + val size = buffer.size + UniFFILib.ffi_aries_askar_rustbuffer_alloc(size.toInt(), status).also { rustBuffer: RustBuffer -> + val data = rustBuffer.data + ?: throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + rustBuffer.writeField("len", size.toInt()) + buffer.read(data.getByteBuffer(0, size)) + } + } + +actual fun RustBufferPointer.setValue(value: RustBuffer) = setValueInternal(value) + +actual fun emptyRustBuffer(): RustBuffer = RustBuffer() \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/RustCallStatusJvm.kt b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/RustCallStatusJvm.kt new file mode 100644 index 00000000..5ff45fce --- /dev/null +++ b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/RustCallStatusJvm.kt @@ -0,0 +1,27 @@ +package aries_askar + +import com.sun.jna.Structure +import com.sun.jna.Structure.ByValue + +@Structure.FieldOrder("code", "error_buf") +actual open class RustCallStatus : Structure() { + @JvmField + var code: Byte = 0 + + @JvmField + var error_buf: RustBuffer = RustBuffer() +} + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("NO_ACTUAL_FOR_EXPECT") +actual open class RustCallStatusByValue : RustCallStatus(), ByValue + +actual val RustCallStatus.statusCode: Byte + get() = code +actual val RustCallStatus.errorBuffer: RustBuffer + get() = error_buf + +actual fun withRustCallStatus(block: (RustCallStatus) -> T): T { + val rustCallStatus = RustCallStatus() + return block(rustCallStatus) +} diff --git a/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/UniFFILib.kt b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/UniFFILib.kt new file mode 100644 index 00000000..9120313c --- /dev/null +++ b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/UniFFILib.kt @@ -0,0 +1,762 @@ +package aries_askar + + + +import com.sun.jna.Library +import com.sun.jna.Native + +@Synchronized +private fun findLibraryName(): String { + val componentName = "aries_askar" + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "aries_askar" +} + +actual object UniFFILib : Library { + init { + Native.register(UniFFILib::class.java, findLibraryName()) + FfiConverterForeignExecutor.register(this) + + } + + @JvmName("uniffi_aries_askar_fn_free_askarentry") + actual external fun uniffi_aries_askar_fn_free_askarentry(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarentry_category") + actual external fun uniffi_aries_askar_fn_method_askarentry_category(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarentry_name") + actual external fun uniffi_aries_askar_fn_method_askarentry_name(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarentry_tags") + actual external fun uniffi_aries_askar_fn_method_askarentry_tags(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarentry_value") + actual external fun uniffi_aries_askar_fn_method_askarentry_value(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_free_askarkeyentry") + actual external fun uniffi_aries_askar_fn_free_askarkeyentry(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarkeyentry_algorithm") + actual external fun uniffi_aries_askar_fn_method_askarkeyentry_algorithm(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarkeyentry_metadata") + actual external fun uniffi_aries_askar_fn_method_askarkeyentry_metadata(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarkeyentry_name") + actual external fun uniffi_aries_askar_fn_method_askarkeyentry_name(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarkeyentry_is_local") + actual external fun uniffi_aries_askar_fn_method_askarkeyentry_is_local(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Byte + + @JvmName("uniffi_aries_askar_fn_method_askarkeyentry_tags") + actual external fun uniffi_aries_askar_fn_method_askarkeyentry_tags(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarkeyentry_load_local_key") + actual external fun uniffi_aries_askar_fn_method_askarkeyentry_load_local_key(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_free_askarscan") + actual external fun uniffi_aries_askar_fn_free_askarscan(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarscan_next") + actual external fun uniffi_aries_askar_fn_method_askarscan_next(`ptr`: Pointer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustBuffer, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarscan_fetch_all") + actual external fun uniffi_aries_askar_fn_method_askarscan_fetch_all(`ptr`: Pointer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustBuffer, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_free_askarsession") + actual external fun uniffi_aries_askar_fn_free_askarsession(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_close") + actual external fun uniffi_aries_askar_fn_method_askarsession_close(`ptr`: Pointer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackUInt8, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_count") + actual external fun uniffi_aries_askar_fn_method_askarsession_count(`ptr`: Pointer, `category`: RustBuffer, `tagFilter`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackInt64, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_fetch") + actual external fun uniffi_aries_askar_fn_method_askarsession_fetch(`ptr`: Pointer, `category`: RustBuffer, `name`: RustBuffer, `forUpdate`: Byte, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustBuffer, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_fetch_all") + actual external fun uniffi_aries_askar_fn_method_askarsession_fetch_all(`ptr`: Pointer, `category`: RustBuffer, `tagFilter`: RustBuffer, `limit`: RustBuffer, `forUpdate`: Byte, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustBuffer, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_update") + actual external fun uniffi_aries_askar_fn_method_askarsession_update(`ptr`: Pointer, `operation`: RustBuffer, `category`: RustBuffer, `name`: RustBuffer, `value`: RustBuffer, `tags`: RustBuffer, `expiryMs`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackUInt8, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_remove_all") + actual external fun uniffi_aries_askar_fn_method_askarsession_remove_all(`ptr`: Pointer, `category`: RustBuffer, `tagFilter`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackInt64, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_insert_key") + actual external fun uniffi_aries_askar_fn_method_askarsession_insert_key(`ptr`: Pointer, `name`: RustBuffer, `key`: Pointer, `metadata`: RustBuffer, `tags`: RustBuffer, `expiryMs`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackUInt8, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_fetch_key") + actual external fun uniffi_aries_askar_fn_method_askarsession_fetch_key(`ptr`: Pointer, `name`: RustBuffer, `forUpdate`: Byte, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustBuffer, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_fetch_all_keys") + actual external fun uniffi_aries_askar_fn_method_askarsession_fetch_all_keys(`ptr`: Pointer, `algorithm`: RustBuffer, `thumbprint`: RustBuffer, `tagFilter`: RustBuffer, `limit`: RustBuffer, `forUpdate`: Byte, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustBuffer, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_remove_key") + actual external fun uniffi_aries_askar_fn_method_askarsession_remove_key(`ptr`: Pointer, `name`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackUInt8, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarsession_update_key") + actual external fun uniffi_aries_askar_fn_method_askarsession_update_key(`ptr`: Pointer, `name`: RustBuffer, `metadata`: RustBuffer, `tags`: RustBuffer, `expiryMs`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackUInt8, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_free_askarstore") + actual external fun uniffi_aries_askar_fn_free_askarstore(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstore_get_profile_name") + actual external fun uniffi_aries_askar_fn_method_askarstore_get_profile_name(`ptr`: Pointer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustBuffer, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstore_create_profile") + actual external fun uniffi_aries_askar_fn_method_askarstore_create_profile(`ptr`: Pointer, `profile`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustBuffer, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstore_remove_profile") + actual external fun uniffi_aries_askar_fn_method_askarstore_remove_profile(`ptr`: Pointer, `profile`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackInt8, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstore_rekey") + actual external fun uniffi_aries_askar_fn_method_askarstore_rekey(`ptr`: Pointer, `keyMethod`: RustBuffer, `passKey`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackUInt8, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstore_close") + actual external fun uniffi_aries_askar_fn_method_askarstore_close(`ptr`: Pointer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackUInt8, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstore_scan") + actual external fun uniffi_aries_askar_fn_method_askarstore_scan(`ptr`: Pointer, `profile`: RustBuffer, `categogy`: RustBuffer, `tagFilter`: RustBuffer, `offset`: RustBuffer, `limit`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarScan, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstore_session") + actual external fun uniffi_aries_askar_fn_method_askarstore_session(`ptr`: Pointer, `profile`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarSession, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_free_askarlocalkey") + actual external fun uniffi_aries_askar_fn_free_askarlocalkey(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_to_public_bytes") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_to_public_bytes(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_to_secret_bytes") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_to_secret_bytes(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_to_key_exchange") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_to_key_exchange(`ptr`: Pointer, `alg`: RustBuffer, `pk`: Pointer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_algorithm") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_algorithm(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_public") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_public(`ptr`: Pointer, `alg`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_secret") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_secret(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprint") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprint(`ptr`: Pointer, `alg`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprints") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprints(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_convert_key") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_convert_key(`ptr`: Pointer, `alg`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_aead_params") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_aead_params(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_aead_padding") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_aead_padding(`ptr`: Pointer, `msgLen`: Int, _uniffi_out_err: RustCallStatus, + ): Int + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_aead_random_nonce") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_aead_random_nonce(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_aead_encrypt") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_aead_encrypt(`ptr`: Pointer, `message`: RustBuffer, `nonce`: RustBuffer, `aad`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_aead_decrypt") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_aead_decrypt(`ptr`: Pointer, `ciphertext`: RustBuffer, `tag`: RustBuffer, `nonce`: RustBuffer, `aad`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_sign_message") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_sign_message(`ptr`: Pointer, `message`: RustBuffer, `sigType`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_verify_signature") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_verify_signature(`ptr`: Pointer, `message`: RustBuffer, `signature`: RustBuffer, `sigType`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Byte + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_wrap_key") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_wrap_key(`ptr`: Pointer, `key`: Pointer, `nonce`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarlocalkey_unwrap_key") + actual external fun uniffi_aries_askar_fn_method_askarlocalkey_unwrap_key(`ptr`: Pointer, `alg`: RustBuffer, `ciphertext`: RustBuffer, `tag`: RustBuffer, `nonce`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_free_encryptedbuffer") + actual external fun uniffi_aries_askar_fn_free_encryptedbuffer(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext") + actual external fun uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_encryptedbuffer_nonce") + actual external fun uniffi_aries_askar_fn_method_encryptedbuffer_nonce(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_encryptedbuffer_tag") + actual external fun uniffi_aries_askar_fn_method_encryptedbuffer_tag(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext_tag") + actual external fun uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext_tag(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_free_localkeyfactory") + actual external fun uniffi_aries_askar_fn_free_localkeyfactory(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_constructor_localkeyfactory_new") + actual external fun uniffi_aries_askar_fn_constructor_localkeyfactory_new(_uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_localkeyfactory_generate") + actual external fun uniffi_aries_askar_fn_method_localkeyfactory_generate(`ptr`: Pointer, `alg`: RustBuffer, `ephemeral`: Byte, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_localkeyfactory_from_seed") + actual external fun uniffi_aries_askar_fn_method_localkeyfactory_from_seed(`ptr`: Pointer, `alg`: RustBuffer, `seed`: RustBuffer, `method`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_localkeyfactory_from_jwk_slice") + actual external fun uniffi_aries_askar_fn_method_localkeyfactory_from_jwk_slice(`ptr`: Pointer, `jwk`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_localkeyfactory_from_jwk") + actual external fun uniffi_aries_askar_fn_method_localkeyfactory_from_jwk(`ptr`: Pointer, `jwk`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_localkeyfactory_from_public_bytes") + actual external fun uniffi_aries_askar_fn_method_localkeyfactory_from_public_bytes(`ptr`: Pointer, `alg`: RustBuffer, `bytes`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_localkeyfactory_from_secret_bytes") + actual external fun uniffi_aries_askar_fn_method_localkeyfactory_from_secret_bytes(`ptr`: Pointer, `alg`: RustBuffer, `bytes`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_free_askarstoremanager") + actual external fun uniffi_aries_askar_fn_free_askarstoremanager(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_constructor_askarstoremanager_new") + actual external fun uniffi_aries_askar_fn_constructor_askarstoremanager_new(_uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarstoremanager_generate_raw_store_key") + actual external fun uniffi_aries_askar_fn_method_askarstoremanager_generate_raw_store_key(`ptr`: Pointer, `seed`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarstoremanager_set_default_logger") + actual external fun uniffi_aries_askar_fn_method_askarstoremanager_set_default_logger(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstoremanager_provision") + actual external fun uniffi_aries_askar_fn_method_askarstoremanager_provision(`ptr`: Pointer, `specUri`: RustBuffer, `keyMethod`: RustBuffer, `passKey`: RustBuffer, `profile`: RustBuffer, `recreate`: Byte, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarStore, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstoremanager_open") + actual external fun uniffi_aries_askar_fn_method_askarstoremanager_open(`ptr`: Pointer, `specUri`: RustBuffer, `keyMethod`: RustBuffer, `passKey`: RustBuffer, `profile`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarStore, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_method_askarstoremanager_remove") + actual external fun uniffi_aries_askar_fn_method_askarstoremanager_remove(`ptr`: Pointer, `specUri`: RustBuffer, `uniffiExecutor`: ULong, `uniffiCallback`: UniFfiFutureCallbackInt8, `uniffiCallbackData`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_free_askarcrypto") + actual external fun uniffi_aries_askar_fn_free_askarcrypto(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_constructor_askarcrypto_new") + actual external fun uniffi_aries_askar_fn_constructor_askarcrypto_new(_uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarcrypto_random_nonce") + actual external fun uniffi_aries_askar_fn_method_askarcrypto_random_nonce(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarcrypto_crypto_box") + actual external fun uniffi_aries_askar_fn_method_askarcrypto_crypto_box(`ptr`: Pointer, `receiverKey`: Pointer, `senderKey`: Pointer, `message`: RustBuffer, `nonce`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarcrypto_box_open") + actual external fun uniffi_aries_askar_fn_method_askarcrypto_box_open(`ptr`: Pointer, `receiverKey`: Pointer, `senderKey`: Pointer, `message`: RustBuffer, `nonce`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarcrypto_box_seal") + actual external fun uniffi_aries_askar_fn_method_askarcrypto_box_seal(`ptr`: Pointer, `receiverKey`: Pointer, `message`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarcrypto_box_seal_open") + actual external fun uniffi_aries_askar_fn_method_askarcrypto_box_seal_open(`ptr`: Pointer, `receiverKey`: Pointer, `ciphertext`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_free_askarecdhes") + actual external fun uniffi_aries_askar_fn_free_askarecdhes(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_constructor_askarecdhes_new") + actual external fun uniffi_aries_askar_fn_constructor_askarecdhes_new(`algId`: RustBuffer, `apu`: RustBuffer, `apv`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarecdhes_derive_key") + actual external fun uniffi_aries_askar_fn_method_askarecdhes_derive_key(`ptr`: Pointer, `encAlg`: RustBuffer, `ephemeralKey`: Pointer, `receiverKey`: Pointer, `receive`: Byte, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarecdhes_encrypt_direct") + actual external fun uniffi_aries_askar_fn_method_askarecdhes_encrypt_direct(`ptr`: Pointer, `encAlg`: RustBuffer, `ephemeralKey`: Pointer, `receiverKey`: Pointer, `message`: RustBuffer, `nonce`: RustBuffer, `aad`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarecdhes_decrypt_direct") + actual external fun uniffi_aries_askar_fn_method_askarecdhes_decrypt_direct(`ptr`: Pointer, `encAlg`: RustBuffer, `ephemeralKey`: Pointer, `receiverKey`: Pointer, `ciphertext`: RustBuffer, `tag`: RustBuffer, `nonce`: RustBuffer, `aad`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarecdhes_sender_wrap_key") + actual external fun uniffi_aries_askar_fn_method_askarecdhes_sender_wrap_key(`ptr`: Pointer, `wrapAlg`: RustBuffer, `ephemeralKey`: Pointer, `receiverKey`: Pointer, `cek`: Pointer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarecdhes_receiver_unwrap_key") + actual external fun uniffi_aries_askar_fn_method_askarecdhes_receiver_unwrap_key(`ptr`: Pointer, `wrapAlg`: RustBuffer, `encAlg`: RustBuffer, `ephemeralKey`: Pointer, `receiverKey`: Pointer, `ciphertext`: RustBuffer, `nonce`: RustBuffer, `tag`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_free_askarecdh1pu") + actual external fun uniffi_aries_askar_fn_free_askarecdh1pu(`ptr`: Pointer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("uniffi_aries_askar_fn_constructor_askarecdh1pu_new") + actual external fun uniffi_aries_askar_fn_constructor_askarecdh1pu_new(`algId`: RustBuffer, `apu`: RustBuffer, `apv`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarecdh1pu_derive_key") + actual external fun uniffi_aries_askar_fn_method_askarecdh1pu_derive_key(`ptr`: Pointer, `encAlg`: RustBuffer, `ephemeralKey`: Pointer, `senderKey`: Pointer, `receiverKey`: Pointer, `ccTag`: RustBuffer, `receive`: Byte, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarecdh1pu_encrypt_direct") + actual external fun uniffi_aries_askar_fn_method_askarecdh1pu_encrypt_direct(`ptr`: Pointer, `encAlg`: RustBuffer, `ephemeralKey`: Pointer, `senderKey`: Pointer, `receiverKey`: Pointer, `message`: RustBuffer, `nonce`: RustBuffer, `aad`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarecdh1pu_decrypt_direct") + actual external fun uniffi_aries_askar_fn_method_askarecdh1pu_decrypt_direct(`ptr`: Pointer, `encAlg`: RustBuffer, `ephemeralKey`: Pointer, `senderKey`: Pointer, `receiverKey`: Pointer, `ciphertext`: RustBuffer, `tag`: RustBuffer, `nonce`: RustBuffer, `aad`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_fn_method_askarecdh1pu_sender_wrap_key") + actual external fun uniffi_aries_askar_fn_method_askarecdh1pu_sender_wrap_key(`ptr`: Pointer, `wrapAlg`: RustBuffer, `ephemeralKey`: Pointer, `senderKey`: Pointer, `receiverKey`: Pointer, `cek`: Pointer, `ccTag`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("uniffi_aries_askar_fn_method_askarecdh1pu_receiver_unwrap_key") + actual external fun uniffi_aries_askar_fn_method_askarecdh1pu_receiver_unwrap_key(`ptr`: Pointer, `wrapAlg`: RustBuffer, `encAlg`: RustBuffer, `ephemeralKey`: Pointer, `senderKey`: Pointer, `receiverKey`: Pointer, `ciphertext`: RustBuffer, `ccTag`: RustBuffer, `nonce`: RustBuffer, `tag`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Pointer + + @JvmName("ffi_aries_askar_rustbuffer_alloc") + actual external fun ffi_aries_askar_rustbuffer_alloc(`size`: Int, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("ffi_aries_askar_rustbuffer_from_bytes") + actual external fun ffi_aries_askar_rustbuffer_from_bytes(`bytes`: ForeignBytes, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("ffi_aries_askar_rustbuffer_free") + actual external fun ffi_aries_askar_rustbuffer_free(`buf`: RustBuffer, _uniffi_out_err: RustCallStatus, + ): Unit + + @JvmName("ffi_aries_askar_rustbuffer_reserve") + actual external fun ffi_aries_askar_rustbuffer_reserve(`buf`: RustBuffer, `additional`: Int, _uniffi_out_err: RustCallStatus, + ): RustBuffer + + @JvmName("uniffi_aries_askar_checksum_method_askarentry_category") + actual external fun uniffi_aries_askar_checksum_method_askarentry_category( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarentry_name") + actual external fun uniffi_aries_askar_checksum_method_askarentry_name( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarentry_tags") + actual external fun uniffi_aries_askar_checksum_method_askarentry_tags( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarentry_value") + actual external fun uniffi_aries_askar_checksum_method_askarentry_value( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarkeyentry_algorithm") + actual external fun uniffi_aries_askar_checksum_method_askarkeyentry_algorithm( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarkeyentry_metadata") + actual external fun uniffi_aries_askar_checksum_method_askarkeyentry_metadata( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarkeyentry_name") + actual external fun uniffi_aries_askar_checksum_method_askarkeyentry_name( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarkeyentry_is_local") + actual external fun uniffi_aries_askar_checksum_method_askarkeyentry_is_local( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarkeyentry_tags") + actual external fun uniffi_aries_askar_checksum_method_askarkeyentry_tags( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarkeyentry_load_local_key") + actual external fun uniffi_aries_askar_checksum_method_askarkeyentry_load_local_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarscan_next") + actual external fun uniffi_aries_askar_checksum_method_askarscan_next( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarscan_fetch_all") + actual external fun uniffi_aries_askar_checksum_method_askarscan_fetch_all( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_close") + actual external fun uniffi_aries_askar_checksum_method_askarsession_close( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_count") + actual external fun uniffi_aries_askar_checksum_method_askarsession_count( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_fetch") + actual external fun uniffi_aries_askar_checksum_method_askarsession_fetch( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_fetch_all") + actual external fun uniffi_aries_askar_checksum_method_askarsession_fetch_all( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_update") + actual external fun uniffi_aries_askar_checksum_method_askarsession_update( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_remove_all") + actual external fun uniffi_aries_askar_checksum_method_askarsession_remove_all( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_insert_key") + actual external fun uniffi_aries_askar_checksum_method_askarsession_insert_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_fetch_key") + actual external fun uniffi_aries_askar_checksum_method_askarsession_fetch_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_fetch_all_keys") + actual external fun uniffi_aries_askar_checksum_method_askarsession_fetch_all_keys( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_remove_key") + actual external fun uniffi_aries_askar_checksum_method_askarsession_remove_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarsession_update_key") + actual external fun uniffi_aries_askar_checksum_method_askarsession_update_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstore_get_profile_name") + actual external fun uniffi_aries_askar_checksum_method_askarstore_get_profile_name( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstore_create_profile") + actual external fun uniffi_aries_askar_checksum_method_askarstore_create_profile( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstore_remove_profile") + actual external fun uniffi_aries_askar_checksum_method_askarstore_remove_profile( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstore_rekey") + actual external fun uniffi_aries_askar_checksum_method_askarstore_rekey( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstore_close") + actual external fun uniffi_aries_askar_checksum_method_askarstore_close( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstore_scan") + actual external fun uniffi_aries_askar_checksum_method_askarstore_scan( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstore_session") + actual external fun uniffi_aries_askar_checksum_method_askarstore_session( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_to_public_bytes") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_to_public_bytes( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_to_secret_bytes") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_to_secret_bytes( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_to_key_exchange") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_to_key_exchange( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_algorithm") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_algorithm( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_public") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_public( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_secret") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_secret( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprint") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprint( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprints") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprints( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_convert_key") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_convert_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_aead_params") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_params( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_aead_padding") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_padding( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_aead_random_nonce") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_random_nonce( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_aead_encrypt") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_encrypt( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_aead_decrypt") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_decrypt( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_sign_message") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_sign_message( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_verify_signature") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_verify_signature( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_wrap_key") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_wrap_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarlocalkey_unwrap_key") + actual external fun uniffi_aries_askar_checksum_method_askarlocalkey_unwrap_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext") + actual external fun uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_encryptedbuffer_nonce") + actual external fun uniffi_aries_askar_checksum_method_encryptedbuffer_nonce( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_encryptedbuffer_tag") + actual external fun uniffi_aries_askar_checksum_method_encryptedbuffer_tag( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext_tag") + actual external fun uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext_tag( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_localkeyfactory_generate") + actual external fun uniffi_aries_askar_checksum_method_localkeyfactory_generate( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_localkeyfactory_from_seed") + actual external fun uniffi_aries_askar_checksum_method_localkeyfactory_from_seed( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk_slice") + actual external fun uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk_slice( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk") + actual external fun uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_localkeyfactory_from_public_bytes") + actual external fun uniffi_aries_askar_checksum_method_localkeyfactory_from_public_bytes( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_localkeyfactory_from_secret_bytes") + actual external fun uniffi_aries_askar_checksum_method_localkeyfactory_from_secret_bytes( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstoremanager_generate_raw_store_key") + actual external fun uniffi_aries_askar_checksum_method_askarstoremanager_generate_raw_store_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstoremanager_set_default_logger") + actual external fun uniffi_aries_askar_checksum_method_askarstoremanager_set_default_logger( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstoremanager_provision") + actual external fun uniffi_aries_askar_checksum_method_askarstoremanager_provision( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstoremanager_open") + actual external fun uniffi_aries_askar_checksum_method_askarstoremanager_open( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarstoremanager_remove") + actual external fun uniffi_aries_askar_checksum_method_askarstoremanager_remove( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarcrypto_random_nonce") + actual external fun uniffi_aries_askar_checksum_method_askarcrypto_random_nonce( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarcrypto_crypto_box") + actual external fun uniffi_aries_askar_checksum_method_askarcrypto_crypto_box( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarcrypto_box_open") + actual external fun uniffi_aries_askar_checksum_method_askarcrypto_box_open( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarcrypto_box_seal") + actual external fun uniffi_aries_askar_checksum_method_askarcrypto_box_seal( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarcrypto_box_seal_open") + actual external fun uniffi_aries_askar_checksum_method_askarcrypto_box_seal_open( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdhes_derive_key") + actual external fun uniffi_aries_askar_checksum_method_askarecdhes_derive_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdhes_encrypt_direct") + actual external fun uniffi_aries_askar_checksum_method_askarecdhes_encrypt_direct( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdhes_decrypt_direct") + actual external fun uniffi_aries_askar_checksum_method_askarecdhes_decrypt_direct( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdhes_sender_wrap_key") + actual external fun uniffi_aries_askar_checksum_method_askarecdhes_sender_wrap_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdhes_receiver_unwrap_key") + actual external fun uniffi_aries_askar_checksum_method_askarecdhes_receiver_unwrap_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdh1pu_derive_key") + actual external fun uniffi_aries_askar_checksum_method_askarecdh1pu_derive_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdh1pu_encrypt_direct") + actual external fun uniffi_aries_askar_checksum_method_askarecdh1pu_encrypt_direct( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdh1pu_decrypt_direct") + actual external fun uniffi_aries_askar_checksum_method_askarecdh1pu_decrypt_direct( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdh1pu_sender_wrap_key") + actual external fun uniffi_aries_askar_checksum_method_askarecdh1pu_sender_wrap_key( + ): UShort + + @JvmName("uniffi_aries_askar_checksum_method_askarecdh1pu_receiver_unwrap_key") + actual external fun uniffi_aries_askar_checksum_method_askarecdh1pu_receiver_unwrap_key( + ): UShort + + @JvmName("uniffi__checksum_constructor_localkeyfactory_new") + actual external fun uniffi__checksum_constructor_localkeyfactory_new( + ): UShort + + @JvmName("uniffi__checksum_constructor_askarstoremanager_new") + actual external fun uniffi__checksum_constructor_askarstoremanager_new( + ): UShort + + @JvmName("uniffi__checksum_constructor_askarcrypto_new") + actual external fun uniffi__checksum_constructor_askarcrypto_new( + ): UShort + + @JvmName("uniffi__checksum_constructor_askarecdhes_new") + actual external fun uniffi__checksum_constructor_askarecdhes_new( + ): UShort + + @JvmName("uniffi__checksum_constructor_askarecdh1pu_new") + actual external fun uniffi__checksum_constructor_askarecdh1pu_new( + ): UShort + + @JvmName("uniffi_foreign_executor_callback_set") + actual external fun uniffi_foreign_executor_callback_set(`callback`: UniFfiForeignExecutorCallback, + ): Unit + + @JvmName("ffi_aries_askar_uniffi_contract_version") + actual external fun ffi_aries_askar_uniffi_contract_version( + ): UInt + + +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/UniFfiForeignExecutorCallback.kt b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/UniFfiForeignExecutorCallback.kt new file mode 100644 index 00000000..58fe6cc7 --- /dev/null +++ b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/UniFfiForeignExecutorCallback.kt @@ -0,0 +1,54 @@ +package aries_askar + +import com.sun.jna.Callback +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +// TODO unify this code with the native source set? See the comment in the corresponding file +interface UniFfiRustTaskCallback: Callback { + fun invoke(rustTaskData: Pointer?) +} + +class UniFfiForeignExecutorCallbackImpl( + private val invokeImpl: ( + handle: ULong, + delayMs: Int, + rustTask: UniFfiRustTaskCallback?, + rustTaskData: Pointer? + ) -> Unit +) : Callback { + fun invoke( + handle: ULong, + delayMs: Int, + rustTask: UniFfiRustTaskCallback?, + rustTaskData: Pointer? + ) = invokeImpl(handle, delayMs, rustTask, rustTaskData) +} + +fun createUniFfiForeignExecutorCallbackImpl( + block: (handle: ULong, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) -> Unit +): UniFfiForeignExecutorCallback = UniFfiForeignExecutorCallbackImpl(block) + +actual typealias UniFfiForeignExecutorCallback = UniFfiForeignExecutorCallbackImpl + +fun invokeUniFfiForeignExecutorCallback( + handle: ULong, + delayMs: Int, + rustTask: UniFfiRustTaskCallback?, + rustTaskData: Pointer? +) { + if (rustTask == null) { + FfiConverterForeignExecutor.drop(handle) + } else { + val coroutineScope = FfiConverterForeignExecutor.lift(handle) + coroutineScope.launch { + if (delayMs > 0) { + delay(delayMs.toLong()) + } + rustTask.invoke(rustTaskData) + } + } +} + +actual fun createUniFfiForeignExecutorCallback(): UniFfiForeignExecutorCallback = + createUniFfiForeignExecutorCallbackImpl(::invokeUniFfiForeignExecutorCallback) \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/UniffiHandleMap.kt b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/UniffiHandleMap.kt new file mode 100644 index 00000000..77059ab7 --- /dev/null +++ b/wrappers/kotlin/askarBindings/jvmMain/kotlin/aries_askar/UniffiHandleMap.kt @@ -0,0 +1,29 @@ +package aries_askar + +import java.util.concurrent.ConcurrentHashMap + +actual class UniFfiHandleMap { + private val map = ConcurrentHashMap() + // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible + // values seems like enough. If somehow we generate 4 billion handles, then this will wrap + // around back to zero and we can assume the first handle generated will have been dropped by + // then. + private val counter = java.util.concurrent.atomic.AtomicInteger(0) + + actual val size: Int + get() = map.size + + actual fun insert(obj: T): ULong { + val handle = counter.getAndAdd(1).toULong() + map.put(handle, obj) + return handle + } + + actual fun get(handle: ULong): T? { + return map.get(handle) + } + + actual fun remove(handle: ULong) { + map.remove(handle) + } +} diff --git a/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/AsyncTypes.kt b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/AsyncTypes.kt new file mode 100644 index 00000000..00b1036d --- /dev/null +++ b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/AsyncTypes.kt @@ -0,0 +1,941 @@ +package aries_askar + +// Async return type handlers + +import kotlinx.cinterop.* +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.test.assertNotNull + +// For each result_type, there could be several handlers for different errors and one for the plain type without any error +// In upstream uniffi, UniFfiFutureCallback\{\{ callback_param.canonical_name() \}\} is a super interface that all the +// different handlers with and without error implement. +// This does not work well for Native where we have Pointer types typealiases for CPointer for several T +// We define a single typealias for the handler that expects no error since the pointer type is identical, anyway +typealias UniFfiFutureCallbackUInt8 = CPointer Unit>> +typealias UniFfiFutureCallbackInt8 = CPointer Unit>> +typealias UniFfiFutureCallbackInt32 = CPointer Unit>> +typealias UniFfiFutureCallbackInt64 = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrAskarCrypto = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrAskarEcdh1PU = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrAskarEcdhEs = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrAskarLocalKey = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrAskarScan = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrAskarSession = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrAskarStore = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrAskarStoreManager = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrEncryptedBuffer = CPointer Unit>> +typealias UniFfiFutureCallbackRustArcPtrLocalKeyFactory = CPointer Unit>> +typealias UniFfiFutureCallbackRustBuffer = CPointer Unit>> + + +// Callback handlers for an async call. These are invoked by Rust when the future is ready. They +// lift the return value or error and resume the suspended function. + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerVoid_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerVoid_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerVoid_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerVoid_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerVoid_TypeErrorCode(): UniFfiFutureCallbackHandlerVoid_TypeErrorCode = + staticCFunction { callbackData: Pointer?, _: UByte, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(Unit) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandleri32 = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandleri32Data(continuation: Continuation) + : UniFfiFutureCallbackHandleri32Data { + val resultHandler = createUniFfiFutureCallbackHandleri32() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandleri32Data( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandleri32(): UniFfiFutureCallbackHandleri32 = + staticCFunction { callbackData: Pointer?, returnValue: Int, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterInt.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandleri64_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandleri64_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandleri64_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandleri64_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandleri64_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandleri64_TypeErrorCode(): UniFfiFutureCallbackHandleri64_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: Long, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterLong.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerbool = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerboolData(continuation: Continuation) + : UniFfiFutureCallbackHandlerboolData { + val resultHandler = createUniFfiFutureCallbackHandlerbool() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerboolData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerbool(): UniFfiFutureCallbackHandlerbool = + staticCFunction { callbackData: Pointer?, returnValue: Byte, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterBoolean.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerbool_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerbool_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerbool_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerbool_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerbool_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerbool_TypeErrorCode(): UniFfiFutureCallbackHandlerbool_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: Byte, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterBoolean.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerstring = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerstringData(continuation: Continuation) + : UniFfiFutureCallbackHandlerstringData { + val resultHandler = createUniFfiFutureCallbackHandlerstring() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerstringData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerstring(): UniFfiFutureCallbackHandlerstring = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterString.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerstring_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerstring_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerstring_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerstring_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerstring_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerstring_TypeErrorCode(): UniFfiFutureCallbackHandlerstring_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterString.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAskarCrypto = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAskarCryptoData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarCryptoData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAskarCrypto() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAskarCryptoData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAskarCrypto(): UniFfiFutureCallbackHandlerTypeAskarCrypto = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarCrypto.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAskarEcdh1PU = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAskarEcdh1PUData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarEcdh1PUData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAskarEcdh1PU() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAskarEcdh1PUData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAskarEcdh1PU(): UniFfiFutureCallbackHandlerTypeAskarEcdh1PU = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarEcdh1Pu.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAskarEcdhEs = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAskarEcdhEsData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarEcdhEsData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAskarEcdhEs() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAskarEcdhEsData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAskarEcdhEs(): UniFfiFutureCallbackHandlerTypeAskarEcdhEs = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarEcdhEs.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCode(): UniFfiFutureCallbackHandlerTypeAskarLocalKey_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAskarLocalKey.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCode(): UniFfiFutureCallbackHandlerTypeAskarScan_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAskarScan.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCode(): UniFfiFutureCallbackHandlerTypeAskarSession_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAskarSession.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCode(): UniFfiFutureCallbackHandlerTypeAskarStore_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAskarStore.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAskarStoreManager = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAskarStoreManagerData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarStoreManagerData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAskarStoreManager() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAskarStoreManagerData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAskarStoreManager(): UniFfiFutureCallbackHandlerTypeAskarStoreManager = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarStoreManager.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCode(): UniFfiFutureCallbackHandlerTypeEncryptedBuffer_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeEncryptedBuffer.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeLocalKeyFactory = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeLocalKeyFactoryData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeLocalKeyFactoryData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeLocalKeyFactory() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeLocalKeyFactoryData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeLocalKeyFactory(): UniFfiFutureCallbackHandlerTypeLocalKeyFactory = + staticCFunction { callbackData: Pointer?, returnValue: Pointer?, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeLocalKeyFactory.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCode(): UniFfiFutureCallbackHandlerTypeAeadParams_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterTypeAeadParams.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerTypeAskarKeyAlg = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerTypeAskarKeyAlgData(continuation: Continuation) + : UniFfiFutureCallbackHandlerTypeAskarKeyAlgData { + val resultHandler = createUniFfiFutureCallbackHandlerTypeAskarKeyAlg() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerTypeAskarKeyAlgData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerTypeAskarKeyAlg(): UniFfiFutureCallbackHandlerTypeAskarKeyAlg = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterTypeAskarKeyAlg.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerOptionalstring = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerOptionalstringData(continuation: Continuation) + : UniFfiFutureCallbackHandlerOptionalstringData { + val resultHandler = createUniFfiFutureCallbackHandlerOptionalstring() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerOptionalstringData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerOptionalstring(): UniFfiFutureCallbackHandlerOptionalstring = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterOptionalString.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCode(): UniFfiFutureCallbackHandlerOptionalTypeAskarEntry_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterOptionalTypeAskarEntry.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData(continuation: Continuation) + : UniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCode(): UniFfiFutureCallbackHandlerOptionalTypeAskarKeyEntry_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterOptionalTypeAskarKeyEntry.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData(continuation: Continuation?>) + : UniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCode(): UniFfiFutureCallbackHandlerOptionalSequenceTypeAskarEntry_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef?>>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterOptionalSequenceTypeAskarEntry.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerSequenceu8 = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerSequenceu8Data(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceu8Data { + val resultHandler = createUniFfiFutureCallbackHandlerSequenceu8() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerSequenceu8Data( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerSequenceu8(): UniFfiFutureCallbackHandlerSequenceu8 = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterSequenceUByte.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerSequenceu8_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerSequenceu8_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceu8_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerSequenceu8_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerSequenceu8_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerSequenceu8_TypeErrorCode(): UniFfiFutureCallbackHandlerSequenceu8_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterSequenceUByte.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerSequencestring_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerSequencestring_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequencestring_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerSequencestring_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerSequencestring_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerSequencestring_TypeErrorCode(): UniFfiFutureCallbackHandlerSequencestring_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterSequenceString.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCode(): UniFfiFutureCallbackHandlerSequenceTypeAskarEntry_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterSequenceTypeAskarEntry.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCode = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData { + val resultHandler = createUniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCode() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCodeData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCode(): UniFfiFutureCallbackHandlerSequenceTypeAskarKeyEntry_TypeErrorCode = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(ErrorCode, callStatus) + continuation.resume(FfiConverterSequenceTypeAskarKeyEntry.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiFutureCallbackHandlerMapStringString = CPointer Unit>> + +actual fun createUniFfiFutureCallbackHandlerMapStringStringData(continuation: Continuation>) + : UniFfiFutureCallbackHandlerMapStringStringData { + val resultHandler = createUniFfiFutureCallbackHandlerMapStringString() + // see https://kotlinlang.org/docs/native-c-interop.html#pass-user-data-to-callbacks + val stableRef = StableRef.create(continuation) + return UniFfiFutureCallbackHandlerMapStringStringData( + resultHandler, + DropHandle(stableRef), + stableRef.asCPointer() + ) +} + +private fun createUniFfiFutureCallbackHandlerMapStringString(): UniFfiFutureCallbackHandlerMapStringString = + staticCFunction { callbackData: Pointer?, returnValue: RustBuffer, callStatus: RustCallStatusByValue -> + assertNotNull(callbackData, "received no continuation to resume...") + // the StableRef is disposed in the scope that held onto the DropHandler wrapper + val stableRef = callbackData.asStableRef>>() + val continuation = stableRef.get() + try { + checkCallStatusByValue(NullCallStatusErrorHandler, callStatus) + continuation.resume(FfiConverterMapStringString.lift(requireNotNull(returnValue))) + } + catch (e: Throwable) { + continuation.resumeWithException(e) + } + } + +actual class DropHandle(private val stableRef: StableRef>) { + actual fun dropIt() { + stableRef.dispose() + } +} + +private inline fun aries_askar.cinterop.RustCallStatus.isSuccess(): Boolean = code == 0.toByte() +private inline fun aries_askar.cinterop.RustCallStatus.isError(): Boolean = code == 1.toByte() +private inline fun aries_askar.cinterop.RustCallStatus.isPanic(): Boolean = code == 2.toByte() + +// TODO remove this crutch that is needed because on Native, RustCallStatusByValue does not extend RustCallStatus +// and "just getting a pointer to the value" apparently requires copying to a special place and *then* getting +// a pointer to that location +private fun checkCallStatusByValue(errorHandler: CallStatusErrorHandler, statusByValue: RustCallStatusByValue) { + // no need to clean up the call status since the caller received it as a c parameter by value + statusByValue.useContents { + if (this.isSuccess()) { + return + } else if (isError()) { + throw errorHandler.lift(errorBuf.readValue()) + } else if (isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + val errorBuffer = errorBuf.readValue() + if (errorBuffer.dataSize > 0) { + // TODO avoid additional copy + throw InternalException(FfiConverterString.lift(errorBuffer)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $statusByValue.code") + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/ForeignBytes.kt b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/ForeignBytes.kt new file mode 100644 index 00000000..c0176ca5 --- /dev/null +++ b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/ForeignBytes.kt @@ -0,0 +1,7 @@ +package aries_askar + +import kotlinx.cinterop.CValue + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias ForeignBytes = CValue \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/ForeignCallback.kt b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/ForeignCallback.kt new file mode 100644 index 00000000..c2fc9f3f --- /dev/null +++ b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/ForeignCallback.kt @@ -0,0 +1,10 @@ +package aries_askar + +import kotlinx.cinterop.CFunction +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.CValue + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") + +actual typealias ForeignCallback = CPointer Int>> diff --git a/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/Pointer.kt b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/Pointer.kt new file mode 100644 index 00000000..aa17b05a --- /dev/null +++ b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/Pointer.kt @@ -0,0 +1,100 @@ +package aries_askar + +import kotlinx.cinterop.* + +import okio.Buffer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_USE_SITE_VARIANCE") +actual typealias Pointer = CPointer + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_USE_SITE_VARIANCE", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UBytePointer = CPointer + +actual fun Long.toPointer(): Pointer = requireNotNull(this.toCPointer()) + +actual fun Pointer.toLong(): Long = this.rawValue.toLong() + +@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +internal inline infix fun Byte.and(other: Long): Long = toLong() and other + +@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +internal inline infix fun Byte.and(other: Int): Int = toInt() and other + +// byte twiddling was basically pasted from okio +actual fun UBytePointer.asSource(len: Long): NoCopySource = object : NoCopySource { + var readBytes: Int = 0 + var remaining: Long = len + + init { + if (len < 0) { + throw IllegalStateException("Trying to create NoCopySource with negative length") + } + } + + private fun requireLen(requiredLen: Long) { + if (remaining < requiredLen) { + throw IllegalStateException("Expected at least ${requiredLen} bytes in source but have only ${len}") + } + remaining -= requiredLen + } + + override fun exhausted(): Boolean = remaining == 0L + + override fun readByte(): Byte { + requireLen(1) + return reinterpret()[readBytes++] + } + + override fun readShort(): Short { + requireLen(2) + val data = reinterpret() + val s = data[readBytes++] and 0xff shl 8 or (data[readBytes++] and 0xff) + return s.toShort() + } + + override fun readInt(): Int { + requireLen(4) + val data = reinterpret() + val i = ( + data[readBytes++] and 0xff shl 24 + or (data[readBytes++] and 0xff shl 16) + or (data[readBytes++] and 0xff shl 8) + or (data[readBytes++] and 0xff) + ) + return i + } + + override fun readLong(): Long { + requireLen(8) + val data = reinterpret() + val v = ( + data[readBytes++] and 0xffL shl 56 + or (data[readBytes++] and 0xffL shl 48) + or (data[readBytes++] and 0xffL shl 40) + or (data[readBytes++] and 0xffL shl 32) + or (data[readBytes++] and 0xffL shl 24) + or (data[readBytes++] and 0xffL shl 16) + or (data[readBytes++] and 0xffL shl 8) // ktlint-disable no-multi-spaces + or (data[readBytes++] and 0xffL) + ) + return v + } + + override fun readByteArray(): ByteArray = readByteArray(len) + + override fun readByteArray(len: Long): ByteArray { + requireLen(len) + + val cast = reinterpret() + val intLen = len.toInt() + val byteArray = ByteArray(intLen) + + for (writeIdx in 0 until intLen) { + byteArray[writeIdx] = cast[readBytes++] + } + + return byteArray + } + +} diff --git a/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/RustBuffer.kt b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/RustBuffer.kt new file mode 100644 index 00000000..70212aef --- /dev/null +++ b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/RustBuffer.kt @@ -0,0 +1,48 @@ +package aries_askar + +import kotlinx.cinterop.* +import okio.Buffer + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias RustBuffer = CValue +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias RustBufferPointer = CPointer + +actual fun RustBuffer.asSource(): NoCopySource { + val data = useContents { data } + val len = useContents { len } + return requireNotNull(data).asSource(len.toLong()) +} + +actual val RustBuffer.dataSize: Int + get() = useContents { len } + +actual fun RustBuffer.free(): Unit = + rustCall { status -> + UniFFILib.ffi_aries_askar_rustbuffer_free(this, status) + } + +actual fun allocRustBuffer(buffer: Buffer): RustBuffer = + rustCall { status -> + val size = buffer.size + UniFFILib.ffi_aries_askar_rustbuffer_alloc(size.toInt(), status).also { + it.useContents { + val notNullData = data + checkNotNull(notNullData) { "RustBuffer.alloc() returned null data pointer (size=${size})" } + buffer.readByteArray().forEachIndexed { index, byte -> + notNullData[index] = byte.toUByte() + } + } + } + } + +actual fun RustBufferPointer.setValue(value: RustBuffer) { + this.pointed.capacity = value.useContents { capacity } + this.pointed.len = value.useContents { len } + this.pointed.data = value.useContents { data } +} + +actual fun emptyRustBuffer(): RustBuffer { + return allocRustBuffer(Buffer()) +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/RustCallStatus.kt b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/RustCallStatus.kt new file mode 100644 index 00000000..fa8bdb99 --- /dev/null +++ b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/RustCallStatus.kt @@ -0,0 +1,24 @@ +package aries_askar + +import kotlinx.cinterop.* + +// TODO remove suppress when https://youtrack.jetbrains.com/issue/KT-29819/New-rules-for-expect-actual-declarations-in-MPP is solved +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias RustCallStatus = CPointer + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias RustCallStatusByValue = CValue + +actual val RustCallStatus.statusCode: Byte + get() = pointed.code +actual val RustCallStatus.errorBuffer: RustBuffer + get() = pointed.errorBuf.readValue() + +actual fun withRustCallStatus(block: (RustCallStatus) -> T): T = + memScoped { + val allocated = alloc().ptr + block(allocated) + } + +val RustCallStatusByValue.statusCode: Byte + get() = useContents { code } \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/UniFFILib.kt b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/UniFFILib.kt new file mode 100644 index 00000000..4d79e8a3 --- /dev/null +++ b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/UniFFILib.kt @@ -0,0 +1,932 @@ +package aries_askar + + + +actual object UniFFILib { + init { + FfiConverterForeignExecutor.register(this) + + } + + actual fun uniffi_aries_askar_fn_free_askarentry(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarentry(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarentry_category(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarentry_category(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarentry_name(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarentry_name(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarentry_tags(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarentry_tags(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarentry_value(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarentry_value(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_askarkeyentry(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarkeyentry(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarkeyentry_algorithm(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarkeyentry_algorithm(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarkeyentry_metadata(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarkeyentry_metadata(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarkeyentry_name(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarkeyentry_name(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarkeyentry_is_local(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Byte = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarkeyentry_is_local(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarkeyentry_tags(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarkeyentry_tags(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarkeyentry_load_local_key(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarkeyentry_load_local_key(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_askarscan(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarscan(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarscan_next(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarscan_next(`ptr`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarscan_fetch_all(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarscan_fetch_all(`ptr`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_askarsession(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarsession(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_close(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_close(`ptr`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_count(`ptr`: Pointer,`category`: RustBuffer,`tagFilter`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackInt64,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_count(`ptr`,`category`,`tagFilter`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_fetch(`ptr`: Pointer,`category`: RustBuffer,`name`: RustBuffer,`forUpdate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_fetch(`ptr`,`category`,`name`,`forUpdate`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_fetch_all(`ptr`: Pointer,`category`: RustBuffer,`tagFilter`: RustBuffer,`limit`: RustBuffer,`forUpdate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_fetch_all(`ptr`,`category`,`tagFilter`,`limit`,`forUpdate`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_update(`ptr`: Pointer,`operation`: RustBuffer,`category`: RustBuffer,`name`: RustBuffer,`value`: RustBuffer,`tags`: RustBuffer,`expiryMs`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_update(`ptr`,`operation`,`category`,`name`,`value`,`tags`,`expiryMs`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_remove_all(`ptr`: Pointer,`category`: RustBuffer,`tagFilter`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackInt64,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_remove_all(`ptr`,`category`,`tagFilter`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_insert_key(`ptr`: Pointer,`name`: RustBuffer,`key`: Pointer,`metadata`: RustBuffer,`tags`: RustBuffer,`expiryMs`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_insert_key(`ptr`,`name`,`key`,`metadata`,`tags`,`expiryMs`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_fetch_key(`ptr`: Pointer,`name`: RustBuffer,`forUpdate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_fetch_key(`ptr`,`name`,`forUpdate`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_fetch_all_keys(`ptr`: Pointer,`algorithm`: RustBuffer,`thumbprint`: RustBuffer,`tagFilter`: RustBuffer,`limit`: RustBuffer,`forUpdate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_fetch_all_keys(`ptr`,`algorithm`,`thumbprint`,`tagFilter`,`limit`,`forUpdate`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_remove_key(`ptr`: Pointer,`name`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_remove_key(`ptr`,`name`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarsession_update_key(`ptr`: Pointer,`name`: RustBuffer,`metadata`: RustBuffer,`tags`: RustBuffer,`expiryMs`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarsession_update_key(`ptr`,`name`,`metadata`,`tags`,`expiryMs`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_askarstore(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarstore(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstore_get_profile_name(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstore_get_profile_name(`ptr`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstore_create_profile(`ptr`: Pointer,`profile`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustBuffer,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstore_create_profile(`ptr`,`profile`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstore_remove_profile(`ptr`: Pointer,`profile`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstore_remove_profile(`ptr`,`profile`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstore_rekey(`ptr`: Pointer,`keyMethod`: RustBuffer,`passKey`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstore_rekey(`ptr`,`keyMethod`,`passKey`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstore_close(`ptr`: Pointer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackUInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstore_close(`ptr`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstore_scan(`ptr`: Pointer,`profile`: RustBuffer,`categogy`: RustBuffer,`tagFilter`: RustBuffer,`offset`: RustBuffer,`limit`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarScan,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstore_scan(`ptr`,`profile`,`categogy`,`tagFilter`,`offset`,`limit`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstore_session(`ptr`: Pointer,`profile`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarSession,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstore_session(`ptr`,`profile`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_askarlocalkey(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarlocalkey(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_to_public_bytes(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_to_public_bytes(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_to_secret_bytes(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_to_secret_bytes(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_to_key_exchange(`ptr`: Pointer,`alg`: RustBuffer,`pk`: Pointer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_to_key_exchange(`ptr`,`alg`,`pk`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_algorithm(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_algorithm(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_public(`ptr`: Pointer,`alg`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_public(`ptr`,`alg`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_secret(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_secret(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprint(`ptr`: Pointer,`alg`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprint(`ptr`,`alg`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprints(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprints(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_convert_key(`ptr`: Pointer,`alg`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_convert_key(`ptr`,`alg`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_aead_params(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_aead_params(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_aead_padding(`ptr`: Pointer,`msgLen`: Int,_uniffi_out_err: RustCallStatus, + ): Int = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_aead_padding(`ptr`,`msgLen`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_aead_random_nonce(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_aead_random_nonce(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_aead_encrypt(`ptr`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_aead_encrypt(`ptr`,`message`,`nonce`,`aad`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_aead_decrypt(`ptr`: Pointer,`ciphertext`: RustBuffer,`tag`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_aead_decrypt(`ptr`,`ciphertext`,`tag`,`nonce`,`aad`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_sign_message(`ptr`: Pointer,`message`: RustBuffer,`sigType`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_sign_message(`ptr`,`message`,`sigType`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_verify_signature(`ptr`: Pointer,`message`: RustBuffer,`signature`: RustBuffer,`sigType`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Byte = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_verify_signature(`ptr`,`message`,`signature`,`sigType`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_wrap_key(`ptr`: Pointer,`key`: Pointer,`nonce`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_wrap_key(`ptr`,`key`,`nonce`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarlocalkey_unwrap_key(`ptr`: Pointer,`alg`: RustBuffer,`ciphertext`: RustBuffer,`tag`: RustBuffer,`nonce`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarlocalkey_unwrap_key(`ptr`,`alg`,`ciphertext`,`tag`,`nonce`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_encryptedbuffer(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_encryptedbuffer(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_encryptedbuffer_nonce(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_encryptedbuffer_nonce(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_encryptedbuffer_tag(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_encryptedbuffer_tag(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext_tag(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext_tag(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_localkeyfactory(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_localkeyfactory(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_constructor_localkeyfactory_new(_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_constructor_localkeyfactory_new(_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_localkeyfactory_generate(`ptr`: Pointer,`alg`: RustBuffer,`ephemeral`: Byte,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_localkeyfactory_generate(`ptr`,`alg`,`ephemeral`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_localkeyfactory_from_seed(`ptr`: Pointer,`alg`: RustBuffer,`seed`: RustBuffer,`method`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_localkeyfactory_from_seed(`ptr`,`alg`,`seed`,`method`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_localkeyfactory_from_jwk_slice(`ptr`: Pointer,`jwk`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_localkeyfactory_from_jwk_slice(`ptr`,`jwk`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_localkeyfactory_from_jwk(`ptr`: Pointer,`jwk`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_localkeyfactory_from_jwk(`ptr`,`jwk`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_localkeyfactory_from_public_bytes(`ptr`: Pointer,`alg`: RustBuffer,`bytes`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_localkeyfactory_from_public_bytes(`ptr`,`alg`,`bytes`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_localkeyfactory_from_secret_bytes(`ptr`: Pointer,`alg`: RustBuffer,`bytes`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_localkeyfactory_from_secret_bytes(`ptr`,`alg`,`bytes`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_askarstoremanager(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarstoremanager(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_constructor_askarstoremanager_new(_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_constructor_askarstoremanager_new(_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstoremanager_generate_raw_store_key(`ptr`: Pointer,`seed`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstoremanager_generate_raw_store_key(`ptr`,`seed`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstoremanager_set_default_logger(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstoremanager_set_default_logger(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstoremanager_provision(`ptr`: Pointer,`specUri`: RustBuffer,`keyMethod`: RustBuffer,`passKey`: RustBuffer,`profile`: RustBuffer,`recreate`: Byte,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarStore,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstoremanager_provision(`ptr`,`specUri`,`keyMethod`,`passKey`,`profile`,`recreate`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstoremanager_open(`ptr`: Pointer,`specUri`: RustBuffer,`keyMethod`: RustBuffer,`passKey`: RustBuffer,`profile`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackRustArcPtrAskarStore,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstoremanager_open(`ptr`,`specUri`,`keyMethod`,`passKey`,`profile`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarstoremanager_remove(`ptr`: Pointer,`specUri`: RustBuffer,`uniffiExecutor`: ULong,`uniffiCallback`: UniFfiFutureCallbackInt8,`uniffiCallbackData`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarstoremanager_remove(`ptr`,`specUri`,`uniffiExecutor`,`uniffiCallback`,`uniffiCallbackData`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_askarcrypto(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarcrypto(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_constructor_askarcrypto_new(_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_constructor_askarcrypto_new(_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarcrypto_random_nonce(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarcrypto_random_nonce(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarcrypto_crypto_box(`ptr`: Pointer,`receiverKey`: Pointer,`senderKey`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarcrypto_crypto_box(`ptr`,`receiverKey`,`senderKey`,`message`,`nonce`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarcrypto_box_open(`ptr`: Pointer,`receiverKey`: Pointer,`senderKey`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarcrypto_box_open(`ptr`,`receiverKey`,`senderKey`,`message`,`nonce`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarcrypto_box_seal(`ptr`: Pointer,`receiverKey`: Pointer,`message`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarcrypto_box_seal(`ptr`,`receiverKey`,`message`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarcrypto_box_seal_open(`ptr`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarcrypto_box_seal_open(`ptr`,`receiverKey`,`ciphertext`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_askarecdhes(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarecdhes(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_constructor_askarecdhes_new(`algId`: RustBuffer,`apu`: RustBuffer,`apv`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_constructor_askarecdhes_new(`algId`,`apu`,`apv`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdhes_derive_key(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`receive`: Byte,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdhes_derive_key(`ptr`,`encAlg`,`ephemeralKey`,`receiverKey`,`receive`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdhes_encrypt_direct(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdhes_encrypt_direct(`ptr`,`encAlg`,`ephemeralKey`,`receiverKey`,`message`,`nonce`,`aad`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdhes_decrypt_direct(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,`tag`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdhes_decrypt_direct(`ptr`,`encAlg`,`ephemeralKey`,`receiverKey`,`ciphertext`,`tag`,`nonce`,`aad`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdhes_sender_wrap_key(`ptr`: Pointer,`wrapAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`cek`: Pointer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdhes_sender_wrap_key(`ptr`,`wrapAlg`,`ephemeralKey`,`receiverKey`,`cek`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdhes_receiver_unwrap_key(`ptr`: Pointer,`wrapAlg`: RustBuffer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,`nonce`: RustBuffer,`tag`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdhes_receiver_unwrap_key(`ptr`,`wrapAlg`,`encAlg`,`ephemeralKey`,`receiverKey`,`ciphertext`,`nonce`,`tag`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_free_askarecdh1pu(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_free_askarecdh1pu(`ptr`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_constructor_askarecdh1pu_new(`algId`: RustBuffer,`apu`: RustBuffer,`apv`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_constructor_askarecdh1pu_new(`algId`,`apu`,`apv`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdh1pu_derive_key(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`ccTag`: RustBuffer,`receive`: Byte,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdh1pu_derive_key(`ptr`,`encAlg`,`ephemeralKey`,`senderKey`,`receiverKey`,`ccTag`,`receive`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdh1pu_encrypt_direct(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`message`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdh1pu_encrypt_direct(`ptr`,`encAlg`,`ephemeralKey`,`senderKey`,`receiverKey`,`message`,`nonce`,`aad`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdh1pu_decrypt_direct(`ptr`: Pointer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,`tag`: RustBuffer,`nonce`: RustBuffer,`aad`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdh1pu_decrypt_direct(`ptr`,`encAlg`,`ephemeralKey`,`senderKey`,`receiverKey`,`ciphertext`,`tag`,`nonce`,`aad`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdh1pu_sender_wrap_key(`ptr`: Pointer,`wrapAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`cek`: Pointer,`ccTag`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdh1pu_sender_wrap_key(`ptr`,`wrapAlg`,`ephemeralKey`,`senderKey`,`receiverKey`,`cek`,`ccTag`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_fn_method_askarecdh1pu_receiver_unwrap_key(`ptr`: Pointer,`wrapAlg`: RustBuffer,`encAlg`: RustBuffer,`ephemeralKey`: Pointer,`senderKey`: Pointer,`receiverKey`: Pointer,`ciphertext`: RustBuffer,`ccTag`: RustBuffer,`nonce`: RustBuffer,`tag`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Pointer = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_fn_method_askarecdh1pu_receiver_unwrap_key(`ptr`,`wrapAlg`,`encAlg`,`ephemeralKey`,`senderKey`,`receiverKey`,`ciphertext`,`ccTag`,`nonce`,`tag`,_uniffi_out_err + )) + + actual fun ffi_aries_askar_rustbuffer_alloc(`size`: Int,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.ffi_aries_askar_rustbuffer_alloc(`size`,_uniffi_out_err + )) + + actual fun ffi_aries_askar_rustbuffer_from_bytes(`bytes`: ForeignBytes,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.ffi_aries_askar_rustbuffer_from_bytes(`bytes`,_uniffi_out_err + )) + + actual fun ffi_aries_askar_rustbuffer_free(`buf`: RustBuffer,_uniffi_out_err: RustCallStatus, + ): Unit = + requireNotNull(aries_askar.cinterop.ffi_aries_askar_rustbuffer_free(`buf`,_uniffi_out_err + )) + + actual fun ffi_aries_askar_rustbuffer_reserve(`buf`: RustBuffer,`additional`: Int,_uniffi_out_err: RustCallStatus, + ): RustBuffer = + requireNotNull(aries_askar.cinterop.ffi_aries_askar_rustbuffer_reserve(`buf`,`additional`,_uniffi_out_err + )) + + actual fun uniffi_aries_askar_checksum_method_askarentry_category( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarentry_category( + )) + + actual fun uniffi_aries_askar_checksum_method_askarentry_name( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarentry_name( + )) + + actual fun uniffi_aries_askar_checksum_method_askarentry_tags( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarentry_tags( + )) + + actual fun uniffi_aries_askar_checksum_method_askarentry_value( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarentry_value( + )) + + actual fun uniffi_aries_askar_checksum_method_askarkeyentry_algorithm( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarkeyentry_algorithm( + )) + + actual fun uniffi_aries_askar_checksum_method_askarkeyentry_metadata( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarkeyentry_metadata( + )) + + actual fun uniffi_aries_askar_checksum_method_askarkeyentry_name( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarkeyentry_name( + )) + + actual fun uniffi_aries_askar_checksum_method_askarkeyentry_is_local( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarkeyentry_is_local( + )) + + actual fun uniffi_aries_askar_checksum_method_askarkeyentry_tags( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarkeyentry_tags( + )) + + actual fun uniffi_aries_askar_checksum_method_askarkeyentry_load_local_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarkeyentry_load_local_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarscan_next( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarscan_next( + )) + + actual fun uniffi_aries_askar_checksum_method_askarscan_fetch_all( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarscan_fetch_all( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_close( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_close( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_count( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_count( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_fetch( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_fetch( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_fetch_all( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_fetch_all( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_update( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_update( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_remove_all( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_remove_all( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_insert_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_insert_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_fetch_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_fetch_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_fetch_all_keys( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_fetch_all_keys( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_remove_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_remove_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarsession_update_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarsession_update_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstore_get_profile_name( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstore_get_profile_name( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstore_create_profile( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstore_create_profile( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstore_remove_profile( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstore_remove_profile( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstore_rekey( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstore_rekey( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstore_close( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstore_close( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstore_scan( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstore_scan( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstore_session( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstore_session( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_to_public_bytes( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_to_public_bytes( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_to_secret_bytes( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_to_secret_bytes( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_to_key_exchange( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_to_key_exchange( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_algorithm( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_algorithm( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_public( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_public( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_secret( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_secret( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprint( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprint( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprints( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprints( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_convert_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_convert_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_params( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_aead_params( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_padding( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_aead_padding( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_random_nonce( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_aead_random_nonce( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_encrypt( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_aead_encrypt( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_aead_decrypt( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_aead_decrypt( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_sign_message( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_sign_message( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_verify_signature( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_verify_signature( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_wrap_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_wrap_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarlocalkey_unwrap_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarlocalkey_unwrap_key( + )) + + actual fun uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext( + )) + + actual fun uniffi_aries_askar_checksum_method_encryptedbuffer_nonce( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_encryptedbuffer_nonce( + )) + + actual fun uniffi_aries_askar_checksum_method_encryptedbuffer_tag( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_encryptedbuffer_tag( + )) + + actual fun uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext_tag( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext_tag( + )) + + actual fun uniffi_aries_askar_checksum_method_localkeyfactory_generate( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_localkeyfactory_generate( + )) + + actual fun uniffi_aries_askar_checksum_method_localkeyfactory_from_seed( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_localkeyfactory_from_seed( + )) + + actual fun uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk_slice( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk_slice( + )) + + actual fun uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk( + )) + + actual fun uniffi_aries_askar_checksum_method_localkeyfactory_from_public_bytes( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_localkeyfactory_from_public_bytes( + )) + + actual fun uniffi_aries_askar_checksum_method_localkeyfactory_from_secret_bytes( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_localkeyfactory_from_secret_bytes( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstoremanager_generate_raw_store_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstoremanager_generate_raw_store_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstoremanager_set_default_logger( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstoremanager_set_default_logger( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstoremanager_provision( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstoremanager_provision( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstoremanager_open( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstoremanager_open( + )) + + actual fun uniffi_aries_askar_checksum_method_askarstoremanager_remove( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarstoremanager_remove( + )) + + actual fun uniffi_aries_askar_checksum_method_askarcrypto_random_nonce( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarcrypto_random_nonce( + )) + + actual fun uniffi_aries_askar_checksum_method_askarcrypto_crypto_box( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarcrypto_crypto_box( + )) + + actual fun uniffi_aries_askar_checksum_method_askarcrypto_box_open( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarcrypto_box_open( + )) + + actual fun uniffi_aries_askar_checksum_method_askarcrypto_box_seal( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarcrypto_box_seal( + )) + + actual fun uniffi_aries_askar_checksum_method_askarcrypto_box_seal_open( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarcrypto_box_seal_open( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdhes_derive_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdhes_derive_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdhes_encrypt_direct( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdhes_encrypt_direct( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdhes_decrypt_direct( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdhes_decrypt_direct( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdhes_sender_wrap_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdhes_sender_wrap_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdhes_receiver_unwrap_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdhes_receiver_unwrap_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdh1pu_derive_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdh1pu_derive_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdh1pu_encrypt_direct( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdh1pu_encrypt_direct( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdh1pu_decrypt_direct( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdh1pu_decrypt_direct( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdh1pu_sender_wrap_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdh1pu_sender_wrap_key( + )) + + actual fun uniffi_aries_askar_checksum_method_askarecdh1pu_receiver_unwrap_key( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi_aries_askar_checksum_method_askarecdh1pu_receiver_unwrap_key( + )) + + actual fun uniffi__checksum_constructor_localkeyfactory_new( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi__checksum_constructor_localkeyfactory_new( + )) + + actual fun uniffi__checksum_constructor_askarstoremanager_new( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi__checksum_constructor_askarstoremanager_new( + )) + + actual fun uniffi__checksum_constructor_askarcrypto_new( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi__checksum_constructor_askarcrypto_new( + )) + + actual fun uniffi__checksum_constructor_askarecdhes_new( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi__checksum_constructor_askarecdhes_new( + )) + + actual fun uniffi__checksum_constructor_askarecdh1pu_new( + ): UShort = + requireNotNull(aries_askar.cinterop.uniffi__checksum_constructor_askarecdh1pu_new( + )) + + actual fun uniffi_foreign_executor_callback_set(`callback`: UniFfiForeignExecutorCallback, + ): Unit = + requireNotNull(aries_askar.cinterop.uniffi_foreign_executor_callback_set(`callback`, + )) + + actual fun ffi_aries_askar_uniffi_contract_version( + ): UInt = + requireNotNull(aries_askar.cinterop.ffi_aries_askar_uniffi_contract_version( + )) + + +} \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/UniFfiForeignExecutorCallback.kt b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/UniFfiForeignExecutorCallback.kt new file mode 100644 index 00000000..25a8c46f --- /dev/null +++ b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/UniFfiForeignExecutorCallback.kt @@ -0,0 +1,42 @@ +package aries_askar + +import kotlinx.cinterop.CFunction +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.invoke +import kotlinx.cinterop.staticCFunction +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +typealias UniFfiRustTaskCallback = CPointer Unit>> + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION") +actual typealias UniFfiForeignExecutorCallback = CPointer Unit>> + +// TODO find a way to unify this with the JVM source? The issue is that with JNA, UniFfiRustTaskCallback is (must be?) +// an interface implementing jna.Callback. On Native, it is (must be?) a CPointer which is a class, not an interface +// `expect typealias UniFfiRustTaskCallback` could be a solution but that is currently not allowed +fun invokeUniFfiForeignExecutorCallback( + handle: ULong, + delayMs: Int, + rustTask: UniFfiRustTaskCallback?, + rustTaskData: Pointer? +) { + if (rustTask == null) { + FfiConverterForeignExecutor.drop(handle) + } else { + val coroutineScope = FfiConverterForeignExecutor.lift(handle) + coroutineScope.launch { + if (delayMs > 0) { + delay(delayMs.toLong()) + } + rustTask.invoke(rustTaskData) + } + } +} + +actual fun createUniFfiForeignExecutorCallback(): UniFfiForeignExecutorCallback = + staticCFunction { handle, delayMs, rustTask, rustTaskData -> + // FIXME the rusty delayMs is an u32, so Int was probalby the wrong type all along? + // fix the java types so no negative values are created here + invokeUniFfiForeignExecutorCallback(handle, delayMs.toInt(), rustTask, rustTaskData) + } \ No newline at end of file diff --git a/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/UniFfiHandleMap.kt b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/UniFfiHandleMap.kt new file mode 100644 index 00000000..cbdee014 --- /dev/null +++ b/wrappers/kotlin/askarBindings/nativeMain/kotlin/aries_askar/UniFfiHandleMap.kt @@ -0,0 +1,43 @@ +package aries_askar + +import kotlinx.atomicfu.getAndUpdate + +// This is actually common kotlin but inefficient because of the coarse granular locking... +// TODO either create some real implementation or at least measure if protecting the counter +// with the lock and using a plain Int wouldn't be faster +actual class UniFfiHandleMap { + private val mapLock = kotlinx.atomicfu.locks.ReentrantLock() + private val map = HashMap() + + // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible + // values seems like enough. If somehow we generate 4 billion handles, then this will wrap + // around back to zero and we can assume the first handle generated will have been dropped by + // then. + private val counter = kotlinx.atomicfu.atomic(0) + + actual val size: Int + get() = map.size + + actual fun insert(obj: T): ULong { + val handle = counter.getAndUpdate { it + 1 }.toULong() + synchronizedMapAccess { map.put(handle, obj) } + return handle + } + + actual fun get(handle: ULong): T? { + return synchronizedMapAccess { map.get(handle) } + } + + actual fun remove(handle: ULong) { + synchronizedMapAccess { map.remove(handle) } + } + + fun synchronizedMapAccess(block: () -> T): T { + mapLock.lock() + try { + return block() + } finally { + mapLock.unlock() + } + } +} diff --git a/wrappers/kotlin/build-targets.sh b/wrappers/kotlin/build-targets.sh new file mode 100644 index 00000000..7e715431 --- /dev/null +++ b/wrappers/kotlin/build-targets.sh @@ -0,0 +1,24 @@ +# BUILD IOS TARGETS +rustup toolchain install 1.65.0 --target aarch64-apple-ios +cargo build --release --features uffi --target aarch64-apple-ios +rustup toolchain install 1.65.0 --target aarch64-apple-ios-sim +cargo build --release --features uffi --target aarch64-apple-ios-sim +rustup toolchain install 1.65.0 --target x86_64-apple-ios +cargo build --release --features uffi --target x86_64-apple-ios + +# BUILD ANDROID TARGETS + +#cargo install --bins --git https://github.com/rust-embedded/cross --tag v0.2.4 cross +cargo install cross --git https://github.com/cross-rs/cross + +rustup toolchain install 1.65.0 --target aarch64-linux-android +cross build --release --features uffi --target aarch64-linux-android +rustup toolchain install 1.65.0 --target armv7-linux-androideabi +cross build --release --features uffi --target armv7-linux-androideabi +rustup toolchain install 1.65.0 --target i686-linux-android +cross build --release --features uffi --target i686-linux-android +rustup toolchain install 1.65.0 --target x86_64-linux-android +cross build --release --features uffi --target x86_64-linux-android + +# BUILD MAC OS TARGETS +../../build-universal.sh \ No newline at end of file diff --git a/wrappers/kotlin/build.gradle.kts b/wrappers/kotlin/build.gradle.kts new file mode 100644 index 00000000..582914c7 --- /dev/null +++ b/wrappers/kotlin/build.gradle.kts @@ -0,0 +1,250 @@ +import com.google.wireless.android.sdk.stats.GradleBuildVariant.KotlinOptions +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask +import java.util.* + +buildscript{ + repositories{ + mavenCentral() + } + dependencies{ + classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.22.0") + } + +} +apply(plugin = "kotlinx-atomicfu") + +plugins { + kotlin("multiplatform") version "1.8.21" + kotlin("plugin.serialization") version "1.8.21" + id("maven-publish") + id("com.android.library") version "7.4.0" +} + +val askarBindings = file("askarBindings") +val binaries = file("../../target") + + +val processBinaries = tasks.register("processBinaries", Copy::class) { + val directory = buildDir + .resolve("processedResources") + .resolve("jvm") + .resolve("main") + + from(binaries.resolve("darwin-universal").resolve("release")) + include("*.dylib") + include("*.so") + into(directory) +} + +tasks.withType{ + dependsOn(processBinaries) +} + +// Stub secrets to let the project sync and build without the publication values set up +ext["githubUsername"] = null +ext["githubToken"] = null +ext["askarVersion"] = "0.2.9-dev.3" +ext["wrapperVersion"] = "3.4" + +val secretPropsFile = project.rootProject.file("local.properties") +if(secretPropsFile.exists()) { + secretPropsFile.reader().use { + Properties().apply { + load(it) + } + }.onEach{ (name, value) -> + ext[name.toString()] = value + } +} else { + ext["githubUsername"] = System.getenv("GITHUB_USERNAME") + ext["githubToken"] = System.getenv("GITHUB_TOKEN") +} + +fun getExtraString(name: String) = ext[name]?.toString() + +group = "org.hyperledger.aries-askar" +version = "${getExtraString("askarVersion")}-wrapper.${getExtraString("wrapperVersion")}" +dependencies { + testImplementation("androidx.test:monitor:1.6.1") + testImplementation("junit:junit:4.13.2") + testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") + testImplementation("org.testng:testng:7.1.0") + testImplementation("org.testng:testng:7.1.0") +} + +publishing{ + repositories{ + maven{ + name = "github" + setUrl("https://maven.pkg.github.com/indicio-tech/aries-askar") + credentials { + username = getExtraString("githubUsername") + password = getExtraString("githubToken") + } + } + } + + publications.withType { + pom { + name.set("Aries Askar Kotlin") + description.set("Kotlin MPP wrapper around aries-askar") + url.set("https://github.com/indicio-tech/aries-askar") + + scm{ + url.set("https://github.com/indicio-tech/aries-askar") + } + } + } +} + + + +private enum class PlatformType { + APPLE, + ANDROID +} + +kotlin { + targetHierarchy.default() + + fun addLibs(libDirectory: String, target: KotlinNativeTarget) { + target.compilations.getByName("main") { + val uniffi by cinterops.creating { + includeDirs( + projectDir.absolutePath + "/src/nativeInterop/cinterop/headers/aries_askar/" + ) + packageName("aries_askar.cinterop") + extraOpts("-libraryPath", libDirectory) + } + } + + target.binaries.all { + linkerOpts("-L${libDirectory}", "-laries_askar") + linkerOpts("-Wl,-framework,Security") + } + } + + androidTarget{ + compilations.all{ + kotlinOptions.jvmTarget = "1.8" + } + instrumentedTestVariant.sourceSetTree.set(KotlinTargetHierarchy.SourceSetTree.test) + unitTestVariant.sourceSetTree.set(KotlinTargetHierarchy.SourceSetTree.unitTest) + } + + jvm{ + compilations.all{ + kotlinOptions.jvmTarget = "1.8" + this.kotlinOptions { + freeCompilerArgs += listOf("-Xdebug") + } + } + testRuns["test"].executionTask.configure{ + useJUnitPlatform() + } + + } + + macosX64{ + val libDirectory = "${projectDir}/../../target/x86_64-apple-darwin/release" + addLibs(libDirectory, this) + } + + macosArm64{ + val libDirectory = "${projectDir}/../../target/aarch64-apple-darwin/release" + addLibs(libDirectory, this) + } + + iosX64 { + val libDirectory = "${projectDir}/../../target/x86_64-apple-ios/release" + addLibs(libDirectory, this) + } + + iosSimulatorArm64 { + val libDirectory = "${projectDir}/../../target/aarch64-apple-ios-sim/release" + addLibs(libDirectory, this) + } + + iosArm64 { + val libDirectory = "${projectDir}/../../target/aarch64-apple-ios/release" + addLibs(libDirectory, this) + } + + sourceSets { + + all { + languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi") + } + + val commonMain by getting { + kotlin.srcDir(askarBindings.resolve("commonMain").resolve("kotlin")) + dependencies { + implementation("com.squareup.okio:okio:3.2.0") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-RC") + } + } + + val commonTest by getting { + dependencies{ + implementation(kotlin("test")) + } + } + + val androidMain by getting { + kotlin.srcDir(binaries) + kotlin.srcDir(askarBindings.resolve("jvmMain").resolve("kotlin")) + dependencies{ + implementation("net.java.dev.jna:jna:5.7.0@aar") + implementation("org.jetbrains.kotlinx:atomicfu:0.22.0") + } + dependsOn(commonMain) + } + + val jvmMain by getting { + kotlin.srcDir(binaries) + kotlin.srcDir(askarBindings.resolve("jvmMain").resolve("kotlin")) + dependencies{ + implementation("net.java.dev.jna:jna:5.13.0") + } + dependsOn(commonMain) + } + + val jvmTest by getting { + dependsOn(commonTest) + } + + val nativeMain by getting { + kotlin.srcDir(askarBindings.resolve("nativeMain").resolve("kotlin")) + } + + val nativeTest by getting{ + dependsOn(nativeMain) + } + } +} + + +android{ + sourceSets["main"].jniLibs.srcDir("src/androidMain/jniLibs") + apply(plugin = "kotlinx-atomicfu") + namespace = "askar" + compileSdk = 33 + defaultConfig{ + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + minSdk = 24 + + testOptions { + execution = "ANDROIDX_TEST_ORCHESTRATOR" + } + } + + dependencies { + androidTestImplementation("androidx.test:runner:1.5.2") + androidTestUtil("androidx.test:orchestrator:1.4.2") + } +} \ No newline at end of file diff --git a/wrappers/kotlin/gradle.properties b/wrappers/kotlin/gradle.properties new file mode 100644 index 00000000..4a40b75f --- /dev/null +++ b/wrappers/kotlin/gradle.properties @@ -0,0 +1,15 @@ +kotlin.code.style=official +kotlin.native.cacheKind.macosX64=none +kotlin.native.cacheKind.iosX64=none +kotlin.mpp.enableCInteropCommonization=true +kotlin.native.cacheKind.androidNativeArm32=none + + +#Android +android.useAndroidX=true +android.nonTransitiveRClass=true + +#MPP +kotlin.mpp.androidSourceSetLayoutVersion=2 + +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 \ No newline at end of file diff --git a/wrappers/kotlin/gradle/wrapper/gradle-wrapper.jar b/wrappers/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..41d9927a Binary files /dev/null and b/wrappers/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/wrappers/kotlin/gradle/wrapper/gradle-wrapper.properties b/wrappers/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..da1db5f0 --- /dev/null +++ b/wrappers/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/wrappers/kotlin/gradlew b/wrappers/kotlin/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/wrappers/kotlin/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/wrappers/kotlin/gradlew.bat b/wrappers/kotlin/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/wrappers/kotlin/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/wrappers/kotlin/libraries/headers/aries_askar.h b/wrappers/kotlin/libraries/headers/aries_askar.h new file mode 100644 index 00000000..292f1808 --- /dev/null +++ b/wrappers/kotlin/libraries/headers/aries_askar.h @@ -0,0 +1,536 @@ + + +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + int32_t capacity; + int32_t len; + uint8_t *_Nullable data; +} RustBuffer; + +// *_Nonnull is ignored by cinterop +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull); + +// Task defined in Rust that the foreign language executes +// TODO why would that be nullable? +typedef void (*UniFfiRustTaskCallback)(const void * _Nullable); + +// Callback to execute Rust tasks using a foreign language Task +// +// Args: +// executor: ForeignExecutor lowered into a size_t value +// delay: Delay in MS +// task: UniFfiRustTaskCallback to call +// task_data: data to pass the task callback +typedef void (*UniFfiForeignExecutorCallback)(size_t, uint32_t, UniFfiRustTaskCallback _Nullable, const void * _Nullable); + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +typedef void (*UniFfiFutureCallbackUInt8)(const void * _Nonnull, uint8_t, RustCallStatus); +typedef void (*UniFfiFutureCallbackInt8)(const void * _Nonnull, int8_t, RustCallStatus); +typedef void (*UniFfiFutureCallbackInt32)(const void * _Nonnull, int32_t, RustCallStatus); +typedef void (*UniFfiFutureCallbackInt64)(const void * _Nonnull, int64_t, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarCrypto)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarEcdh1PU)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarEcdhEs)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarLocalKey)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarScan)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarSession)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarStore)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarStoreManager)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrEncryptedBuffer)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrLocalKeyFactory)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustBuffer)(const void * _Nonnull, RustBuffer, RustCallStatus); + +// Scaffolding functions +void uniffi_aries_askar_fn_free_askarentry(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarentry_category(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarentry_name(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarentry_tags(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarentry_value(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarkeyentry(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarkeyentry_algorithm(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarkeyentry_metadata(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarkeyentry_name(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +int8_t uniffi_aries_askar_fn_method_askarkeyentry_is_local(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarkeyentry_tags(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarkeyentry_load_local_key(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarscan(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarscan_next(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarscan_fetch_all(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarsession(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_close(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_count(void*_Nonnull ptr, RustBuffer category, RustBuffer tag_filter, size_t uniffi_executor, UniFfiFutureCallbackInt64 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_fetch(void*_Nonnull ptr, RustBuffer category, RustBuffer name, int8_t for_update, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_fetch_all(void*_Nonnull ptr, RustBuffer category, RustBuffer tag_filter, RustBuffer limit, int8_t for_update, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_update(void*_Nonnull ptr, RustBuffer operation, RustBuffer category, RustBuffer name, RustBuffer value, RustBuffer tags, RustBuffer expiry_ms, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_remove_all(void*_Nonnull ptr, RustBuffer category, RustBuffer tag_filter, size_t uniffi_executor, UniFfiFutureCallbackInt64 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_insert_key(void*_Nonnull ptr, RustBuffer name, void*_Nonnull key, RustBuffer metadata, RustBuffer tags, RustBuffer expiry_ms, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_fetch_key(void*_Nonnull ptr, RustBuffer name, int8_t for_update, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_fetch_all_keys(void*_Nonnull ptr, RustBuffer algorithm, RustBuffer thumbprint, RustBuffer tag_filter, RustBuffer limit, int8_t for_update, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_remove_key(void*_Nonnull ptr, RustBuffer name, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_update_key(void*_Nonnull ptr, RustBuffer name, RustBuffer metadata, RustBuffer tags, RustBuffer expiry_ms, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarstore(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_get_profile_name(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_create_profile(void*_Nonnull ptr, RustBuffer profile, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_remove_profile(void*_Nonnull ptr, RustBuffer profile, size_t uniffi_executor, UniFfiFutureCallbackInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_rekey(void*_Nonnull ptr, RustBuffer key_method, RustBuffer pass_key, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_close(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_scan(void*_Nonnull ptr, RustBuffer profile, RustBuffer categogy, RustBuffer tag_filter, RustBuffer offset, RustBuffer limit, size_t uniffi_executor, UniFfiFutureCallbackRustArcPtrAskarScan _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_session(void*_Nonnull ptr, RustBuffer profile, size_t uniffi_executor, UniFfiFutureCallbackRustArcPtrAskarSession _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarlocalkey(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_public_bytes(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_secret_bytes(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_to_key_exchange(void*_Nonnull ptr, RustBuffer alg, void*_Nonnull pk, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_algorithm(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_public(void*_Nonnull ptr, RustBuffer alg, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_secret(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprint(void*_Nonnull ptr, RustBuffer alg, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprints(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_convert_key(void*_Nonnull ptr, RustBuffer alg, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_aead_params(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +int32_t uniffi_aries_askar_fn_method_askarlocalkey_aead_padding(void*_Nonnull ptr, int32_t msg_len, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_aead_random_nonce(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_aead_encrypt(void*_Nonnull ptr, RustBuffer message, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_aead_decrypt(void*_Nonnull ptr, RustBuffer ciphertext, RustBuffer tag, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_sign_message(void*_Nonnull ptr, RustBuffer message, RustBuffer sig_type, RustCallStatus *_Nonnull out_status +); +int8_t uniffi_aries_askar_fn_method_askarlocalkey_verify_signature(void*_Nonnull ptr, RustBuffer message, RustBuffer signature, RustBuffer sig_type, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_wrap_key(void*_Nonnull ptr, void*_Nonnull key, RustBuffer nonce, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_unwrap_key(void*_Nonnull ptr, RustBuffer alg, RustBuffer ciphertext, RustBuffer tag, RustBuffer nonce, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_encryptedbuffer(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_encryptedbuffer_nonce(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_encryptedbuffer_tag(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext_tag(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_localkeyfactory(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_localkeyfactory_new(RustCallStatus *_Nonnull out_status + +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_generate(void*_Nonnull ptr, RustBuffer alg, int8_t ephemeral, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_seed(void*_Nonnull ptr, RustBuffer alg, RustBuffer seed, RustBuffer method, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_jwk_slice(void*_Nonnull ptr, RustBuffer jwk, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_jwk(void*_Nonnull ptr, RustBuffer jwk, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_public_bytes(void*_Nonnull ptr, RustBuffer alg, RustBuffer bytes, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_secret_bytes(void*_Nonnull ptr, RustBuffer alg, RustBuffer bytes, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarstoremanager(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_askarstoremanager_new(RustCallStatus *_Nonnull out_status + +); +RustBuffer uniffi_aries_askar_fn_method_askarstoremanager_generate_raw_store_key(void*_Nonnull ptr, RustBuffer seed, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstoremanager_set_default_logger(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstoremanager_provision(void*_Nonnull ptr, RustBuffer spec_uri, RustBuffer key_method, RustBuffer pass_key, RustBuffer profile, int8_t recreate, size_t uniffi_executor, UniFfiFutureCallbackRustArcPtrAskarStore _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstoremanager_open(void*_Nonnull ptr, RustBuffer spec_uri, RustBuffer key_method, RustBuffer pass_key, RustBuffer profile, size_t uniffi_executor, UniFfiFutureCallbackRustArcPtrAskarStore _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstoremanager_remove(void*_Nonnull ptr, RustBuffer spec_uri, size_t uniffi_executor, UniFfiFutureCallbackInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarcrypto(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_askarcrypto_new(RustCallStatus *_Nonnull out_status + +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_random_nonce(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_crypto_box(void*_Nonnull ptr, void*_Nonnull receiver_key, void*_Nonnull sender_key, RustBuffer message, RustBuffer nonce, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_box_open(void*_Nonnull ptr, void*_Nonnull receiver_key, void*_Nonnull sender_key, RustBuffer message, RustBuffer nonce, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_box_seal(void*_Nonnull ptr, void*_Nonnull receiver_key, RustBuffer message, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_box_seal_open(void*_Nonnull ptr, void*_Nonnull receiver_key, RustBuffer ciphertext, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarecdhes(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_askarecdhes_new(RustBuffer alg_id, RustBuffer apu, RustBuffer apv, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdhes_derive_key(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, int8_t receive, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdhes_encrypt_direct(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, RustBuffer message, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarecdhes_decrypt_direct(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, RustBuffer ciphertext, RustBuffer tag, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdhes_sender_wrap_key(void*_Nonnull ptr, RustBuffer wrap_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, void*_Nonnull cek, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdhes_receiver_unwrap_key(void*_Nonnull ptr, RustBuffer wrap_alg, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, RustBuffer ciphertext, RustBuffer nonce, RustBuffer tag, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarecdh1pu(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_askarecdh1pu_new(RustBuffer alg_id, RustBuffer apu, RustBuffer apv, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdh1pu_derive_key(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, RustBuffer cc_tag, int8_t receive, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdh1pu_encrypt_direct(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, RustBuffer message, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarecdh1pu_decrypt_direct(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, RustBuffer ciphertext, RustBuffer tag, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdh1pu_sender_wrap_key(void*_Nonnull ptr, RustBuffer wrap_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, void*_Nonnull cek, RustBuffer cc_tag, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdh1pu_receiver_unwrap_key(void*_Nonnull ptr, RustBuffer wrap_alg, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, RustBuffer ciphertext, RustBuffer cc_tag, RustBuffer nonce, RustBuffer tag, RustCallStatus *_Nonnull out_status +); +RustBuffer ffi_aries_askar_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status +); +RustBuffer ffi_aries_askar_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +void ffi_aries_askar_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +RustBuffer ffi_aries_askar_rustbuffer_reserve(RustBuffer buf, int32_t additional, RustCallStatus *_Nonnull out_status +); +uint16_t uniffi_aries_askar_checksum_method_askarentry_category(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarentry_name(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarentry_tags(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarentry_value(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_algorithm(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_metadata(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_name(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_is_local(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_tags(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_load_local_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarscan_next(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarscan_fetch_all(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_close(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_count(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_fetch(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_fetch_all(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_update(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_remove_all(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_insert_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_fetch_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_fetch_all_keys(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_remove_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_update_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_get_profile_name(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_create_profile(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_remove_profile(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_rekey(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_close(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_scan(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_session(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_public_bytes(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_secret_bytes(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_key_exchange(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_algorithm(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_public(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_secret(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprint(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprints(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_convert_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_params(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_padding(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_random_nonce(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_encrypt(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_decrypt(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_sign_message(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_verify_signature(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_wrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_unwrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext(void + +); +uint16_t uniffi_aries_askar_checksum_method_encryptedbuffer_nonce(void + +); +uint16_t uniffi_aries_askar_checksum_method_encryptedbuffer_tag(void + +); +uint16_t uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext_tag(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_generate(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_seed(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk_slice(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_public_bytes(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_secret_bytes(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_generate_raw_store_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_set_default_logger(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_provision(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_open(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_remove(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_random_nonce(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_crypto_box(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_box_open(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_box_seal(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_box_seal_open(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_derive_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_encrypt_direct(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_decrypt_direct(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_sender_wrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_receiver_unwrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_derive_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_encrypt_direct(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_decrypt_direct(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_sender_wrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_receiver_unwrap_key(void + +); +uint16_t uniffi__checksum_constructor_localkeyfactory_new(void + +); +uint16_t uniffi__checksum_constructor_askarstoremanager_new(void + +); +uint16_t uniffi__checksum_constructor_askarcrypto_new(void + +); +uint16_t uniffi__checksum_constructor_askarecdhes_new(void + +); +uint16_t uniffi__checksum_constructor_askarecdh1pu_new(void + +); +void uniffi_foreign_executor_callback_set(UniFfiForeignExecutorCallback _Nonnull callback +); +uint32_t ffi_aries_askar_uniffi_contract_version(void + +); \ No newline at end of file diff --git a/wrappers/kotlin/settings.gradle.kts b/wrappers/kotlin/settings.gradle.kts new file mode 100644 index 00000000..75644aa3 --- /dev/null +++ b/wrappers/kotlin/settings.gradle.kts @@ -0,0 +1,18 @@ + +rootProject.name = "aries-askar-kotlin" + +pluginManagement{ + repositories{ + google() + gradlePluginPortal() + mavenCentral() + } +} + + +dependencyResolutionManagement{ + repositories{ + google() + mavenCentral() + } +} diff --git a/wrappers/kotlin/src/androidInstrumentedTest/kotlin/AskarTest.android.kt b/wrappers/kotlin/src/androidInstrumentedTest/kotlin/AskarTest.android.kt new file mode 100644 index 00000000..c6938901 --- /dev/null +++ b/wrappers/kotlin/src/androidInstrumentedTest/kotlin/AskarTest.android.kt @@ -0,0 +1,6 @@ +import androidx.test.platform.app.InstrumentationRegistry + +actual fun databaseUri(): String { + val a = InstrumentationRegistry.getInstrumentation().targetContext + return a.filesDir.absolutePath +} \ No newline at end of file diff --git a/wrappers/kotlin/src/androidMain/cpp/CMakeLists.txt b/wrappers/kotlin/src/androidMain/cpp/CMakeLists.txt new file mode 100644 index 00000000..7b4323e2 --- /dev/null +++ b/wrappers/kotlin/src/androidMain/cpp/CMakeLists.txt @@ -0,0 +1,38 @@ + +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html. +# For more examples on how to use CMake, see https://github.com/android/ndk-samples. + +# Sets the minimum CMake version required for this project. +cmake_minimum_required(VERSION 3.22.1) + +# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, +# Since this is the top level CMakeLists.txt, the project name is also accessible +# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level +# build script scope). +project("main") + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. +# +# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define +# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} +# is preferred for the same purpose. +# +# In order to load a library into your app from Java/Kotlin, you must call +# System.loadLibrary() and pass the name of the library defined here; +# for GameActivity/NativeActivity derived applications, the same library name must be +# used in the AndroidManifest.xml file. +add_library(${CMAKE_PROJECT_NAME} SHARED + # List C/C++ source files with relative paths to this CMakeLists.txt. + main.cpp) + +# Specifies libraries CMake should link to your target library. You +# can link libraries from various origins, such as libraries defined in this +# build script, prebuilt third-party libraries, or Android system libraries. +target_link_libraries(${CMAKE_PROJECT_NAME} + # List libraries link to the target library + android + log) diff --git a/wrappers/kotlin/src/androidUnitTest/kotlin/AskarTest.android.kt b/wrappers/kotlin/src/androidUnitTest/kotlin/AskarTest.android.kt new file mode 100644 index 00000000..9fb55f64 --- /dev/null +++ b/wrappers/kotlin/src/androidUnitTest/kotlin/AskarTest.android.kt @@ -0,0 +1,6 @@ +//import androidx.test.platform.app.InstrumentationRegistry +// +//actual fun databaseUri(): String { +// val a = InstrumentationRegistry.getInstrumentation().targetContext +// return a.filesDir.absolutePath +//} diff --git a/wrappers/kotlin/src/androidUnitTest/kotlin/askar/TestAndroid.kt b/wrappers/kotlin/src/androidUnitTest/kotlin/askar/TestAndroid.kt new file mode 100644 index 00000000..20cf8381 --- /dev/null +++ b/wrappers/kotlin/src/androidUnitTest/kotlin/askar/TestAndroid.kt @@ -0,0 +1,22 @@ +package askar + + +import androidx.test.platform.app.InstrumentationRegistry +import aries_askar.AskarCrypto +import aries_askar.AskarKeyAlg +import aries_askar.LocalKeyFactory +import askar.store.KdfMethod +import askar.store.Store +import kotlinx.coroutines.runBlocking +import org.junit.Test + + +class TestAndroid { + + @Test + fun testExample() { + + + + } +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/Types.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/Types.kt new file mode 100644 index 00000000..0af95882 --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/Types.kt @@ -0,0 +1,17 @@ +package askar + +import aries_askar.AskarKeyAlg +import askar.crypto.Jwk +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@Serializable +class ProtectedJson(val alg: String, val enc: String, val apu: String, val apv: String, val epk: Jwk) { + + constructor(alg: String, enc: AskarKeyAlg, apu: String, apv: String, epk: Jwk) : this(alg, enc.name, apu, apv, epk) + + override fun toString(): String { + return Json.encodeToString(this) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/CryptoBox.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/CryptoBox.kt new file mode 100644 index 00000000..938e69dc --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/CryptoBox.kt @@ -0,0 +1,71 @@ +@file:OptIn(ExperimentalUnsignedTypes::class) + +package askar.crypto + +import aries_askar.AskarLocalKey + +class CryptoBox { + + companion object { + private val crypto = aries_askar.AskarCrypto() + + fun randomNonce(): ByteArray { + val temp = crypto.randomNonce() + return temp.toUByteArray().toByteArray() + } + + fun cryptoBox(recipientKey: Key, senderKey: Key, message: String, nonce: ByteArray): ByteArray { + val messageList = message.map{ + it.code.toUByte() + } + val nonceList = nonce.map{ + it.toUByte() + } + return crypto.cryptoBox(recipientKey.handle(), senderKey.handle(), messageList, nonceList).toUByteArray().toByteArray() + } + + fun cryptoBox(recipientKey: Key, senderKey: Key, message: ByteArray, nonce: ByteArray): ByteArray { + val messageList = message.map{ + it.toUByte() + } + val nonceList = nonce.map{ + it.toUByte() + } + return crypto.cryptoBox(recipientKey.handle(), senderKey.handle(), messageList, nonceList).toUByteArray().toByteArray() + } + + fun open(recipientKey: Key, senderKey: Key, message: ByteArray, nonce: ByteArray): ByteArray { + val messageList = message.map{ + it.toUByte() + } + val nonceList = nonce.map{ + it.toUByte() + } + return crypto.boxOpen(recipientKey.handle(), senderKey.handle(), messageList, nonceList).toUByteArray().toByteArray() + } + + fun seal(recipientKey: Key, message: ByteArray): ByteArray { + val messageList = message.map { + it.toUByte() + } + return crypto.boxSeal(recipientKey.handle(), messageList).toUByteArray().toByteArray() + } + + fun seal(recipientKey: Key, message: String): ByteArray { + val messageList = message.map { + it.code.toUByte() + } + return crypto.boxSeal(recipientKey.handle(), messageList).toUByteArray().toByteArray() + } + + fun sealOpen(recipientKey: Key, cipherText: ByteArray): ByteArray { + val cipherList = cipherText.map { + it.toUByte() + } + return crypto.boxSealOpen(recipientKey.handle(), cipherList).toUByteArray().toByteArray() + } + + + + } +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/Ecdh1PU.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/Ecdh1PU.kt new file mode 100644 index 00000000..5c95884d --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/Ecdh1PU.kt @@ -0,0 +1,98 @@ +package askar.crypto + +import aries_askar.AskarKeyAlg +import aries_askar.EncryptedBuffer +import askar.toUbyteList + +class Ecdh1PU( + private val algId: String, + private val apu: String, + private val apv: String +) { + + private val askarEcdh1Pu = aries_askar.AskarEcdh1Pu(algId, apu, apv) + + fun deriveKey( + encAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + senderKey: Key, + receive: Boolean, + ccTag: ByteArray = byteArrayOf() + ): Key { + return Key( + askarEcdh1Pu.deriveKey( + encAlg, + ephemeralKey.handle(), + senderKey.handle(), + recipientKey.handle(), + ccTag.toUbyteList(), + receive + ) + ) + } + + fun encryptDirect( + encAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + senderKey: Key, + message: String, + nonce: ByteArray? = null, + aad: String? = null, + ): EncryptedBuffer { + val derived = this.deriveKey(encAlg, ephemeralKey, recipientKey, senderKey, false, ccTag = byteArrayOf()) + val buf = derived.aeadEncrypt(message, nonce = nonce?: byteArrayOf(), aad = aad?: "") + derived.free() + return buf + } + + fun decryptDirect( + encAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + senderKey: Key, + cipherText: ByteArray, + nonce: ByteArray, + aad: String? = null, + tag: ByteArray, + ): ByteArray { + val derived = this.deriveKey(encAlg, ephemeralKey, recipientKey, senderKey, false, ccTag = byteArrayOf()) + val encryptedBuffer = derived.aeadDecrypt(cipherText, aad = aad?: "", tag = tag, nonce = nonce) + derived.free() + return encryptedBuffer + } + + fun senderWrapKey( + wrapAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + senderKey: Key, + cek: Key, + ccTag: ByteArray + ):EncryptedBuffer { + val derived = this.deriveKey(wrapAlg, ephemeralKey, recipientKey, senderKey, false, ccTag) + val encryptedBuffer = derived.wrapKey(cek) + derived.free() + return encryptedBuffer + } + + fun receiverUnwrapKey( + wrapAlg: AskarKeyAlg, + encAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + senderKey: Key, + cipherText: ByteArray, + nonce: ByteArray = byteArrayOf(), + tag: String = "", + ccTag: ByteArray = byteArrayOf() + ): Key { + val derived = this.deriveKey(wrapAlg, ephemeralKey, recipientKey, senderKey, false, ccTag) + val encryptedBuffer = derived.unwrapKey(encAlg, tag, cipherText, nonce) + derived.free() + return encryptedBuffer + } + + +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/EcdhEs.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/EcdhEs.kt new file mode 100644 index 00000000..ef3fd732 --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/EcdhEs.kt @@ -0,0 +1,139 @@ +package askar.crypto + +import aries_askar.AskarKeyAlg +import aries_askar.EncryptedBuffer + +class EcdhEs( + private val algId: String, + private val apu: String, + private val apv: String +) { + private val askarEcdhEs = aries_askar.AskarEcdhEs(algId, apu, apv) + + + fun deriveKey( + encAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + receive: Boolean, + ): Key { + return Key( + askarEcdhEs.deriveKey( + encAlg, ephemeralKey.handle(), recipientKey.handle(), receive + ) + ) + } + + fun encryptDirect( + encAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + message: String, + nonce: ByteArray? = null, + aad: String? = null, + ): EncryptedBuffer { + val derived = this.deriveKey(encAlg, ephemeralKey, recipientKey, false) + val encryptedBuffer = derived.aeadEncrypt(message, aad = aad?: "", nonce = nonce?: ByteArray(0)) + derived.free() + return encryptedBuffer + } + + fun encryptDirect( + encAlg: AskarKeyAlg, + ephemeralKey: Jwk, + recipientKey: Jwk, + message: String, + nonce: ByteArray? = null, + aad: String? = null, + ): EncryptedBuffer { + val derived = this.deriveKey(encAlg, Key.fromJwk(ephemeralKey), Key.fromJwk(recipientKey), false) + val encryptedBuffer = derived.aeadEncrypt(message, aad = aad?: "", nonce = nonce?: ByteArray(1)) + derived.free() + return encryptedBuffer + } + + fun decryptDirect( + encAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + cipherText: ByteArray, + nonce: ByteArray, + aad: String? = null, + tag: ByteArray, + ): ByteArray { + val derived = this.deriveKey(encAlg, ephemeralKey, recipientKey, false) + val encryptedBuffer = derived.aeadDecrypt(cipherText, aad = aad?: "", tag = tag, nonce = nonce) + derived.free() + return encryptedBuffer + } + + fun decryptDirect( + encAlg: AskarKeyAlg, + ephemeralKey: Jwk, + recipientKey: Jwk, + cipherText: ByteArray, + nonce: ByteArray, + aad: String? = null, + tag: ByteArray, + ): ByteArray { + val derived = this.deriveKey(encAlg, Key.fromJwk(ephemeralKey), Key.fromJwk(recipientKey), false) + val encryptedBuffer = derived.aeadDecrypt(cipherText, aad = aad?: "", tag = tag, nonce = nonce) + derived.free() + return encryptedBuffer + } + + fun senderWrapKey( + wrapAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + cek: Key, + ): EncryptedBuffer { + val derived = this.deriveKey(wrapAlg, ephemeralKey, recipientKey, false) + val encryptedBuffer = derived.wrapKey(cek) + derived.free() + return encryptedBuffer + } + + fun senderWrapKey( + wrapAlg: AskarKeyAlg, + ephemeralKey: Jwk, + recipientKey: Jwk, + cek: Key, + ): EncryptedBuffer { + val derived = this.deriveKey(wrapAlg, Key.fromJwk(ephemeralKey), Key.fromJwk(recipientKey), false) + val encryptedBuffer = derived.wrapKey(cek) + derived.free() + return encryptedBuffer + } + + fun receiverUnwrapKey( + wrapAlg: AskarKeyAlg, + encAlg: AskarKeyAlg, + ephemeralKey: Key, + recipientKey: Key, + cipherText: ByteArray, + nonce: ByteArray = byteArrayOf(), + tag: String = "", + ): Key { + val derived = this.deriveKey(wrapAlg, ephemeralKey, recipientKey,true) + val encryptedBuffer = derived.unwrapKey(encAlg, tag, cipherText, nonce) + derived.free() + return encryptedBuffer + } + + fun receiverUnwrapKey( + wrapAlg: AskarKeyAlg, + encAlg: AskarKeyAlg, + ephemeralKey: Jwk, + recipientKey: Jwk, + cipherText: ByteArray, + nonce: ByteArray = byteArrayOf(), + tag: String = "", + ): Key { + val derived = this.deriveKey(wrapAlg, Key.fromJwk(ephemeralKey), Key.fromJwk(recipientKey),true) + val encryptedBuffer = derived.unwrapKey(encAlg, tag, cipherText, nonce) + derived.free() + return encryptedBuffer + } + +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/Jwk.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/Jwk.kt new file mode 100644 index 00000000..fe46357c --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/Jwk.kt @@ -0,0 +1,25 @@ +package askar.crypto + +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@Serializable +class Jwk( + val kty: String, + val crv: String? = null, + val x: String? = null, + val d: String? = null, + val y: String? = null, + val alg: String? = null, + val k: String? = null +) { + override fun toString(): String { + return Json.encodeToString(this) + } + + override fun equals(other: Any?): Boolean { + val o = other as Jwk + return this.toString() == o.toString() + } +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/Key.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/Key.kt new file mode 100644 index 00000000..68fd9d90 --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/crypto/Key.kt @@ -0,0 +1,178 @@ +@file:OptIn(ExperimentalUnsignedTypes::class) + +package askar.crypto + +import aries_askar.* +import askar.toUbyteList +import kotlinx.serialization.json.Json + +enum class SigAlgs (val alg: String) { + EdDSA("eddsa"), + ES256("es256"), + ES256K("es256k") +} + +class Key(private val localKey: AskarLocalKey) { + + companion object { + private val localKeyFactory = LocalKeyFactory() + + fun generate(algorithm: AskarKeyAlg, ephemeral: Boolean = false): Key { + return Key(localKeyFactory.generate(algorithm, ephemeral)) + } + + fun fromSeed(method: SeedMethod? = null, algorithm: AskarKeyAlg, seed: String): Key { + val seedList = seed.map { + it.code.toUByte() + } + return Key(localKeyFactory.fromSeed(algorithm, seedList, method)) + } + + fun fromSecretBytes(algorithm: AskarKeyAlg, secretKey: ByteArray): Key + { + val secretKeyList = secretKey.map { + it.toUByte() + } + return Key(localKeyFactory.fromSecretBytes(algorithm, secretKeyList)) + } + + fun fromPublicBytes(algorithm: AskarKeyAlg, publicKey: ByteArray): Key { + val publicKeyList = publicKey.map{ + it.toUByte() + } + return Key(localKeyFactory.fromPublicBytes(algorithm, publicKeyList)) + } + + fun fromJwk(jwk: Jwk): Key { + return Key(localKeyFactory.fromJwk(jwk.toString())) + } + } + + fun handle(): AskarLocalKey { + return this.localKey + } + + fun convertKey(algorithm: AskarKeyAlg): Key { + return Key(localKey.convertKey(algorithm)) + } + + fun fromKeyExchange(algorithm: AskarKeyAlg, publicKey: Key): Key { + return Key(localKey.toKeyExchange(algorithm, publicKey.handle())) + } + + fun algorithm(): AskarKeyAlg { + return localKey.algorithm() + } + +// fun ephemeral(): Boolean { +// TODO("Implement this in a future version that Uniffi has this method") +// } + + fun publicBytes(): ByteArray { + return localKey.toPublicBytes().toUByteArray().toByteArray() + } + + fun secretBytes(): ByteArray { + return localKey.toSecretBytes().toUByteArray().toByteArray() + } + + fun jwkPublic(): Jwk { + val jwkStr = localKey.toJwkPublic(this.algorithm()) + return Json.decodeFromString(jwkStr) + } + + fun jwkSecret(): Jwk { + val jwkStr = localKey.toJwkSecret().toUByteArray().toByteArray().decodeToString() + return Json.decodeFromString(jwkStr) + } + + fun jwkThumbprint(): String { + return localKey.toJwkThumbprint(this.algorithm()) + } + + fun aeadParams(): AeadParams { + return localKey.aeadParams() + } + + fun aeadRandomNonce(): ByteArray { + return localKey.aeadRandomNonce().toUByteArray().toByteArray() + } + + fun aeadEncrypt( + message: String, + nonce: ByteArray = ByteArray(0), + aad: String + ): EncryptedBuffer { + val messageList = message.map { + it.code.toUByte() + } + val nonceList = nonce.map{ + it.toUByte() + } + val aadList = aad.map{ + it.code.toUByte() + } + return localKey.aeadEncrypt(messageList, nonceList, aadList) + } + + fun aeadEncrypt( + message: String, + nonce: ByteArray = ByteArray(0), + aad: ByteArray = ByteArray(0) + ): EncryptedBuffer { + val messageList = message.map { + it.code.toUByte() + } + val nonceList = nonce.map{ + it.toUByte() + } + val aadList = aad.map{ + it.toUByte() + } + return localKey.aeadEncrypt(messageList, nonceList, aadList) + } + + fun aeadDecrypt( + cipherText: ByteArray, + nonce: ByteArray = ByteArray(0), + tag: ByteArray = ByteArray(0), + aad: String = "" + ): ByteArray { + return localKey.aeadDecrypt( + cipherText.toUbyteList(), + tag.toUbyteList(), + nonce.toUbyteList(), + aad.toUbyteList() + ).toUByteArray().toByteArray() + } + + fun signMessage(message: String, sigType: SigAlgs? = null): ByteArray { + return localKey.signMessage(message.toUbyteList(), sigType?.alg).toUByteArray().toByteArray() + } + + fun signMessage(message: ByteArray, sigType: SigAlgs? = null): ByteArray { + return localKey.signMessage(message.toUbyteList(), sigType?.alg).toUByteArray().toByteArray() + } + + fun verifySignature(message: String, signature: ByteArray, sigType: SigAlgs? = null): Boolean { + return localKey.verifySignature(message.toUbyteList(), signature.toUbyteList(), sigType?.alg) + } + + fun verifySignature(message: ByteArray, signature: ByteArray, sigType: SigAlgs? = null): Boolean { + return localKey.verifySignature(message.toUbyteList(), signature.toUbyteList(), sigType?.alg) + } + + fun wrapKey(other: Key, nonce: String = ""): EncryptedBuffer { + return localKey.wrapKey(other.handle(), nonce.toUbyteList()) + } + + fun unwrapKey(algorithm: AskarKeyAlg, tag: String? = null, cipherText: ByteArray = ByteArray(0), nonce: ByteArray? = null): Key { + return Key(localKey.unwrapKey(algorithm, cipherText.toUbyteList(),tag?.toUbyteList(), nonce?.toUbyteList())) + } + + fun free() { + localKey.destroy() + } + + +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/store/OpenSession.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/store/OpenSession.kt new file mode 100644 index 00000000..ccbb5d01 --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/store/OpenSession.kt @@ -0,0 +1,13 @@ +package askar.store + +import aries_askar.AskarStore + +class OpenSession(private val store: AskarStore, private val profile: String? = null, private val isTxn: Boolean) { + private var session: Session? = null + + suspend fun open(): Session { + if(session != null) throw Error("Session is already opened") + val sessionHandle = store.session(profile) + return Session(sessionHandle, isTxn) + } +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/store/Session.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/store/Session.kt new file mode 100644 index 00000000..15615c88 --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/store/Session.kt @@ -0,0 +1,171 @@ +package askar.store + +import aries_askar.* +import askar.crypto.Key +import askar.toUbyteList + +class Session(private var handle: AskarSession?, private val isTxn: Boolean) { + + + fun isTransaction(): Boolean { + return this.isTxn + } + + fun handle(): AskarSession? { + return this.handle + } + + private fun assertHandle() { + if(this.handle == null) + throw Error("Cannot operate on a closed Session") + } + + suspend fun count(category: String, tagFilter: String): Long { + assertHandle() + return handle!!.count(category, tagFilter) + } + + suspend fun fetch( + category: String, + name: String, + forUpdate: Boolean = false + ): AskarEntry? { + assertHandle() + return handle!!.fetch(category, name, forUpdate) + } + + suspend fun fetchAll( + category: String, + tagFilter: String = "{}", + forUpdate: Boolean = false, + limit: Long? = null + ): List { + assertHandle() + return handle!!.fetchAll(category, tagFilter, limit, forUpdate) + } + + suspend fun insert( + category: String, + name: String, + expiryMs: Long? = null, + tags: String? = null, + value: String + ) { + assertHandle() + handle!!.update(AskarEntryOperation.INSERT, category, name, value.toUbyteList(), tags, expiryMs) + } + + suspend fun replace( + category: String, + name: String, + expiryMs: Long? = null, + tags: String?, + value: String + ) { + assertHandle() + handle!!.update(AskarEntryOperation.REPLACE, category, name, value.toUbyteList(), tags, expiryMs) + } + + suspend fun remove(category: String, name: String): Boolean { + assertHandle() + return try { + handle!!.update(AskarEntryOperation.REMOVE, category, name, listOf(), null, null) + true + } catch (_: Throwable) { + false + } + } + + suspend fun removeAll(category: String, tagFilter: String? = null): Boolean { + assertHandle() + return try { + handle!!.removeAll(category, tagFilter) + true + } catch (_: Throwable) { + false + } + } + + suspend fun insertKey( + name: String, + key: Key, + expiryMs: Long? = null, + metadata: String? = null, + tags: String? = null + ): Boolean { + assertHandle() + return try { + handle!!.insertKey(name, key.handle(), metadata, tags, expiryMs) + true + } catch (_: Throwable) { + false + } + } + + suspend fun fetchKey( + name: String, + forUpdate: Boolean = false + ): AskarKeyEntry? { + assertHandle() + return handle!!.fetchKey(name, forUpdate) + } + + suspend fun fetchAllKeys( + algorithm: AskarKeyAlg? = null, + thumbprint: String? = null, + tagFilter: String? = null, + limit: Long? = null, + forUpdate: Boolean = false + ): List { + assertHandle() + return handle!!.fetchAllKeys(algorithm?.name?.lowercase(), thumbprint, tagFilter, limit, forUpdate) + } + + suspend fun updateKey( + name: String, + metadata: String? = null, + tags: String? = null, + expiryMs: Long? = null + ): Boolean { + assertHandle() + return try { + handle!!.updateKey(name, metadata, tags, expiryMs) + true + } catch (_: Throwable) { + false + } + } + + suspend fun removeKey(name: String): Boolean { + assertHandle() + return try { + handle!!.removeKey(name) + true + } catch (_: Throwable){ + false + } + } + + suspend fun commit(): Boolean { + assertHandle() + if(!isTxn) throw Error("Session is a transaction cannot commit") + return try { + handle!!.close() + this.handle = null + true + } catch (_: Throwable) { + false + } + } + + suspend fun close(): Boolean { + if(handle == null) throw Error("Cannot close a session that is already closed") + return try { + handle!!.close() + handle = null + true + } catch (_: Throwable) { + false + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/store/Store.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/store/Store.kt new file mode 100644 index 00000000..45220dc4 --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/store/Store.kt @@ -0,0 +1,104 @@ +package askar.store + +import aries_askar.AskarScan +import aries_askar.AskarStore +import aries_askar.AskarStoreManager + +enum class KdfMethod(val method: String) { + Raw("raw"), + None("None"), + Argon2IMod("kdf:argon2i:mod"), + Argon2IInt("kdf:argon2i:int") +} + +class Store(private val handle: AskarStore, private val uri: String) { + private var opener: OpenSession? = null + + fun handle(): AskarStore { + return handle + } + + fun uri(): String { + return uri + } + + suspend fun removeProfile(name: String): Boolean { + return handle.removeProfile(name) + } + + suspend fun createProfile(name: String = ""): String { + return handle.createProfile(name) + } + + suspend fun rekey(keyMethod: KdfMethod = KdfMethod.Argon2IInt, passKey: String): Boolean { + return try { + handle.rekey(keyMethod.method, passKey) + true + } catch (_: Throwable) { + false + } + } + + suspend fun close(remove: Boolean = false): Boolean { + this.opener = null + handle.close() + return if (remove) remove(this.uri) else false + } + + fun session(profile: String? = null): OpenSession { + return OpenSession(handle, profile, false) + } + + fun transaction(profile: String = "local"): OpenSession { + return OpenSession(handle, profile, true) + } + + suspend fun openSession(isTxn: Boolean = false): Session { + this.opener = OpenSession(handle, isTxn = isTxn) + return opener!!.open() + } + + suspend fun scan( + category: String, + tagFilter: String? = null, + offset: Long? = null, + limit: Long? = null, + profile: String? = null + ): AskarScan { + return handle.scan(profile, category, tagFilter, offset, limit) + } + + companion object { + private val storeManger = AskarStoreManager() + + fun generateRawKey(seed: String? = null): String { + return storeManger.generateRawStoreKey(seed) + } + + suspend fun provision( + uri: String, + keyMethod: KdfMethod? = null, + passkey: String? = null, + profile: String? = null, + recreate: Boolean + ): Store { + val handle = storeManger.provision(uri, keyMethod?.method, passkey, profile, recreate) + return Store(handle, uri) + } + + suspend fun open( + uri: String, + keyMethod: KdfMethod? = null, + passkey: String? = null, + profile: String? = null + ): Store { + val handle = storeManger.open(uri, keyMethod?.method, passkey, profile) + return Store(handle, uri) + } + + suspend fun remove(uri: String): Boolean { + return storeManger.remove(uri) + } + } + +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonMain/kotlin/askar/utils.kt b/wrappers/kotlin/src/commonMain/kotlin/askar/utils.kt new file mode 100644 index 00000000..5bbeaabc --- /dev/null +++ b/wrappers/kotlin/src/commonMain/kotlin/askar/utils.kt @@ -0,0 +1,14 @@ +package askar + + + internal fun String.toUbyteList(): List { + return this.map { + it.code.toUByte() + } + } + + internal fun ByteArray.toUbyteList(): List { + return this.map{ + it.toUByte() + } + } diff --git a/wrappers/kotlin/src/commonTest/kotlin/AskarTest.kt b/wrappers/kotlin/src/commonTest/kotlin/AskarTest.kt new file mode 100644 index 00000000..f7cd998a --- /dev/null +++ b/wrappers/kotlin/src/commonTest/kotlin/AskarTest.kt @@ -0,0 +1,388 @@ +import aries_askar.AskarEntry +import aries_askar.AskarKeyAlg +import aries_askar.AskarStoreManager +import askar.crypto.Key +import askar.store.KdfMethod +import askar.store.Store +import askar.toUbyteList +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.* +import kotlin.test.* +import kotlin.test.Test + + +expect fun databaseUri(): String + +class AskarTest { + + + private var store: Store? = null + + @BeforeTest + fun beforeEach() { + runBlocking { + store = setupWallet() + } + } + + @AfterTest + fun afterEach() { + runBlocking { + store?.close() + } + } + + @Test + fun argon2imod() { + runBlocking { + val argonStore = Store.provision( + recreate = true, + passkey = "abc", + uri = testStoreUri + "1", //Cannot have duplicate URI otherwise error is thrown + keyMethod = KdfMethod.Argon2IMod, + profile = "test" + ) + + assertEquals(testStoreUri + "1", argonStore.uri()) + + val session = argonStore.openSession() + val entry = session.fetch("unknownCategory", "unknownKey") + assertNull(entry) + session.close() + argonStore.close() + } + } + + @Test + fun argon2iint() { + runBlocking { + val argonStore = Store.provision( + recreate = true, + passkey = "abc", + uri = testStoreUri + "1", //Cannot have duplicate URI otherwise error is thrown + keyMethod = KdfMethod.Argon2IInt, + profile = "test" + ) + + val session = argonStore.openSession() + assertNull(session.fetch("unknownCategory", "unknownKey")) + session.close() + argonStore.close() + } + } + + @Test + fun rekey() { + runBlocking { + val initialKey = Store.generateRawKey("1234") + var newStore = Store.provision( + recreate = true, + profile = "rekey", + uri = "sqlite://${databaseUri()}/rekey.db", + keyMethod = KdfMethod.Raw, + passkey = initialKey + ) + val newKey = Store.generateRawKey("12345") + newStore.rekey(KdfMethod.Raw, newKey) + newStore.close() + assertFails { + Store.open( + profile = "rekey", + uri = "sqlite://${databaseUri()}/rekey.db", + keyMethod = KdfMethod.Raw, + passkey = initialKey + ) + } + println("store rekeyed") + newStore = Store.open( + profile = "rekey", + uri = "sqlite://${databaseUri()}/rekey.db", + keyMethod = KdfMethod.Raw, + passkey = newKey + ) + newStore.close(true) + } + } + + @Test + fun insert() { + runBlocking { + val session = store!!.openSession() + session.insert( + firstEntry.category, + firstEntry.name, + value = firstEntry.value, + tags = Json.encodeToString(firstEntry.tags) + ) + assertEquals(1, session.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + + session.close() + } + } + + @Test + fun replace() { + runBlocking { + val session = store!!.openSession() + session.insert( + firstEntry.category, + firstEntry.name, + value = firstEntry.value, + tags = Json.encodeToString(firstEntry.tags) + ) + assertEquals(1, session.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + + val updatedEntry = TestEntry( + firstEntry.category, firstEntry.name, value = "bar", tags = mapOf(Pair("foo", "bar")) + ) + println(updatedEntry) + session.replace( + updatedEntry.category, + updatedEntry.name, + value = updatedEntry.value, + tags = Json.encodeToString(updatedEntry.tags) + ) + assertEquals(1, session.count(updatedEntry.category, Json.encodeToString(updatedEntry.tags))) + session.close() + } + } + + @Test + fun remove() { + runBlocking { + val session = store!!.openSession() + session.insert( + firstEntry.category, + firstEntry.name, + value = firstEntry.value, + tags = Json.encodeToString(firstEntry.tags) + ) + + assertEquals(1, session.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + + session.remove(firstEntry.category, firstEntry.name) + + assertEquals(0, session.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + + session.close() + } + } + + @Test + fun removeAll() { + runBlocking { + val session = store!!.openSession() + session.insert( + firstEntry.category, + firstEntry.name, + value = firstEntry.value, + tags = Json.encodeToString(firstEntry.tags) + ) + session.insert( + secondEntry.category, + secondEntry.name, + value = secondEntry.value, + tags = Json.encodeToString(secondEntry.tags) + ) + + assertEquals(2, session.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + + session.removeAll(firstEntry.category) + + assertEquals(0, session.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + + session.close() + } + } + + @Test + fun scan() { + runBlocking { + val session = store!!.openSession() + session.insert( + firstEntry.category, + firstEntry.name, + value = firstEntry.value, + tags = Json.encodeToString(firstEntry.tags) + ) + session.insert( + category = secondEntry.category, + name = secondEntry.name, + value = secondEntry.value, + tags = Json.encodeToString(firstEntry.tags) + ) + + val found = store!!.scan(category = firstEntry.category).fetchAll() + + assertEquals(2, found.size) + + session.close() + } + } + + private fun compareEntry(askarEntry: AskarEntry, testEntry: TestEntry): Boolean { + if (askarEntry.category() != testEntry.category) return false + if (askarEntry.name() != testEntry.name) return false + if (askarEntry.value() != testEntry.value.toUbyteList()) return false + if (askarEntry.tags() != testEntry.tags) return false + return true + } + + + @Test + fun transactionBasic() { + runBlocking { + var txn = store!!.openSession(true) + + txn.insert( + firstEntry.category, + firstEntry.name, + value = firstEntry.value, + tags = Json.encodeToString(firstEntry.tags) + ) + + val count = txn.count(firstEntry.category, Json.encodeToString(firstEntry.tags)) + + assertEquals(1, count) + + val ret = txn.fetch(firstEntry.category, firstEntry.name) ?: throw Error("should not happen") + + txn.commit() + + assertTrue(compareEntry(ret, firstEntry)) + + txn = store!!.openSession(true) + + val found = txn.fetchAll(firstEntry.category) + + txn.commit() + + assertTrue(compareEntry(found[0], firstEntry)) + + val session = store!!.openSession() + + val fetch = session.fetch(firstEntry.category, firstEntry.name) ?: throw Error("could not fetch entry") + + assertTrue(compareEntry(fetch, firstEntry)) + + session.close() + } + } + + @Test + fun keyStore() { + runBlocking { + var session = store!!.openSession() + + val key = Key.generate(AskarKeyAlg.ED25519) + + val keyName = "testKey" + + session.insertKey(keyName, key, metadata = "metadata", tags = Json.encodeToString(mapOf(Pair("a", "b")))) + + val fetchedKey = session.fetchKey(keyName) ?: throw Error("Key was not fetched") + session.close() + + assertEquals(fetchedKey.algorithm(), AskarKeyAlg.ED25519.name.lowercase()) + assertEquals(fetchedKey.name(), keyName) + assertEquals(fetchedKey.metadata(), "metadata") + assertEquals(mapOf(Pair("a", "b")) , fetchedKey.tags()) + + session = store!!.openSession() + + session.updateKey( + keyName, + "updated metadata", + tags = Json.encodeToString(mapOf(Pair("a", "c"))) + ) + + val updatedFetch = session.fetchKey(keyName) + session.close() + + assertNotEquals(fetchedKey, updatedFetch) + + val ptr = fetchedKey.loadLocalKey() + val newKey = Key(ptr) + + assertEquals(key.jwkThumbprint(), newKey.jwkThumbprint()) + + session = store!!.openSession() + + val found = session.fetchAllKeys( + AskarKeyAlg.ED25519, + key.jwkThumbprint(), + Json.encodeToString(mapOf(Pair("a", "c"))), + ) + + session.close() + + assertEquals(found[0].algorithm(), updatedFetch?.algorithm()) + assertEquals(found[0].tags(), updatedFetch?.tags()) + assertEquals(found[0].name(), updatedFetch?.name()) + assertEquals(found[0].metadata(), updatedFetch?.metadata()) + + + session = store!!.openSession() + session.removeKey(keyName) + + assertNull(session.fetchKey(keyName)) + + session.close() + + key.handle().destroy() + newKey.handle().destroy() + fetchedKey.destroy() + updatedFetch?.destroy() + found.forEach { entry -> entry.destroy() } + } + } + + + @Test + fun profile() { + runBlocking { + val session = store!!.openSession() + session.insert(firstEntry.category, firstEntry.name, value = firstEntry.value, tags = Json.encodeToString(firstEntry.tags)) + session.close() + + val profile = store!!.createProfile() + + val session2 = store!!.session(profile).open() + assertEquals(0, session2.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + session2.insert(firstEntry.category, firstEntry.name, value = firstEntry.value, tags = Json.encodeToString(firstEntry.tags)) + assertEquals(1, session2.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + session2.close() + + //TODO: Find out why this fails +// if(!store.uri().contains(":memory:")){ +// val key = getRawKey()!! +// val store2 = Store.open(testStoreUri, StoreKeyMethod(KdfMethod.Raw), passkey = key) +// val session3 = store2.openSession() +// assertEquals(0, session3.count(firstEntry.category, firstEntry.tags)) +// session3.close() +// store2.close() +// } + + assertFails { store!!.createProfile(profile) } + + val session4 = store!!.session(profile).open() + assertEquals(1, session4.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + session4.close() + + store!!.removeProfile(profile) + + val session5 = store!!.session(profile).open() + assertEquals(0, session5.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + session5.close() + + val session6 = store!!.session("unknown profile").open() + assertFails { session6.count(firstEntry.category, Json.encodeToString(firstEntry.tags)) } + session6.close() + + val session7 = store!!.session(profile).open() + assertEquals(0, session7.count(firstEntry.category, Json.encodeToString(firstEntry.tags))) + session7.close() + } + } +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonTest/kotlin/AskarUtils/initialize.kt b/wrappers/kotlin/src/commonTest/kotlin/AskarUtils/initialize.kt new file mode 100644 index 00000000..c225420a --- /dev/null +++ b/wrappers/kotlin/src/commonTest/kotlin/AskarUtils/initialize.kt @@ -0,0 +1,65 @@ +import aries_askar.AskarEntry +import askar.store.KdfMethod +import askar.store.Store +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +fun getRawKey(): String { + return Store.generateRawKey("00000000000000000000000000000My1") +} + +val firstEntry = TestEntry( + "category-one", + "test-entry", + mapOf(Pair("plaintag", "a"), Pair("enctag", "b")), + "foo" +) + +val secondEntry = TestEntry( + "category-one", + "secondEntry", + mapOf( + Pair("plaintag", "a"), + Pair("enctag", "b") + ), + buildJsonObject { put("foo", "bar") }.toString() +) + +val thirdEntry = TestEntry( + "category-one", + "secondEntry", + mapOf( + Pair("~plaintag", "a"), + Pair("enctag", "b") + ), + buildJsonObject { put("foo", "baz") }.toString() +) + +@Serializable +class TestEntry( + val category: String, + val name: String, + val tags: Map, + val value: String +) { + override fun toString(): String { + return Json.encodeToString(this) + } +} + +val testStoreUri = "sqlite://${databaseUri()}/test.db" + +suspend fun setupWallet(): Store { + val key = getRawKey() + return Store.provision( + recreate = true, + uri = testStoreUri, + keyMethod = KdfMethod.Raw, + passkey = key + ) +} + diff --git a/wrappers/kotlin/src/commonTest/kotlin/CryptoBoxTest.kt b/wrappers/kotlin/src/commonTest/kotlin/CryptoBoxTest.kt new file mode 100644 index 00000000..fea7b045 --- /dev/null +++ b/wrappers/kotlin/src/commonTest/kotlin/CryptoBoxTest.kt @@ -0,0 +1,22 @@ +import aries_askar.AskarKeyAlg +import askar.crypto.CryptoBox +import askar.crypto.Key + +import kotlin.test.Test +import kotlin.test.assertEquals + +class CryptoBoxTest { + + @Test + fun seal() { + val x25519Key = Key.generate(AskarKeyAlg.X25519) + + val message = "foobar" + val sealed = CryptoBox.seal(x25519Key, message) + + val opened = CryptoBox.sealOpen(x25519Key, sealed) + assertEquals(message, opened.decodeToString()) + + x25519Key.handle().destroy() + } +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonTest/kotlin/JoseEcdh.kt b/wrappers/kotlin/src/commonTest/kotlin/JoseEcdh.kt new file mode 100644 index 00000000..e20ff9ff --- /dev/null +++ b/wrappers/kotlin/src/commonTest/kotlin/JoseEcdh.kt @@ -0,0 +1,330 @@ +@file:OptIn( + ExperimentalEncodingApi::class, ExperimentalUnsignedTypes::class, ExperimentalEncodingApi::class +) + +import aries_askar.AskarKeyAlg +import askar.ProtectedJson +import askar.crypto.Ecdh1PU +import kotlin.io.encoding.ExperimentalEncodingApi +import askar.crypto.EcdhEs +import askar.crypto.Jwk +import askar.crypto.Key +import kotlin.io.encoding.Base64 +import kotlin.test.Test +import kotlin.test.assertEquals + +class JoseEcdh { + + + @OptIn(ExperimentalEncodingApi::class) + fun base64Url(str: String): String { + return Base64.UrlSafe.encode(str.encodeToByteArray()) + } + + @Test + fun ecdhEsDirect() { + val bobKey = Key.generate(AskarKeyAlg.P256) + //val bobJwk = bobKey.jwkPublic() + val ephemeralKey = Key.generate(AskarKeyAlg.P256) + val ephemeralJwk = ephemeralKey.jwkPublic() + val message = "Hello there" + val alg = "ECDH-ES" + val apu = "Alice" + val apv = "Bob" + val encAlg = AskarKeyAlg.A128_GCM + val json = ProtectedJson(alg, AskarKeyAlg.A128_GCM, "Alice", "Bob", ephemeralJwk) + + val b64 = base64Url(json.toString()) + + val encryptedMessage = + EcdhEs(encAlg.name, apu, apv).encryptDirect(encAlg, ephemeralKey, bobKey, message, aad = b64) + val nonce = encryptedMessage.nonce().toUByteArray().toByteArray() + val tags = encryptedMessage.tag().toUByteArray().toByteArray() + val cipherText = encryptedMessage.ciphertext().toUByteArray().toByteArray() + + val messageReceived = EcdhEs(encAlg.name, apu, apv).decryptDirect( + encAlg, + ephemeralKey, + bobKey, + cipherText, + nonce, + b64, + tags + ) + assertEquals(message, messageReceived.decodeToString()) + ephemeralKey.handle().destroy() + bobKey.handle().destroy() + } + + @Test + fun ecdhEsWrapped() { + val bobKey = Key.generate(AskarKeyAlg.X25519) + val bobJwk = bobKey.jwkPublic() + val ephemeralKey = Key.generate(AskarKeyAlg.X25519) + val ephemeralJwk = ephemeralKey.jwkPublic() + val message = "Hello there" + val alg = "ECDH-ES+A128KW" + val enc = "A256GCM" + val apu = "Alice" + val apv = "bob" + + val json = ProtectedJson(alg, enc, apu, apv, ephemeralJwk) + + val b64 = base64Url(json.toString()) + + val cek = Key.generate(AskarKeyAlg.A128_GCM) + + val encryptedMessage = cek.aeadEncrypt(message, aad = b64) + val nonce = encryptedMessage.nonce().toUByteArray().toByteArray() + val tags = encryptedMessage.tag().toUByteArray().toByteArray() + val cipherText = encryptedMessage.ciphertext().toUByteArray().toByteArray() + + val encryptedKey = EcdhEs(alg, apu, apv).senderWrapKey( + AskarKeyAlg.A128_KW, + ephemeralKey, + Key.fromJwk(bobJwk), + cek, + ).ciphertext().toUByteArray().toByteArray() + + val cekReceiver = EcdhEs(alg, apu, apv).receiverUnwrapKey( + AskarKeyAlg.A128_KW, + AskarKeyAlg.A128_GCM, + Key.fromJwk(ephemeralJwk), + bobKey, + cipherText = encryptedKey + ) + + val messageReceived = cekReceiver.aeadDecrypt(cipherText, nonce, tags, b64) + + assertEquals(message, messageReceived.decodeToString()) + ephemeralKey.handle().destroy() + bobKey.handle().destroy() + cek.handle().destroy() + cekReceiver.handle().destroy() + } + + @OptIn(ExperimentalEncodingApi::class) + @Test + fun ecdh1puDirect() { + val aliceKey = Key.generate(AskarKeyAlg.P256) + val aliceJwk = aliceKey.jwkSecret() + val bobKey = Key.generate(AskarKeyAlg.P256) + val bobJwk = bobKey.jwkPublic() + val ephemeralKey = Key.generate(AskarKeyAlg.P256) + val ephemeralJwk = ephemeralKey.jwkPublic() + val message = "Hello there" + val alg = "ECDH-1PU" + val enc = AskarKeyAlg.A128_GCM + val apu = "Alice" + val apv = "Bob" + val protectedJson = ProtectedJson( + alg, + enc, + base64Url(apu), + base64Url(apv), + ephemeralJwk + ) + val protectedString = protectedJson.toString() + val protectedB64 = Base64.UrlSafe.encode(protectedString.encodeToByteArray()) + + val encryptedMessage = Ecdh1PU( + enc.name, + apu, + apv + ).encryptDirect( + AskarKeyAlg.A128_GCM, + ephemeralKey, + Key.fromJwk(bobJwk), + senderKey = aliceKey, + message = message, + aad = protectedB64, + ) + + val nonce = encryptedMessage.nonce().toUByteArray().toByteArray() + val tag = encryptedMessage.tag().toUByteArray().toByteArray() + val ciphertext = encryptedMessage.ciphertext().toUByteArray().toByteArray() + + val messageReceived = Ecdh1PU( + enc.name, + apu, + apv, + ).decryptDirect( + AskarKeyAlg.A128_GCM, + ephemeralKey, + bobKey, + Key.fromJwk(aliceJwk), + ciphertext, + nonce, + protectedB64, + tag + ) + + assertEquals(message, messageReceived.decodeToString()) + aliceKey.handle().destroy() + bobKey.handle().destroy() + ephemeralKey.handle().destroy() + } + + private fun bufferFromHex(str: String): ByteArray { + val list = str.chunked(2) + val arr = ByteArray(list.size) { + list[it].toUInt(16).toByte() + } + return arr + } + + /** + * + * These tests have been implemented as a copy from the python wapper. + * The test vectores have been taken from: + * https://www.ietf.org/archive/id/draft-madden-jose-ecdh-1pu-04.txt + */ + @Test + fun ecdh1puWrapped() { + val ephemeral = Key.fromJwk( + Jwk( + "OKP", + "X25519", + "k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc", + "x8EVZH4Fwk673_mUujnliJoSrLz0zYzzCWp5GUX2fc8" + ) + ) + + val alice = Key.fromJwk( + Jwk( + "OKP", + "X25519", + "Knbm_BcdQr7WIoz-uqit9M0wbcfEr6y-9UfIZ8QnBD4", + "i9KuFhSzEBsiv3PKVL5115OCdsqQai5nj_Flzfkw5jU", + ) + ) + + val bob = Key.fromJwk( + Jwk( + "OKP", + "X25519", + "BT7aR0ItXfeDAldeeOlXL_wXqp-j5FltT0vRSG16kRw", + "1gDirl_r_Y3-qUa3WXHgEXrrEHngWThU3c9zj9A2uBg", + ) + ) + + val alg = "ECDH-1PU+A128KW" + val apu = "Alice" + val apv = "Bob and Charlie" + val base64urlApu = base64Url(apu) + val base64urlApv = base64Url(apv) + + assertEquals("QWxpY2U=", base64urlApu) + assertEquals("Qm9iIGFuZCBDaGFybGll", base64urlApv) + + val protectedJson = ProtectedJson( + alg, + "A256CBC-HS512", + "QWxpY2U", + "Qm9iIGFuZCBDaGFybGll", + Jwk("OKP", "X25519", "k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc"), + ) + + val b64 = base64Url(protectedJson.toString()) + + val str = + "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0" + + val arr = bufferFromHex(str) + + val cek = Key.fromSecretBytes(AskarKeyAlg.A256_CBC_HS512, arr) + + val iv = byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + val message = "Three is a magic number." + + val enc = cek.aeadEncrypt(message, iv, b64) + + val tags = enc.tag().toUByteArray().toByteArray() + val ciphertext = enc.ciphertext().toUByteArray().toByteArray() + + assertEquals("Az2IWsISEMDJvyc5XRL-3-d-RgNBOGolCsxFFoUXFYw=", Base64.UrlSafe.encode(ciphertext)) + assertEquals("HLb4fTlm8spGmij3RyOs2gJ4DpHM4hhVRwdF_hGb3WQ=", Base64.UrlSafe.encode(tags)) + + val derived = Ecdh1PU( + apv = apv, + apu = apu, + algId = alg + ).deriveKey( + encAlg = AskarKeyAlg.A128_KW, + recipientKey = bob, + senderKey = alice, + ccTag = tags, + ephemeralKey = ephemeral, + receive = false + ) + + val expectedBuf = bufferFromHex("df4c37a0668306a11e3d6b0074b5d8df") + + derived.secretBytes().forEachIndexed { index, byte -> + assertEquals(expectedBuf[index], byte) + } + + val encryptedKey = derived.wrapKey(cek).ciphertext() + + val expectedKey = + Base64.UrlSafe.decode("pOMVA9_PtoRe7xXW1139NzzN1UhiFoio8lGto9cf0t8PyU-sjNXH8-LIRLycq8CHJQbDwvQeU1cSl55cQ0hGezJu2N9IY0QN") + + encryptedKey.forEachIndexed { index, byte -> + assertEquals(expectedKey[index], byte.toByte()) + } + + val encryptedKey2 = Ecdh1PU(apv = apv, apu = apu, algId = alg).senderWrapKey( + wrapAlg = AskarKeyAlg.A128_KW, + ephemeralKey = ephemeral, + senderKey = alice, + recipientKey = bob, + ccTag = tags, + cek = cek + ) + + encryptedKey2.ciphertext().forEachIndexed { index, byte -> + assertEquals(byte, encryptedKey[index]) + } + + val derivedReciever = Ecdh1PU(apv = apv, apu = apu, algId = alg).deriveKey( + encAlg = AskarKeyAlg.A128_KW, + ephemeralKey = ephemeral, + senderKey = alice, + recipientKey = bob, + ccTag = tags, + receive = true + ) + + val cekReveiver = derivedReciever.unwrapKey( + algorithm = AskarKeyAlg.A256_CBC_HS512, + cipherText = encryptedKey.toUByteArray().toByteArray() + ) + + val messageReceived = cekReveiver.aeadDecrypt(cipherText = ciphertext, nonce = iv, aad = b64, tag = tags) + + assertEquals(message, messageReceived.decodeToString()) + + val cekReceiver2 = Ecdh1PU( + apv = apv, + apu = apu, + algId = alg + ).receiverUnwrapKey( + wrapAlg = AskarKeyAlg.A128_KW, + encAlg = AskarKeyAlg.A256_CBC_HS512, + ephemeralKey = ephemeral, + senderKey = alice, + recipientKey = bob, + cipherText = encryptedKey.toUByteArray().toByteArray(), + ccTag = tags + ) + + assertEquals(cekReceiver2.jwkSecret(), cek.jwkSecret()) + + cek.handle().destroy() + ephemeral.handle().destroy() + alice.handle().destroy() + bob.handle().destroy() + derived.handle().destroy() + } + +} \ No newline at end of file diff --git a/wrappers/kotlin/src/commonTest/kotlin/KeyTest.kt b/wrappers/kotlin/src/commonTest/kotlin/KeyTest.kt new file mode 100644 index 00000000..c0b22f3b --- /dev/null +++ b/wrappers/kotlin/src/commonTest/kotlin/KeyTest.kt @@ -0,0 +1,121 @@ +import aries_askar.AskarKeyAlg +import aries_askar.SeedMethod +import askar.crypto.Key +import kotlinx.coroutines.runBlocking +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertTrue + +class KeyTest { + + fun List.toByteArray(): ByteArray { + return this.toUByteArray().toByteArray() + } + + /** + * aes cbc hmac + */ + @Test + fun aesAlgTest() { + runBlocking { + val key = Key.generate(AskarKeyAlg.A128_CBC_HS256) + assertEquals(key.algorithm(), AskarKeyAlg.A128_CBC_HS256) + val message = "test message" + val aeadNonce = key.aeadRandomNonce() + val params = key.aeadParams() + assertEquals(16, params.nonceLength) + assertEquals(16, params.tagLength) + val enc = key.aeadEncrypt(message = message, nonce = aeadNonce) + val dec = key.aeadDecrypt(enc.ciphertext().toByteArray(), nonce = enc.nonce().toByteArray(), tag = enc.tag().toByteArray()) + assertEquals(message, dec.decodeToString()) + } + } + + @Test + fun blsG2KeyGen() { + val seed = "testseed000000000000000000000001" + val key = Key.fromSeed(algorithm = AskarKeyAlg.BLS12_381G2, seed = seed) + + val jwk = key.jwkPublic() + + assertEquals("BLS12381_G2", jwk.crv) + assertEquals("OKP", jwk.kty) + assertEquals( + "lH6hIRPzjlKW6LvPm0sHqyEbGqf8ag7UWpA_GFfefwq_kzDXSHmls9Yoza_be23zEw-pSOmKI_MGR1DahBa7Jbho2BGwDNV_QmyhxMYBwTH12Ltk_GLyPD4AP6pQVgge", + jwk.x + ) + + key.handle().destroy() + } + + @Test + fun blsG1KeyGen() { + val seed = "testseed000000000000000000000001" + val key = Key.fromSeed(algorithm = AskarKeyAlg.BLS12_381G1, seed = seed) + + val jwk = key.jwkPublic() + + assertEquals("BLS12381_G1", jwk.crv) + assertEquals("OKP", jwk.kty) + assertEquals( + "hsjb9FSBUJXuB1fCluEcUBLeAPgIbnZGfxPKyeN3LVjQaKFWzXfNtMFAY8VL-eu-", + jwk.x + ) + + key.handle().destroy() + } + + @Test + fun blsG1G2KeyGen() { + val seed = "testseed000000000000000000000001" + val key = Key.fromSeed(algorithm = AskarKeyAlg.BLS12_381G1G2, seed = seed, method = SeedMethod.BLS_KEY_GEN) + + val jwk = key.jwkPublic() + + assertEquals("BLS12381_G1G2", jwk.crv) + assertEquals("OKP", jwk.kty) + assertEquals( + "h56eYI8Qkq5hitICb-ik8wRTzcn6Fd4iY8aDNVc9q1xoPS3lh4DB_B4wNtar1HrViZIOsO6BgLV72zCrBE2ym3DEhDYcghnUMO4O8IVVD8yS-C_zu6OA3L-ny-AO4rbkAo-WuApZEjn83LY98UtoKpTufn4PCUFVQZzJNH_gXWHR3oDspJaCbOajBfm5qj6d" + ,jwk.x + ) + + key.handle().destroy() + } + + @Test + fun ed25519() { + val key = Key.generate(AskarKeyAlg.ED25519) + assertEquals(key.algorithm(), AskarKeyAlg.ED25519) + val message = "Test Message" + val signature = key.signMessage(message) + assertTrue(key.verifySignature(message, signature)) + + val messageBuffer = message.encodeToByteArray() + val byteSignature = key.signMessage(messageBuffer) + assertTrue(key.verifySignature(messageBuffer, byteSignature)) + + val x25519Key = key.convertKey(AskarKeyAlg.X25519) + val x25519Key2 = Key.generate(AskarKeyAlg.X25519) + + val kex = x25519Key.fromKeyExchange(AskarKeyAlg.XC20P, x25519Key2) + assertIs(kex) + + val jwkPub = key.jwkPublic() + val jwkSec = key.jwkSecret() + + assertEquals("OKP", jwkPub.kty) + assertEquals("Ed25519", jwkPub.crv) + + assertEquals("OKP", jwkSec.kty) + assertEquals("Ed25519", jwkSec.crv) + + key.handle().destroy() + x25519Key.handle().destroy() + x25519Key2.handle().destroy() + kex.handle().destroy() + } + +} + + diff --git a/wrappers/kotlin/src/jvmTest/kotlin/AskarTest.jvm.kt b/wrappers/kotlin/src/jvmTest/kotlin/AskarTest.jvm.kt new file mode 100644 index 00000000..14f471ec --- /dev/null +++ b/wrappers/kotlin/src/jvmTest/kotlin/AskarTest.jvm.kt @@ -0,0 +1,3 @@ +actual fun databaseUri(): String { + return "tmp" +} \ No newline at end of file diff --git a/wrappers/kotlin/src/jvmTest/kotlin/askar/TestJVM.kt b/wrappers/kotlin/src/jvmTest/kotlin/askar/TestJVM.kt new file mode 100644 index 00000000..b9af85d8 --- /dev/null +++ b/wrappers/kotlin/src/jvmTest/kotlin/askar/TestJVM.kt @@ -0,0 +1,20 @@ +package askar + +import aries_askar.AskarCrypto +import aries_askar.AskarKeyAlg +import aries_askar.LocalKeyFactory +import org.junit.jupiter.api.Test + +class JVMGreetingTest { + + @Test + fun testExample() { + println("Load askar") + + println("Yeet") + val keyFactory = LocalKeyFactory() + val crypto = AskarCrypto() + + val key = keyFactory.generate(alg = AskarKeyAlg.X25519, ephemeral = false) + } +} diff --git a/wrappers/kotlin/src/nativeInterop/cinterop/headers/aries_askar/aries_askar.h b/wrappers/kotlin/src/nativeInterop/cinterop/headers/aries_askar/aries_askar.h new file mode 100644 index 00000000..292f1808 --- /dev/null +++ b/wrappers/kotlin/src/nativeInterop/cinterop/headers/aries_askar/aries_askar.h @@ -0,0 +1,536 @@ + + +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + int32_t capacity; + int32_t len; + uint8_t *_Nullable data; +} RustBuffer; + +// *_Nonnull is ignored by cinterop +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull); + +// Task defined in Rust that the foreign language executes +// TODO why would that be nullable? +typedef void (*UniFfiRustTaskCallback)(const void * _Nullable); + +// Callback to execute Rust tasks using a foreign language Task +// +// Args: +// executor: ForeignExecutor lowered into a size_t value +// delay: Delay in MS +// task: UniFfiRustTaskCallback to call +// task_data: data to pass the task callback +typedef void (*UniFfiForeignExecutorCallback)(size_t, uint32_t, UniFfiRustTaskCallback _Nullable, const void * _Nullable); + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +typedef void (*UniFfiFutureCallbackUInt8)(const void * _Nonnull, uint8_t, RustCallStatus); +typedef void (*UniFfiFutureCallbackInt8)(const void * _Nonnull, int8_t, RustCallStatus); +typedef void (*UniFfiFutureCallbackInt32)(const void * _Nonnull, int32_t, RustCallStatus); +typedef void (*UniFfiFutureCallbackInt64)(const void * _Nonnull, int64_t, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarCrypto)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarEcdh1PU)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarEcdhEs)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarLocalKey)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarScan)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarSession)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarStore)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrAskarStoreManager)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrEncryptedBuffer)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustArcPtrLocalKeyFactory)(const void * _Nonnull, void*_Nonnull, RustCallStatus); +typedef void (*UniFfiFutureCallbackRustBuffer)(const void * _Nonnull, RustBuffer, RustCallStatus); + +// Scaffolding functions +void uniffi_aries_askar_fn_free_askarentry(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarentry_category(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarentry_name(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarentry_tags(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarentry_value(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarkeyentry(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarkeyentry_algorithm(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarkeyentry_metadata(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarkeyentry_name(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +int8_t uniffi_aries_askar_fn_method_askarkeyentry_is_local(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarkeyentry_tags(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarkeyentry_load_local_key(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarscan(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarscan_next(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarscan_fetch_all(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarsession(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_close(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_count(void*_Nonnull ptr, RustBuffer category, RustBuffer tag_filter, size_t uniffi_executor, UniFfiFutureCallbackInt64 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_fetch(void*_Nonnull ptr, RustBuffer category, RustBuffer name, int8_t for_update, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_fetch_all(void*_Nonnull ptr, RustBuffer category, RustBuffer tag_filter, RustBuffer limit, int8_t for_update, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_update(void*_Nonnull ptr, RustBuffer operation, RustBuffer category, RustBuffer name, RustBuffer value, RustBuffer tags, RustBuffer expiry_ms, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_remove_all(void*_Nonnull ptr, RustBuffer category, RustBuffer tag_filter, size_t uniffi_executor, UniFfiFutureCallbackInt64 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_insert_key(void*_Nonnull ptr, RustBuffer name, void*_Nonnull key, RustBuffer metadata, RustBuffer tags, RustBuffer expiry_ms, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_fetch_key(void*_Nonnull ptr, RustBuffer name, int8_t for_update, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_fetch_all_keys(void*_Nonnull ptr, RustBuffer algorithm, RustBuffer thumbprint, RustBuffer tag_filter, RustBuffer limit, int8_t for_update, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_remove_key(void*_Nonnull ptr, RustBuffer name, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarsession_update_key(void*_Nonnull ptr, RustBuffer name, RustBuffer metadata, RustBuffer tags, RustBuffer expiry_ms, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarstore(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_get_profile_name(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_create_profile(void*_Nonnull ptr, RustBuffer profile, size_t uniffi_executor, UniFfiFutureCallbackRustBuffer _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_remove_profile(void*_Nonnull ptr, RustBuffer profile, size_t uniffi_executor, UniFfiFutureCallbackInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_rekey(void*_Nonnull ptr, RustBuffer key_method, RustBuffer pass_key, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_close(void*_Nonnull ptr, size_t uniffi_executor, UniFfiFutureCallbackUInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_scan(void*_Nonnull ptr, RustBuffer profile, RustBuffer categogy, RustBuffer tag_filter, RustBuffer offset, RustBuffer limit, size_t uniffi_executor, UniFfiFutureCallbackRustArcPtrAskarScan _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstore_session(void*_Nonnull ptr, RustBuffer profile, size_t uniffi_executor, UniFfiFutureCallbackRustArcPtrAskarSession _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarlocalkey(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_public_bytes(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_secret_bytes(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_to_key_exchange(void*_Nonnull ptr, RustBuffer alg, void*_Nonnull pk, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_algorithm(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_public(void*_Nonnull ptr, RustBuffer alg, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_secret(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprint(void*_Nonnull ptr, RustBuffer alg, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprints(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_convert_key(void*_Nonnull ptr, RustBuffer alg, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_aead_params(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +int32_t uniffi_aries_askar_fn_method_askarlocalkey_aead_padding(void*_Nonnull ptr, int32_t msg_len, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_aead_random_nonce(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_aead_encrypt(void*_Nonnull ptr, RustBuffer message, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_aead_decrypt(void*_Nonnull ptr, RustBuffer ciphertext, RustBuffer tag, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarlocalkey_sign_message(void*_Nonnull ptr, RustBuffer message, RustBuffer sig_type, RustCallStatus *_Nonnull out_status +); +int8_t uniffi_aries_askar_fn_method_askarlocalkey_verify_signature(void*_Nonnull ptr, RustBuffer message, RustBuffer signature, RustBuffer sig_type, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_wrap_key(void*_Nonnull ptr, void*_Nonnull key, RustBuffer nonce, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarlocalkey_unwrap_key(void*_Nonnull ptr, RustBuffer alg, RustBuffer ciphertext, RustBuffer tag, RustBuffer nonce, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_encryptedbuffer(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_encryptedbuffer_nonce(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_encryptedbuffer_tag(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext_tag(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_localkeyfactory(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_localkeyfactory_new(RustCallStatus *_Nonnull out_status + +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_generate(void*_Nonnull ptr, RustBuffer alg, int8_t ephemeral, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_seed(void*_Nonnull ptr, RustBuffer alg, RustBuffer seed, RustBuffer method, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_jwk_slice(void*_Nonnull ptr, RustBuffer jwk, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_jwk(void*_Nonnull ptr, RustBuffer jwk, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_public_bytes(void*_Nonnull ptr, RustBuffer alg, RustBuffer bytes, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_localkeyfactory_from_secret_bytes(void*_Nonnull ptr, RustBuffer alg, RustBuffer bytes, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarstoremanager(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_askarstoremanager_new(RustCallStatus *_Nonnull out_status + +); +RustBuffer uniffi_aries_askar_fn_method_askarstoremanager_generate_raw_store_key(void*_Nonnull ptr, RustBuffer seed, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstoremanager_set_default_logger(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstoremanager_provision(void*_Nonnull ptr, RustBuffer spec_uri, RustBuffer key_method, RustBuffer pass_key, RustBuffer profile, int8_t recreate, size_t uniffi_executor, UniFfiFutureCallbackRustArcPtrAskarStore _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstoremanager_open(void*_Nonnull ptr, RustBuffer spec_uri, RustBuffer key_method, RustBuffer pass_key, RustBuffer profile, size_t uniffi_executor, UniFfiFutureCallbackRustArcPtrAskarStore _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_method_askarstoremanager_remove(void*_Nonnull ptr, RustBuffer spec_uri, size_t uniffi_executor, UniFfiFutureCallbackInt8 _Nonnull uniffi_callback, void* _Nonnull uniffi_callback_data, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarcrypto(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_askarcrypto_new(RustCallStatus *_Nonnull out_status + +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_random_nonce(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_crypto_box(void*_Nonnull ptr, void*_Nonnull receiver_key, void*_Nonnull sender_key, RustBuffer message, RustBuffer nonce, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_box_open(void*_Nonnull ptr, void*_Nonnull receiver_key, void*_Nonnull sender_key, RustBuffer message, RustBuffer nonce, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_box_seal(void*_Nonnull ptr, void*_Nonnull receiver_key, RustBuffer message, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarcrypto_box_seal_open(void*_Nonnull ptr, void*_Nonnull receiver_key, RustBuffer ciphertext, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarecdhes(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_askarecdhes_new(RustBuffer alg_id, RustBuffer apu, RustBuffer apv, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdhes_derive_key(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, int8_t receive, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdhes_encrypt_direct(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, RustBuffer message, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarecdhes_decrypt_direct(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, RustBuffer ciphertext, RustBuffer tag, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdhes_sender_wrap_key(void*_Nonnull ptr, RustBuffer wrap_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, void*_Nonnull cek, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdhes_receiver_unwrap_key(void*_Nonnull ptr, RustBuffer wrap_alg, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull receiver_key, RustBuffer ciphertext, RustBuffer nonce, RustBuffer tag, RustCallStatus *_Nonnull out_status +); +void uniffi_aries_askar_fn_free_askarecdh1pu(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_constructor_askarecdh1pu_new(RustBuffer alg_id, RustBuffer apu, RustBuffer apv, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdh1pu_derive_key(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, RustBuffer cc_tag, int8_t receive, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdh1pu_encrypt_direct(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, RustBuffer message, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +RustBuffer uniffi_aries_askar_fn_method_askarecdh1pu_decrypt_direct(void*_Nonnull ptr, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, RustBuffer ciphertext, RustBuffer tag, RustBuffer nonce, RustBuffer aad, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdh1pu_sender_wrap_key(void*_Nonnull ptr, RustBuffer wrap_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, void*_Nonnull cek, RustBuffer cc_tag, RustCallStatus *_Nonnull out_status +); +void*_Nonnull uniffi_aries_askar_fn_method_askarecdh1pu_receiver_unwrap_key(void*_Nonnull ptr, RustBuffer wrap_alg, RustBuffer enc_alg, void*_Nonnull ephemeral_key, void*_Nonnull sender_key, void*_Nonnull receiver_key, RustBuffer ciphertext, RustBuffer cc_tag, RustBuffer nonce, RustBuffer tag, RustCallStatus *_Nonnull out_status +); +RustBuffer ffi_aries_askar_rustbuffer_alloc(int32_t size, RustCallStatus *_Nonnull out_status +); +RustBuffer ffi_aries_askar_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +void ffi_aries_askar_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +RustBuffer ffi_aries_askar_rustbuffer_reserve(RustBuffer buf, int32_t additional, RustCallStatus *_Nonnull out_status +); +uint16_t uniffi_aries_askar_checksum_method_askarentry_category(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarentry_name(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarentry_tags(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarentry_value(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_algorithm(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_metadata(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_name(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_is_local(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_tags(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarkeyentry_load_local_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarscan_next(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarscan_fetch_all(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_close(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_count(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_fetch(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_fetch_all(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_update(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_remove_all(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_insert_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_fetch_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_fetch_all_keys(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_remove_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarsession_update_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_get_profile_name(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_create_profile(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_remove_profile(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_rekey(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_close(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_scan(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstore_session(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_public_bytes(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_secret_bytes(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_key_exchange(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_algorithm(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_public(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_secret(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprint(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprints(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_convert_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_params(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_padding(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_random_nonce(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_encrypt(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_aead_decrypt(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_sign_message(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_verify_signature(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_wrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarlocalkey_unwrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext(void + +); +uint16_t uniffi_aries_askar_checksum_method_encryptedbuffer_nonce(void + +); +uint16_t uniffi_aries_askar_checksum_method_encryptedbuffer_tag(void + +); +uint16_t uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext_tag(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_generate(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_seed(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk_slice(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_public_bytes(void + +); +uint16_t uniffi_aries_askar_checksum_method_localkeyfactory_from_secret_bytes(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_generate_raw_store_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_set_default_logger(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_provision(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_open(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarstoremanager_remove(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_random_nonce(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_crypto_box(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_box_open(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_box_seal(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarcrypto_box_seal_open(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_derive_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_encrypt_direct(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_decrypt_direct(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_sender_wrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdhes_receiver_unwrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_derive_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_encrypt_direct(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_decrypt_direct(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_sender_wrap_key(void + +); +uint16_t uniffi_aries_askar_checksum_method_askarecdh1pu_receiver_unwrap_key(void + +); +uint16_t uniffi__checksum_constructor_localkeyfactory_new(void + +); +uint16_t uniffi__checksum_constructor_askarstoremanager_new(void + +); +uint16_t uniffi__checksum_constructor_askarcrypto_new(void + +); +uint16_t uniffi__checksum_constructor_askarecdhes_new(void + +); +uint16_t uniffi__checksum_constructor_askarecdh1pu_new(void + +); +void uniffi_foreign_executor_callback_set(UniFfiForeignExecutorCallback _Nonnull callback +); +uint32_t ffi_aries_askar_uniffi_contract_version(void + +); \ No newline at end of file diff --git a/wrappers/kotlin/src/nativeInterop/cinterop/uniffi.def b/wrappers/kotlin/src/nativeInterop/cinterop/uniffi.def new file mode 100644 index 00000000..52b2d3b3 --- /dev/null +++ b/wrappers/kotlin/src/nativeInterop/cinterop/uniffi.def @@ -0,0 +1,18 @@ +package = aries_askar +headers = aries_askar.h +headerFilter = ** + +staticLibraries.ios = libaries_askar.a +libraryPaths.ios_x64 = ../../target/x86_64-apple-ios/release +libraryPaths.ios_arm64 = ../../target/aarch64-apple-ios/release +libraryPaths.ios_simulator_arm64 = ../../target/aarch64-apple-ios-sim/release + +staticLibraries.osx = libaries_askar.a +libraryPaths.macos_x64 = ../../target/x86_64-apple-darwin/release ../../target/aarch64-apple-darwin/release +libraryPaths.osx = ../../target/x86_64-apple-darwin/release ../../target/aarch64-apple-darwin/release + +staticLibraries.android = libaries_askar.a +libraryPaths.android_x64 = ../../target/i686-linux-android/release +libraryPaths.android_arm64 = ../../target/aarch64-linux-android/release +libraryPaths.android_arm32 = ../../target/armv7-linux-androideabi/release +libraryPaths.android_x86 = ../../target/x86_64-linux-android/release \ No newline at end of file diff --git a/wrappers/kotlin/src/nativeTest/kotlin/AskarTest.native.kt b/wrappers/kotlin/src/nativeTest/kotlin/AskarTest.native.kt new file mode 100644 index 00000000..14f471ec --- /dev/null +++ b/wrappers/kotlin/src/nativeTest/kotlin/AskarTest.native.kt @@ -0,0 +1,3 @@ +actual fun databaseUri(): String { + return "tmp" +} \ No newline at end of file diff --git a/wrappers/python/aries_askar/bindings/__init__.py b/wrappers/python/aries_askar/bindings/__init__.py index 4b6f6684..5ee741dd 100644 --- a/wrappers/python/aries_askar/bindings/__init__.py +++ b/wrappers/python/aries_askar/bindings/__init__.py @@ -3,10 +3,9 @@ import asyncio import json import logging -import sys -from ctypes import POINTER, byref, c_char_p, c_int8, c_int32, c_int64 -from typing import Optional, Union +from ctypes import POINTER, byref, c_int8, c_int32, c_int64 +from typing import Optional, Sequence, Union from ..types import EntryOperation, KeyAlg, SeedMethod @@ -28,6 +27,7 @@ ScanHandle, SessionHandle, StoreHandle, + StringListHandle, ) @@ -126,7 +126,7 @@ async def store_create_profile(handle: StoreHandle, name: str = None) -> str: async def store_get_profile_name(handle: StoreHandle) -> str: - """Get the name of the default Store instance profile.""" + """Get the name of the selected Store instance profile.""" return str( await invoke_async( "askar_store_get_profile_name", @@ -137,6 +137,28 @@ async def store_get_profile_name(handle: StoreHandle) -> str: ) +async def store_get_default_profile(handle: StoreHandle) -> str: + """Get the name of the default Store instance profile.""" + return str( + await invoke_async( + "askar_store_get_default_profile", + (StoreHandle,), + handle, + return_type=StrBuffer, + ) + ) + + +async def store_set_default_profile(handle: StoreHandle, profile: str): + """Set the name of the default Store instance profile.""" + await invoke_async( + "askar_store_set_default_profile", + (StoreHandle, FfiStr), + handle, + profile, + ) + + async def store_remove_profile(handle: StoreHandle, name: str) -> bool: """Remove an existing profile from a Store.""" return ( @@ -151,6 +173,35 @@ async def store_remove_profile(handle: StoreHandle, name: str) -> bool: ) +async def store_list_profiles(handle: StoreHandle) -> Sequence[str]: + """List the profile identifiers present in a Store.""" + handle = await invoke_async( + "askar_store_list_profiles", + (StoreHandle,), + handle, + return_type=StringListHandle, + ) + count = c_int32() + invoke( + "askar_string_list_count", + (StringListHandle, POINTER(c_int32)), + handle, + byref(count), + ) + ret = [] + for idx in range(count.value): + buf = StrBuffer() + invoke( + "askar_string_list_get_item", + (StringListHandle, c_int32, POINTER(StrBuffer)), + handle, + idx, + byref(buf), + ) + ret.append(str(buf)) + return ret + + async def store_rekey( handle: StoreHandle, key_method: str = None, @@ -167,6 +218,26 @@ async def store_rekey( ) +async def store_copy( + handle: StoreHandle, + target_uri: str, + key_method: str = None, + pass_key: str = None, + recreate: bool = False, +) -> StoreHandle: + """Copy the Store contents to a new location.""" + return await invoke_async( + "askar_store_copy", + (StoreHandle, FfiStr, FfiStr, FfiStr, c_int8), + handle, + target_uri, + key_method and key_method.lower(), + pass_key, + recreate, + return_type=StoreHandle, + ) + + async def store_remove(uri: str) -> bool: """Remove an existing Store, if any.""" return ( @@ -196,7 +267,7 @@ async def session_start( async def session_count( - handle: SessionHandle, category: str, tag_filter: Union[str, dict] = None + handle: SessionHandle, category: str = None, tag_filter: Union[str, dict] = None ) -> int: """Count rows in the Store.""" return int( @@ -228,7 +299,7 @@ async def session_fetch( async def session_fetch_all( handle: SessionHandle, - category: str, + category: str = None, tag_filter: Union[str, dict] = None, limit: int = None, for_update: bool = False, @@ -248,7 +319,7 @@ async def session_fetch_all( async def session_remove_all( handle: SessionHandle, - category: str, + category: str = None, tag_filter: Union[str, dict] = None, ) -> int: """Remove all matching rows in the Store.""" @@ -374,7 +445,7 @@ async def session_remove_key(handle: SessionHandle, name: str): async def scan_start( handle: StoreHandle, profile: Optional[str], - category: str, + category: str = None, tag_filter: Union[str, dict] = None, offset: int = None, limit: int = None, diff --git a/wrappers/python/aries_askar/bindings/handle.py b/wrappers/python/aries_askar/bindings/handle.py index 8f6b0c25..0065f078 100644 --- a/wrappers/python/aries_askar/bindings/handle.py +++ b/wrappers/python/aries_askar/bindings/handle.py @@ -246,3 +246,9 @@ class LocalKeyHandle(ArcHandle): """Handle for an active LocalKey instance.""" _dtor_ = "askar_key_free" + + +class StringListHandle(ArcHandle): + """Handle for an active string list instance.""" + + _dtor_ = "askar_string_list_free" diff --git a/wrappers/python/aries_askar/bindings/lib.py b/wrappers/python/aries_askar/bindings/lib.py index 8eb64aab..6c13cfb7 100644 --- a/wrappers/python/aries_askar/bindings/lib.py +++ b/wrappers/python/aries_askar/bindings/lib.py @@ -76,11 +76,17 @@ def _struct_dtor(ctype: type, address: int, dtor: Callable): def finalize_struct(instance, ctype): + """Attach a struct destructor.""" finalize( instance, _struct_dtor, ctype, addressof(instance), instance.__class__._cleanup ) +def keepalive(instance, *depend): + """Ensure that dependencies are kept alive as long as the instance.""" + finalize(instance, lambda *_args: None, *depend) + + class LibLoad: def __init__(self, lib_name: str): """Load the CDLL library. @@ -199,14 +205,20 @@ def _enabled_cb(_context, level: int) -> bool: def invoke(self, name, argtypes, *args): """Perform a synchronous library function call.""" method = self.method(name, argtypes, restype=c_int64) + if not method: + raise ValueError(f"FFI method not found: {name}") args = _load_method_arguments(name, argtypes, args) result = method(*args) if result: raise self.get_current_error(True) - def invoke_async(self, name: str, argtypes, *args, return_type=None): + def invoke_async( + self, name: str, argtypes, *args, return_type=None + ) -> asyncio.Future: """Perform an asynchronous library function call.""" method = self.method(name, (*argtypes, c_void_p, c_int64), restype=c_int64) + if not method: + raise ValueError(f"FFI method not found: {name}") loop = asyncio.get_event_loop() fut = loop.create_future() cb_info = self._cfuncs.get(name) @@ -310,6 +322,8 @@ def _wait_callbacks(cb): self._lib_name, ) + self.method("askar_terminate", None, restype=None)() + class Lib: """The loaded library instance.""" @@ -362,7 +376,7 @@ def set_max_log_level(self, level: Union[str, int, None]): def version(self) -> str: """Get the version of the installed library.""" return str( - self.loaded._method( + self.loaded.method( "askar_version", None, restype=StrBuffer, @@ -396,7 +410,7 @@ def __bytes__(self) -> bytes: return bytes(self.array) def __len__(self) -> int: - return int(self.len) + return self.len.value @property def array(self) -> Array: @@ -454,7 +468,6 @@ class ByteBuffer(Structure): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._a = addressof(self) finalize_struct(self, RawBuffer) @property @@ -468,8 +481,7 @@ def array(self) -> Array: @property def view(self) -> memoryview: m = memoryview(self.array) - # ensure self stays alive until the view is dropped - finalize(m, lambda _: (), self) + keepalive(m, self) return m def __bytes__(self) -> bytes: diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index 39d1a09c..994056ca 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -69,8 +69,8 @@ def get_secret_bytes(self) -> bytes: def get_jwk_public(self, alg: Union[str, KeyAlg] = None) -> str: return bindings.key_get_jwk_public(self._handle, alg) - def get_jwk_secret(self) -> str: - return str(bindings.key_get_jwk_secret(self._handle)) + def get_jwk_secret(self) -> bytes: + return bytes(bindings.key_get_jwk_secret(self._handle)) def get_jwk_thumbprint(self, alg: Union[str, KeyAlg] = None) -> str: return bindings.key_get_jwk_thumbprint(self._handle, alg) diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index 67b0565a..f92002a6 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -3,13 +3,11 @@ import json from typing import Optional, Sequence, Union -from weakref import ref from cached_property import cached_property from . import bindings from .bindings import ( - ByteBuffer, EntryListHandle, KeyEntryListHandle, ScanHandle, @@ -61,8 +59,8 @@ def tags(self) -> dict: """Accessor for the entry tags.""" return self._list.get_tags(self._pos) - def keys(self): - """Mapping keys.""" + def keys(self) -> Sequence[str]: + """Accessor for the list of mapping keys.""" return Entry._KEYS def __getitem__(self, key): @@ -71,6 +69,10 @@ def __getitem__(self, key): return getattr(self, key) return KeyError + def __hasitem__(self, key) -> bool: + """Check if a key is defined.""" + return key in Entry._KEYS + def __repr__(self) -> str: """Format entry handle as a string.""" return ( @@ -97,27 +99,35 @@ def handle(self) -> EntryListHandle: return self._handle def __getitem__(self, index) -> Entry: + """Fetch an entry by index.""" if not isinstance(index, int) or index < 0 or index >= self._len: return IndexError() return Entry(self._handle, index) def __iter__(self): + """Iterate the entry list.""" return IterEntryList(self) def __len__(self) -> int: + """Accessor for the length of the list.""" return self._len def __repr__(self) -> str: + """Format entry list as a string.""" return f"" class IterEntryList: + """Iterator for the records in an entry list.""" + def __init__(self, list: EntryList): + """Create a new entry list iterator.""" self._handle = list._handle self._len = list._len self._pos = 0 def __next__(self): + """Fetch the next entry from the iterator.""" if self._pos < self._len: entry = Entry(self._handle, self._pos) self._pos += 1 @@ -187,29 +197,37 @@ def handle(self) -> KeyEntryListHandle: return self._handle def __getitem__(self, index) -> KeyEntry: + """Fetch the key entry at a specific index.""" if not isinstance(index, int) or index < 0 or index >= self._len: return IndexError() return KeyEntry(self._handle, index) def __iter__(self): + """Create an iterator over the key entry list.""" return IterKeyEntryList(self) def __len__(self) -> int: + """Accessor for the number of key entries.""" return self._len def __repr__(self) -> str: + """Format this key entry list as a string.""" return ( f"" ) class IterKeyEntryList: + """Iterator for a list of key entries.""" + def __init__(self, list: KeyEntryList): + """Create a new key entry iterator.""" self._handle = list._handle self._len = list._len self._pos = 0 def __next__(self): + """Fetch the next key entry from the iterator.""" if self._pos < self._len: entry = KeyEntry(self._handle, self._pos) self._pos += 1 @@ -225,7 +243,7 @@ def __init__( self, store: "Store", profile: Optional[str], - category: Union[str, bytes], + category: Optional[str], tag_filter: Union[str, dict] = None, offset: int = None, limit: int = None, @@ -241,9 +259,11 @@ def handle(self) -> ScanHandle: return self._handle def __aiter__(self): + """Async iterator for the scan results.""" return self async def __anext__(self): + """Fetch the next scan result during async iteration.""" if self._handle is None: (store, profile, category, tag_filter, offset, limit) = self._params self._params = None @@ -266,12 +286,14 @@ async def __anext__(self): self._buffer = iter(EntryList(list_handle)) if list_handle else None async def fetch_all(self) -> Sequence[Entry]: + """Fetch all remaining rows.""" rows = [] async for row in self: rows.append(row) return rows def __repr__(self) -> str: + """Format the scan instance as a string.""" return f"" @@ -309,6 +331,7 @@ async def provision( profile: str = None, recreate: bool = False, ) -> "Store": + """Provision a new store.""" return Store( await bindings.store_provision( uri, key_method, pass_key, profile, recreate @@ -325,53 +348,97 @@ async def open( *, profile: str = None, ) -> "Store": + """Open an existing store.""" return Store(await bindings.store_open(uri, key_method, pass_key, profile), uri) @classmethod async def remove(cls, uri: str) -> bool: + """Remove an existing store.""" return await bindings.store_remove(uri) async def __aenter__(self) -> "Session": + """Start a new session when used as an async context.""" if not self._opener: self._opener = OpenSession(self._handle, None, False) return await self._opener.__aenter__() async def __aexit__(self, exc_type, exc, tb): + """Async context termination.""" opener = self._opener self._opener = None return await opener.__aexit__(exc_type, exc, tb) async def create_profile(self, name: str = None) -> str: + """ + Create a new profile in the store. + + Returns the name of the profile, which is automatically + generated if not provided. + """ return await bindings.store_create_profile(self._handle, name) async def get_profile_name(self) -> str: + """Accessor for the currently selected profile name.""" return await bindings.store_get_profile_name(self._handle) + async def get_default_profile(self) -> str: + """Accessor for the default profile name when the store is opened.""" + return await bindings.store_get_default_profile(self._handle) + + async def set_default_profile(self, profile: str): + """Setter for the default profile name when the store is opened.""" + await bindings.store_set_default_profile(self._handle, profile) + async def remove_profile(self, name: str) -> bool: + """Remove a profile from the store.""" return await bindings.store_remove_profile(self._handle, name) + async def list_profiles(self) -> Sequence[str]: + """List the profile identifiers present in the store.""" + return await bindings.store_list_profiles(self._handle) + async def rekey( self, key_method: str = None, pass_key: str = None, ): + """Update the master encryption key of the store.""" await bindings.store_rekey(self._handle, key_method, pass_key) + async def copy_to( + self, + target_uri: str, + key_method: str = None, + pass_key: str = None, + *, + recreate: bool = False, + ) -> "Store": + """Copy the store contents to a new location.""" + return Store( + await bindings.store_copy( + self._handle, target_uri, key_method, pass_key, recreate + ), + target_uri, + ) + def scan( self, - category: str, + category: str = None, tag_filter: Union[str, dict] = None, offset: int = None, limit: int = None, profile: str = None, ) -> Scan: + """Start a new record scan.""" return Scan(self, profile, category, tag_filter, offset, limit) def session(self, profile: str = None) -> "OpenSession": + """Open a new session on the store without starting a transaction.""" return OpenSession(self._handle, profile, False) - def transaction(self, profile: str = None) -> "OpenSession": - return OpenSession(self._handle, profile, True) + def transaction(self, profile: str = None, *, autocommit=None) -> "OpenSession": + """Open a new transactional session on the store.""" + return OpenSession(self._handle, profile, True, autocommit) async def close(self, *, remove: bool = False) -> bool: """Close and free the pool instance.""" @@ -385,17 +452,35 @@ async def close(self, *, remove: bool = False) -> bool: return False def __repr__(self) -> str: + """Format the store instance as a string.""" return f"" class Session: """An opened Session instance.""" - def __init__(self, store: StoreHandle, handle: SessionHandle, is_txn: bool): + def __init__( + self, + store: StoreHandle, + handle: SessionHandle, + is_txn: bool = False, + autocommit: Optional[bool] = None, + ): """Initialize the Session instance.""" self._store = store self._handle = handle self._is_txn = is_txn + self._autocommit = autocommit or False + + @property + def autocommit(self) -> bool: + """Determine if autocommit is enabled for a transaction.""" + return self._autocommit + + @autocommit.setter + def autocommit(self, val: bool): + """Set the autocommit flag for a transaction.""" + self._autocommit = val or False @property def is_transaction(self) -> bool: @@ -407,7 +492,10 @@ def handle(self) -> SessionHandle: """Accessor for the SessionHandle instance.""" return self._handle - async def count(self, category: str, tag_filter: Union[str, dict] = None) -> int: + async def count( + self, category: str = None, tag_filter: Union[str, dict] = None + ) -> int: + """Count the records matching a category and tag filter.""" if not self._handle: raise AskarError(AskarErrorCode.WRAPPER, "Cannot count from closed session") return await bindings.session_count(self._handle, category, tag_filter) @@ -415,6 +503,7 @@ async def count(self, category: str, tag_filter: Union[str, dict] = None) -> int async def fetch( self, category: str, name: str, *, for_update: bool = False ) -> Optional[Entry]: + """Fetch a record from the store by category and name.""" if not self._handle: raise AskarError(AskarErrorCode.WRAPPER, "Cannot fetch from closed session") result_handle = await bindings.session_fetch( @@ -424,12 +513,13 @@ async def fetch( async def fetch_all( self, - category: str, + category: str = None, tag_filter: Union[str, dict] = None, limit: int = None, *, for_update: bool = False, ) -> EntryList: + """Fetch all records matching a category and tag filter.""" if not self._handle: raise AskarError(AskarErrorCode.WRAPPER, "Cannot fetch from closed session") return EntryList( @@ -447,6 +537,7 @@ async def insert( expiry_ms: int = None, value_json=None, ): + """Insert a new record into the store.""" if not self._handle: raise AskarError(AskarErrorCode.WRAPPER, "Cannot update closed session") if value is None and value_json is not None: @@ -464,6 +555,7 @@ async def replace( expiry_ms: int = None, value_json=None, ): + """Replace a record in the store matching a category and name.""" if not self._handle: raise AskarError(AskarErrorCode.WRAPPER, "Cannot update closed session") if value is None and value_json is not None: @@ -477,6 +569,7 @@ async def remove( category: str, name: str, ): + """Remove a record by category and name.""" if not self._handle: raise AskarError(AskarErrorCode.WRAPPER, "Cannot update closed session") await bindings.session_update( @@ -485,9 +578,10 @@ async def remove( async def remove_all( self, - category: str, + category: str = None, tag_filter: Union[str, dict] = None, ) -> int: + """Remove all records matching a category and tag filter.""" if not self._handle: raise AskarError( AskarErrorCode.WRAPPER, "Cannot remove all for closed session" @@ -503,6 +597,7 @@ async def insert_key( tags: dict = None, expiry_ms: int = None, ) -> str: + """Insert a new key into the store.""" if not self._handle: raise AskarError( AskarErrorCode.WRAPPER, "Cannot insert key with closed session" @@ -516,6 +611,7 @@ async def insert_key( async def fetch_key( self, name: str, *, for_update: bool = False ) -> Optional[KeyEntry]: + """Fetch a key in the store by name.""" if not self._handle: raise AskarError( AskarErrorCode.WRAPPER, "Cannot fetch key from closed session" @@ -534,6 +630,7 @@ async def fetch_all_keys( limit: int = None, for_update: bool = False, ) -> KeyEntryList: + """Fetch a set of keys in the store..""" if not self._handle: raise AskarError( AskarErrorCode.WRAPPER, "Cannot fetch key from closed session" @@ -551,6 +648,7 @@ async def update_key( tags: dict = None, expiry_ms: int = None, ): + """Update details of a key in the store.""" if not self._handle: raise AskarError( AskarErrorCode.WRAPPER, "Cannot update key with closed session" @@ -558,6 +656,7 @@ async def update_key( await bindings.session_update_key(self._handle, name, metadata, tags, expiry_ms) async def remove_key(self, name: str): + """Remove a key from the store.""" if not self._handle: raise AskarError( AskarErrorCode.WRAPPER, "Cannot remove key with closed session" @@ -565,6 +664,7 @@ async def remove_key(self, name: str): await bindings.session_remove_key(self._handle, name) async def commit(self): + """Commit the current transaction and close the session.""" if not self._is_txn: raise AskarError(AskarErrorCode.WRAPPER, "Session is not a transaction") if not self._handle: @@ -573,6 +673,7 @@ async def commit(self): self._handle = None async def rollback(self): + """Roll back the current transaction and close the session.""" if not self._is_txn: raise AskarError(AskarErrorCode.WRAPPER, "Session is not a transaction") if not self._handle: @@ -583,27 +684,44 @@ async def rollback(self): self._handle = None async def close(self): + """Close the session without specifying the commit behaviour.""" if self._handle: - await self._handle.close(commit=False) + await self._handle.close(commit=self._autocommit) self._handle = None def __repr__(self) -> str: - return f"" + """Format a string representation of the session.""" + return ( + f"" + ) class OpenSession: - def __init__(self, store: StoreHandle, profile: Optional[str], is_txn: bool): + """A pending session instance.""" + + def __init__( + self, + store: StoreHandle, + profile: Optional[str], + is_txn: bool, + autocommit: Optional[bool] = None, + ): """Initialize the OpenSession instance.""" self._store = store self._profile = profile self._is_txn = is_txn + self._autocommit = autocommit self._session: Session = None @property def is_transaction(self) -> bool: + """Determine if this instance would begin a transaction.""" return self._is_txn async def _open(self) -> Session: + """Open this pending session.""" if not self._store: raise AskarError( AskarErrorCode.WRAPPER, "Cannot start session from closed store" @@ -614,16 +732,22 @@ async def _open(self) -> Session: self._store, await bindings.session_start(self._store, self._profile, self._is_txn), self._is_txn, + self._autocommit, ) def __await__(self) -> Session: + """Open this pending session.""" return self._open().__await__() async def __aenter__(self) -> Session: + """Use this pending session as an async context manager, opening the session.""" self._session = await self._open() return self._session async def __aexit__(self, exc_type, exc, tb): + """Terminate the async context and close the session.""" session = self._session self._session = None + if exc: + session.autocommit = False await session.close() diff --git a/wrappers/python/aries_askar/types.py b/wrappers/python/aries_askar/types.py index 03bc688d..ac212c59 100644 --- a/wrappers/python/aries_askar/types.py +++ b/wrappers/python/aries_askar/types.py @@ -18,6 +18,7 @@ class KeyAlg(Enum): X25519 = "x25519" K256 = "k256" P256 = "p256" + P384 = "p384" @classmethod def from_key_alg(cls, alg: str) -> Optional["KeyAlg"]: diff --git a/wrappers/python/aries_askar/version.py b/wrappers/python/aries_askar/version.py index b4d49e71..a3b05cb0 100644 --- a/wrappers/python/aries_askar/version.py +++ b/wrappers/python/aries_askar/version.py @@ -1,3 +1,3 @@ """aries_askar library wrapper version.""" -__version__ = "0.2.5" +__version__ = "0.2.9" diff --git a/wrappers/python/tests/test_keys.py b/wrappers/python/tests/test_keys.py index 774b2a4f..49a79f9e 100644 --- a/wrappers/python/tests/test_keys.py +++ b/wrappers/python/tests/test_keys.py @@ -1,5 +1,7 @@ import json +import pytest + from aries_askar import ( KeyAlg, Key, @@ -73,3 +75,32 @@ def test_ed25519(): jwk = json.loads(key.get_jwk_public()) assert jwk["kty"] == "OKP" assert jwk["crv"] == "Ed25519" + + jwk = json.loads(key.get_jwk_secret()) + assert jwk["kty"] == "OKP" + assert jwk["crv"] == "Ed25519" + + +@pytest.mark.parametrize( + "key_alg", + [KeyAlg.K256, KeyAlg.P256, KeyAlg.P384], +) +def test_ec_curves(key_alg: KeyAlg): + key = Key.generate(key_alg) + assert key.algorithm == key_alg + message = b"test message" + sig = key.sign_message(message) + assert key.verify_signature(message, sig) + + jwk = json.loads(key.get_jwk_public()) + assert jwk["kty"] == "EC" + assert jwk["crv"] + assert jwk["x"] + assert jwk["y"] + + jwk = json.loads(key.get_jwk_secret()) + assert jwk["kty"] == "EC" + assert jwk["crv"] + assert jwk["x"] + assert jwk["y"] + assert jwk["d"] diff --git a/wrappers/python/tests/test_store.py b/wrappers/python/tests/test_store.py index 684b9d45..9aedda2e 100644 --- a/wrappers/python/tests/test_store.py +++ b/wrappers/python/tests/test_store.py @@ -36,7 +36,6 @@ async def store() -> Store: @mark.asyncio async def test_insert_update(store: Store): - async with store as session: # Insert a new entry await session.insert( @@ -63,6 +62,19 @@ async def test_insert_update(store: Store): ) assert len(found) == 1 and dict(found[0]) == TEST_ENTRY + # Update an entry (outside of a transaction) + upd_entry = TEST_ENTRY.copy() + upd_entry["value"] = b"new_value" + upd_entry["tags"] = {"upd": "tagval"} + await session.replace( + TEST_ENTRY["category"], + TEST_ENTRY["name"], + upd_entry["value"], + upd_entry["tags"], + ) + found = await session.fetch(TEST_ENTRY["category"], TEST_ENTRY["name"]) + assert dict(found) == upd_entry + # Remove entry await session.remove(TEST_ENTRY["category"], TEST_ENTRY["name"]) found = await session.fetch(TEST_ENTRY["category"], TEST_ENTRY["name"]) @@ -71,7 +83,6 @@ async def test_insert_update(store: Store): @mark.asyncio async def test_remove_all(store: Store): - async with store as session: # Insert a new entry await session.insert( @@ -95,7 +106,6 @@ async def test_remove_all(store: Store): @mark.asyncio async def test_scan(store: Store): - async with store as session: await session.insert( TEST_ENTRY["category"], @@ -110,11 +120,22 @@ async def test_scan(store: Store): ).fetch_all() assert len(rows) == 1 and dict(rows[0]) == TEST_ENTRY + # Scan entries with non-matching category + rows = await store.scan("not the category").fetch_all() + assert len(rows) == 0 + + # Scan entries with non-matching tag filter + rows = await store.scan(TEST_ENTRY["category"], {"~plaintag": "X"}).fetch_all() + assert len(rows) == 0 + + # Scan entries with no category filter + rows = await store.scan(None, {"~plaintag": "a", "enctag": "b"}).fetch_all() + assert len(rows) == 1 and dict(rows[0]) == TEST_ENTRY + @mark.asyncio async def test_txn_basic(store: Store): async with store.transaction() as txn: - # Insert a new entry await txn.insert( TEST_ENTRY["category"], @@ -146,6 +167,42 @@ async def test_txn_basic(store: Store): assert dict(found) == TEST_ENTRY +@mark.asyncio +async def test_txn_autocommit(store: Store): + with raises(Exception): + async with store.transaction(autocommit=True) as txn: + # Insert a new entry + await txn.insert( + TEST_ENTRY["category"], + TEST_ENTRY["name"], + TEST_ENTRY["value"], + TEST_ENTRY["tags"], + ) + + found = await txn.fetch(TEST_ENTRY["category"], TEST_ENTRY["name"]) + assert dict(found) == TEST_ENTRY + + raise Exception() + + # Row should not have been inserted + async with store as session: + assert (await session.fetch(TEST_ENTRY["category"], TEST_ENTRY["name"])) is None + + async with store.transaction(autocommit=True) as txn: + # Insert a new entry + await txn.insert( + TEST_ENTRY["category"], + TEST_ENTRY["name"], + TEST_ENTRY["value"], + TEST_ENTRY["tags"], + ) + + # Transaction should have been committed + async with store as session: + found = await session.fetch(TEST_ENTRY["category"], TEST_ENTRY["name"]) + assert dict(found) == TEST_ENTRY + + @mark.asyncio async def test_txn_contention(store: Store): async with store.transaction() as txn: @@ -236,6 +293,10 @@ async def test_profile(store: Store): profile = await store.create_profile() + active_profile = await store.get_profile_name() + assert (await store.get_default_profile()) == active_profile + assert set(await store.list_profiles()) == {active_profile, profile} + async with store.session(profile) as session: # Should not find previously stored record assert ( @@ -305,3 +366,29 @@ async def test_profile(store: Store): TEST_ENTRY["category"], {"~plaintag": "a", "enctag": "b"} ) ) == 0 + + assert (await store.get_default_profile()) != profile + await store.set_default_profile(profile) + assert (await store.get_default_profile()) == profile + + +@mark.asyncio +async def test_copy(store: Store): + async with store as session: + # Insert a new entry + await session.insert( + TEST_ENTRY["category"], + TEST_ENTRY["name"], + TEST_ENTRY["value"], + TEST_ENTRY["tags"], + ) + profiles = await store.list_profiles() + + copied = await store.copy_to("sqlite://:memory:", "raw", raw_key()) + assert profiles == await copied.list_profiles() + await copied.close(remove=True) + + async with store as session: + entries = await session.fetch_all(TEST_ENTRY["category"]) + assert len(entries) == 1 + assert entries[0].name == TEST_ENTRY["name"] diff --git a/wrappers/swift/Askar/.gitignore b/wrappers/swift/Askar/.gitignore new file mode 100644 index 00000000..62b7e8f0 --- /dev/null +++ b/wrappers/swift/Askar/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm +.netrc diff --git a/wrappers/swift/Askar/Package.swift b/wrappers/swift/Askar/Package.swift new file mode 100644 index 00000000..4bdf8e80 --- /dev/null +++ b/wrappers/swift/Askar/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.7 +import PackageDescription + +let package = Package( + name: "Askar", + platforms: [ + .macOS(.v10_15) + ], + products: [ + .library( + name: "Askar", + targets: ["Askar"]), + ], + dependencies: [ + ], + targets: [ + .target( + name: "Askar", + dependencies: ["aries_askarFFI"]), + .testTarget( + name: "AskarTests", + dependencies: ["Askar"]), + .binaryTarget( + name: "aries_askarFFI", + path: "../../../out/aries_askarFFI.xcframework"), + ] +) diff --git a/wrappers/swift/Askar/Sources/Askar/aries_askar.swift b/wrappers/swift/Askar/Sources/Askar/aries_askar.swift new file mode 100644 index 00000000..0d22e128 --- /dev/null +++ b/wrappers/swift/Askar/Sources/Askar/aries_askar.swift @@ -0,0 +1,4105 @@ +#if os(macOS) +import SystemConfiguration +#endif +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +import Foundation + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport(aries_askarFFI) +import aries_askarFFI +#endif + +fileprivate extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func from(_ ptr: UnsafeBufferPointer) -> RustBuffer { + try! rustCall { ffi_aries_askar_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { ffi_aries_askar_rustbuffer_free(self, $0) } + } +} + +fileprivate extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + +// Helper classes/extensions that don't change. +// Someday, this will be in a library of its own. + +fileprivate extension Data { + init(rustBuffer: RustBuffer) { + // TODO: This copies the buffer. Can we read directly from a + // Rust buffer? + self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len)) + } +} + +// Define reader functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. +// +// With external types, one swift source file needs to be able to call the read +// method on another source file's FfiConverter, but then what visibility +// should Reader have? +// - If Reader is fileprivate, then this means the read() must also +// be fileprivate, which doesn't work with external types. +// - If Reader is internal/public, we'll get compile errors since both source +// files will try define the same type. +// +// Instead, the read() method and these helper functions input a tuple of data + +fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) { + (data: data, offset: 0) +} + +// Reads an integer at the current offset, in big-endian order, and advances +// the offset on success. Throws if reading the integer would move the +// offset past the end of the buffer. +fileprivate func readInt(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset...size + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = reader.data[reader.offset] + reader.offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)}) + reader.offset = range.upperBound + return value.bigEndian +} + +// Reads an arbitrary number of bytes, to be used to read +// raw bytes, this is useful when lifting strings +fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array { + let range = reader.offset..<(reader.offset+count) + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + reader.data.copyBytes(to: buffer, from: range) + }) + reader.offset = range.upperBound + return value +} + +// Reads a float at the current offset. +fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float { + return Float(bitPattern: try readInt(&reader)) +} + +// Reads a float at the current offset. +fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double { + return Double(bitPattern: try readInt(&reader)) +} + +// Indicates if the offset has reached the end of the buffer. +fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool { + return reader.offset < reader.data.count +} + +// Define writer functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. See the above discussion on Readers for details. + +fileprivate func createWriter() -> [UInt8] { + return [] +} + +fileprivate func writeBytes(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 { + writer.append(contentsOf: byteArr) +} + +// Writes an integer in big-endian order. +// +// Warning: make sure what you are trying to write +// is in the correct type! +fileprivate func writeInt(_ writer: inout [UInt8], _ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) } +} + +fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) { + writeInt(&writer, value.bitPattern) +} + +fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) { + writeInt(&writer, value.bitPattern) +} + +// Protocol for types that transfer other types across the FFI. This is +// analogous go the Rust trait of the same name. +fileprivate protocol FfiConverter { + associatedtype FfiType + associatedtype SwiftType + + static func lift(_ value: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType + static func write(_ value: SwiftType, into buf: inout [UInt8]) +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { } + +extension FfiConverterPrimitive { + public static func lift(_ value: FfiType) throws -> SwiftType { + return value + } + + public static func lower(_ value: SwiftType) -> FfiType { + return value + } +} + +// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`. +// Used for complex types where it's hard to write a custom lift/lower. +fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} + +extension FfiConverterRustBuffer { + public static func lift(_ buf: RustBuffer) throws -> SwiftType { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + + public static func lower(_ value: SwiftType) -> RustBuffer { + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) + } +} +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +fileprivate enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +fileprivate let CALL_SUCCESS: Int8 = 0 +fileprivate let CALL_ERROR: Int8 = 1 +fileprivate let CALL_PANIC: Int8 = 2 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall(_ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: nil) +} + +private func rustCallWithError( + _ errorHandler: @escaping (RustBuffer) throws -> Error, + _ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: errorHandler) +} + +private func makeRustCall( + _ callback: (UnsafeMutablePointer) -> T, + errorHandler: ((RustBuffer) throws -> Error)? +) throws -> T { + uniffiEnsureInitialized() + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler) + return returnedVal +} + +private func uniffiCheckCallStatus( + callStatus: RustCallStatus, + errorHandler: ((RustBuffer) throws -> Error)? +) throws { + switch callStatus.code { + case CALL_SUCCESS: + return + + case CALL_ERROR: + if let errorHandler = errorHandler { + throw try errorHandler(callStatus.errorBuf) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.unexpectedRustCallError + } + + case CALL_PANIC: + // When the rust code sees a panic, it tries to construct a RustBuffer + // with the message. But if that code panics, then it just sends back + // an empty buffer. + if callStatus.errorBuf.len > 0 { + throw UniffiInternalError.rustPanic(try FfiConverterString.lift(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} + +// Public interface members begin here. + + +fileprivate struct FfiConverterUInt8: FfiConverterPrimitive { + typealias FfiType = UInt8 + typealias SwiftType = UInt8 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: UInt8, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +fileprivate struct FfiConverterInt32: FfiConverterPrimitive { + typealias FfiType = Int32 + typealias SwiftType = Int32 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int32, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +fileprivate struct FfiConverterInt64: FfiConverterPrimitive { + typealias FfiType = Int64 + typealias SwiftType = Int64 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int64, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +fileprivate struct FfiConverterBool : FfiConverter { + typealias FfiType = Int8 + typealias SwiftType = Bool + + public static func lift(_ value: Int8) throws -> Bool { + return value != 0 + } + + public static func lower(_ value: Bool) -> Int8 { + return value ? 1 : 0 + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Bool, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +fileprivate struct FfiConverterString: FfiConverter { + typealias SwiftType = String + typealias FfiType = RustBuffer + + public static func lift(_ value: RustBuffer) throws -> String { + defer { + value.deallocate() + } + if value.data == nil { + return String() + } + let bytes = UnsafeBufferPointer(start: value.data!, count: Int(value.len)) + return String(bytes: bytes, encoding: String.Encoding.utf8)! + } + + public static func lower(_ value: String) -> RustBuffer { + return value.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + let len: Int32 = try readInt(&buf) + return String(bytes: try readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)! + } + + public static func write(_ value: String, into buf: inout [UInt8]) { + let len = Int32(value.utf8.count) + writeInt(&buf, len) + writeBytes(&buf, value.utf8) + } +} + + +public protocol AskarCryptoProtocol { + func `boxOpen`(`receiverKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `message`: [UInt8], `nonce`: [UInt8]) throws -> [UInt8] + func `boxSeal`(`receiverKey`: AskarLocalKey, `message`: [UInt8]) throws -> [UInt8] + func `boxSealOpen`(`receiverKey`: AskarLocalKey, `ciphertext`: [UInt8]) throws -> [UInt8] + func `cryptoBox`(`receiverKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `message`: [UInt8], `nonce`: [UInt8]) throws -> [UInt8] + func `randomNonce`() throws -> [UInt8] + +} + +public class AskarCrypto: AskarCryptoProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init() { + self.init(unsafeFromRawPointer: try! rustCall() { + uniffi_aries_askar_fn_constructor_askarcrypto_new($0) +}) + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarcrypto(pointer, $0) } + } + + + + + + + public func `boxOpen`(`receiverKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `message`: [UInt8], `nonce`: [UInt8]) throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarcrypto_box_open(self.pointer, + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterTypeAskarLocalKey.lower(`senderKey`), + FfiConverterSequenceUInt8.lower(`message`), + FfiConverterSequenceUInt8.lower(`nonce`),$0 + ) +} + ) + } + + public func `boxSeal`(`receiverKey`: AskarLocalKey, `message`: [UInt8]) throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarcrypto_box_seal(self.pointer, + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterSequenceUInt8.lower(`message`),$0 + ) +} + ) + } + + public func `boxSealOpen`(`receiverKey`: AskarLocalKey, `ciphertext`: [UInt8]) throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarcrypto_box_seal_open(self.pointer, + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterSequenceUInt8.lower(`ciphertext`),$0 + ) +} + ) + } + + public func `cryptoBox`(`receiverKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `message`: [UInt8], `nonce`: [UInt8]) throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarcrypto_crypto_box(self.pointer, + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterTypeAskarLocalKey.lower(`senderKey`), + FfiConverterSequenceUInt8.lower(`message`), + FfiConverterSequenceUInt8.lower(`nonce`),$0 + ) +} + ) + } + + public func `randomNonce`() throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarcrypto_random_nonce(self.pointer, $0 + ) +} + ) + } +} + +public struct FfiConverterTypeAskarCrypto: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarCrypto + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarCrypto { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarCrypto, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarCrypto { + return AskarCrypto(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarCrypto) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarCrypto_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarCrypto { + return try FfiConverterTypeAskarCrypto.lift(pointer) +} + +public func FfiConverterTypeAskarCrypto_lower(_ value: AskarCrypto) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarCrypto.lower(value) +} + + +public protocol AskarEcdh1PUProtocol { + func `decryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: [UInt8], `tag`: [UInt8]?, `nonce`: [UInt8], `aad`: [UInt8]?) throws -> [UInt8] + func `deriveKey`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ccTag`: [UInt8], `receive`: Bool) throws -> AskarLocalKey + func `encryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `message`: [UInt8], `nonce`: [UInt8]?, `aad`: [UInt8]?) throws -> EncryptedBuffer + func `receiverUnwrapKey`(`wrapAlg`: AskarKeyAlg, `encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: [UInt8], `ccTag`: [UInt8], `nonce`: [UInt8]?, `tag`: [UInt8]?) throws -> AskarLocalKey + func `senderWrapKey`(`wrapAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `cek`: AskarLocalKey, `ccTag`: [UInt8]) throws -> EncryptedBuffer + +} + +public class AskarEcdh1Pu: AskarEcdh1PUProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init(`algId`: String, `apu`: String, `apv`: String) { + self.init(unsafeFromRawPointer: try! rustCall() { + uniffi_aries_askar_fn_constructor_askarecdh1pu_new( + FfiConverterString.lower(`algId`), + FfiConverterString.lower(`apu`), + FfiConverterString.lower(`apv`),$0) +}) + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarecdh1pu(pointer, $0) } + } + + + + + + + public func `decryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: [UInt8], `tag`: [UInt8]?, `nonce`: [UInt8], `aad`: [UInt8]?) throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdh1pu_decrypt_direct(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`encAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`senderKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterSequenceUInt8.lower(`ciphertext`), + FfiConverterOptionSequenceUInt8.lower(`tag`), + FfiConverterSequenceUInt8.lower(`nonce`), + FfiConverterOptionSequenceUInt8.lower(`aad`),$0 + ) +} + ) + } + + public func `deriveKey`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ccTag`: [UInt8], `receive`: Bool) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdh1pu_derive_key(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`encAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`senderKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterSequenceUInt8.lower(`ccTag`), + FfiConverterBool.lower(`receive`),$0 + ) +} + ) + } + + public func `encryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `message`: [UInt8], `nonce`: [UInt8]?, `aad`: [UInt8]?) throws -> EncryptedBuffer { + return try FfiConverterTypeEncryptedBuffer.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdh1pu_encrypt_direct(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`encAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`senderKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterSequenceUInt8.lower(`message`), + FfiConverterOptionSequenceUInt8.lower(`nonce`), + FfiConverterOptionSequenceUInt8.lower(`aad`),$0 + ) +} + ) + } + + public func `receiverUnwrapKey`(`wrapAlg`: AskarKeyAlg, `encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: [UInt8], `ccTag`: [UInt8], `nonce`: [UInt8]?, `tag`: [UInt8]?) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdh1pu_receiver_unwrap_key(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`wrapAlg`), + FfiConverterTypeAskarKeyAlg.lower(`encAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`senderKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterSequenceUInt8.lower(`ciphertext`), + FfiConverterSequenceUInt8.lower(`ccTag`), + FfiConverterOptionSequenceUInt8.lower(`nonce`), + FfiConverterOptionSequenceUInt8.lower(`tag`),$0 + ) +} + ) + } + + public func `senderWrapKey`(`wrapAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `senderKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `cek`: AskarLocalKey, `ccTag`: [UInt8]) throws -> EncryptedBuffer { + return try FfiConverterTypeEncryptedBuffer.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdh1pu_sender_wrap_key(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`wrapAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`senderKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterTypeAskarLocalKey.lower(`cek`), + FfiConverterSequenceUInt8.lower(`ccTag`),$0 + ) +} + ) + } +} + +public struct FfiConverterTypeAskarEcdh1PU: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarEcdh1Pu + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarEcdh1Pu { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarEcdh1Pu, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarEcdh1Pu { + return AskarEcdh1Pu(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarEcdh1Pu) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarEcdh1PU_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarEcdh1Pu { + return try FfiConverterTypeAskarEcdh1PU.lift(pointer) +} + +public func FfiConverterTypeAskarEcdh1PU_lower(_ value: AskarEcdh1Pu) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarEcdh1PU.lower(value) +} + + +public protocol AskarEcdhEsProtocol { + func `decryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: [UInt8], `tag`: [UInt8]?, `nonce`: [UInt8], `aad`: [UInt8]?) throws -> [UInt8] + func `deriveKey`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `receive`: Bool) throws -> AskarLocalKey + func `encryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `message`: [UInt8], `nonce`: [UInt8]?, `aad`: [UInt8]?) throws -> EncryptedBuffer + func `receiverUnwrapKey`(`wrapAlg`: AskarKeyAlg, `encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: [UInt8], `nonce`: [UInt8]?, `tag`: [UInt8]?) throws -> AskarLocalKey + func `senderWrapKey`(`wrapAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `cek`: AskarLocalKey) throws -> EncryptedBuffer + +} + +public class AskarEcdhEs: AskarEcdhEsProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init(`algId`: String, `apu`: String, `apv`: String) { + self.init(unsafeFromRawPointer: try! rustCall() { + uniffi_aries_askar_fn_constructor_askarecdhes_new( + FfiConverterString.lower(`algId`), + FfiConverterString.lower(`apu`), + FfiConverterString.lower(`apv`),$0) +}) + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarecdhes(pointer, $0) } + } + + + + + + + public func `decryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: [UInt8], `tag`: [UInt8]?, `nonce`: [UInt8], `aad`: [UInt8]?) throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdhes_decrypt_direct(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`encAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterSequenceUInt8.lower(`ciphertext`), + FfiConverterOptionSequenceUInt8.lower(`tag`), + FfiConverterSequenceUInt8.lower(`nonce`), + FfiConverterOptionSequenceUInt8.lower(`aad`),$0 + ) +} + ) + } + + public func `deriveKey`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `receive`: Bool) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdhes_derive_key(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`encAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterBool.lower(`receive`),$0 + ) +} + ) + } + + public func `encryptDirect`(`encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `message`: [UInt8], `nonce`: [UInt8]?, `aad`: [UInt8]?) throws -> EncryptedBuffer { + return try FfiConverterTypeEncryptedBuffer.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdhes_encrypt_direct(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`encAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterSequenceUInt8.lower(`message`), + FfiConverterOptionSequenceUInt8.lower(`nonce`), + FfiConverterOptionSequenceUInt8.lower(`aad`),$0 + ) +} + ) + } + + public func `receiverUnwrapKey`(`wrapAlg`: AskarKeyAlg, `encAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `ciphertext`: [UInt8], `nonce`: [UInt8]?, `tag`: [UInt8]?) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdhes_receiver_unwrap_key(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`wrapAlg`), + FfiConverterTypeAskarKeyAlg.lower(`encAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterSequenceUInt8.lower(`ciphertext`), + FfiConverterOptionSequenceUInt8.lower(`nonce`), + FfiConverterOptionSequenceUInt8.lower(`tag`),$0 + ) +} + ) + } + + public func `senderWrapKey`(`wrapAlg`: AskarKeyAlg, `ephemeralKey`: AskarLocalKey, `receiverKey`: AskarLocalKey, `cek`: AskarLocalKey) throws -> EncryptedBuffer { + return try FfiConverterTypeEncryptedBuffer.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarecdhes_sender_wrap_key(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`wrapAlg`), + FfiConverterTypeAskarLocalKey.lower(`ephemeralKey`), + FfiConverterTypeAskarLocalKey.lower(`receiverKey`), + FfiConverterTypeAskarLocalKey.lower(`cek`),$0 + ) +} + ) + } +} + +public struct FfiConverterTypeAskarEcdhEs: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarEcdhEs + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarEcdhEs { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarEcdhEs, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarEcdhEs { + return AskarEcdhEs(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarEcdhEs) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarEcdhEs_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarEcdhEs { + return try FfiConverterTypeAskarEcdhEs.lift(pointer) +} + +public func FfiConverterTypeAskarEcdhEs_lower(_ value: AskarEcdhEs) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarEcdhEs.lower(value) +} + + +public protocol AskarEntryProtocol { + func `category`() -> String + func `name`() -> String + func `tags`() -> [String: String] + func `value`() -> [UInt8] + +} + +public class AskarEntry: AskarEntryProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarentry(pointer, $0) } + } + + + + + + + public func `category`() -> String { + return try! FfiConverterString.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarentry_category(self.pointer, $0 + ) +} + ) + } + + public func `name`() -> String { + return try! FfiConverterString.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarentry_name(self.pointer, $0 + ) +} + ) + } + + public func `tags`() -> [String: String] { + return try! FfiConverterDictionaryStringString.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarentry_tags(self.pointer, $0 + ) +} + ) + } + + public func `value`() -> [UInt8] { + return try! FfiConverterSequenceUInt8.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarentry_value(self.pointer, $0 + ) +} + ) + } +} + +public struct FfiConverterTypeAskarEntry: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarEntry + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarEntry { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarEntry, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarEntry { + return AskarEntry(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarEntry) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarEntry_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarEntry { + return try FfiConverterTypeAskarEntry.lift(pointer) +} + +public func FfiConverterTypeAskarEntry_lower(_ value: AskarEntry) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarEntry.lower(value) +} + + +public protocol AskarKeyEntryProtocol { + func `algorithm`() -> String? + func `isLocal`() -> Bool + func `loadLocalKey`() throws -> AskarLocalKey + func `metadata`() -> String? + func `name`() -> String + func `tags`() -> [String: String] + +} + +public class AskarKeyEntry: AskarKeyEntryProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarkeyentry(pointer, $0) } + } + + + + + + + public func `algorithm`() -> String? { + return try! FfiConverterOptionString.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarkeyentry_algorithm(self.pointer, $0 + ) +} + ) + } + + public func `isLocal`() -> Bool { + return try! FfiConverterBool.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarkeyentry_is_local(self.pointer, $0 + ) +} + ) + } + + public func `loadLocalKey`() throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarkeyentry_load_local_key(self.pointer, $0 + ) +} + ) + } + + public func `metadata`() -> String? { + return try! FfiConverterOptionString.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarkeyentry_metadata(self.pointer, $0 + ) +} + ) + } + + public func `name`() -> String { + return try! FfiConverterString.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarkeyentry_name(self.pointer, $0 + ) +} + ) + } + + public func `tags`() -> [String: String] { + return try! FfiConverterDictionaryStringString.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarkeyentry_tags(self.pointer, $0 + ) +} + ) + } +} + +public struct FfiConverterTypeAskarKeyEntry: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarKeyEntry + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarKeyEntry { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarKeyEntry, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarKeyEntry { + return AskarKeyEntry(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarKeyEntry) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarKeyEntry_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarKeyEntry { + return try FfiConverterTypeAskarKeyEntry.lift(pointer) +} + +public func FfiConverterTypeAskarKeyEntry_lower(_ value: AskarKeyEntry) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarKeyEntry.lower(value) +} + + +public protocol AskarLocalKeyProtocol { + func `aeadDecrypt`(`ciphertext`: [UInt8], `tag`: [UInt8]?, `nonce`: [UInt8], `aad`: [UInt8]?) throws -> [UInt8] + func `aeadEncrypt`(`message`: [UInt8], `nonce`: [UInt8]?, `aad`: [UInt8]?) throws -> EncryptedBuffer + func `aeadPadding`(`msgLen`: Int32) -> Int32 + func `aeadParams`() throws -> AeadParams + func `aeadRandomNonce`() throws -> [UInt8] + func `algorithm`() -> AskarKeyAlg + func `convertKey`(`alg`: AskarKeyAlg) throws -> AskarLocalKey + func `signMessage`(`message`: [UInt8], `sigType`: String?) throws -> [UInt8] + func `toJwkPublic`(`alg`: AskarKeyAlg?) throws -> String + func `toJwkSecret`() throws -> [UInt8] + func `toJwkThumbprint`(`alg`: AskarKeyAlg?) throws -> String + func `toJwkThumbprints`() throws -> [String] + func `toKeyExchange`(`alg`: AskarKeyAlg, `pk`: AskarLocalKey) throws -> AskarLocalKey + func `toPublicBytes`() throws -> [UInt8] + func `toSecretBytes`() throws -> [UInt8] + func `unwrapKey`(`alg`: AskarKeyAlg, `ciphertext`: [UInt8], `tag`: [UInt8]?, `nonce`: [UInt8]?) throws -> AskarLocalKey + func `verifySignature`(`message`: [UInt8], `signature`: [UInt8], `sigType`: String?) throws -> Bool + func `wrapKey`(`key`: AskarLocalKey, `nonce`: [UInt8]?) throws -> EncryptedBuffer + +} + +public class AskarLocalKey: AskarLocalKeyProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarlocalkey(pointer, $0) } + } + + + + + + + public func `aeadDecrypt`(`ciphertext`: [UInt8], `tag`: [UInt8]?, `nonce`: [UInt8], `aad`: [UInt8]?) throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_aead_decrypt(self.pointer, + FfiConverterSequenceUInt8.lower(`ciphertext`), + FfiConverterOptionSequenceUInt8.lower(`tag`), + FfiConverterSequenceUInt8.lower(`nonce`), + FfiConverterOptionSequenceUInt8.lower(`aad`),$0 + ) +} + ) + } + + public func `aeadEncrypt`(`message`: [UInt8], `nonce`: [UInt8]?, `aad`: [UInt8]?) throws -> EncryptedBuffer { + return try FfiConverterTypeEncryptedBuffer.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_aead_encrypt(self.pointer, + FfiConverterSequenceUInt8.lower(`message`), + FfiConverterOptionSequenceUInt8.lower(`nonce`), + FfiConverterOptionSequenceUInt8.lower(`aad`),$0 + ) +} + ) + } + + public func `aeadPadding`(`msgLen`: Int32) -> Int32 { + return try! FfiConverterInt32.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarlocalkey_aead_padding(self.pointer, + FfiConverterInt32.lower(`msgLen`),$0 + ) +} + ) + } + + public func `aeadParams`() throws -> AeadParams { + return try FfiConverterTypeAeadParams.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_aead_params(self.pointer, $0 + ) +} + ) + } + + public func `aeadRandomNonce`() throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_aead_random_nonce(self.pointer, $0 + ) +} + ) + } + + public func `algorithm`() -> AskarKeyAlg { + return try! FfiConverterTypeAskarKeyAlg.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_askarlocalkey_algorithm(self.pointer, $0 + ) +} + ) + } + + public func `convertKey`(`alg`: AskarKeyAlg) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_convert_key(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`alg`),$0 + ) +} + ) + } + + public func `signMessage`(`message`: [UInt8], `sigType`: String?) throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_sign_message(self.pointer, + FfiConverterSequenceUInt8.lower(`message`), + FfiConverterOptionString.lower(`sigType`),$0 + ) +} + ) + } + + public func `toJwkPublic`(`alg`: AskarKeyAlg?) throws -> String { + return try FfiConverterString.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_public(self.pointer, + FfiConverterOptionTypeAskarKeyAlg.lower(`alg`),$0 + ) +} + ) + } + + public func `toJwkSecret`() throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_secret(self.pointer, $0 + ) +} + ) + } + + public func `toJwkThumbprint`(`alg`: AskarKeyAlg?) throws -> String { + return try FfiConverterString.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprint(self.pointer, + FfiConverterOptionTypeAskarKeyAlg.lower(`alg`),$0 + ) +} + ) + } + + public func `toJwkThumbprints`() throws -> [String] { + return try FfiConverterSequenceString.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_to_jwk_thumbprints(self.pointer, $0 + ) +} + ) + } + + public func `toKeyExchange`(`alg`: AskarKeyAlg, `pk`: AskarLocalKey) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_to_key_exchange(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`alg`), + FfiConverterTypeAskarLocalKey.lower(`pk`),$0 + ) +} + ) + } + + public func `toPublicBytes`() throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_to_public_bytes(self.pointer, $0 + ) +} + ) + } + + public func `toSecretBytes`() throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_to_secret_bytes(self.pointer, $0 + ) +} + ) + } + + public func `unwrapKey`(`alg`: AskarKeyAlg, `ciphertext`: [UInt8], `tag`: [UInt8]?, `nonce`: [UInt8]?) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_unwrap_key(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`alg`), + FfiConverterSequenceUInt8.lower(`ciphertext`), + FfiConverterOptionSequenceUInt8.lower(`tag`), + FfiConverterOptionSequenceUInt8.lower(`nonce`),$0 + ) +} + ) + } + + public func `verifySignature`(`message`: [UInt8], `signature`: [UInt8], `sigType`: String?) throws -> Bool { + return try FfiConverterBool.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_verify_signature(self.pointer, + FfiConverterSequenceUInt8.lower(`message`), + FfiConverterSequenceUInt8.lower(`signature`), + FfiConverterOptionString.lower(`sigType`),$0 + ) +} + ) + } + + public func `wrapKey`(`key`: AskarLocalKey, `nonce`: [UInt8]?) throws -> EncryptedBuffer { + return try FfiConverterTypeEncryptedBuffer.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarlocalkey_wrap_key(self.pointer, + FfiConverterTypeAskarLocalKey.lower(`key`), + FfiConverterOptionSequenceUInt8.lower(`nonce`),$0 + ) +} + ) + } +} + +public struct FfiConverterTypeAskarLocalKey: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarLocalKey + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarLocalKey { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarLocalKey, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarLocalKey { + return AskarLocalKey(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarLocalKey) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarLocalKey_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift(pointer) +} + +public func FfiConverterTypeAskarLocalKey_lower(_ value: AskarLocalKey) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarLocalKey.lower(value) +} + + +public protocol AskarScanProtocol { + func `fetchAll`() async throws -> [AskarEntry] + func `next`() async throws -> [AskarEntry]? + +} + +public class AskarScan: AskarScanProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarscan(pointer, $0) } + } + + + + + + + public func `fetchAll`() async throws -> [AskarEntry] { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<[AskarEntry], Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarscan_fetch_all( + self.pointer, + + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerSequenceTypeAskarEntryTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `next`() async throws -> [AskarEntry]? { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<[AskarEntry]?, Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarscan_next( + self.pointer, + + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerOptionSequenceTypeAskarEntryTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + +} + +public struct FfiConverterTypeAskarScan: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarScan + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarScan { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarScan, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarScan { + return AskarScan(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarScan) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarScan_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarScan { + return try FfiConverterTypeAskarScan.lift(pointer) +} + +public func FfiConverterTypeAskarScan_lower(_ value: AskarScan) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarScan.lower(value) +} + + +public protocol AskarSessionProtocol { + func `close`() async throws + func `count`(`category`: String, `tagFilter`: String?) async throws -> Int64 + func `fetch`(`category`: String, `name`: String, `forUpdate`: Bool) async throws -> AskarEntry? + func `fetchAll`(`category`: String, `tagFilter`: String?, `limit`: Int64?, `forUpdate`: Bool) async throws -> [AskarEntry] + func `fetchAllKeys`(`algorithm`: String?, `thumbprint`: String?, `tagFilter`: String?, `limit`: Int64?, `forUpdate`: Bool) async throws -> [AskarKeyEntry] + func `fetchKey`(`name`: String, `forUpdate`: Bool) async throws -> AskarKeyEntry? + func `insertKey`(`name`: String, `key`: AskarLocalKey, `metadata`: String?, `tags`: String?, `expiryMs`: Int64?) async throws + func `removeAll`(`category`: String, `tagFilter`: String?) async throws -> Int64 + func `removeKey`(`name`: String) async throws + func `update`(`operation`: AskarEntryOperation, `category`: String, `name`: String, `value`: [UInt8], `tags`: String?, `expiryMs`: Int64?) async throws + func `updateKey`(`name`: String, `metadata`: String?, `tags`: String?, `expiryMs`: Int64?) async throws + +} + +public class AskarSession: AskarSessionProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarsession(pointer, $0) } + } + + + + + + + public func `close`() async throws { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<(), Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_close( + self.pointer, + + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerVoidTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `count`(`category`: String, `tagFilter`: String?) async throws -> Int64 { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_count( + self.pointer, + + FfiConverterString.lower(`category`), + FfiConverterOptionString.lower(`tagFilter`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerInt64TypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `fetch`(`category`: String, `name`: String, `forUpdate`: Bool) async throws -> AskarEntry? { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_fetch( + self.pointer, + + FfiConverterString.lower(`category`), + FfiConverterString.lower(`name`), + FfiConverterBool.lower(`forUpdate`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerOptionTypeAskarEntryTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `fetchAll`(`category`: String, `tagFilter`: String?, `limit`: Int64?, `forUpdate`: Bool) async throws -> [AskarEntry] { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<[AskarEntry], Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_fetch_all( + self.pointer, + + FfiConverterString.lower(`category`), + FfiConverterOptionString.lower(`tagFilter`), + FfiConverterOptionInt64.lower(`limit`), + FfiConverterBool.lower(`forUpdate`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerSequenceTypeAskarEntryTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `fetchAllKeys`(`algorithm`: String?, `thumbprint`: String?, `tagFilter`: String?, `limit`: Int64?, `forUpdate`: Bool) async throws -> [AskarKeyEntry] { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<[AskarKeyEntry], Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_fetch_all_keys( + self.pointer, + + FfiConverterOptionString.lower(`algorithm`), + FfiConverterOptionString.lower(`thumbprint`), + FfiConverterOptionString.lower(`tagFilter`), + FfiConverterOptionInt64.lower(`limit`), + FfiConverterBool.lower(`forUpdate`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerSequenceTypeAskarKeyEntryTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `fetchKey`(`name`: String, `forUpdate`: Bool) async throws -> AskarKeyEntry? { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_fetch_key( + self.pointer, + + FfiConverterString.lower(`name`), + FfiConverterBool.lower(`forUpdate`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerOptionTypeAskarKeyEntryTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `insertKey`(`name`: String, `key`: AskarLocalKey, `metadata`: String?, `tags`: String?, `expiryMs`: Int64?) async throws { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<(), Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_insert_key( + self.pointer, + + FfiConverterString.lower(`name`), + FfiConverterTypeAskarLocalKey.lower(`key`), + FfiConverterOptionString.lower(`metadata`), + FfiConverterOptionString.lower(`tags`), + FfiConverterOptionInt64.lower(`expiryMs`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerVoidTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `removeAll`(`category`: String, `tagFilter`: String?) async throws -> Int64 { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_remove_all( + self.pointer, + + FfiConverterString.lower(`category`), + FfiConverterOptionString.lower(`tagFilter`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerInt64TypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `removeKey`(`name`: String) async throws { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<(), Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_remove_key( + self.pointer, + + FfiConverterString.lower(`name`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerVoidTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `update`(`operation`: AskarEntryOperation, `category`: String, `name`: String, `value`: [UInt8], `tags`: String?, `expiryMs`: Int64?) async throws { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<(), Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_update( + self.pointer, + + FfiConverterTypeAskarEntryOperation.lower(`operation`), + FfiConverterString.lower(`category`), + FfiConverterString.lower(`name`), + FfiConverterSequenceUInt8.lower(`value`), + FfiConverterOptionString.lower(`tags`), + FfiConverterOptionInt64.lower(`expiryMs`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerVoidTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `updateKey`(`name`: String, `metadata`: String?, `tags`: String?, `expiryMs`: Int64?) async throws { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<(), Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarsession_update_key( + self.pointer, + + FfiConverterString.lower(`name`), + FfiConverterOptionString.lower(`metadata`), + FfiConverterOptionString.lower(`tags`), + FfiConverterOptionInt64.lower(`expiryMs`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerVoidTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + +} + +public struct FfiConverterTypeAskarSession: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarSession + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarSession { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarSession, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarSession { + return AskarSession(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarSession) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarSession_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarSession { + return try FfiConverterTypeAskarSession.lift(pointer) +} + +public func FfiConverterTypeAskarSession_lower(_ value: AskarSession) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarSession.lower(value) +} + + +public protocol AskarStoreProtocol { + func `close`() async throws + func `createProfile`(`profile`: String?) async throws -> String + func `getProfileName`() async throws -> String + func `rekey`(`keyMethod`: String?, `passKey`: String?) async throws + func `removeProfile`(`profile`: String) async throws -> Bool + func `scan`(`profile`: String?, `categogy`: String, `tagFilter`: String?, `offset`: Int64?, `limit`: Int64?) async throws -> AskarScan + func `session`(`profile`: String?) async throws -> AskarSession + +} + +public class AskarStore: AskarStoreProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarstore(pointer, $0) } + } + + + + + + + public func `close`() async throws { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<(), Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstore_close( + self.pointer, + + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerVoidTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `createProfile`(`profile`: String?) async throws -> String { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstore_create_profile( + self.pointer, + + FfiConverterOptionString.lower(`profile`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerStringTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `getProfileName`() async throws -> String { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstore_get_profile_name( + self.pointer, + + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerStringTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `rekey`(`keyMethod`: String?, `passKey`: String?) async throws { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation<(), Error>? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstore_rekey( + self.pointer, + + FfiConverterOptionString.lower(`keyMethod`), + FfiConverterOptionString.lower(`passKey`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerVoidTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `removeProfile`(`profile`: String) async throws -> Bool { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstore_remove_profile( + self.pointer, + + FfiConverterString.lower(`profile`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerBoolTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `scan`(`profile`: String?, `categogy`: String, `tagFilter`: String?, `offset`: Int64?, `limit`: Int64?) async throws -> AskarScan { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstore_scan( + self.pointer, + + FfiConverterOptionString.lower(`profile`), + FfiConverterString.lower(`categogy`), + FfiConverterOptionString.lower(`tagFilter`), + FfiConverterOptionInt64.lower(`offset`), + FfiConverterOptionInt64.lower(`limit`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerTypeAskarScanTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `session`(`profile`: String?) async throws -> AskarSession { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstore_session( + self.pointer, + + FfiConverterOptionString.lower(`profile`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerTypeAskarSessionTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + +} + +public struct FfiConverterTypeAskarStore: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarStore + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarStore { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarStore, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarStore { + return AskarStore(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarStore) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarStore_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarStore { + return try FfiConverterTypeAskarStore.lift(pointer) +} + +public func FfiConverterTypeAskarStore_lower(_ value: AskarStore) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarStore.lower(value) +} + + +public protocol AskarStoreManagerProtocol { + func `generateRawStoreKey`(`seed`: String?) throws -> String + func `open`(`specUri`: String, `keyMethod`: String?, `passKey`: String?, `profile`: String?) async throws -> AskarStore + func `provision`(`specUri`: String, `keyMethod`: String?, `passKey`: String?, `profile`: String?, `recreate`: Bool) async throws -> AskarStore + func `remove`(`specUri`: String) async throws -> Bool + func `setDefaultLogger`() throws + +} + +public class AskarStoreManager: AskarStoreManagerProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init() { + self.init(unsafeFromRawPointer: try! rustCall() { + uniffi_aries_askar_fn_constructor_askarstoremanager_new($0) +}) + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_askarstoremanager(pointer, $0) } + } + + + + + + + public func `generateRawStoreKey`(`seed`: String?) throws -> String { + return try FfiConverterString.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarstoremanager_generate_raw_store_key(self.pointer, + FfiConverterOptionString.lower(`seed`),$0 + ) +} + ) + } + + public func `open`(`specUri`: String, `keyMethod`: String?, `passKey`: String?, `profile`: String?) async throws -> AskarStore { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstoremanager_open( + self.pointer, + + FfiConverterString.lower(`specUri`), + FfiConverterOptionString.lower(`keyMethod`), + FfiConverterOptionString.lower(`passKey`), + FfiConverterOptionString.lower(`profile`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerTypeAskarStoreTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `provision`(`specUri`: String, `keyMethod`: String?, `passKey`: String?, `profile`: String?, `recreate`: Bool) async throws -> AskarStore { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstoremanager_provision( + self.pointer, + + FfiConverterString.lower(`specUri`), + FfiConverterOptionString.lower(`keyMethod`), + FfiConverterOptionString.lower(`passKey`), + FfiConverterOptionString.lower(`profile`), + FfiConverterBool.lower(`recreate`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerTypeAskarStoreTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `remove`(`specUri`: String) async throws -> Bool { + // Suspend the function and call the scaffolding function, passing it a callback handler from + // `AsyncTypes.swift` + // + // Make sure to hold on to a reference to the continuation in the top-level scope so that + // it's not freed before the callback is invoked. + var continuation: CheckedContinuation? = nil + return try await withCheckedThrowingContinuation { + continuation = $0 + try! rustCall() { + uniffi_aries_askar_fn_method_askarstoremanager_remove( + self.pointer, + + FfiConverterString.lower(`specUri`), + FfiConverterForeignExecutor.lower(UniFfiForeignExecutor()), + uniffiFutureCallbackHandlerBoolTypeErrorCode, + &continuation, + $0 + ) + } + } + } + + + + public func `setDefaultLogger`() throws { + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_askarstoremanager_set_default_logger(self.pointer, $0 + ) +} + } +} + +public struct FfiConverterTypeAskarStoreManager: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AskarStoreManager + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarStoreManager { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AskarStoreManager, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarStoreManager { + return AskarStoreManager(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AskarStoreManager) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeAskarStoreManager_lift(_ pointer: UnsafeMutableRawPointer) throws -> AskarStoreManager { + return try FfiConverterTypeAskarStoreManager.lift(pointer) +} + +public func FfiConverterTypeAskarStoreManager_lower(_ value: AskarStoreManager) -> UnsafeMutableRawPointer { + return FfiConverterTypeAskarStoreManager.lower(value) +} + + +public protocol EncryptedBufferProtocol { + func `ciphertext`() -> [UInt8] + func `ciphertextTag`() -> [UInt8] + func `nonce`() -> [UInt8] + func `tag`() -> [UInt8] + +} + +public class EncryptedBuffer: EncryptedBufferProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_encryptedbuffer(pointer, $0) } + } + + + + + + + public func `ciphertext`() -> [UInt8] { + return try! FfiConverterSequenceUInt8.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext(self.pointer, $0 + ) +} + ) + } + + public func `ciphertextTag`() -> [UInt8] { + return try! FfiConverterSequenceUInt8.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_encryptedbuffer_ciphertext_tag(self.pointer, $0 + ) +} + ) + } + + public func `nonce`() -> [UInt8] { + return try! FfiConverterSequenceUInt8.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_encryptedbuffer_nonce(self.pointer, $0 + ) +} + ) + } + + public func `tag`() -> [UInt8] { + return try! FfiConverterSequenceUInt8.lift( + try! + rustCall() { + + uniffi_aries_askar_fn_method_encryptedbuffer_tag(self.pointer, $0 + ) +} + ) + } +} + +public struct FfiConverterTypeEncryptedBuffer: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = EncryptedBuffer + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> EncryptedBuffer { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: EncryptedBuffer, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> EncryptedBuffer { + return EncryptedBuffer(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: EncryptedBuffer) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeEncryptedBuffer_lift(_ pointer: UnsafeMutableRawPointer) throws -> EncryptedBuffer { + return try FfiConverterTypeEncryptedBuffer.lift(pointer) +} + +public func FfiConverterTypeEncryptedBuffer_lower(_ value: EncryptedBuffer) -> UnsafeMutableRawPointer { + return FfiConverterTypeEncryptedBuffer.lower(value) +} + + +public protocol LocalKeyFactoryProtocol { + func `fromJwk`(`jwk`: String) throws -> AskarLocalKey + func `fromJwkSlice`(`jwk`: [UInt8]) throws -> AskarLocalKey + func `fromPublicBytes`(`alg`: AskarKeyAlg, `bytes`: [UInt8]) throws -> AskarLocalKey + func `fromSecretBytes`(`alg`: AskarKeyAlg, `bytes`: [UInt8]) throws -> AskarLocalKey + func `fromSeed`(`alg`: AskarKeyAlg, `seed`: [UInt8], `method`: SeedMethod?) throws -> AskarLocalKey + func `generate`(`alg`: AskarKeyAlg, `ephemeral`: Bool) throws -> AskarLocalKey + +} + +public class LocalKeyFactory: LocalKeyFactoryProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init() { + self.init(unsafeFromRawPointer: try! rustCall() { + uniffi_aries_askar_fn_constructor_localkeyfactory_new($0) +}) + } + + deinit { + try! rustCall { uniffi_aries_askar_fn_free_localkeyfactory(pointer, $0) } + } + + + + + + + public func `fromJwk`(`jwk`: String) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_localkeyfactory_from_jwk(self.pointer, + FfiConverterString.lower(`jwk`),$0 + ) +} + ) + } + + public func `fromJwkSlice`(`jwk`: [UInt8]) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_localkeyfactory_from_jwk_slice(self.pointer, + FfiConverterSequenceUInt8.lower(`jwk`),$0 + ) +} + ) + } + + public func `fromPublicBytes`(`alg`: AskarKeyAlg, `bytes`: [UInt8]) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_localkeyfactory_from_public_bytes(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`alg`), + FfiConverterSequenceUInt8.lower(`bytes`),$0 + ) +} + ) + } + + public func `fromSecretBytes`(`alg`: AskarKeyAlg, `bytes`: [UInt8]) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_localkeyfactory_from_secret_bytes(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`alg`), + FfiConverterSequenceUInt8.lower(`bytes`),$0 + ) +} + ) + } + + public func `fromSeed`(`alg`: AskarKeyAlg, `seed`: [UInt8], `method`: SeedMethod?) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_localkeyfactory_from_seed(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`alg`), + FfiConverterSequenceUInt8.lower(`seed`), + FfiConverterOptionTypeSeedMethod.lower(`method`),$0 + ) +} + ) + } + + public func `generate`(`alg`: AskarKeyAlg, `ephemeral`: Bool) throws -> AskarLocalKey { + return try FfiConverterTypeAskarLocalKey.lift( + try + rustCallWithError(FfiConverterTypeErrorCode.lift) { + uniffi_aries_askar_fn_method_localkeyfactory_generate(self.pointer, + FfiConverterTypeAskarKeyAlg.lower(`alg`), + FfiConverterBool.lower(`ephemeral`),$0 + ) +} + ) + } +} + +public struct FfiConverterTypeLocalKeyFactory: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = LocalKeyFactory + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> LocalKeyFactory { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: LocalKeyFactory, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> LocalKeyFactory { + return LocalKeyFactory(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: LocalKeyFactory) -> UnsafeMutableRawPointer { + return value.pointer + } +} + + +public func FfiConverterTypeLocalKeyFactory_lift(_ pointer: UnsafeMutableRawPointer) throws -> LocalKeyFactory { + return try FfiConverterTypeLocalKeyFactory.lift(pointer) +} + +public func FfiConverterTypeLocalKeyFactory_lower(_ value: LocalKeyFactory) -> UnsafeMutableRawPointer { + return FfiConverterTypeLocalKeyFactory.lower(value) +} + +// Encapsulates an executor that can run Rust tasks +// +// On Swift, `Task.detached` can handle this we just need to know what priority to send it. +public struct UniFfiForeignExecutor { + var priority: TaskPriority + + public init(priority: TaskPriority) { + self.priority = priority + } + + public init() { + self.priority = Task.currentPriority + } +} + +fileprivate struct FfiConverterForeignExecutor: FfiConverter { + typealias SwiftType = UniFfiForeignExecutor + // Rust uses a pointer to represent the FfiConverterForeignExecutor, but we only need a u8. + // let's use `Int`, which is equivalent to `size_t` + typealias FfiType = Int + + static func lift(_ value: FfiType) throws -> SwiftType { + UniFfiForeignExecutor(priority: TaskPriority(rawValue: numericCast(value))) + } + static func lower(_ value: SwiftType) -> FfiType { + numericCast(value.priority.rawValue) + } + + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + fatalError("FfiConverterForeignExecutor.read not implemented yet") + } + static func write(_ value: SwiftType, into buf: inout [UInt8]) { + fatalError("FfiConverterForeignExecutor.read not implemented yet") + } +} + + +fileprivate func uniffiForeignExecutorCallback(executorHandle: Int, delayMs: UInt32, rustTask: UniFfiRustTaskCallback?, taskData: UnsafeRawPointer?) { + if let rustTask = rustTask { + let executor = try! FfiConverterForeignExecutor.lift(executorHandle) + Task.detached(priority: executor.priority) { + if delayMs != 0 { + let nanoseconds: UInt64 = numericCast(delayMs * 1000000) + try! await Task.sleep(nanoseconds: nanoseconds) + } + rustTask(taskData) + } + + } + // No else branch: when rustTask is null, we should drop the foreign executor. However, since + // its just a value type, we don't need to do anything here. +} + +fileprivate func uniffiInitForeignExecutor() { + uniffi_foreign_executor_callback_set(uniffiForeignExecutorCallback) +} + + +public struct AeadParams { + public var `nonceLength`: Int32 + public var `tagLength`: Int32 + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(`nonceLength`: Int32, `tagLength`: Int32) { + self.`nonceLength` = `nonceLength` + self.`tagLength` = `tagLength` + } +} + + +extension AeadParams: Equatable, Hashable { + public static func ==(lhs: AeadParams, rhs: AeadParams) -> Bool { + if lhs.`nonceLength` != rhs.`nonceLength` { + return false + } + if lhs.`tagLength` != rhs.`tagLength` { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(`nonceLength`) + hasher.combine(`tagLength`) + } +} + + +public struct FfiConverterTypeAeadParams: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AeadParams { + return try AeadParams( + `nonceLength`: FfiConverterInt32.read(from: &buf), + `tagLength`: FfiConverterInt32.read(from: &buf) + ) + } + + public static func write(_ value: AeadParams, into buf: inout [UInt8]) { + FfiConverterInt32.write(value.`nonceLength`, into: &buf) + FfiConverterInt32.write(value.`tagLength`, into: &buf) + } +} + + +public func FfiConverterTypeAeadParams_lift(_ buf: RustBuffer) throws -> AeadParams { + return try FfiConverterTypeAeadParams.lift(buf) +} + +public func FfiConverterTypeAeadParams_lower(_ value: AeadParams) -> RustBuffer { + return FfiConverterTypeAeadParams.lower(value) +} + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum AskarEntryOperation { + + case `insert` + case `replace` + case `remove` +} + +public struct FfiConverterTypeAskarEntryOperation: FfiConverterRustBuffer { + typealias SwiftType = AskarEntryOperation + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarEntryOperation { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .`insert` + + case 2: return .`replace` + + case 3: return .`remove` + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: AskarEntryOperation, into buf: inout [UInt8]) { + switch value { + + + case .`insert`: + writeInt(&buf, Int32(1)) + + + case .`replace`: + writeInt(&buf, Int32(2)) + + + case .`remove`: + writeInt(&buf, Int32(3)) + + } + } +} + + +public func FfiConverterTypeAskarEntryOperation_lift(_ buf: RustBuffer) throws -> AskarEntryOperation { + return try FfiConverterTypeAskarEntryOperation.lift(buf) +} + +public func FfiConverterTypeAskarEntryOperation_lower(_ value: AskarEntryOperation) -> RustBuffer { + return FfiConverterTypeAskarEntryOperation.lower(value) +} + + +extension AskarEntryOperation: Equatable, Hashable {} + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum AskarKeyAlg { + + case `a128Gcm` + case `a256Gcm` + case `a128CbcHs256` + case `a256CbcHs512` + case `a128Kw` + case `a256Kw` + case `bls12381g1` + case `bls12381g2` + case `bls12381g1g2` + case `c20p` + case `xc20p` + case `ed25519` + case `x25519` + case `k256` + case `p256` + case `p384` +} + +public struct FfiConverterTypeAskarKeyAlg: FfiConverterRustBuffer { + typealias SwiftType = AskarKeyAlg + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AskarKeyAlg { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .`a128Gcm` + + case 2: return .`a256Gcm` + + case 3: return .`a128CbcHs256` + + case 4: return .`a256CbcHs512` + + case 5: return .`a128Kw` + + case 6: return .`a256Kw` + + case 7: return .`bls12381g1` + + case 8: return .`bls12381g2` + + case 9: return .`bls12381g1g2` + + case 10: return .`c20p` + + case 11: return .`xc20p` + + case 12: return .`ed25519` + + case 13: return .`x25519` + + case 14: return .`k256` + + case 15: return .`p256` + + case 16: return .`p384` + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: AskarKeyAlg, into buf: inout [UInt8]) { + switch value { + + + case .`a128Gcm`: + writeInt(&buf, Int32(1)) + + + case .`a256Gcm`: + writeInt(&buf, Int32(2)) + + + case .`a128CbcHs256`: + writeInt(&buf, Int32(3)) + + + case .`a256CbcHs512`: + writeInt(&buf, Int32(4)) + + + case .`a128Kw`: + writeInt(&buf, Int32(5)) + + + case .`a256Kw`: + writeInt(&buf, Int32(6)) + + + case .`bls12381g1`: + writeInt(&buf, Int32(7)) + + + case .`bls12381g2`: + writeInt(&buf, Int32(8)) + + + case .`bls12381g1g2`: + writeInt(&buf, Int32(9)) + + + case .`c20p`: + writeInt(&buf, Int32(10)) + + + case .`xc20p`: + writeInt(&buf, Int32(11)) + + + case .`ed25519`: + writeInt(&buf, Int32(12)) + + + case .`x25519`: + writeInt(&buf, Int32(13)) + + + case .`k256`: + writeInt(&buf, Int32(14)) + + + case .`p256`: + writeInt(&buf, Int32(15)) + + + case .`p384`: + writeInt(&buf, Int32(16)) + + } + } +} + + +public func FfiConverterTypeAskarKeyAlg_lift(_ buf: RustBuffer) throws -> AskarKeyAlg { + return try FfiConverterTypeAskarKeyAlg.lift(buf) +} + +public func FfiConverterTypeAskarKeyAlg_lower(_ value: AskarKeyAlg) -> RustBuffer { + return FfiConverterTypeAskarKeyAlg.lower(value) +} + + +extension AskarKeyAlg: Equatable, Hashable {} + + + +public enum ErrorCode { + + + + case Success(`message`: String) + case Backend(`message`: String) + case Busy(`message`: String) + case Duplicate(`message`: String) + case Encryption(`message`: String) + case Input(`message`: String) + case NotFound(`message`: String) + case Unexpected(`message`: String) + case Unsupported(`message`: String) + case Custom(`message`: String) + + fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { + return try FfiConverterTypeErrorCode.lift(error) + } +} + + +public struct FfiConverterTypeErrorCode: FfiConverterRustBuffer { + typealias SwiftType = ErrorCode + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ErrorCode { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .Success( + `message`: try FfiConverterString.read(from: &buf) + ) + case 2: return .Backend( + `message`: try FfiConverterString.read(from: &buf) + ) + case 3: return .Busy( + `message`: try FfiConverterString.read(from: &buf) + ) + case 4: return .Duplicate( + `message`: try FfiConverterString.read(from: &buf) + ) + case 5: return .Encryption( + `message`: try FfiConverterString.read(from: &buf) + ) + case 6: return .Input( + `message`: try FfiConverterString.read(from: &buf) + ) + case 7: return .NotFound( + `message`: try FfiConverterString.read(from: &buf) + ) + case 8: return .Unexpected( + `message`: try FfiConverterString.read(from: &buf) + ) + case 9: return .Unsupported( + `message`: try FfiConverterString.read(from: &buf) + ) + case 10: return .Custom( + `message`: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: ErrorCode, into buf: inout [UInt8]) { + switch value { + + + + + + case let .Success(`message`): + writeInt(&buf, Int32(1)) + FfiConverterString.write(`message`, into: &buf) + + + case let .Backend(`message`): + writeInt(&buf, Int32(2)) + FfiConverterString.write(`message`, into: &buf) + + + case let .Busy(`message`): + writeInt(&buf, Int32(3)) + FfiConverterString.write(`message`, into: &buf) + + + case let .Duplicate(`message`): + writeInt(&buf, Int32(4)) + FfiConverterString.write(`message`, into: &buf) + + + case let .Encryption(`message`): + writeInt(&buf, Int32(5)) + FfiConverterString.write(`message`, into: &buf) + + + case let .Input(`message`): + writeInt(&buf, Int32(6)) + FfiConverterString.write(`message`, into: &buf) + + + case let .NotFound(`message`): + writeInt(&buf, Int32(7)) + FfiConverterString.write(`message`, into: &buf) + + + case let .Unexpected(`message`): + writeInt(&buf, Int32(8)) + FfiConverterString.write(`message`, into: &buf) + + + case let .Unsupported(`message`): + writeInt(&buf, Int32(9)) + FfiConverterString.write(`message`, into: &buf) + + + case let .Custom(`message`): + writeInt(&buf, Int32(10)) + FfiConverterString.write(`message`, into: &buf) + + } + } +} + + +extension ErrorCode: Equatable, Hashable {} + +extension ErrorCode: Error { } + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum SeedMethod { + + case `blsKeyGen` +} + +public struct FfiConverterTypeSeedMethod: FfiConverterRustBuffer { + typealias SwiftType = SeedMethod + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SeedMethod { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .`blsKeyGen` + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: SeedMethod, into buf: inout [UInt8]) { + switch value { + + + case .`blsKeyGen`: + writeInt(&buf, Int32(1)) + + } + } +} + + +public func FfiConverterTypeSeedMethod_lift(_ buf: RustBuffer) throws -> SeedMethod { + return try FfiConverterTypeSeedMethod.lift(buf) +} + +public func FfiConverterTypeSeedMethod_lower(_ value: SeedMethod) -> RustBuffer { + return FfiConverterTypeSeedMethod.lower(value) +} + + +extension SeedMethod: Equatable, Hashable {} + + + +fileprivate struct FfiConverterOptionInt64: FfiConverterRustBuffer { + typealias SwiftType = Int64? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterInt64.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterInt64.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionString: FfiConverterRustBuffer { + typealias SwiftType = String? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterString.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterString.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionTypeAskarEntry: FfiConverterRustBuffer { + typealias SwiftType = AskarEntry? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeAskarEntry.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeAskarEntry.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionTypeAskarKeyEntry: FfiConverterRustBuffer { + typealias SwiftType = AskarKeyEntry? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeAskarKeyEntry.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeAskarKeyEntry.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionTypeAskarKeyAlg: FfiConverterRustBuffer { + typealias SwiftType = AskarKeyAlg? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeAskarKeyAlg.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeAskarKeyAlg.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionTypeSeedMethod: FfiConverterRustBuffer { + typealias SwiftType = SeedMethod? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeSeedMethod.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeSeedMethod.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionSequenceUInt8: FfiConverterRustBuffer { + typealias SwiftType = [UInt8]? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterSequenceUInt8.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterSequenceUInt8.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterOptionSequenceTypeAskarEntry: FfiConverterRustBuffer { + typealias SwiftType = [AskarEntry]? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterSequenceTypeAskarEntry.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterSequenceTypeAskarEntry.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterSequenceUInt8: FfiConverterRustBuffer { + typealias SwiftType = [UInt8] + + public static func write(_ value: [UInt8], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterUInt8.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [UInt8] { + let len: Int32 = try readInt(&buf) + var seq = [UInt8]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterUInt8.read(from: &buf)) + } + return seq + } +} + +fileprivate struct FfiConverterSequenceString: FfiConverterRustBuffer { + typealias SwiftType = [String] + + public static func write(_ value: [String], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterString.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [String] { + let len: Int32 = try readInt(&buf) + var seq = [String]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterString.read(from: &buf)) + } + return seq + } +} + +fileprivate struct FfiConverterSequenceTypeAskarEntry: FfiConverterRustBuffer { + typealias SwiftType = [AskarEntry] + + public static func write(_ value: [AskarEntry], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeAskarEntry.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [AskarEntry] { + let len: Int32 = try readInt(&buf) + var seq = [AskarEntry]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeAskarEntry.read(from: &buf)) + } + return seq + } +} + +fileprivate struct FfiConverterSequenceTypeAskarKeyEntry: FfiConverterRustBuffer { + typealias SwiftType = [AskarKeyEntry] + + public static func write(_ value: [AskarKeyEntry], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeAskarKeyEntry.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [AskarKeyEntry] { + let len: Int32 = try readInt(&buf) + var seq = [AskarKeyEntry]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeAskarKeyEntry.read(from: &buf)) + } + return seq + } +} + +fileprivate struct FfiConverterDictionaryStringString: FfiConverterRustBuffer { + public static func write(_ value: [String: String], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for (key, value) in value { + FfiConverterString.write(key, into: &buf) + FfiConverterString.write(value, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [String: String] { + let len: Int32 = try readInt(&buf) + var dict = [String: String]() + dict.reserveCapacity(Int(len)) + for _ in 0...self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: ()) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerInt32( + rawContinutation: UnsafeRawPointer, + returnValue: Int32, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterInt32.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerInt64TypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: Int64, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterInt64.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerBool( + rawContinutation: UnsafeRawPointer, + returnValue: Int8, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterBool.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerBoolTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: Int8, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterBool.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerString( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterString.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerStringTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterString.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAskarCrypto( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterTypeAskarCrypto.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAskarEcdh1PU( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterTypeAskarEcdh1PU.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAskarEcdhEs( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterTypeAskarEcdhEs.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAskarLocalKeyTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterTypeAskarLocalKey.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAskarScanTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterTypeAskarScan.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAskarSessionTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterTypeAskarSession.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAskarStoreTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterTypeAskarStore.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAskarStoreManager( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterTypeAskarStoreManager.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeEncryptedBufferTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterTypeEncryptedBuffer.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeLocalKeyFactory( + rawContinutation: UnsafeRawPointer, + returnValue: UnsafeMutableRawPointer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterTypeLocalKeyFactory.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAeadParamsTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterTypeAeadParams.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerTypeAskarKeyAlg( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterTypeAskarKeyAlg.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerOptionString( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterOptionString.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerOptionTypeAskarEntryTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterOptionTypeAskarEntry.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerOptionTypeAskarKeyEntryTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterOptionTypeAskarKeyEntry.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerOptionSequenceTypeAskarEntryTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation<[AskarEntry]?, Error>.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterOptionSequenceTypeAskarEntry.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerSequenceUInt8( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation<[UInt8], Error>.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterSequenceUInt8.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerSequenceUInt8TypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation<[UInt8], Error>.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterSequenceUInt8.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerSequenceStringTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation<[String], Error>.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterSequenceString.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerSequenceTypeAskarEntryTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation<[AskarEntry], Error>.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterSequenceTypeAskarEntry.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerSequenceTypeAskarKeyEntryTypeErrorCode( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation<[AskarKeyEntry], Error>.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: FfiConverterTypeErrorCode.lift) + continuation.pointee.resume(returning: try FfiConverterSequenceTypeAskarKeyEntry.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} +fileprivate func uniffiFutureCallbackHandlerDictionaryStringString( + rawContinutation: UnsafeRawPointer, + returnValue: RustBuffer, + callStatus: RustCallStatus) { + + let continuation = rawContinutation.bindMemory( + to: CheckedContinuation<[String: String], Error>.self, + capacity: 1 + ) + + do { + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: nil) + continuation.pointee.resume(returning: try FfiConverterDictionaryStringString.lift(returnValue)) + } catch let error { + continuation.pointee.resume(throwing: error) + } +} + +private enum InitializationResult { + case ok + case contractVersionMismatch + case apiChecksumMismatch +} +// Use a global variables to perform the versioning checks. Swift ensures that +// the code inside is only computed once. +private var initializationResult: InitializationResult { + // Get the bindings contract version from our ComponentInterface + let bindings_contract_version = 22 + // Get the scaffolding contract version by calling the into the dylib + let scaffolding_contract_version = ffi_aries_askar_uniffi_contract_version() + if bindings_contract_version != scaffolding_contract_version { + return InitializationResult.contractVersionMismatch + } + if (uniffi_aries_askar_checksum_method_askarentry_category() != 3260) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarentry_name() != 32165) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarentry_tags() != 25644) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarentry_value() != 15374) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarkeyentry_algorithm() != 49759) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarkeyentry_is_local() != 55452) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarkeyentry_load_local_key() != 54061) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarkeyentry_metadata() != 37970) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarkeyentry_name() != 37206) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarkeyentry_tags() != 44110) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarscan_fetch_all() != 38256) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarscan_next() != 61044) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_close() != 21663) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_count() != 23751) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_fetch() != 15446) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_fetch_all() != 14311) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_fetch_all_keys() != 20309) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_fetch_key() != 29640) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_insert_key() != 50809) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_remove_all() != 30181) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_remove_key() != 7409) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_update() != 45947) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarsession_update_key() != 58440) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstore_close() != 5720) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstore_create_profile() != 52699) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstore_get_profile_name() != 43933) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstore_rekey() != 35956) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstore_remove_profile() != 36069) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstore_scan() != 18668) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstore_session() != 46605) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_aead_decrypt() != 28744) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_aead_encrypt() != 26220) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_aead_padding() != 19466) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_aead_params() != 51278) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_aead_random_nonce() != 61893) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_algorithm() != 53705) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_convert_key() != 35516) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_sign_message() != 39020) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_public() != 54824) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_secret() != 54958) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprint() != 30788) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_to_jwk_thumbprints() != 2898) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_to_key_exchange() != 29493) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_to_public_bytes() != 10734) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_to_secret_bytes() != 33910) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_unwrap_key() != 32063) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_verify_signature() != 24569) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarlocalkey_wrap_key() != 48354) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext() != 25707) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_encryptedbuffer_ciphertext_tag() != 4068) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_encryptedbuffer_nonce() != 58146) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_encryptedbuffer_tag() != 5792) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk() != 44160) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_localkeyfactory_from_jwk_slice() != 57872) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_localkeyfactory_from_public_bytes() != 47192) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_localkeyfactory_from_secret_bytes() != 44895) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_localkeyfactory_from_seed() != 47817) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_localkeyfactory_generate() != 15969) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstoremanager_generate_raw_store_key() != 65070) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstoremanager_open() != 59212) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstoremanager_provision() != 51063) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstoremanager_remove() != 60582) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarstoremanager_set_default_logger() != 42575) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarcrypto_box_open() != 21968) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarcrypto_box_seal() != 6843) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarcrypto_box_seal_open() != 65089) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarcrypto_crypto_box() != 6491) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarcrypto_random_nonce() != 3121) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdhes_decrypt_direct() != 57738) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdhes_derive_key() != 30605) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdhes_encrypt_direct() != 39447) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdhes_receiver_unwrap_key() != 44741) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdhes_sender_wrap_key() != 43003) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdh1pu_decrypt_direct() != 45235) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdh1pu_derive_key() != 856) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdh1pu_encrypt_direct() != 19815) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdh1pu_receiver_unwrap_key() != 16953) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_aries_askar_checksum_method_askarecdh1pu_sender_wrap_key() != 48518) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi__checksum_constructor_localkeyfactory_new() != 64154) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi__checksum_constructor_askarstoremanager_new() != 57892) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi__checksum_constructor_askarcrypto_new() != 42300) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi__checksum_constructor_askarecdhes_new() != 15713) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi__checksum_constructor_askarecdh1pu_new() != 32728) { + return InitializationResult.apiChecksumMismatch + } + + uniffiInitForeignExecutor() + return InitializationResult.ok +} + +private func uniffiEnsureInitialized() { + switch initializationResult { + case .ok: + break + case .contractVersionMismatch: + fatalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + case .apiChecksumMismatch: + fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} \ No newline at end of file diff --git a/wrappers/swift/Askar/Tests/AskarTests/CryptoTests.swift b/wrappers/swift/Askar/Tests/AskarTests/CryptoTests.swift new file mode 100644 index 00000000..0304a278 --- /dev/null +++ b/wrappers/swift/Askar/Tests/AskarTests/CryptoTests.swift @@ -0,0 +1,25 @@ +import XCTest +@testable import Askar + +final class CryptoTests: XCTestCase { + let keyFactory = LocalKeyFactory() + let crypto = AskarCrypto() + + func testCryptoBoxSeal() throws { + let key = try keyFactory.generate(alg: .x25519, ephemeral: false) + let message = "test message".data(using: .utf8)! + let enc = try crypto.boxSeal(receiverKey: key, message: [UInt8](message)) + let dec = try crypto.boxSealOpen(receiverKey: key, ciphertext: [UInt8](enc)) + XCTAssertEqual(message, Data(dec)) + } + + func testCryptoBox() throws { + let senderKey = try keyFactory.generate(alg: .x25519, ephemeral: false) + let receiverKey = try keyFactory.generate(alg: .x25519, ephemeral: false) + let message = "test message".data(using: .utf8)! + let nonce = try crypto.randomNonce() + let enc = try crypto.cryptoBox(receiverKey: receiverKey, senderKey: senderKey, message: [UInt8](message), nonce: nonce) + let dec = try crypto.boxOpen(receiverKey: receiverKey, senderKey: senderKey, message: enc, nonce: nonce) + XCTAssertEqual(message, Data(dec)) + } +} diff --git a/wrappers/swift/Askar/Tests/AskarTests/JoseEcdhTests.swift b/wrappers/swift/Askar/Tests/AskarTests/JoseEcdhTests.swift new file mode 100644 index 00000000..2f6962c2 --- /dev/null +++ b/wrappers/swift/Askar/Tests/AskarTests/JoseEcdhTests.swift @@ -0,0 +1,206 @@ +import XCTest +@testable import Askar + +extension String { + func base64url() -> String { + return self.data(using: .utf8)!.base64url() + } +} + +extension Data { + func base64url() -> String { + let base64url = self.base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=", with: "") + return base64url + } + + static func fromHex(_ string: String) -> Data? { + let len = string.count / 2 + var data = Data(capacity: len) + for i in 0..