From f8bfe37f2a991015b64c48b58538e3d43d7020a7 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Fri, 27 Oct 2023 15:05:10 +0700 Subject: [PATCH 1/9] feat: protons 7.2.1 protons-runtime 5.1.0 --- .gitignore | 3 +- package-lock.json | 260 ++++- package.json | 6 +- src/index.ts | 83 +- src/message-cache.ts | 8 +- src/message/decodeRpc.ts | 291 +++-- src/message/rpc.cjs | 1878 ------------------------------- src/message/rpc.d.ts | 666 ----------- src/message/rpc.js | 3 - src/message/rpc.proto | 4 +- src/message/rpc.ts | 715 ++++++++++++ src/metrics.ts | 6 +- src/types.ts | 2 +- src/utils/buildRawMessage.ts | 12 +- src/utils/create-gossip-rpc.ts | 32 + test/benchmark/protobuf.test.ts | 15 +- test/decodeRpc.spec.ts | 60 +- test/e2e/go-gossipsub.spec.ts | 14 +- test/message-cache.spec.ts | 4 +- test/utils/index.ts | 4 + test/utils/msgId.ts | 6 +- tsconfig.json | 2 - 22 files changed, 1262 insertions(+), 2812 deletions(-) delete mode 100644 src/message/rpc.cjs delete mode 100644 src/message/rpc.d.ts delete mode 100644 src/message/rpc.js create mode 100644 src/message/rpc.ts create mode 100644 src/utils/create-gossip-rpc.ts diff --git a/.gitignore b/.gitignore index bfc994ef..f1016601 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ dist/ docs/ .nyc_output/coverage-final.json .vscode/settings.json -benchmark_data/ \ No newline at end of file +benchmark_data/ +.vscode/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4e8e37f9..d0fa94be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3917,6 +3917,22 @@ "@types/node": "*" } }, + "node_modules/@types/linkify-it": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.4.tgz", + "integrity": "sha512-hPpIeeHb/2UuCw06kSNAOVWgehBLXEo0/fUs0mw3W2qhqX89PI2yvok83MnuctYGCPrabGIoi0fFso4DQ+sNUQ==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, "node_modules/@types/mdast": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", @@ -3926,6 +3942,12 @@ "@types/unist": "*" } }, + "node_modules/@types/mdurl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.4.tgz", + "integrity": "sha512-ARVxjAEX5TARFRzpDRVC6cEk0hUIXCCwaMhz8y7S1/PxU6zZS1UMjyobz7q4w/D/R552r4++EhwmXK1N2rAy0A==", + "dev": true + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -5702,6 +5724,18 @@ "cdl": "bin/cdl.js" } }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -7728,6 +7762,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-ci": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.0.0.tgz", @@ -7935,6 +7978,79 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/eslint": { "version": "8.56.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", @@ -11217,6 +11333,63 @@ "node": ">=12.0.0" } }, + "node_modules/jsdoc/node_modules/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsdoc/node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/jsdoc/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsdoc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -11356,6 +11529,15 @@ "node": ">=0.10.0" } }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, "node_modules/klaw-sync": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", @@ -11402,6 +11584,15 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/listr": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", @@ -11860,6 +12051,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/markdown-table": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", @@ -20129,6 +20352,15 @@ "node": ">=0.10.5" } }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -22470,12 +22702,17 @@ "node": ">=8" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, - "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -22523,6 +22760,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, "node_modules/undici": { "version": "5.28.2", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", @@ -22978,6 +23221,15 @@ "lodash": "^4.17.14" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -23166,6 +23418,12 @@ "node": ">=4.0" } }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 1be67b90..c6e3f7c1 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "scripts": { "lint": "aegir lint", "release": "aegir release --no-types", - "copy": "mkdirp dist/src/message && cp src/message/*.* dist/src/message", - "build": "npm run copy && aegir build", + "build": "aegir build", + "generate": "protons ./src/message/rpc.proto", "prepare": "npm run build", "pretest": "npm run build", "pretest:e2e": "npm run build", @@ -84,6 +84,7 @@ "it-pushable": "^3.2.3", "multiformats": "^13.0.1", "protobufjs": "^7.2.6", + "protons-runtime": "^5.1.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.1" }, @@ -105,6 +106,7 @@ "p-retry": "^6.2.0", "p-wait-for": "^5.0.2", "sinon": "^17.0.1", + "protons": "^7.2.1", "time-cache": "^0.3.0", "ts-sinon": "^2.0.2" }, diff --git a/src/index.ts b/src/index.ts index f3278b32..f291a6a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,7 @@ import { BACKOFF_SLACK } from './constants.js' import { decodeRpc, type DecodeRPCLimits, defaultDecodeRpcLimits } from './message/decodeRpc.js' -import { RPC, type IRPC } from './message/rpc.js' +import { RPC } from './message/rpc.js' import { MessageCache, type MessageCacheRecord } from './message-cache.js' import { ChurnReason, @@ -303,13 +303,13 @@ export class GossipSub extends TypedEventEmitter implements Pub * Map of pending messages to gossip * peer id => control messages */ - public readonly gossip = new Map() + public readonly gossip = new Map() /** * Map of control messages * peer id => control message */ - public readonly control = new Map() + public readonly control = new Map() /** * Number of IHAVEs received from peer in the last heartbeat @@ -999,7 +999,7 @@ export class GossipSub extends TypedEventEmitter implements Pub /** * Handles an rpc request from a peer */ - public async handleReceivedRpc (from: PeerId, rpc: IRPC): Promise { + public async handleReceivedRpc (from: PeerId, rpc: RPC): Promise { // Check if peer is graylisted in which case we ignore the event if (!this.acceptFrom(from.toString())) { this.log('received message from unacceptable peer %p', from) @@ -1061,16 +1061,15 @@ export class GossipSub extends TypedEventEmitter implements Pub continue } - const handleReceivedMessagePromise = this.handleReceivedMessage(from, message) - // Should never throw, but handle just in case - .catch((err) => { - this.metrics?.onMsgRecvError(message.topic) - this.log(err) - }) + const handleReceivedMessagePromise = this.handleReceivedMessage(from, message) + // Should never throw, but handle just in case + .catch((err) => { + this.metrics?.onMsgRecvError(message.topic) + this.log(err) + }) - if (this.opts.awaitRpcMessageHandler) { - await handleReceivedMessagePromise - } + if (this.opts.awaitRpcMessageHandler) { + await handleReceivedMessagePromise } } @@ -1107,7 +1106,7 @@ export class GossipSub extends TypedEventEmitter implements Pub * Handles a newly received message from an RPC. * May forward to all peers in the mesh. */ - private async handleReceivedMessage (from: PeerId, rpcMsg: RPC.IMessage): Promise { + private async handleReceivedMessage (from: PeerId, rpcMsg: RPC.Message): Promise { this.metrics?.onMsgRecvPreValidation(rpcMsg.topic) const validationResult = await this.validateReceivedMessage(from, rpcMsg) @@ -1190,7 +1189,7 @@ export class GossipSub extends TypedEventEmitter implements Pub */ private async validateReceivedMessage ( propagationSource: PeerId, - rpcMsg: RPC.IMessage + rpcMsg: RPC.Message ): Promise { // Fast message ID stuff const fastMsgIdStr = this.fastMsgIdFn?.(rpcMsg) @@ -1280,14 +1279,15 @@ export class GossipSub extends TypedEventEmitter implements Pub */ private sendSubscriptions (toPeer: PeerIdStr, topics: string[], subscribe: boolean): void { this.sendRpc(toPeer, { - subscriptions: topics.map((topic) => ({ topic, subscribe })) + subscriptions: topics.map((topic) => ({ topic, subscribe })), + messages: [] }) } /** * Handles an rpc control message from a peer */ - private async handleControlMessage (id: PeerIdStr, controlMsg: RPC.IControlMessage): Promise { + private async handleControlMessage (id: PeerIdStr, controlMsg: RPC.ControlMessage): Promise { if (controlMsg === undefined) { return } @@ -1301,7 +1301,7 @@ export class GossipSub extends TypedEventEmitter implements Pub return } - const sent = this.sendRpc(id, { messages: ihave, control: { iwant, prune } }) + const sent = this.sendRpc(id, createGossipRpc(ihave, { iwant, prune })) const iwantMessageIds = iwant[0]?.messageIDs if (iwantMessageIds != null) { if (sent) { @@ -1346,7 +1346,7 @@ export class GossipSub extends TypedEventEmitter implements Pub /** * Handles IHAVE messages */ - private handleIHave (id: PeerIdStr, ihave: RPC.IControlIHave[]): RPC.IControlIWant[] { + private handleIHave (id: PeerIdStr, ihave: RPC.ControlIHave[]): RPC.ControlIWant[] { if (ihave.length === 0) { return [] } @@ -1432,7 +1432,7 @@ export class GossipSub extends TypedEventEmitter implements Pub * Handles IWANT messages * Returns messages to send back to peer */ - private handleIWant (id: PeerIdStr, iwant: RPC.IControlIWant[]): RPC.IMessage[] { + private handleIWant (id: PeerIdStr, iwant: RPC.ControlIWant[]): RPC.Message[] { if (iwant.length === 0) { return [] } @@ -1444,7 +1444,7 @@ export class GossipSub extends TypedEventEmitter implements Pub return [] } - const ihave = new Map() + const ihave = new Map() const iwantByTopic = new Map() let iwantDonthave = 0 @@ -1483,7 +1483,7 @@ export class GossipSub extends TypedEventEmitter implements Pub /** * Handles Graft messages */ - private async handleGraft (id: PeerIdStr, graft: RPC.IControlGraft[]): Promise { + private async handleGraft (id: PeerIdStr, graft: RPC.ControlGraft[]): Promise { const prune: TopicStr[] = [] const score = this.score.score(id) const now = Date.now() @@ -1576,7 +1576,7 @@ export class GossipSub extends TypedEventEmitter implements Pub /** * Handles Prune messages */ - private async handlePrune (id: PeerIdStr, prune: RPC.IControlPrune[]): Promise { + private async handlePrune (id: PeerIdStr, prune: RPC.ControlPrune[]): Promise { const score = this.score.score(id) for (const { topicID, backoff, peers } of prune) { @@ -1697,7 +1697,7 @@ export class GossipSub extends TypedEventEmitter implements Pub /** * Maybe attempt connection given signed peer records */ - private async pxConnect (peers: RPC.IPeerInfo[]): Promise { + private async pxConnect (peers: RPC.PeerInfo[]): Promise { if (peers.length > this.opts.prunePeers) { shuffle(peers) peers = peers.slice(0, this.opts.prunePeers) @@ -2035,7 +2035,7 @@ export class GossipSub extends TypedEventEmitter implements Pub */ private forwardMessage ( msgIdStr: string, - rawMsg: RPC.IMessage, + rawMsg: RPC.Message, propagationSource?: PeerIdStr, excludePeers?: Set ): void { @@ -2051,7 +2051,7 @@ export class GossipSub extends TypedEventEmitter implements Pub // forward the message to peers tosend.forEach((id) => { // sendRpc may mutate RPC message on piggyback, create a new message for each peer - this.sendRpc(id, { messages: [rawMsg] }) + this.sendRpc(id, createGossipRpc([rawMsg])) }) this.metrics?.onForwardMsg(rawMsg.topic, tosend.size) @@ -2253,8 +2253,8 @@ export class GossipSub extends TypedEventEmitter implements Pub topicID: topic } ] - - this.sendRpc(id, { control: { graft } }) + const out = createGossipRpc([], { graft }) + this.sendRpc(id, out) } /** @@ -2264,14 +2264,14 @@ export class GossipSub extends TypedEventEmitter implements Pub // this is only called from leave() function const onUnsubscribe = true const prune = [await this.makePrune(id, topic, this.opts.doPX, onUnsubscribe)] - - this.sendRpc(id, { control: { prune } }) + const out = createGossipRpc([], { prune }) + this.sendRpc(id, out) } /** * Send an rpc object to a peer */ - private sendRpc (id: PeerIdStr, rpc: IRPC): boolean { + private sendRpc (id: PeerIdStr, rpc: RPC): boolean { const outboundStream = this.streamsOutbound.get(id) if (outboundStream == null) { this.log(`Cannot send RPC to ${id} as there is no open stream to it available`) @@ -2292,7 +2292,7 @@ export class GossipSub extends TypedEventEmitter implements Pub this.gossip.delete(id) } - const rpcBytes = RPC.encode(rpc).finish() + const rpcBytes = RPC.encode(rpc) try { outboundStream.push(rpcBytes) } catch (e) { @@ -2315,7 +2315,7 @@ export class GossipSub extends TypedEventEmitter implements Pub } /** Mutates `outRpc` adding graft and prune control messages */ - public piggybackControl (id: PeerIdStr, outRpc: IRPC, ctrl: RPC.IControlMessage): void { + public piggybackControl (id: PeerIdStr, outRpc: RPC, ctrl: RPC.ControlMessage): void { if (ctrl.graft != null) { if (outRpc.control == null) outRpc.control = {} if (outRpc.control.graft == null) outRpc.control.graft = [] @@ -2338,7 +2338,7 @@ export class GossipSub extends TypedEventEmitter implements Pub } /** Mutates `outRpc` adding ihave control messages */ - private piggybackGossip (id: PeerIdStr, outRpc: IRPC, ihave: RPC.IControlIHave[]): void { + private piggybackGossip (id: PeerIdStr, outRpc: RPC, ihave: RPC.ControlIHave[]): void { if (outRpc.control == null) outRpc.control = {} outRpc.control.ihave = ihave } @@ -2358,7 +2358,7 @@ export class GossipSub extends TypedEventEmitter implements Pub const onUnsubscribe = false for (const [id, topics] of tograft) { const graft = topics.map((topicID) => ({ topicID })) - let prune: RPC.IControlPrune[] = [] + let prune: RPC.ControlPrune[] = [] // If a peer also has prunes, process them now const pruning = toprune.get(id) if (pruning != null) { @@ -2370,7 +2370,7 @@ export class GossipSub extends TypedEventEmitter implements Pub toprune.delete(id) } - this.sendRpc(id, { control: { graft, prune } }) + this.sendRpc(id, createGossipRpc([], { graft, prune })) } for (const [id, topics] of toprune) { const prune = await Promise.all( @@ -2378,7 +2378,7 @@ export class GossipSub extends TypedEventEmitter implements Pub async (topicID) => this.makePrune(id, topicID, doPX && !(noPX.get(id) ?? false), onUnsubscribe) ) ) - this.sendRpc(id, { control: { prune } }) + this.sendRpc(id, createGossipRpc([], { prune })) } } @@ -2452,19 +2452,20 @@ export class GossipSub extends TypedEventEmitter implements Pub // send gossip first, which will also piggyback control for (const [peer, ihave] of this.gossip.entries()) { this.gossip.delete(peer) - this.sendRpc(peer, { control: { ihave } }) + this.sendRpc(peer, createGossipRpc([], { ihave })) } // send the remaining control messages for (const [peer, control] of this.control.entries()) { this.control.delete(peer) - this.sendRpc(peer, { control: { graft: control.graft, prune: control.prune } }) + const out = createGossipRpc([], { graft: control.graft, prune: control.prune }) + this.sendRpc(peer, out) } } /** * Adds new IHAVE messages to pending gossip */ - private pushGossip (id: PeerIdStr, controlIHaveMsgs: RPC.IControlIHave): void { + private pushGossip (id: PeerIdStr, controlIHaveMsgs: RPC.ControlIHave): void { this.log('Add gossip to %s', id) const gossip = this.gossip.get(id) ?? [] this.gossip.set(id, gossip.concat(controlIHaveMsgs)) @@ -2478,7 +2479,7 @@ export class GossipSub extends TypedEventEmitter implements Pub topic: string, doPX: boolean, onUnsubscribe: boolean - ): Promise { + ): Promise { this.score.prune(id, topic) if (this.streamsOutbound.get(id)?.protocol === constants.GossipsubIDv10) { // Gossipsub v1.0 -- no backoff, the peer won't be able to parse it anyway diff --git a/src/message-cache.ts b/src/message-cache.ts index ca39fb44..a382373e 100644 --- a/src/message-cache.ts +++ b/src/message-cache.ts @@ -8,7 +8,7 @@ export type CacheEntry = MessageId & { export type MessageCacheRecord = Pick interface MessageCacheEntry { - message: RPC.IMessage + message: RPC.Message /** * Tracks if the message has been validated by the app layer and thus forwarded */ @@ -60,7 +60,7 @@ export class MessageCache { * Adds a message to the current window and the cache * Returns true if the message is not known and is inserted in the cache */ - put (messageId: MessageId, msg: RPC.IMessage, validated = false): boolean { + put (messageId: MessageId, msg: RPC.Message, validated = false): boolean { const { msgIdStr } = messageId // Don't add duplicate entries to the cache. if (this.msgs.has(msgIdStr)) { @@ -99,7 +99,7 @@ export class MessageCache { /** * Retrieves a message from the cache by its ID, if it is still present */ - get (msgId: Uint8Array): RPC.IMessage | undefined { + get (msgId: Uint8Array): RPC.Message | undefined { return this.msgs.get(this.msgIdToStrFn(msgId))?.message } @@ -107,7 +107,7 @@ export class MessageCache { * Increases the iwant count for the given message by one and returns the message together * with the iwant if the message exists. */ - getWithIWantCount (msgIdStr: string, p: string): { msg: RPC.IMessage, count: number } | null { + getWithIWantCount (msgIdStr: string, p: string): { msg: RPC.Message, count: number } | null { const msg = this.msgs.get(msgIdStr) if (msg == null) { return null diff --git a/src/message/decodeRpc.ts b/src/message/decodeRpc.ts index 1c396a10..4959a895 100644 --- a/src/message/decodeRpc.ts +++ b/src/message/decodeRpc.ts @@ -1,5 +1,5 @@ -import protobuf from 'protobufjs/minimal.js' -import type { IRPC, RPC } from './rpc.js' +import { RPC } from './rpc.js' +import { reader as r, type Reader } from 'protons-runtime' export interface DecodeRPCLimits { maxSubscriptions: number @@ -20,236 +20,205 @@ export const defaultDecodeRpcLimits: DecodeRPCLimits = { } /** - * Copied code from src/message/rpc.cjs but with decode limits to prevent OOM attacks + * Copied code from src/message/rpc.ts but with decode limits to prevent OOM attacks */ -export function decodeRpc (bytes: Uint8Array, opts: DecodeRPCLimits): IRPC { +export function decodeRpc (bytes: Uint8Array, opts: DecodeRPCLimits): RPC { // Mutate to use the option as stateful counter. Must limit the total count of messageIDs across all IWANT, IHAVE // else one count put 100 messageIDs into each 100 IWANT and "get around" the limit opts = { ...opts } - const r = protobuf.Reader.create(bytes) - const l = bytes.length + const reader = r(bytes) + const obj: any = { + subscriptions: [], + messages: [] + } + + const end = reader.len - const c = l === undefined ? r.len : r.pos + l - const m: IRPC = {} - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { case 1: - if (!((m.subscriptions != null) && (m.subscriptions.length > 0))) m.subscriptions = [] - if (m.subscriptions.length < opts.maxSubscriptions) m.subscriptions.push(decodeSubOpts(r, r.uint32())) - else r.skipType(t & 7) + if (obj.subscriptions.length < opts.maxSubscriptions) { + obj.subscriptions.push(RPC.SubOpts.codec().decode(reader, reader.uint32())) + } else { + reader.skipType(tag & 7) + } break case 2: - if (!((m.messages != null) && (m.messages.length > 0))) m.messages = [] - if (m.messages.length < opts.maxMessages) m.messages.push(decodeMessage(r, r.uint32())) - else r.skipType(t & 7) + if (obj.messages.length < opts.maxMessages) { + obj.messages.push(RPC.Message.codec().decode(reader, reader.uint32())) + } else { + reader.skipType(tag & 7) + } break case 3: - m.control = decodeControlMessage(r, r.uint32(), opts) + obj.control = decodeControlMessage(reader, reader.uint32(), opts) break default: - r.skipType(t & 7) + reader.skipType(tag & 7) break } } - return m + return obj } -function decodeSubOpts (r: protobuf.Reader, l: number): RPC.ISubOpts { - const c = l === undefined ? r.len : r.pos + l - const m: RPC.ISubOpts = {} - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - m.subscribe = r.bool() - break - case 2: - m.topic = r.string() - break - default: - r.skipType(t & 7) - break - } +function decodeControlMessage(reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlMessage { + const obj: any = { + ihave: [], + iwant: [], + graft: [], + prune: [] } - return m -} -function decodeMessage (r: protobuf.Reader, l: number): RPC.IMessage { - const c = l === undefined ? r.len : r.pos + l - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const m = {} as RPC.IMessage - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { case 1: - m.from = r.bytes() + if (obj.ihave.length < opts.maxControlMessages) { + obj.ihave.push(decodeControlIHave(reader, reader.uint32(), opts)) + } else { + reader.skipType(tag & 7) + } break case 2: - m.data = r.bytes() + if (obj.iwant.length < opts.maxControlMessages) { + obj.iwant.push(decodeControlIWant(reader, reader.uint32(), opts)) + } else { + reader.skipType(tag & 7) + } break case 3: - m.seqno = r.bytes() + if (obj.graft.length < opts.maxControlMessages) { + obj.graft.push(decodeControlGraft(reader, reader.uint32())) + } else { + reader.skipType(tag & 7) + } break case 4: - m.topic = r.string() - break - case 5: - m.signature = r.bytes() - break - case 6: - m.key = r.bytes() + if (obj.prune.length < opts.maxControlMessages) { + obj.prune.push(decodeControlPrune(reader, reader.uint32(), opts)) + } else { + reader.skipType(tag & 7) + } break default: - r.skipType(t & 7) + reader.skipType(tag & 7) break } } - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (!m.topic) throw Error("missing required 'topic'") - return m + + return obj } -function decodeControlMessage (r: protobuf.Reader, l: number, opts: DecodeRPCLimits): RPC.IControlMessage { - const c = l === undefined ? r.len : r.pos + l - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const m = {} as RPC.IControlMessage - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - if (!((m.ihave != null) && (m.ihave.length > 0))) m.ihave = [] - if (m.ihave.length < opts.maxControlMessages) m.ihave.push(decodeControlIHave(r, r.uint32(), opts)) - else r.skipType(t & 7) - break - case 2: - if (!((m.iwant != null) && (m.iwant.length > 0))) m.iwant = [] - if (m.iwant.length < opts.maxControlMessages) m.iwant.push(decodeControlIWant(r, r.uint32(), opts)) - else r.skipType(t & 7) - break - case 3: - if (!((m.graft != null) && (m.graft.length > 0))) m.graft = [] - if (m.graft.length < opts.maxControlMessages) m.graft.push(decodeControlGraft(r, r.uint32())) - else r.skipType(t & 7) - break - case 4: - if (!((m.prune != null) && (m.prune.length > 0))) m.prune = [] - if (m.prune.length < opts.maxControlMessages) m.prune.push(decodeControlPrune(r, r.uint32(), opts)) - else r.skipType(t & 7) - break - default: - r.skipType(t & 7) - break - } +function decodeControlIHave(reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlIHave { + const obj: any = { + messageIDs: [] } - return m -} -function decodeControlIHave (r: protobuf.Reader, l: number, opts: DecodeRPCLimits): RPC.IControlIHave { - const c = l === undefined ? r.len : r.pos + l - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const m = {} as RPC.IControlIHave - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { case 1: - m.topicID = r.string() + obj.topicID = reader.string() break case 2: - if (!((m.messageIDs != null) && (m.messageIDs.length > 0))) m.messageIDs = [] - if (opts.maxIhaveMessageIDs-- > 0) m.messageIDs.push(r.bytes()) - else r.skipType(t & 7) + if (opts.maxIhaveMessageIDs-- > 0) obj.messageIDs.push(reader.bytes()) + else reader.skipType(tag & 7) break default: - r.skipType(t & 7) + reader.skipType(tag & 7) break } } - return m + + return obj } -function decodeControlIWant (r: protobuf.Reader, l: number, opts: DecodeRPCLimits): RPC.IControlIWant { - const c = l === undefined ? r.len : r.pos + l - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const m = {} as RPC.IControlIWant - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { +function decodeControlIWant(reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlIWant { + const obj: any = { + messageIDs: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { case 1: - if (!((m.messageIDs != null) && (m.messageIDs.length > 0))) m.messageIDs = [] - if (opts.maxIwantMessageIDs-- > 0) m.messageIDs.push(r.bytes()) - else r.skipType(t & 7) + if (opts.maxIwantMessageIDs-- > 0) { + obj.messageIDs.push(reader.bytes()) + } else { + reader.skipType(tag & 7) + } break default: - r.skipType(t & 7) + reader.skipType(tag & 7) break } } - return m + + return obj } -function decodeControlGraft (r: protobuf.Reader, l: number): RPC.IControlGraft { - const c = l === undefined ? r.len : r.pos + l - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const m = {} as RPC.IControlGraft - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { +function decodeControlGraft(reader: Reader, length: number) { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { case 1: - m.topicID = r.string() + obj.topicID = reader.string() break default: - r.skipType(t & 7) + reader.skipType(tag & 7) break } } - return m + + return obj } -function decodeControlPrune (r: protobuf.Reader, l: number, opts: DecodeRPCLimits): RPC.IControlPrune { - const c = l === undefined ? r.len : r.pos + l - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const m = {} as RPC.IControlPrune - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { +function decodeControlPrune(reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlPrune { + const obj: any = { + peers: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { case 1: - m.topicID = r.string() + obj.topicID = reader.string() break case 2: - if (!((m.peers != null) && (m.peers.length > 0))) m.peers = [] - if (opts.maxPeerInfos-- > 0) m.peers.push(decodePeerInfo(r, r.uint32())) - else r.skipType(t & 7) + if (opts.maxPeerInfos-- > 0) { + obj.peers.push(RPC.PeerInfo.codec().decode(reader, reader.uint32())) + } else { + reader.skipType(tag & 7) + } break case 3: - m.backoff = r.uint64() as unknown as number + obj.backoff = reader.uint64() break default: - r.skipType(t & 7) + reader.skipType(tag & 7) break } } - return m -} -function decodePeerInfo (r: protobuf.Reader, l: number): RPC.IPeerInfo { - const c = l === undefined ? r.len : r.pos + l - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const m = {} as RPC.IPeerInfo - while (r.pos < c) { - const t = r.uint32() - switch (t >>> 3) { - case 1: - m.peerID = r.bytes() - break - case 2: - m.signedPeerRecord = r.bytes() - break - default: - r.skipType(t & 7) - break - } - } - return m + return obj } diff --git a/src/message/rpc.cjs b/src/message/rpc.cjs deleted file mode 100644 index 4f5f4ce6..00000000 --- a/src/message/rpc.cjs +++ /dev/null @@ -1,1878 +0,0 @@ -// @ts-nocheck -/*eslint-disable*/ -(function(global, factory) { /* global define, require, module */ - - /* AMD */ if (typeof define === 'function' && define.amd) - define(["protobufjs/minimal"], factory); - - /* CommonJS */ else if (typeof require === 'function' && typeof module === 'object' && module && module.exports) - module.exports = factory(require("protobufjs/minimal")); - -})(this, function($protobuf) { - "use strict"; - - // Common aliases - var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; - - // Exported root namespace - var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); - - $root.RPC = (function() { - - /** - * Properties of a RPC. - * @exports IRPC - * @interface IRPC - * @property {Array.|null} [subscriptions] RPC subscriptions - * @property {Array.|null} [messages] RPC messages - * @property {RPC.IControlMessage|null} [control] RPC control - */ - - /** - * Constructs a new RPC. - * @exports RPC - * @classdesc Represents a RPC. - * @implements IRPC - * @constructor - * @param {IRPC=} [p] Properties to set - */ - function RPC(p) { - this.subscriptions = []; - this.messages = []; - 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]]; - } - - /** - * RPC subscriptions. - * @member {Array.} subscriptions - * @memberof RPC - * @instance - */ - RPC.prototype.subscriptions = $util.emptyArray; - - /** - * RPC messages. - * @member {Array.} messages - * @memberof RPC - * @instance - */ - RPC.prototype.messages = $util.emptyArray; - - /** - * RPC control. - * @member {RPC.IControlMessage|null|undefined} control - * @memberof RPC - * @instance - */ - RPC.prototype.control = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * RPC _control. - * @member {"control"|undefined} _control - * @memberof RPC - * @instance - */ - Object.defineProperty(RPC.prototype, "_control", { - get: $util.oneOfGetter($oneOfFields = ["control"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified RPC message. Does not implicitly {@link RPC.verify|verify} messages. - * @function encode - * @memberof RPC - * @static - * @param {IRPC} m RPC message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - RPC.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.subscriptions != null && m.subscriptions.length) { - for (var i = 0; i < m.subscriptions.length; ++i) - $root.RPC.SubOpts.encode(m.subscriptions[i], w.uint32(10).fork()).ldelim(); - } - if (m.messages != null && m.messages.length) { - for (var i = 0; i < m.messages.length; ++i) - $root.RPC.Message.encode(m.messages[i], w.uint32(18).fork()).ldelim(); - } - if (m.control != null && Object.hasOwnProperty.call(m, "control")) - $root.RPC.ControlMessage.encode(m.control, w.uint32(26).fork()).ldelim(); - return w; - }; - - /** - * Decodes a RPC message from the specified reader or buffer. - * @function decode - * @memberof RPC - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC} RPC - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - RPC.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.RPC(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.subscriptions && m.subscriptions.length)) - m.subscriptions = []; - m.subscriptions.push($root.RPC.SubOpts.decode(r, r.uint32())); - break; - case 2: - if (!(m.messages && m.messages.length)) - m.messages = []; - m.messages.push($root.RPC.Message.decode(r, r.uint32())); - break; - case 3: - m.control = $root.RPC.ControlMessage.decode(r, r.uint32()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a RPC message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC - * @static - * @param {Object.} d Plain object - * @returns {RPC} RPC - */ - RPC.fromObject = function fromObject(d) { - if (d instanceof $root.RPC) - return d; - var m = new $root.RPC(); - if (d.subscriptions) { - if (!Array.isArray(d.subscriptions)) - throw TypeError(".RPC.subscriptions: array expected"); - m.subscriptions = []; - for (var i = 0; i < d.subscriptions.length; ++i) { - if (typeof d.subscriptions[i] !== "object") - throw TypeError(".RPC.subscriptions: object expected"); - m.subscriptions[i] = $root.RPC.SubOpts.fromObject(d.subscriptions[i]); - } - } - if (d.messages) { - if (!Array.isArray(d.messages)) - throw TypeError(".RPC.messages: array expected"); - m.messages = []; - for (var i = 0; i < d.messages.length; ++i) { - if (typeof d.messages[i] !== "object") - throw TypeError(".RPC.messages: object expected"); - m.messages[i] = $root.RPC.Message.fromObject(d.messages[i]); - } - } - if (d.control != null) { - if (typeof d.control !== "object") - throw TypeError(".RPC.control: object expected"); - m.control = $root.RPC.ControlMessage.fromObject(d.control); - } - return m; - }; - - /** - * Creates a plain object from a RPC message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC - * @static - * @param {RPC} m RPC - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - RPC.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.subscriptions = []; - d.messages = []; - } - if (m.subscriptions && m.subscriptions.length) { - d.subscriptions = []; - for (var j = 0; j < m.subscriptions.length; ++j) { - d.subscriptions[j] = $root.RPC.SubOpts.toObject(m.subscriptions[j], o); - } - } - if (m.messages && m.messages.length) { - d.messages = []; - for (var j = 0; j < m.messages.length; ++j) { - d.messages[j] = $root.RPC.Message.toObject(m.messages[j], o); - } - } - if (m.control != null && m.hasOwnProperty("control")) { - d.control = $root.RPC.ControlMessage.toObject(m.control, o); - if (o.oneofs) - d._control = "control"; - } - return d; - }; - - /** - * Converts this RPC to JSON. - * @function toJSON - * @memberof RPC - * @instance - * @returns {Object.} JSON object - */ - RPC.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - RPC.SubOpts = (function() { - - /** - * Properties of a SubOpts. - * @memberof RPC - * @interface ISubOpts - * @property {boolean|null} [subscribe] SubOpts subscribe - * @property {string|null} [topic] SubOpts topic - */ - - /** - * Constructs a new SubOpts. - * @memberof RPC - * @classdesc Represents a SubOpts. - * @implements ISubOpts - * @constructor - * @param {RPC.ISubOpts=} [p] Properties to set - */ - function SubOpts(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]]; - } - - /** - * SubOpts subscribe. - * @member {boolean|null|undefined} subscribe - * @memberof RPC.SubOpts - * @instance - */ - SubOpts.prototype.subscribe = null; - - /** - * SubOpts topic. - * @member {string|null|undefined} topic - * @memberof RPC.SubOpts - * @instance - */ - SubOpts.prototype.topic = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * SubOpts _subscribe. - * @member {"subscribe"|undefined} _subscribe - * @memberof RPC.SubOpts - * @instance - */ - Object.defineProperty(SubOpts.prototype, "_subscribe", { - get: $util.oneOfGetter($oneOfFields = ["subscribe"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * SubOpts _topic. - * @member {"topic"|undefined} _topic - * @memberof RPC.SubOpts - * @instance - */ - Object.defineProperty(SubOpts.prototype, "_topic", { - get: $util.oneOfGetter($oneOfFields = ["topic"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified SubOpts message. Does not implicitly {@link RPC.SubOpts.verify|verify} messages. - * @function encode - * @memberof RPC.SubOpts - * @static - * @param {RPC.ISubOpts} m SubOpts message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - SubOpts.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.subscribe != null && Object.hasOwnProperty.call(m, "subscribe")) - w.uint32(8).bool(m.subscribe); - if (m.topic != null && Object.hasOwnProperty.call(m, "topic")) - w.uint32(18).string(m.topic); - return w; - }; - - /** - * Decodes a SubOpts message from the specified reader or buffer. - * @function decode - * @memberof RPC.SubOpts - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.SubOpts} SubOpts - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - SubOpts.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.RPC.SubOpts(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.subscribe = r.bool(); - break; - case 2: - m.topic = r.string(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a SubOpts message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.SubOpts - * @static - * @param {Object.} d Plain object - * @returns {RPC.SubOpts} SubOpts - */ - SubOpts.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.SubOpts) - return d; - var m = new $root.RPC.SubOpts(); - if (d.subscribe != null) { - m.subscribe = Boolean(d.subscribe); - } - if (d.topic != null) { - m.topic = String(d.topic); - } - return m; - }; - - /** - * Creates a plain object from a SubOpts message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.SubOpts - * @static - * @param {RPC.SubOpts} m SubOpts - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - SubOpts.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (m.subscribe != null && m.hasOwnProperty("subscribe")) { - d.subscribe = m.subscribe; - if (o.oneofs) - d._subscribe = "subscribe"; - } - if (m.topic != null && m.hasOwnProperty("topic")) { - d.topic = m.topic; - if (o.oneofs) - d._topic = "topic"; - } - return d; - }; - - /** - * Converts this SubOpts to JSON. - * @function toJSON - * @memberof RPC.SubOpts - * @instance - * @returns {Object.} JSON object - */ - SubOpts.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return SubOpts; - })(); - - RPC.Message = (function() { - - /** - * Properties of a Message. - * @memberof RPC - * @interface IMessage - * @property {Uint8Array|null} [from] Message from - * @property {Uint8Array|null} [data] Message data - * @property {Uint8Array|null} [seqno] Message seqno - * @property {string} topic Message topic - * @property {Uint8Array|null} [signature] Message signature - * @property {Uint8Array|null} [key] Message key - */ - - /** - * Constructs a new Message. - * @memberof RPC - * @classdesc Represents a Message. - * @implements IMessage - * @constructor - * @param {RPC.IMessage=} [p] Properties to set - */ - function Message(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]]; - } - - /** - * Message from. - * @member {Uint8Array|null|undefined} from - * @memberof RPC.Message - * @instance - */ - Message.prototype.from = null; - - /** - * Message data. - * @member {Uint8Array|null|undefined} data - * @memberof RPC.Message - * @instance - */ - Message.prototype.data = null; - - /** - * Message seqno. - * @member {Uint8Array|null|undefined} seqno - * @memberof RPC.Message - * @instance - */ - Message.prototype.seqno = null; - - /** - * Message topic. - * @member {string} topic - * @memberof RPC.Message - * @instance - */ - Message.prototype.topic = ""; - - /** - * Message signature. - * @member {Uint8Array|null|undefined} signature - * @memberof RPC.Message - * @instance - */ - Message.prototype.signature = null; - - /** - * Message key. - * @member {Uint8Array|null|undefined} key - * @memberof RPC.Message - * @instance - */ - Message.prototype.key = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * Message _from. - * @member {"from"|undefined} _from - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_from", { - get: $util.oneOfGetter($oneOfFields = ["from"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Message _data. - * @member {"data"|undefined} _data - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_data", { - get: $util.oneOfGetter($oneOfFields = ["data"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Message _seqno. - * @member {"seqno"|undefined} _seqno - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_seqno", { - get: $util.oneOfGetter($oneOfFields = ["seqno"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Message _signature. - * @member {"signature"|undefined} _signature - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_signature", { - get: $util.oneOfGetter($oneOfFields = ["signature"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Message _key. - * @member {"key"|undefined} _key - * @memberof RPC.Message - * @instance - */ - Object.defineProperty(Message.prototype, "_key", { - get: $util.oneOfGetter($oneOfFields = ["key"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified Message message. Does not implicitly {@link RPC.Message.verify|verify} messages. - * @function encode - * @memberof RPC.Message - * @static - * @param {RPC.IMessage} m Message message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Message.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.from != null && Object.hasOwnProperty.call(m, "from")) - w.uint32(10).bytes(m.from); - if (m.data != null && Object.hasOwnProperty.call(m, "data")) - w.uint32(18).bytes(m.data); - if (m.seqno != null && Object.hasOwnProperty.call(m, "seqno")) - w.uint32(26).bytes(m.seqno); - w.uint32(34).string(m.topic); - if (m.signature != null && Object.hasOwnProperty.call(m, "signature")) - w.uint32(42).bytes(m.signature); - if (m.key != null && Object.hasOwnProperty.call(m, "key")) - w.uint32(50).bytes(m.key); - return w; - }; - - /** - * Decodes a Message message from the specified reader or buffer. - * @function decode - * @memberof RPC.Message - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.Message} Message - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Message.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.RPC.Message(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.from = r.bytes(); - break; - case 2: - m.data = r.bytes(); - break; - case 3: - m.seqno = r.bytes(); - break; - case 4: - m.topic = r.string(); - break; - case 5: - m.signature = r.bytes(); - break; - case 6: - m.key = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - if (!m.hasOwnProperty("topic")) - throw $util.ProtocolError("missing required 'topic'", { instance: m }); - return m; - }; - - /** - * Creates a Message message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.Message - * @static - * @param {Object.} d Plain object - * @returns {RPC.Message} Message - */ - Message.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.Message) - return d; - var m = new $root.RPC.Message(); - if (d.from != null) { - if (typeof d.from === "string") - $util.base64.decode(d.from, m.from = $util.newBuffer($util.base64.length(d.from)), 0); - else if (d.from.length) - m.from = d.from; - } - if (d.data != null) { - if (typeof d.data === "string") - $util.base64.decode(d.data, m.data = $util.newBuffer($util.base64.length(d.data)), 0); - else if (d.data.length) - m.data = d.data; - } - if (d.seqno != null) { - if (typeof d.seqno === "string") - $util.base64.decode(d.seqno, m.seqno = $util.newBuffer($util.base64.length(d.seqno)), 0); - else if (d.seqno.length) - m.seqno = d.seqno; - } - if (d.topic != null) { - m.topic = String(d.topic); - } - if (d.signature != null) { - if (typeof d.signature === "string") - $util.base64.decode(d.signature, m.signature = $util.newBuffer($util.base64.length(d.signature)), 0); - else if (d.signature.length) - m.signature = d.signature; - } - if (d.key != null) { - if (typeof d.key === "string") - $util.base64.decode(d.key, m.key = $util.newBuffer($util.base64.length(d.key)), 0); - else if (d.key.length) - m.key = d.key; - } - return m; - }; - - /** - * Creates a plain object from a Message message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.Message - * @static - * @param {RPC.Message} m Message - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - Message.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.defaults) { - d.topic = ""; - } - if (m.from != null && m.hasOwnProperty("from")) { - d.from = o.bytes === String ? $util.base64.encode(m.from, 0, m.from.length) : o.bytes === Array ? Array.prototype.slice.call(m.from) : m.from; - if (o.oneofs) - d._from = "from"; - } - if (m.data != null && m.hasOwnProperty("data")) { - d.data = o.bytes === String ? $util.base64.encode(m.data, 0, m.data.length) : o.bytes === Array ? Array.prototype.slice.call(m.data) : m.data; - if (o.oneofs) - d._data = "data"; - } - if (m.seqno != null && m.hasOwnProperty("seqno")) { - d.seqno = o.bytes === String ? $util.base64.encode(m.seqno, 0, m.seqno.length) : o.bytes === Array ? Array.prototype.slice.call(m.seqno) : m.seqno; - if (o.oneofs) - d._seqno = "seqno"; - } - if (m.topic != null && m.hasOwnProperty("topic")) { - d.topic = m.topic; - } - if (m.signature != null && m.hasOwnProperty("signature")) { - d.signature = o.bytes === String ? $util.base64.encode(m.signature, 0, m.signature.length) : o.bytes === Array ? Array.prototype.slice.call(m.signature) : m.signature; - if (o.oneofs) - d._signature = "signature"; - } - if (m.key != null && m.hasOwnProperty("key")) { - d.key = o.bytes === String ? $util.base64.encode(m.key, 0, m.key.length) : o.bytes === Array ? Array.prototype.slice.call(m.key) : m.key; - if (o.oneofs) - d._key = "key"; - } - return d; - }; - - /** - * Converts this Message to JSON. - * @function toJSON - * @memberof RPC.Message - * @instance - * @returns {Object.} JSON object - */ - Message.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Message; - })(); - - RPC.ControlMessage = (function() { - - /** - * Properties of a ControlMessage. - * @memberof RPC - * @interface IControlMessage - * @property {Array.|null} [ihave] ControlMessage ihave - * @property {Array.|null} [iwant] ControlMessage iwant - * @property {Array.|null} [graft] ControlMessage graft - * @property {Array.|null} [prune] ControlMessage prune - */ - - /** - * Constructs a new ControlMessage. - * @memberof RPC - * @classdesc Represents a ControlMessage. - * @implements IControlMessage - * @constructor - * @param {RPC.IControlMessage=} [p] Properties to set - */ - function ControlMessage(p) { - this.ihave = []; - this.iwant = []; - this.graft = []; - this.prune = []; - 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]]; - } - - /** - * ControlMessage ihave. - * @member {Array.} ihave - * @memberof RPC.ControlMessage - * @instance - */ - ControlMessage.prototype.ihave = $util.emptyArray; - - /** - * ControlMessage iwant. - * @member {Array.} iwant - * @memberof RPC.ControlMessage - * @instance - */ - ControlMessage.prototype.iwant = $util.emptyArray; - - /** - * ControlMessage graft. - * @member {Array.} graft - * @memberof RPC.ControlMessage - * @instance - */ - ControlMessage.prototype.graft = $util.emptyArray; - - /** - * ControlMessage prune. - * @member {Array.} prune - * @memberof RPC.ControlMessage - * @instance - */ - ControlMessage.prototype.prune = $util.emptyArray; - - /** - * Encodes the specified ControlMessage message. Does not implicitly {@link RPC.ControlMessage.verify|verify} messages. - * @function encode - * @memberof RPC.ControlMessage - * @static - * @param {RPC.IControlMessage} m ControlMessage message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlMessage.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.ihave != null && m.ihave.length) { - for (var i = 0; i < m.ihave.length; ++i) - $root.RPC.ControlIHave.encode(m.ihave[i], w.uint32(10).fork()).ldelim(); - } - if (m.iwant != null && m.iwant.length) { - for (var i = 0; i < m.iwant.length; ++i) - $root.RPC.ControlIWant.encode(m.iwant[i], w.uint32(18).fork()).ldelim(); - } - if (m.graft != null && m.graft.length) { - for (var i = 0; i < m.graft.length; ++i) - $root.RPC.ControlGraft.encode(m.graft[i], w.uint32(26).fork()).ldelim(); - } - if (m.prune != null && m.prune.length) { - for (var i = 0; i < m.prune.length; ++i) - $root.RPC.ControlPrune.encode(m.prune[i], w.uint32(34).fork()).ldelim(); - } - return w; - }; - - /** - * Decodes a ControlMessage message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlMessage - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlMessage} ControlMessage - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlMessage.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.RPC.ControlMessage(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.ihave && m.ihave.length)) - m.ihave = []; - m.ihave.push($root.RPC.ControlIHave.decode(r, r.uint32())); - break; - case 2: - if (!(m.iwant && m.iwant.length)) - m.iwant = []; - m.iwant.push($root.RPC.ControlIWant.decode(r, r.uint32())); - break; - case 3: - if (!(m.graft && m.graft.length)) - m.graft = []; - m.graft.push($root.RPC.ControlGraft.decode(r, r.uint32())); - break; - case 4: - if (!(m.prune && m.prune.length)) - m.prune = []; - m.prune.push($root.RPC.ControlPrune.decode(r, r.uint32())); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlMessage message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlMessage - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlMessage} ControlMessage - */ - ControlMessage.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlMessage) - return d; - var m = new $root.RPC.ControlMessage(); - if (d.ihave) { - if (!Array.isArray(d.ihave)) - throw TypeError(".RPC.ControlMessage.ihave: array expected"); - m.ihave = []; - for (var i = 0; i < d.ihave.length; ++i) { - if (typeof d.ihave[i] !== "object") - throw TypeError(".RPC.ControlMessage.ihave: object expected"); - m.ihave[i] = $root.RPC.ControlIHave.fromObject(d.ihave[i]); - } - } - if (d.iwant) { - if (!Array.isArray(d.iwant)) - throw TypeError(".RPC.ControlMessage.iwant: array expected"); - m.iwant = []; - for (var i = 0; i < d.iwant.length; ++i) { - if (typeof d.iwant[i] !== "object") - throw TypeError(".RPC.ControlMessage.iwant: object expected"); - m.iwant[i] = $root.RPC.ControlIWant.fromObject(d.iwant[i]); - } - } - if (d.graft) { - if (!Array.isArray(d.graft)) - throw TypeError(".RPC.ControlMessage.graft: array expected"); - m.graft = []; - for (var i = 0; i < d.graft.length; ++i) { - if (typeof d.graft[i] !== "object") - throw TypeError(".RPC.ControlMessage.graft: object expected"); - m.graft[i] = $root.RPC.ControlGraft.fromObject(d.graft[i]); - } - } - if (d.prune) { - if (!Array.isArray(d.prune)) - throw TypeError(".RPC.ControlMessage.prune: array expected"); - m.prune = []; - for (var i = 0; i < d.prune.length; ++i) { - if (typeof d.prune[i] !== "object") - throw TypeError(".RPC.ControlMessage.prune: object expected"); - m.prune[i] = $root.RPC.ControlPrune.fromObject(d.prune[i]); - } - } - return m; - }; - - /** - * Creates a plain object from a ControlMessage message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlMessage - * @static - * @param {RPC.ControlMessage} m ControlMessage - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlMessage.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.ihave = []; - d.iwant = []; - d.graft = []; - d.prune = []; - } - if (m.ihave && m.ihave.length) { - d.ihave = []; - for (var j = 0; j < m.ihave.length; ++j) { - d.ihave[j] = $root.RPC.ControlIHave.toObject(m.ihave[j], o); - } - } - if (m.iwant && m.iwant.length) { - d.iwant = []; - for (var j = 0; j < m.iwant.length; ++j) { - d.iwant[j] = $root.RPC.ControlIWant.toObject(m.iwant[j], o); - } - } - if (m.graft && m.graft.length) { - d.graft = []; - for (var j = 0; j < m.graft.length; ++j) { - d.graft[j] = $root.RPC.ControlGraft.toObject(m.graft[j], o); - } - } - if (m.prune && m.prune.length) { - d.prune = []; - for (var j = 0; j < m.prune.length; ++j) { - d.prune[j] = $root.RPC.ControlPrune.toObject(m.prune[j], o); - } - } - return d; - }; - - /** - * Converts this ControlMessage to JSON. - * @function toJSON - * @memberof RPC.ControlMessage - * @instance - * @returns {Object.} JSON object - */ - ControlMessage.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlMessage; - })(); - - RPC.ControlIHave = (function() { - - /** - * Properties of a ControlIHave. - * @memberof RPC - * @interface IControlIHave - * @property {string|null} [topicID] ControlIHave topicID - * @property {Array.|null} [messageIDs] ControlIHave messageIDs - */ - - /** - * Constructs a new ControlIHave. - * @memberof RPC - * @classdesc Represents a ControlIHave. - * @implements IControlIHave - * @constructor - * @param {RPC.IControlIHave=} [p] Properties to set - */ - function ControlIHave(p) { - this.messageIDs = []; - 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]]; - } - - /** - * ControlIHave topicID. - * @member {string|null|undefined} topicID - * @memberof RPC.ControlIHave - * @instance - */ - ControlIHave.prototype.topicID = null; - - /** - * ControlIHave messageIDs. - * @member {Array.} messageIDs - * @memberof RPC.ControlIHave - * @instance - */ - ControlIHave.prototype.messageIDs = $util.emptyArray; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * ControlIHave _topicID. - * @member {"topicID"|undefined} _topicID - * @memberof RPC.ControlIHave - * @instance - */ - Object.defineProperty(ControlIHave.prototype, "_topicID", { - get: $util.oneOfGetter($oneOfFields = ["topicID"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified ControlIHave message. Does not implicitly {@link RPC.ControlIHave.verify|verify} messages. - * @function encode - * @memberof RPC.ControlIHave - * @static - * @param {RPC.IControlIHave} m ControlIHave message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlIHave.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.topicID != null && Object.hasOwnProperty.call(m, "topicID")) - w.uint32(10).string(m.topicID); - if (m.messageIDs != null && m.messageIDs.length) { - for (var i = 0; i < m.messageIDs.length; ++i) - w.uint32(18).bytes(m.messageIDs[i]); - } - return w; - }; - - /** - * Decodes a ControlIHave message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlIHave - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlIHave} ControlIHave - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlIHave.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.RPC.ControlIHave(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.topicID = r.string(); - break; - case 2: - if (!(m.messageIDs && m.messageIDs.length)) - m.messageIDs = []; - m.messageIDs.push(r.bytes()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlIHave message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlIHave - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlIHave} ControlIHave - */ - ControlIHave.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlIHave) - return d; - var m = new $root.RPC.ControlIHave(); - if (d.topicID != null) { - m.topicID = String(d.topicID); - } - if (d.messageIDs) { - if (!Array.isArray(d.messageIDs)) - throw TypeError(".RPC.ControlIHave.messageIDs: array expected"); - m.messageIDs = []; - for (var i = 0; i < d.messageIDs.length; ++i) { - if (typeof d.messageIDs[i] === "string") - $util.base64.decode(d.messageIDs[i], m.messageIDs[i] = $util.newBuffer($util.base64.length(d.messageIDs[i])), 0); - else if (d.messageIDs[i].length) - m.messageIDs[i] = d.messageIDs[i]; - } - } - return m; - }; - - /** - * Creates a plain object from a ControlIHave message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlIHave - * @static - * @param {RPC.ControlIHave} m ControlIHave - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlIHave.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.messageIDs = []; - } - if (m.topicID != null && m.hasOwnProperty("topicID")) { - d.topicID = m.topicID; - if (o.oneofs) - d._topicID = "topicID"; - } - if (m.messageIDs && m.messageIDs.length) { - d.messageIDs = []; - for (var j = 0; j < m.messageIDs.length; ++j) { - d.messageIDs[j] = o.bytes === String ? $util.base64.encode(m.messageIDs[j], 0, m.messageIDs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.messageIDs[j]) : m.messageIDs[j]; - } - } - return d; - }; - - /** - * Converts this ControlIHave to JSON. - * @function toJSON - * @memberof RPC.ControlIHave - * @instance - * @returns {Object.} JSON object - */ - ControlIHave.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlIHave; - })(); - - RPC.ControlIWant = (function() { - - /** - * Properties of a ControlIWant. - * @memberof RPC - * @interface IControlIWant - * @property {Array.|null} [messageIDs] ControlIWant messageIDs - */ - - /** - * Constructs a new ControlIWant. - * @memberof RPC - * @classdesc Represents a ControlIWant. - * @implements IControlIWant - * @constructor - * @param {RPC.IControlIWant=} [p] Properties to set - */ - function ControlIWant(p) { - this.messageIDs = []; - 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]]; - } - - /** - * ControlIWant messageIDs. - * @member {Array.} messageIDs - * @memberof RPC.ControlIWant - * @instance - */ - ControlIWant.prototype.messageIDs = $util.emptyArray; - - /** - * Encodes the specified ControlIWant message. Does not implicitly {@link RPC.ControlIWant.verify|verify} messages. - * @function encode - * @memberof RPC.ControlIWant - * @static - * @param {RPC.IControlIWant} m ControlIWant message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlIWant.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.messageIDs != null && m.messageIDs.length) { - for (var i = 0; i < m.messageIDs.length; ++i) - w.uint32(10).bytes(m.messageIDs[i]); - } - return w; - }; - - /** - * Decodes a ControlIWant message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlIWant - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlIWant} ControlIWant - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlIWant.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.RPC.ControlIWant(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - if (!(m.messageIDs && m.messageIDs.length)) - m.messageIDs = []; - m.messageIDs.push(r.bytes()); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlIWant message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlIWant - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlIWant} ControlIWant - */ - ControlIWant.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlIWant) - return d; - var m = new $root.RPC.ControlIWant(); - if (d.messageIDs) { - if (!Array.isArray(d.messageIDs)) - throw TypeError(".RPC.ControlIWant.messageIDs: array expected"); - m.messageIDs = []; - for (var i = 0; i < d.messageIDs.length; ++i) { - if (typeof d.messageIDs[i] === "string") - $util.base64.decode(d.messageIDs[i], m.messageIDs[i] = $util.newBuffer($util.base64.length(d.messageIDs[i])), 0); - else if (d.messageIDs[i].length) - m.messageIDs[i] = d.messageIDs[i]; - } - } - return m; - }; - - /** - * Creates a plain object from a ControlIWant message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlIWant - * @static - * @param {RPC.ControlIWant} m ControlIWant - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlIWant.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.messageIDs = []; - } - if (m.messageIDs && m.messageIDs.length) { - d.messageIDs = []; - for (var j = 0; j < m.messageIDs.length; ++j) { - d.messageIDs[j] = o.bytes === String ? $util.base64.encode(m.messageIDs[j], 0, m.messageIDs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.messageIDs[j]) : m.messageIDs[j]; - } - } - return d; - }; - - /** - * Converts this ControlIWant to JSON. - * @function toJSON - * @memberof RPC.ControlIWant - * @instance - * @returns {Object.} JSON object - */ - ControlIWant.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlIWant; - })(); - - RPC.ControlGraft = (function() { - - /** - * Properties of a ControlGraft. - * @memberof RPC - * @interface IControlGraft - * @property {string|null} [topicID] ControlGraft topicID - */ - - /** - * Constructs a new ControlGraft. - * @memberof RPC - * @classdesc Represents a ControlGraft. - * @implements IControlGraft - * @constructor - * @param {RPC.IControlGraft=} [p] Properties to set - */ - function ControlGraft(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]]; - } - - /** - * ControlGraft topicID. - * @member {string|null|undefined} topicID - * @memberof RPC.ControlGraft - * @instance - */ - ControlGraft.prototype.topicID = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * ControlGraft _topicID. - * @member {"topicID"|undefined} _topicID - * @memberof RPC.ControlGraft - * @instance - */ - Object.defineProperty(ControlGraft.prototype, "_topicID", { - get: $util.oneOfGetter($oneOfFields = ["topicID"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified ControlGraft message. Does not implicitly {@link RPC.ControlGraft.verify|verify} messages. - * @function encode - * @memberof RPC.ControlGraft - * @static - * @param {RPC.IControlGraft} m ControlGraft message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlGraft.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.topicID != null && Object.hasOwnProperty.call(m, "topicID")) - w.uint32(10).string(m.topicID); - return w; - }; - - /** - * Decodes a ControlGraft message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlGraft - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlGraft} ControlGraft - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlGraft.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.RPC.ControlGraft(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.topicID = r.string(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlGraft message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlGraft - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlGraft} ControlGraft - */ - ControlGraft.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlGraft) - return d; - var m = new $root.RPC.ControlGraft(); - if (d.topicID != null) { - m.topicID = String(d.topicID); - } - return m; - }; - - /** - * Creates a plain object from a ControlGraft message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlGraft - * @static - * @param {RPC.ControlGraft} m ControlGraft - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlGraft.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (m.topicID != null && m.hasOwnProperty("topicID")) { - d.topicID = m.topicID; - if (o.oneofs) - d._topicID = "topicID"; - } - return d; - }; - - /** - * Converts this ControlGraft to JSON. - * @function toJSON - * @memberof RPC.ControlGraft - * @instance - * @returns {Object.} JSON object - */ - ControlGraft.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlGraft; - })(); - - RPC.ControlPrune = (function() { - - /** - * Properties of a ControlPrune. - * @memberof RPC - * @interface IControlPrune - * @property {string|null} [topicID] ControlPrune topicID - * @property {Array.|null} [peers] ControlPrune peers - * @property {number|null} [backoff] ControlPrune backoff - */ - - /** - * Constructs a new ControlPrune. - * @memberof RPC - * @classdesc Represents a ControlPrune. - * @implements IControlPrune - * @constructor - * @param {RPC.IControlPrune=} [p] Properties to set - */ - function ControlPrune(p) { - this.peers = []; - 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]]; - } - - /** - * ControlPrune topicID. - * @member {string|null|undefined} topicID - * @memberof RPC.ControlPrune - * @instance - */ - ControlPrune.prototype.topicID = null; - - /** - * ControlPrune peers. - * @member {Array.} peers - * @memberof RPC.ControlPrune - * @instance - */ - ControlPrune.prototype.peers = $util.emptyArray; - - /** - * ControlPrune backoff. - * @member {number|null|undefined} backoff - * @memberof RPC.ControlPrune - * @instance - */ - ControlPrune.prototype.backoff = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * ControlPrune _topicID. - * @member {"topicID"|undefined} _topicID - * @memberof RPC.ControlPrune - * @instance - */ - Object.defineProperty(ControlPrune.prototype, "_topicID", { - get: $util.oneOfGetter($oneOfFields = ["topicID"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * ControlPrune _backoff. - * @member {"backoff"|undefined} _backoff - * @memberof RPC.ControlPrune - * @instance - */ - Object.defineProperty(ControlPrune.prototype, "_backoff", { - get: $util.oneOfGetter($oneOfFields = ["backoff"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified ControlPrune message. Does not implicitly {@link RPC.ControlPrune.verify|verify} messages. - * @function encode - * @memberof RPC.ControlPrune - * @static - * @param {RPC.IControlPrune} m ControlPrune message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - ControlPrune.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.topicID != null && Object.hasOwnProperty.call(m, "topicID")) - w.uint32(10).string(m.topicID); - if (m.peers != null && m.peers.length) { - for (var i = 0; i < m.peers.length; ++i) - $root.RPC.PeerInfo.encode(m.peers[i], w.uint32(18).fork()).ldelim(); - } - if (m.backoff != null && Object.hasOwnProperty.call(m, "backoff")) - w.uint32(24).uint64(m.backoff); - return w; - }; - - /** - * Decodes a ControlPrune message from the specified reader or buffer. - * @function decode - * @memberof RPC.ControlPrune - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.ControlPrune} ControlPrune - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - ControlPrune.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.RPC.ControlPrune(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.topicID = r.string(); - break; - case 2: - if (!(m.peers && m.peers.length)) - m.peers = []; - m.peers.push($root.RPC.PeerInfo.decode(r, r.uint32())); - break; - case 3: - m.backoff = r.uint64(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a ControlPrune message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.ControlPrune - * @static - * @param {Object.} d Plain object - * @returns {RPC.ControlPrune} ControlPrune - */ - ControlPrune.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.ControlPrune) - return d; - var m = new $root.RPC.ControlPrune(); - if (d.topicID != null) { - m.topicID = String(d.topicID); - } - if (d.peers) { - if (!Array.isArray(d.peers)) - throw TypeError(".RPC.ControlPrune.peers: array expected"); - m.peers = []; - for (var i = 0; i < d.peers.length; ++i) { - if (typeof d.peers[i] !== "object") - throw TypeError(".RPC.ControlPrune.peers: object expected"); - m.peers[i] = $root.RPC.PeerInfo.fromObject(d.peers[i]); - } - } - if (d.backoff != null) { - if ($util.Long) - (m.backoff = $util.Long.fromValue(d.backoff)).unsigned = true; - else if (typeof d.backoff === "string") - m.backoff = parseInt(d.backoff, 10); - else if (typeof d.backoff === "number") - m.backoff = d.backoff; - else if (typeof d.backoff === "object") - m.backoff = new $util.LongBits(d.backoff.low >>> 0, d.backoff.high >>> 0).toNumber(true); - } - return m; - }; - - /** - * Creates a plain object from a ControlPrune message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.ControlPrune - * @static - * @param {RPC.ControlPrune} m ControlPrune - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - ControlPrune.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (o.arrays || o.defaults) { - d.peers = []; - } - if (m.topicID != null && m.hasOwnProperty("topicID")) { - d.topicID = m.topicID; - if (o.oneofs) - d._topicID = "topicID"; - } - if (m.peers && m.peers.length) { - d.peers = []; - for (var j = 0; j < m.peers.length; ++j) { - d.peers[j] = $root.RPC.PeerInfo.toObject(m.peers[j], o); - } - } - if (m.backoff != null && m.hasOwnProperty("backoff")) { - if (typeof m.backoff === "number") - d.backoff = o.longs === String ? String(m.backoff) : m.backoff; - else - d.backoff = o.longs === String ? $util.Long.prototype.toString.call(m.backoff) : o.longs === Number ? new $util.LongBits(m.backoff.low >>> 0, m.backoff.high >>> 0).toNumber(true) : m.backoff; - if (o.oneofs) - d._backoff = "backoff"; - } - return d; - }; - - /** - * Converts this ControlPrune to JSON. - * @function toJSON - * @memberof RPC.ControlPrune - * @instance - * @returns {Object.} JSON object - */ - ControlPrune.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return ControlPrune; - })(); - - RPC.PeerInfo = (function() { - - /** - * Properties of a PeerInfo. - * @memberof RPC - * @interface IPeerInfo - * @property {Uint8Array|null} [peerID] PeerInfo peerID - * @property {Uint8Array|null} [signedPeerRecord] PeerInfo signedPeerRecord - */ - - /** - * Constructs a new PeerInfo. - * @memberof RPC - * @classdesc Represents a PeerInfo. - * @implements IPeerInfo - * @constructor - * @param {RPC.IPeerInfo=} [p] Properties to set - */ - function PeerInfo(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]]; - } - - /** - * PeerInfo peerID. - * @member {Uint8Array|null|undefined} peerID - * @memberof RPC.PeerInfo - * @instance - */ - PeerInfo.prototype.peerID = null; - - /** - * PeerInfo signedPeerRecord. - * @member {Uint8Array|null|undefined} signedPeerRecord - * @memberof RPC.PeerInfo - * @instance - */ - PeerInfo.prototype.signedPeerRecord = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; - - /** - * PeerInfo _peerID. - * @member {"peerID"|undefined} _peerID - * @memberof RPC.PeerInfo - * @instance - */ - Object.defineProperty(PeerInfo.prototype, "_peerID", { - get: $util.oneOfGetter($oneOfFields = ["peerID"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * PeerInfo _signedPeerRecord. - * @member {"signedPeerRecord"|undefined} _signedPeerRecord - * @memberof RPC.PeerInfo - * @instance - */ - Object.defineProperty(PeerInfo.prototype, "_signedPeerRecord", { - get: $util.oneOfGetter($oneOfFields = ["signedPeerRecord"]), - set: $util.oneOfSetter($oneOfFields) - }); - - /** - * Encodes the specified PeerInfo message. Does not implicitly {@link RPC.PeerInfo.verify|verify} messages. - * @function encode - * @memberof RPC.PeerInfo - * @static - * @param {RPC.IPeerInfo} m PeerInfo message or plain object to encode - * @param {$protobuf.Writer} [w] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - PeerInfo.encode = function encode(m, w) { - if (!w) - w = $Writer.create(); - if (m.peerID != null && Object.hasOwnProperty.call(m, "peerID")) - w.uint32(10).bytes(m.peerID); - if (m.signedPeerRecord != null && Object.hasOwnProperty.call(m, "signedPeerRecord")) - w.uint32(18).bytes(m.signedPeerRecord); - return w; - }; - - /** - * Decodes a PeerInfo message from the specified reader or buffer. - * @function decode - * @memberof RPC.PeerInfo - * @static - * @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from - * @param {number} [l] Message length if known beforehand - * @returns {RPC.PeerInfo} PeerInfo - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - PeerInfo.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.RPC.PeerInfo(); - while (r.pos < c) { - var t = r.uint32(); - switch (t >>> 3) { - case 1: - m.peerID = r.bytes(); - break; - case 2: - m.signedPeerRecord = r.bytes(); - break; - default: - r.skipType(t & 7); - break; - } - } - return m; - }; - - /** - * Creates a PeerInfo message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof RPC.PeerInfo - * @static - * @param {Object.} d Plain object - * @returns {RPC.PeerInfo} PeerInfo - */ - PeerInfo.fromObject = function fromObject(d) { - if (d instanceof $root.RPC.PeerInfo) - return d; - var m = new $root.RPC.PeerInfo(); - if (d.peerID != null) { - if (typeof d.peerID === "string") - $util.base64.decode(d.peerID, m.peerID = $util.newBuffer($util.base64.length(d.peerID)), 0); - else if (d.peerID.length) - m.peerID = d.peerID; - } - if (d.signedPeerRecord != null) { - if (typeof d.signedPeerRecord === "string") - $util.base64.decode(d.signedPeerRecord, m.signedPeerRecord = $util.newBuffer($util.base64.length(d.signedPeerRecord)), 0); - else if (d.signedPeerRecord.length) - m.signedPeerRecord = d.signedPeerRecord; - } - return m; - }; - - /** - * Creates a plain object from a PeerInfo message. Also converts values to other types if specified. - * @function toObject - * @memberof RPC.PeerInfo - * @static - * @param {RPC.PeerInfo} m PeerInfo - * @param {$protobuf.IConversionOptions} [o] Conversion options - * @returns {Object.} Plain object - */ - PeerInfo.toObject = function toObject(m, o) { - if (!o) - o = {}; - var d = {}; - if (m.peerID != null && m.hasOwnProperty("peerID")) { - d.peerID = o.bytes === String ? $util.base64.encode(m.peerID, 0, m.peerID.length) : o.bytes === Array ? Array.prototype.slice.call(m.peerID) : m.peerID; - if (o.oneofs) - d._peerID = "peerID"; - } - if (m.signedPeerRecord != null && m.hasOwnProperty("signedPeerRecord")) { - d.signedPeerRecord = o.bytes === String ? $util.base64.encode(m.signedPeerRecord, 0, m.signedPeerRecord.length) : o.bytes === Array ? Array.prototype.slice.call(m.signedPeerRecord) : m.signedPeerRecord; - if (o.oneofs) - d._signedPeerRecord = "signedPeerRecord"; - } - return d; - }; - - /** - * Converts this PeerInfo to JSON. - * @function toJSON - * @memberof RPC.PeerInfo - * @instance - * @returns {Object.} JSON object - */ - PeerInfo.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return PeerInfo; - })(); - - return RPC; - })(); - - return $root; -}); diff --git a/src/message/rpc.d.ts b/src/message/rpc.d.ts deleted file mode 100644 index 3716d3cc..00000000 --- a/src/message/rpc.d.ts +++ /dev/null @@ -1,666 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Properties of a RPC. */ -export interface IRPC { - - /** RPC subscriptions */ - subscriptions?: (RPC.ISubOpts[]|null); - - /** RPC messages */ - messages?: (RPC.IMessage[]|null); - - /** RPC control */ - control?: (RPC.IControlMessage|null); -} - -/** Represents a RPC. */ -export class RPC implements IRPC { - - /** - * Constructs a new RPC. - * @param [p] Properties to set - */ - constructor(p?: IRPC); - - /** RPC subscriptions. */ - public subscriptions: RPC.ISubOpts[]; - - /** RPC messages. */ - public messages: RPC.IMessage[]; - - /** RPC control. */ - public control?: (RPC.IControlMessage|null); - - /** RPC _control. */ - public _control?: "control"; - - /** - * Encodes the specified RPC message. Does not implicitly {@link RPC.verify|verify} messages. - * @param m RPC message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: IRPC, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a RPC message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns RPC - * @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): RPC; - - /** - * Creates a RPC message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns RPC - */ - public static fromObject(d: { [k: string]: any }): RPC; - - /** - * Creates a plain object from a RPC message. Also converts values to other types if specified. - * @param m RPC - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this RPC to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; -} - -export namespace RPC { - - /** Properties of a SubOpts. */ - interface ISubOpts { - - /** SubOpts subscribe */ - subscribe?: (boolean|null); - - /** SubOpts topic */ - topic?: (string|null); - } - - /** Represents a SubOpts. */ - class SubOpts implements ISubOpts { - - /** - * Constructs a new SubOpts. - * @param [p] Properties to set - */ - constructor(p?: RPC.ISubOpts); - - /** SubOpts subscribe. */ - public subscribe?: (boolean|null); - - /** SubOpts topic. */ - public topic?: (string|null); - - /** SubOpts _subscribe. */ - public _subscribe?: "subscribe"; - - /** SubOpts _topic. */ - public _topic?: "topic"; - - /** - * Encodes the specified SubOpts message. Does not implicitly {@link RPC.SubOpts.verify|verify} messages. - * @param m SubOpts message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.ISubOpts, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a SubOpts message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns SubOpts - * @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): RPC.SubOpts; - - /** - * Creates a SubOpts message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns SubOpts - */ - public static fromObject(d: { [k: string]: any }): RPC.SubOpts; - - /** - * Creates a plain object from a SubOpts message. Also converts values to other types if specified. - * @param m SubOpts - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.SubOpts, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this SubOpts to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a Message. */ - interface IMessage { - - /** Message from */ - from?: (Uint8Array|null); - - /** Message data */ - data?: (Uint8Array|null); - - /** Message seqno */ - seqno?: (Uint8Array|null); - - /** Message topic */ - topic: string; - - /** Message signature */ - signature?: (Uint8Array|null); - - /** Message key */ - key?: (Uint8Array|null); - } - - /** Represents a Message. */ - class Message implements IMessage { - - /** - * Constructs a new Message. - * @param [p] Properties to set - */ - constructor(p?: RPC.IMessage); - - /** Message from. */ - public from?: (Uint8Array|null); - - /** Message data. */ - public data?: (Uint8Array|null); - - /** Message seqno. */ - public seqno?: (Uint8Array|null); - - /** Message topic. */ - public topic: string; - - /** Message signature. */ - public signature?: (Uint8Array|null); - - /** Message key. */ - public key?: (Uint8Array|null); - - /** Message _from. */ - public _from?: "from"; - - /** Message _data. */ - public _data?: "data"; - - /** Message _seqno. */ - public _seqno?: "seqno"; - - /** Message _signature. */ - public _signature?: "signature"; - - /** Message _key. */ - public _key?: "key"; - - /** - * Encodes the specified Message message. Does not implicitly {@link RPC.Message.verify|verify} messages. - * @param m Message message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IMessage, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a Message message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns Message - * @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): RPC.Message; - - /** - * Creates a Message message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns Message - */ - public static fromObject(d: { [k: string]: any }): RPC.Message; - - /** - * Creates a plain object from a Message message. Also converts values to other types if specified. - * @param m Message - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.Message, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Message to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlMessage. */ - interface IControlMessage { - - /** ControlMessage ihave */ - ihave?: (RPC.IControlIHave[]|null); - - /** ControlMessage iwant */ - iwant?: (RPC.IControlIWant[]|null); - - /** ControlMessage graft */ - graft?: (RPC.IControlGraft[]|null); - - /** ControlMessage prune */ - prune?: (RPC.IControlPrune[]|null); - } - - /** Represents a ControlMessage. */ - class ControlMessage implements IControlMessage { - - /** - * Constructs a new ControlMessage. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlMessage); - - /** ControlMessage ihave. */ - public ihave: RPC.IControlIHave[]; - - /** ControlMessage iwant. */ - public iwant: RPC.IControlIWant[]; - - /** ControlMessage graft. */ - public graft: RPC.IControlGraft[]; - - /** ControlMessage prune. */ - public prune: RPC.IControlPrune[]; - - /** - * Encodes the specified ControlMessage message. Does not implicitly {@link RPC.ControlMessage.verify|verify} messages. - * @param m ControlMessage message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlMessage, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlMessage message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlMessage - * @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): RPC.ControlMessage; - - /** - * Creates a ControlMessage message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlMessage - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlMessage; - - /** - * Creates a plain object from a ControlMessage message. Also converts values to other types if specified. - * @param m ControlMessage - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlMessage, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlMessage to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlIHave. */ - interface IControlIHave { - - /** ControlIHave topicID */ - topicID?: (string|null); - - /** ControlIHave messageIDs */ - messageIDs?: (Uint8Array[]|null); - } - - /** Represents a ControlIHave. */ - class ControlIHave implements IControlIHave { - - /** - * Constructs a new ControlIHave. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlIHave); - - /** ControlIHave topicID. */ - public topicID?: (string|null); - - /** ControlIHave messageIDs. */ - public messageIDs: Uint8Array[]; - - /** ControlIHave _topicID. */ - public _topicID?: "topicID"; - - /** - * Encodes the specified ControlIHave message. Does not implicitly {@link RPC.ControlIHave.verify|verify} messages. - * @param m ControlIHave message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlIHave, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlIHave message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlIHave - * @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): RPC.ControlIHave; - - /** - * Creates a ControlIHave message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlIHave - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlIHave; - - /** - * Creates a plain object from a ControlIHave message. Also converts values to other types if specified. - * @param m ControlIHave - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlIHave, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlIHave to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlIWant. */ - interface IControlIWant { - - /** ControlIWant messageIDs */ - messageIDs?: (Uint8Array[]|null); - } - - /** Represents a ControlIWant. */ - class ControlIWant implements IControlIWant { - - /** - * Constructs a new ControlIWant. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlIWant); - - /** ControlIWant messageIDs. */ - public messageIDs: Uint8Array[]; - - /** - * Encodes the specified ControlIWant message. Does not implicitly {@link RPC.ControlIWant.verify|verify} messages. - * @param m ControlIWant message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlIWant, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlIWant message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlIWant - * @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): RPC.ControlIWant; - - /** - * Creates a ControlIWant message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlIWant - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlIWant; - - /** - * Creates a plain object from a ControlIWant message. Also converts values to other types if specified. - * @param m ControlIWant - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlIWant, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlIWant to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlGraft. */ - interface IControlGraft { - - /** ControlGraft topicID */ - topicID?: (string|null); - } - - /** Represents a ControlGraft. */ - class ControlGraft implements IControlGraft { - - /** - * Constructs a new ControlGraft. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlGraft); - - /** ControlGraft topicID. */ - public topicID?: (string|null); - - /** ControlGraft _topicID. */ - public _topicID?: "topicID"; - - /** - * Encodes the specified ControlGraft message. Does not implicitly {@link RPC.ControlGraft.verify|verify} messages. - * @param m ControlGraft message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlGraft, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlGraft message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlGraft - * @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): RPC.ControlGraft; - - /** - * Creates a ControlGraft message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlGraft - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlGraft; - - /** - * Creates a plain object from a ControlGraft message. Also converts values to other types if specified. - * @param m ControlGraft - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlGraft, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlGraft to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a ControlPrune. */ - interface IControlPrune { - - /** ControlPrune topicID */ - topicID?: (string|null); - - /** ControlPrune peers */ - peers?: (RPC.IPeerInfo[]|null); - - /** ControlPrune backoff */ - backoff?: (number|null); - } - - /** Represents a ControlPrune. */ - class ControlPrune implements IControlPrune { - - /** - * Constructs a new ControlPrune. - * @param [p] Properties to set - */ - constructor(p?: RPC.IControlPrune); - - /** ControlPrune topicID. */ - public topicID?: (string|null); - - /** ControlPrune peers. */ - public peers: RPC.IPeerInfo[]; - - /** ControlPrune backoff. */ - public backoff?: (number|null); - - /** ControlPrune _topicID. */ - public _topicID?: "topicID"; - - /** ControlPrune _backoff. */ - public _backoff?: "backoff"; - - /** - * Encodes the specified ControlPrune message. Does not implicitly {@link RPC.ControlPrune.verify|verify} messages. - * @param m ControlPrune message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IControlPrune, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a ControlPrune message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns ControlPrune - * @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): RPC.ControlPrune; - - /** - * Creates a ControlPrune message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns ControlPrune - */ - public static fromObject(d: { [k: string]: any }): RPC.ControlPrune; - - /** - * Creates a plain object from a ControlPrune message. Also converts values to other types if specified. - * @param m ControlPrune - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.ControlPrune, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this ControlPrune to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } - - /** Properties of a PeerInfo. */ - interface IPeerInfo { - - /** PeerInfo peerID */ - peerID?: (Uint8Array|null); - - /** PeerInfo signedPeerRecord */ - signedPeerRecord?: (Uint8Array|null); - } - - /** Represents a PeerInfo. */ - class PeerInfo implements IPeerInfo { - - /** - * Constructs a new PeerInfo. - * @param [p] Properties to set - */ - constructor(p?: RPC.IPeerInfo); - - /** PeerInfo peerID. */ - public peerID?: (Uint8Array|null); - - /** PeerInfo signedPeerRecord. */ - public signedPeerRecord?: (Uint8Array|null); - - /** PeerInfo _peerID. */ - public _peerID?: "peerID"; - - /** PeerInfo _signedPeerRecord. */ - public _signedPeerRecord?: "signedPeerRecord"; - - /** - * Encodes the specified PeerInfo message. Does not implicitly {@link RPC.PeerInfo.verify|verify} messages. - * @param m PeerInfo message or plain object to encode - * @param [w] Writer to encode to - * @returns Writer - */ - public static encode(m: RPC.IPeerInfo, w?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a PeerInfo message from the specified reader or buffer. - * @param r Reader or buffer to decode from - * @param [l] Message length if known beforehand - * @returns PeerInfo - * @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): RPC.PeerInfo; - - /** - * Creates a PeerInfo message from a plain object. Also converts values to their respective internal types. - * @param d Plain object - * @returns PeerInfo - */ - public static fromObject(d: { [k: string]: any }): RPC.PeerInfo; - - /** - * Creates a plain object from a PeerInfo message. Also converts values to other types if specified. - * @param m PeerInfo - * @param [o] Conversion options - * @returns Plain object - */ - public static toObject(m: RPC.PeerInfo, o?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this PeerInfo to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } -} diff --git a/src/message/rpc.js b/src/message/rpc.js deleted file mode 100644 index b569a782..00000000 --- a/src/message/rpc.js +++ /dev/null @@ -1,3 +0,0 @@ -import cjs from "./rpc.cjs" - -export const {RPC} = cjs diff --git a/src/message/rpc.proto b/src/message/rpc.proto index 4e09b4eb..daefc676 100644 --- a/src/message/rpc.proto +++ b/src/message/rpc.proto @@ -14,7 +14,7 @@ message RPC { optional bytes from = 1; optional bytes data = 2; optional bytes seqno = 3; - required string topic = 4; + string topic = 4; optional bytes signature = 5; optional bytes key = 6; } @@ -42,7 +42,7 @@ message RPC { message ControlPrune { optional string topicID = 1; repeated PeerInfo peers = 2; - optional uint64 backoff = 3; + optional uint64 backoff = 3 [jstype = JS_NUMBER]; } message PeerInfo { diff --git a/src/message/rpc.ts b/src/message/rpc.ts new file mode 100644 index 00000000..a059f31c --- /dev/null +++ b/src/message/rpc.ts @@ -0,0 +1,715 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { encodeMessage, decodeMessage, message } from 'protons-runtime' +import type { Codec } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface RPC { + subscriptions: RPC.SubOpts[] + messages: RPC.Message[] + control?: RPC.ControlMessage +} + +export namespace RPC { + export interface SubOpts { + subscribe?: boolean + topic?: string + } + + export namespace SubOpts { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.subscribe != null) { + w.uint32(8) + w.bool(obj.subscribe) + } + + if (obj.topic != null) { + w.uint32(18) + w.string(obj.topic) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.subscribe = reader.bool() + break + } + case 2: { + obj.topic = reader.string() + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, SubOpts.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): SubOpts => { + return decodeMessage(buf, SubOpts.codec()) + } + } + + export interface Message { + from?: Uint8Array + data?: Uint8Array + seqno?: Uint8Array + topic: string + signature?: Uint8Array + key?: Uint8Array + } + + export namespace Message { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.from != null) { + w.uint32(10) + w.bytes(obj.from) + } + + if (obj.data != null) { + w.uint32(18) + w.bytes(obj.data) + } + + if (obj.seqno != null) { + w.uint32(26) + w.bytes(obj.seqno) + } + + if ((obj.topic != null && obj.topic !== '')) { + w.uint32(34) + w.string(obj.topic) + } + + if (obj.signature != null) { + w.uint32(42) + w.bytes(obj.signature) + } + + if (obj.key != null) { + w.uint32(50) + w.bytes(obj.key) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + topic: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.from = reader.bytes() + break + } + case 2: { + obj.data = reader.bytes() + break + } + case 3: { + obj.seqno = reader.bytes() + break + } + case 4: { + obj.topic = reader.string() + break + } + case 5: { + obj.signature = reader.bytes() + break + } + case 6: { + obj.key = reader.bytes() + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, Message.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { + return decodeMessage(buf, Message.codec()) + } + } + + export interface ControlMessage { + ihave: RPC.ControlIHave[] + iwant: RPC.ControlIWant[] + graft: RPC.ControlGraft[] + prune: RPC.ControlPrune[] + } + + export namespace ControlMessage { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.ihave != null) { + for (const value of obj.ihave) { + w.uint32(10) + RPC.ControlIHave.codec().encode(value, w) + } + } + + if (obj.iwant != null) { + for (const value of obj.iwant) { + w.uint32(18) + RPC.ControlIWant.codec().encode(value, w) + } + } + + if (obj.graft != null) { + for (const value of obj.graft) { + w.uint32(26) + RPC.ControlGraft.codec().encode(value, w) + } + } + + if (obj.prune != null) { + for (const value of obj.prune) { + w.uint32(34) + RPC.ControlPrune.codec().encode(value, w) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + ihave: [], + iwant: [], + graft: [], + prune: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.ihave.push(RPC.ControlIHave.codec().decode(reader, reader.uint32())) + break + } + case 2: { + obj.iwant.push(RPC.ControlIWant.codec().decode(reader, reader.uint32())) + break + } + case 3: { + obj.graft.push(RPC.ControlGraft.codec().decode(reader, reader.uint32())) + break + } + case 4: { + obj.prune.push(RPC.ControlPrune.codec().decode(reader, reader.uint32())) + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, ControlMessage.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): ControlMessage => { + return decodeMessage(buf, ControlMessage.codec()) + } + } + + export interface ControlIHave { + topicID?: string + messageIDs: Uint8Array[] + } + + export namespace ControlIHave { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.topicID != null) { + w.uint32(10) + w.string(obj.topicID) + } + + if (obj.messageIDs != null) { + for (const value of obj.messageIDs) { + w.uint32(18) + w.bytes(value) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + messageIDs: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.topicID = reader.string() + break + } + case 2: { + obj.messageIDs.push(reader.bytes()) + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, ControlIHave.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): ControlIHave => { + return decodeMessage(buf, ControlIHave.codec()) + } + } + + export interface ControlIWant { + messageIDs: Uint8Array[] + } + + export namespace ControlIWant { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.messageIDs != null) { + for (const value of obj.messageIDs) { + w.uint32(10) + w.bytes(value) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + messageIDs: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.messageIDs.push(reader.bytes()) + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, ControlIWant.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): ControlIWant => { + return decodeMessage(buf, ControlIWant.codec()) + } + } + + export interface ControlGraft { + topicID?: string + } + + export namespace ControlGraft { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.topicID != null) { + w.uint32(10) + w.string(obj.topicID) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.topicID = reader.string() + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, ControlGraft.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): ControlGraft => { + return decodeMessage(buf, ControlGraft.codec()) + } + } + + export interface ControlPrune { + topicID?: string + peers: RPC.PeerInfo[] + backoff?: number + } + + export namespace ControlPrune { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.topicID != null) { + w.uint32(10) + w.string(obj.topicID) + } + + if (obj.peers != null) { + for (const value of obj.peers) { + w.uint32(18) + RPC.PeerInfo.codec().encode(value, w) + } + } + + if (obj.backoff != null) { + w.uint32(24) + w.uint64Number(obj.backoff) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + peers: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.topicID = reader.string() + break + } + case 2: { + obj.peers.push(RPC.PeerInfo.codec().decode(reader, reader.uint32())) + break + } + case 3: { + obj.backoff = reader.uint64Number() + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, ControlPrune.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): ControlPrune => { + return decodeMessage(buf, ControlPrune.codec()) + } + } + + export interface PeerInfo { + peerID?: Uint8Array + signedPeerRecord?: Uint8Array + } + + export namespace PeerInfo { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.peerID != null) { + w.uint32(10) + w.bytes(obj.peerID) + } + + if (obj.signedPeerRecord != null) { + w.uint32(18) + w.bytes(obj.signedPeerRecord) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = {} + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.peerID = reader.bytes() + break + } + case 2: { + obj.signedPeerRecord = reader.bytes() + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, PeerInfo.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): PeerInfo => { + return decodeMessage(buf, PeerInfo.codec()) + } + } + + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.subscriptions != null) { + for (const value of obj.subscriptions) { + w.uint32(10) + RPC.SubOpts.codec().encode(value, w) + } + } + + if (obj.messages != null) { + for (const value of obj.messages) { + w.uint32(18) + RPC.Message.codec().encode(value, w) + } + } + + if (obj.control != null) { + w.uint32(26) + RPC.ControlMessage.codec().encode(obj.control, w) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length) => { + const obj: any = { + subscriptions: [], + messages: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.subscriptions.push(RPC.SubOpts.codec().decode(reader, reader.uint32())) + break + } + case 2: { + obj.messages.push(RPC.Message.codec().decode(reader, reader.uint32())) + break + } + case 3: { + obj.control = RPC.ControlMessage.codec().decode(reader, reader.uint32()) + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, RPC.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList): RPC => { + return decodeMessage(buf, RPC.codec()) + } +} diff --git a/src/metrics.ts b/src/metrics.ts index 28b12f27..d098f03a 100644 --- a/src/metrics.ts +++ b/src/metrics.ts @@ -7,7 +7,7 @@ import { type TopicStr, type ValidateError } from './types.js' -import type { IRPC } from './message/rpc.js' +import type { RPC } from './message/rpc.js' import type { PeerScoreThresholds } from './score/peer-score-thresholds.js' /** Topic label as provided in `topicStrToLabel` */ @@ -893,7 +893,7 @@ export function getMetrics ( this.rpcDataError.inc(1) }, - onRpcRecv (rpc: IRPC, rpcBytes: number): void { + onRpcRecv (rpc: RPC, rpcBytes: number): void { this.rpcRecvBytes.inc(rpcBytes) this.rpcRecvCount.inc(1) if (rpc.subscriptions != null) this.rpcRecvSubscription.inc(rpc.subscriptions.length) @@ -907,7 +907,7 @@ export function getMetrics ( } }, - onRpcSent (rpc: IRPC, rpcBytes: number): void { + onRpcSent (rpc: RPC, rpcBytes: number): void { this.rpcSentBytes.inc(rpcBytes) this.rpcSentCount.inc(1) if (rpc.subscriptions != null) this.rpcSentSubscription.inc(rpc.subscriptions.length) diff --git a/src/types.ts b/src/types.ts index c1c623c4..d2b33b3b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,7 +16,7 @@ export interface AddrInfo { * Compute a local non-spec'ed msg-id for faster de-duplication of seen messages. * Used exclusively for a local seen_cache */ -export interface FastMsgIdFn { (msg: RPC.IMessage): string | number } +export interface FastMsgIdFn { (msg: RPC.Message): string | number } /** * By default, gossipsub only provide a browser friendly function to convert Uint8Array message id to string. diff --git a/src/utils/buildRawMessage.ts b/src/utils/buildRawMessage.ts index bb1b870b..71b40072 100644 --- a/src/utils/buildRawMessage.ts +++ b/src/utils/buildRawMessage.ts @@ -12,7 +12,7 @@ import { type PublishConfig, PublishConfigType, type TopicStr, ValidateError } f export const SignPrefix = uint8ArrayFromString('libp2p-pubsub:') export interface RawMessageAndMessage { - raw: RPC.IMessage + raw: RPC.Message msg: Message } @@ -24,7 +24,7 @@ export async function buildRawMessage ( ): Promise { switch (publishConfig.type) { case PublishConfigType.Signing: { - const rpcMsg: RPC.IMessage = { + const rpcMsg: RPC.Message = { from: publishConfig.author.toBytes(), data: transformedData, seqno: randomBytes(8), @@ -35,7 +35,7 @@ export async function buildRawMessage ( // Get the message in bytes, and prepend with the pubsub prefix // the signature is over the bytes "libp2p-pubsub:" - const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsg).finish()]) + const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsg)]) rpcMsg.signature = await publishConfig.privateKey.sign(bytes) rpcMsg.key = publishConfig.key @@ -82,7 +82,7 @@ export type ValidationResult = { valid: true, message: Message } | { valid: fals export async function validateToRawMessage ( signaturePolicy: typeof StrictNoSign | typeof StrictSign, - msg: RPC.IMessage + msg: RPC.Message ): Promise { // If strict-sign, verify all // If anonymous (no-sign), ensure no preven @@ -134,7 +134,7 @@ export async function validateToRawMessage ( publicKey = unmarshalPublicKey(fromPeerId.publicKey) } - const rpcMsgPreSign: RPC.IMessage = { + const rpcMsgPreSign: RPC.Message = { from: msg.from, data: msg.data, seqno: msg.seqno, @@ -145,7 +145,7 @@ export async function validateToRawMessage ( // Get the message in bytes, and prepend with the pubsub prefix // the signature is over the bytes "libp2p-pubsub:" - const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsgPreSign).finish()]) + const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsgPreSign)]) if (!(await publicKey.verify(bytes, msg.signature))) { return { valid: false, error: ValidateError.InvalidSignature } diff --git a/src/utils/create-gossip-rpc.ts b/src/utils/create-gossip-rpc.ts new file mode 100644 index 00000000..93a80dc6 --- /dev/null +++ b/src/utils/create-gossip-rpc.ts @@ -0,0 +1,32 @@ +import type { RPC } from '../message/rpc.js' + +/** + * Create a gossipsub RPC object + */ +export function createGossipRpc(messages: RPC.Message[] = [], control?: Partial): RPC { + return { + subscriptions: [], + messages, + control: control + ? { + graft: control.graft || [], + prune: control.prune || [], + ihave: control.ihave || [], + iwant: control.iwant || [] + } + : undefined + } +} + +export function ensureControl(rpc: RPC): Required { + if (!rpc.control) { + rpc.control = { + graft: [], + prune: [], + ihave: [], + iwant: [] + } + } + + return rpc as Required +} diff --git a/test/benchmark/protobuf.test.ts b/test/benchmark/protobuf.test.ts index ca3912e4..0b373e4a 100644 --- a/test/benchmark/protobuf.test.ts +++ b/test/benchmark/protobuf.test.ts @@ -1,15 +1,14 @@ import { itBench, setBenchOpts } from '@dapplion/benchmark' -import { type IRPC, RPC } from '../../src/message/rpc.js' +import { RPC } from '../../src/message/rpc.js' describe('protobuf', function () { this.timeout(0) setBenchOpts({ maxMs: 200 * 1000, - minMs: 120 * 1000, - minRuns: 200 + minMs: 120 * 1000 }) - const rpc: IRPC = { + const rpc: RPC = { subscriptions: [], messages: [ { @@ -25,12 +24,12 @@ describe('protobuf', function () { control: undefined } - const bytes = RPC.encode(rpc).finish() + const bytes = RPC.encode(rpc) const runsFactor = 1000 itBench({ - id: 'decode Attestation message using protobufjs', + id: 'decode Attestation message using protons-runtime 5.1.0', fn: () => { for (let i = 0; i < runsFactor; i++) { RPC.decode(bytes) @@ -40,10 +39,10 @@ describe('protobuf', function () { }) itBench({ - id: 'encode Attestation message using protobufjs', + id: 'encode Attestation message using protons-runtime 5.1.0', fn: () => { for (let i = 0; i < runsFactor; i++) { - RPC.encode(rpc).finish() + RPC.encode(rpc) } }, runsFactor diff --git a/test/decodeRpc.spec.ts b/test/decodeRpc.spec.ts index d43535b7..f5c5f813 100644 --- a/test/decodeRpc.spec.ts +++ b/test/decodeRpc.spec.ts @@ -1,22 +1,22 @@ import { expect } from 'aegir/chai' import { decodeRpc, type DecodeRPCLimits, defaultDecodeRpcLimits } from '../src/message/decodeRpc.js' -import { RPC, type IRPC } from '../src/message/index.js' +import { RPC } from '../src/message/index.js' describe('decodeRpc', () => { const topicID = 'topic' const msgID = new Uint8Array(8) - const subscription: RPC.ISubOpts = { subscribe: true, topic: topicID } - const message: RPC.IMessage = { topic: topicID, data: new Uint8Array(100) } - const peerInfo: RPC.IPeerInfo = { peerID: msgID, signedPeerRecord: msgID } - const ihave: RPC.IControlIHave = { topicID, messageIDs: [msgID] } - const iwant: RPC.IControlIWant = { messageIDs: [msgID] } - const graft: RPC.IControlGraft = { topicID } - const prune: RPC.IControlPrune = { topicID, peers: [peerInfo] } + const subscription: RPC.SubOpts = { subscribe: true, topic: topicID } + const message: RPC.Message = { topic: topicID, data: new Uint8Array(100) } + const peerInfo: RPC.PeerInfo = { peerID: msgID, signedPeerRecord: msgID } + const ihave: RPC.ControlIHave = { topicID, messageIDs: [msgID] } + const iwant: RPC.ControlIWant = { messageIDs: [msgID] } + const graft: RPC.ControlGraft = { topicID } + const prune: RPC.ControlPrune = { topicID, peers: [peerInfo] } describe('decode correctness', () => { it('Should decode full RPC', () => { - const rpc: IRPC = { + const rpc: RPC = { subscriptions: [subscription, subscription], messages: [message, message], control: { @@ -27,10 +27,10 @@ describe('decodeRpc', () => { } } - const bytes = RPC.encode(rpc).finish() + const bytes = RPC.encode(rpc) // Compare as JSON - expect(RPC.fromObject(decodeRpc(bytes, defaultDecodeRpcLimits)).toJSON()).deep.equals(RPC.decode(bytes).toJSON()) + expect(decodeRpc(bytes, defaultDecodeRpcLimits)).to.be.deep.equals(RPC.decode(bytes)) }) }) @@ -51,7 +51,7 @@ describe('decodeRpc', () => { expect(limitsAfter).deep.equals(decodeRpcLimits) }) - const rpcEmpty: IRPC = { + const rpcEmpty: RPC = { subscriptions: [], messages: [], control: { @@ -62,7 +62,7 @@ describe('decodeRpc', () => { } } - const rpcEmptyBytes = RPC.encode(rpcEmpty).finish() + const rpcEmptyBytes = RPC.encode(rpcEmpty) it('limit subscriptions.length', () => { // Decode a fresh instance to allow safe mutations @@ -79,32 +79,38 @@ describe('decodeRpc', () => { it('limit control.ihave.length', () => { const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { ihave: [ihave, ihave, ihave] } + rpc.control = { ihave: [ihave, ihave, ihave], iwant: [], graft: [], prune: [] } expect(endecode(rpc).control?.ihave).length(decodeRpcLimits.maxControlMessages) }) it('limit control.iwant.length', () => { const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { iwant: [iwant, iwant, iwant] } + // rpc.control = { iwant: [iwant, iwant, iwant], ihave: [], graft: [], prune: [] } + rpc.control?.iwant.push(...[iwant, iwant, iwant]) expect(endecode(rpc).control?.iwant).length(decodeRpcLimits.maxControlMessages) }) it('limit control.graft.length', () => { const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { graft: [graft, graft, graft] } + rpc.control = { graft: [graft, graft, graft], ihave: [], iwant: [], prune: [] } expect(endecode(rpc).control?.graft).length(decodeRpcLimits.maxControlMessages) }) it('limit control.prune.length', () => { const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { prune: [prune, prune, prune] } + rpc.control = { prune: [prune, prune, prune], ihave: [], iwant: [], graft: [] } expect(endecode(rpc).control?.prune).length(decodeRpcLimits.maxControlMessages) }) it('limit ihave.messageIDs.length', () => { const rpc = RPC.decode(rpcEmptyBytes) // Limit to 3 items total, 2 (all) on the first one, 1 on the second one - rpc.control = { ihave: [{ messageIDs: [msgID, msgID] }, { messageIDs: [msgID, msgID] }] } + rpc.control = { + ihave: [{ messageIDs: [msgID, msgID] }, { messageIDs: [msgID, msgID] }], + iwant: [], + graft: [], + prune: [] + } expect(decodeRpcLimits.maxIhaveMessageIDs).equals(3, 'Wrong maxIhaveMessageIDs') expect(endecode(rpc).control?.ihave?.[0].messageIDs).length(2, 'Wrong ihave?.[0].messageIDs len') expect(endecode(rpc).control?.ihave?.[1].messageIDs).length(1, 'Wrong ihave?.[1].messageIDs len') @@ -113,7 +119,12 @@ describe('decodeRpc', () => { it('limit iwant.messageIDs.length', () => { const rpc = RPC.decode(rpcEmptyBytes) // Limit to 3 items total, 2 (all) on the first one, 1 on the second one - rpc.control = { iwant: [{ messageIDs: [msgID, msgID] }, { messageIDs: [msgID, msgID] }] } + rpc.control = { + iwant: [{ messageIDs: [msgID, msgID] }, { messageIDs: [msgID, msgID] }], + ihave: [], + graft: [], + prune: [] + } expect(decodeRpcLimits.maxIwantMessageIDs).equals(3, 'Wrong maxIwantMessageIDs') expect(endecode(rpc).control?.iwant?.[0].messageIDs).length(2, 'Wrong iwant?.[0].messageIDs len') expect(endecode(rpc).control?.iwant?.[1].messageIDs).length(1, 'Wrong iwant?.[1].messageIDs len') @@ -122,14 +133,19 @@ describe('decodeRpc', () => { it('limit prune.peers.length', () => { const rpc = RPC.decode(rpcEmptyBytes) // Limit to 3 items total, 2 (all) on the first one, 1 on the second one - rpc.control = { prune: [{ peers: [peerInfo, peerInfo] }, { peers: [peerInfo, peerInfo] }] } + rpc.control = { + prune: [{ peers: [peerInfo, peerInfo] }, { peers: [peerInfo, peerInfo] }], + ihave: [], + iwant: [], + graft: [] + } expect(decodeRpcLimits.maxPeerInfos).equals(3, 'Wrong maxPeerInfos') expect(endecode(rpc).control?.prune?.[0].peers).length(2, 'Wrong prune?.[0].peers len') expect(endecode(rpc).control?.prune?.[1].peers).length(1, 'Wrong prune?.[1].peers len') }) - function endecode (rpc: IRPC): IRPC { - return decodeRpc(RPC.encode(rpc).finish(), decodeRpcLimits) + function endecode(rpc: RPC): RPC { + return decodeRpc(RPC.encode(rpc), decodeRpcLimits) } }) }) diff --git a/test/e2e/go-gossipsub.spec.ts b/test/e2e/go-gossipsub.spec.ts index 776b992c..ebb81e87 100644 --- a/test/e2e/go-gossipsub.spec.ts +++ b/test/e2e/go-gossipsub.spec.ts @@ -20,7 +20,7 @@ import { } from '../utils/create-pubsub.js' import { awaitEvents, checkReceivedSubscription, checkReceivedSubscriptions } from '../utils/events.js' import { fastMsgIdFn } from '../utils/index.js' -import type { IRPC, RPC } from '../../src/message/rpc.js' +import type { RPC } from '../../src/message/rpc.js' import type { TopicScoreParams } from '../../src/score/peer-score-params.js' /** @@ -1189,13 +1189,13 @@ describe('go-libp2p-pubsub gossipsub tests', function () { psub.mesh.set(topic1, new Set([otherId])) psub.mesh.set(topic2, new Set()) - const rpc: IRPC = { + const rpc: RPC = { subscriptions: [], messages: [] } - const toGraft = (topicID: string): RPC.IControlGraft => ({ topicID }) - const toPrune = (topicID: string): RPC.IControlPrune => ({ topicID, peers: [] }) + const toGraft = (topicID: string): RPC.ControlGraft => ({ topicID }) + const toPrune = (topicID: string): RPC.ControlPrune => ({ topicID, peers: [] }) psub.piggybackControl(otherId, rpc, { graft: [toGraft(topic1), toGraft(topic2), toGraft(topic3)], @@ -1204,12 +1204,14 @@ describe('go-libp2p-pubsub gossipsub tests', function () { iwant: [] }) - const expectedRpc: IRPC = { + const expectedRpc: RPC = { subscriptions: [], messages: [], control: { graft: [toGraft(topic1)], - prune: [toPrune(topic2), toPrune(topic3)] + prune: [toPrune(topic2), toPrune(topic3)], + ihave: [], + iwant: [] } } diff --git a/test/message-cache.spec.ts b/test/message-cache.spec.ts index 59da7b3d..e3f76d15 100644 --- a/test/message-cache.spec.ts +++ b/test/message-cache.spec.ts @@ -16,7 +16,7 @@ const toMessageId = (msgId: Uint8Array): MessageId => { describe('Testing Message Cache Operations', () => { const messageCache = new MessageCache(3, 5, messageIdToString) - const testMessages: RPC.IMessage[] = [] + const testMessages: RPC.Message[] = [] const topic = 'test' const getGossipIDs = (mcache: MessageCache, topic: string): Uint8Array[] => { const gossipIDsByTopic = mcache.getGossipIDs(new Set([topic])) @@ -24,7 +24,7 @@ describe('Testing Message Cache Operations', () => { } before(async () => { - const makeTestMessage = (n: number): RPC.IMessage => { + const makeTestMessage = (n: number): RPC.Message => { return { from: new Uint8Array(0), data: uint8ArrayFromString(n.toString()), diff --git a/test/utils/index.ts b/test/utils/index.ts index 82ed1770..a26fb119 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -15,7 +15,11 @@ export const createPeerId = async (): Promise => { let seq = 0n const defaultPeer = uint8ArrayFromString('12D3KooWBsYhazxNL7aeisdwttzc6DejNaM48889t5ifiS6tTrBf', 'base58btc') +<<<<<<< HEAD export function makeTestMessage (i: number, topic: TopicStr, from?: PeerId): RPC.IMessage { +======= +export function makeTestMessage(i: number, topic: TopicStr, from?: PeerId): RPC.Message { +>>>>>>> b433f8e (feat: protons 7.2.1 protons-runtime 5.1.0) return { seqno: uint8ArrayFromString((seq++).toString(16).padStart(16, '0'), 'base16'), data: Uint8Array.from([i]), diff --git a/test/utils/msgId.ts b/test/utils/msgId.ts index 1d603345..992f4a18 100644 --- a/test/utils/msgId.ts +++ b/test/utils/msgId.ts @@ -3,7 +3,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { messageIdToString } from '../../src/utils/messageIdToString.js' import type { RPC } from '../../src/message/rpc.js' -export const getMsgId = (msg: RPC.IMessage): Uint8Array => { +export const getMsgId = (msg: RPC.Message): Uint8Array => { const from = msg.from != null ? msg.from : new Uint8Array(0) const seqno = msg.seqno instanceof Uint8Array ? msg.seqno : uint8ArrayFromString(msg.seqno ?? '') const result = new Uint8Array(from.length + seqno.length) @@ -12,8 +12,8 @@ export const getMsgId = (msg: RPC.IMessage): Uint8Array => { return result } -export const getMsgIdStr = (msg: RPC.IMessage): string => messageIdToString(getMsgId(msg)) +export const getMsgIdStr = (msg: RPC.Message): string => messageIdToString(getMsgId(msg)) -export const fastMsgIdFn = (msg: RPC.IMessage): string => +export const fastMsgIdFn = (msg: RPC.Message): string => // eslint-disable-next-line @typescript-eslint/ban-ts-comment msg.data != null ? messageIdToString(digest(msg.data)) : '0' diff --git a/tsconfig.json b/tsconfig.json index 10e6081b..13e1e3bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,8 +5,6 @@ "noImplicitReturns": true, "outDir": "dist", "useUnknownInCatchVariables": true, - "allowJs": false, - "checkJs": false }, "include": [ "src", From be83d945eb087dcab854b44050124c0a46f451f2 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Fri, 27 Oct 2023 15:50:35 +0700 Subject: [PATCH 2/9] fix: remove protobufjs dependency --- package-lock.json | 43 +++++++++++++++++++++++++++++++++---------- package.json | 1 - 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index d0fa94be..ea3bf9d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3079,27 +3079,37 @@ "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "peer": true }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "peer": true }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true, + "peer": true }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "peer": true }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "peer": true, "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -3108,27 +3118,37 @@ "node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "peer": true }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true, + "peer": true }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "peer": true }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "peer": true }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true, + "peer": true }, "node_modules/@samverschueren/stream-to-observable": { "version": "0.3.1", @@ -11961,7 +11981,9 @@ "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "dev": true, + "peer": true }, "node_modules/longest-streak": { "version": "3.1.0", @@ -19187,6 +19209,7 @@ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", "hasInstallScript": true, + "peer": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", diff --git a/package.json b/package.json index c6e3f7c1..93801aec 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,6 @@ "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "multiformats": "^13.0.1", - "protobufjs": "^7.2.6", "protons-runtime": "^5.1.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.1" From e9ae0285c22b25a772bea110e656b1437a8b011b Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Tue, 31 Oct 2023 13:51:53 +0700 Subject: [PATCH 3/9] chore: bechmark messages at different lengths --- test/benchmark/protobuf.test.ts | 81 ++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/test/benchmark/protobuf.test.ts b/test/benchmark/protobuf.test.ts index 0b373e4a..f0a514af 100644 --- a/test/benchmark/protobuf.test.ts +++ b/test/benchmark/protobuf.test.ts @@ -1,3 +1,4 @@ +import crypto from 'node:crypto' import { itBench, setBenchOpts } from '@dapplion/benchmark' import { RPC } from '../../src/message/rpc.js' @@ -5,46 +6,54 @@ describe('protobuf', function () { this.timeout(0) setBenchOpts({ maxMs: 200 * 1000, - minMs: 120 * 1000 + minMs: 60 * 1000 }) - const rpc: RPC = { - subscriptions: [], - messages: [ - { - topic: 'topic1', - // typical Attestation - data: Buffer.from( - 'e40000000a000000000000000a00000000000000a45c8daa336e17a150300afd4c717313c84f291754c51a378f20958083c5fa070a00000000000000a45c8daa336e17a150300afd4c717313c84f291754c51a378f20958083c5fa070a00000000000000a45c8daa336e17a150300afd4c717313c84f291754c51a378f20958083c5fa0795d2ef8ae4e2b4d1e5b3d5ce47b518e3db2c8c4d082e4498805ac2a686c69f248761b78437db2927470c1e77ede9c18606110faacbcbe4f13052bde7f7eff6aab09edf7bc4929fda2230f943aba2c47b6f940d350cb20c76fad4a8d40e2f3f1f01', - 'hex' - ), - signature: Uint8Array.from(Array.from({ length: 96 }, () => 100)) - } - ], - control: undefined - } + const testCases: { name: string; length: number }[] = [ + // As of Oct 2023, Attestation length = 281 + { name: 'Attestation', length: 300 }, + // A SignedBeaconBlock could be from 70_000 to 300_000 + { name: 'SignedBeaconBlock', length: 70_000 }, + { name: 'SignedBeaconBlock', length: 140_000 }, + { name: 'SignedBeaconBlock', length: 210_000 }, + { name: 'SignedBeaconBlock', length: 280_000 } + ] - const bytes = RPC.encode(rpc) + for (const { name, length } of testCases) { + const rpc: RPC = { + subscriptions: [], + messages: [ + { + topic: 'topic1', + data: crypto.randomBytes(length), + signature: Uint8Array.from(Array.from({ length: 96 }, () => 100)) + } + ], + control: undefined + } - const runsFactor = 1000 + const bytes = RPC.encode(rpc) - itBench({ - id: 'decode Attestation message using protons-runtime 5.1.0', - fn: () => { - for (let i = 0; i < runsFactor; i++) { - RPC.decode(bytes) - } - }, - runsFactor - }) + const runsFactor = 1000 - itBench({ - id: 'encode Attestation message using protons-runtime 5.1.0', - fn: () => { - for (let i = 0; i < runsFactor; i++) { - RPC.encode(rpc) - } - }, - runsFactor - }) + itBench({ + id: `decode ${name} message ${length} bytes`, + fn: () => { + for (let i = 0; i < runsFactor; i++) { + RPC.decode(bytes) + } + }, + runsFactor + }) + + itBench({ + id: `encode ${name} message ${length} bytes`, + fn: () => { + for (let i = 0; i < runsFactor; i++) { + RPC.encode(rpc) + } + }, + runsFactor + }) + } }) From 7914a545fb7cb64bfbbb975016b6e91913876cb6 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Wed, 31 Jan 2024 13:12:46 +0700 Subject: [PATCH 4/9] fix: rebase errors and lint --- package-lock.json | 389 +++++++++++++++++++++++++++++--- src/index.ts | 45 ++-- src/message/decodeRpc.ts | 12 +- src/message/rpc.ts | 3 +- src/utils/create-gossip-rpc.ts | 16 +- test/benchmark/protobuf.test.ts | 2 +- test/decodeRpc.spec.ts | 2 +- test/utils/index.ts | 6 +- 8 files changed, 391 insertions(+), 84 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea3bf9d7..80a6a549 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "multiformats": "^13.0.1", - "protobufjs": "^7.2.6", + "protons-runtime": "^5.1.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.1" }, @@ -42,6 +42,7 @@ "p-event": "^6.0.0", "p-retry": "^6.2.0", "p-wait-for": "^5.0.2", + "protons": "^7.2.1", "sinon": "^17.0.1", "time-cache": "^0.3.0", "ts-sinon": "^2.0.2" @@ -2568,6 +2569,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.7.tgz", + "integrity": "sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@libp2p/crypto": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-4.0.1.tgz", @@ -3996,6 +4009,7 @@ "version": "20.11.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz", "integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==", + "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -4494,6 +4508,18 @@ "source-map-js": "^1.0.2" } }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/@vue/compiler-dom": { "version": "3.4.15", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", @@ -4819,15 +4845,6 @@ "ajv": ">=5.0.0" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "extraneous": true, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", @@ -5343,6 +5360,12 @@ "node": ">=10.0.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -7770,18 +7793,6 @@ "once": "^1.4.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", @@ -11344,25 +11355,51 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", - "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, "engines": { "node": ">=12.0.0" } }, - "node_modules/jsdoc/node_modules/@babel/parser": { - "version": "7.22.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", - "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, "engines": { - "node": ">=6.0.0" + "node": ">=12.0.0" } }, "node_modules/jsdoc/node_modules/escape-string-regexp": { @@ -12424,6 +12461,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, "node_modules/meow": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", @@ -19208,6 +19251,7 @@ "version": "7.2.6", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", + "dev": true, "hasInstallScript": true, "peer": true, "dependencies": { @@ -19228,6 +19272,268 @@ "node": ">=12.0.0" } }, + "node_modules/protobufjs-cli": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.2.tgz", + "integrity": "sha512-8ivXWxT39gZN4mm4ArQyJrRgnIwZqffBWoLDsE21TmMcKI3XwJMV4lEF2WU02C4JAtgYYc2SfJIltelD8to35g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/protobufjs-cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/protobufjs-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/protobufjs-cli/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/protobufjs-cli/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/protobufjs-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/protobufjs-cli/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs-cli/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs-cli/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/protobufjs-cli/node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/protobufjs-cli/node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/protobufjs-cli/node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/protobufjs-cli/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protobufjs-cli/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/protobufjs-cli/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/protons": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/protons/-/protons-7.3.4.tgz", + "integrity": "sha512-dk4ZQX2ko3spPZHB1nGqX/XUfA3TmTC/hDw+i45gQO0sYrqOky5LbISYYUCszzP9d8lw3AIdRdQbq1xwc0LN/A==", + "dev": true, + "dependencies": { + "meow": "^13.0.0", + "protobufjs-cli": "^1.0.0" + }, + "bin": { + "protons": "dist/bin/protons.js" + } + }, "node_modules/protons-runtime": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.2.2.tgz", @@ -19237,6 +19543,18 @@ "uint8arrays": "^5.0.1" } }, + "node_modules/protons/node_modules/meow": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.1.0.tgz", + "integrity": "sha512-o5R/R3Tzxq0PJ3v3qcQJtSvSE9nKOLSAaDuuoMzDVuGTwHdccMWcYomh9Xolng2tjT6O/Y83d+0coVGof6tqmA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -22804,7 +23122,8 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", diff --git a/src/index.ts b/src/index.ts index f291a6a8..27715885 100644 --- a/src/index.ts +++ b/src/index.ts @@ -54,6 +54,7 @@ import { type PublishOpts } from './types.js' import { buildRawMessage, validateToRawMessage } from './utils/buildRawMessage.js' +import { createGossipRpc, ensureControl } from './utils/create-gossip-rpc.js' import { shuffle, messageIdToString } from './utils/index.js' import { msgIdFnStrictNoSign, msgIdFnStrictSign } from './utils/msgIdFn.js' import { multiaddrToIPStr } from './utils/multiaddr.js' @@ -1053,13 +1054,12 @@ export class GossipSub extends TypedEventEmitter implements Pub // Handle messages // TODO: (up to limit) - if (rpc.messages != null) { - for (const message of rpc.messages) { - if ((this.allowedTopics != null) && !this.allowedTopics.has(message.topic)) { - // Not allowed: message cache data-structures are not bounded by topic count - // TODO: Should apply behaviour penalties? - continue - } + for (const message of rpc.messages) { + if ((this.allowedTopics != null) && !this.allowedTopics.has(message.topic)) { + // Not allowed: message cache data-structures are not bounded by topic count + // TODO: Should apply behaviour penalties? + continue + } const handleReceivedMessagePromise = this.handleReceivedMessage(from, message) // Should never throw, but handle just in case @@ -2111,7 +2111,7 @@ export class GossipSub extends TypedEventEmitter implements Pub this.publishedMessageIds.put(msgIdStr) const batchPublish = opts?.batchPublish ?? this.opts.batchPublish - const rpc = { messages: [rawMsg] } + const rpc = createGossipRpc([rawMsg]) if (batchPublish) { this.sendRpcInBatch(tosend, rpc) } else { @@ -2163,8 +2163,8 @@ export class GossipSub extends TypedEventEmitter implements Pub * This is not only faster but also avoid allocating memory for each peer * see https://github.com/ChainSafe/js-libp2p-gossipsub/issues/344 */ - private sendRpcInBatch (tosend: Set, rpc: IRPC): void { - const rpcBytes = RPC.encode(rpc).finish() + private sendRpcInBatch (tosend: Set, rpc: RPC): void { + const rpcBytes = RPC.encode(rpc) const prefixedData = encode.single(rpcBytes) for (const id of tosend) { const outboundStream = this.streamsOutbound.get(id) @@ -2316,31 +2316,24 @@ export class GossipSub extends TypedEventEmitter implements Pub /** Mutates `outRpc` adding graft and prune control messages */ public piggybackControl (id: PeerIdStr, outRpc: RPC, ctrl: RPC.ControlMessage): void { - if (ctrl.graft != null) { - if (outRpc.control == null) outRpc.control = {} - if (outRpc.control.graft == null) outRpc.control.graft = [] - for (const graft of ctrl.graft) { - if (graft.topicID != null && (this.mesh.get(graft.topicID)?.has(id) ?? false)) { - outRpc.control.graft.push(graft) - } + const rpc = ensureControl(outRpc) + for (const graft of ctrl.graft) { + if (graft.topicID != null && (this.mesh.get(graft.topicID)?.has(id) ?? false)) { + rpc.control.graft.push(graft) } } - if (ctrl.prune != null) { - if (outRpc.control == null) outRpc.control = {} - if (outRpc.control.prune == null) outRpc.control.prune = [] - for (const prune of ctrl.prune) { - if (prune.topicID != null && !(this.mesh.get(prune.topicID)?.has(id) ?? false)) { - outRpc.control.prune.push(prune) - } + for (const prune of ctrl.prune) { + if (prune.topicID != null && !(this.mesh.get(prune.topicID)?.has(id) ?? false)) { + rpc.control.prune.push(prune) } } } /** Mutates `outRpc` adding ihave control messages */ private piggybackGossip (id: PeerIdStr, outRpc: RPC, ihave: RPC.ControlIHave[]): void { - if (outRpc.control == null) outRpc.control = {} - outRpc.control.ihave = ihave + const rpc = ensureControl(outRpc) + rpc.control.ihave = ihave } /** diff --git a/src/message/decodeRpc.ts b/src/message/decodeRpc.ts index 4959a895..1535f123 100644 --- a/src/message/decodeRpc.ts +++ b/src/message/decodeRpc.ts @@ -1,5 +1,5 @@ -import { RPC } from './rpc.js' import { reader as r, type Reader } from 'protons-runtime' +import { RPC } from './rpc.js' export interface DecodeRPCLimits { maxSubscriptions: number @@ -64,7 +64,7 @@ export function decodeRpc (bytes: Uint8Array, opts: DecodeRPCLimits): RPC { return obj } -function decodeControlMessage(reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlMessage { +function decodeControlMessage (reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlMessage { const obj: any = { ihave: [], iwant: [], @@ -115,7 +115,7 @@ function decodeControlMessage(reader: Reader, length: number, opts: DecodeRPCLim return obj } -function decodeControlIHave(reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlIHave { +function decodeControlIHave (reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlIHave { const obj: any = { messageIDs: [] } @@ -142,7 +142,7 @@ function decodeControlIHave(reader: Reader, length: number, opts: DecodeRPCLimit return obj } -function decodeControlIWant(reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlIWant { +function decodeControlIWant (reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlIWant { const obj: any = { messageIDs: [] } @@ -169,7 +169,7 @@ function decodeControlIWant(reader: Reader, length: number, opts: DecodeRPCLimit return obj } -function decodeControlGraft(reader: Reader, length: number) { +function decodeControlGraft (reader: Reader, length: number) { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -190,7 +190,7 @@ function decodeControlGraft(reader: Reader, length: number) { return obj } -function decodeControlPrune(reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlPrune { +function decodeControlPrune (reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlPrune { const obj: any = { peers: [] } diff --git a/src/message/rpc.ts b/src/message/rpc.ts index a059f31c..95e42939 100644 --- a/src/message/rpc.ts +++ b/src/message/rpc.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, encodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface RPC { diff --git a/src/utils/create-gossip-rpc.ts b/src/utils/create-gossip-rpc.ts index 93a80dc6..9ba7891e 100644 --- a/src/utils/create-gossip-rpc.ts +++ b/src/utils/create-gossip-rpc.ts @@ -3,23 +3,23 @@ import type { RPC } from '../message/rpc.js' /** * Create a gossipsub RPC object */ -export function createGossipRpc(messages: RPC.Message[] = [], control?: Partial): RPC { +export function createGossipRpc (messages: RPC.Message[] = [], control?: Partial): RPC { return { subscriptions: [], messages, - control: control + control: control !== undefined ? { - graft: control.graft || [], - prune: control.prune || [], - ihave: control.ihave || [], - iwant: control.iwant || [] + graft: control.graft ?? [], + prune: control.prune ?? [], + ihave: control.ihave ?? [], + iwant: control.iwant ?? [] } : undefined } } -export function ensureControl(rpc: RPC): Required { - if (!rpc.control) { +export function ensureControl (rpc: RPC): Required { + if (rpc.control === undefined) { rpc.control = { graft: [], prune: [], diff --git a/test/benchmark/protobuf.test.ts b/test/benchmark/protobuf.test.ts index f0a514af..967f35fa 100644 --- a/test/benchmark/protobuf.test.ts +++ b/test/benchmark/protobuf.test.ts @@ -9,7 +9,7 @@ describe('protobuf', function () { minMs: 60 * 1000 }) - const testCases: { name: string; length: number }[] = [ + const testCases: Array<{ name: string, length: number }> = [ // As of Oct 2023, Attestation length = 281 { name: 'Attestation', length: 300 }, // A SignedBeaconBlock could be from 70_000 to 300_000 diff --git a/test/decodeRpc.spec.ts b/test/decodeRpc.spec.ts index f5c5f813..5b0f39ca 100644 --- a/test/decodeRpc.spec.ts +++ b/test/decodeRpc.spec.ts @@ -144,7 +144,7 @@ describe('decodeRpc', () => { expect(endecode(rpc).control?.prune?.[1].peers).length(1, 'Wrong prune?.[1].peers len') }) - function endecode(rpc: RPC): RPC { + function endecode (rpc: RPC): RPC { return decodeRpc(RPC.encode(rpc), decodeRpcLimits) } }) diff --git a/test/utils/index.ts b/test/utils/index.ts index a26fb119..97faa349 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -15,11 +15,7 @@ export const createPeerId = async (): Promise => { let seq = 0n const defaultPeer = uint8ArrayFromString('12D3KooWBsYhazxNL7aeisdwttzc6DejNaM48889t5ifiS6tTrBf', 'base58btc') -<<<<<<< HEAD -export function makeTestMessage (i: number, topic: TopicStr, from?: PeerId): RPC.IMessage { -======= -export function makeTestMessage(i: number, topic: TopicStr, from?: PeerId): RPC.Message { ->>>>>>> b433f8e (feat: protons 7.2.1 protons-runtime 5.1.0) +export function makeTestMessage (i: number, topic: TopicStr, from?: PeerId): RPC.Message { return { seqno: uint8ArrayFromString((seq++).toString(16).padStart(16, '0'), 'base16'), data: Uint8Array.from([i]), From 95ae34c265c9a3b8e5506d80c1a200a7fabf1458 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Wed, 31 Jan 2024 13:22:19 +0700 Subject: [PATCH 5/9] chore: fix decodeControlGraft() type --- src/message/decodeRpc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/message/decodeRpc.ts b/src/message/decodeRpc.ts index 1535f123..4b0e8572 100644 --- a/src/message/decodeRpc.ts +++ b/src/message/decodeRpc.ts @@ -169,7 +169,7 @@ function decodeControlIWant (reader: Reader, length: number, opts: DecodeRPCLimi return obj } -function decodeControlGraft (reader: Reader, length: number) { +function decodeControlGraft (reader: Reader, length: number): RPC.ControlGraft { const obj: any = {} const end = length == null ? reader.len : reader.pos + length From cd36b579d798594f4218b712f2cf04b3b0bc2197 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Thu, 1 Feb 2024 11:25:13 +0700 Subject: [PATCH 6/9] chore: protons 7.2.1 and protons-runtime 5.1.0 --- package-lock.json | 19 +++++----- package.json | 4 +- src/message/rpc.ts | 92 ++++++++++++++++++++++++++++++++-------------- 3 files changed, 76 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 80a6a549..2887cf3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "multiformats": "^13.0.1", - "protons-runtime": "^5.1.0", + "protons-runtime": "^5.3.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.1" }, @@ -42,7 +42,7 @@ "p-event": "^6.0.0", "p-retry": "^6.2.0", "p-wait-for": "^5.0.2", - "protons": "^7.2.1", + "protons": "^7.4.0", "sinon": "^17.0.1", "time-cache": "^0.3.0", "ts-sinon": "^2.0.2" @@ -19522,12 +19522,12 @@ "dev": true }, "node_modules/protons": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/protons/-/protons-7.3.4.tgz", - "integrity": "sha512-dk4ZQX2ko3spPZHB1nGqX/XUfA3TmTC/hDw+i45gQO0sYrqOky5LbISYYUCszzP9d8lw3AIdRdQbq1xwc0LN/A==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protons/-/protons-7.4.0.tgz", + "integrity": "sha512-IGkEiHt3Rlvtuj+vJJSiZlFiu7QQuecKyJBWR+RkvLFEeI6/Pha9GQDQLhTqHnvc6j6R1h95mlDcuGG8VjvvjQ==", "dev": true, "dependencies": { - "meow": "^13.0.0", + "meow": "^13.1.0", "protobufjs-cli": "^1.0.0" }, "bin": { @@ -19535,10 +19535,11 @@ } }, "node_modules/protons-runtime": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.2.2.tgz", - "integrity": "sha512-o97rNPN9pE3cxOxjs/waZNRKlbY/DR11oc20rUvarWZgFzQLLLzJU0RFh5JPi6GJCN67VGVn9/FDIEtFblfB3A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.3.0.tgz", + "integrity": "sha512-RySXxx+jvz4mi/rr2VsnvgWvC6dFP2pVyWpVRFYb/jxmwih862ZjXJLUkjd+nOtXBx0Lwk2xeuY2f2fmd3FT9w==", "dependencies": { + "uint8-varint": "^2.0.2", "uint8arraylist": "^2.4.3", "uint8arrays": "^5.0.1" } diff --git a/package.json b/package.json index 93801aec..6cf99bf3 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "multiformats": "^13.0.1", - "protons-runtime": "^5.1.0", + "protons-runtime": "^5.3.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.1" }, @@ -105,7 +105,7 @@ "p-retry": "^6.2.0", "p-wait-for": "^5.0.2", "sinon": "^17.0.1", - "protons": "^7.2.1", + "protons": "^7.4.0", "time-cache": "^0.3.0", "ts-sinon": "^2.0.2" }, diff --git a/src/message/rpc.ts b/src/message/rpc.ts index 95e42939..a31ab91a 100644 --- a/src/message/rpc.ts +++ b/src/message/rpc.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, message } from 'protons-runtime' +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface RPC { @@ -42,7 +42,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -77,8 +77,8 @@ export namespace RPC { return encodeMessage(obj, SubOpts.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): SubOpts => { - return decodeMessage(buf, SubOpts.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): SubOpts => { + return decodeMessage(buf, SubOpts.codec(), opts) } } @@ -134,7 +134,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { topic: '' } @@ -187,8 +187,8 @@ export namespace RPC { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } @@ -240,7 +240,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { ihave: [], iwant: [], @@ -255,18 +255,34 @@ export namespace RPC { switch (tag >>> 3) { case 1: { + if (opts.limits?.ihave != null && obj.ihave.length === opts.limits.ihave) { + throw new CodeError('decode error - map field "ihave" had too many elements', 'ERR_MAX_LENGTH') + } + obj.ihave.push(RPC.ControlIHave.codec().decode(reader, reader.uint32())) break } case 2: { + if (opts.limits?.iwant != null && obj.iwant.length === opts.limits.iwant) { + throw new CodeError('decode error - map field "iwant" had too many elements', 'ERR_MAX_LENGTH') + } + obj.iwant.push(RPC.ControlIWant.codec().decode(reader, reader.uint32())) break } case 3: { + if (opts.limits?.graft != null && obj.graft.length === opts.limits.graft) { + throw new CodeError('decode error - map field "graft" had too many elements', 'ERR_MAX_LENGTH') + } + obj.graft.push(RPC.ControlGraft.codec().decode(reader, reader.uint32())) break } case 4: { + if (opts.limits?.prune != null && obj.prune.length === opts.limits.prune) { + throw new CodeError('decode error - map field "prune" had too many elements', 'ERR_MAX_LENGTH') + } + obj.prune.push(RPC.ControlPrune.codec().decode(reader, reader.uint32())) break } @@ -288,8 +304,8 @@ export namespace RPC { return encodeMessage(obj, ControlMessage.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlMessage => { - return decodeMessage(buf, ControlMessage.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlMessage => { + return decodeMessage(buf, ControlMessage.codec(), opts) } } @@ -323,7 +339,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { messageIDs: [] } @@ -339,6 +355,10 @@ export namespace RPC { break } case 2: { + if (opts.limits?.messageIDs != null && obj.messageIDs.length === opts.limits.messageIDs) { + throw new CodeError('decode error - map field "messageIDs" had too many elements', 'ERR_MAX_LENGTH') + } + obj.messageIDs.push(reader.bytes()) break } @@ -360,8 +380,8 @@ export namespace RPC { return encodeMessage(obj, ControlIHave.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlIHave => { - return decodeMessage(buf, ControlIHave.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlIHave => { + return decodeMessage(buf, ControlIHave.codec(), opts) } } @@ -389,7 +409,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { messageIDs: [] } @@ -401,6 +421,10 @@ export namespace RPC { switch (tag >>> 3) { case 1: { + if (opts.limits?.messageIDs != null && obj.messageIDs.length === opts.limits.messageIDs) { + throw new CodeError('decode error - map field "messageIDs" had too many elements', 'ERR_MAX_LENGTH') + } + obj.messageIDs.push(reader.bytes()) break } @@ -422,8 +446,8 @@ export namespace RPC { return encodeMessage(obj, ControlIWant.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlIWant => { - return decodeMessage(buf, ControlIWant.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlIWant => { + return decodeMessage(buf, ControlIWant.codec(), opts) } } @@ -449,7 +473,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -480,8 +504,8 @@ export namespace RPC { return encodeMessage(obj, ControlGraft.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlGraft => { - return decodeMessage(buf, ControlGraft.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlGraft => { + return decodeMessage(buf, ControlGraft.codec(), opts) } } @@ -521,7 +545,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { peers: [] } @@ -537,6 +561,10 @@ export namespace RPC { break } case 2: { + if (opts.limits?.peers != null && obj.peers.length === opts.limits.peers) { + throw new CodeError('decode error - map field "peers" had too many elements', 'ERR_MAX_LENGTH') + } + obj.peers.push(RPC.PeerInfo.codec().decode(reader, reader.uint32())) break } @@ -562,8 +590,8 @@ export namespace RPC { return encodeMessage(obj, ControlPrune.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlPrune => { - return decodeMessage(buf, ControlPrune.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlPrune => { + return decodeMessage(buf, ControlPrune.codec(), opts) } } @@ -595,7 +623,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -630,8 +658,8 @@ export namespace RPC { return encodeMessage(obj, PeerInfo.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerInfo => { - return decodeMessage(buf, PeerInfo.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerInfo => { + return decodeMessage(buf, PeerInfo.codec(), opts) } } @@ -666,7 +694,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { subscriptions: [], messages: [] @@ -679,10 +707,18 @@ export namespace RPC { switch (tag >>> 3) { case 1: { + if (opts.limits?.subscriptions != null && obj.subscriptions.length === opts.limits.subscriptions) { + throw new CodeError('decode error - map field "subscriptions" had too many elements', 'ERR_MAX_LENGTH') + } + obj.subscriptions.push(RPC.SubOpts.codec().decode(reader, reader.uint32())) break } case 2: { + if (opts.limits?.messages != null && obj.messages.length === opts.limits.messages) { + throw new CodeError('decode error - map field "messages" had too many elements', 'ERR_MAX_LENGTH') + } + obj.messages.push(RPC.Message.codec().decode(reader, reader.uint32())) break } @@ -708,7 +744,7 @@ export namespace RPC { return encodeMessage(obj, RPC.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): RPC => { - return decodeMessage(buf, RPC.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): RPC => { + return decodeMessage(buf, RPC.codec(), opts) } } From a82b44f295730aec1b5f4b1f558a8b282bceafdf Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Wed, 7 Feb 2024 16:49:56 +0700 Subject: [PATCH 7/9] chore: rebase against master --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 2887cf3e..af24f6fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "@libp2p/peer-id": "^4.0.5", "@libp2p/pubsub": "^9.0.8", "@multiformats/multiaddr": "^12.1.14", - "abortable-iterator": "^5.0.1", "denque": "^2.1.0", "it-length-prefixed": "^9.0.4", "it-pipe": "^3.0.1", @@ -4585,6 +4584,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/abortable-iterator/-/abortable-iterator-5.0.1.tgz", "integrity": "sha512-hlZ5Z8UwqrKsJcelVPEqDduZowJPBQJ9ZhBC2FXpja3lXy8X6MoI5uMzIgmrA8+3jcVnp8TF/tx+IBBqYJNUrg==", + "dev": true, "dependencies": { "get-iterator": "^2.0.0", "it-stream-types": "^2.0.1" From 79fb5d7d01ae31e530f0b4a0ebb287d21f366d1f Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Fri, 9 Feb 2024 13:30:08 +0700 Subject: [PATCH 8/9] feat: limit rpc fields using protons api --- package-lock.json | 16 +-- package.json | 4 +- src/index.ts | 19 +++- src/message/decodeRpc.ts | 207 --------------------------------------- test/decodeRpc.spec.ts | 151 ---------------------------- 5 files changed, 27 insertions(+), 370 deletions(-) delete mode 100644 test/decodeRpc.spec.ts diff --git a/package-lock.json b/package-lock.json index af24f6fb..c7a43715 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "multiformats": "^13.0.1", - "protons-runtime": "^5.3.0", + "protons-runtime": "5.4.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.1" }, @@ -41,7 +41,7 @@ "p-event": "^6.0.0", "p-retry": "^6.2.0", "p-wait-for": "^5.0.2", - "protons": "^7.4.0", + "protons": "^7.5.0", "sinon": "^17.0.1", "time-cache": "^0.3.0", "ts-sinon": "^2.0.2" @@ -19522,9 +19522,9 @@ "dev": true }, "node_modules/protons": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protons/-/protons-7.4.0.tgz", - "integrity": "sha512-IGkEiHt3Rlvtuj+vJJSiZlFiu7QQuecKyJBWR+RkvLFEeI6/Pha9GQDQLhTqHnvc6j6R1h95mlDcuGG8VjvvjQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/protons/-/protons-7.5.0.tgz", + "integrity": "sha512-ahKkRTLWDgWmwktuyod+gs7bnMMLh5EjmsWtjvlk7X8FgUoHt7qkVQ05UIJPlMvZJdhiicpZtPueD3BbC2fLcQ==", "dev": true, "dependencies": { "meow": "^13.1.0", @@ -19535,9 +19535,9 @@ } }, "node_modules/protons-runtime": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.3.0.tgz", - "integrity": "sha512-RySXxx+jvz4mi/rr2VsnvgWvC6dFP2pVyWpVRFYb/jxmwih862ZjXJLUkjd+nOtXBx0Lwk2xeuY2f2fmd3FT9w==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.4.0.tgz", + "integrity": "sha512-XfA++W/WlQOSyjUyuF5lgYBfXZUEMP01Oh1C2dSwZAlF2e/ZrMRPfWonXj6BGM+o8Xciv7w0tsRMKYwYEuQvaw==", "dependencies": { "uint8-varint": "^2.0.2", "uint8arraylist": "^2.4.3", diff --git a/package.json b/package.json index 6cf99bf3..dcfb5b8c 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "it-pipe": "^3.0.1", "it-pushable": "^3.2.3", "multiformats": "^13.0.1", - "protons-runtime": "^5.3.0", + "protons-runtime": "5.4.0", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.0.1" }, @@ -105,7 +105,7 @@ "p-retry": "^6.2.0", "p-wait-for": "^5.0.2", "sinon": "^17.0.1", - "protons": "^7.4.0", + "protons": "^7.5.0", "time-cache": "^0.3.0", "ts-sinon": "^2.0.2" }, diff --git a/src/index.ts b/src/index.ts index 27715885..6d75c132 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ import { ACCEPT_FROM_WHITELIST_THRESHOLD_SCORE, BACKOFF_SLACK } from './constants.js' -import { decodeRpc, type DecodeRPCLimits, defaultDecodeRpcLimits } from './message/decodeRpc.js' +import { type DecodeRPCLimits, defaultDecodeRpcLimits } from './message/decodeRpc.js' import { RPC } from './message/rpc.js' import { MessageCache, type MessageCacheRecord } from './message-cache.js' import { @@ -955,7 +955,22 @@ export class GossipSub extends TypedEventEmitter implements Pub const rpcBytes = data.subarray() // Note: This function may throw, it must be wrapped in a try {} catch {} to prevent closing the stream. // TODO: What should we do if the entire RPC is invalid? - const rpc = decodeRpc(rpcBytes, this.decodeRpcLimits) + // const rpc = decodeRpc(rpcBytes, this.decodeRpcLimits) + const rpc = RPC.decode(rpcBytes, { + limits: { + subscriptions: this.decodeRpcLimits.maxSubscriptions, + messages: this.decodeRpcLimits.maxMessages, + control$: { + ihave: this.decodeRpcLimits.maxIhaveMessageIDs, + iwant: this.decodeRpcLimits.maxIwantMessageIDs, + graft: this.decodeRpcLimits.maxControlMessages, + prune: this.decodeRpcLimits.maxControlMessages, + prune$: { + peers: this.decodeRpcLimits.maxPeerInfos + } + } + } + }) this.metrics?.onRpcRecv(rpc, rpcBytes.length) diff --git a/src/message/decodeRpc.ts b/src/message/decodeRpc.ts index 4b0e8572..dc9579c6 100644 --- a/src/message/decodeRpc.ts +++ b/src/message/decodeRpc.ts @@ -1,6 +1,3 @@ -import { reader as r, type Reader } from 'protons-runtime' -import { RPC } from './rpc.js' - export interface DecodeRPCLimits { maxSubscriptions: number maxMessages: number @@ -18,207 +15,3 @@ export const defaultDecodeRpcLimits: DecodeRPCLimits = { maxControlMessages: Infinity, maxPeerInfos: Infinity } - -/** - * Copied code from src/message/rpc.ts but with decode limits to prevent OOM attacks - */ -export function decodeRpc (bytes: Uint8Array, opts: DecodeRPCLimits): RPC { - // Mutate to use the option as stateful counter. Must limit the total count of messageIDs across all IWANT, IHAVE - // else one count put 100 messageIDs into each 100 IWANT and "get around" the limit - opts = { ...opts } - - const reader = r(bytes) - const obj: any = { - subscriptions: [], - messages: [] - } - - const end = reader.len - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - if (obj.subscriptions.length < opts.maxSubscriptions) { - obj.subscriptions.push(RPC.SubOpts.codec().decode(reader, reader.uint32())) - } else { - reader.skipType(tag & 7) - } - break - case 2: - if (obj.messages.length < opts.maxMessages) { - obj.messages.push(RPC.Message.codec().decode(reader, reader.uint32())) - } else { - reader.skipType(tag & 7) - } - break - case 3: - obj.control = decodeControlMessage(reader, reader.uint32(), opts) - break - default: - reader.skipType(tag & 7) - break - } - } - return obj -} - -function decodeControlMessage (reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlMessage { - const obj: any = { - ihave: [], - iwant: [], - graft: [], - prune: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - if (obj.ihave.length < opts.maxControlMessages) { - obj.ihave.push(decodeControlIHave(reader, reader.uint32(), opts)) - } else { - reader.skipType(tag & 7) - } - break - case 2: - if (obj.iwant.length < opts.maxControlMessages) { - obj.iwant.push(decodeControlIWant(reader, reader.uint32(), opts)) - } else { - reader.skipType(tag & 7) - } - break - case 3: - if (obj.graft.length < opts.maxControlMessages) { - obj.graft.push(decodeControlGraft(reader, reader.uint32())) - } else { - reader.skipType(tag & 7) - } - break - case 4: - if (obj.prune.length < opts.maxControlMessages) { - obj.prune.push(decodeControlPrune(reader, reader.uint32(), opts)) - } else { - reader.skipType(tag & 7) - } - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj -} - -function decodeControlIHave (reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlIHave { - const obj: any = { - messageIDs: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.topicID = reader.string() - break - case 2: - if (opts.maxIhaveMessageIDs-- > 0) obj.messageIDs.push(reader.bytes()) - else reader.skipType(tag & 7) - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj -} - -function decodeControlIWant (reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlIWant { - const obj: any = { - messageIDs: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - if (opts.maxIwantMessageIDs-- > 0) { - obj.messageIDs.push(reader.bytes()) - } else { - reader.skipType(tag & 7) - } - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj -} - -function decodeControlGraft (reader: Reader, length: number): RPC.ControlGraft { - const obj: any = {} - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.topicID = reader.string() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj -} - -function decodeControlPrune (reader: Reader, length: number, opts: DecodeRPCLimits): RPC.ControlPrune { - const obj: any = { - peers: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: - obj.topicID = reader.string() - break - case 2: - if (opts.maxPeerInfos-- > 0) { - obj.peers.push(RPC.PeerInfo.codec().decode(reader, reader.uint32())) - } else { - reader.skipType(tag & 7) - } - break - case 3: - obj.backoff = reader.uint64() - break - default: - reader.skipType(tag & 7) - break - } - } - - return obj -} diff --git a/test/decodeRpc.spec.ts b/test/decodeRpc.spec.ts deleted file mode 100644 index 5b0f39ca..00000000 --- a/test/decodeRpc.spec.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { expect } from 'aegir/chai' -import { decodeRpc, type DecodeRPCLimits, defaultDecodeRpcLimits } from '../src/message/decodeRpc.js' -import { RPC } from '../src/message/index.js' - -describe('decodeRpc', () => { - const topicID = 'topic' - const msgID = new Uint8Array(8) - - const subscription: RPC.SubOpts = { subscribe: true, topic: topicID } - const message: RPC.Message = { topic: topicID, data: new Uint8Array(100) } - const peerInfo: RPC.PeerInfo = { peerID: msgID, signedPeerRecord: msgID } - const ihave: RPC.ControlIHave = { topicID, messageIDs: [msgID] } - const iwant: RPC.ControlIWant = { messageIDs: [msgID] } - const graft: RPC.ControlGraft = { topicID } - const prune: RPC.ControlPrune = { topicID, peers: [peerInfo] } - - describe('decode correctness', () => { - it('Should decode full RPC', () => { - const rpc: RPC = { - subscriptions: [subscription, subscription], - messages: [message, message], - control: { - ihave: [ihave, ihave], - iwant: [iwant, iwant], - graft: [graft, graft], - prune: [prune, prune] - } - } - - const bytes = RPC.encode(rpc) - - // Compare as JSON - expect(decodeRpc(bytes, defaultDecodeRpcLimits)).to.be.deep.equals(RPC.decode(bytes)) - }) - }) - - describe('decode limits', () => { - const decodeRpcLimits: DecodeRPCLimits = { - maxSubscriptions: 2, - maxMessages: 2, - maxControlMessages: 2, - maxIhaveMessageIDs: 3, - maxIwantMessageIDs: 3, - maxPeerInfos: 3 - } - - // Check no mutations on limits - const limitsAfter = { ...decodeRpcLimits } - - after('decodeRpcLimits has not been mutated', () => { - expect(limitsAfter).deep.equals(decodeRpcLimits) - }) - - const rpcEmpty: RPC = { - subscriptions: [], - messages: [], - control: { - ihave: [], - iwant: [], - graft: [], - prune: [] - } - } - - const rpcEmptyBytes = RPC.encode(rpcEmpty) - - it('limit subscriptions.length', () => { - // Decode a fresh instance to allow safe mutations - const rpc = RPC.decode(rpcEmptyBytes) - rpc.subscriptions = [subscription, subscription, subscription] - expect(endecode(rpc).subscriptions).length(decodeRpcLimits.maxSubscriptions) - }) - - it('limit messages.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - rpc.messages = [message, message, message] - expect(endecode(rpc).messages).length(decodeRpcLimits.maxMessages) - }) - - it('limit control.ihave.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { ihave: [ihave, ihave, ihave], iwant: [], graft: [], prune: [] } - expect(endecode(rpc).control?.ihave).length(decodeRpcLimits.maxControlMessages) - }) - - it('limit control.iwant.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - // rpc.control = { iwant: [iwant, iwant, iwant], ihave: [], graft: [], prune: [] } - rpc.control?.iwant.push(...[iwant, iwant, iwant]) - expect(endecode(rpc).control?.iwant).length(decodeRpcLimits.maxControlMessages) - }) - - it('limit control.graft.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { graft: [graft, graft, graft], ihave: [], iwant: [], prune: [] } - expect(endecode(rpc).control?.graft).length(decodeRpcLimits.maxControlMessages) - }) - - it('limit control.prune.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - rpc.control = { prune: [prune, prune, prune], ihave: [], iwant: [], graft: [] } - expect(endecode(rpc).control?.prune).length(decodeRpcLimits.maxControlMessages) - }) - - it('limit ihave.messageIDs.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - // Limit to 3 items total, 2 (all) on the first one, 1 on the second one - rpc.control = { - ihave: [{ messageIDs: [msgID, msgID] }, { messageIDs: [msgID, msgID] }], - iwant: [], - graft: [], - prune: [] - } - expect(decodeRpcLimits.maxIhaveMessageIDs).equals(3, 'Wrong maxIhaveMessageIDs') - expect(endecode(rpc).control?.ihave?.[0].messageIDs).length(2, 'Wrong ihave?.[0].messageIDs len') - expect(endecode(rpc).control?.ihave?.[1].messageIDs).length(1, 'Wrong ihave?.[1].messageIDs len') - }) - - it('limit iwant.messageIDs.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - // Limit to 3 items total, 2 (all) on the first one, 1 on the second one - rpc.control = { - iwant: [{ messageIDs: [msgID, msgID] }, { messageIDs: [msgID, msgID] }], - ihave: [], - graft: [], - prune: [] - } - expect(decodeRpcLimits.maxIwantMessageIDs).equals(3, 'Wrong maxIwantMessageIDs') - expect(endecode(rpc).control?.iwant?.[0].messageIDs).length(2, 'Wrong iwant?.[0].messageIDs len') - expect(endecode(rpc).control?.iwant?.[1].messageIDs).length(1, 'Wrong iwant?.[1].messageIDs len') - }) - - it('limit prune.peers.length', () => { - const rpc = RPC.decode(rpcEmptyBytes) - // Limit to 3 items total, 2 (all) on the first one, 1 on the second one - rpc.control = { - prune: [{ peers: [peerInfo, peerInfo] }, { peers: [peerInfo, peerInfo] }], - ihave: [], - iwant: [], - graft: [] - } - expect(decodeRpcLimits.maxPeerInfos).equals(3, 'Wrong maxPeerInfos') - expect(endecode(rpc).control?.prune?.[0].peers).length(2, 'Wrong prune?.[0].peers len') - expect(endecode(rpc).control?.prune?.[1].peers).length(1, 'Wrong prune?.[1].peers len') - }) - - function endecode (rpc: RPC): RPC { - return decodeRpc(RPC.encode(rpc), decodeRpcLimits) - } - }) -}) From 3060f20239d80784cc4235db3f3aa562229832fc Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Fri, 9 Feb 2024 17:30:21 +0700 Subject: [PATCH 9/9] chore: remove unused code --- src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 6d75c132..50d7e9ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -955,7 +955,6 @@ export class GossipSub extends TypedEventEmitter implements Pub const rpcBytes = data.subarray() // Note: This function may throw, it must be wrapped in a try {} catch {} to prevent closing the stream. // TODO: What should we do if the entire RPC is invalid? - // const rpc = decodeRpc(rpcBytes, this.decodeRpcLimits) const rpc = RPC.decode(rpcBytes, { limits: { subscriptions: this.decodeRpcLimits.maxSubscriptions,