From 74d3b7abf1e857f7320c100734e797855ea728c1 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 31 Mar 2022 08:39:35 +0100 Subject: [PATCH] feat: transpile to ts (#17) - Updates module to transpile `.proto` to `.ts` that follows our linting rules - Updates benchmark suite - Supports `Uint8Array` for `bytes` and `BigInt` for the various flavours of `int64` - No extra deps like long etc - Uses protobufjs to generate `.json` from `.proto` then turns `.json` into `.ts` - Adds auto-release and unified CI For follow up PRs: - Support packed fields - Replace the bit twiddling parts of `protons-runtime` with whatever OS module is fastest BREAKING CHANGE: This module is now ESM only --- .aegir.js | 11 - .github/ISSUE_TEMPLATE/config.yml | 8 - .github/ISSUE_TEMPLATE/open_an_issue.md | 19 - .github/config.yml | 68 -- .github/dependabot.yml | 8 + .github/workflows/automerge.yml | 50 ++ .github/workflows/js-test-and-release.yml | 152 ++++ .github/workflows/main.yml | 79 -- .gitignore | 42 +- CHANGELOG.md | 93 -- LICENSE | 23 +- LICENSE-APACHE | 5 + LICENSE-MIT | 19 + README.md | 156 +--- bench/index.js | 57 -- lerna.json | 12 + package.json | 104 +-- packages/protons-benchmark/LICENSE | 4 + packages/protons-benchmark/LICENSE-APACHE | 5 + packages/protons-benchmark/LICENSE-MIT | 19 + packages/protons-benchmark/README.md | 45 + packages/protons-benchmark/package.json | 79 ++ .../protons-benchmark/src/bench.proto | 11 +- packages/protons-benchmark/src/index.ts | 56 ++ packages/protons-benchmark/src/pbjs/bench.ts | 837 ++++++++++++++++++ .../src/protobufjs/bench.d.ts | 292 ++++++ .../protons-benchmark/src/protobufjs/bench.js | 813 +++++++++++++++++ .../protons-benchmark/src/protons/bench.ts | 111 +++ packages/protons-benchmark/tsconfig.json | 21 + packages/protons-runtime/LICENSE | 4 + packages/protons-runtime/LICENSE-APACHE | 5 + packages/protons-runtime/LICENSE-MIT | 19 + packages/protons-runtime/README.md | 22 + packages/protons-runtime/package.json | 152 ++++ packages/protons-runtime/src/codecs/bool.ts | 15 + packages/protons-runtime/src/codecs/bytes.ts | 26 + packages/protons-runtime/src/codecs/codec.ts | 41 + packages/protons-runtime/src/codecs/double.ts | 19 + packages/protons-runtime/src/codecs/enum.ts | 36 + .../protons-runtime/src/codecs/fixed32.ts | 19 + .../protons-runtime/src/codecs/fixed64.ts | 19 + packages/protons-runtime/src/codecs/float.ts | 19 + packages/protons-runtime/src/codecs/int32.ts | 19 + packages/protons-runtime/src/codecs/int64.ts | 19 + .../protons-runtime/src/codecs/message.ts | 142 +++ .../protons-runtime/src/codecs/sfixed32.ts | 19 + .../protons-runtime/src/codecs/sfixed64.ts | 19 + packages/protons-runtime/src/codecs/sint32.ts | 20 + packages/protons-runtime/src/codecs/sint64.ts | 19 + packages/protons-runtime/src/codecs/string.ts | 28 + packages/protons-runtime/src/codecs/uint32.ts | 24 + packages/protons-runtime/src/codecs/uint64.ts | 20 + packages/protons-runtime/src/decode.ts | 11 + packages/protons-runtime/src/encode.ts | 10 + packages/protons-runtime/src/index.ts | 37 + .../protons-runtime/src/utils/accessor.ts | 25 + .../protons-runtime/src/utils/big-varint.ts | 70 ++ .../protons-runtime/src/utils/long-bits.ts | 184 ++++ packages/protons-runtime/src/utils/varint.ts | 173 ++++ packages/protons-runtime/tsconfig.json | 12 + packages/protons/LICENSE | 4 + packages/protons/LICENSE-APACHE | 5 + packages/protons/LICENSE-MIT | 19 + packages/protons/README.md | 62 ++ packages/protons/bin/protons.ts | 38 + packages/protons/package.json | 161 ++++ packages/protons/src/index.ts | 282 ++++++ packages/protons/test/fixtures/basic.proto | 4 + packages/protons/test/fixtures/basic.ts | 24 + packages/protons/test/fixtures/dht.proto | 75 ++ packages/protons/test/fixtures/dht.ts | 102 +++ packages/protons/test/fixtures/test.proto | 31 + packages/protons/test/fixtures/test.ts | 83 ++ packages/protons/test/index.spec.ts | 136 +++ packages/protons/tsconfig.json | 18 + src/compile/decode.js | 330 ------- src/compile/encode.js | 133 --- src/compile/encoding-length.js | 102 --- src/compile/encodings/bool.js | 21 - src/compile/encodings/bytes.js | 42 - src/compile/encodings/double.js | 21 - src/compile/encodings/encoder.js | 14 - src/compile/encodings/fixed32.js | 21 - src/compile/encodings/fixed64.js | 25 - src/compile/encodings/float.js | 21 - src/compile/encodings/index.js | 22 - src/compile/encodings/int32.js | 22 - src/compile/encodings/int64.js | 49 - src/compile/encodings/sfixed32.js | 21 - src/compile/encodings/sint64.js | 19 - src/compile/encodings/string.js | 41 - src/compile/encodings/varint.js | 19 - src/compile/index.js | 165 ---- src/compile/utils.js | 5 - src/index.js | 39 - test/basic.spec.js | 109 --- test/booleans.spec.js | 37 - test/bytes.spec.js | 36 - test/corrupted.spec.js | 50 -- test/custom-types.spec.js | 55 -- test/defaults.spec.js | 44 - test/enums.spec.js | 21 - test/float.spec.js | 28 - test/integers.spec.js | 72 -- test/map.spec.js | 35 - test/nan.spec.js | 28 - test/nested.spec.js | 70 -- test/notpacked.spec.js | 33 - test/oneof.spec.js | 59 -- test/optional.spec.js | 65 -- test/packed.spec.js | 61 -- test/repeated.spec.js | 74 -- test/strings.spec.js | 45 - test/test.proto.js | 134 --- test/utf-8.spec.js | 21 - 115 files changed, 4877 insertions(+), 2822 deletions(-) delete mode 100644 .aegir.js delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/open_an_issue.md delete mode 100644 .github/config.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/automerge.yml create mode 100644 .github/workflows/js-test-and-release.yml delete mode 100644 .github/workflows/main.yml delete mode 100644 CHANGELOG.md create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT delete mode 100644 bench/index.js create mode 100644 lerna.json create mode 100644 packages/protons-benchmark/LICENSE create mode 100644 packages/protons-benchmark/LICENSE-APACHE create mode 100644 packages/protons-benchmark/LICENSE-MIT create mode 100644 packages/protons-benchmark/README.md create mode 100644 packages/protons-benchmark/package.json rename bench/bench.proto.js => packages/protons-benchmark/src/bench.proto (75%) mode change 100644 => 100755 create mode 100644 packages/protons-benchmark/src/index.ts create mode 100644 packages/protons-benchmark/src/pbjs/bench.ts create mode 100644 packages/protons-benchmark/src/protobufjs/bench.d.ts create mode 100644 packages/protons-benchmark/src/protobufjs/bench.js create mode 100644 packages/protons-benchmark/src/protons/bench.ts create mode 100644 packages/protons-benchmark/tsconfig.json create mode 100644 packages/protons-runtime/LICENSE create mode 100644 packages/protons-runtime/LICENSE-APACHE create mode 100644 packages/protons-runtime/LICENSE-MIT create mode 100644 packages/protons-runtime/README.md create mode 100644 packages/protons-runtime/package.json create mode 100644 packages/protons-runtime/src/codecs/bool.ts create mode 100644 packages/protons-runtime/src/codecs/bytes.ts create mode 100644 packages/protons-runtime/src/codecs/codec.ts create mode 100644 packages/protons-runtime/src/codecs/double.ts create mode 100644 packages/protons-runtime/src/codecs/enum.ts create mode 100644 packages/protons-runtime/src/codecs/fixed32.ts create mode 100644 packages/protons-runtime/src/codecs/fixed64.ts create mode 100644 packages/protons-runtime/src/codecs/float.ts create mode 100644 packages/protons-runtime/src/codecs/int32.ts create mode 100644 packages/protons-runtime/src/codecs/int64.ts create mode 100644 packages/protons-runtime/src/codecs/message.ts create mode 100644 packages/protons-runtime/src/codecs/sfixed32.ts create mode 100644 packages/protons-runtime/src/codecs/sfixed64.ts create mode 100644 packages/protons-runtime/src/codecs/sint32.ts create mode 100644 packages/protons-runtime/src/codecs/sint64.ts create mode 100644 packages/protons-runtime/src/codecs/string.ts create mode 100644 packages/protons-runtime/src/codecs/uint32.ts create mode 100644 packages/protons-runtime/src/codecs/uint64.ts create mode 100644 packages/protons-runtime/src/decode.ts create mode 100644 packages/protons-runtime/src/encode.ts create mode 100644 packages/protons-runtime/src/index.ts create mode 100644 packages/protons-runtime/src/utils/accessor.ts create mode 100644 packages/protons-runtime/src/utils/big-varint.ts create mode 100644 packages/protons-runtime/src/utils/long-bits.ts create mode 100644 packages/protons-runtime/src/utils/varint.ts create mode 100644 packages/protons-runtime/tsconfig.json create mode 100644 packages/protons/LICENSE create mode 100644 packages/protons/LICENSE-APACHE create mode 100644 packages/protons/LICENSE-MIT create mode 100644 packages/protons/README.md create mode 100644 packages/protons/bin/protons.ts create mode 100644 packages/protons/package.json create mode 100644 packages/protons/src/index.ts create mode 100644 packages/protons/test/fixtures/basic.proto create mode 100644 packages/protons/test/fixtures/basic.ts create mode 100644 packages/protons/test/fixtures/dht.proto create mode 100644 packages/protons/test/fixtures/dht.ts create mode 100644 packages/protons/test/fixtures/test.proto create mode 100644 packages/protons/test/fixtures/test.ts create mode 100644 packages/protons/test/index.spec.ts create mode 100644 packages/protons/tsconfig.json delete mode 100644 src/compile/decode.js delete mode 100644 src/compile/encode.js delete mode 100644 src/compile/encoding-length.js delete mode 100644 src/compile/encodings/bool.js delete mode 100644 src/compile/encodings/bytes.js delete mode 100644 src/compile/encodings/double.js delete mode 100644 src/compile/encodings/encoder.js delete mode 100644 src/compile/encodings/fixed32.js delete mode 100644 src/compile/encodings/fixed64.js delete mode 100644 src/compile/encodings/float.js delete mode 100644 src/compile/encodings/index.js delete mode 100644 src/compile/encodings/int32.js delete mode 100644 src/compile/encodings/int64.js delete mode 100644 src/compile/encodings/sfixed32.js delete mode 100644 src/compile/encodings/sint64.js delete mode 100644 src/compile/encodings/string.js delete mode 100644 src/compile/encodings/varint.js delete mode 100644 src/compile/index.js delete mode 100644 src/compile/utils.js delete mode 100644 src/index.js delete mode 100644 test/basic.spec.js delete mode 100644 test/booleans.spec.js delete mode 100644 test/bytes.spec.js delete mode 100644 test/corrupted.spec.js delete mode 100644 test/custom-types.spec.js delete mode 100644 test/defaults.spec.js delete mode 100644 test/enums.spec.js delete mode 100644 test/float.spec.js delete mode 100644 test/integers.spec.js delete mode 100644 test/map.spec.js delete mode 100644 test/nan.spec.js delete mode 100644 test/nested.spec.js delete mode 100644 test/notpacked.spec.js delete mode 100644 test/oneof.spec.js delete mode 100644 test/optional.spec.js delete mode 100644 test/packed.spec.js delete mode 100644 test/repeated.spec.js delete mode 100644 test/strings.spec.js delete mode 100644 test/test.proto.js delete mode 100644 test/utf-8.spec.js diff --git a/.aegir.js b/.aegir.js deleted file mode 100644 index 5166cb4..0000000 --- a/.aegir.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict' - -module.exports = { - webpack: { - node: { - // this is needed until protocol-buffers stops using node core APIs in browser code - os: true, - Buffer: true - } - } -} diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 4b86d71..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Getting Help on IPFS - url: https://ipfs.io/help - about: All information about how and where to get help on IPFS. - - name: IPFS Official Forum - url: https://discuss.ipfs.io - about: Please post general questions, support requests, and discussions here. diff --git a/.github/ISSUE_TEMPLATE/open_an_issue.md b/.github/ISSUE_TEMPLATE/open_an_issue.md deleted file mode 100644 index 4fcbd00..0000000 --- a/.github/ISSUE_TEMPLATE/open_an_issue.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Open an issue -about: Only for actionable issues relevant to this repository. -title: '' -labels: need/triage -assignees: '' - ---- - diff --git a/.github/config.yml b/.github/config.yml deleted file mode 100644 index ed26646..0000000 --- a/.github/config.yml +++ /dev/null @@ -1,68 +0,0 @@ -# Configuration for welcome - https://github.com/behaviorbot/welcome - -# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome -# Comment to be posted to on first time issues -newIssueWelcomeComment: > - Thank you for submitting your first issue to this repository! A maintainer - will be here shortly to triage and review. - - In the meantime, please double-check that you have provided all the - necessary information to make this process easy! Any information that can - help save additional round trips is useful! We currently aim to give - initial feedback within **two business days**. If this does not happen, feel - free to leave a comment. - - Please keep an eye on how this issue will be labeled, as labels give an - overview of priorities, assignments and additional actions requested by the - maintainers: - - - "Priority" labels will show how urgent this is for the team. - - "Status" labels will show if this is ready to be worked on, blocked, or in progress. - - "Need" labels will indicate if additional input or analysis is required. - - Finally, remember to use https://discuss.ipfs.io if you just need general - support. - -# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome -# Comment to be posted to on PRs from first time contributors in your repository -newPRWelcomeComment: > - Thank you for submitting this PR! - - A maintainer will be here shortly to review it. - - We are super grateful, but we are also overloaded! Help us by making sure - that: - - * The context for this PR is clear, with relevant discussion, decisions - and stakeholders linked/mentioned. - - * Your contribution itself is clear (code comments, self-review for the - rest) and in its best form. Follow the [code contribution - guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md#code-contribution-guidelines) - if they apply. - - Getting other community members to do a review would be great help too on - complex PRs (you can ask in the chats/forums). If you are unsure about - something, just leave us a comment. - - Next steps: - - * A maintainer will triage and assign priority to this PR, commenting on - any missing things and potentially assigning a reviewer for high - priority items. - - * The PR gets reviews, discussed and approvals as needed. - - * The PR is merged by maintainers when it has been approved and comments addressed. - - We currently aim to provide initial feedback/triaging within **two business - days**. Please keep an eye on any labelling actions, as these will indicate - priorities and status of your contribution. - - We are very grateful for your contribution! - - -# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge -# Comment to be posted to on pull requests merged by a first time user -# Currently disabled -#firstPRMergeComment: "" diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..290ad02 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + time: "10:00" + open-pull-requests-limit: 10 diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..13da9c1 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,50 @@ +# Automatically merge pull requests opened by web3-bot, as soon as (and only if) all tests pass. +# This reduces the friction associated with updating with our workflows. + +on: [ pull_request ] +name: Automerge + +jobs: + automerge-check: + if: github.event.pull_request.user.login == 'web3-bot' + runs-on: ubuntu-latest + outputs: + status: ${{ steps.should-automerge.outputs.status }} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Check if we should automerge + id: should-automerge + run: | + for commit in $(git rev-list --first-parent origin/${{ github.event.pull_request.base.ref }}..${{ github.event.pull_request.head.sha }}); do + committer=$(git show --format=$'%ce' -s $commit) + echo "Committer: $committer" + if [[ "$committer" != "web3-bot@users.noreply.github.com" ]]; then + echo "Commit $commit wasn't committed by web3-bot, but by $committer." + echo "::set-output name=status::false" + exit + fi + done + echo "::set-output name=status::true" + automerge: + needs: automerge-check + runs-on: ubuntu-latest + # The check for the user is redundant here, as this job depends on the automerge-check job, + # but it prevents this job from spinning up, just to be skipped shortly after. + if: github.event.pull_request.user.login == 'web3-bot' && needs.automerge-check.outputs.status == 'true' + steps: + - name: Wait on tests + uses: lewagon/wait-on-check-action@bafe56a6863672c681c3cf671f5e10b20abf2eaa # v0.2 + with: + ref: ${{ github.event.pull_request.head.sha }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + running-workflow-name: 'automerge' # the name of this job + - name: Merge PR + uses: pascalgn/automerge-action@741c311a47881be9625932b0a0de1b0937aab1ae # v0.13.1 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + MERGE_LABELS: "" + MERGE_METHOD: "squash" + MERGE_DELETE_BRANCH: true diff --git a/.github/workflows/js-test-and-release.yml b/.github/workflows/js-test-and-release.yml new file mode 100644 index 0000000..8630dc5 --- /dev/null +++ b/.github/workflows/js-test-and-release.yml @@ -0,0 +1,152 @@ +name: test & maybe release +on: + push: + branches: + - master # with #262 - ${{{ github.default_branch }}} + pull_request: + branches: + - master # with #262 - ${{{ github.default_branch }}} + +jobs: + + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present lint + - run: npm run --if-present dep-check + + test-node: + needs: check + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + node: [16] + fail-fast: true + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:node + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: node + + test-chrome: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:chrome + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: chrome + + test-chrome-webworker: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:chrome-webworker + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: chrome-webworker + + test-firefox: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:firefox + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: firefox + + test-firefox-webworker: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:firefox-webworker + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: firefox-webworker + + test-electron-main: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx xvfb-maybe npm run --if-present test:electron-main + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: electron-main + + test-electron-renderer: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx xvfb-maybe npm run --if-present test:electron-renderer + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: electron-renderer + + release: + needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-electron-main, test-electron-renderer] + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/master' # with #262 - 'refs/heads/${{{ github.default_branch }}}' + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - uses: ipfs/aegir/actions/docker-login@master + with: + docker-token: ${{ secrets.DOCKER_TOKEN }} + docker-username: ${{ secrets.DOCKER_USERNAME }} + - run: npm run --if-present release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 7ec9dd6..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: ci -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 14 - - run: npm install - - run: npx aegir lint - - run: npx aegir build - - run: npx aegir dep-check - - uses: ipfs/aegir/actions/bundle-size@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - test-node: - needs: check - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - node: [14, 15] - fail-fast: true - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node }} - - run: npm install - - run: npx aegir test -t node --bail --cov - - uses: codecov/codecov-action@v1 - test-chrome: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: microsoft/playwright-github-action@v1 - - run: npm install - - run: npx aegir test -t browser -t webworker --bail - - uses: codecov/codecov-action@v1 - test-firefox: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: microsoft/playwright-github-action@v1 - - run: npm install - - run: npx aegir test -t browser -t webworker --bail -- --browser firefox - test-webkit: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: microsoft/playwright-github-action@v1 - - run: npm install - - run: npx aegir test -t browser -t webworker --bail -- --browser webkit - test-electron-main: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: npm install - - run: npx xvfb-maybe aegir test -t electron-main --bail - test-electron-renderer: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: npm install - - run: npx xvfb-maybe aegir test -t electron-renderer --bail \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7d4860e..d25b2ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,45 +1,13 @@ -docs -**/node_modules/ -**/*.log -test/repo-tests* -**/bundle.js -.nyc_output/ -coverage.lcov - -# Logs -logs -*.log - -coverage - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - # Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript +.nyc_output build +dist +docs # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules - -lib -dist -test/test-data/go-ipfs-repo/LOCK -test/test-data/go-ipfs-repo/LOG -test/test-data/go-ipfs-repo/LOG.old - -# while testing npm5 +# Lock files package-lock.json +yarn.lock diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index c1d2dd5..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,93 +0,0 @@ - -## [2.0.3](https://github.com/ipfs/protons/compare/v2.0.2...v2.0.3) (2021-08-24) - - - - -## [2.0.2](https://github.com/ipfs/protons/compare/v2.0.1...v2.0.2) (2021-08-11) - - - - -## [2.0.1](https://github.com/ipfs/protons/compare/v2.0.0...v2.0.1) (2021-04-16) - - -### Bug Fixes - -* update uint8arrays ([#15](https://github.com/ipfs/protons/issues/15)) ([8a9dbf9](https://github.com/ipfs/protons/commit/8a9dbf9)) - - - - -# [2.0.0](https://github.com/ipfs/protons/compare/v1.2.1...v2.0.0) (2020-08-04) - - -### Bug Fixes - -* replace node buffers with uint8arrays ([#14](https://github.com/ipfs/protons/issues/14)) ([d3547cb](https://github.com/ipfs/protons/commit/d3547cb)) - - -### BREAKING CHANGES - -* - Byte arrays returned from this module will now be UintArrays and not Node Buffers - - - - -## [1.2.1](https://github.com/ipfs/protons/compare/v1.2.0...v1.2.1) (2020-06-24) - - -### Bug Fixes - -* **ci:** add empty commit to fix lint checks on master ([3b5276f](https://github.com/ipfs/protons/commit/3b5276f)) - - -### Features - -* support Uint8Array input in place of Buffer ([#13](https://github.com/ipfs/protons/issues/13)) ([6c7add4](https://github.com/ipfs/protons/commit/6c7add4)), closes [/github.com/ipfs/protons/blob/3b5276f052f2e17c2d806d27cd9a88e156588977/src/compile/encodings.js#L30-L43](https://github.com//github.com/ipfs/protons/blob/3b5276f052f2e17c2d806d27cd9a88e156588977/src/compile/encodings.js/issues/L30-L43) [#12](https://github.com/ipfs/protons/issues/12) - - - - -# [1.2.0](https://github.com/ipfs/protons/compare/v1.1.0...v1.2.0) (2020-04-24) - - - - -# [1.1.0](https://github.com/ipfs/protons/compare/v1.0.2...v1.1.0) (2020-04-24) - - - - -## [1.0.2](https://github.com/ipfs/protons/compare/v1.0.0...v1.0.2) (2020-03-18) - - -### Bug Fixes - -* add buffer and allow browser tests ([#11](https://github.com/ipfs/protons/issues/11)) ([c1d3d7e](https://github.com/ipfs/protons/commit/c1d3d7e)), closes [ipfs/js-ipfs#2924](https://github.com/ipfs/js-ipfs/issues/2924) -* make default value only return default value ([fce4635](https://github.com/ipfs/protons/commit/fce4635)) - - -### Features - -* adds accessor methods for object properties ([3beeb49](https://github.com/ipfs/protons/commit/3beeb49)) - - - - -# [1.0.0](https://github.com/ipfs/protons/compare/122d5c0...v1.0.0) (2017-09-06) - - -### Bug Fixes - -* updated for packed fixes ([122d5c0](https://github.com/ipfs/protons/commit/122d5c0)) - - -### Features - -* make benchmarks browser compatible ([9645adf](https://github.com/ipfs/protons/commit/9645adf)) -* rename to protons ([db6d2fb](https://github.com/ipfs/protons/commit/db6d2fb)) -* upgrade dependencies ([fd66b86](https://github.com/ipfs/protons/commit/fd66b86)) - - - diff --git a/LICENSE b/LICENSE index 757562e..20ce483 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,4 @@ -The MIT License (MIT) +This project is dual licensed under MIT and Apache-2.0. -Copyright (c) 2014 Mathias Buus - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,5 @@ +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/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 92e6289..7a539e8 100644 --- a/README.md +++ b/README.md @@ -1,155 +1,23 @@ -⛔️ DEPRECATED: This module is no longer maintained +# protons -# protons +> `.proto` to `.ts` transpiler -[![Dependency Status](https://david-dm.org/ipfs/protons.svg?style=flat-square)](https://david-dm.org/ipfs/protons) -[![Travis CI](https://travis-ci.org/ipfs/protons.svg?branch=master)](https://travis-ci.org/ipfs/protons) +Transpiles `.proto` files to `.ts` - uses `Uint8Array` for `byte` fields and `BigInt` for `int64`/`uint64` and `sint64`. -> [Protocol Buffers](https://developers.google.com/protocol-buffers/) for Node.js and the browser without compilation. -> -> Forked from [protocol-buffers](https://github.com/mafintosh/protocol-buffers). +## Packages -## Lead Maintainer +* [`/packages/protons`](./packages/protons) The transpiler +* [`/packages/protons-benchmark`](./packages/protons-benchmark) A benchmark suite +* [`/packages/protons-runtime`](./packages/protons-runtime) Shared components that turn values to bytes and back again -[Alex Potsides](https://github.com/achingbrain) +## Contribute -## Table of Contents +Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/protons/issues)! -- [Install](#install) -- [Usage](#usage) -- [Properties](#properties) -- [Performance](#performance) -- [Leveldb encoding compatibility](#leveldb-encoding-compatibility) -- [License](#license) +This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). -## Install - -```sh -> npm install protons -``` - -## Usage - -Assuming the following `test.proto` file exists - -```proto -enum FOO { - BAR = 1; -} - -message Test { - required float num = 1; - required string payload = 2; -} - -message AnotherOne { - repeated FOO list = 1; -} - -message WithOptional { - optional string payload = 1; -} -``` - -Use the above proto file to encode/decode messages by doing - -``` js -const protons = require('protons') - -// pass a proto file as a buffer/string or pass a parsed protobuf-schema object -const messages = protons(fs.readFileSync('test.proto')) - -const buf = messages.Test.encode({ - num: 42, - payload: 'hello world' -}) - -console.log(buf) // should print a buffer -``` - -To decode a message use `Test.decode` - -``` js -const obj = messages.Test.decode(buf) -console.log(obj) // should print an object similar to above -``` - -Enums are accessed in the same way as messages - -``` js -const buf = messages.AnotherOne.encode({ - list: [ - messages.FOO.BAR - ] -}) -``` - -Nested emums are accessed as properties on the corresponding message - -``` js -const buf = message.SomeMessage.encode({ - list: [ - messages.SomeMessage.NESTED_ENUM.VALUE - ] -}) -``` - -See the [Google Protocol Buffers docs](https://developers.google.com/protocol-buffers/) for more information about the -available types etc. - -## Properties - -Decoded object properties can be interacted with using accessor methods: - -```javascript - const obj = messages.WithOptional.decode(messages.WithOptional.encode({})) - -obj.hasPayload() // false -obj.getPayload() // '' -obj.setPayload('hello world') -obj.getPayload() // 'hello world' -obj.clearPayload() -obj.getPayload() // undefined -``` - -## Performance - -This module is pretty fast. - -You can run the benchmarks yourself by doing `npm run bench`. - -On my Macbook Pro it gives the following results - -``` -JSON (encode) x 703,160 ops/sec ±2.06% (91 runs sampled) -JSON (decode) x 619,564 ops/sec ±1.60% (94 runs sampled) -JSON (encode + decode) x 308,635 ops/sec ±1.74% (92 runs sampled) -protocol-buffers@4.1.0 (encode) x 693,570 ops/sec ±1.55% (92 runs sampled) -protocol-buffers@4.1.0 (decode) x 1,894,031 ops/sec ±1.61% (93 runs sampled) -protocol-buffers@4.1.0 (encode + decode) x 444,229 ops/sec ±1.50% (93 runs sampled) -protons@1.0.1 (encode) x 435,058 ops/sec ±1.46% (91 runs sampled) -protons@1.0.1 (decode) x 29,548 ops/sec ±3.29% (78 runs sampled) -protons@1.0.1 (encode + decode) x 27,042 ops/sec ±4.41% (80 runs sampled) -``` - -Note that JSON parsing/serialization in node is a native function that is *really* fast. - -## Leveldb encoding compatibility - -Compiled protocol buffers messages are valid levelup encodings. -This means you can pass them as `valueEncoding` and `keyEncoding`. - -``` js -const level = require('level') -const db = level('db') - -db.put('hello', {payload:'world'}, {valueEncoding:messages.Test}, (err) => { - db.get('hello', {valueEncoding:messages.Test}, (err, message) => { - console.log(message) - }) -}) -``` +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) ## License -MIT +[Apache-2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT) © Protocol Labs diff --git a/bench/index.js b/bench/index.js deleted file mode 100644 index 5c3a708..0000000 --- a/bench/index.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict' - -const Benchmark = require('benchmark') -if (typeof window !== 'undefined') { - window.Benchmark = Benchmark -} - -const protobuf = require('protocol-buffers') -const protonsNpm = require('protons') -const protons = require('../') -const proto = require('./bench.proto') -const messages = protobuf(proto) -const messagesBuf = protons(proto) -const messagesNpm = protonsNpm(proto) -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -const EXAMPLE = { - foo: 'hello', - hello: 42, - payload: uint8ArrayFromString('a'), - meh: { - b: { - tmp: { - baz: 1000 - } - }, - lol: 'lol' - } -} - -const suite = new Benchmark.Suite() - -function add (name, encode, decode) { - const EXAMPLE_BUFFER = encode(EXAMPLE) - - suite - .add(name + ' (encode)', function () { - return encode(EXAMPLE) - }) - .add(name + ' (decode)', function () { - return decode(EXAMPLE_BUFFER) - }) - .add(name + ' (encode + decode)', function () { - return decode(encode(EXAMPLE)) - }) -} - -add('JSON', JSON.stringify, JSON.parse) -add(`protocol-buffers@${require('protocol-buffers/package.json').version}`, messages.Test.encode, messages.Test.decode) -add(`protons@${require('protons/package.json').version}`, messagesNpm.Test.encode, messagesNpm.Test.decode) -add('local', messagesBuf.Test.encode, messagesBuf.Test.decode) - -suite - .on('cycle', (e) => { - console.log(String(e.target)) - }) - .run() diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000..aa6d000 --- /dev/null +++ b/lerna.json @@ -0,0 +1,12 @@ +{ + "lerna": "4.0.0", + "packages": [ + "packages/*" + ], + "version": "independent", + "command": { + "run": { + "stream": true + } + } +} diff --git a/package.json b/package.json index 46e28ea..f391b77 100644 --- a/package.json +++ b/package.json @@ -1,83 +1,45 @@ { "name": "protons", - "version": "2.0.3", - "description": "Protocol Buffers for Node.js without compiliation", - "leadMaintainer": "Alex Potsides ", + "version": "1.0.0", + "description": "Protobuf to ts transpiler", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/protons#readme", "repository": { "type": "git", - "url": "https://github.com/ipfs/protons" - }, - "dependencies": { - "protocol-buffers-schema": "^3.3.1", - "signed-varint": "^2.0.1", - "uint8arrays": "^3.0.0", - "varint": "^5.0.0" - }, - "devDependencies": { - "aegir": "^25.1.0", - "benchmark": "^2.1.4", - "protocol-buffers": "^4.1.0", - "protons": "^1.0.0", - "tape": "^4.8.0", - "util": "^0.12.4" - }, - "scripts": { - "test": "aegir test", - "test:browser": "aegir test --target browser", - "test:node": "aegir test --target node", - "lint": "aegir lint", - "release": "aegir release", - "release-minor": "aegir release --type minor", - "release-major": "aegir release --type major", - "build": "aegir build", - "bench": "node bench" + "url": "git+https://github.com/ipfs/protons.git" }, "bugs": { "url": "https://github.com/ipfs/protons/issues" }, - "homepage": "https://github.com/ipfs/protons", - "main": "src/index.js", - "directories": { - "test": "test" - }, "keywords": [ - "protobuf", - "protocol", - "buffers", - "protocolbuffers", - "encode", - "decode", - "google", - "serialize", - "parse", - "levelup", - "encodings", - "encoding" + "interface", + "libp2p" ], - "author": "Friedel Ziegelmayer ", - "license": "MIT", - "contributors": [ - "Mathias Buus ", - "achingbrain ", - "dignifiedquire ", - "David Dias ", - "Hector Sanjuan ", - "Young Hahn ", - "Mo Kamioner ", - "tcme ", - "Eduardo Sorribas ", - "Hugo Dias ", - "Lars-Magnus Skog ", - "Patrick Pfeiffer ", - "Kevin Ennis ", - "Elijah Insua ", - "Astro ", - "Andreas Madsen ", - "Sergii Shutovskyi ", - "Steven Allen ", - "David Bruant ", - "Irakli Gozalishvili ", - "João Antunes ", - "Brendan Ward " + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "private": true, + "scripts": { + "reset": "lerna run clean && rimraf ./node_modules ./package-lock.json packages/*/node_modules packages/*/package-lock.json packages/*/dist", + "test": "lerna run --concurrency 1 test -- --", + "test:node": "lerna run --concurrency 1 test:node -- --", + "test:chrome": "lerna run --concurrency 1 test:chrome -- --", + "test:chrome-webworker": "lerna --concurrency 1 run test:chrome-webworker -- --", + "test:firefox": "lerna run --concurrency 1 test:firefox -- --", + "test:firefox-webworker": "lerna run --concurrency 1 test:firefox-webworker -- --", + "test:electron-main": "lerna run --concurrency 1 test:electron-main -- --", + "test:electron-renderer": "lerna run --concurrency 1 test:electron-renderer -- --", + "build": "lerna run build", + "lint": "lerna run lint", + "dep-check": "lerna run dep-check", + "release": "lerna run release --concurrency 1" + }, + "dependencies": { + "lerna": "^4.0.0", + "rimraf": "^3.0.2" + }, + "workspaces": [ + "packages/*" ] } diff --git a/packages/protons-benchmark/LICENSE b/packages/protons-benchmark/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/packages/protons-benchmark/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/protons-benchmark/LICENSE-APACHE b/packages/protons-benchmark/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/packages/protons-benchmark/LICENSE-APACHE @@ -0,0 +1,5 @@ +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/packages/protons-benchmark/LICENSE-MIT b/packages/protons-benchmark/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/packages/protons-benchmark/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/protons-benchmark/README.md b/packages/protons-benchmark/README.md new file mode 100644 index 0000000..c72dcfd --- /dev/null +++ b/packages/protons-benchmark/README.md @@ -0,0 +1,45 @@ +# protons-benchmark + +> Benchmark protons against other protobuf modules + +## Table of contents + +- [Install](#install) +- [Usage](#usage) + +## Install + +- Clone the parent repo +- Install the deps +- Switch to the benchmark directory + +```console +$ git clone git@github.com:ipfs/protons.git +$ cd protons +$ npm i +$ cd packages/protons-benchmark +``` + +## Usage + +Run the benchmark suite: + +```console +$ npm start + +Running "Encode/Decode" suite... +Progress: 100% + + pbjs: + 12 166 ops/s, ±3.92% | 5.12% slower + + protons: + 9 755 ops/s, ±2.19% | slowest, 23.93% slower + + protobufjs: + 12 823 ops/s, ±2.02% | fastest + +Finished 3 cases! + Fastest: protobufjs + Slowest: protons +``` diff --git a/packages/protons-benchmark/package.json b/packages/protons-benchmark/package.json new file mode 100644 index 0000000..2194dcd --- /dev/null +++ b/packages/protons-benchmark/package.json @@ -0,0 +1,79 @@ +{ + "name": "protons-benchmark", + "version": "0.0.0", + "description": "Protobuf to ts transpiler", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/protons/tree/master/packages/protons-benchmark#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/protons.git" + }, + "bugs": { + "url": "https://github.com/ipfs/protons/issues" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist/src", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "import": "./dist/src/index.js", + "types": "./dist/src/index.d.ts" + }, + "./status": { + "import": "./dist/src/status.js", + "types": "./dist/src/status.d.ts" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + }, + "ignorePatterns": [ + "src/protobufjs/*.ts" + ] + }, + "scripts": { + "lint": "aegir lint", + "dep-check": "aegir dep-check dist/src/**/*.js dist/test/**/*.js", + "build": "tsc && cp src/protobufjs/bench.js dist/src/protobufjs", + "prestart": "npm run build", + "start": "node dist/src/index.js" + }, + "dependencies": { + "benny": "^3.7.1", + "pbjs": "^0.0.14", + "protobufjs": "^6.11.2", + "protons": "^2.0.0", + "protons-runtime": "^0.0.0" + }, + "devDependencies": { + "aegir": "^36.1.3" + }, + "private": true +} diff --git a/bench/bench.proto.js b/packages/protons-benchmark/src/bench.proto old mode 100644 new mode 100755 similarity index 75% rename from bench/bench.proto.js rename to packages/protons-benchmark/src/bench.proto index 2ff5f71..c50b5a8 --- a/bench/bench.proto.js +++ b/packages/protons-benchmark/src/bench.proto @@ -1,10 +1,9 @@ -'use strict' -module.exports = `message Bar { - message Foo { - optional uint32 baz = 1; - } +message Foo { + optional uint32 baz = 1; +} +message Bar { optional Foo tmp = 1; } @@ -27,4 +26,4 @@ message Test { optional uint32 hello = 3; optional string foo = 1; optional bytes payload = 7; -}` +} diff --git a/packages/protons-benchmark/src/index.ts b/packages/protons-benchmark/src/index.ts new file mode 100644 index 0000000..22fa008 --- /dev/null +++ b/packages/protons-benchmark/src/index.ts @@ -0,0 +1,56 @@ + +import benny from 'benny' +import { expect } from 'aegir/utils/chai.js' +import { Test as ProtonsTest } from './protons/bench.js' +import { encodeTest as pbjsEncodeTest, decodeTest as pbjsDecodeTest } from './pbjs/bench.js' +import { Test as ProtobufjsTest } from './protobufjs/bench.js' + +const message = { + meh: { + lol: 'sdkljfoee', + b: { + tmp: { + baz: 2309292 + } + } + }, + hello: 3493822, + foo: 'derp derp derp', + payload: Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +} + +function expectDecodedCorrectly (result: any) { + expect(result).to.have.nested.property('meh.lol', message.meh.lol) + expect(result).to.have.nested.property('meh.b.tmp.baz', message.meh.b.tmp.baz) + expect(result).to.have.property('hello', message.hello) + expect(result).to.have.property('foo', message.foo) + expect(result).to.have.property('payload').that.equalBytes(message.payload) +} + +void benny.suite( + 'Encode/Decode', + + benny.add('pbjs', () => { + const buf = pbjsEncodeTest(message) + const result = pbjsDecodeTest(buf) + + expectDecodedCorrectly(result) + }), + + benny.add('protons', () => { + const buf = ProtonsTest.encode(message) + const result = ProtonsTest.decode(buf) + + expectDecodedCorrectly(result) + }), + + benny.add('protobufjs', () => { + const buf = ProtobufjsTest.encode(message).finish() + const result = ProtobufjsTest.decode(buf) + + expectDecodedCorrectly(result) + }), + + benny.cycle(), + benny.complete() +) diff --git a/packages/protons-benchmark/src/pbjs/bench.ts b/packages/protons-benchmark/src/pbjs/bench.ts new file mode 100644 index 0000000..64bc154 --- /dev/null +++ b/packages/protons-benchmark/src/pbjs/bench.ts @@ -0,0 +1,837 @@ +/*eslint-disable*/ +// @ts-nocheck + +export const enum FOO { + LOL = "LOL", + ABE = "ABE", +} + +export const encodeFOO: { [key: string]: number } = { + LOL: 1, + ABE: 3, +}; + +export const decodeFOO: { [key: number]: FOO } = { + 1: FOO.LOL, + 3: FOO.ABE, +}; + +export interface Foo { + baz?: number; +} + +export function encodeFoo(message: Foo): Uint8Array { + let bb = popByteBuffer(); + _encodeFoo(message, bb); + return toUint8Array(bb); +} + +function _encodeFoo(message: Foo, bb: ByteBuffer): void { + // optional uint32 baz = 1; + let $baz = message.baz; + if ($baz !== undefined) { + writeVarint32(bb, 8); + writeVarint32(bb, $baz); + } +} + +export function decodeFoo(binary: Uint8Array): Foo { + return _decodeFoo(wrapByteBuffer(binary)); +} + +function _decodeFoo(bb: ByteBuffer): Foo { + let message: Foo = {} as any; + + end_of_message: while (!isAtEnd(bb)) { + let tag = readVarint32(bb); + + switch (tag >>> 3) { + case 0: + break end_of_message; + + // optional uint32 baz = 1; + case 1: { + message.baz = readVarint32(bb) >>> 0; + break; + } + + default: + skipUnknownField(bb, tag & 7); + } + } + + return message; +} + +export interface Bar { + tmp?: Foo; +} + +export function encodeBar(message: Bar): Uint8Array { + let bb = popByteBuffer(); + _encodeBar(message, bb); + return toUint8Array(bb); +} + +function _encodeBar(message: Bar, bb: ByteBuffer): void { + // optional Foo tmp = 1; + let $tmp = message.tmp; + if ($tmp !== undefined) { + writeVarint32(bb, 10); + let nested = popByteBuffer(); + _encodeFoo($tmp, nested); + writeVarint32(bb, nested.limit); + writeByteBuffer(bb, nested); + pushByteBuffer(nested); + } +} + +export function decodeBar(binary: Uint8Array): Bar { + return _decodeBar(wrapByteBuffer(binary)); +} + +function _decodeBar(bb: ByteBuffer): Bar { + let message: Bar = {} as any; + + end_of_message: while (!isAtEnd(bb)) { + let tag = readVarint32(bb); + + switch (tag >>> 3) { + case 0: + break end_of_message; + + // optional Foo tmp = 1; + case 1: { + let limit = pushTemporaryLength(bb); + message.tmp = _decodeFoo(bb); + bb.limit = limit; + break; + } + + default: + skipUnknownField(bb, tag & 7); + } + } + + return message; +} + +export interface Yo { + lol?: FOO[]; +} + +export function encodeYo(message: Yo): Uint8Array { + let bb = popByteBuffer(); + _encodeYo(message, bb); + return toUint8Array(bb); +} + +function _encodeYo(message: Yo, bb: ByteBuffer): void { + // repeated FOO lol = 1; + let array$lol = message.lol; + if (array$lol !== undefined) { + let packed = popByteBuffer(); + for (let value of array$lol) { + writeVarint32(packed, encodeFOO[value]); + } + writeVarint32(bb, 10); + writeVarint32(bb, packed.offset); + writeByteBuffer(bb, packed); + pushByteBuffer(packed); + } +} + +export function decodeYo(binary: Uint8Array): Yo { + return _decodeYo(wrapByteBuffer(binary)); +} + +function _decodeYo(bb: ByteBuffer): Yo { + let message: Yo = {} as any; + + end_of_message: while (!isAtEnd(bb)) { + let tag = readVarint32(bb); + + switch (tag >>> 3) { + case 0: + break end_of_message; + + // repeated FOO lol = 1; + case 1: { + let values = message.lol || (message.lol = []); + if ((tag & 7) === 2) { + let outerLimit = pushTemporaryLength(bb); + while (!isAtEnd(bb)) { + values.push(decodeFOO[readVarint32(bb)]); + } + bb.limit = outerLimit; + } else { + values.push(decodeFOO[readVarint32(bb)]); + } + break; + } + + default: + skipUnknownField(bb, tag & 7); + } + } + + return message; +} + +export interface Lol { + lol?: string; + b: Bar; +} + +export function encodeLol(message: Lol): Uint8Array { + let bb = popByteBuffer(); + _encodeLol(message, bb); + return toUint8Array(bb); +} + +function _encodeLol(message: Lol, bb: ByteBuffer): void { + // optional string lol = 1; + let $lol = message.lol; + if ($lol !== undefined) { + writeVarint32(bb, 10); + writeString(bb, $lol); + } + + // required Bar b = 2; + let $b = message.b; + if ($b !== undefined) { + writeVarint32(bb, 18); + let nested = popByteBuffer(); + _encodeBar($b, nested); + writeVarint32(bb, nested.limit); + writeByteBuffer(bb, nested); + pushByteBuffer(nested); + } +} + +export function decodeLol(binary: Uint8Array): Lol { + return _decodeLol(wrapByteBuffer(binary)); +} + +function _decodeLol(bb: ByteBuffer): Lol { + let message: Lol = {} as any; + + end_of_message: while (!isAtEnd(bb)) { + let tag = readVarint32(bb); + + switch (tag >>> 3) { + case 0: + break end_of_message; + + // optional string lol = 1; + case 1: { + message.lol = readString(bb, readVarint32(bb)); + break; + } + + // required Bar b = 2; + case 2: { + let limit = pushTemporaryLength(bb); + message.b = _decodeBar(bb); + bb.limit = limit; + break; + } + + default: + skipUnknownField(bb, tag & 7); + } + } + + if (message.b === undefined) + throw new Error("Missing required field: b"); + + return message; +} + +export interface Test { + meh?: Lol; + hello?: number; + foo?: string; + payload?: Uint8Array; +} + +export function encodeTest(message: Test): Uint8Array { + let bb = popByteBuffer(); + _encodeTest(message, bb); + return toUint8Array(bb); +} + +function _encodeTest(message: Test, bb: ByteBuffer): void { + // optional Lol meh = 6; + let $meh = message.meh; + if ($meh !== undefined) { + writeVarint32(bb, 50); + let nested = popByteBuffer(); + _encodeLol($meh, nested); + writeVarint32(bb, nested.limit); + writeByteBuffer(bb, nested); + pushByteBuffer(nested); + } + + // optional uint32 hello = 3; + let $hello = message.hello; + if ($hello !== undefined) { + writeVarint32(bb, 24); + writeVarint32(bb, $hello); + } + + // optional string foo = 1; + let $foo = message.foo; + if ($foo !== undefined) { + writeVarint32(bb, 10); + writeString(bb, $foo); + } + + // optional bytes payload = 7; + let $payload = message.payload; + if ($payload !== undefined) { + writeVarint32(bb, 58); + writeVarint32(bb, $payload.length), writeBytes(bb, $payload); + } +} + +export function decodeTest(binary: Uint8Array): Test { + return _decodeTest(wrapByteBuffer(binary)); +} + +function _decodeTest(bb: ByteBuffer): Test { + let message: Test = {} as any; + + end_of_message: while (!isAtEnd(bb)) { + let tag = readVarint32(bb); + + switch (tag >>> 3) { + case 0: + break end_of_message; + + // optional Lol meh = 6; + case 6: { + let limit = pushTemporaryLength(bb); + message.meh = _decodeLol(bb); + bb.limit = limit; + break; + } + + // optional uint32 hello = 3; + case 3: { + message.hello = readVarint32(bb) >>> 0; + break; + } + + // optional string foo = 1; + case 1: { + message.foo = readString(bb, readVarint32(bb)); + break; + } + + // optional bytes payload = 7; + case 7: { + message.payload = readBytes(bb, readVarint32(bb)); + break; + } + + default: + skipUnknownField(bb, tag & 7); + } + } + + return message; +} + +export interface Long { + low: number; + high: number; + unsigned: boolean; +} + +interface ByteBuffer { + bytes: Uint8Array; + offset: number; + limit: number; +} + +function pushTemporaryLength(bb: ByteBuffer): number { + let length = readVarint32(bb); + let limit = bb.limit; + bb.limit = bb.offset + length; + return limit; +} + +function skipUnknownField(bb: ByteBuffer, type: number): void { + switch (type) { + case 0: while (readByte(bb) & 0x80) { } break; + case 2: skip(bb, readVarint32(bb)); break; + case 5: skip(bb, 4); break; + case 1: skip(bb, 8); break; + default: throw new Error("Unimplemented type: " + type); + } +} + +function stringToLong(value: string): Long { + return { + low: value.charCodeAt(0) | (value.charCodeAt(1) << 16), + high: value.charCodeAt(2) | (value.charCodeAt(3) << 16), + unsigned: false, + }; +} + +function longToString(value: Long): string { + let low = value.low; + let high = value.high; + return String.fromCharCode( + low & 0xFFFF, + low >>> 16, + high & 0xFFFF, + high >>> 16); +} + +// The code below was modified from https://github.com/protobufjs/bytebuffer.js +// which is under the Apache License 2.0. + +let f32 = new Float32Array(1); +let f32_u8 = new Uint8Array(f32.buffer); + +let f64 = new Float64Array(1); +let f64_u8 = new Uint8Array(f64.buffer); + +function intToLong(value: number): Long { + value |= 0; + return { + low: value, + high: value >> 31, + unsigned: value >= 0, + }; +} + +let bbStack: ByteBuffer[] = []; + +function popByteBuffer(): ByteBuffer { + const bb = bbStack.pop(); + if (!bb) return { bytes: new Uint8Array(64), offset: 0, limit: 0 }; + bb.offset = bb.limit = 0; + return bb; +} + +function pushByteBuffer(bb: ByteBuffer): void { + bbStack.push(bb); +} + +function wrapByteBuffer(bytes: Uint8Array): ByteBuffer { + return { bytes, offset: 0, limit: bytes.length }; +} + +function toUint8Array(bb: ByteBuffer): Uint8Array { + let bytes = bb.bytes; + let limit = bb.limit; + return bytes.length === limit ? bytes : bytes.subarray(0, limit); +} + +function skip(bb: ByteBuffer, offset: number): void { + if (bb.offset + offset > bb.limit) { + throw new Error('Skip past limit'); + } + bb.offset += offset; +} + +function isAtEnd(bb: ByteBuffer): boolean { + return bb.offset >= bb.limit; +} + +function grow(bb: ByteBuffer, count: number): number { + let bytes = bb.bytes; + let offset = bb.offset; + let limit = bb.limit; + let finalOffset = offset + count; + if (finalOffset > bytes.length) { + let newBytes = new Uint8Array(finalOffset * 2); + newBytes.set(bytes); + bb.bytes = newBytes; + } + bb.offset = finalOffset; + if (finalOffset > limit) { + bb.limit = finalOffset; + } + return offset; +} + +function advance(bb: ByteBuffer, count: number): number { + let offset = bb.offset; + if (offset + count > bb.limit) { + throw new Error('Read past limit'); + } + bb.offset += count; + return offset; +} + +function readBytes(bb: ByteBuffer, count: number): Uint8Array { + let offset = advance(bb, count); + return bb.bytes.subarray(offset, offset + count); +} + +function writeBytes(bb: ByteBuffer, buffer: Uint8Array): void { + let offset = grow(bb, buffer.length); + bb.bytes.set(buffer, offset); +} + +function readString(bb: ByteBuffer, count: number): string { + // Sadly a hand-coded UTF8 decoder is much faster than subarray+TextDecoder in V8 + let offset = advance(bb, count); + let fromCharCode = String.fromCharCode; + let bytes = bb.bytes; + let invalid = '\uFFFD'; + let text = ''; + + for (let i = 0; i < count; i++) { + let c1 = bytes[i + offset], c2: number, c3: number, c4: number, c: number; + + // 1 byte + if ((c1 & 0x80) === 0) { + text += fromCharCode(c1); + } + + // 2 bytes + else if ((c1 & 0xE0) === 0xC0) { + if (i + 1 >= count) text += invalid; + else { + c2 = bytes[i + offset + 1]; + if ((c2 & 0xC0) !== 0x80) text += invalid; + else { + c = ((c1 & 0x1F) << 6) | (c2 & 0x3F); + if (c < 0x80) text += invalid; + else { + text += fromCharCode(c); + i++; + } + } + } + } + + // 3 bytes + else if ((c1 & 0xF0) == 0xE0) { + if (i + 2 >= count) text += invalid; + else { + c2 = bytes[i + offset + 1]; + c3 = bytes[i + offset + 2]; + if (((c2 | (c3 << 8)) & 0xC0C0) !== 0x8080) text += invalid; + else { + c = ((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F); + if (c < 0x0800 || (c >= 0xD800 && c <= 0xDFFF)) text += invalid; + else { + text += fromCharCode(c); + i += 2; + } + } + } + } + + // 4 bytes + else if ((c1 & 0xF8) == 0xF0) { + if (i + 3 >= count) text += invalid; + else { + c2 = bytes[i + offset + 1]; + c3 = bytes[i + offset + 2]; + c4 = bytes[i + offset + 3]; + if (((c2 | (c3 << 8) | (c4 << 16)) & 0xC0C0C0) !== 0x808080) text += invalid; + else { + c = ((c1 & 0x07) << 0x12) | ((c2 & 0x3F) << 0x0C) | ((c3 & 0x3F) << 0x06) | (c4 & 0x3F); + if (c < 0x10000 || c > 0x10FFFF) text += invalid; + else { + c -= 0x10000; + text += fromCharCode((c >> 10) + 0xD800, (c & 0x3FF) + 0xDC00); + i += 3; + } + } + } + } + + else text += invalid; + } + + return text; +} + +function writeString(bb: ByteBuffer, text: string): void { + // Sadly a hand-coded UTF8 encoder is much faster than TextEncoder+set in V8 + let n = text.length; + let byteCount = 0; + + // Write the byte count first + for (let i = 0; i < n; i++) { + let c = text.charCodeAt(i); + if (c >= 0xD800 && c <= 0xDBFF && i + 1 < n) { + c = (c << 10) + text.charCodeAt(++i) - 0x35FDC00; + } + byteCount += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } + writeVarint32(bb, byteCount); + + let offset = grow(bb, byteCount); + let bytes = bb.bytes; + + // Then write the bytes + for (let i = 0; i < n; i++) { + let c = text.charCodeAt(i); + if (c >= 0xD800 && c <= 0xDBFF && i + 1 < n) { + c = (c << 10) + text.charCodeAt(++i) - 0x35FDC00; + } + if (c < 0x80) { + bytes[offset++] = c; + } else { + if (c < 0x800) { + bytes[offset++] = ((c >> 6) & 0x1F) | 0xC0; + } else { + if (c < 0x10000) { + bytes[offset++] = ((c >> 12) & 0x0F) | 0xE0; + } else { + bytes[offset++] = ((c >> 18) & 0x07) | 0xF0; + bytes[offset++] = ((c >> 12) & 0x3F) | 0x80; + } + bytes[offset++] = ((c >> 6) & 0x3F) | 0x80; + } + bytes[offset++] = (c & 0x3F) | 0x80; + } + } +} + +function writeByteBuffer(bb: ByteBuffer, buffer: ByteBuffer): void { + let offset = grow(bb, buffer.limit); + let from = bb.bytes; + let to = buffer.bytes; + + // This for loop is much faster than subarray+set on V8 + for (let i = 0, n = buffer.limit; i < n; i++) { + from[i + offset] = to[i]; + } +} + +function readByte(bb: ByteBuffer): number { + return bb.bytes[advance(bb, 1)]; +} + +function writeByte(bb: ByteBuffer, value: number): void { + let offset = grow(bb, 1); + bb.bytes[offset] = value; +} + +function readFloat(bb: ByteBuffer): number { + let offset = advance(bb, 4); + let bytes = bb.bytes; + + // Manual copying is much faster than subarray+set in V8 + f32_u8[0] = bytes[offset++]; + f32_u8[1] = bytes[offset++]; + f32_u8[2] = bytes[offset++]; + f32_u8[3] = bytes[offset++]; + return f32[0]; +} + +function writeFloat(bb: ByteBuffer, value: number): void { + let offset = grow(bb, 4); + let bytes = bb.bytes; + f32[0] = value; + + // Manual copying is much faster than subarray+set in V8 + bytes[offset++] = f32_u8[0]; + bytes[offset++] = f32_u8[1]; + bytes[offset++] = f32_u8[2]; + bytes[offset++] = f32_u8[3]; +} + +function readDouble(bb: ByteBuffer): number { + let offset = advance(bb, 8); + let bytes = bb.bytes; + + // Manual copying is much faster than subarray+set in V8 + f64_u8[0] = bytes[offset++]; + f64_u8[1] = bytes[offset++]; + f64_u8[2] = bytes[offset++]; + f64_u8[3] = bytes[offset++]; + f64_u8[4] = bytes[offset++]; + f64_u8[5] = bytes[offset++]; + f64_u8[6] = bytes[offset++]; + f64_u8[7] = bytes[offset++]; + return f64[0]; +} + +function writeDouble(bb: ByteBuffer, value: number): void { + let offset = grow(bb, 8); + let bytes = bb.bytes; + f64[0] = value; + + // Manual copying is much faster than subarray+set in V8 + bytes[offset++] = f64_u8[0]; + bytes[offset++] = f64_u8[1]; + bytes[offset++] = f64_u8[2]; + bytes[offset++] = f64_u8[3]; + bytes[offset++] = f64_u8[4]; + bytes[offset++] = f64_u8[5]; + bytes[offset++] = f64_u8[6]; + bytes[offset++] = f64_u8[7]; +} + +function readInt32(bb: ByteBuffer): number { + let offset = advance(bb, 4); + let bytes = bb.bytes; + return ( + bytes[offset] | + (bytes[offset + 1] << 8) | + (bytes[offset + 2] << 16) | + (bytes[offset + 3] << 24) + ); +} + +function writeInt32(bb: ByteBuffer, value: number): void { + let offset = grow(bb, 4); + let bytes = bb.bytes; + bytes[offset] = value; + bytes[offset + 1] = value >> 8; + bytes[offset + 2] = value >> 16; + bytes[offset + 3] = value >> 24; +} + +function readInt64(bb: ByteBuffer, unsigned: boolean): Long { + return { + low: readInt32(bb), + high: readInt32(bb), + unsigned, + }; +} + +function writeInt64(bb: ByteBuffer, value: Long): void { + writeInt32(bb, value.low); + writeInt32(bb, value.high); +} + +function readVarint32(bb: ByteBuffer): number { + let c = 0; + let value = 0; + let b: number; + do { + b = readByte(bb); + if (c < 32) value |= (b & 0x7F) << c; + c += 7; + } while (b & 0x80); + return value; +} + +function writeVarint32(bb: ByteBuffer, value: number): void { + value >>>= 0; + while (value >= 0x80) { + writeByte(bb, (value & 0x7f) | 0x80); + value >>>= 7; + } + writeByte(bb, value); +} + +function readVarint64(bb: ByteBuffer, unsigned: boolean): Long { + let part0 = 0; + let part1 = 0; + let part2 = 0; + let b: number; + + b = readByte(bb); part0 = (b & 0x7F); if (b & 0x80) { + b = readByte(bb); part0 |= (b & 0x7F) << 7; if (b & 0x80) { + b = readByte(bb); part0 |= (b & 0x7F) << 14; if (b & 0x80) { + b = readByte(bb); part0 |= (b & 0x7F) << 21; if (b & 0x80) { + + b = readByte(bb); part1 = (b & 0x7F); if (b & 0x80) { + b = readByte(bb); part1 |= (b & 0x7F) << 7; if (b & 0x80) { + b = readByte(bb); part1 |= (b & 0x7F) << 14; if (b & 0x80) { + b = readByte(bb); part1 |= (b & 0x7F) << 21; if (b & 0x80) { + + b = readByte(bb); part2 = (b & 0x7F); if (b & 0x80) { + b = readByte(bb); part2 |= (b & 0x7F) << 7; + } + } + } + } + } + } + } + } + } + + return { + low: part0 | (part1 << 28), + high: (part1 >>> 4) | (part2 << 24), + unsigned, + }; +} + +function writeVarint64(bb: ByteBuffer, value: Long): void { + let part0 = value.low >>> 0; + let part1 = ((value.low >>> 28) | (value.high << 4)) >>> 0; + let part2 = value.high >>> 24; + + // ref: src/google/protobuf/io/coded_stream.cc + let size = + part2 === 0 ? + part1 === 0 ? + part0 < 1 << 14 ? + part0 < 1 << 7 ? 1 : 2 : + part0 < 1 << 21 ? 3 : 4 : + part1 < 1 << 14 ? + part1 < 1 << 7 ? 5 : 6 : + part1 < 1 << 21 ? 7 : 8 : + part2 < 1 << 7 ? 9 : 10; + + let offset = grow(bb, size); + let bytes = bb.bytes; + + switch (size) { + case 10: bytes[offset + 9] = (part2 >>> 7) & 0x01; + case 9: bytes[offset + 8] = size !== 9 ? part2 | 0x80 : part2 & 0x7F; + case 8: bytes[offset + 7] = size !== 8 ? (part1 >>> 21) | 0x80 : (part1 >>> 21) & 0x7F; + case 7: bytes[offset + 6] = size !== 7 ? (part1 >>> 14) | 0x80 : (part1 >>> 14) & 0x7F; + case 6: bytes[offset + 5] = size !== 6 ? (part1 >>> 7) | 0x80 : (part1 >>> 7) & 0x7F; + case 5: bytes[offset + 4] = size !== 5 ? part1 | 0x80 : part1 & 0x7F; + case 4: bytes[offset + 3] = size !== 4 ? (part0 >>> 21) | 0x80 : (part0 >>> 21) & 0x7F; + case 3: bytes[offset + 2] = size !== 3 ? (part0 >>> 14) | 0x80 : (part0 >>> 14) & 0x7F; + case 2: bytes[offset + 1] = size !== 2 ? (part0 >>> 7) | 0x80 : (part0 >>> 7) & 0x7F; + case 1: bytes[offset] = size !== 1 ? part0 | 0x80 : part0 & 0x7F; + } +} + +function readVarint32ZigZag(bb: ByteBuffer): number { + let value = readVarint32(bb); + + // ref: src/google/protobuf/wire_format_lite.h + return (value >>> 1) ^ -(value & 1); +} + +function writeVarint32ZigZag(bb: ByteBuffer, value: number): void { + // ref: src/google/protobuf/wire_format_lite.h + writeVarint32(bb, (value << 1) ^ (value >> 31)); +} + +function readVarint64ZigZag(bb: ByteBuffer): Long { + let value = readVarint64(bb, /* unsigned */ false); + let low = value.low; + let high = value.high; + let flip = -(low & 1); + + // ref: src/google/protobuf/wire_format_lite.h + return { + low: ((low >>> 1) | (high << 31)) ^ flip, + high: (high >>> 1) ^ flip, + unsigned: false, + }; +} + +function writeVarint64ZigZag(bb: ByteBuffer, value: Long): void { + let low = value.low; + let high = value.high; + let flip = high >> 31; + + // ref: src/google/protobuf/wire_format_lite.h + writeVarint64(bb, { + low: (low << 1) ^ flip, + high: ((high << 1) | (low >>> 31)) ^ flip, + unsigned: false, + }); +} diff --git a/packages/protons-benchmark/src/protobufjs/bench.d.ts b/packages/protons-benchmark/src/protobufjs/bench.d.ts new file mode 100644 index 0000000..9b55831 --- /dev/null +++ b/packages/protons-benchmark/src/protobufjs/bench.d.ts @@ -0,0 +1,292 @@ +import * as $protobuf from "protobufjs"; +/** Properties of a Foo. */ +export interface IFoo { + + /** Foo baz */ + baz?: (number|null); +} + +/** Represents a Foo. */ +export class Foo implements IFoo { + + /** + * Constructs a new Foo. + * @param [p] Properties to set + */ + constructor(p?: IFoo); + + /** Foo baz. */ + public baz: number; + + /** + * Encodes the specified Foo message. Does not implicitly {@link Foo.verify|verify} messages. + * @param m Foo message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IFoo, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Foo message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Foo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Foo; + + /** + * Creates a Foo message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Foo + */ + public static fromObject(d: { [k: string]: any }): Foo; + + /** + * Creates a plain object from a Foo message. Also converts values to other types if specified. + * @param m Foo + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Foo, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Foo to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** Properties of a Bar. */ +export interface IBar { + + /** Bar tmp */ + tmp?: (IFoo|null); +} + +/** Represents a Bar. */ +export class Bar implements IBar { + + /** + * Constructs a new Bar. + * @param [p] Properties to set + */ + constructor(p?: IBar); + + /** Bar tmp. */ + public tmp?: (IFoo|null); + + /** + * Encodes the specified Bar message. Does not implicitly {@link Bar.verify|verify} messages. + * @param m Bar message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IBar, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Bar message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Bar + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Bar; + + /** + * Creates a Bar message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Bar + */ + public static fromObject(d: { [k: string]: any }): Bar; + + /** + * Creates a plain object from a Bar message. Also converts values to other types if specified. + * @param m Bar + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Bar, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Bar to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** FOO enum. */ +export enum FOO { + LOL = 1, + ABE = 3 +} + +/** Represents a Yo. */ +export class Yo implements IYo { + + /** + * Constructs a new Yo. + * @param [p] Properties to set + */ + constructor(p?: IYo); + + /** Yo lol. */ + public lol: FOO[]; + + /** + * Encodes the specified Yo message. Does not implicitly {@link Yo.verify|verify} messages. + * @param m Yo message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: IYo, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Yo message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Yo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Yo; + + /** + * Creates a Yo message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Yo + */ + public static fromObject(d: { [k: string]: any }): Yo; + + /** + * Creates a plain object from a Yo message. Also converts values to other types if specified. + * @param m Yo + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Yo, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Yo to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** Represents a Lol. */ +export class Lol implements ILol { + + /** + * Constructs a new Lol. + * @param [p] Properties to set + */ + constructor(p?: ILol); + + /** Lol lol. */ + public lol: string; + + /** Lol b. */ + public b: IBar; + + /** + * Encodes the specified Lol message. Does not implicitly {@link Lol.verify|verify} messages. + * @param m Lol message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: ILol, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Lol message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Lol + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Lol; + + /** + * Creates a Lol message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Lol + */ + public static fromObject(d: { [k: string]: any }): Lol; + + /** + * Creates a plain object from a Lol message. Also converts values to other types if specified. + * @param m Lol + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Lol, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Lol to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} + +/** Represents a Test. */ +export class Test implements ITest { + + /** + * Constructs a new Test. + * @param [p] Properties to set + */ + constructor(p?: ITest); + + /** Test meh. */ + public meh?: (ILol|null); + + /** Test hello. */ + public hello: number; + + /** Test foo. */ + public foo: string; + + /** Test payload. */ + public payload: Uint8Array; + + /** + * Encodes the specified Test message. Does not implicitly {@link Test.verify|verify} messages. + * @param m Test message or plain object to encode + * @param [w] Writer to encode to + * @returns Writer + */ + public static encode(m: ITest, w?: $protobuf.Writer): $protobuf.Writer; + + /** + * Decodes a Test message from the specified reader or buffer. + * @param r Reader or buffer to decode from + * @param [l] Message length if known beforehand + * @returns Test + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Test; + + /** + * Creates a Test message from a plain object. Also converts values to their respective internal types. + * @param d Plain object + * @returns Test + */ + public static fromObject(d: { [k: string]: any }): Test; + + /** + * Creates a plain object from a Test message. Also converts values to other types if specified. + * @param m Test + * @param [o] Conversion options + * @returns Plain object + */ + public static toObject(m: Test, o?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Test to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; +} diff --git a/packages/protons-benchmark/src/protobufjs/bench.js b/packages/protons-benchmark/src/protobufjs/bench.js new file mode 100644 index 0000000..6a33065 --- /dev/null +++ b/packages/protons-benchmark/src/protobufjs/bench.js @@ -0,0 +1,813 @@ +/*eslint-disable*/ +// @ts-nocheck +import $protobuf from "protobufjs/minimal.js"; + +// Common aliases +const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +const $root = $protobuf.roots.bench || ($protobuf.roots.bench = {}); + +export const Foo = $root.Foo = (() => { + + /** + * Properties of a Foo. + * @exports IFoo + * @interface IFoo + * @property {number|null} [baz] Foo baz + */ + + /** + * Constructs a new Foo. + * @exports Foo + * @classdesc Represents a Foo. + * @implements IFoo + * @constructor + * @param {IFoo=} [p] Properties to set + */ + function Foo(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Foo baz. + * @member {number} baz + * @memberof Foo + * @instance + */ + Foo.prototype.baz = 0; + + /** + * Encodes the specified Foo message. Does not implicitly {@link Foo.verify|verify} messages. + * @function encode + * @memberof Foo + * @static + * @param {IFoo} m Foo message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Foo.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.baz != null && Object.hasOwnProperty.call(m, "baz")) + w.uint32(8).uint32(m.baz); + return w; + }; + + /** + * Decodes a Foo message from the specified reader or buffer. + * @function decode + * @memberof Foo + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Foo} Foo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Foo.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Foo(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.baz = r.uint32(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a Foo message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Foo + * @static + * @param {Object.} d Plain object + * @returns {Foo} Foo + */ + Foo.fromObject = function fromObject(d) { + if (d instanceof $root.Foo) + return d; + var m = new $root.Foo(); + if (d.baz != null) { + m.baz = d.baz >>> 0; + } + return m; + }; + + /** + * Creates a plain object from a Foo message. Also converts values to other types if specified. + * @function toObject + * @memberof Foo + * @static + * @param {Foo} m Foo + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Foo.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.baz = 0; + } + if (m.baz != null && m.hasOwnProperty("baz")) { + d.baz = m.baz; + } + return d; + }; + + /** + * Converts this Foo to JSON. + * @function toJSON + * @memberof Foo + * @instance + * @returns {Object.} JSON object + */ + Foo.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Foo; +})(); + +export const Bar = $root.Bar = (() => { + + /** + * Properties of a Bar. + * @exports IBar + * @interface IBar + * @property {IFoo|null} [tmp] Bar tmp + */ + + /** + * Constructs a new Bar. + * @exports Bar + * @classdesc Represents a Bar. + * @implements IBar + * @constructor + * @param {IBar=} [p] Properties to set + */ + function Bar(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Bar tmp. + * @member {IFoo|null|undefined} tmp + * @memberof Bar + * @instance + */ + Bar.prototype.tmp = null; + + /** + * Encodes the specified Bar message. Does not implicitly {@link Bar.verify|verify} messages. + * @function encode + * @memberof Bar + * @static + * @param {IBar} m Bar message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Bar.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.tmp != null && Object.hasOwnProperty.call(m, "tmp")) + $root.Foo.encode(m.tmp, w.uint32(10).fork()).ldelim(); + return w; + }; + + /** + * Decodes a Bar message from the specified reader or buffer. + * @function decode + * @memberof Bar + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Bar} Bar + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Bar.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Bar(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.tmp = $root.Foo.decode(r, r.uint32()); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a Bar message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Bar + * @static + * @param {Object.} d Plain object + * @returns {Bar} Bar + */ + Bar.fromObject = function fromObject(d) { + if (d instanceof $root.Bar) + return d; + var m = new $root.Bar(); + if (d.tmp != null) { + if (typeof d.tmp !== "object") + throw TypeError(".Bar.tmp: object expected"); + m.tmp = $root.Foo.fromObject(d.tmp); + } + return m; + }; + + /** + * Creates a plain object from a Bar message. Also converts values to other types if specified. + * @function toObject + * @memberof Bar + * @static + * @param {Bar} m Bar + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Bar.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.tmp = null; + } + if (m.tmp != null && m.hasOwnProperty("tmp")) { + d.tmp = $root.Foo.toObject(m.tmp, o); + } + return d; + }; + + /** + * Converts this Bar to JSON. + * @function toJSON + * @memberof Bar + * @instance + * @returns {Object.} JSON object + */ + Bar.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Bar; +})(); + +/** + * FOO enum. + * @exports FOO + * @enum {number} + * @property {number} LOL=1 LOL value + * @property {number} ABE=3 ABE value + */ +export const FOO = $root.FOO = (() => { + const valuesById = {}, values = Object.create(valuesById); + values[valuesById[1] = "LOL"] = 1; + values[valuesById[3] = "ABE"] = 3; + return values; +})(); + +export const Yo = $root.Yo = (() => { + + /** + * Properties of a Yo. + * @exports IYo + * @interface IYo + * @property {Array.|null} [lol] Yo lol + */ + + /** + * Constructs a new Yo. + * @exports Yo + * @classdesc Represents a Yo. + * @implements IYo + * @constructor + * @param {IYo=} [p] Properties to set + */ + function Yo(p) { + this.lol = []; + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Yo lol. + * @member {Array.} lol + * @memberof Yo + * @instance + */ + Yo.prototype.lol = $util.emptyArray; + + /** + * Encodes the specified Yo message. Does not implicitly {@link Yo.verify|verify} messages. + * @function encode + * @memberof Yo + * @static + * @param {IYo} m Yo message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Yo.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.lol != null && m.lol.length) { + for (var i = 0; i < m.lol.length; ++i) + w.uint32(8).int32(m.lol[i]); + } + return w; + }; + + /** + * Decodes a Yo message from the specified reader or buffer. + * @function decode + * @memberof Yo + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Yo} Yo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Yo.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Yo(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + if (!(m.lol && m.lol.length)) + m.lol = []; + if ((t & 7) === 2) { + var c2 = r.uint32() + r.pos; + while (r.pos < c2) + m.lol.push(r.int32()); + } else + m.lol.push(r.int32()); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a Yo message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Yo + * @static + * @param {Object.} d Plain object + * @returns {Yo} Yo + */ + Yo.fromObject = function fromObject(d) { + if (d instanceof $root.Yo) + return d; + var m = new $root.Yo(); + if (d.lol) { + if (!Array.isArray(d.lol)) + throw TypeError(".Yo.lol: array expected"); + m.lol = []; + for (var i = 0; i < d.lol.length; ++i) { + switch (d.lol[i]) { + default: + case "LOL": + case 1: + m.lol[i] = 1; + break; + case "ABE": + case 3: + m.lol[i] = 3; + break; + } + } + } + return m; + }; + + /** + * Creates a plain object from a Yo message. Also converts values to other types if specified. + * @function toObject + * @memberof Yo + * @static + * @param {Yo} m Yo + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Yo.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.arrays || o.defaults) { + d.lol = []; + } + if (m.lol && m.lol.length) { + d.lol = []; + for (var j = 0; j < m.lol.length; ++j) { + d.lol[j] = o.enums === String ? $root.FOO[m.lol[j]] : m.lol[j]; + } + } + return d; + }; + + /** + * Converts this Yo to JSON. + * @function toJSON + * @memberof Yo + * @instance + * @returns {Object.} JSON object + */ + Yo.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Yo; +})(); + +export const Lol = $root.Lol = (() => { + + /** + * Properties of a Lol. + * @exports ILol + * @interface ILol + * @property {string|null} [lol] Lol lol + * @property {IBar} b Lol b + */ + + /** + * Constructs a new Lol. + * @exports Lol + * @classdesc Represents a Lol. + * @implements ILol + * @constructor + * @param {ILol=} [p] Properties to set + */ + function Lol(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Lol lol. + * @member {string} lol + * @memberof Lol + * @instance + */ + Lol.prototype.lol = ""; + + /** + * Lol b. + * @member {IBar} b + * @memberof Lol + * @instance + */ + Lol.prototype.b = null; + + /** + * Encodes the specified Lol message. Does not implicitly {@link Lol.verify|verify} messages. + * @function encode + * @memberof Lol + * @static + * @param {ILol} m Lol message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Lol.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.lol != null && Object.hasOwnProperty.call(m, "lol")) + w.uint32(10).string(m.lol); + $root.Bar.encode(m.b, w.uint32(18).fork()).ldelim(); + return w; + }; + + /** + * Decodes a Lol message from the specified reader or buffer. + * @function decode + * @memberof Lol + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Lol} Lol + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Lol.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Lol(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 1: + m.lol = r.string(); + break; + case 2: + m.b = $root.Bar.decode(r, r.uint32()); + break; + default: + r.skipType(t & 7); + break; + } + } + if (!m.hasOwnProperty("b")) + throw $util.ProtocolError("missing required 'b'", { instance: m }); + return m; + }; + + /** + * Creates a Lol message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Lol + * @static + * @param {Object.} d Plain object + * @returns {Lol} Lol + */ + Lol.fromObject = function fromObject(d) { + if (d instanceof $root.Lol) + return d; + var m = new $root.Lol(); + if (d.lol != null) { + m.lol = String(d.lol); + } + if (d.b != null) { + if (typeof d.b !== "object") + throw TypeError(".Lol.b: object expected"); + m.b = $root.Bar.fromObject(d.b); + } + return m; + }; + + /** + * Creates a plain object from a Lol message. Also converts values to other types if specified. + * @function toObject + * @memberof Lol + * @static + * @param {Lol} m Lol + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Lol.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.lol = ""; + d.b = null; + } + if (m.lol != null && m.hasOwnProperty("lol")) { + d.lol = m.lol; + } + if (m.b != null && m.hasOwnProperty("b")) { + d.b = $root.Bar.toObject(m.b, o); + } + return d; + }; + + /** + * Converts this Lol to JSON. + * @function toJSON + * @memberof Lol + * @instance + * @returns {Object.} JSON object + */ + Lol.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Lol; +})(); + +export const Test = $root.Test = (() => { + + /** + * Properties of a Test. + * @exports ITest + * @interface ITest + * @property {ILol|null} [meh] Test meh + * @property {number|null} [hello] Test hello + * @property {string|null} [foo] Test foo + * @property {Uint8Array|null} [payload] Test payload + */ + + /** + * Constructs a new Test. + * @exports Test + * @classdesc Represents a Test. + * @implements ITest + * @constructor + * @param {ITest=} [p] Properties to set + */ + function Test(p) { + if (p) + for (var ks = Object.keys(p), i = 0; i < ks.length; ++i) + if (p[ks[i]] != null) + this[ks[i]] = p[ks[i]]; + } + + /** + * Test meh. + * @member {ILol|null|undefined} meh + * @memberof Test + * @instance + */ + Test.prototype.meh = null; + + /** + * Test hello. + * @member {number} hello + * @memberof Test + * @instance + */ + Test.prototype.hello = 0; + + /** + * Test foo. + * @member {string} foo + * @memberof Test + * @instance + */ + Test.prototype.foo = ""; + + /** + * Test payload. + * @member {Uint8Array} payload + * @memberof Test + * @instance + */ + Test.prototype.payload = $util.newBuffer([]); + + /** + * Encodes the specified Test message. Does not implicitly {@link Test.verify|verify} messages. + * @function encode + * @memberof Test + * @static + * @param {ITest} m Test message or plain object to encode + * @param {$protobuf.Writer} [w] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Test.encode = function encode(m, w) { + if (!w) + w = $Writer.create(); + if (m.foo != null && Object.hasOwnProperty.call(m, "foo")) + w.uint32(10).string(m.foo); + if (m.hello != null && Object.hasOwnProperty.call(m, "hello")) + w.uint32(24).uint32(m.hello); + if (m.meh != null && Object.hasOwnProperty.call(m, "meh")) + $root.Lol.encode(m.meh, w.uint32(50).fork()).ldelim(); + if (m.payload != null && Object.hasOwnProperty.call(m, "payload")) + w.uint32(58).bytes(m.payload); + return w; + }; + + /** + * Decodes a Test message from the specified reader or buffer. + * @function decode + * @memberof Test + * @static + * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from + * @param {number} [l] Message length if known beforehand + * @returns {Test} Test + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Test.decode = function decode(r, l) { + if (!(r instanceof $Reader)) + r = $Reader.create(r); + var c = l === undefined ? r.len : r.pos + l, m = new $root.Test(); + while (r.pos < c) { + var t = r.uint32(); + switch (t >>> 3) { + case 6: + m.meh = $root.Lol.decode(r, r.uint32()); + break; + case 3: + m.hello = r.uint32(); + break; + case 1: + m.foo = r.string(); + break; + case 7: + m.payload = r.bytes(); + break; + default: + r.skipType(t & 7); + break; + } + } + return m; + }; + + /** + * Creates a Test message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof Test + * @static + * @param {Object.} d Plain object + * @returns {Test} Test + */ + Test.fromObject = function fromObject(d) { + if (d instanceof $root.Test) + return d; + var m = new $root.Test(); + if (d.meh != null) { + if (typeof d.meh !== "object") + throw TypeError(".Test.meh: object expected"); + m.meh = $root.Lol.fromObject(d.meh); + } + if (d.hello != null) { + m.hello = d.hello >>> 0; + } + if (d.foo != null) { + m.foo = String(d.foo); + } + if (d.payload != null) { + if (typeof d.payload === "string") + $util.base64.decode(d.payload, m.payload = $util.newBuffer($util.base64.length(d.payload)), 0); + else if (d.payload.length) + m.payload = d.payload; + } + return m; + }; + + /** + * Creates a plain object from a Test message. Also converts values to other types if specified. + * @function toObject + * @memberof Test + * @static + * @param {Test} m Test + * @param {$protobuf.IConversionOptions} [o] Conversion options + * @returns {Object.} Plain object + */ + Test.toObject = function toObject(m, o) { + if (!o) + o = {}; + var d = {}; + if (o.defaults) { + d.foo = ""; + d.hello = 0; + d.meh = null; + if (o.bytes === String) + d.payload = ""; + else { + d.payload = []; + if (o.bytes !== Array) + d.payload = $util.newBuffer(d.payload); + } + } + if (m.foo != null && m.hasOwnProperty("foo")) { + d.foo = m.foo; + } + if (m.hello != null && m.hasOwnProperty("hello")) { + d.hello = m.hello; + } + if (m.meh != null && m.hasOwnProperty("meh")) { + d.meh = $root.Lol.toObject(m.meh, o); + } + if (m.payload != null && m.hasOwnProperty("payload")) { + d.payload = o.bytes === String ? $util.base64.encode(m.payload, 0, m.payload.length) : o.bytes === Array ? Array.prototype.slice.call(m.payload) : m.payload; + } + return d; + }; + + /** + * Converts this Test to JSON. + * @function toJSON + * @memberof Test + * @instance + * @returns {Object.} JSON object + */ + Test.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return Test; +})(); + +export { $root as default }; diff --git a/packages/protons-benchmark/src/protons/bench.ts b/packages/protons-benchmark/src/protons/bench.ts new file mode 100644 index 0000000..5ec9d7d --- /dev/null +++ b/packages/protons-benchmark/src/protons/bench.ts @@ -0,0 +1,111 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { encodeMessage, decodeMessage, message, uint32, enumeration, string, bytes } from 'protons-runtime' + +export interface Foo { + baz: number +} + +export namespace Foo { + export const codec = message({ + 1: { name: 'baz', codec: uint32 } + }) + + export const encode = (obj: Foo): Uint8Array => { + return encodeMessage(obj, Foo.codec) + } + + export const decode = (buf: Uint8Array): Foo => { + return decodeMessage(buf, Foo.codec) + } +} + +export interface Bar { + tmp: Foo +} + +export namespace Bar { + export const codec = message({ + 1: { name: 'tmp', codec: Foo.codec } + }) + + export const encode = (obj: Bar): Uint8Array => { + return encodeMessage(obj, Bar.codec) + } + + export const decode = (buf: Uint8Array): Bar => { + return decodeMessage(buf, Bar.codec) + } +} + +export enum FOO { + LOL = 'LOL', + ABE = 'ABE' +} + +export namespace FOO { + export const codec = enumeration(FOO) +} + +export interface Yo { + lol: FOO[] +} + +export namespace Yo { + export const codec = message({ + 1: { name: 'lol', codec: FOO.codec, repeats: true } + }) + + export const encode = (obj: Yo): Uint8Array => { + return encodeMessage(obj, Yo.codec) + } + + export const decode = (buf: Uint8Array): Yo => { + return decodeMessage(buf, Yo.codec) + } +} + +export interface Lol { + lol: string + b: Bar +} + +export namespace Lol { + export const codec = message({ + 1: { name: 'lol', codec: string }, + 2: { name: 'b', codec: Bar.codec } + }) + + export const encode = (obj: Lol): Uint8Array => { + return encodeMessage(obj, Lol.codec) + } + + export const decode = (buf: Uint8Array): Lol => { + return decodeMessage(buf, Lol.codec) + } +} + +export interface Test { + meh: Lol + hello: number + foo: string + payload: Uint8Array +} + +export namespace Test { + export const codec = message({ + 6: { name: 'meh', codec: Lol.codec }, + 3: { name: 'hello', codec: uint32 }, + 1: { name: 'foo', codec: string }, + 7: { name: 'payload', codec: bytes } + }) + + export const encode = (obj: Test): Uint8Array => { + return encodeMessage(obj, Test.codec) + } + + export const decode = (buf: Uint8Array): Test => { + return decodeMessage(buf, Test.codec) + } +} diff --git a/packages/protons-benchmark/tsconfig.json b/packages/protons-benchmark/tsconfig.json new file mode 100644 index 0000000..ec40462 --- /dev/null +++ b/packages/protons-benchmark/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist", + "emitDeclarationOnly": false, + "module": "ES2020" + }, + "include": [ + "bin", + "src", + "test" + ], + "exclude": [ + "src/protobufjs/bench.js" + ], + "references": [ + { + "path": "../protons" + } + ] +} diff --git a/packages/protons-runtime/LICENSE b/packages/protons-runtime/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/packages/protons-runtime/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/protons-runtime/LICENSE-APACHE b/packages/protons-runtime/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/packages/protons-runtime/LICENSE-APACHE @@ -0,0 +1,5 @@ +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/packages/protons-runtime/LICENSE-MIT b/packages/protons-runtime/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/packages/protons-runtime/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/protons-runtime/README.md b/packages/protons-runtime/README.md new file mode 100644 index 0000000..5639077 --- /dev/null +++ b/packages/protons-runtime/README.md @@ -0,0 +1,22 @@ +# protons-runtime + +> Shared components that turn values to bytes and back again + +Contains shared code to reduce code duplication between modules transpiled by protons. + +## Table of contents + +- [Contribute](#contribute) +- [License](#license) + +## Contribute + +Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/protons/issues)! + +This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) + +## License + +[Apache-2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT) © Protocol Labs diff --git a/packages/protons-runtime/package.json b/packages/protons-runtime/package.json new file mode 100644 index 0000000..3f09974 --- /dev/null +++ b/packages/protons-runtime/package.json @@ -0,0 +1,152 @@ +{ + "name": "protons-runtime", + "version": "0.0.0", + "description": "Shared code to make your bundle smaller when running protons in your app", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/protons/tree/master/packages/protons-runtime#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/protons.git" + }, + "bugs": { + "url": "https://github.com/ipfs/protons/issues" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist/src", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "import": "./dist/src/index.js", + "types": "./dist/src/index.d.ts" + }, + "./status": { + "import": "./dist/src/status.js", + "types": "./dist/src/status.d.ts" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "chore", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Trivial Changes" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "lint": "aegir lint", + "dep-check": "aegir dep-check dist/src/**/*.js dist/test/**/*.js", + "build": "tsc", + "release": "semantic-release -e semantic-release-monorepo" + }, + "dependencies": { + "uint8arraylist": "^1.4.0", + "uint8arrays": "^3.0.0" + }, + "devDependencies": { + "aegir": "^36.1.3" + } +} diff --git a/packages/protons-runtime/src/codecs/bool.ts b/packages/protons-runtime/src/codecs/bool.ts new file mode 100644 index 0000000..aa3c0d1 --- /dev/null +++ b/packages/protons-runtime/src/codecs/bool.ts @@ -0,0 +1,15 @@ +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function boolEncodingLength () { + return 1 +} + +const encode: EncodeFunction = function boolEncode (value) { + return Uint8Array.from([value ? 1 : 0]) +} + +const decode: DecodeFunction = function boolDecode (buffer, offset) { + return buffer.get(offset) > 0 +} + +export const bool = createCodec('bool', CODEC_TYPES.VARINT, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/bytes.ts b/packages/protons-runtime/src/codecs/bytes.ts new file mode 100644 index 0000000..37123f2 --- /dev/null +++ b/packages/protons-runtime/src/codecs/bytes.ts @@ -0,0 +1,26 @@ + +import { Uint8ArrayList } from 'uint8arraylist' +import { unsigned } from '../utils/varint.js' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function bytesEncodingLength (val) { + const len = val.byteLength + return unsigned.encodingLength(len) + len +} + +const encode: EncodeFunction = function bytesEncode (val) { + const prefix = new Uint8Array(unsigned.encodingLength(val.byteLength)) + + unsigned.encode(val.byteLength, prefix) + + return new Uint8ArrayList(prefix, val) +} + +const decode: DecodeFunction = function bytesDecode (buf, offset) { + const byteLength = unsigned.decode(buf, offset) + offset += unsigned.encodingLength(byteLength) + + return buf.slice(offset, offset + byteLength) +} + +export const bytes = createCodec('bytes', CODEC_TYPES.LENGTH_DELIMITED, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/codec.ts b/packages/protons-runtime/src/codecs/codec.ts new file mode 100644 index 0000000..7332fd7 --- /dev/null +++ b/packages/protons-runtime/src/codecs/codec.ts @@ -0,0 +1,41 @@ +import type { Uint8ArrayList } from 'uint8arraylist' + +// https://developers.google.com/protocol-buffers/docs/encoding#structure +export enum CODEC_TYPES { + VARINT = 0, + BIT64, + LENGTH_DELIMITED, + START_GROUP, + END_GROUP, + BIT32 +} + +export interface EncodeFunction { + (value: T): Uint8Array | Uint8ArrayList +} + +export interface DecodeFunction { + (buf: Uint8ArrayList, offset: number): T +} + +export interface EncodingLengthFunction { + (value: T): number +} + +export interface Codec { + name: string + type: number + encode: EncodeFunction + decode: DecodeFunction + encodingLength: EncodingLengthFunction +} + +export function createCodec (name: string, type: number, encode: EncodeFunction, decode: DecodeFunction, encodingLength: EncodingLengthFunction): Codec { + return { + name, + type, + encode, + decode, + encodingLength + } +} diff --git a/packages/protons-runtime/src/codecs/double.ts b/packages/protons-runtime/src/codecs/double.ts new file mode 100644 index 0000000..1d887fc --- /dev/null +++ b/packages/protons-runtime/src/codecs/double.ts @@ -0,0 +1,19 @@ +import { Uint8ArrayList } from 'uint8arraylist' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function doubleEncodingLength () { + return 8 +} + +const encode: EncodeFunction = function doubleEncode (val) { + const buf = new Uint8ArrayList(new Uint8Array(encodingLength(val))) + buf.setFloat64(0, val, true) + + return buf +} + +const decode: DecodeFunction = function doubleDecode (buf, offset) { + return buf.getFloat64(offset, true) +} + +export const double = createCodec('double', CODEC_TYPES.BIT64, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/enum.ts b/packages/protons-runtime/src/codecs/enum.ts new file mode 100644 index 0000000..26f8578 --- /dev/null +++ b/packages/protons-runtime/src/codecs/enum.ts @@ -0,0 +1,36 @@ + +import { unsigned } from '../utils/varint.js' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, Codec, CODEC_TYPES } from './codec.js' + +export function enumeration (e: T): Codec { + const encodingLength: EncodingLengthFunction = function enumEncodingLength (val: string) { + const keys = Object.keys(e) + const index = keys.indexOf(val) + + return unsigned.encodingLength(index) + } + + const encode: EncodeFunction = function enumEncode (val) { + const keys = Object.keys(e) + const index = keys.indexOf(val) + const buf = new Uint8Array(unsigned.encodingLength(index)) + + unsigned.encode(index, buf) + + return buf + } + + const decode: DecodeFunction = function enumDecode (buf, offset) { + const index = unsigned.decode(buf, offset) + const keys = Object.keys(e) + + if (keys[index] == null) { + throw new Error('Could not find enum key for value') + } + + return keys[index] + } + + // @ts-expect-error yeah yeah + return createCodec('enum', CODEC_TYPES.VARINT, encode, decode, encodingLength) +} diff --git a/packages/protons-runtime/src/codecs/fixed32.ts b/packages/protons-runtime/src/codecs/fixed32.ts new file mode 100644 index 0000000..cd29e3d --- /dev/null +++ b/packages/protons-runtime/src/codecs/fixed32.ts @@ -0,0 +1,19 @@ +import { Uint8ArrayList } from 'uint8arraylist' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function fixed32EncodingLength () { + return 4 +} + +const encode: EncodeFunction = function fixed32Encode (val) { + const buf = new Uint8ArrayList(new Uint8Array(encodingLength(val))) + buf.setInt32(0, val, true) + + return buf +} + +const decode: DecodeFunction = function fixed32Decode (buf, offset) { + return buf.getInt32(offset, true) +} + +export const fixed32 = createCodec('fixed32', CODEC_TYPES.BIT32, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/fixed64.ts b/packages/protons-runtime/src/codecs/fixed64.ts new file mode 100644 index 0000000..d8113f1 --- /dev/null +++ b/packages/protons-runtime/src/codecs/fixed64.ts @@ -0,0 +1,19 @@ +import { Uint8ArrayList } from 'uint8arraylist' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function int64EncodingLength (val) { + return 8 +} + +const encode: EncodeFunction = function int64Encode (val) { + const buf = new Uint8ArrayList(new Uint8Array(encodingLength(val))) + buf.setBigInt64(0, val, true) + + return buf +} + +const decode: DecodeFunction = function int64Decode (buf, offset) { + return buf.getBigInt64(offset, true) +} + +export const fixed64 = createCodec('fixed64', CODEC_TYPES.BIT64, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/float.ts b/packages/protons-runtime/src/codecs/float.ts new file mode 100644 index 0000000..10270af --- /dev/null +++ b/packages/protons-runtime/src/codecs/float.ts @@ -0,0 +1,19 @@ +import { Uint8ArrayList } from 'uint8arraylist' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function floatEncodingLength () { + return 4 +} + +const encode: EncodeFunction = function floatEncode (val) { + const buf = new Uint8ArrayList(new Uint8Array(encodingLength(1))) + buf.setFloat32(0, val, true) + + return buf +} + +const decode: DecodeFunction = function floatDecode (buf, offset) { + return buf.getFloat32(offset, true) +} + +export const float = createCodec('float', CODEC_TYPES.BIT32, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/int32.ts b/packages/protons-runtime/src/codecs/int32.ts new file mode 100644 index 0000000..0889d58 --- /dev/null +++ b/packages/protons-runtime/src/codecs/int32.ts @@ -0,0 +1,19 @@ +import { signed } from '../utils/varint.js' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function int32EncodingLength (val) { + return signed.encodingLength(val) +} + +const encode: EncodeFunction = function int32Encode (val) { + const buf = new Uint8Array(encodingLength(val)) + signed.encode(val, buf) + + return buf +} + +const decode: DecodeFunction = function int32Decode (buf, offset) { + return signed.decode(buf, offset) +} + +export const int32 = createCodec('int32', CODEC_TYPES.VARINT, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/int64.ts b/packages/protons-runtime/src/codecs/int64.ts new file mode 100644 index 0000000..d58c0a7 --- /dev/null +++ b/packages/protons-runtime/src/codecs/int64.ts @@ -0,0 +1,19 @@ +import { signed } from '../utils/big-varint.js' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function int64EncodingLength (val) { + return signed.encodingLength(val) +} + +const encode: EncodeFunction = function int64Encode (val) { + const buf = new Uint8Array(encodingLength(val)) + signed.encode(val, buf) + + return buf +} + +const decode: DecodeFunction = function int64Decode (buf, offset) { + return signed.decode(buf, offset) | 0n +} + +export const int64 = createCodec('int64', CODEC_TYPES.VARINT, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/message.ts b/packages/protons-runtime/src/codecs/message.ts new file mode 100644 index 0000000..64cf995 --- /dev/null +++ b/packages/protons-runtime/src/codecs/message.ts @@ -0,0 +1,142 @@ +import { unsigned } from '../utils/varint.js' +import type { FieldDef, FieldDefs } from '../index.js' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, Codec, CODEC_TYPES } from './codec.js' +import { Uint8ArrayList } from 'uint8arraylist' + +export interface Factory { + new (obj: A): T +} + +export function message (fieldDefs: FieldDefs): Codec { + const encodingLength: EncodingLengthFunction = function messageEncodingLength (val: Record) { + let length = 0 + + for (const fieldDef of Object.values(fieldDefs)) { + length += fieldDef.codec.encodingLength(val[fieldDef.name]) + } + + return unsigned.encodingLength(length) + length + } + + const encode: EncodeFunction> = function messageEncode (val) { + const bytes = new Uint8ArrayList() + + function encodeValue (value: any, fieldNumber: number, fieldDef: FieldDef) { + if (value == null) { + if (fieldDef.optional === true) { + return + } + + throw new Error(`Non optional field "${fieldDef.name}" was ${value === null ? 'null' : 'undefined'}`) + } + + const key = (fieldNumber << 3) | fieldDef.codec.type + const prefix = new Uint8Array(unsigned.encodingLength(key)) + unsigned.encode(key, prefix) + const encoded = fieldDef.codec.encode(value) + + bytes.append(prefix) + bytes.append(encoded) + } + + for (const [fieldNumberStr, fieldDef] of Object.entries(fieldDefs)) { + const fieldNumber = parseInt(fieldNumberStr) + + if (fieldDef.repeats === true) { + if (!Array.isArray(val[fieldDef.name])) { + throw new Error(`Repeating field "${fieldDef.name}" was not an array`) + } + + for (const value of val[fieldDef.name]) { + encodeValue(value, fieldNumber, fieldDef) + } + } else { + encodeValue(val[fieldDef.name], fieldNumber, fieldDef) + } + } + + const prefix = new Uint8Array(unsigned.encodingLength(bytes.length)) + unsigned.encode(bytes.length, prefix) + + return new Uint8ArrayList(prefix, bytes) + } + + const decode: DecodeFunction = function messageDecode (buffer, offset) { + const length = unsigned.decode(buffer, offset) + offset += unsigned.encodingLength(length) + + const fields: any = {} + + while (offset < buffer.length) { + // console.info('start offset', offset) + + const key = unsigned.decode(buffer, offset) + offset += unsigned.encodingLength(key) + + const wireType = key & 0x7 + const fieldNumber = key >> 3 + const fieldDef = fieldDefs[fieldNumber] + let fieldLength = 0 + + // console.info('fieldNumber', fieldNumber, 'wireType', wireType, 'offset', offset) + + if (wireType === CODEC_TYPES.VARINT) { + // console.info('decode varint') + if (fieldDef != null) { + // use the codec if it is available as this could be a bigint + const value = fieldDef.codec.decode(buffer, offset) + fieldLength = fieldDef.codec.encodingLength(value) + } else { + const value = unsigned.decode(buffer, offset) + fieldLength = unsigned.encodingLength(value) + } + } else if (wireType === CODEC_TYPES.BIT64) { + // console.info('decode 64bit') + fieldLength = 8 + } else if (wireType === CODEC_TYPES.LENGTH_DELIMITED) { + // console.info('decode length delimited') + const valueLength = unsigned.decode(buffer, offset) + fieldLength = valueLength + unsigned.encodingLength(valueLength) + } else if (wireType === CODEC_TYPES.BIT32) { + // console.info('decode 32 bit') + fieldLength = 4 + } else if (wireType === CODEC_TYPES.START_GROUP) { + throw new Error('Unsupported wire type START_GROUP') + } else if (wireType === CODEC_TYPES.END_GROUP) { + throw new Error('Unsupported wire type END_GROUP') + } + + // console.info('fieldLength', fieldLength) + + if (fieldDef != null) { + // console.info('decode', fieldDef.codec.name, fieldDef.name, 'at offset', offset) + const value = fieldDef.codec.decode(buffer, offset) + + if (fieldDef.repeats === true) { + if (fields[fieldDef.name] == null) { + fields[fieldDef.name] = [] + } + + fields[fieldDef.name].push(value) + } else { + fields[fieldDef.name] = value + } + + // console.info('decoded', value) + } + + offset += fieldLength + } + + // make sure repeated fields have an array if not set + for (const fieldDef of Object.values(fieldDefs)) { + if (fieldDef.repeats === true && fields[fieldDef.name] == null) { + fields[fieldDef.name] = [] + } + } + + return fields + } + + return createCodec('message', CODEC_TYPES.LENGTH_DELIMITED, encode, decode, encodingLength) +} diff --git a/packages/protons-runtime/src/codecs/sfixed32.ts b/packages/protons-runtime/src/codecs/sfixed32.ts new file mode 100644 index 0000000..f165b90 --- /dev/null +++ b/packages/protons-runtime/src/codecs/sfixed32.ts @@ -0,0 +1,19 @@ +import { Uint8ArrayList } from 'uint8arraylist' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function sfixed32EncodingLength () { + return 4 +} + +const encode: EncodeFunction = function sfixed32Encode (val) { + const buf = new Uint8ArrayList(new Uint8Array(encodingLength(val))) + buf.setInt32(0, val, true) + + return buf +} + +const decode: DecodeFunction = function sfixed32Decode (buf, offset) { + return buf.getInt32(offset, true) +} + +export const sfixed32 = createCodec('sfixed32', CODEC_TYPES.BIT32, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/sfixed64.ts b/packages/protons-runtime/src/codecs/sfixed64.ts new file mode 100644 index 0000000..68359b0 --- /dev/null +++ b/packages/protons-runtime/src/codecs/sfixed64.ts @@ -0,0 +1,19 @@ +import { Uint8ArrayList } from 'uint8arraylist' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function sfixed64EncodingLength () { + return 8 +} + +const encode: EncodeFunction = function sfixed64Encode (val) { + const buf = new Uint8ArrayList(new Uint8Array(encodingLength(val))) + buf.setBigInt64(0, val, true) + + return buf +} + +const decode: DecodeFunction = function sfixed64Decode (buf, offset) { + return buf.getBigInt64(offset, true) +} + +export const sfixed64 = createCodec('sfixed64', CODEC_TYPES.BIT64, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/sint32.ts b/packages/protons-runtime/src/codecs/sint32.ts new file mode 100644 index 0000000..3c72c2c --- /dev/null +++ b/packages/protons-runtime/src/codecs/sint32.ts @@ -0,0 +1,20 @@ +import { zigzag } from '../utils/varint.js' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function sint32EncodingLength (val) { + return zigzag.encodingLength(val) +} + +const encode: EncodeFunction = function svarintEncode (val) { + const buf = new Uint8Array(encodingLength(val)) + + zigzag.encode(val, buf) + + return buf +} + +const decode: DecodeFunction = function svarintDecode (buf, offset) { + return zigzag.decode(buf, offset) +} + +export const sint32 = createCodec('sint32', CODEC_TYPES.VARINT, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/sint64.ts b/packages/protons-runtime/src/codecs/sint64.ts new file mode 100644 index 0000000..2dc4550 --- /dev/null +++ b/packages/protons-runtime/src/codecs/sint64.ts @@ -0,0 +1,19 @@ +import { zigzag } from '../utils/big-varint.js' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function int64EncodingLength (val) { + return zigzag.encodingLength(val) +} + +const encode: EncodeFunction = function int64Encode (val) { + const buf = new Uint8Array(encodingLength(val)) + zigzag.encode(val, buf) + + return buf +} + +const decode: DecodeFunction = function int64Decode (buf, offset) { + return zigzag.decode(buf, offset) +} + +export const sint64 = createCodec('sint64', CODEC_TYPES.VARINT, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/string.ts b/packages/protons-runtime/src/codecs/string.ts new file mode 100644 index 0000000..04216dd --- /dev/null +++ b/packages/protons-runtime/src/codecs/string.ts @@ -0,0 +1,28 @@ +import { unsigned } from '../utils/varint.js' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' +import { Uint8ArrayList } from 'uint8arraylist' + +const encodingLength: EncodingLengthFunction = function stringEncodingLength (val) { + const len = uint8ArrayFromString(val).byteLength + return unsigned.encodingLength(len) + len +} + +const encode: EncodeFunction = function stringEncode (val) { + const asBuf = uint8ArrayFromString(val) + const prefix = new Uint8Array(unsigned.encodingLength(asBuf.byteLength)) + + unsigned.encode(asBuf.length, prefix) + + return new Uint8ArrayList(prefix, asBuf) +} + +const decode: DecodeFunction = function stringDecode (buf, offset) { + const strLen = unsigned.decode(buf, offset) + offset += unsigned.encodingLength(strLen) + + return uint8ArrayToString(buf.slice(offset, offset + strLen)) +} + +export const string = createCodec('string', CODEC_TYPES.LENGTH_DELIMITED, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/uint32.ts b/packages/protons-runtime/src/codecs/uint32.ts new file mode 100644 index 0000000..57aa1a7 --- /dev/null +++ b/packages/protons-runtime/src/codecs/uint32.ts @@ -0,0 +1,24 @@ +import { unsigned } from '../utils/varint.js' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function uint32EncodingLength (val) { + return unsigned.encodingLength(val) +} + +const encode: EncodeFunction = function uint32Encode (val) { + // val = val < 0 ? val + 4294967296 : val + + const buf = new Uint8Array(encodingLength(val)) + + unsigned.encode(val, buf) + + return buf +} + +const decode: DecodeFunction = function uint32Decode (buf, offset) { + return unsigned.decode(buf, offset) + + // return value > 2147483647 ? value - 4294967296 : value +} + +export const uint32 = createCodec('uint32', CODEC_TYPES.VARINT, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/codecs/uint64.ts b/packages/protons-runtime/src/codecs/uint64.ts new file mode 100644 index 0000000..4753114 --- /dev/null +++ b/packages/protons-runtime/src/codecs/uint64.ts @@ -0,0 +1,20 @@ +import { unsigned } from '../utils/big-varint.js' +import { DecodeFunction, EncodeFunction, createCodec, EncodingLengthFunction, CODEC_TYPES } from './codec.js' + +const encodingLength: EncodingLengthFunction = function uint64EncodingLength (val) { + return unsigned.encodingLength(val) +} + +const encode: EncodeFunction = function uint64Encode (val) { + const buf = new Uint8Array(unsigned.encodingLength(val)) + + unsigned.encode(val, buf) + + return buf +} + +const decode: DecodeFunction = function uint64Decode (buf, offset) { + return unsigned.decode(buf, offset) +} + +export const uint64 = createCodec('uint64', CODEC_TYPES.VARINT, encode, decode, encodingLength) diff --git a/packages/protons-runtime/src/decode.ts b/packages/protons-runtime/src/decode.ts new file mode 100644 index 0000000..99b427d --- /dev/null +++ b/packages/protons-runtime/src/decode.ts @@ -0,0 +1,11 @@ +import { Uint8ArrayList } from 'uint8arraylist' +import { unsigned } from './utils/varint.js' +import type { Codec } from './codecs/codec.js' + +export function decodeMessage (buf: Uint8Array, codec: Codec) { + // wrap root message + const prefix = new Uint8Array(unsigned.encodingLength(buf.length)) + unsigned.encode(buf.length, prefix) + + return codec.decode(new Uint8ArrayList(prefix, buf), 0) +} diff --git a/packages/protons-runtime/src/encode.ts b/packages/protons-runtime/src/encode.ts new file mode 100644 index 0000000..83f8bae --- /dev/null +++ b/packages/protons-runtime/src/encode.ts @@ -0,0 +1,10 @@ +import type { Codec } from './codecs/codec.js' +import { unsigned } from './utils/varint.js' + +export function encodeMessage (message: T, codec: Codec) { + // unwrap root message + const encoded = codec.encode(message) + const skip = unsigned.encodingLength(unsigned.decode(encoded)) + + return encoded.slice(skip) +} diff --git a/packages/protons-runtime/src/index.ts b/packages/protons-runtime/src/index.ts new file mode 100644 index 0000000..7cd6656 --- /dev/null +++ b/packages/protons-runtime/src/index.ts @@ -0,0 +1,37 @@ +import type { Codec } from './codecs/codec.js' + +export interface FieldDef { + name: string + codec: Codec + optional?: true + repeats?: true + packed?: true +} + +export type FieldDefs = Record + +export { + decodeMessage +} from './decode.js' + +export { + encodeMessage +} from './encode.js' + +export { bool } from './codecs/bool.js' +export { bytes } from './codecs/bytes.js' +export { double } from './codecs/double.js' +export { enumeration } from './codecs/enum.js' +export { fixed32 } from './codecs/fixed32.js' +export { fixed64 } from './codecs/fixed64.js' +export { float } from './codecs/float.js' +export { int32 } from './codecs/int32.js' +export { int64 } from './codecs/int64.js' +export { message } from './codecs/message.js' +export { sfixed32 } from './codecs/sfixed32.js' +export { sfixed64 } from './codecs/sfixed64.js' +export { sint32 } from './codecs/sint32.js' +export { sint64 } from './codecs/sint64.js' +export { string } from './codecs/string.js' +export { uint32 } from './codecs/uint32.js' +export { uint64 } from './codecs/uint64.js' diff --git a/packages/protons-runtime/src/utils/accessor.ts b/packages/protons-runtime/src/utils/accessor.ts new file mode 100644 index 0000000..e19c387 --- /dev/null +++ b/packages/protons-runtime/src/utils/accessor.ts @@ -0,0 +1,25 @@ +import type { Uint8ArrayList } from 'uint8arraylist' + +export default function accessor (buf: Uint8Array | Uint8ArrayList) { + if (buf instanceof Uint8Array) { + return { + get (index: number) { + return buf[index] + }, + + set (index: number, value: number) { + buf[index] = value + } + } + } + + return { + get (index: number) { + return buf.get(index) + }, + + set (index: number, value: number) { + buf.set(index, value) + } + } +} diff --git a/packages/protons-runtime/src/utils/big-varint.ts b/packages/protons-runtime/src/utils/big-varint.ts new file mode 100644 index 0000000..a899e1e --- /dev/null +++ b/packages/protons-runtime/src/utils/big-varint.ts @@ -0,0 +1,70 @@ +import type { Uint8ArrayList } from 'uint8arraylist' +import accessor from './accessor.js' +import { LongBits } from './long-bits.js' + +const LIMIT = 0x7fn + +// https://github.com/joeltg/big-varint/blob/main/src/unsigned.ts +export const unsigned = { + encodingLength (value: bigint): number { + let i = 0 + for (; value >= 0x80n; i++) { + value >>= 7n + } + return i + 1 + }, + + encode (value: bigint, buf: Uint8ArrayList | Uint8Array) { + const access = accessor(buf) + + let offset = 0 + while (LIMIT < value) { + access.set(offset++, Number(value & LIMIT) | 0x80) + value >>= 7n + } + + access.set(offset, Number(value)) + }, + + decode (buf: Uint8ArrayList | Uint8Array, offset = 0) { + return LongBits.fromBytes(buf, offset).toBigInt(true) + } +} + +export const signed = { + encodingLength (value: bigint): number { + if (value < 0n) { + return 10 // 10 bytes per spec + } + + return unsigned.encodingLength(value) + }, + + encode (value: bigint, buf: Uint8ArrayList | Uint8Array, offset = 0) { + if (value < 0n) { + LongBits.fromBigInt(value).toBytes(buf, offset) + + return + } + + return unsigned.encode(value, buf) + }, + + decode (buf: Uint8ArrayList | Uint8Array, offset = 0) { + return LongBits.fromBytes(buf, offset).toBigInt(false) + } +} + +export const zigzag = { + encodingLength (value: bigint): number { + return unsigned.encodingLength(value >= 0 ? value * 2n : value * -2n - 1n) + }, + + encode (value: bigint, buf: Uint8ArrayList | Uint8Array, offset = 0) { + LongBits.fromBigInt(value).zzEncode().toBytes(buf, offset) + }, + + decode (buf: Uint8ArrayList | Uint8Array, offset = 0) { + return LongBits.fromBytes(buf, offset).zzDecode().toBigInt(false) + } +} diff --git a/packages/protons-runtime/src/utils/long-bits.ts b/packages/protons-runtime/src/utils/long-bits.ts new file mode 100644 index 0000000..ad1ddcd --- /dev/null +++ b/packages/protons-runtime/src/utils/long-bits.ts @@ -0,0 +1,184 @@ +import type { Uint8ArrayList } from 'uint8arraylist' +import accessor from './accessor.js' + +const TWO_32 = 4294967296 + +export class LongBits { + public hi: number + public lo: number + + constructor (hi: number = 0, lo: number = 0) { + this.hi = hi + this.lo = lo + } + + toBigInt (unsigned: boolean): bigint { + if (unsigned) { + return BigInt(this.lo >>> 0) + (BigInt(this.hi >>> 0) << 32n) + } + + if ((this.hi >>> 31) !== 0) { + const lo = ~this.lo + 1 >>> 0 + let hi = ~this.hi >>> 0 + + if (lo === 0) { + hi = hi + 1 >>> 0 + } + + return -(BigInt(lo) + (BigInt(hi) << 32n)) + } + + return BigInt(this.lo >>> 0) + (BigInt(this.hi >>> 0) << 32n) + } + + zzDecode () { + const mask = -(this.lo & 1) + const lo = ((this.lo >>> 1 | this.hi << 31) ^ mask) >>> 0 + const hi = (this.hi >>> 1 ^ mask) >>> 0 + + return new LongBits(hi, lo) + } + + zzEncode () { + const mask = this.hi >> 31 + const hi = ((this.hi << 1 | this.lo >>> 31) ^ mask) >>> 0 + const lo = (this.lo << 1 ^ mask) >>> 0 + + return new LongBits(hi, lo) + } + + toBytes (buf: Uint8ArrayList | Uint8Array, offset = 0) { + const access = accessor(buf) + + while (this.hi > 0) { + access.set(offset++, this.lo & 127 | 128) + this.lo = (this.lo >>> 7 | this.hi << 25) >>> 0 + this.hi >>>= 7 + } + + while (this.lo > 127) { + access.set(offset++, this.lo & 127 | 128) + this.lo = this.lo >>> 7 + } + + access.set(offset++, this.lo) + } + + static fromBigInt (value: bigint) { + if (value === 0n) { + return new LongBits() + } + + const negative = value < 0 + + if (negative) { + value = -value + } + + let hi = Number(value >> 32n) | 0 + let lo = Number(value - (BigInt(hi) << 32n)) | 0 + + if (negative) { + hi = ~hi >>> 0 + lo = ~lo >>> 0 + + if (++lo > TWO_32) { + lo = 0 + + if (++hi > TWO_32) { + hi = 0 + } + } + } + + return new LongBits(hi, lo) + } + + static fromNumber (value: number) { + if (value === 0) { + return new LongBits() + } + + const sign = value < 0 + + if (sign) { + value = -value + } + + let lo = value >>> 0 + let hi = (value - lo) / 4294967296 >>> 0 + + if (sign) { + hi = ~hi >>> 0 + lo = ~lo >>> 0 + + if (++lo > 4294967295) { + lo = 0 + + if (++hi > 4294967295) { + hi = 0 + } + } + } + + return new LongBits(hi, lo) + } + + static fromBytes (buf: Uint8ArrayList | Uint8Array, offset: number) { + const access = accessor(buf) + + // tends to deopt with local vars for octet etc. + const bits = new LongBits() + let i = 0 + + if (buf.length - offset > 4) { // fast route (lo) + for (; i < 4; ++i) { + // 1st..4th + bits.lo = (bits.lo | (access.get(offset) & 127) << i * 7) >>> 0 + if (access.get(offset++) < 128) { return bits } + } + // 5th + bits.lo = (bits.lo | (access.get(offset) & 127) << 28) >>> 0 + bits.hi = (bits.hi | (access.get(offset) & 127) >> 4) >>> 0 + if (access.get(offset++) < 128) { return bits } + i = 0 + } else { + for (; i < 3; ++i) { + /* istanbul ignore if */ + if (offset >= buf.length) { + throw RangeError(`index out of range: ${offset} > ${buf.length}`) + } + + // 1st..3th + bits.lo = (bits.lo | (access.get(offset) & 127) << i * 7) >>> 0 + if (access.get(offset++) < 128) { return bits } + } + // 4th + bits.lo = (bits.lo | (access.get(offset++) & 127) << i * 7) >>> 0 + return bits + } + if (buf.length - offset > 4) { // fast route (hi) + for (; i < 5; ++i) { + // 6th..10th + bits.hi = (bits.hi | (access.get(offset) & 127) << i * 7 + 3) >>> 0 + if (access.get(offset++) < 128) { return bits } + } + } else { + for (; i < 5; ++i) { + /* istanbul ignore if */ + if (offset >= buf.length) { + throw RangeError(`index out of range: ${offset} > ${buf.length}`) + } + + // 6th..10th + bits.hi = (bits.hi | (access.get(offset) & 127) << i * 7 + 3) >>> 0 + if (access.get(offset++) < 128) { + return bits + } + } + } + + /* istanbul ignore next */ + throw Error('invalid varint encoding') + } +} diff --git a/packages/protons-runtime/src/utils/varint.ts b/packages/protons-runtime/src/utils/varint.ts new file mode 100644 index 0000000..f3b0738 --- /dev/null +++ b/packages/protons-runtime/src/utils/varint.ts @@ -0,0 +1,173 @@ +import type { Uint8ArrayList } from 'uint8arraylist' +import accessor from './accessor.js' +import { LongBits } from './long-bits.js' + +const MSB = 0x80 +const REST = 0x7F +const MSBALL = ~REST +const INT = Math.pow(2, 31) +const N1 = Math.pow(2, 7) +const N2 = Math.pow(2, 14) +const N3 = Math.pow(2, 21) +const N4 = Math.pow(2, 28) +const N5 = Math.pow(2, 35) +const N6 = Math.pow(2, 42) +const N7 = Math.pow(2, 49) +const N8 = Math.pow(2, 56) +const N9 = Math.pow(2, 63) + +export const unsigned = { + encodingLength (value: number): number { + if (value < N1) { + return 1 + } + + if (value < N2) { + return 2 + } + + if (value < N3) { + return 3 + } + + if (value < N4) { + return 4 + } + + if (value < N5) { + return 5 + } + + if (value < N6) { + return 6 + } + + if (value < N7) { + return 7 + } + + if (value < N8) { + return 8 + } + + if (value < N9) { + return 9 + } + + return 10 + }, + + encode (value: number, buf: Uint8ArrayList | Uint8Array) { + let offset = 0 + const access = accessor(buf) + + while (value >= INT) { + access.set(offset++, (value & 0xFF) | MSB) + value /= 128 + } + + while ((value & MSBALL) > 0) { + access.set(offset++, (value & 0xFF) | MSB) + value >>>= 7 + } + + access.set(offset, value | 0) + }, + + decode (buf: Uint8ArrayList | Uint8Array, offset: number = 0) { + const access = accessor(buf) + let value = 4294967295 // optimizer type-hint, tends to deopt otherwise (?!) + + value = (access.get(offset) & 127) >>> 0 + + if (access.get(offset++) < 128) { + return value + } + + value = (value | (access.get(offset) & 127) << 7) >>> 0 + + if (access.get(offset++) < 128) { + return value + } + + value = (value | (access.get(offset) & 127) << 14) >>> 0 + + if (access.get(offset++) < 128) { + return value + } + + value = (value | (access.get(offset) & 127) << 21) >>> 0 + + if (access.get(offset++) < 128) { + return value + } + + value = (value | (access.get(offset) & 15) << 28) >>> 0 + + if (access.get(offset++) < 128) { + return value + } + + if ((offset += 5) > buf.length) { + throw RangeError(`index out of range: ${offset} > ${buf.length}`) + } + + return value + } +} + +export const signed = { + encodingLength (value: number): number { + if (value < 0) { + return 10 // 10 bytes per spec + } + + return unsigned.encodingLength(value) + }, + + encode (value: number, buf: Uint8ArrayList | Uint8Array) { + if (value < 0) { + let offset = 0 + const access = accessor(buf) + const bits = LongBits.fromNumber(value) + + while (bits.hi > 0) { + access.set(offset++, bits.lo & 127 | 128) + bits.lo = (bits.lo >>> 7 | bits.hi << 25) >>> 0 + bits.hi >>>= 7 + } + + while (bits.lo > 127) { + access.set(offset++, bits.lo & 127 | 128) + bits.lo = bits.lo >>> 7 + } + + access.set(offset++, bits.lo) + + return + } + + unsigned.encode(value, buf) + }, + + decode (data: Uint8ArrayList | Uint8Array, offset = 0) { + return unsigned.decode(data, offset) | 0 + } +} + +export const zigzag = { + encodingLength (value: number): number { + value = (value << 1 ^ value >> 31) >>> 0 + return unsigned.encodingLength(value) + }, + + encode (value: number, buf: Uint8ArrayList | Uint8Array, offset = 0) { + value = (value << 1 ^ value >> 31) >>> 0 + return unsigned.encode(value, buf) + }, + + decode (data: Uint8ArrayList | Uint8Array, offset = 0) { + const value = unsigned.decode(data, offset) + return value >>> 1 ^ -(value & 1) | 0 + } +} diff --git a/packages/protons-runtime/tsconfig.json b/packages/protons-runtime/tsconfig.json new file mode 100644 index 0000000..f296f99 --- /dev/null +++ b/packages/protons-runtime/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist", + "emitDeclarationOnly": false, + "module": "ES2020" + }, + "include": [ + "src", + "test" + ] +} diff --git a/packages/protons/LICENSE b/packages/protons/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/packages/protons/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/packages/protons/LICENSE-APACHE b/packages/protons/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/packages/protons/LICENSE-APACHE @@ -0,0 +1,5 @@ +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/packages/protons/LICENSE-MIT b/packages/protons/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/packages/protons/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/protons/README.md b/packages/protons/README.md new file mode 100644 index 0000000..f35e3a1 --- /dev/null +++ b/packages/protons/README.md @@ -0,0 +1,62 @@ +# protons + +> Generate typescript from .proto files + +## Table of contents + +- [Usage](#usage) +- [Contribute](#contribute) +- [License](#license) + +## Install + +To use this project, add `protons` as a development dependency and `protons-runtime` as a runtime dependency. + +`protons` contains the code to compile `.proto` files to `.ts` files and `protons-runtime` contains the code to do serialization/deserialization to `Uint8Array`s during application execution. + +```console +$ npm install --save-dev protons +$ npm install --save protons-runtime +``` + +## Usage + +First generate your `.ts` files: + +```console +$ protons ./path/to/foo.proto ./path/to/output.ts +``` + +Then run tsc over them as normal: + +```console +$ tsc +``` + +In your code import the generated classes and use them to transform to/from bytes: + +```js +import { Foo } from './foo.js' + +const foo = { + message: 'hello world' +} + +const encoded = Foo.encode(foo) +const decoded = Foo.decode(encoded) + +console.info(decoded.message) +// 'hello world' +``` + +## Contribute + +Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/protons/issues)! + +This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) + +## License + +[Apache-2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT) © Protocol Labs diff --git a/packages/protons/bin/protons.ts b/packages/protons/bin/protons.ts new file mode 100644 index 0000000..9cccdf2 --- /dev/null +++ b/packages/protons/bin/protons.ts @@ -0,0 +1,38 @@ +#! /usr/bin/env node + +import meow from 'meow' +import { generate } from '../src/index.js' + +async function main () { + const cli = meow(` + Usage + $ protons source + + Options + --output, -o Path to a directory to write transpiled typescript files into + + Examples + $ protons ./path/to/file.proto +`, { + importMeta: import.meta, + flags: { + output: { + type: 'string', + alias: 'o' + } + } + }) + + if (cli.input.length === 0) { + throw new Error('source must be specified') + } + + for (const source of cli.input) { + await generate(source, cli.flags) + } +} + +main().catch(err => { + console.error(err) // eslint-disable-line no-console + process.exit(1) +}) diff --git a/packages/protons/package.json b/packages/protons/package.json new file mode 100644 index 0000000..ab3677a --- /dev/null +++ b/packages/protons/package.json @@ -0,0 +1,161 @@ +{ + "name": "protons", + "version": "2.0.3", + "description": "Protobuf to ts transpiler", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/ipfs/protons/tree/master/packages/protons#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/ipfs/protons.git" + }, + "bugs": { + "url": "https://github.com/ipfs/protons/issues" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "bin": { + "protons": "./dist/bin/protons.js" + }, + "type": "module", + "types": "./dist/src/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ], + "src/*": [ + "*", + "dist/*", + "dist/src/*", + "dist/src/*/index" + ] + } + }, + "files": [ + "src", + "dist/src", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "import": "./dist/src/index.js", + "types": "./dist/src/index.d.ts" + }, + "./status": { + "import": "./dist/src/status.js", + "types": "./dist/src/status.d.ts" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "chore", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Trivial Changes" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "lint": "aegir lint", + "dep-check": "aegir dep-check dist/src/**/*.js dist/test/**/*.js", + "build": "tsc", + "pretest": "npm run build", + "test": "aegir test -t node -f ./dist/test/*.js -f ./dist/test/**/*.js", + "test:node": "npm run test -- -t node --cov", + "release": "semantic-release -e semantic-release-monorepo" + }, + "dependencies": { + "meow": "^10.1.2", + "protobufjs": "^6.11.2" + }, + "devDependencies": { + "aegir": "^36.1.3", + "long": "^4.0.0", + "pbjs": "^0.0.14", + "protons-runtime": "^0.0.0" + } +} diff --git a/packages/protons/src/index.ts b/packages/protons/src/index.ts new file mode 100644 index 0000000..f6977b6 --- /dev/null +++ b/packages/protons/src/index.ts @@ -0,0 +1,282 @@ +import { main as pbjs } from 'protobufjs/cli/pbjs.js' +import path from 'path' +import { promisify } from 'util' +import fs from 'fs/promises' + +function pathWithExtension (input: string, extension: string) { + return path.join(path.dirname(input), path.basename(input).split('.').slice(0, -1).join('.') + extension) +} + +const types: Record = { + double: 'number', + float: 'number', + int32: 'number', + int64: 'bigint', + uint32: 'number', + uint64: 'bigint', + sint32: 'number', + sint64: 'bigint', + fixed32: 'number', + fixed64: 'bigint', + sfixed32: 'number', + sfixed64: 'bigint', + bool: 'boolean', + string: 'string', + bytes: 'Uint8Array' +} + +function findTypeName (typeName: string, classDef: MessageDef, moduleDef: ModuleDef): string { + if (types[typeName] != null) { + return types[typeName] + } + + if (isEnumDef(classDef)) { + throw new Error('Could not find type in enum') + } + + if (classDef.nested?.[typeName] != null) { + return `${classDef.fullName}.${typeName}` + } + + if (classDef.parent != null) { + return findTypeName(typeName, classDef.parent, moduleDef) + } + + if (moduleDef.globals[typeName] != null) { + return typeName + } + + throw new Error(`Could not resolve type name "${typeName}"`) +} + +function findDef (typeName: string, classDef: MessageDef, moduleDef: ModuleDef): MessageDef { + if (isEnumDef(classDef)) { + throw new Error('Could not find type in enum') + } + + if (classDef.nested?.[typeName] != null) { + return classDef.nested?.[typeName] + } + + if (classDef.parent != null) { + return findDef(typeName, classDef.parent, moduleDef) + } + + if (moduleDef.globals[typeName] != null) { + return moduleDef.globals[typeName] + } + + throw new Error(`Could not resolve type name "${typeName}"`) +} + +const encoders: Record = { + bool: 'bool', + double: 'double', + bytes: 'bytes', + fixed32: 'fixed32', + fixed64: 'fixed64', + float: 'float', + int32: 'int32', + int64: 'int64', + sint32: 'sint32', + sint64: 'sint64', + string: 'string', + uint32: 'uint32', + uint64: 'uint64', + sfixed32: 'sfixed32', + sfixed64: 'sfixed64' +} + +interface ClassDef { + name: string + fullName: string + parent?: ClassDef + fields?: Record + nested?: Record +} + +interface EnumDef { + name: string + fullName: string + parent?: ClassDef + values: Record +} + +type MessageDef = ClassDef | EnumDef + +function isEnumDef (obj: any): obj is EnumDef { + return obj.values != null +} + +interface FieldDef { + type: string + id: number + options?: Record + rule: string +} + +function defineFields (fields: Record, messageDef: MessageDef, moduleDef: ModuleDef) { + return Object.entries(fields).map(([fieldName, fieldDef]) => { + const isArray = fieldDef.rule === 'repeated' + const isOptional = !isArray && fieldDef.options?.proto3_optional === true + + return `${fieldName}${isOptional ? '?' : ''}: ${findTypeName(fieldDef.type, messageDef, moduleDef)}${isArray ? '[]' : ''}` + }) +} + +function compileMessage (messageDef: MessageDef, moduleDef: ModuleDef): string { + if (isEnumDef(messageDef)) { + moduleDef.imports.add('enumeration') + + return ` +export enum ${messageDef.name} { + ${ + Object.entries(messageDef.values).map(([enumValueName, enumValue]) => { + return `${enumValueName} = '${enumValueName}'` + }).join(',\n ').trim() + } +} + +export namespace ${messageDef.name} { + export const codec = enumeration(${messageDef.name}) +} +` + } + + let nested = '' + + if (messageDef.nested != null) { + nested = '\n' + nested += Object.values(messageDef.nested) + .map(def => compileMessage(def, moduleDef).trim()) + .join('\n') + .split('\n') + .map(line => line.trim() === '' ? '' : ` ${line}`) + .join('\n') + nested += '\n' + } + + const fields = messageDef.fields ?? {} + + // import relevant modules + moduleDef.imports.add('encodeMessage') + moduleDef.imports.add('decodeMessage') + moduleDef.imports.add('message') + + return ` +export interface ${messageDef.name} { + ${ + defineFields(fields, messageDef, moduleDef) + .join('\n ') + .trim() + } +} + +export namespace ${messageDef.name} {${nested} + export const codec = message<${messageDef.name}>({ + ${Object.entries(fields) + .map(([name, fieldDef]) => { + let codec = encoders[fieldDef.type] + + if (codec == null) { + const def = findDef(fieldDef.type, messageDef, moduleDef) + + if (isEnumDef(def)) { + moduleDef.imports.add('enumeration') + } else { + moduleDef.imports.add('message') + } + + const typeName = findTypeName(fieldDef.type, messageDef, moduleDef) + codec = `${typeName}.codec` + } else { + moduleDef.imports.add(codec) + } + + return `${fieldDef.id}: { name: '${name}', codec: ${codec}${fieldDef.options?.proto3_optional === true ? ', optional: true' : ''}${fieldDef.rule === 'repeated' ? ', repeats: true' : ''} }` + }).join(',\n ')} + }) + + export const encode = (obj: ${messageDef.name}): Uint8Array => { + return encodeMessage(obj, ${messageDef.name}.codec) + } + + export const decode = (buf: Uint8Array): ${messageDef.name} => { + return decodeMessage(buf, ${messageDef.name}.codec) + } +} +` +} + +interface ModuleDef { + imports: Set + types: Set + compiled: string[] + globals: Record +} + +function defineModule (def: ClassDef): ModuleDef { + const moduleDef: ModuleDef = { + imports: new Set(), + types: new Set(), + compiled: [], + globals: {} + } + + const defs = def.nested + + if (defs == null) { + throw new Error('No top-level messages found in protobuf') + } + + function defineMessage (defs: Record, parent?: ClassDef) { + for (const className of Object.keys(defs)) { + const classDef = defs[className] + + classDef.name = className + classDef.parent = parent + classDef.fullName = parent == null ? className : `${parent.fullName}.${className}` + + if (classDef.nested != null) { + defineMessage(classDef.nested, classDef) + } + + if (parent == null) { + moduleDef.globals[className] = classDef + } + } + } + + defineMessage(defs) + + for (const className of Object.keys(defs)) { + const classDef = defs[className] + + moduleDef.compiled.push(compileMessage(classDef, moduleDef)) + } + + return moduleDef +} + +export async function generate (source: string, flags: any) { + // convert .protobuf to .json + const json = await promisify(pbjs)(['-t', 'json', source]) + + if (json == null) { + throw new Error(`Could not convert ${source} to intermediate JSON format`) + } + + const def = JSON.parse(json) + const moduleDef = defineModule(def) + + const content = ` +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { ${Array.from(moduleDef.imports).join(', ')} } from 'protons-runtime' + +${moduleDef.compiled.map(str => str.trim()).join('\n\n').trim()} +`.trim() + + await fs.writeFile(pathWithExtension(source, '.ts'), content + '\n') +} diff --git a/packages/protons/test/fixtures/basic.proto b/packages/protons/test/fixtures/basic.proto new file mode 100644 index 0000000..7ada835 --- /dev/null +++ b/packages/protons/test/fixtures/basic.proto @@ -0,0 +1,4 @@ +message Basic { + optional string foo = 1; + required int32 num = 2; +} diff --git a/packages/protons/test/fixtures/basic.ts b/packages/protons/test/fixtures/basic.ts new file mode 100644 index 0000000..f294002 --- /dev/null +++ b/packages/protons/test/fixtures/basic.ts @@ -0,0 +1,24 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { encodeMessage, decodeMessage, message, string, int32 } from 'protons-runtime' + +export interface Basic { + foo: string + num: number +} + +export namespace Basic { + export const codec = message({ + 1: { name: 'foo', codec: string }, + 2: { name: 'num', codec: int32 } + }) + + export const encode = (obj: Basic): Uint8Array => { + return encodeMessage(obj, Basic.codec) + } + + export const decode = (buf: Uint8Array): Basic => { + return decodeMessage(buf, Basic.codec) + } +} diff --git a/packages/protons/test/fixtures/dht.proto b/packages/protons/test/fixtures/dht.proto new file mode 100644 index 0000000..160735b --- /dev/null +++ b/packages/protons/test/fixtures/dht.proto @@ -0,0 +1,75 @@ +syntax = "proto3"; +// can't use, because protocol-buffers doesn't support imports +// so we have to duplicate for now :( +// import "record.proto"; + +message Record { + // adjusted for javascript + optional bytes key = 1; + optional bytes value = 2; + optional bytes author = 3; + optional bytes signature = 4; + optional string timeReceived = 5; +} + +message Message { + enum MessageType { + PUT_VALUE = 0; + GET_VALUE = 1; + ADD_PROVIDER = 2; + GET_PROVIDERS = 3; + FIND_NODE = 4; + PING = 5; + } + + enum ConnectionType { + // sender does not have a connection to peer, and no extra information (default) + NOT_CONNECTED = 0; + + // sender has a live connection to peer + CONNECTED = 1; + + // sender recently connected to peer + CAN_CONNECT = 2; + + // sender recently tried to connect to peer repeatedly but failed to connect + // ("try" here is loose, but this should signal "made strong effort, failed") + CANNOT_CONNECT = 3; + } + + message Peer { + // ID of a given peer. + optional bytes id = 1; + + // multiaddrs for a given peer + repeated bytes addrs = 2; + + // used to signal the sender's connection capabilities to the peer + optional ConnectionType connection = 3; + } + + // defines what type of message it is. + optional MessageType type = 1; + + // defines what coral cluster level this query/response belongs to. + // in case we want to implement coral's cluster rings in the future. + optional int32 clusterLevelRaw = 10; + + // Used to specify the key associated with this message. + // PUT_VALUE, GET_VALUE, ADD_PROVIDER, GET_PROVIDERS + // adjusted for javascript + optional bytes key = 2; + + // Used to return a value + // PUT_VALUE, GET_VALUE + // adjusted Record to bytes for js + optional bytes record = 3; + + // Used to return peers closer to a key in a query + // GET_VALUE, GET_PROVIDERS, FIND_NODE + repeated Peer closerPeers = 8; + + // Used to return Providers + // GET_VALUE, ADD_PROVIDER, GET_PROVIDERS + repeated Peer providerPeers = 9; +} diff --git a/packages/protons/test/fixtures/dht.ts b/packages/protons/test/fixtures/dht.ts new file mode 100644 index 0000000..2076c81 --- /dev/null +++ b/packages/protons/test/fixtures/dht.ts @@ -0,0 +1,102 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { encodeMessage, decodeMessage, message, bytes, string, enumeration, int32 } from 'protons-runtime' + +export interface Record { + key?: Uint8Array + value?: Uint8Array + author?: Uint8Array + signature?: Uint8Array + timeReceived?: string +} + +export namespace Record { + export const codec = message({ + 1: { name: 'key', codec: bytes, optional: true }, + 2: { name: 'value', codec: bytes, optional: true }, + 3: { name: 'author', codec: bytes, optional: true }, + 4: { name: 'signature', codec: bytes, optional: true }, + 5: { name: 'timeReceived', codec: string, optional: true } + }) + + export const encode = (obj: Record): Uint8Array => { + return encodeMessage(obj, Record.codec) + } + + export const decode = (buf: Uint8Array): Record => { + return decodeMessage(buf, Record.codec) + } +} + +export interface Message { + type?: Message.MessageType + clusterLevelRaw?: number + key?: Uint8Array + record?: Uint8Array + closerPeers: Message.Peer[] + providerPeers: Message.Peer[] +} + +export namespace Message { + export enum MessageType { + PUT_VALUE = 'PUT_VALUE', + GET_VALUE = 'GET_VALUE', + ADD_PROVIDER = 'ADD_PROVIDER', + GET_PROVIDERS = 'GET_PROVIDERS', + FIND_NODE = 'FIND_NODE', + PING = 'PING' + } + + export namespace MessageType { + export const codec = enumeration(MessageType) + } + export enum ConnectionType { + NOT_CONNECTED = 'NOT_CONNECTED', + CONNECTED = 'CONNECTED', + CAN_CONNECT = 'CAN_CONNECT', + CANNOT_CONNECT = 'CANNOT_CONNECT' + } + + export namespace ConnectionType { + export const codec = enumeration(ConnectionType) + } + export interface Peer { + id?: Uint8Array + addrs: Uint8Array[] + connection?: Message.ConnectionType + } + + export namespace Peer { + export const codec = message({ + 1: { name: 'id', codec: bytes, optional: true }, + 2: { name: 'addrs', codec: bytes, repeats: true }, + 3: { name: 'connection', codec: Message.ConnectionType.codec, optional: true } + }) + + export const encode = (obj: Peer): Uint8Array => { + return encodeMessage(obj, Peer.codec) + } + + export const decode = (buf: Uint8Array): Peer => { + return decodeMessage(buf, Peer.codec) + } + } + + export const codec = message({ + 1: { name: 'type', codec: Message.MessageType.codec, optional: true }, + 10: { name: 'clusterLevelRaw', codec: int32, optional: true }, + 2: { name: 'key', codec: bytes, optional: true }, + 3: { name: 'record', codec: bytes, optional: true }, + 8: { name: 'closerPeers', codec: Message.Peer.codec, repeats: true }, + 9: { name: 'providerPeers', codec: Message.Peer.codec, repeats: true } + }) + + export const encode = (obj: Message): Uint8Array => { + return encodeMessage(obj, Message.codec) + } + + export const decode = (buf: Uint8Array): Message => { + return decodeMessage(buf, Message.codec) + } +} diff --git a/packages/protons/test/fixtures/test.proto b/packages/protons/test/fixtures/test.proto new file mode 100644 index 0000000..96123c3 --- /dev/null +++ b/packages/protons/test/fixtures/test.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +enum AnEnum { + HERP = 0; + DERP = 1; +} + +message SubMessage { + string foo = 1; +} + +message AllTheTypes { + optional bool field1 = 1; + optional int32 field2 = 2; + optional int64 field3 = 3; + optional uint32 field4 = 4; + optional uint64 field5 = 5; + optional sint32 field6 = 6; + optional sint64 field7 = 7; + optional double field8 = 8; + optional float field9 = 9; + optional string field10 = 10; + optional bytes field11 = 11; + optional AnEnum field12 = 12; + optional SubMessage field13 = 13; + repeated string field14 = 14; + optional fixed32 field15 = 15; + optional fixed64 field16 = 16; + optional sfixed32 field17 = 17; + optional sfixed64 field18 = 18; +} diff --git a/packages/protons/test/fixtures/test.ts b/packages/protons/test/fixtures/test.ts new file mode 100644 index 0000000..becfffc --- /dev/null +++ b/packages/protons/test/fixtures/test.ts @@ -0,0 +1,83 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { enumeration, encodeMessage, decodeMessage, message, string, bool, int32, int64, uint32, uint64, sint32, sint64, double, float, bytes, fixed32, fixed64, sfixed32, sfixed64 } from 'protons-runtime' + +export enum AnEnum { + HERP = 'HERP', + DERP = 'DERP' +} + +export namespace AnEnum { + export const codec = enumeration(AnEnum) +} + +export interface SubMessage { + foo: string +} + +export namespace SubMessage { + export const codec = message({ + 1: { name: 'foo', codec: string } + }) + + export const encode = (obj: SubMessage): Uint8Array => { + return encodeMessage(obj, SubMessage.codec) + } + + export const decode = (buf: Uint8Array): SubMessage => { + return decodeMessage(buf, SubMessage.codec) + } +} + +export interface AllTheTypes { + field1?: boolean + field2?: number + field3?: bigint + field4?: number + field5?: bigint + field6?: number + field7?: bigint + field8?: number + field9?: number + field10?: string + field11?: Uint8Array + field12?: AnEnum + field13?: SubMessage + field14: string[] + field15?: number + field16?: bigint + field17?: number + field18?: bigint +} + +export namespace AllTheTypes { + export const codec = message({ + 1: { name: 'field1', codec: bool, optional: true }, + 2: { name: 'field2', codec: int32, optional: true }, + 3: { name: 'field3', codec: int64, optional: true }, + 4: { name: 'field4', codec: uint32, optional: true }, + 5: { name: 'field5', codec: uint64, optional: true }, + 6: { name: 'field6', codec: sint32, optional: true }, + 7: { name: 'field7', codec: sint64, optional: true }, + 8: { name: 'field8', codec: double, optional: true }, + 9: { name: 'field9', codec: float, optional: true }, + 10: { name: 'field10', codec: string, optional: true }, + 11: { name: 'field11', codec: bytes, optional: true }, + 12: { name: 'field12', codec: AnEnum.codec, optional: true }, + 13: { name: 'field13', codec: SubMessage.codec, optional: true }, + 14: { name: 'field14', codec: string, repeats: true }, + 15: { name: 'field15', codec: fixed32, optional: true }, + 16: { name: 'field16', codec: fixed64, optional: true }, + 17: { name: 'field17', codec: sfixed32, optional: true }, + 18: { name: 'field18', codec: sfixed64, optional: true } + }) + + export const encode = (obj: AllTheTypes): Uint8Array => { + return encodeMessage(obj, AllTheTypes.codec) + } + + export const decode = (buf: Uint8Array): AllTheTypes => { + return decodeMessage(buf, AllTheTypes.codec) + } +} diff --git a/packages/protons/test/index.spec.ts b/packages/protons/test/index.spec.ts new file mode 100644 index 0000000..2ba943b --- /dev/null +++ b/packages/protons/test/index.spec.ts @@ -0,0 +1,136 @@ +/* eslint-env mocha */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ + +import { expect } from 'aegir/utils/chai.js' +import pbjs from 'pbjs' +import { Basic } from './fixtures/basic.js' +import { AllTheTypes, AnEnum } from './fixtures/test.js' +import fs from 'fs' +import Long from 'long' + +function longifyBigInts (obj: any) { + const output = { + ...obj + } + + for (const key of Object.keys(output)) { + if (typeof output[key] === 'bigint') { + output[key] = Long.fromString(`${output[key].toString()}`) + } + } + + return output +} + +describe('encode', () => { + it('should encode', () => { + expect.toString() + + const basic: Basic = { + foo: 'hello world', + num: 5 + } + + const schema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/basic.proto', 'utf-8')).compile() + const pbjsBuf = schema.encodeBasic(basic) + + const encoded = Basic.encode(basic) + expect(encoded).to.equalBytes(pbjsBuf) + + const decoded = Basic.decode(encoded) + expect(decoded).to.deep.equal(basic) + + expect(Basic.decode(encoded)).to.deep.equal(basic) + expect(Basic.decode(pbjsBuf)).to.deep.equal(basic) + }) + + it('should encode all the types', () => { + const allTheTypes: AllTheTypes = { + field1: true, + field2: 1, + field3: 1n, + field4: 1, + field5: 1n, + field6: 1, + field7: 1n, + field8: 1, + field9: 1, + field10: 'hello', + field11: Uint8Array.from([1, 2, 3]), + field12: AnEnum.DERP, + field13: { + foo: 'bar' + }, + field14: ['qux', 'garply'], + field15: 1, + field16: 1n, + field17: 1, + field18: 1n + } + + const schema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/test.proto', 'utf-8')).compile() + const pbjsBuf = schema.encodeAllTheTypes(longifyBigInts(allTheTypes)) + + const encoded = AllTheTypes.encode(allTheTypes) + expect(encoded).to.equalBytes(pbjsBuf) + + expect(AllTheTypes.decode(encoded)).to.deep.equal(allTheTypes) + expect(AllTheTypes.decode(pbjsBuf)).to.deep.equal(allTheTypes) + }) + + it('should encode all the types with max numeric values', () => { + const allTheTypes: AllTheTypes = { + field1: true, + field2: 2147483647, + field3: 9223372036854775807n, + field4: 4294967295, + field5: 18446744073709551615n, + field6: 2147483647, + field7: 9223372036854775807n, + field8: 2147483647, + field9: 2147483648, + field14: [], + field15: 2147483647, + field16: 9223372036854775807n, + field17: 2147483647, + field18: 9223372036854775807n + } + + const schema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/test.proto', 'utf-8')).compile() + const pbjsBuf = schema.encodeAllTheTypes(longifyBigInts(allTheTypes)) + + const encoded = AllTheTypes.encode(allTheTypes) + expect(encoded).to.equalBytes(pbjsBuf) + + expect(AllTheTypes.decode(encoded)).to.deep.equal(allTheTypes) + expect(AllTheTypes.decode(pbjsBuf)).to.deep.equal(allTheTypes) + }) + + it('should encode all the types with min numeric values', () => { + const allTheTypes: AllTheTypes = { + field1: false, + field2: -2147483647, + field3: -9223372036854775807n, + field4: 0, + field5: 0n, + field6: -2147483647, + field7: -9223372036854775807n, + field8: -2147483647, + field9: -2147483648, + field14: [], + field15: -2147483647, + field16: -9223372036854775807n, + field17: -2147483647, + field18: -9223372036854775807n + } + + const schema = pbjs.parseSchema(fs.readFileSync('./test/fixtures/test.proto', 'utf-8')).compile() + const pbjsBuf = schema.encodeAllTheTypes(longifyBigInts(allTheTypes)) + + const encoded = AllTheTypes.encode(allTheTypes) + expect(encoded).to.equalBytes(pbjsBuf) + + expect(AllTheTypes.decode(encoded)).to.deep.equal(allTheTypes) + expect(AllTheTypes.decode(pbjsBuf)).to.deep.equal(allTheTypes) + }) +}) diff --git a/packages/protons/tsconfig.json b/packages/protons/tsconfig.json new file mode 100644 index 0000000..7f21b4d --- /dev/null +++ b/packages/protons/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist", + "emitDeclarationOnly": false, + "module": "ES2020" + }, + "include": [ + "bin", + "src", + "test" + ], + "references": [ + { + "path": "../protons-runtime" + } + ] +} diff --git a/src/compile/decode.js b/src/compile/decode.js deleted file mode 100644 index 37c9166..0000000 --- a/src/compile/decode.js +++ /dev/null @@ -1,330 +0,0 @@ -/* eslint max-depth: 1 */ -'use strict' - -const varint = require('varint') -const defined = require('./utils').defined - -function toSentenceCase (string) { - return `${string.substring(0, 1).toUpperCase()}${string.substring(1)}` -} - -function addPropertyAccessors (obj, name, value, defaultValue) { - if (Object.prototype.hasOwnProperty.call(obj, name)) { - // have already added this property - return - } - - const sentenceCaseName = toSentenceCase(name) - - Object.defineProperties(obj, { - [name]: { - enumerable: true, - configurable: true, - set: (val) => { - value = val - }, - get: () => { - if (value === undefined) { - return defaultValue - } - - return value - } - }, - [`has${sentenceCaseName}`]: { - configurable: true, - value: () => { - return value !== undefined - } - }, - [`set${sentenceCaseName}`]: { - configurable: true, - value: (val) => { - value = val - } - }, - [`get${sentenceCaseName}`]: { - configurable: true, - value: () => { - return value - } - }, - [`clear${sentenceCaseName}`]: { - configurable: true, - value: () => { - value = undefined - obj[name] = undefined - } - } - }) -} - -function compileDecode (m, resolve, enc) { - const requiredFields = [] - const fields = {} - const oneofFields = [] - const vals = [] - - for (var i = 0; i < enc.length; i++) { - const field = m.fields[i] - - fields[field.tag] = i - - const def = field.options && field.options.default - const resolved = resolve(field.type, m.id, false) - vals[i] = [def, resolved && resolved.values] - - m.fields[i].packed = field.repeated && field.options && field.options.packed && field.options.packed !== 'false' - - if (field.required) { - requiredFields.push(field.name) - } - - if (field.oneof) { - oneofFields.push(field.name) - } - } - - function decodeField (e, field, obj, buf, dataView, offset, i) { - const name = field.name - - if (field.oneof) { - // clear already defined oneof fields - const props = Object.keys(obj) - for (var j = 0; j < props.length; j++) { - if (oneofFields.indexOf(props[j]) > -1) { - const sentenceCase = toSentenceCase(props[j]) - delete obj[`has${sentenceCase}`] - delete obj[`get${sentenceCase}`] - delete obj[`set${sentenceCase}`] - delete obj[`clear${sentenceCase}`] - delete obj[props[j]] - } - } - } - - let value - - if (e.message) { - const len = varint.decode(buf, offset) - offset += varint.decode.bytes - - const decoded = e.decode(buf, dataView, offset, offset + len) - - if (field.map) { - value = obj[name] || {} - value[decoded.key] = decoded.value - } else if (field.repeated) { - value = obj[name] || [] - value.push(decoded) - } else { - value = decoded - } - } else { - if (field.repeated) { - value = obj[name] || [] - value.push(e.decode(buf, dataView, offset)) - } else { - value = e.decode(buf, dataView, offset) - } - } - - addPropertyAccessors(obj, name, value) - - offset += e.decode.bytes - - return offset - } - - return function decode (buf, view, offset, end) { - if (offset == null) { - offset = 0 - } - - if (end == null) { - end = buf.length - } - - if (!(end <= buf.length && offset <= buf.length)) { - throw new Error('Decoded message is not valid') - } - - if (!view) { - view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength) - } - - var oldOffset = offset - var obj = {} - var field - - while (true) { - if (end <= offset) { - // finished - - // check required methods - var name = '' - var j = 0 - for (j = 0; j < requiredFields.length; j++) { - name = requiredFields[j] - if (!defined(obj[name])) { - throw new Error('Decoded message is not valid, missing required field: ' + name) - } - } - - // fill out missing defaults - var val - var def - for (j = 0; j < enc.length; j++) { - field = m.fields[j] - def = vals[j][0] - val = vals[j][1] - name = field.name - let defaultVal - - if (Object.prototype.hasOwnProperty.call(obj, name)) { - continue - } - - var done = false - - if (field.oneof) { - var props = Object.keys(obj) - - for (var k = 0; k < props.length; k++) { - if (oneofFields.indexOf(props[k]) > -1) { - done = true - break - } - } - } - - if (done) { - continue - } - - if (val) { // is enum - if (field.repeated) { - def = [] - } else { - def = (def && val[def]) ? val[def].value : val[Object.keys(val)[0]].value - def = parseInt(def || 0, 10) - } - } else { - defaultVal = defaultValue(field) - def = coerceValue(field, def) - } - - addPropertyAccessors(obj, name, def, defaultVal) - } - - decode.bytes = offset - oldOffset - return obj - } - - var prefix = varint.decode(buf, offset) - offset += varint.decode.bytes - var tag = prefix >> 3 - - var i = fields[tag] - - if (i == null) { - offset = skip(prefix & 7, buf, view, offset) - continue - } - - var e = enc[i] - field = m.fields[i] - - if (field.packed) { - var packedEnd = varint.decode(buf, offset) - offset += varint.decode.bytes - packedEnd += offset - - while (offset < packedEnd) { - offset = decodeField(e, field, obj, buf, view, offset, i) - } - } else { - offset = decodeField(e, field, obj, buf, view, offset, i) - } - } - } -} - -var skip = function (type, buffer, view, offset) { - switch (type) { - case 0: - varint.decode(buffer, offset) - return offset + varint.decode.bytes - - case 1: - return offset + 8 - - case 2: - var len = varint.decode(buffer, offset) - return offset + varint.decode.bytes + len - - case 3: - case 4: - throw new Error('Groups are not supported') - - case 5: - return offset + 4 - default: - throw new Error('Unknown wire type: ' + type) - } -} - -var defaultValue = function (f) { - if (f.map) return {} - if (f.repeated) return [] - - switch (f.type) { - case 'string': - return '' - case 'bool': - return false - case 'float': - case 'double': - case 'sfixed32': - case 'fixed32': - case 'varint': - case 'enum': - case 'uint64': - case 'uint32': - case 'int64': - case 'int32': - case 'sint64': - case 'sint32': - return 0 - default: - return null - } -} - -var coerceValue = function (f, def) { - if (def === undefined) { - return def - } - - switch (f.type) { - case 'bool': - return def === 'true' - case 'float': - case 'double': - case 'sfixed32': - case 'fixed32': - case 'varint': - case 'enum': - case 'uint64': - case 'uint32': - case 'int64': - case 'int32': - case 'sint64': - case 'sint32': - return parseInt(def, 10) - default: - return def - } -} - -module.exports = compileDecode diff --git a/src/compile/encode.js b/src/compile/encode.js deleted file mode 100644 index 0406357..0000000 --- a/src/compile/encode.js +++ /dev/null @@ -1,133 +0,0 @@ -'use strict' - -var defined = require('./utils').defined -var varint = require('varint') - -function compileEncode (m, resolve, enc, oneofs, encodingLength) { - const oneofsKeys = Object.keys(oneofs) - const encLength = enc.length - const ints = {} - for (let i = 0; i < encLength; i++) { - ints[i] = { - p: varint.encode(m.fields[i].tag << 3 | 2), - h: varint.encode(m.fields[i].tag << 3 | enc[i].type) - } - - const field = m.fields[i] - m.fields[i].packed = field.repeated && field.options && field.options.packed && field.options.packed !== 'false' - } - - function encodeField (buf, view, offset, h, e, packed, innerVal) { - let j = 0 - if (!packed) { - for (j = 0; j < h.length; j++) { - buf[offset++] = h[j] - } - } - - if (e.message) { - varint.encode(e.encodingLength(innerVal), buf, offset) - offset += varint.encode.bytes - } - - e.encode(innerVal, buf, view, offset) - - return offset + e.encode.bytes - } - - return function encode (obj, buf, view, offset = 0) { - if (buf == null) { - buf = new Uint8Array(encodingLength(obj)) - } - - if (view == null) { - view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength) - } - - const oldOffset = offset - const objKeys = Object.keys(obj) - let i = 0 - - // oneof checks - - let match = false - for (i = 0; i < oneofsKeys.length; i++) { - const name = oneofsKeys[i] - const prop = oneofs[i] - if (objKeys.indexOf(prop) > -1) { - if (match) { - throw new Error('only one of the properties defined in oneof ' + name + ' can be set') - } - - match = true - } - } - - for (i = 0; i < encLength; i++) { - const e = enc[i] - const field = m.fields[i] // was f - let val = obj[field.name] - let j = 0 - - if (!defined(val)) { - if (field.required) { - throw new Error(field.name + ' is required') - } - continue - } - const p = ints[i].p - const h = ints[i].h - - const packed = field.packed - - if (field.map) { - const tmp = Object.keys(val) - for (j = 0; j < tmp.length; j++) { - tmp[j] = { - key: tmp[j], - value: val[tmp[j]] - } - } - val = tmp - } - - if (packed) { - let packedLen = 0 - for (j = 0; j < val.length; j++) { - if (!Object.prototype.hasOwnProperty.call(val, j)) { - continue - } - - packedLen += e.encodingLength(val[j]) - } - - if (packedLen) { - for (j = 0; j < h.length; j++) { - buf[offset++] = p[j] - } - varint.encode(packedLen, buf, offset) - offset += varint.encode.bytes - } - } - - if (field.repeated) { - let innerVal - for (j = 0; j < val.length; j++) { - innerVal = val[j] - if (!defined(innerVal)) { - continue - } - - offset = encodeField(buf, view, offset, h, e, packed, innerVal) - } - } else { - offset = encodeField(buf, view, offset, h, e, packed, val) - } - } - - encode.bytes = offset - oldOffset - return buf - } -} - -module.exports = compileEncode diff --git a/src/compile/encoding-length.js b/src/compile/encoding-length.js deleted file mode 100644 index 450e251..0000000 --- a/src/compile/encoding-length.js +++ /dev/null @@ -1,102 +0,0 @@ -'use strict' - -var defined = require('./utils').defined -var varint = require('varint') - -function compileEncodingLength (m, enc, oneofs) { - const oneofsKeys = Object.keys(oneofs) - const encLength = enc.length - - const hls = new Array(encLength) - - for (let i = 0; i < m.fields.length; i++) { - hls[i] = varint.encodingLength(m.fields[i].tag << 3 | enc[i].type) - - const field = m.fields[i] - m.fields[i].packed = field.repeated && field.options && field.options.packed && field.options.packed !== 'false' - } - - return function encodingLength (obj) { - let length = 0 - let i = 0 - let j = 0 - - for (i = 0; i < oneofsKeys.length; i++) { - const name = oneofsKeys[i] - const props = oneofs[name] - - let match = false - for (j = 0; j < props.length; j++) { - if (defined(obj[props[j]])) { - if (match) { - throw new Error('only one of the properties defined in oneof ' + name + ' can be set') - } - match = true - } - } - } - - for (i = 0; i < encLength; i++) { - const e = enc[i] - const field = m.fields[i] - let val = obj[field.name] - const hl = hls[i] - let len - - if (!defined(val)) { - if (field.required) { - throw new Error(field.name + ' is required') - } - - continue - } - - if (field.map) { - const tmp = Object.keys(val) - for (j = 0; j < tmp.length; j++) { - tmp[j] = { - key: tmp[j], - value: val[tmp[j]] - } - } - - val = tmp - } - - if (field.packed) { - let packedLen = 0 - for (j = 0; j < val.length; j++) { - if (!defined(val[j])) { - continue - } - len = e.encodingLength(val[j]) - packedLen += len - - if (e.message) { - packedLen += varint.encodingLength(len) - } - } - - if (packedLen) { - length += hl + packedLen + varint.encodingLength(packedLen) - } - } else if (field.repeated) { - for (j = 0; j < val.length; j++) { - if (!defined(val[j])) { - continue - } - - len = e.encodingLength(val[j]) - length += hl + len + (e.message ? varint.encodingLength(len) : 0) - } - } else { - len = e.encodingLength(val) - length += hl + len + (e.message ? varint.encodingLength(len) : 0) - } - } - - return length - } -} - -module.exports = compileEncodingLength diff --git a/src/compile/encodings/bool.js b/src/compile/encodings/bool.js deleted file mode 100644 index 7abdc43..0000000 --- a/src/compile/encodings/bool.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const encoder = require('./encoder') - -function boolEncodingLength () { - return 1 -} - -function boolEncode (value, buffer, dataView, offset) { - buffer[offset] = value ? 1 : 0 - boolEncode.bytes = 1 -} - -function boolDecode (buffer, dataView, offset) { - const bool = buffer[offset] > 0 - boolDecode.bytes = 1 - - return bool -} - -module.exports = encoder(0, boolEncode, boolDecode, boolEncodingLength) diff --git a/src/compile/encodings/bytes.js b/src/compile/encodings/bytes.js deleted file mode 100644 index b06ac90..0000000 --- a/src/compile/encodings/bytes.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict' - -const varint = require('varint') -const encoder = require('./encoder') - -function bytesBufferLength (val) { - return val.byteLength -} - -function bytesEncodingLength (val) { - const len = bytesBufferLength(val) - return varint.encodingLength(len) + len -} - -function bytesEncode (val, buffer, dataView, offset) { - const oldOffset = offset - const len = bytesBufferLength(val) - - varint.encode(len, buffer, offset) - offset += varint.encode.bytes - - buffer.set(val, offset) - offset += len - - bytesEncode.bytes = offset - oldOffset -} - -function bytesDecode (buffer, dataView, offset) { - const oldOffset = offset - - const len = varint.decode(buffer, offset) - offset += varint.decode.bytes - - const val = buffer.slice(offset, offset + len) - offset += val.length - - bytesDecode.bytes = offset - oldOffset - - return val -} - -module.exports = encoder(2, bytesEncode, bytesDecode, bytesEncodingLength) diff --git a/src/compile/encodings/double.js b/src/compile/encodings/double.js deleted file mode 100644 index e6a6354..0000000 --- a/src/compile/encodings/double.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const encoder = require('./encoder') - -function doubleEncodingLength () { - return 8 -} - -function doubleEncode (val, buffer, dataView, offset) { - dataView.setFloat64(offset, val, true) - doubleEncode.bytes = 8 -} - -function doubleDecode (buffer, dataView, offset) { - const val = dataView.getFloat64(offset, true) - doubleDecode.bytes = 8 - - return val -} - -module.exports = encoder(1, doubleEncode, doubleDecode, doubleEncodingLength) diff --git a/src/compile/encodings/encoder.js b/src/compile/encodings/encoder.js deleted file mode 100644 index 02868cc..0000000 --- a/src/compile/encodings/encoder.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict' - -function encoder (type, encode, decode, encodingLength) { - encode.bytes = decode.bytes = 0 - - return { - type: type, - encode: encode, - decode: decode, - encodingLength: encodingLength - } -} - -module.exports = encoder diff --git a/src/compile/encodings/fixed32.js b/src/compile/encodings/fixed32.js deleted file mode 100644 index a9f0ded..0000000 --- a/src/compile/encodings/fixed32.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const encoder = require('./encoder') - -function fixed32EncodingLength (val) { - return 4 -} - -function fixed32Encode (val, buffer, dataView, offset) { - dataView.setUint32(offset, val, true) - fixed32Encode.bytes = 4 -} - -function fixed32Decode (buffer, dataView, offset) { - const val = dataView.getUint32(offset, true) - fixed32Decode.bytes = 4 - - return val -} - -module.exports = encoder(5, fixed32Encode, fixed32Decode, fixed32EncodingLength) diff --git a/src/compile/encodings/fixed64.js b/src/compile/encodings/fixed64.js deleted file mode 100644 index 1c5c75a..0000000 --- a/src/compile/encodings/fixed64.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict' - -const encoder = require('./encoder') - -function fixed64EncodingLength () { - return 8 -} - -function fixed64Encode (val, buffer, dataView, offset) { - for (const byte of val) { - buffer[offset] = byte - offset++ - } - - fixed64Encode.bytes = 8 -} - -function fixed64Decode (buffer, dataView, offset) { - const val = buffer.slice(offset, offset + 8) - fixed64Decode.bytes = 8 - - return val -} - -module.exports = encoder(1, fixed64Encode, fixed64Decode, fixed64EncodingLength) diff --git a/src/compile/encodings/float.js b/src/compile/encodings/float.js deleted file mode 100644 index f996364..0000000 --- a/src/compile/encodings/float.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const encoder = require('./encoder') - -function floatEncodingLength () { - return 4 -} - -function floatEncode (val, buffer, dataView, offset) { - dataView.setFloat32(offset, val, true) - floatEncode.bytes = 4 -} - -function floatDecode (buffer, dataView, offset) { - const val = dataView.getFloat32(offset, true) - floatDecode.bytes = 4 - - return val -} - -module.exports = encoder(5, floatEncode, floatDecode, floatEncodingLength) diff --git a/src/compile/encodings/index.js b/src/compile/encodings/index.js deleted file mode 100644 index 2069778..0000000 --- a/src/compile/encodings/index.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict' - -exports.make = require('./encoder') -exports.bytes = require('./bytes') -exports.string = require('./string') -exports.bool = require('./bool') -exports.int32 = require('./int32') -exports.int64 = require('./int64') -exports.sint32 = -exports.sint64 = require('./sint64') -exports.uint32 = -exports.uint64 = -exports.enum = -exports.varint = require('./varint') - -// we cannot represent these in javascript so we just use buffers -exports.fixed64 = -exports.sfixed64 = require('./fixed64') -exports.double = require('./double') -exports.fixed32 = require('./fixed32') -exports.sfixed32 = require('./sfixed32') -exports.float = require('./float') diff --git a/src/compile/encodings/int32.js b/src/compile/encodings/int32.js deleted file mode 100644 index 9d9fe82..0000000 --- a/src/compile/encodings/int32.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict' - -const varint = require('varint') -const encoder = require('./encoder') - -function in32Encode (val, buffer, dataView, offset) { - varint.encode(val < 0 ? val + 4294967296 : val, buffer, offset) - in32Encode.bytes = varint.encode.bytes -} - -function int32Decode (buffer, dataView, offset) { - const val = varint.decode(buffer, offset) - int32Decode.bytes = varint.decode.bytes - - return val > 2147483647 ? val - 4294967296 : val -} - -function int32EncodingLength (val) { - return varint.encodingLength(val < 0 ? val + 4294967296 : val) -} - -module.exports = encoder(0, in32Encode, int32Decode, int32EncodingLength) diff --git a/src/compile/encodings/int64.js b/src/compile/encodings/int64.js deleted file mode 100644 index fcb01ec..0000000 --- a/src/compile/encodings/int64.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict' - -const varint = require('varint') -const encoder = require('./encoder') - -function int64Encode (val, buffer, dataView, offset) { - if (val < 0) { - const last = offset + 9 - varint.encode(val * -1, buffer, offset) - - offset += varint.encode.bytes - 1 - buffer[offset] = buffer[offset] | 0x80 - - while (offset < last - 1) { - offset++ - buffer[offset] = 0xff - } - buffer[last] = 0x01 - - int64Encode.bytes = 10 - } else { - varint.encode(val, buffer, offset) - int64Encode.bytes = varint.encode.bytes - } -} - -function int64Decode (buffer, dataView, offset) { - let val = varint.decode(buffer, offset) - - if (val >= Math.pow(2, 63)) { - let limit = 9 - while (buffer[offset + limit - 1] === 0xff) limit-- - limit = limit || 9 - const subset = buffer.subarray(offset, offset + limit) - subset[limit - 1] = subset[limit - 1] & 0x7f - val = -1 * varint.decode(subset, 0) - int64Decode.bytes = 10 - } else { - int64Decode.bytes = varint.decode.bytes - } - - return val -} - -function int64EncodingLength (val) { - return val < 0 ? 10 : varint.encodingLength(val) -} - -module.exports = encoder(0, int64Encode, int64Decode, int64EncodingLength) diff --git a/src/compile/encodings/sfixed32.js b/src/compile/encodings/sfixed32.js deleted file mode 100644 index 875db0f..0000000 --- a/src/compile/encodings/sfixed32.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const encoder = require('./encoder') - -function sfixed32EncodingLength (val) { - return 4 -} - -function sfixed32Encode (val, buffer, dataView, offset) { - dataView.setInt32(offset, val, true) - sfixed32Encode.bytes = 4 -} - -function sfixed32Decode (buffer, dataView, offset) { - const val = dataView.getInt32(offset, true) - sfixed32Decode.bytes = 4 - - return val -} - -module.exports = encoder(5, sfixed32Encode, sfixed32Decode, sfixed32EncodingLength) diff --git a/src/compile/encodings/sint64.js b/src/compile/encodings/sint64.js deleted file mode 100644 index 614b3df..0000000 --- a/src/compile/encodings/sint64.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -const svarint = require('signed-varint') -const encoder = require('./encoder') - -function svarintEncode (val, buffer, dataView, offset) { - svarint.encode(val, buffer, offset) - - svarintEncode.bytes = svarint.encode.bytes -} - -function svarintDecode (buffer, dataView, offset) { - const val = svarint.decode(buffer, offset) - svarintDecode.bytes = svarint.decode.bytes - - return val -} - -module.exports = encoder(0, svarintEncode, svarintDecode, svarint.encodingLength) diff --git a/src/compile/encodings/string.js b/src/compile/encodings/string.js deleted file mode 100644 index 5b4ebc1..0000000 --- a/src/compile/encodings/string.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict' - -const varint = require('varint') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') -const encoder = require('./encoder') - -function stringEncodingLength (val) { - const len = uint8ArrayFromString(val).byteLength - return varint.encodingLength(len) + len -} - -function stringEncode (val, buffer, dataView, offset) { - const oldOffset = offset - const len = uint8ArrayFromString(val).byteLength - - varint.encode(len, buffer, offset, 'utf-8') - offset += varint.encode.bytes - - const arr = uint8ArrayFromString(val) - buffer.set(arr, offset) - offset += arr.length - - stringEncode.bytes = offset - oldOffset -} - -function stringDecode (buffer, dataView, offset) { - const oldOffset = offset - - const len = varint.decode(buffer, offset) - offset += varint.decode.bytes - - const val = uint8ArrayToString(buffer.subarray(offset, offset + len)) - offset += len - - stringDecode.bytes = offset - oldOffset - - return val -} - -module.exports = encoder(2, stringEncode, stringDecode, stringEncodingLength) diff --git a/src/compile/encodings/varint.js b/src/compile/encodings/varint.js deleted file mode 100644 index a1d267a..0000000 --- a/src/compile/encodings/varint.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -const varint = require('varint') -const encoder = require('./encoder') - -function varintEncode (val, buffer, dataView, offset) { - varint.encode(val, buffer, offset) - - varintEncode.bytes = varint.encode.bytes -} - -function varintDecode (buffer, dataView, offset) { - const val = varint.decode(buffer, offset) - varintDecode.bytes = varint.decode.bytes - - return val -} - -module.exports = encoder(0, varintEncode, varintDecode, varint.encodingLength) diff --git a/src/compile/index.js b/src/compile/index.js deleted file mode 100644 index 69b9310..0000000 --- a/src/compile/index.js +++ /dev/null @@ -1,165 +0,0 @@ -'use strict' - -const encodings = require('./encodings') -const compileDecode = require('./decode') -const compileEncode = require('./encode') -const compileEncodingLength = require('./encoding-length') -const varint = require('varint') - -const flatten = function (values) { - if (!values) return null - const result = {} - Object.keys(values).forEach(function (k) { - result[k] = values[k].value - }) - return result -} - -module.exports = function (schema, extraEncodings) { - const messages = {} - const enums = {} - const cache = {} - - const visit = function (schema, prefix) { - if (schema.enums) { - schema.enums.forEach(function (e) { - e.id = prefix + (prefix ? '.' : '') + e.name - enums[e.id] = e - visit(e, e.id) - }) - } - if (schema.messages) { - schema.messages.forEach(function (m) { - m.id = prefix + (prefix ? '.' : '') + m.name - messages[m.id] = m - m.fields.forEach(function (f) { - if (!f.map) return - - const name = 'Map_' + f.map.from + '_' + f.map.to - const map = { - name: name, - enums: [], - messages: [], - fields: [{ - name: 'key', - type: f.map.from, - tag: 1, - repeated: false, - required: true - }, { - name: 'value', - type: f.map.to, - tag: 2, - repeated: false, - required: false - }], - extensions: null, - id: prefix + (prefix ? '.' : '') + name - } - - if (!messages[map.id]) { - messages[map.id] = map - schema.messages.push(map) - } - f.type = name - f.repeated = true - }) - visit(m, m.id) - }) - } - } - - visit(schema, '') - - const compileEnum = function (e) { - const values = Object.keys(e.values || []).map(function (k) { - return parseInt(e.values[k].value, 10) - }) - - const encode = function enumEncode (val, buf, view, offset) { - if (!values.length || values.indexOf(val) === -1) { - throw new Error('Invalid enum value: ' + val) - } - varint.encode(val, buf, offset) - enumEncode.bytes = varint.encode.bytes - return buf - } - - const decode = function enumDecode (buf, view, offset) { - var val = varint.decode(buf, offset) - if (!values.length || values.indexOf(val) === -1) { - throw new Error('Invalid enum value: ' + val) - } - enumDecode.bytes = varint.decode.bytes - return val - } - - return encodings.make(0, encode, decode, varint.encodingLength) - } - - const compileMessage = function (m, exports) { - m.messages.forEach(function (nested) { - exports[nested.name] = resolve(nested.name, m.id) - }) - - m.enums.forEach(function (val) { - exports[val.name] = flatten(val.values) - }) - - exports.type = 2 - exports.message = true - exports.name = m.name - - const oneofs = {} - - m.fields.forEach(function (f) { - if (!f.oneof) return - if (!oneofs[f.oneof]) oneofs[f.oneof] = [] - oneofs[f.oneof].push(f.name) - }) - - const enc = m.fields.map(function (f) { - return resolve(f.type, m.id) - }) - - const encodingLength = compileEncodingLength(m, enc, oneofs) - const encode = compileEncode(m, resolve, enc, oneofs, encodingLength) - const decode = compileDecode(m, resolve, enc) - - // end of compilation - return all the things - - encode.bytes = decode.bytes = 0 - - exports.buffer = true - exports.encode = encode - exports.decode = decode - exports.encodingLength = encodingLength - - return exports - } - - const resolve = function (name, from, compile) { - if (extraEncodings && extraEncodings[name]) return extraEncodings[name] - if (encodings[name]) return encodings[name] - - const m = (from ? from + '.' + name : name).split('.') - .map(function (part, i, list) { - return list.slice(0, i).concat(name).join('.') - }) - .reverse() - .reduce(function (result, id) { - return result || messages[id] || enums[id] - }, null) - - if (compile === false) return m - if (!m) throw new Error('Could not resolve ' + name) - - if (m.values) return compileEnum(m) - const res = cache[m.id] || compileMessage(m, cache[m.id] = {}) - return res - } - - return (schema.enums || []).concat((schema.messages || []).map(function (message) { - return resolve(message.id) - })) -} diff --git a/src/compile/utils.js b/src/compile/utils.js deleted file mode 100644 index 5ca1671..0000000 --- a/src/compile/utils.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict' - -exports.defined = function (val) { - return val !== null && val !== undefined && (typeof val !== 'number' || !isNaN(val)) -} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index ede5874..0000000 --- a/src/index.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict' - -var schema = require('protocol-buffers-schema') -var compile = require('./compile') - -var flatten = function (values) { - if (!values) return null - var result = {} - Object.keys(values).forEach(function (k) { - result[k] = values[k].value - }) - return result -} - -module.exports = function (proto, opts) { - if (!opts) opts = {} - if (!proto) throw new Error('Pass in a .proto string or a protobuf-schema parsed object') - - var sch = (typeof proto === 'object' && !(proto instanceof Uint8Array)) ? proto : schema.parse(proto) - - // to not make toString,toJSON enumarable we make a fire-and-forget prototype - var Messages = function () { - var self = this - - compile(sch, opts.encodings || {}).forEach(function (m) { - self[m.name] = flatten(m.values) || m - }) - } - - Messages.prototype.toString = function () { - return schema.stringify(sch) - } - - Messages.prototype.toJSON = function () { - return sch - } - - return new Messages() -} diff --git a/test/basic.spec.js b/test/basic.spec.js deleted file mode 100644 index 3d222f2..0000000 --- a/test/basic.spec.js +++ /dev/null @@ -1,109 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobufNpm = require('protocol-buffers') -const protobuf = require('../src') -const proto = require('./test.proto') -const Basic = protobuf(proto).Basic -const BasicNpm = protobufNpm(proto).Basic -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const { toString: uint8ArrayToString } = require('uint8arrays/to-string') - -describe('basic', () => { - it('should encode basic object', () => { - const first = { - num: 1, - payload: uint8ArrayFromString('lol') - } - - const b1 = Basic.encode(first) - - const bn1 = BasicNpm.encode({ - ...first, - payload: 'lol' // old version does not support Uint8Arrays - }) - - expect(uint8ArrayToString(b1, 'base16')).to.equal(uint8ArrayToString(bn1, 'base16')) - - const b2 = Basic.encode({ - num: 1, - payload: uint8ArrayFromString('lol'), - meeeh: 42 - }) - - const b3 = Basic.encode({ - num: 1, - payload: uint8ArrayFromString('lol'), - meeeh: 42 - }) - - expect(b2).to.deep.equal(b1) - expect(b3).to.deep.equal(b1) - }) - - it('should encode and decode basic object', () => { - const b1 = Basic.encode({ - num: 1, - payload: uint8ArrayFromString('lol') - }) - - const o1 = Basic.decode(b1) - - expect(o1).to.have.property('num', 1) - expect(o1).to.have.deep.property('payload', uint8ArrayFromString('lol')) - - const b2 = Basic.encode({ - num: 1, - payload: uint8ArrayFromString('lol'), - meeeh: 42 - }) - - const o2 = Basic.decode(b2) - - expect(o2).to.deep.equal(o1) - }) - - it('should add basic accessors', () => { - const b1 = Basic.encode({ - num: 1, - payload: uint8ArrayFromString('lol') - }) - - const o1 = Basic.decode(b1) - - expect(o1).to.have.property('hasNum').that.is.a('function') - expect(o1.hasNum()).to.be.true() - - expect(o1).to.have.property('setNum').that.is.a('function') - o1.setNum(5) - - expect(o1).to.have.property('getNum').that.is.a('function') - expect(o1.getNum(5)).to.equal(5) - - expect(o1).to.have.property('clearNum').that.is.a('function') - o1.clearNum() - - expect(o1.getNum(5)).to.be.undefined() - - const methods = Object.keys(o1) - - expect(methods).to.not.include('getNum') - expect(methods).to.not.include('setNum') - expect(methods).to.not.include('hasNum') - expect(methods).to.not.include('clearNum') - }) - - it('should encode and decode floats in a basic object', () => { - const b1 = Basic.encode({ - num: 1.1, - payload: uint8ArrayFromString('lol') - }) - - const o1 = Basic.decode(b1) - - expect(o1).to.have.property('num', 1.1) - expect(o1).to.have.deep.property('payload', uint8ArrayFromString('lol')) - }) -}) diff --git a/test/booleans.spec.js b/test/booleans.spec.js deleted file mode 100644 index bf83de5..0000000 --- a/test/booleans.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const proto = require('./test.proto') -const Booleans = protobuf(proto).Booleans - -describe('booleans', () => { - it('should encode and decode booleans', () => { - const b1 = Booleans.encode({ - bool1: true, - bool2: false - }) - - const o1 = Booleans.decode(b1) - - expect(o1).to.deep.equal({ - bool1: true, - bool2: false - }) - }) - - it('should encode and decode optional booleans', () => { - const b1 = Booleans.encode({ - bool1: true - }) - const o1 = Booleans.decode(b1) - - expect(o1).to.deep.equal({ - bool1: true, - bool2: false - }) - expect(o1.hasBool2()).to.be.false() - }) -}) diff --git a/test/bytes.spec.js b/test/bytes.spec.js deleted file mode 100644 index b199111..0000000 --- a/test/bytes.spec.js +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const Bytes = protobuf(require('./test.proto')).Bytes - -describe('bytes', () => { - it('should encode and decode bytes', () => { - const b1 = Bytes.encode({ - req: Uint8Array.from([0, 1, 2, 3]), - opt: Uint8Array.from([4, 5, 6, 7]) - }) - - const o1 = Bytes.decode(b1) - - expect(o1).to.deep.equal({ - req: Uint8Array.from([0, 1, 2, 3]), - opt: Uint8Array.from([4, 5, 6, 7]) - }) - }) - - it('should encode and decode optional bytes', () => { - const b1 = Bytes.encode({ - req: Uint8Array.from([0, 1, 2, 3]) - }) - const o1 = Bytes.decode(b1) - - expect(o1).to.deep.equal({ - req: Uint8Array.from([0, 1, 2, 3]), - opt: null - }) - expect(o1.hasOpt()).to.be.false() - }) -}) diff --git a/test/corrupted.spec.js b/test/corrupted.spec.js deleted file mode 100644 index 025ff5d..0000000 --- a/test/corrupted.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') -const protobuf = require('../src') - -const protoStr = 'enum AbcType {\n' + - ' IGNORE = 0;\n' + - ' ACK_CONFIRMATION_TOKEN = 1;\n' + - '}\n' + - 'message AbcAcknowledgeConfirmationToken { // 0x01\n' + - ' optional uint64 confirmation_token = 1;\n' + - ' extensions 1000 to max;\n' + - '}\n' + - 'message ABC {\n' + - ' required AbcType type = 9;\n' + - ' required uint32 api_version = 8;\n' + - ' optional AbcAcknowledgeConfirmationToken ack_confirmation_token = 1;\n' + - ' extensions 1000 to max;\n' + - '}\n' + - 'message Open {\n' + - ' required bytes feed = 1;\n' + - ' required bytes nonce = 2;\n' + - '}' - -const messages = protobuf(protoStr) - -describe('corrupted', () => { - it('should fail to decode an invalid message', () => { - expect(() => { - messages.ABC.decode(Uint8Array.from([8, 182, 168, 235, 144, 178, 41])) - }).to.throw(/not valid/) - }) - - it('should fail to decode non-byte arrays', () => { - expect(() => { - messages.ABC.decode({}) - }).to.throw(/not valid/) - }) - - it('should fail to decode a base16 message', () => { - const buf = uint8ArrayFromString('cec1', 'base16') - - expect(() => { - messages.Open.decode(buf) - }).to.throw(/Could not decode varint/) - }) -}) diff --git a/test/custom-types.spec.js b/test/custom-types.spec.js deleted file mode 100644 index eab554b..0000000 --- a/test/custom-types.spec.js +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const CustomType = protobuf(require('./test.proto')).CustomType - -describe('custom types', () => { - it('should encode and decode custom types', () => { - var b1 = CustomType.encode({ - req: { - num: 5, - payload: Uint8Array.from([]) - }, - opt: { - num: 6, - payload: Uint8Array.from([]) - } - }) - - var o1 = CustomType.decode(b1) - - expect(o1).to.deep.equal({ - req: { - num: 5, - payload: Uint8Array.from([]) - }, - opt: { - num: 6, - payload: Uint8Array.from([]) - } - }) - }) - - it('should encode and decode custom types with optional fields', () => { - var b1 = CustomType.encode({ - req: { - num: 5, - payload: Uint8Array.from([]) - } - }) - - var o1 = CustomType.decode(b1) - - expect(o1).to.deep.equal({ - req: { - num: 5, - payload: Uint8Array.from([]) - }, - opt: null - }) - expect(o1.hasOpt()).to.be.false() - }) -}) diff --git a/test/defaults.spec.js b/test/defaults.spec.js deleted file mode 100644 index 08852b9..0000000 --- a/test/defaults.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const Defaults = protobuf(require('./test.proto')).Defaults - -describe('default', () => { - it('should decode with defaults', () => { - const o1 = Defaults.decode(new Uint8Array()) // everything default - - const b2 = Defaults.encode({ - num: 10, - foos: [1] - }) - - const b3 = Defaults.encode({ - num: 10, - foo2: 2 - }) - - expect(Defaults.decode(b3)).to.deep.equal({ - num: 10, - foo1: 2, - foo2: 2, - foos: [] - }) - - expect(o1).to.deep.equal({ - num: 42, - foo1: 2, - foo2: 1, - foos: [] - }) - - expect(Defaults.decode(b2)).to.deep.equal({ - num: 10, - foo1: 2, - foo2: 1, - foos: [1] - }) - }) -}) diff --git a/test/enums.spec.js b/test/enums.spec.js deleted file mode 100644 index 1da47df..0000000 --- a/test/enums.spec.js +++ /dev/null @@ -1,21 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const messages = protobuf(require('./test.proto')) - -describe('enums', () => { - it('should encode and decode enums', () => { - const e = messages.FOO - - expect(e).to.deep.equal({ A: 1, B: 2 }) - }) - - it('should encode and decode hex enums', () => { - const e = messages.FOO_HEX - - expect(e).to.deep.equal({ A: 1, B: 2 }) - }) -}) diff --git a/test/float.spec.js b/test/float.spec.js deleted file mode 100644 index a9eaa48..0000000 --- a/test/float.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const Float = protobuf(require('./test.proto')).Float - -describe('floats', () => { - it('should encode and decode floats', () => { - const arr = new Float32Array(3) - arr[0] = 1.1 - arr[1] = 0 - arr[2] = -2.3 - - const obj = { - float1: arr[0], - float2: arr[1], - float3: arr[2] - } - - const b1 = Float.encode(obj) - - const o1 = Float.decode(b1) - - expect(o1).to.deep.equal(obj) - }) -}) diff --git a/test/integers.spec.js b/test/integers.spec.js deleted file mode 100644 index 2b7bf69..0000000 --- a/test/integers.spec.js +++ /dev/null @@ -1,72 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const Integers = protobuf(require('./test.proto')).Integers - -describe('integers', () => { - it('should encode and decode integers', () => { - const b1 = Integers.encode({ - sint32: 1, - sint64: 2, - int32: 3, - uint32: 4, - int64: 5, - fixed32: 6 - }) - - const o1 = Integers.decode(b1) - - expect(o1).to.deep.equal({ - sint32: 1, - sint64: 2, - int32: 3, - uint32: 4, - int64: 5, - fixed32: 6 - }) - }) - - it('should encode and decode negative integers', () => { - const b1 = Integers.encode({ - sint32: -1, - sint64: -2, - int32: -3, - uint32: 0, - int64: -1 * Math.pow(2, 52) - 5, - fixed32: 0 - }) - - const o1 = Integers.decode(b1) - - expect(o1).to.deep.equal({ - sint32: -1, - sint64: -2, - int32: -3, - uint32: 0, - int64: -1 * Math.pow(2, 52) - 5, - fixed32: 0 - }) - }) - - it('should encode and decode optional integers', () => { - const b1 = Integers.encode({ - sint32: null - }) - const b2 = Integers.encode({ - sint32: 0 - }) - - // sint32 is optional, verify that setting it to null does not - // cause a value to be written into the encoded buffer - expect(b1.length).to.be.lessThan(b2.length) - - const o1 = Integers.decode(b1) - expect(o1.hasSint32()).to.be.false() - - const o2 = Integers.decode(b2) - expect(o2.sint32).to.equal(0) - }) -}) diff --git a/test/map.spec.js b/test/map.spec.js deleted file mode 100644 index e8de565..0000000 --- a/test/map.spec.js +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const Map = protobuf(require('./test.proto')).Map - -describe('maps', () => { - it('should encode and decode maps', () => { - const b1 = Map.encode({ - foo: { - hello: 'world' - } - }) - - const o1 = Map.decode(b1) - - expect(o1).to.have.deep.property('foo', { hello: 'world' }) - }) - - it('should encode and decode maps with multiple fields', () => { - const doc = { - foo: { - hello: 'world', - hi: 'verden' - } - } - - const b2 = Map.encode(doc) - const o2 = Map.decode(b2) - - expect(o2).to.deep.equal(doc) - }) -}) diff --git a/test/nan.spec.js b/test/nan.spec.js deleted file mode 100644 index 4e11c85..0000000 --- a/test/nan.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') - -const protoStr = 'message MyMessage {\n' + - ' optional uint32 my_number = 1;\n' + - ' required string my_other = 2;\n' + - '}' - -const messages = protobuf(protoStr) - -describe('NaN', () => { - it('should consider NaN as not defined', () => { - const testString = 'hello!' - const properResult = Uint8Array.from([0x12, 0x06, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x21]) - const encoded = messages.MyMessage.encode({ - my_number: NaN, - my_other: testString - }) - const decoded = messages.MyMessage.decode(encoded) - - expect(decoded).to.have.property('my_other', testString) - expect(encoded).to.deep.equal(properResult) - }) -}) diff --git a/test/nested.spec.js b/test/nested.spec.js deleted file mode 100644 index 30dcc08..0000000 --- a/test/nested.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -const Nested = protobuf(require('./test.proto')).Nested - -describe('nested', () => { - it('should encode nested objects', () => { - const b1 = Nested.encode({ - num: 1, - payload: uint8ArrayFromString('lol'), - meh: { - num: 2, - payload: uint8ArrayFromString('bar') - } - }) - - const b2 = Nested.encode({ - num: 1, - payload: uint8ArrayFromString('lol'), - meeeh: 42, - meh: { - num: 2, - payload: uint8ArrayFromString('bar') - } - }) - - expect(b2).to.deep.equal(b1) - }) - - it('should decode nested objects', () => { - const b1 = Nested.encode({ - num: 1, - payload: uint8ArrayFromString('lol'), - meh: { - num: 2, - payload: uint8ArrayFromString('bar') - } - }) - - const o1 = Nested.decode(b1) - - expect(o1).to.have.property('num', 1) - expect(o1).to.have.deep.property('payload', uint8ArrayFromString('lol')) - expect(o1).to.have.deep.property('meh') - - expect(o1).to.have.deep.property('meh', { - num: 2, - payload: uint8ArrayFromString('bar') - }) - - const b2 = Nested.encode({ - num: 1, - payload: uint8ArrayFromString('lol'), - meeeh: 42, - meh: { - num: 2, - payload: uint8ArrayFromString('bar') - } - }) - - const o2 = Nested.decode(b2) - - expect(o2).to.deep.equal(o1) - }) -}) diff --git a/test/notpacked.spec.js b/test/notpacked.spec.js deleted file mode 100644 index a11d690..0000000 --- a/test/notpacked.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const proto = require('./test.proto') -const NotPacked = protobuf(proto).NotPacked -const FalsePacked = protobuf(proto).FalsePacked - -describe('not packed', () => { - it('should encode NotPacked and decode FalsePacked', () => { - const b1 = NotPacked.encode({ - id: [9847136125], - value: 10000 - }) - - const o1 = FalsePacked.decode(b1) - - expect(o1).to.have.deep.property('id', [9847136125]) - }) - - it('should encode FalsePacked and decode NotPacked', () => { - const b1 = FalsePacked.encode({ - id: [9847136125], - value: 10000 - }) - - const o1 = NotPacked.decode(b1) - - expect(o1).to.have.deep.property('id', [9847136125]) - }) -}) diff --git a/test/oneof.spec.js b/test/oneof.spec.js deleted file mode 100644 index f0d4c42..0000000 --- a/test/oneof.spec.js +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const proto = protobuf(require('./test.proto')) -const Property = proto.Property -const PropertyNoOneof = proto.PropertyNoOneof - -const data = { - name: 'Foo', - desc: 'optional description', - int_value: 12345 -} - -describe('onof', () => { - it('should encode oneof', () => { - expect(Property.encode(data)).to.be.ok() - }) - - it('should encode and decode oneof', () => { - const buf = Property.encode(data) - const out = Property.decode(buf) - - expect(out).to.deep.equal(data) - }) - - it('should throw when encoding overloaded json', () => { - expect(() => { - Property.encode({ - name: 'Foo', - desc: 'optional description', - string_value: 'Bar', // ignored - bool_value: true, // ignored - int_value: 12345 // retained, was last entered - }) - }).to.throw(/only one of the properties defined in oneof value can be set/) - }) - - it('should encode and decode overloaded oneof buffer', () => { - const invalidData = { - name: 'Foo', - desc: 'optional description', - string_value: 'Bar', // retained, has highest tag number - bool_value: true, // ignored - int_value: 12345 // ignored - } - const validData = { - name: 'Foo', - desc: 'optional description', - string_value: 'Bar' - } - - const buf = PropertyNoOneof.encode(invalidData) - const out = Property.decode(buf) - expect(validData).to.deep.equal(out) - }) -}) diff --git a/test/optional.spec.js b/test/optional.spec.js deleted file mode 100644 index adc4461..0000000 --- a/test/optional.spec.js +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const proto = require('./test.proto') -const Optional = protobuf(proto).Optional - -describe('optional', () => { - it('should encode and decode zero value for optional value', () => { - const o1 = {} - const b1 = Optional.encode(o1) - const o2 = Optional.decode(b1) - - expect(o1).to.not.have.property('value') - expect(o2).to.have.property('value', 0) - }) - - it('should create accessors for optional values', () => { - const o1 = Optional.decode(Optional.encode({})) - - expect(o1).to.have.property('hasValue').that.is.a('function') - expect(o1.hasValue()).to.be.false() - - expect(o1).to.have.property('setValue').that.is.a('function') - o1.setValue(5) - expect(o1.hasValue()).to.be.true() - - expect(o1).to.have.property('getValue').that.is.a('function') - expect(o1.getValue()).to.equal(5) - - expect(o1).to.have.property('clearValue').that.is.a('function') - o1.clearValue() - - expect(o1.hasValue()).to.be.false() - expect(o1.getValue()).to.be.undefined() - }) - - it('should have non-enumerable accessors for optional values', () => { - const o1 = Optional.decode(Optional.encode({})) - const methods = Object.keys(o1) - - expect(methods).to.not.include('getValue') - expect(methods).to.not.include('setValue') - expect(methods).to.not.include('hasValue') - expect(methods).to.not.include('clearValue') - }) - - it('should return zero values from optional accessors when no value has been set', () => { - const o1 = Optional.decode(Optional.encode({})) - - expect(o1.hasValue()).to.be.false() - o1.setValue(0) - expect(o1.hasValue()).to.be.true() - - expect(o1.getValue).to.be.a('function') - expect(o1.getValue()).to.equal(0) - - const o2 = Optional.decode(Optional.encode(o1)) - - expect(o2.hasValue()).to.be.ok() - expect(o2.getValue()).to.equal(0) - }) -}) diff --git a/test/packed.spec.js b/test/packed.spec.js deleted file mode 100644 index ae7eaa2..0000000 --- a/test/packed.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const Packed = protobuf(require('./test.proto')).Packed - -describe('packed', () => { - it('should encode packed fields', () => { - const b1 = Packed.encode({ - packed: [ - 12, - 13, - 14 - ] - }) - - const b2 = Packed.encode({ - packed: [ - 12, - 13, - 14 - ], - meeh: 42 - }) - - expect(b2).to.deep.equal(b1) - }) - - it('should decode packed fields', () => { - const b1 = Packed.encode({ - packed: [ - 12, - 13, - 14 - ] - }) - - const o1 = Packed.decode(b1) - - expect(o1).to.have.deep.property('packed', [ - 12, - 13, - 14 - ]) - - const b2 = Packed.encode({ - packed: [ - 12, - 13, - 14 - ], - meeh: 42 - }) - - const o2 = Packed.decode(b2) - - expect(o2).to.deep.equal(o1) - }) -}) diff --git a/test/repeated.spec.js b/test/repeated.spec.js deleted file mode 100644 index aec2784..0000000 --- a/test/repeated.spec.js +++ /dev/null @@ -1,74 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const Repeated = protobuf(require('./test.proto')).Repeated -const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') - -describe('repeated', () => { - it('should encode repeated fields', () => { - const b1 = Repeated.encode({ - list: [{ - num: 1, - payload: uint8ArrayFromString('lol') - }, { - num: 2, - payload: uint8ArrayFromString('lol1') - }] - }) - - const b2 = Repeated.encode({ - list: [{ - num: 1, - payload: uint8ArrayFromString('lol') - }, { - num: 2, - payload: uint8ArrayFromString('lol1'), - meeeeh: 100 - }], - meeh: 42 - }) - - expect(b2).to.deep.equal(b1) - }) - - it('should decode repeated fields', () => { - const b1 = Repeated.encode({ - list: [{ - num: 1, - payload: uint8ArrayFromString('lol') - }, { - num: 2, - payload: uint8ArrayFromString('lol1') - }] - }) - - const o1 = Repeated.decode(b1) - - expect(o1).to.have.deep.nested.property('list', [{ - num: 1, - payload: uint8ArrayFromString('lol') - }, { - num: 2, - payload: uint8ArrayFromString('lol1') - }]) - - const b2 = Repeated.encode({ - list: [{ - num: 1, - payload: uint8ArrayFromString('lol') - }, { - num: 2, - payload: uint8ArrayFromString('lol1'), - meeeeh: 100 - }], - meeh: 42 - }) - - const o2 = Repeated.decode(b2) - - expect(o2).to.deep.equal(o1) - }) -}) diff --git a/test/strings.spec.js b/test/strings.spec.js deleted file mode 100644 index 972317a..0000000 --- a/test/strings.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const Strings = protobuf(require('./test.proto')).Strings - -describe('strings', () => { - it('should encode and decode strings', () => { - const b1 = Strings.encode({ - name: 'hello', - desc: 'world' - }) - - const o1 = Strings.decode(b1) - - expect(o1).to.deep.equal({ - name: 'hello', - desc: 'world' - }) - }) - - it('should encode and decode optional strings', () => { - const b1 = Strings.encode({ - name: 'hello' - }) - - const o1 = Strings.decode(b1) - - expect(o1).to.have.property('name', 'hello') - expect(o1.hasDesc()).to.be.false() - }) - - it('should encode and decode empty strings', () => { - const b1 = Strings.encode({ - name: '' - }) - - const o1 = Strings.decode(b1) - - expect(o1).to.have.property('name', '') - expect(o1.hasDesc()).to.be.false() - }) -}) diff --git a/test/test.proto.js b/test/test.proto.js deleted file mode 100644 index af59cec..0000000 --- a/test/test.proto.js +++ /dev/null @@ -1,134 +0,0 @@ -'use strict' -module.exports = `message Basic { - required double num = 1; - required bytes payload = 2; -} - -message Map { - map foo = 1; -} - -message UTF8 { - required string foo = 1; - required uint32 bar = 2; -} - -message Nested { - required double num = 1; - required bytes payload = 2; - required Basic meh = 3; -} - -message Repeated { - repeated Basic list = 1; -} - -message Optional { - optional int32 value = 1; -} - -message Integers { - optional sint32 sint32 = 1; - optional sint64 sint64 = 2; - optional int32 int32 = 3; - optional uint32 uint32 = 4; - optional int64 int64 = 5; - optional fixed32 fixed32 = 6; -} - -message Float { - optional float float1 = 1; - optional float float2 = 2; - optional float float3 = 3; -} - -message Strings { - required string name = 1; - optional string desc = 2; -} - -message Booleans { - required bool bool1 = 1; - optional bool bool2 = 2; -} - -message Bytes { - required bytes req = 1; - optional bytes opt = 2; -} - -message CustomType { - required Basic req = 1; - optional Basic opt = 2; -} - -message Packed { - repeated Basic list = 1; - repeated int32 packed = 2 [packed=true]; -} - -message NotPacked { - repeated int64 id = 2; - optional int64 value = 5; -} -message FalsePacked { - repeated int64 id = 2 [packed=false]; - optional int64 value = 5; -} - -enum FOO { - A = 1; - B = 2; -} - -message Defaults { - optional int32 num = 1 [default = 42]; - optional FOO foo1 = 2 [default = B]; - optional FOO foo2 = 3; - repeated FOO foos = 4; -} - -enum FOO_HEX { - A = 0x01; - B = 0x02; -} - -message Property { - required string name = 1; - optional string desc = 2; - - oneof value { - bool bool_value = 3; - float float_value = 4; - int32 int_value = 5; - string string_value = 6; - } -} - -message PropertyNoOneof { - required string name = 1; - optional string desc = 2; - - optional bool bool_value = 3; - optional float float_value = 4; - optional int32 int_value = 5; - optional string string_value = 6; -} - -message ComplexProperty { - required string name = 1; - - oneof value { - bool bool_value = 3; - float float_value = 4; - int32 int_value = 5; - string string_value = 6; - } - - oneof another_value { - bool bool_value = 7; - float float_value = 8; - int32 int_value = 9; - string string_value = 10; - } -}` diff --git a/test/utf-8.spec.js b/test/utf-8.spec.js deleted file mode 100644 index de28354..0000000 --- a/test/utf-8.spec.js +++ /dev/null @@ -1,21 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const { expect } = require('aegir/utils/chai') -const protobuf = require('../src') -const UTF8 = protobuf(require('./test.proto')).UTF8 - -describe('utf-8', () => { - it('should encode multi-byte strings', () => { - const ex = { - foo: 'ビッグデータ「人間の解釈が必要」「量の問題ではない」論と、もう一つのビッグデータ「人間の解釈が必要」「量の問題ではない」論と、もう一つの', - bar: 42 - } - - const b1 = UTF8.encode(ex) - const b2 = UTF8.decode(b1) - - expect(b2).to.deep.equal(ex) - }) -})