diff --git a/Cargo.lock b/Cargo.lock index 2097ec81..b7221b23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,15 +183,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "intrusive-collections" version = "0.9.6" @@ -290,7 +281,6 @@ dependencies = [ "serde", "serde_json", "thread_local", - "tokio", ] [[package]] @@ -343,16 +333,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num_cpus" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "octets" version = "0.2.0" @@ -371,12 +351,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - [[package]] name = "proc-macro2" version = "1.0.56" @@ -563,17 +537,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tokio" -version = "1.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" -dependencies = [ - "autocfg", - "num_cpus", - "pin-project-lite", -] - [[package]] name = "unicode-ident" version = "1.0.5" diff --git a/Cargo.toml b/Cargo.toml index b65ec4ea..0f6bbb5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,28 @@ license-file = "LICENSE" edition = "2021" exclude = ["index.node"] +[profile.release] +debug = true +strip = false +opt-level = 0 +lto = false +codegen-units = 1 +incremental = false + +[profile.dev] +debug = true +strip = false +opt-level = 0 +lto = false +codegen-units = 1 +incremental = false + [lib] crate-type = ["cdylib"] path = "src/native/napi/lib.rs" [dependencies] -napi = { version = "2", features = ["async", "napi6", "serde-json"] } +napi = { version = "2", features = ["napi6", "serde-json"] } serde = { version = "1.0", features = ["derive"] } napi-derive = { version = "2", default-features = false, features = ["strict", "compat-mode"] } quiche = { version = "0.18.0", features = ["boringssl-boring-crate", "boringssl-vendored"] } diff --git a/async-init.patch b/async-init.patch new file mode 100644 index 00000000..a348e747 --- /dev/null +++ b/async-init.patch @@ -0,0 +1,346 @@ +diff -crB dist/CreateDestroy.js patched/CreateDestroy.js +*** dist/CreateDestroy.js 2025-05-13 17:01:44.521843798 +1000 +--- patched/CreateDestroy.js 2025-05-13 16:43:22.019287860 +1000 +*************** +*** 1,8 **** + import { Evented } from '@matrixai/events'; + import { RWLockWriter } from '@matrixai/async-locks'; +! import { _destroyed, destroyed, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, promise, resetStackTrace, } from './utils.js'; + import { EventAsyncInitDestroy, EventAsyncInitDestroyed } from './events.js'; + import { ErrorAsyncInitDestroyed } from './errors.js'; + function CreateDestroy({ eventDestroy = EventAsyncInitDestroy, eventDestroyed = EventAsyncInitDestroyed, } = {}) { + return (constructor) => { + const { p, resolveP } = promise(); +--- 1,10 ---- + import { Evented } from '@matrixai/events'; + import { RWLockWriter } from '@matrixai/async-locks'; +! import { _destroyed, destroyed, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, promise, resetStackTrace, _spanId, spanId, spanContext, } from './utils.js'; + import { EventAsyncInitDestroy, EventAsyncInitDestroyed } from './events.js'; + import { ErrorAsyncInitDestroyed } from './errors.js'; ++ import { tracer } from '@matrixai/logger' ++ let i = 0; + function CreateDestroy({ eventDestroy = EventAsyncInitDestroy, eventDestroyed = EventAsyncInitDestroyed, } = {}) { + return (constructor) => { + const { p, resolveP } = promise(); +*************** +*** 12,17 **** +--- 14,20 ---- + [_statusP] = p; + [resolveStatusP] = resolveP; + [initLock] = new RWLockWriter(); ++ [_spanId] = undefined; + get [destroyed]() { + return this[_destroyed]; + } +*************** +*** 21,26 **** +--- 24,38 ---- + get [statusP]() { + return this[_statusP]; + } ++ get [spanId]() { ++ return this[_spanId]; ++ } ++ constructor(...args) { ++ super(...args); ++ const ctx = spanContext.getStore(); ++ const parentSpanId = ctx?.currentSpanId; ++ this[_spanId] = tracer.startSpan(`cd-${this.constructor.name || i++}`, parentSpanId); ++ } + async destroy(...args) { + return this[initLock] + .withWriteF(async () => { +*************** +*** 37,42 **** +--- 49,55 ---- + if (typeof super['destroy'] === 'function') { + result = await super.destroy(...args); + } ++ tracer.endSpan(this[_spanId]); + this[_destroyed] = true; + this.dispatchEvent(new eventDestroyed()); + return result; +diff -crB dist/CreateDestroyStartStop.js patched/CreateDestroyStartStop.js +*** dist/CreateDestroyStartStop.js 2025-05-13 17:01:44.547844173 +1000 +--- patched/CreateDestroyStartStop.js 2025-05-13 16:43:34.415385091 +1000 +*************** +*** 1,8 **** + import { Evented } from '@matrixai/events'; + import { RWLockWriter } from '@matrixai/async-locks'; +! import { _running, running, _destroyed, destroyed, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, promise, resetStackTrace, } from './utils.js'; + import { EventAsyncInitStart, EventAsyncInitStarted, EventAsyncInitStop, EventAsyncInitStopped, EventAsyncInitDestroy, EventAsyncInitDestroyed, } from './events.js'; + import { ErrorAsyncInitRunning, ErrorAsyncInitNotRunning, ErrorAsyncInitDestroyed, } from './errors.js'; + function CreateDestroyStartStop(errorRunning = new ErrorAsyncInitRunning(), errorDestroyed = new ErrorAsyncInitDestroyed(), { eventStart = EventAsyncInitStart, eventStarted = EventAsyncInitStarted, eventStop = EventAsyncInitStop, eventStopped = EventAsyncInitStopped, eventDestroy = EventAsyncInitDestroy, eventDestroyed = EventAsyncInitDestroyed, } = {}) { + return (constructor) => { + const { p, resolveP } = promise(); +--- 1,10 ---- + import { Evented } from '@matrixai/events'; + import { RWLockWriter } from '@matrixai/async-locks'; +! import { _running, running, _destroyed, destroyed, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, promise, resetStackTrace, _spanId, spanId, spanContext } from './utils.js'; + import { EventAsyncInitStart, EventAsyncInitStarted, EventAsyncInitStop, EventAsyncInitStopped, EventAsyncInitDestroy, EventAsyncInitDestroyed, } from './events.js'; + import { ErrorAsyncInitRunning, ErrorAsyncInitNotRunning, ErrorAsyncInitDestroyed, } from './errors.js'; ++ import { tracer } from '@matrixai/logger' ++ let i = 0; + function CreateDestroyStartStop(errorRunning = new ErrorAsyncInitRunning(), errorDestroyed = new ErrorAsyncInitDestroyed(), { eventStart = EventAsyncInitStart, eventStarted = EventAsyncInitStarted, eventStop = EventAsyncInitStop, eventStopped = EventAsyncInitStopped, eventDestroy = EventAsyncInitDestroy, eventDestroyed = EventAsyncInitDestroyed, } = {}) { + return (constructor) => { + const { p, resolveP } = promise(); +*************** +*** 13,18 **** +--- 15,21 ---- + [_statusP] = p; + [resolveStatusP] = resolveP; + [initLock] = new RWLockWriter(); ++ [_spanId] = undefined; + get [running]() { + return this[_running]; + } +*************** +*** 25,30 **** +--- 28,36 ---- + get [statusP]() { + return this[_statusP]; + } ++ get [spanId]() { ++ return this[_spanId]; ++ } + async destroy(...args) { + return this[initLock] + .withWriteF(async () => { +*************** +*** 69,87 **** + resetStackTrace(errorDestroyed); + throw errorDestroyed; + } +! this[_status] = 'starting'; +! this[resolveStatusP]('starting'); +! const { p, resolveP } = promise(); +! this[_statusP] = p; +! this[resolveStatusP] = resolveP; +! this.dispatchEvent(new eventStart()); +! let result; +! if (typeof super['start'] === 'function') { +! result = await super.start(...args); +! } +! this[_running] = true; +! this.dispatchEvent(new eventStarted()); +! return result; + }) + .finally(() => { + this[_status] = null; +--- 75,98 ---- + resetStackTrace(errorDestroyed); + throw errorDestroyed; + } +! const ctx = spanContext.getStore(); +! const parentSpanId = ctx?.currentSpanId; +! this[_spanId] = tracer.startSpan(`ss-${this.constructor.name || i++}`, parentSpanId); +! return spanContext.run({ currentSpanId: this[_spanId] }, async () => { +! this[_status] = 'starting'; +! this[resolveStatusP]('starting'); +! const { p, resolveP } = promise(); +! this[_statusP] = p; +! this[resolveStatusP] = resolveP; +! this.dispatchEvent(new eventStart()); +! let result; +! if (typeof super['start'] === 'function') { +! result = await super.start(...args); +! } +! this[_running] = true; +! this.dispatchEvent(new eventStarted()); +! return result; +! }); + }) + .finally(() => { + this[_status] = null; +*************** +*** 114,119 **** +--- 125,131 ---- + if (typeof super['stop'] === 'function') { + result = await super.stop(...args); + } ++ tracer.endSpan(this[_spanId]); + this[_running] = false; + this.dispatchEvent(new eventStopped()); + return result; +diff -crB dist/StartStop.js patched/StartStop.js +*** dist/StartStop.js 2025-05-13 17:01:44.604844994 +1000 +--- patched/StartStop.js 2025-05-13 15:45:54.003601036 +1000 +*************** +*** 1,8 **** + import { Evented } from '@matrixai/events'; + import { RWLockWriter } from '@matrixai/async-locks'; +! import { _running, running, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, promise, resetStackTrace, } from './utils.js'; + import { EventAsyncInitStart, EventAsyncInitStarted, EventAsyncInitStop, EventAsyncInitStopped, } from './events.js'; + import { ErrorAsyncInitNotRunning } from './errors.js'; + function StartStop({ eventStart = EventAsyncInitStart, eventStarted = EventAsyncInitStarted, eventStop = EventAsyncInitStop, eventStopped = EventAsyncInitStopped, } = {}) { + return (constructor) => { + const { p, resolveP } = promise(); +--- 1,10 ---- + import { Evented } from '@matrixai/events'; + import { RWLockWriter } from '@matrixai/async-locks'; +! import { _running, running, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, promise, resetStackTrace, _spanId, spanId, spanContext, } from './utils.js'; + import { EventAsyncInitStart, EventAsyncInitStarted, EventAsyncInitStop, EventAsyncInitStopped, } from './events.js'; + import { ErrorAsyncInitNotRunning } from './errors.js'; ++ import { tracer } from '@matrixai/logger'; ++ let i = 0; + function StartStop({ eventStart = EventAsyncInitStart, eventStarted = EventAsyncInitStarted, eventStop = EventAsyncInitStop, eventStopped = EventAsyncInitStopped, } = {}) { + return (constructor) => { + const { p, resolveP } = promise(); +*************** +*** 10,15 **** +--- 12,18 ---- + [_running] = false; + [_status] = null; + [_statusP] = p; ++ [_spanId] = undefined; + [resolveStatusP] = resolveP; + [initLock] = new RWLockWriter(); + get [running]() { +*************** +*** 21,45 **** + get [statusP]() { + return this[_statusP]; + } + async start(...args) { + return this[initLock] + .withWriteF(async () => { + if (this[_running]) { + return; + } +! this[_status] = 'starting'; +! this[resolveStatusP]('starting'); +! const { p, resolveP } = promise(); +! this[_statusP] = p; +! this[resolveStatusP] = resolveP; +! this.dispatchEvent(new eventStart()); +! let result; +! if (typeof super['start'] === 'function') { +! result = await super.start(...args); +! } +! this[_running] = true; +! this.dispatchEvent(new eventStarted()); +! return result; + }) + .finally(() => { + this[_status] = null; +--- 24,56 ---- + get [statusP]() { + return this[_statusP]; + } ++ get [spanId]() { ++ return this[_spanId]; ++ } + async start(...args) { + return this[initLock] + .withWriteF(async () => { + if (this[_running]) { + return; + } +! const ctx = spanContext.getStore(); +! const parentSpanId = ctx?.currentSpanId; +! this[_spanId] = tracer.startSpan(`ss-${this.constructor.name || i++}`, parentSpanId); +! return spanContext.run({ currentSpanId: this[_spanId] }, async () => { +! this[_status] = 'starting'; +! this[resolveStatusP]('starting'); +! const { p, resolveP } = promise(); +! this[_statusP] = p; +! this[resolveStatusP] = resolveP; +! this.dispatchEvent(new eventStart()); +! let result; +! if (typeof super['start'] === 'function') { +! result = await super.start(...args); +! } +! this[_running] = true; +! this.dispatchEvent(new eventStarted()); +! return result; +! }); + }) + .finally(() => { + this[_status] = null; +*************** +*** 67,72 **** +--- 78,84 ---- + } + this[_running] = false; + this.dispatchEvent(new eventStopped()); ++ tracer.endSpan(this[_spanId]); + return result; + }) + .finally(() => { +diff -crB dist/utils.d.ts patched/utils.d.ts +*** dist/utils.d.ts 2025-05-13 17:01:44.754847155 +1000 +--- patched/utils.d.ts 2025-05-13 16:48:52.064927307 +1000 +*************** +*** 1,3 **** +--- 1,4 ---- ++ import type { AsyncLocalStorage } from 'node:async_hooks'; + import type { PromiseDeconstructed } from './types.js'; + /** + * Symbols prevents name clashes with decorated classes +*************** +*** 12,17 **** +--- 13,21 ---- + declare const statusP: unique symbol; + declare const resolveStatusP: unique symbol; + declare const initLock: unique symbol; ++ declare const _spanId: unique symbol; ++ declare const spanId: unique symbol; ++ declare const spanContext: AsyncLocalStorage; + declare const AsyncFunction: Function; + declare const GeneratorFunction: Function; + declare const AsyncGeneratorFunction: Function; +*************** +*** 27,30 **** + * function is called, giving a more useful stack trace + */ + declare function resetStackTrace(error: Error, decorated?: Function): void; +! export { _running, running, _destroyed, destroyed, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, hasCaptureStackTrace, promise, resetStackTrace, }; +--- 31,34 ---- + * function is called, giving a more useful stack trace + */ + declare function resetStackTrace(error: Error, decorated?: Function): void; +! export { _running, running, _destroyed, destroyed, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, hasCaptureStackTrace, _spanId, spanId, spanContext, promise, resetStackTrace, }; +diff -crB dist/utils.js patched/utils.js +*** dist/utils.js 2025-05-13 17:01:44.617845181 +1000 +--- patched/utils.js 2025-05-13 15:45:45.001524143 +1000 +*************** +*** 1,3 **** +--- 1,5 ---- ++ import { AsyncLocalStorage } from 'node:async_hooks'; ++ + /** + * Symbols prevents name clashes with decorated classes + */ +*************** +*** 9,20 **** +--- 11,25 ---- + const status = Symbol('status'); + const _statusP = Symbol('_statusP'); + const statusP = Symbol('statusP'); ++ const _spanId = Symbol('_spanId'); ++ const spanId = Symbol('spanId'); + const resolveStatusP = Symbol('resolveStatusP'); + const initLock = Symbol('initLock'); + const AsyncFunction = (async () => { }).constructor; + const GeneratorFunction = function* () { }.constructor; + const AsyncGeneratorFunction = async function* () { }.constructor; + const hasCaptureStackTrace = 'captureStackTrace' in Error; ++ const spanContext = new AsyncLocalStorage(); + /** + * Deconstructed promise + */ +*************** +*** 53,57 **** + error.stack = error.stack.replace(/[^\n]+\n/, stackTitle); + } + } +! export { _running, running, _destroyed, destroyed, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, hasCaptureStackTrace, promise, resetStackTrace, }; + //# sourceMappingURL=utils.js.map +\ No newline at end of file +--- 58,62 ---- + error.stack = error.stack.replace(/[^\n]+\n/, stackTitle); + } + } +! export { _running, running, _destroyed, destroyed, _status, status, _statusP, statusP, resolveStatusP, initLock, AsyncFunction, GeneratorFunction, AsyncGeneratorFunction, hasCaptureStackTrace, _spanId, spanId, spanContext, promise, resetStackTrace, }; + //# sourceMappingURL=utils.js.map +\ No newline at end of file diff --git a/flake.nix b/flake.nix index 1b29287a..9362386f 100644 --- a/flake.nix +++ b/flake.nix @@ -24,6 +24,9 @@ cargo cmake rustPlatform.bindgenHook + valgrind + heaptrack + massif-visualizer ]; NIX_DONT_SET_RPATH = true; NIX_NO_SELF_RPATH = true; diff --git a/package-lock.json b/package-lock.json index 4bf7612a..44a3e475 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "@matrixai/contexts": "^2.0.2", "@matrixai/errors": "^2.1.3", "@matrixai/events": "^4.0.1", - "@matrixai/logger": "^4.0.3", + "@matrixai/id": "^4.0.0", + "@matrixai/logger": "^4.0.4-alpha.5", "@matrixai/resources": "^2.0.1", "@matrixai/timer": "^2.1.1", "ip-num": "^1.5.0" @@ -2027,6 +2028,15 @@ "resolved": "https://registry.npmjs.org/@matrixai/events/-/events-4.0.1.tgz", "integrity": "sha512-75hH7ZTmhM/VXeICXCPiVr/ZxQSoBwXh2HOI3AhD8AGYDDsEJsm4tnDSr/6vT3vS0ryZb3kb9mpAmCeibdrF3w==" }, + "node_modules/@matrixai/id": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@matrixai/id/-/id-4.0.0.tgz", + "integrity": "sha512-xLwYlK4d75wnpfIF+A0XRS5VmX/aDj/4E/XFkwrYsSDxoiWj7DoRRVSs/ryorwZHgufs/kL8aS1eTKadUQRevg==", + "dependencies": { + "multiformats": "^13.3.2", + "uuid": "^8.3.2" + } + }, "node_modules/@matrixai/lint": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/@matrixai/lint/-/lint-0.2.6.tgz", @@ -2248,9 +2258,9 @@ } }, "node_modules/@matrixai/logger": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-4.0.3.tgz", - "integrity": "sha512-cu7e82iwN32H+K8HxsrvrWEYSEj7+RP/iVFhJ4RuacC8/BSOLFOYxry3EchVjrx4FP5G7QP1HnKYXAGpZN/46w==" + "version": "4.0.4-alpha.5", + "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-4.0.4-alpha.5.tgz", + "integrity": "sha512-JVnxhzDfy21Y7cb9ouI7m0/BJAfkdJWhBefH/K76BnaA9SvgApyju4VsP2CRGLokp9jCk+1YBT/Tyf4EJwrdmA==" }, "node_modules/@matrixai/quic-darwin-arm64": { "version": "2.0.9", @@ -7199,6 +7209,11 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/multiformats": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.5.tgz", + "integrity": "sha512-dXsVGtaekmpKMHUngnXkPpXnJU9h8ee2+P85kTETViXcDkQjkWLrEkj/b5pJ23ZhvBlicr9eq3B9IJOa28R70w==" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -9432,7 +9447,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "bin": { "uuid": "dist/bin/uuid" } diff --git a/package.json b/package.json index 2722eb6c..635d338a 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "@matrixai/contexts": "^2.0.2", "@matrixai/errors": "^2.1.3", "@matrixai/events": "^4.0.1", - "@matrixai/logger": "^4.0.3", + "@matrixai/id": "^4.0.0", + "@matrixai/logger": "^4.0.4-alpha.5", "@matrixai/resources": "^2.0.1", "@matrixai/timer": "^2.1.1", "ip-num": "^1.5.0" @@ -95,5 +96,8 @@ "tsx": "^3.12.7", "typedoc": "^0.24.8", "typescript": "^5.1.6" + }, + "overrides": { + "@matrixai/logger": "^4.0.4-alpha.5" } } diff --git a/scripts/memtest.mjs b/scripts/memtest.mjs new file mode 100644 index 00000000..ddb0236e --- /dev/null +++ b/scripts/memtest.mjs @@ -0,0 +1,388 @@ +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import * as peculiarWebcrypto from '@peculiar/webcrypto'; +import * as x509 from '@peculiar/x509'; +import * as events from '../dist/events.js'; +import * as utils from '../dist/utils.js'; +import QUICServer from '../dist/QUICServer.js'; +import QUICClient from '../dist/QUICClient.js'; +import QUICStream from '../dist/QUICStream.js'; + +const webcrypto = new peculiarWebcrypto.Crypto(); +x509.cryptoProvider.set(webcrypto); + +const extendedKeyUsageFlags = { + serverAuth: '1.3.6.1.5.5.7.3.1', + clientAuth: '1.3.6.1.5.5.7.3.2', + codeSigning: '1.3.6.1.5.5.7.3.3', + emailProtection: '1.3.6.1.5.5.7.3.4', + timeStamping: '1.3.6.1.5.5.7.3.8', + ocspSigning: '1.3.6.1.5.5.7.3.9', +}; + +async function generateKeyHMAC() { + const cryptoKey = await webcrypto.subtle.generateKey( + { name: 'HMAC', hash: 'SHA-256' }, + true, + ['sign', 'verify'], + ); + const key = await webcrypto.subtle.exportKey('raw', cryptoKey); + return key; +} + +function socketCleanupFactory() { + const sockets = new Set(); + return { + extractSocket: (thing) => sockets.add(thing.socket), + stopSockets: async () => { + const stopProms = []; + for (const socket of sockets) { + stopProms.push(socket.stop({ force: true })); + } + await Promise.all(stopProms); + }, + sockets, + }; +} + +async function signHMAC(key, data) { + const cryptoKey = await webcrypto.subtle.importKey( + 'raw', + key, + { name: 'HMAC', hash: 'SHA-256' }, + true, + ['sign', 'verify'], + ); + return webcrypto.subtle.sign('HMAC', cryptoKey, data); +} + +async function verifyHMAC(key, data, sig) { + const cryptoKey = await webcrypto.subtle.importKey( + 'raw', + key, + { name: 'HMAC', hash: 'SHA-256' }, + true, + ['sign', 'verify'], + ); + return webcrypto.subtle.verify('HMAC', cryptoKey, sig, data); +} + +async function importPublicKey(publicKey) { + let algorithm; + switch (publicKey.kty) { + case 'RSA': + switch (publicKey.alg) { + case 'RS256': + algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }; + break; + case 'RS384': + algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-384' }; + break; + case 'RS512': + algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-512' }; + break; + default: + throw new Error(`Unsupported algorithm ${publicKey.alg}`); + } + break; + default: + throw new Error(`Unsupported key type ${publicKey.kty}`); + } + return await webcrypto.subtle.importKey('jwk', publicKey, algorithm, true, [ + 'verify', + ]); +} + +async function importPrivateKey(privateKey) { + let algorithm; + switch (privateKey.kty) { + case 'RSA': + switch (privateKey.alg) { + case 'RS256': + algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }; + break; + case 'RS384': + algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-384' }; + break; + case 'RS512': + algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-512' }; + break; + default: + throw new Error(`Unsupported algorithm ${privateKey.alg}`); + } + break; + default: + throw new Error(`Unsupported key type ${privateKey.kty}`); + } + return await webcrypto.subtle.importKey('jwk', privateKey, algorithm, true, [ + 'sign', + ]); +} + +async function generateKeyPairRSA() { + const keyPair = await webcrypto.subtle.generateKey( + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: 'SHA-256', + }, + true, + ['sign', 'verify'], + ); + return { + publicKey: await webcrypto.subtle.exportKey('jwk', keyPair.publicKey), + privateKey: await webcrypto.subtle.exportKey('jwk', keyPair.privateKey), + }; +} + +async function keyPairRSAToPEM(keyPair) { + const publicKey = await importPublicKey(keyPair.publicKey); + const privatekey = await importPrivateKey(keyPair.privateKey); + const publicKeySPKI = await webcrypto.subtle.exportKey('spki', publicKey); + const publicKeySPKIBuffer = Buffer.from(publicKeySPKI); + const publicKeyPEMBody = + publicKeySPKIBuffer + .toString('base64') + .replace(/(.{64})/g, '$1\n') + .trimEnd() + '\n'; + const publicKeyPEM = `-----BEGIN PUBLIC KEY-----\n${publicKeyPEMBody}\n-----END PUBLIC KEY-----\n`; + const privateKeyPKCS8 = await webcrypto.subtle.exportKey('pkcs8', privatekey); + const privateKeyPKCS8Buffer = Buffer.from(privateKeyPKCS8); + const privateKeyPEMBody = + privateKeyPKCS8Buffer + .toString('base64') + .replace(/(.{64})/g, '$1\n') + .trimEnd() + '\n'; + const privateKeyPEM = `-----BEGIN PRIVATE KEY-----\n${privateKeyPEMBody}-----END PRIVATE KEY-----\n`; + return { + publicKey: publicKeyPEM, + privateKey: privateKeyPEM, + }; +} + +async function publicKeyFromPrivateKey(privateKey) { + switch (privateKey.kty) { + case 'RSA': + return { + kty: privateKey.kty, + alg: privateKey.alg, + key_ops: ['verify'], + ext: privateKey.ext, + n: privateKey.n, + e: privateKey.e, + }; + default: + throw new Error(`Unsupported key type ${privateKey.kty}`); + } +} + +async function generateCertificate({ + certId, + subjectKeyPair, + issuerPrivateKey, + duration, + subjectAttrsExtra = [], + issuerAttrsExtra = [], + now = new Date(), +}) { + const subjectPublicCryptoKey = await importPublicKey( + subjectKeyPair.publicKey, + ); + const subjectPrivateCryptoKey = await importPrivateKey( + subjectKeyPair.privateKey, + ); + const issuerPrivateCryptoKey = await importPrivateKey(issuerPrivateKey); + if (duration < 0) { + throw new RangeError('`duration` must be positive'); + } + const notBeforeDate = new Date(now.getTime() - (now.getTime() % 1000)); + const notAfterDate = new Date(now.getTime() - (now.getTime() % 1000)); + notAfterDate.setSeconds(notAfterDate.getSeconds() + duration); + if (notBeforeDate < new Date(0)) { + throw new RangeError( + '`notBeforeDate` cannot be before 1970-01-01T00:00:00Z', + ); + } + if (notAfterDate > new Date(new Date('2050').getTime() - 1)) { + throw new RangeError('`notAfterDate` cannot be after 2049-12-31T23:59:59Z'); + } + const subjectNodeId = await webcrypto.subtle.digest( + 'SHA-256', + await webcrypto.subtle.exportKey('spki', subjectPublicCryptoKey), + ); + const issuerPublicKey = await publicKeyFromPrivateKey(issuerPrivateKey); + const issuerPublicCryptoKey = await importPublicKey(issuerPublicKey); + const issuerNodeId = await webcrypto.subtle.digest( + 'SHA-256', + await webcrypto.subtle.exportKey('spki', issuerPublicCryptoKey), + ); + const serialNumber = certId; + const subjectNodeIdEncoded = Buffer.from(subjectNodeId).toString('hex'); + const issuerNodeIdEncoded = Buffer.from(issuerNodeId).toString('hex'); + const subjectAttrs = [ + { CN: [subjectNodeIdEncoded] }, + ...subjectAttrsExtra.filter((attr) => !('CN' in attr)), + ]; + const issuerAttrs = [ + { CN: [issuerNodeIdEncoded] }, + ...issuerAttrsExtra.filter((attr) => !('CN' in attr)), + ]; + const signingAlgorithm = issuerPrivateCryptoKey.algorithm; + const certConfig = { + serialNumber, + notBefore: notBeforeDate, + notAfter: notAfterDate, + subject: subjectAttrs, + issuer: issuerAttrs, + signingAlgorithm, + publicKey: subjectPublicCryptoKey, + signingKey: subjectPrivateCryptoKey, + extensions: [ + new x509.BasicConstraintsExtension(true), + new x509.KeyUsagesExtension( + x509.KeyUsageFlags.keyCertSign | + x509.KeyUsageFlags.cRLSign | + x509.KeyUsageFlags.digitalSignature | + x509.KeyUsageFlags.nonRepudiation | + x509.KeyUsageFlags.keyAgreement | + x509.KeyUsageFlags.keyEncipherment | + x509.KeyUsageFlags.dataEncipherment, + ), + new x509.ExtendedKeyUsageExtension([ + extendedKeyUsageFlags.serverAuth, + extendedKeyUsageFlags.clientAuth, + extendedKeyUsageFlags.codeSigning, + extendedKeyUsageFlags.emailProtection, + extendedKeyUsageFlags.timeStamping, + extendedKeyUsageFlags.ocspSigning, + ]), + new x509.SubjectAlternativeNameExtension([ + { type: 'dns', value: subjectNodeIdEncoded }, + { type: 'dns', value: 'localhost' }, + { type: 'dns', value: '127.0.0.1' }, + { type: 'dns', value: '::1' }, + { type: 'ip', value: '127.0.0.1' }, + { type: 'ip', value: '::1' }, + ]), + await x509.SubjectKeyIdentifierExtension.create(subjectPublicCryptoKey), + ], + }; + certConfig.signingKey = issuerPrivateCryptoKey; + return await x509.X509CertificateGenerator.create(certConfig); +} + +async function generateTLSConfig() { + let leafKeyPair; + let leafKeyPairPEM; + let caKeyPair; + let caKeyPairPEM; + + leafKeyPair = await generateKeyPairRSA(); + leafKeyPairPEM = await keyPairRSAToPEM(leafKeyPair); + caKeyPair = await generateKeyPairRSA(); + caKeyPairPEM = await keyPairRSAToPEM(caKeyPair); + + const caCert = await generateCertificate({ + certId: '0', + issuerPrivateKey: caKeyPair.privateKey, + subjectKeyPair: caKeyPair, + duration: 60 * 60 * 24 * 365 * 10, + }); + const leafCert = await generateCertificate({ + certId: '1', + issuerPrivateKey: caKeyPair.privateKey, + subjectKeyPair: leafKeyPair, + duration: 60 * 60 * 24 * 365 * 10, + }); + return { + leafKeyPair, + leafKeyPairPEM, + leafCert, + leafCertPEM: leafCert.toString('pem') + '\n', + caKeyPair, + caKeyPairPEM, + caCert, + caCertPEM: caCert.toString('pem') + '\n', + }; +} + +/* eslint-disable no-console */ +const main = async () => { + const logger = new Logger(`${QUICStream.name} Test`, LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const key = await generateKeyHMAC(); + let socketCleanMethods = socketCleanupFactory(); + const serverCrypto = { + sign: signHMAC, + verify: verifyHMAC, + }; + const clientCrypto = { + randomBytes: (data) => webcrypto.getRandomValues(new Uint8Array(data)), + }; + const message = Buffer.from('The Quick Brown Fox Jumped Over The Lazy Dog'); + const connectionEventProm = utils.promise(); + const tlsConfig = await generateTLSConfig(); + const server = new QUICServer({ + crypto: { + key, + ops: serverCrypto, + }, + logger: logger.getChild(QUICServer.name), + config: { + key: tlsConfig.leafKeyPairPEM.privateKey, + cert: tlsConfig.leafCertPEM, + verifyPeer: false, + }, + }); + socketCleanMethods.extractSocket(server); + server.addEventListener(events.EventQUICServerConnection.name, (e) => + connectionEventProm.resolveP(e), + ); + await server.start({ host: '127.0.0.1' }); + const client = await QUICClient.createQUICClient({ + host: '127.0.0.1', + port: server.port, + localHost: '127.0.0.1', + crypto: { + ops: clientCrypto, + }, + logger: logger.getChild(QUICClient.name), + config: { + verifyPeer: false, + }, + }); + socketCleanMethods.extractSocket(client); + const conn = (await connectionEventProm.p).detail; + const activeServerStreams = []; + conn.addEventListener( + events.EventQUICConnectionStream.name, + (streamEvent) => { + const stream = streamEvent.detail; + const streamProm = stream.readable.pipeTo(stream.writable); + activeServerStreams.push(streamProm); + }, + ); + + for (let i = 0; i < 1000; i++) { + console.error('loop'); + const stream = client.connection.newStream(); + const writer = stream.writable.getWriter(); + await writer.write(message); + await writer.close(); + const reader = stream.readable.getReader(); + let finished = false; + while (!finished) { + finished = (await reader.read()).done; + } + } + await Promise.all([activeServerStreams]); + + await client.destroy({ force: true }); + await server.stop({ force: true }); + console.error('Test passed!'); +}; + +void main(); diff --git a/src/errors.ts b/src/errors.ts index d70d71f4..af889c68 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,4 +1,4 @@ -import type { POJO } from '@matrixai/errors'; +import type { POJO } from './types.js'; import type { ConnectionError, CryptoError } from './native/types.js'; import { AbstractError } from '@matrixai/errors'; @@ -6,15 +6,15 @@ class ErrorQUIC extends AbstractError { static description = 'QUIC error'; } -class ErrorQUICUndefinedBehaviour extends AbstractError { +class ErrorQUICUndefinedBehaviour extends ErrorQUIC { static description = 'You should never see this error'; } -class ErrorQUICHostInvalid extends AbstractError { +class ErrorQUICHostInvalid extends ErrorQUIC { static description = 'Host provided was not valid'; } -class ErrorQUICPortInvalid extends AbstractError { +class ErrorQUICPortInvalid extends ErrorQUIC { static description = 'Port provided was not valid'; } diff --git a/src/native/napi/config.rs b/src/native/napi/config.rs index 96b6df6b..9995b99d 100644 --- a/src/native/napi/config.rs +++ b/src/native/napi/config.rs @@ -1,364 +1,319 @@ -use napi_derive::napi; use napi::bindgen_prelude::*; +use napi_derive::napi; #[napi] -pub struct Config(pub (crate) quiche::Config); +pub struct Config(pub(crate) quiche::Config); /// Equivalent to quiche::CongestionControlAlgorithm #[napi] pub enum CongestionControlAlgorithm { - Reno = 0, - CUBIC = 1, - BBR = 2, + Reno = 0, + CUBIC = 1, + BBR = 2, } impl From for quiche::CongestionControlAlgorithm { - fn from(algo: CongestionControlAlgorithm) -> Self { - match algo { - CongestionControlAlgorithm::Reno => quiche::CongestionControlAlgorithm::Reno, - CongestionControlAlgorithm::CUBIC => quiche::CongestionControlAlgorithm::CUBIC, - CongestionControlAlgorithm::BBR => quiche::CongestionControlAlgorithm::BBR, + fn from(algo: CongestionControlAlgorithm) -> Self { + match algo { + CongestionControlAlgorithm::Reno => quiche::CongestionControlAlgorithm::Reno, + CongestionControlAlgorithm::CUBIC => quiche::CongestionControlAlgorithm::CUBIC, + CongestionControlAlgorithm::BBR => quiche::CongestionControlAlgorithm::BBR, + } } - } } impl From for CongestionControlAlgorithm { - fn from(item: quiche::CongestionControlAlgorithm) -> Self { - match item { - quiche::CongestionControlAlgorithm::Reno => CongestionControlAlgorithm::Reno, - quiche::CongestionControlAlgorithm::CUBIC => CongestionControlAlgorithm::CUBIC, - quiche::CongestionControlAlgorithm::BBR => CongestionControlAlgorithm::BBR, + fn from(item: quiche::CongestionControlAlgorithm) -> Self { + match item { + quiche::CongestionControlAlgorithm::Reno => CongestionControlAlgorithm::Reno, + quiche::CongestionControlAlgorithm::CUBIC => CongestionControlAlgorithm::CUBIC, + quiche::CongestionControlAlgorithm::BBR => CongestionControlAlgorithm::BBR, + } } - } } #[napi] impl Config { + #[napi(constructor)] + pub fn new() -> Result { + let config = quiche::Config::new(quiche::PROTOCOL_VERSION) + .or_else(|err| Err(napi::Error::from_reason(err.to_string())))?; + return Ok(Config(config)); + } - #[napi(constructor)] - pub fn new() -> Result { - let config = quiche::Config::new( - quiche::PROTOCOL_VERSION - ).or_else( - |err| Err(napi::Error::from_reason(err.to_string())) - )?; - return Ok(Config(config)); - } - - /// Creates configuration with custom TLS context - /// Servers must be setup with a key and cert - #[napi(factory)] - pub fn with_boring_ssl_ctx( - verify_peer: bool, - verify_allow_fail: bool, - ca: Option, - key: Option>, - cert: Option>, - sigalgs: Option, - ) -> Result { - let mut ssl_ctx_builder = boring::ssl::SslContextBuilder::new( - boring::ssl::SslMethod::tls(), - ).or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - )?; - let verify_value = if verify_peer { - boring::ssl::SslVerifyMode::PEER | boring::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT - } else { - boring::ssl::SslVerifyMode::NONE - }; - ssl_ctx_builder.set_verify_callback(verify_value, move |pre_verify, _| { - // Override any validation errors, this is needed so we can request certs but validate them - // manually. It's essentially allowing insecure certificates - if verify_allow_fail { - true - } else { - pre_verify - } - }); - // Setup all CA certificates - if let Some(ca) = ca { - let mut x509_store_builder = boring::x509::store::X509StoreBuilder::new() - .or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - )?; - let x509_certs = boring::x509::X509::stack_from_pem( - &ca.to_vec() - ).or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - )?; - for x509 in x509_certs.into_iter() { - x509_store_builder.add_cert(x509) - .or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - )?; - } - let x509_store = x509_store_builder.build(); - ssl_ctx_builder.set_verify_cert_store(x509_store) - .or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - )?; - } - // Setup all certificates and keys - if let (Some(key), Some(cert)) = (key, cert) { - // Right now the boring crate does not provide a straight forward way of - // setting multiple independent certificate chains. So we are just picking - // the first key and cert pair. - let (k, c) = (key[0].to_vec(), cert[0].to_vec()); - let private_key = boring::pkey::PKey::private_key_from_pem(&k) - .or_else( - |err| Err(Error::from_reason(err.to_string())) - )?; - ssl_ctx_builder.set_private_key(&private_key).or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - )?; - let x509_cert_chain = boring::x509::X509::stack_from_pem( - &c - ).or_else( - |err| Err(napi::Error::from_reason(err.to_string())) - )?; - for (i, cert) in x509_cert_chain.iter().enumerate() { - if i == 0 { - ssl_ctx_builder.set_certificate(cert,).or_else( - |err| Err(Error::from_reason(err.to_string())) - )?; + /// Creates configuration with custom TLS context + /// Servers must be setup with a key and cert + #[napi(factory)] + pub fn with_boring_ssl_ctx( + verify_peer: bool, + verify_allow_fail: bool, + ca: Option, + key: Option>, + cert: Option>, + sigalgs: Option, + ) -> Result { + let mut ssl_ctx_builder = + boring::ssl::SslContextBuilder::new(boring::ssl::SslMethod::tls()) + .or_else(|e| Err(napi::Error::from_reason(e.to_string())))?; + let verify_value = if verify_peer { + boring::ssl::SslVerifyMode::PEER | boring::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT } else { - ssl_ctx_builder.add_extra_chain_cert( - cert.clone(), - ).or_else( - |err| Err(Error::from_reason(err.to_string())) - )?; + boring::ssl::SslVerifyMode::NONE + }; + ssl_ctx_builder.set_verify_callback(verify_value, move |pre_verify, _| { + // Override any validation errors, this is needed so we can request certs but validate them + // manually. It's essentially allowing insecure certificates + if verify_allow_fail { + true + } else { + pre_verify + } + }); + // Setup all CA certificates + if let Some(ca) = ca { + let mut x509_store_builder = boring::x509::store::X509StoreBuilder::new() + .or_else(|e| Err(napi::Error::from_reason(e.to_string())))?; + let x509_certs = boring::x509::X509::stack_from_pem(&ca.to_vec()) + .or_else(|e| Err(napi::Error::from_reason(e.to_string())))?; + for x509 in x509_certs.into_iter() { + x509_store_builder + .add_cert(x509) + .or_else(|e| Err(napi::Error::from_reason(e.to_string())))?; + } + let x509_store = x509_store_builder.build(); + ssl_ctx_builder + .set_verify_cert_store(x509_store) + .or_else(|e| Err(napi::Error::from_reason(e.to_string())))?; } - } - } - // Setup supported signature algorithms - if let Some(sigalgs) = sigalgs { - ssl_ctx_builder.set_sigalgs_list(&sigalgs).or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - )?; - } - let config = quiche::Config::with_boring_ssl_ctx_builder( - quiche::PROTOCOL_VERSION, - ssl_ctx_builder, - ).or_else( - |e| Err(Error::from_reason(e.to_string())) - )?; - return Ok(Config(config)); - } - - #[napi] - pub fn load_cert_chain_from_pem_file(&mut self, file: String) -> Result<()> { - return self.0.load_cert_chain_from_pem_file(&file).or_else( - |err| Err(Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn load_priv_key_from_pem_file(&mut self, file: String) -> Result<()> { - return self.0.load_priv_key_from_pem_file(&file).or_else( - |err| Err(Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn load_verify_locations_from_file(&mut self, file: String) -> Result<()> { - return self.0.load_verify_locations_from_file(&file).or_else( - |err| Err(Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn load_verify_locations_from_directory(&mut self, dir: String) -> Result<()> { - return self.0.load_verify_locations_from_directory(&dir).or_else( - |err| Err(Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn verify_peer(&mut self, verify: bool) -> () { - return self.0.verify_peer(verify); - } - - #[napi] - pub fn grease(&mut self, grease: bool) -> () { - return self.0.grease(grease); - } - - #[napi] - pub fn log_keys(&mut self) -> () { - return self.0.log_keys(); - } - - #[napi] - pub fn set_ticket_key(&mut self, key: Uint8Array) -> Result<()> { - return self.0.set_ticket_key(&key).or_else( - |err| Err(Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn enable_early_data(&mut self) -> () { - return self.0.enable_early_data(); - } - - #[napi] - pub fn set_application_protos( - &mut self, - protos_list: Vec, - ) -> Result<()> { - let protos_list = protos_list.iter().map( - |proto| proto.as_bytes() - ).collect::>(); - return self.0.set_application_protos(&protos_list).or_else( - |err| Err(Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn set_application_protos_wire_format( - &mut self, - protos: Uint8Array - ) -> Result<()> { - return self.0.set_application_protos_wire_format(&protos).or_else( - |err| Err(Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn set_max_idle_timeout(&mut self, timeout: i64) -> () { - self.0.set_max_idle_timeout(timeout as u64); - } - - #[napi] - pub fn set_max_recv_udp_payload_size(&mut self, size: i64) -> () { - return self.0.set_max_recv_udp_payload_size( - size as usize - ); - } - - #[napi] - pub fn set_max_send_udp_payload_size(&mut self, size: i64) -> () { - return self.0.set_max_send_udp_payload_size( - size as usize - ); - } - - #[napi] - pub fn set_initial_max_data(&mut self, v: i64) -> () { - return self.0.set_initial_max_data(v as u64); - } - - #[napi] - pub fn set_initial_max_stream_data_bidi_local(&mut self, v: i64) -> () { - return self.0.set_initial_max_stream_data_bidi_local(v as u64); - } - - #[napi] - pub fn set_initial_max_stream_data_bidi_remote(&mut self, v: i64) -> () { - return self.0.set_initial_max_stream_data_bidi_remote(v as u64); - } - - #[napi] - pub fn set_initial_max_stream_data_uni(&mut self, v: i64) -> () { - return self.0.set_initial_max_stream_data_uni(v as u64); - } - - #[napi] - pub fn set_initial_max_streams_bidi(&mut self, v: i64) -> () { - return self.0.set_initial_max_streams_bidi(v as u64); - } - - #[napi] - pub fn set_initial_max_streams_uni( - &mut self, - v: i64 - ) -> () { - return self.0.set_initial_max_streams_uni( - v as u64 - ); - } - - #[napi] - pub fn set_ack_delay_exponent(&mut self, v: i64) -> () { - return self.0.set_ack_delay_exponent( - v as u64 - ); - } - - #[napi] - pub fn set_max_ack_delay(&mut self, v: i64) -> () { - return self.0.set_max_ack_delay( - v as u64 - ); - } - - #[napi] - pub fn set_active_connection_id_limit( - &mut self, - v: i64 - ) -> () { - return self.0.set_active_connection_id_limit(v as u64); - } - - #[napi] - pub fn set_disable_active_migration(&mut self, v: bool) -> () { - return self.0.set_disable_active_migration(v); - } - - #[napi] - pub fn set_cc_algorithm_name(&mut self, name: String) -> Result<()> { - return self.0.set_cc_algorithm_name(&name).or_else( - |err| Err(Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn set_cc_algorithm(&mut self, algo: CongestionControlAlgorithm) -> () { - return self.0.set_cc_algorithm(algo.into()); - } - - #[napi] - pub fn enable_hystart(&mut self, v: bool) { - return self.0.enable_hystart(v); - } - - #[napi] - pub fn enable_pacing(&mut self, v: bool) { - return self.0.enable_pacing(v); - } - - #[napi] - pub fn enable_dgram( - &mut self, - enabled: bool, - recv_queue_len: i64, - send_queue_len: i64, - ) -> () { - return self.0.enable_dgram( - enabled, - recv_queue_len as usize, - send_queue_len as usize - ); - } - - #[napi] - pub fn set_max_stream_window(&mut self, v: i64) { - return self.0.set_max_stream_window(v as u64); - } - - #[napi] - pub fn set_max_connection_window(&mut self, v: i64) -> () { - return self.0.set_max_connection_window(v as u64); - } - - #[napi] - pub fn set_stateless_reset_token(&mut self, v: Option) -> () { - return self.0.set_stateless_reset_token( - v.map(|v| v.get_u128().1) - ); - } - - #[napi] - pub fn set_disable_dcid_reuse(&mut self, v: bool) -> () { - return self.0.set_disable_dcid_reuse(v); - } + // Setup all certificates and keys + if let (Some(key), Some(cert)) = (key, cert) { + // Right now the boring crate does not provide a straight forward way of + // setting multiple independent certificate chains. So we are just picking + // the first key and cert pair. + let (k, c) = (key[0].to_vec(), cert[0].to_vec()); + let private_key = boring::pkey::PKey::private_key_from_pem(&k) + .or_else(|err| Err(Error::from_reason(err.to_string())))?; + ssl_ctx_builder + .set_private_key(&private_key) + .or_else(|e| Err(napi::Error::from_reason(e.to_string())))?; + let x509_cert_chain = boring::x509::X509::stack_from_pem(&c) + .or_else(|err| Err(napi::Error::from_reason(err.to_string())))?; + for (i, cert) in x509_cert_chain.iter().enumerate() { + if i == 0 { + ssl_ctx_builder + .set_certificate(cert) + .or_else(|err| Err(Error::from_reason(err.to_string())))?; + } else { + ssl_ctx_builder + .add_extra_chain_cert(cert.clone()) + .or_else(|err| Err(Error::from_reason(err.to_string())))?; + } + } + } + // Setup supported signature algorithms + if let Some(sigalgs) = sigalgs { + ssl_ctx_builder + .set_sigalgs_list(&sigalgs) + .or_else(|e| Err(napi::Error::from_reason(e.to_string())))?; + } + let config = + quiche::Config::with_boring_ssl_ctx_builder(quiche::PROTOCOL_VERSION, ssl_ctx_builder) + .or_else(|e| Err(Error::from_reason(e.to_string())))?; + return Ok(Config(config)); + } + + #[napi] + pub fn load_cert_chain_from_pem_file(&mut self, file: String) -> Result<()> { + return self + .0 + .load_cert_chain_from_pem_file(&file) + .or_else(|err| Err(Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn load_priv_key_from_pem_file(&mut self, file: String) -> Result<()> { + return self + .0 + .load_priv_key_from_pem_file(&file) + .or_else(|err| Err(Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn load_verify_locations_from_file(&mut self, file: String) -> Result<()> { + return self + .0 + .load_verify_locations_from_file(&file) + .or_else(|err| Err(Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn load_verify_locations_from_directory(&mut self, dir: String) -> Result<()> { + return self + .0 + .load_verify_locations_from_directory(&dir) + .or_else(|err| Err(Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn verify_peer(&mut self, verify: bool) -> () { + return self.0.verify_peer(verify); + } + + #[napi] + pub fn grease(&mut self, grease: bool) -> () { + return self.0.grease(grease); + } + + #[napi] + pub fn log_keys(&mut self) -> () { + return self.0.log_keys(); + } + + #[napi] + pub fn set_ticket_key(&mut self, key: Uint8Array) -> Result<()> { + return self + .0 + .set_ticket_key(&key) + .or_else(|err| Err(Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn enable_early_data(&mut self) -> () { + return self.0.enable_early_data(); + } + + #[napi] + pub fn set_application_protos(&mut self, protos_list: Vec) -> Result<()> { + let protos_list = protos_list + .iter() + .map(|proto| proto.as_bytes()) + .collect::>(); + return self + .0 + .set_application_protos(&protos_list) + .or_else(|err| Err(Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn set_application_protos_wire_format(&mut self, protos: Uint8Array) -> Result<()> { + return self + .0 + .set_application_protos_wire_format(&protos) + .or_else(|err| Err(Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn set_max_idle_timeout(&mut self, timeout: i64) -> () { + self.0.set_max_idle_timeout(timeout as u64); + } + + #[napi] + pub fn set_max_recv_udp_payload_size(&mut self, size: i64) -> () { + return self.0.set_max_recv_udp_payload_size(size as usize); + } + + #[napi] + pub fn set_max_send_udp_payload_size(&mut self, size: i64) -> () { + return self.0.set_max_send_udp_payload_size(size as usize); + } + + #[napi] + pub fn set_initial_max_data(&mut self, v: i64) -> () { + return self.0.set_initial_max_data(v as u64); + } + + #[napi] + pub fn set_initial_max_stream_data_bidi_local(&mut self, v: i64) -> () { + return self.0.set_initial_max_stream_data_bidi_local(v as u64); + } + + #[napi] + pub fn set_initial_max_stream_data_bidi_remote(&mut self, v: i64) -> () { + return self.0.set_initial_max_stream_data_bidi_remote(v as u64); + } + + #[napi] + pub fn set_initial_max_stream_data_uni(&mut self, v: i64) -> () { + return self.0.set_initial_max_stream_data_uni(v as u64); + } + + #[napi] + pub fn set_initial_max_streams_bidi(&mut self, v: i64) -> () { + return self.0.set_initial_max_streams_bidi(v as u64); + } + + #[napi] + pub fn set_initial_max_streams_uni(&mut self, v: i64) -> () { + return self.0.set_initial_max_streams_uni(v as u64); + } + + #[napi] + pub fn set_ack_delay_exponent(&mut self, v: i64) -> () { + return self.0.set_ack_delay_exponent(v as u64); + } + + #[napi] + pub fn set_max_ack_delay(&mut self, v: i64) -> () { + return self.0.set_max_ack_delay(v as u64); + } + + #[napi] + pub fn set_active_connection_id_limit(&mut self, v: i64) -> () { + return self.0.set_active_connection_id_limit(v as u64); + } + + #[napi] + pub fn set_disable_active_migration(&mut self, v: bool) -> () { + return self.0.set_disable_active_migration(v); + } + + #[napi] + pub fn set_cc_algorithm_name(&mut self, name: String) -> Result<()> { + return self + .0 + .set_cc_algorithm_name(&name) + .or_else(|err| Err(Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn set_cc_algorithm(&mut self, algo: CongestionControlAlgorithm) -> () { + return self.0.set_cc_algorithm(algo.into()); + } + + #[napi] + pub fn enable_hystart(&mut self, v: bool) { + return self.0.enable_hystart(v); + } + + #[napi] + pub fn enable_pacing(&mut self, v: bool) { + return self.0.enable_pacing(v); + } + + #[napi] + pub fn enable_dgram(&mut self, enabled: bool, recv_queue_len: i64, send_queue_len: i64) -> () { + return self + .0 + .enable_dgram(enabled, recv_queue_len as usize, send_queue_len as usize); + } + + #[napi] + pub fn set_max_stream_window(&mut self, v: i64) { + return self.0.set_max_stream_window(v as u64); + } + + #[napi] + pub fn set_max_connection_window(&mut self, v: i64) -> () { + return self.0.set_max_connection_window(v as u64); + } + + #[napi] + pub fn set_stateless_reset_token(&mut self, v: Option) -> () { + return self.0.set_stateless_reset_token(v.map(|v| v.get_u128().1)); + } + + #[napi] + pub fn set_disable_dcid_reuse(&mut self, v: bool) -> () { + return self.0.set_disable_dcid_reuse(v); + } } diff --git a/src/native/napi/connection.rs b/src/native/napi/connection.rs index 04a2518c..084ece37 100644 --- a/src/native/napi/connection.rs +++ b/src/native/napi/connection.rs @@ -1,924 +1,831 @@ -use std::io; -use std::fs::File; -use std::net::{ - SocketAddr, - Ipv4Addr, - Ipv6Addr, -}; -use napi_derive::napi; -use napi::bindgen_prelude::*; -use serde::{Serialize, Deserialize}; use crate::config; -use crate::stream; use crate::path; +use crate::stream; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; #[napi] pub enum ConnectionErrorCode { - NoError = 0x0, - InternalError = 0x1, - ConnectionRefused = 0x2, - FlowControlError = 0x3, - StreamLimitError = 0x4, - StreamStateError = 0x5, - FinalSizeError = 0x6, - FrameEncodingError = 0x7, - TransportParameterError = 0x8, - ConnectionIdLimitError = 0x9, - ProtocolViolation = 0xa, - InvalidToken = 0xb, - ApplicationError = 0xc, - CryptoBufferExceeded = 0xd, - KeyUpdateError = 0xe, - AEADLimitReached = 0xf, - NoViablePath = 0x10, + NoError = 0x0, + InternalError = 0x1, + ConnectionRefused = 0x2, + FlowControlError = 0x3, + StreamLimitError = 0x4, + StreamStateError = 0x5, + FinalSizeError = 0x6, + FrameEncodingError = 0x7, + TransportParameterError = 0x8, + ConnectionIdLimitError = 0x9, + ProtocolViolation = 0xa, + InvalidToken = 0xb, + ApplicationError = 0xc, + CryptoBufferExceeded = 0xd, + KeyUpdateError = 0xe, + AEADLimitReached = 0xf, + NoViablePath = 0x10, } #[napi(object)] pub struct ConnectionError { - pub is_app: bool, - pub error_code: i64, - pub reason: Uint8Array, + pub is_app: bool, + pub error_code: i64, + pub reason: Uint8Array, } impl From for ConnectionError { - fn from(err: quiche::ConnectionError) -> Self { - return ConnectionError { - is_app: err.is_app, - error_code: err.error_code as i64, - reason: Uint8Array::new(err.reason), - }; - } + fn from(err: quiche::ConnectionError) -> Self { + return ConnectionError { + is_app: err.is_app, + error_code: err.error_code as i64, + reason: Uint8Array::new(err.reason), + }; + } } #[napi(object)] pub struct Stats { - pub recv: i64, - pub sent: i64, - pub lost: i64, - pub retrans: i64, - pub sent_bytes: i64, - pub recv_bytes: i64, - pub lost_bytes: i64, - pub stream_retrans_bytes: i64, - pub paths_count: i64, - pub peer_max_idle_timeout: i64, - pub peer_max_udp_payload_size: i64, - pub peer_initial_max_data: i64, - pub peer_initial_max_stream_data_bidi_local: i64, - pub peer_initial_max_stream_data_bidi_remote: i64, - pub peer_initial_max_stream_data_uni: i64, - pub peer_initial_max_streams_bidi: i64, - pub peer_initial_max_streams_uni: i64, - pub peer_ack_delay_exponent: i64, - pub peer_max_ack_delay: i64, - pub peer_disable_active_migration: bool, - pub peer_active_conn_id_limit: i64, - pub peer_max_datagram_frame_size: Option, + pub recv: i64, + pub sent: i64, + pub lost: i64, + pub retrans: i64, + pub sent_bytes: i64, + pub recv_bytes: i64, + pub lost_bytes: i64, + pub stream_retrans_bytes: i64, + pub paths_count: i64, + pub peer_max_idle_timeout: i64, + pub peer_max_udp_payload_size: i64, + pub peer_initial_max_data: i64, + pub peer_initial_max_stream_data_bidi_local: i64, + pub peer_initial_max_stream_data_bidi_remote: i64, + pub peer_initial_max_stream_data_uni: i64, + pub peer_initial_max_streams_bidi: i64, + pub peer_initial_max_streams_uni: i64, + pub peer_ack_delay_exponent: i64, + pub peer_max_ack_delay: i64, + pub peer_disable_active_migration: bool, + pub peer_active_conn_id_limit: i64, + pub peer_max_datagram_frame_size: Option, } impl From for Stats { - fn from(stats: quiche::Stats) -> Self { - return Stats { - recv: stats.recv as i64, - sent: stats.sent as i64, - lost: stats.lost as i64, - retrans: stats.retrans as i64, - sent_bytes: stats.sent_bytes as i64, - recv_bytes: stats.recv_bytes as i64, - lost_bytes: stats.lost_bytes as i64, - stream_retrans_bytes: stats.stream_retrans_bytes as i64, - paths_count: stats.paths_count as i64, - peer_max_idle_timeout: stats.peer_max_idle_timeout as i64, - peer_max_udp_payload_size: stats.peer_max_udp_payload_size as i64, - peer_initial_max_data: stats.peer_initial_max_data as i64, - peer_initial_max_stream_data_bidi_local: stats.peer_initial_max_stream_data_bidi_local as i64, - peer_initial_max_stream_data_bidi_remote: stats.peer_initial_max_stream_data_bidi_remote as i64, - peer_initial_max_stream_data_uni: stats.peer_initial_max_stream_data_uni as i64, - peer_initial_max_streams_bidi: stats.peer_initial_max_streams_bidi as i64, - peer_initial_max_streams_uni: stats.peer_initial_max_streams_uni as i64, - peer_ack_delay_exponent: stats.peer_ack_delay_exponent as i64, - peer_max_ack_delay: stats.peer_max_ack_delay as i64, - peer_disable_active_migration: stats.peer_disable_active_migration, - peer_active_conn_id_limit: stats.peer_active_conn_id_limit as i64, - peer_max_datagram_frame_size: stats.peer_max_datagram_frame_size.map(|v| v as i64), - }; - } + fn from(stats: quiche::Stats) -> Self { + return Stats { + recv: stats.recv as i64, + sent: stats.sent as i64, + lost: stats.lost as i64, + retrans: stats.retrans as i64, + sent_bytes: stats.sent_bytes as i64, + recv_bytes: stats.recv_bytes as i64, + lost_bytes: stats.lost_bytes as i64, + stream_retrans_bytes: stats.stream_retrans_bytes as i64, + paths_count: stats.paths_count as i64, + peer_max_idle_timeout: stats.peer_max_idle_timeout as i64, + peer_max_udp_payload_size: stats.peer_max_udp_payload_size as i64, + peer_initial_max_data: stats.peer_initial_max_data as i64, + peer_initial_max_stream_data_bidi_local: stats.peer_initial_max_stream_data_bidi_local + as i64, + peer_initial_max_stream_data_bidi_remote: stats.peer_initial_max_stream_data_bidi_remote + as i64, + peer_initial_max_stream_data_uni: stats.peer_initial_max_stream_data_uni as i64, + peer_initial_max_streams_bidi: stats.peer_initial_max_streams_bidi as i64, + peer_initial_max_streams_uni: stats.peer_initial_max_streams_uni as i64, + peer_ack_delay_exponent: stats.peer_ack_delay_exponent as i64, + peer_max_ack_delay: stats.peer_max_ack_delay as i64, + peer_disable_active_migration: stats.peer_disable_active_migration, + peer_active_conn_id_limit: stats.peer_active_conn_id_limit as i64, + peer_max_datagram_frame_size: stats.peer_max_datagram_frame_size.map(|v| v as i64), + }; + } } /// Equivalent to quiche::Shutdown enum #[napi] pub enum Shutdown { - Read = 0, - Write = 1 + Read = 0, + Write = 1, } impl From for quiche::Shutdown { - fn from(shutdown: Shutdown) -> Self { - match shutdown { - Shutdown::Read => quiche::Shutdown::Read, - Shutdown::Write => quiche::Shutdown::Write, + fn from(shutdown: Shutdown) -> Self { + match shutdown { + Shutdown::Read => quiche::Shutdown::Read, + Shutdown::Write => quiche::Shutdown::Write, + } } - } } impl From for Shutdown { - fn from(item: quiche::Shutdown) -> Self { - match item { - quiche::Shutdown::Read => Shutdown::Read, - quiche::Shutdown::Write => Shutdown::Write, + fn from(item: quiche::Shutdown) -> Self { + match item { + quiche::Shutdown::Read => Shutdown::Read, + quiche::Shutdown::Write => Shutdown::Write, + } } - } } #[napi(object)] #[derive(Serialize, Deserialize)] pub struct HostPort { - pub host: String, - pub port: u16, + pub host: String, + pub port: u16, } impl TryFrom for SocketAddr { - type Error = io::Error; - fn try_from(host: HostPort) -> io::Result { - if let Ok(ipv4) = host.host.parse::() { - return Ok(SocketAddr::new(ipv4.into(), host.port)); + type Error = io::Error; + fn try_from(host: HostPort) -> io::Result { + if let Ok(ipv4) = host.host.parse::() { + return Ok(SocketAddr::new(ipv4.into(), host.port)); + } + + if let Ok(ipv6) = host.host.parse::() { + return Ok(SocketAddr::new(ipv6.into(), host.port)); + } + + Err(io::Error::new( + io::ErrorKind::Other, + "Could not convert host to socket address", + )) } - - if let Ok(ipv6) = host.host.parse::() { - return Ok(SocketAddr::new(ipv6.into(), host.port)); - } - - Err(io::Error::new( - io::ErrorKind::Other, - "Could not convert host to socket address", - )) - } } impl From for HostPort { - fn from(socket_addr: SocketAddr) -> Self { - HostPort { - host: socket_addr.ip().to_string(), - port: socket_addr.port(), + fn from(socket_addr: SocketAddr) -> Self { + HostPort { + host: socket_addr.ip().to_string(), + port: socket_addr.port(), + } } - } } #[napi(object)] pub struct SendInfo { - /// The local address the packet should be sent from. - pub from: HostPort, - /// The remote address the packet should be sent to. - pub to: HostPort, - /// The time to send the packet out for pacing. - pub at: External, + /// The local address the packet should be sent from. + pub from: HostPort, + /// The remote address the packet should be sent to. + pub to: HostPort, + /// The time to send the packet out for pacing. + pub at: External, } #[napi(object)] pub struct RecvInfo { - /// The remote address the packet was received from. - pub from: HostPort, - /// The local address the packet was sent to. - pub to: HostPort, + /// The remote address the packet was received from. + pub from: HostPort, + /// The local address the packet was sent to. + pub to: HostPort, } #[napi] -pub struct Connection(pub (crate) quiche::Connection); +pub struct Connection(pub(crate) quiche::Connection); #[napi] impl Connection { + /// Creates QUIC Client Connection + /// + /// This can take both IP addresses and hostnames + #[napi(factory)] + pub fn connect( + server_name: Option, + scid: Uint8Array, + local_host: HostPort, + remote_host: HostPort, + config: &mut config::Config, + ) -> napi::Result { + let local_addr: SocketAddr = local_host + .try_into() + .or_else(|err: io::Error| Err(napi::Error::from_reason(err.to_string())))?; + let remote_addr: SocketAddr = remote_host + .try_into() + .or_else(|err: io::Error| Err(napi::Error::from_reason(err.to_string())))?; + let scid = quiche::ConnectionId::from_ref(&scid); + let connection = quiche::connect( + server_name.as_deref(), + &scid, + local_addr, + remote_addr, + &mut config.0, + ) + .or_else(|err| Err(napi::Error::from_reason(err.to_string())))?; + return Ok(Connection(connection)); + } + + #[napi(factory)] + pub fn accept( + scid: Uint8Array, + odcid: Option, + local_host: HostPort, + remote_host: HostPort, + config: &mut config::Config, + ) -> napi::Result { + let local_addr: SocketAddr = local_host + .try_into() + .or_else(|err: io::Error| Err(napi::Error::from_reason(err.to_string())))?; + let remote_addr: SocketAddr = remote_host + .try_into() + .or_else(|err: io::Error| Err(napi::Error::from_reason(err.to_string())))?; + let scid = quiche::ConnectionId::from_ref(&scid); + let odcid = odcid.map(|dcid| quiche::ConnectionId::from_vec(dcid.to_vec())); + let connection = quiche::accept( + &scid, + odcid.as_ref(), + local_addr, + remote_addr, + &mut config.0, + ) + .or_else(|err| Err(napi::Error::from_reason(err.to_string())))?; + return Ok(Connection(connection)); + } + + #[napi] + pub fn set_keylog(&mut self, path: String) -> napi::Result<()> { + let file = + File::create(path).or_else(|err| Err(napi::Error::from_reason(err.to_string())))?; + self.0.set_keylog(Box::new(file)); + return Ok(()); + } - /// Creates QUIC Client Connection - /// - /// This can take both IP addresses and hostnames - #[napi(factory)] - pub fn connect( - server_name: Option, - scid: Uint8Array, - local_host: HostPort, - remote_host: HostPort, - config: &mut config::Config, - ) -> napi::Result { - let local_addr: SocketAddr = local_host.try_into().or_else( - |err: io::Error| Err(napi::Error::from_reason(err.to_string())) - )?; - let remote_addr: SocketAddr = remote_host.try_into().or_else( - |err: io::Error| Err(napi::Error::from_reason(err.to_string())) - )?; - let scid = quiche::ConnectionId::from_ref(&scid); - let connection = quiche::connect( - server_name.as_deref(), - &scid, - local_addr, - remote_addr, - &mut config.0 - ).or_else( - |err| Err(napi::Error::from_reason(err.to_string())) - )?; - return Ok(Connection(connection)); - } - - #[napi(factory)] - pub fn accept( - scid: Uint8Array, - odcid: Option, - local_host: HostPort, - remote_host: HostPort, - config: &mut config::Config, - ) -> napi::Result { - let local_addr: SocketAddr = local_host.try_into().or_else( - |err: io::Error| Err(napi::Error::from_reason(err.to_string())) - )?; - let remote_addr: SocketAddr = remote_host.try_into().or_else( - |err: io::Error| Err(napi::Error::from_reason(err.to_string())) - )?; - let scid = quiche::ConnectionId::from_ref(&scid); - let odcid = odcid.map( - |dcid| quiche::ConnectionId::from_vec(dcid.to_vec()) - ); - let connection = quiche::accept( - &scid, - odcid.as_ref(), - local_addr, - remote_addr, - &mut config.0 - ).or_else( - |err| Err(napi::Error::from_reason(err.to_string())) - )?; - return Ok(Connection(connection)); - } - - #[napi] - pub fn set_keylog(&mut self, path: String) -> napi::Result<()> { - let file = File::create(path).or_else( - |err| Err(napi::Error::from_reason(err.to_string())) - )?; - self.0.set_keylog(Box::new(file)); - return Ok(()); - } - - #[napi] - pub fn set_session(&mut self, session: Uint8Array) -> napi::Result<()> { - return self.0.set_session(&session).or_else( - |err| Err(napi::Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn recv( - &mut self, - mut data: Uint8Array, - recv_info: RecvInfo, - ) -> napi::Result { - let recv_info = quiche::RecvInfo { - from: recv_info.from.try_into().or_else( - |err: io::Error| Err(napi::Error::from_reason(err.to_string())) - )?, - to: recv_info.to.try_into().or_else( - |err: io::Error| Err(napi::Error::from_reason(err.to_string())) - )?, - }; - let read = match self.0.recv( - &mut data, - recv_info - ) { - Ok(v) => v, - Err(e) => return Err(napi::Error::from_reason(e.to_string())), - }; - return Ok(read as i64); - } - - - /// Sends a QUIC packet - /// - /// This writes to the data buffer passed in. - /// The buffer must be allocated to the size of MAX_DATAGRAM_SIZE. - /// This will return a JS array of `[length, send_info]`. - /// It is possible for the length to be 0. - /// You may then send a 0-length buffer. - /// If there is nothing to be sent a Done error will be thrown. - #[napi(ts_return_type = "[number, SendInfo]")] - pub fn send(&mut self, env: Env, mut data: Uint8Array) -> napi::Result> { - let (write, send_info) = match self.0.send(&mut data) { - Ok((write, send_info)) => (write, send_info), - Err(quiche::Error::Done) => return Ok(None), - Err(e) => return Err(napi::Error::from_reason(e.to_string())), - }; - let send_info = { - let from = HostPort { - host: send_info.from.ip().to_string(), - port: send_info.from.port(), - }; - let to = HostPort { - host: send_info.to.ip().to_string(), - port: send_info.to.port(), - }; - let at = External::new(send_info.at); - SendInfo { from, to, at } - }; - let mut write_and_send_info = env.create_array(2)?; - write_and_send_info.set(0, write as i64)?; - write_and_send_info.set(1, send_info)?; - return Ok(Some(write_and_send_info)); - } - - #[napi(ts_return_type = "[number, SendInfo | null]")] - pub fn send_on_path( - &mut self, - env: Env, - mut data: Uint8Array, - from: Option, - to: Option - ) -> napi::Result> { - let from: Option = match from { - Some(host) => Some( - host.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )? - ), - _ => None - }; - let to: Option = match to { - Some(host) => Some( - host.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )? - ), - _ => None - }; - let (write, send_info) = match self.0.send_on_path(&mut data, from, to) { - Ok((write, send_info)) => (write, send_info), - Err(quiche::Error::Done) => return Ok(None), - Err(e) => return Err(napi::Error::from_reason(e.to_string())), - }; - let send_info = { - let from = HostPort { - host: send_info.from.ip().to_string(), - port: send_info.from.port(), - }; - let to = HostPort { - host: send_info.to.ip().to_string(), - port: send_info.to.port(), - }; - let at = External::new(send_info.at); - SendInfo { from, to, at } - }; - let mut write_and_send_info = env.create_array(2)?; - write_and_send_info.set(0, write as i64)?; - write_and_send_info.set(1, send_info)?; - return Ok(Some(write_and_send_info)); - } - - #[napi] - pub fn send_quantum(&self) -> i64 { - return self.0.send_quantum() as i64; - } - - #[napi] - pub fn send_quantum_on_path(&self, local_host: HostPort, peer_host: HostPort) -> napi::Result { - let local_addr: SocketAddr = local_host.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - let remote_addr: SocketAddr = peer_host.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - return Ok(self.0.send_quantum_on_path(local_addr, remote_addr) as i64); - } - - #[napi(ts_return_type = "[number, boolean]")] - pub fn stream_recv( - &mut self, - env: Env, - stream_id: i64, - mut data: Uint8Array, - ) -> napi::Result> { - let (read, fin) = match self.0.stream_recv( - stream_id as u64, - &mut data, - ) { - Ok((read, fin)) => (read, fin), - Err(quiche::Error::Done) => return Ok(None), - Err(e) => return Err(napi::Error::from_reason(e.to_string())), - }; - let mut read_and_fin = env.create_array(2)?; - read_and_fin.set(0, read as i64)?; - read_and_fin.set(1, fin)?; - return Ok(Some(read_and_fin)); - } - - #[napi] - pub fn stream_send( - &mut self, - stream_id: i64, - data: Uint8Array, - fin: bool - ) -> napi::Result> { - match self.0.stream_send( - stream_id as u64, - &data, - fin - ) { - Ok(v) => return Ok(Some(v as i64)), - Err(quiche::Error::Done) => return Ok(None), - Err(e) => return Err(napi::Error::from_reason(e.to_string())), - }; - } - - #[napi] - pub fn stream_priority( - &mut self, - stream_id: i64, - urgency: u8, - incremental: bool - ) -> napi::Result<()> { - return self.0.stream_priority( - stream_id as u64, - urgency, - incremental - ).map_err(|e| napi::Error::from_reason(e.to_string())); - } - - #[napi] - pub fn stream_shutdown( - &mut self, - stream_id: i64, - direction: Shutdown, - err: i64 - ) -> napi::Result> { - return match self.0.stream_shutdown( - stream_id as u64, - direction.into(), - err as u64 - ) { - Ok(()) => Ok(Some(())), - Err(quiche::Error::Done) => Ok(None), - Err(e) => Err(napi::Error::from_reason(e.to_string())), - }; - } - - #[napi] - pub fn stream_capacity( - &self, - stream_id: i64, - ) -> napi::Result { - return self.0.stream_capacity( - stream_id as u64 - ).or_else( - |err| Err(napi::Error::from_reason(err.to_string())) - ).map(|v| v as i64); - } - - #[napi] - pub fn stream_readable( - &self, - stream_id: i64, - ) -> bool { - return self.0.stream_readable( - stream_id as u64 - ); - } - - #[napi] - pub fn stream_writable( - &mut self, - stream_id: i64, - len: i64 - ) -> napi::Result { - return self.0.stream_writable(stream_id as u64, len as usize).or_else( - |err| Err(napi::Error::from_reason(err.to_string())) - ); - } - - #[napi] - pub fn stream_finished( - &self, - stream_id: i64 - ) -> bool { - return self.0.stream_finished(stream_id as u64); - } - - #[napi] - pub fn peer_streams_left_bidi(&self) -> i64 { - return self.0.peer_streams_left_bidi() as i64; - } - - #[napi] - pub fn peer_streams_left_uni(&self) -> i64 { - return self.0.peer_streams_left_uni() as i64; - } - - #[napi] - pub fn readable(&self) -> stream::StreamIter { - return stream::StreamIter(self.0.readable()); - } - - #[napi] - pub fn writable(&self) -> stream::StreamIter { - return stream::StreamIter(self.0.writable()); - } - - #[napi] - pub fn max_send_udp_payload_size(&self) -> i64 { - return self.0.max_send_udp_payload_size() as i64; - } - - #[napi] - pub fn dgram_recv( - &mut self, - mut data: Uint8Array - ) -> napi::Result> { - match self.0.dgram_recv( - &mut data, - ) { - Ok(v) => return Ok(Some(v as i64)), - Err(quiche::Error::Done) => return Ok(None), - Err(e) => return Err(napi::Error::from_reason(e.to_string())), - }; - } - - #[napi] - pub fn dgram_recv_vec( - &mut self, - ) -> napi::Result> { - match self.0.dgram_recv_vec() { - Ok(v) => return Ok(Some(v.into())), - Err(quiche::Error::Done) => return Ok(None), - Err(e) => return Err(napi::Error::from_reason(e.to_string())), - }; - } - - #[napi] - pub fn dgram_recv_peek(&self, mut data: Uint8Array, len: i64) -> napi::Result> { - match self.0.dgram_recv_peek( - &mut data, - len as usize, - ) { - Ok(v) => return Ok(Some(v as i64)), - Err(quiche::Error::Done) => return Ok(None), - Err(e) => return Err(napi::Error::from_reason(e.to_string())) - }; - } - - #[napi] - pub fn dgram_recv_front_len(&self) -> Option { - return self.0.dgram_recv_front_len().map(|v| v as i64); - } - - #[napi] - pub fn dgram_recv_queue_len(&self) -> i64 { - return self.0.dgram_recv_queue_len() as i64; - } - - #[napi] - pub fn dgram_recv_queue_byte_size(&self) -> i64 { - return self.0.dgram_recv_queue_byte_size() as i64; - } - - #[napi] - pub fn dgram_send_queue_len(&self) -> i64 { - return self.0.dgram_send_queue_len() as i64; - } - - #[napi] - pub fn dgram_send_queue_byte_size(&self) -> i64 { - return self.0.dgram_send_queue_byte_size() as i64; - } - - #[napi] - pub fn is_dgram_send_queue_full(&self) -> bool { - return self.0.is_dgram_send_queue_full(); - } - - #[napi] - pub fn is_dgram_recv_queue_full(&self) -> bool { - return self.0.is_dgram_recv_queue_full(); - } - - #[napi] - pub fn dgram_send( - &mut self, - data: Uint8Array, - ) -> napi::Result> { - match self.0.dgram_send( - &data, - ) { - Ok(v) => return Ok(Some(v)), - Err(quiche::Error::Done) => return Ok(None), - Err(e) => return Err(napi::Error::from_reason(e.to_string())), - }; - } - - #[napi] - pub fn dgram_send_vec( - &mut self, - data: Uint8Array - ) -> napi::Result> { - match self.0.dgram_send_vec( - data.to_vec() - ) { - Ok(v) => return Ok(Some(v)), - Err(quiche::Error::Done) => return Ok(None), - Err(e) => return Err(napi::Error::from_reason(e.to_string())), - }; - } - - #[napi] - pub fn dgram_purge_outgoing napi::Result>( - &mut self, - f: F - ) -> () { - return self.0.dgram_purge_outgoing( - |data: &[u8]| match f(data.into()) { - Ok(v) => v, - // If error occurs, this must return false - _ => false - } - ); - } - - #[napi] - pub fn dgram_max_writable_len(&mut self) -> Option { - return self.0.dgram_max_writable_len().map(|v| v as i64); - } - - #[napi] - pub fn timeout(&self) -> Option { - return self.0.timeout().map(|t| t.as_millis() as i64); - } - - #[napi] - pub fn on_timeout(&mut self) -> () { - return self.0.on_timeout(); - } - - #[napi] - pub fn probe_path( - &mut self, - local_host: HostPort, - peer_host: HostPort - ) -> napi::Result { - let local_addr: SocketAddr = local_host.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - let peer_addr: SocketAddr = peer_host.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - return self.0.probe_path( - local_addr, - peer_addr - ) - .map(|v| v as i64) - .or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - ); - } - - #[napi] - pub fn migrate_source(&mut self, local_host: HostPort) -> napi::Result { - let local_addr: SocketAddr = local_host.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - return self.0.migrate_source(local_addr).map(|v| v as i64).or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - ); - } - - #[napi] - pub fn migrate(&mut self, local_host: HostPort, peer_host: HostPort) -> napi::Result { - let local_addr: SocketAddr = local_host.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - let peer_addr: SocketAddr = peer_host.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - return self.0.migrate(local_addr, peer_addr).map(|v| v as i64).or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - ); - } - - #[napi] - pub fn new_source_cid( - &mut self, - scid: Uint8Array, - reset_token: BigInt, - retire_if_needed: bool - ) -> napi::Result { - return self.0.new_source_cid( - &quiche::ConnectionId::from_ref(&scid), - reset_token.get_u128().1, - retire_if_needed - ).map(|v| v as i64).or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - ); - } - - #[napi] - pub fn active_source_cids(&self) -> i64 { - return self.0.active_source_cids() as i64; - } - - #[napi] - pub fn source_cids_left(&self) -> i64 { - return self.0.source_cids_left() as i64; - } - - #[napi] - pub fn retire_destination_cid(&mut self, dcid_seq: i64) -> napi::Result<()> { - return self.0.retire_destination_cid(dcid_seq as u64).or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - ); - } - - #[napi(ts_return_type = "object")] - pub fn path_event_next( - &mut self, - env: Env - ) -> napi::Result> { - let path_event: Option = self.0.path_event_next().map( - |v| v.into() - ); - return path_event.map(|v| env.to_js_value(&v)).transpose(); - } - - #[napi] - pub fn retired_scid_next(&mut self) -> Option { - return self.0.retired_scid_next().map(|v| v.into()); - } - - #[napi] - pub fn available_dcids(&self) -> i64 { - return self.0.available_dcids() as i64; - } - - #[napi] - pub fn paths_iter(&self, from: HostPort) -> napi::Result { - let from_addr: SocketAddr = from.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - let socket_addr_iter = self.0.paths_iter(from_addr); - return Ok(path::HostIter(socket_addr_iter)); - } - - #[napi] - pub fn close(&mut self, app: bool, err: i64, reason: Uint8Array) -> napi::Result> { - return match self.0.close(app, err as u64, &reason) { - Ok(_) => Ok(Some(())), - Err(quiche::Error::Done) => Ok(None), - Err(e) => Err(napi::Error::from_reason(e.to_string())), - }; - } - - #[napi] - pub fn trace_id(&self) -> String { - return self.0.trace_id().to_string(); - } - - #[napi] - pub fn application_proto(&self) -> Uint8Array { - return self.0.application_proto().to_vec().into(); - } - - #[napi] - pub fn server_name(&self) -> Option { - return self.0.server_name().map(|v| v.to_string()); - } - - #[napi] - pub fn peer_cert_chain(&self) -> Option> { - return self.0.peer_cert_chain().map( - |certs| certs.iter().map( - |cert| cert.to_vec().into() - ).collect() - ); - } - - #[napi] - pub fn session(&self) -> Option { - return self.0.session().map(|s| s.to_vec().into()); - } - - #[napi] - pub fn source_id(&self) -> Uint8Array { - return self.0.source_id().as_ref().into(); - } - - #[napi] - pub fn destination_id(&self) -> Uint8Array { - return self.0.destination_id().as_ref().into(); - } - - #[napi] - pub fn is_established(&self) -> bool { - return self.0.is_established(); - } - - #[napi] - pub fn is_resumed(&self) -> bool { - return self.0.is_resumed(); - } - - #[napi] - pub fn is_in_early_data(&self) -> bool { - return self.0.is_in_early_data(); - } - - #[napi] - pub fn is_readable(&self) -> bool { - return self.0.is_readable(); - } - - #[napi] - pub fn is_path_validated( - &self, - from: HostPort, - to: HostPort - ) -> napi::Result { - let from_addr: SocketAddr = from.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - let to_addr: SocketAddr = to.try_into().or_else( - |err: io::Error| Err( - napi::Error::new(napi::Status::InvalidArg, err.to_string()) - ) - )?; - return self.0.is_path_validated(from_addr, to_addr).or_else( - |e| Err(napi::Error::from_reason(e.to_string())) - ); - } - - #[napi] - pub fn is_draining(&self) -> bool { - return self.0.is_draining(); - } - - #[napi] - pub fn is_closed(&self) -> bool { - let x = self.0.is_closed(); - return x; - } - - #[napi] - pub fn is_timed_out(&self) -> bool { - return self.0.is_timed_out(); - } - - #[napi] - pub fn peer_error(&self) -> Option { - return self.0.peer_error().map(|e| e.clone().into()); - } - - #[napi] - pub fn local_error(&self) -> Option { - return self.0.local_error().map(|e| e.clone().into()); - } - - #[napi] - pub fn stats(&self) -> Stats { - return self.0.stats().into(); - } - - /// Path stats as an array - /// - /// Normally this would be an iterator. - /// However the iterator can only exist in the lifetime of the connection. - /// This collects the all the data, converts them to our PathStats - /// Then returns it all as 1 giant array. - /// - /// https://stackoverflow.com/q/74609430/582917 - /// https://stackoverflow.com/q/50343130/582917 - #[napi] - pub fn path_stats(&self) -> Vec { - return self.0.path_stats().map( - |s| s.into() - ).collect(); - } - - #[napi] - pub fn send_ack_eliciting(&mut self) -> napi::Result<()> { - return self.0.send_ack_eliciting().or_else( - |err| Err(Error::from_reason(err.to_string())) - ); - } + #[napi] + pub fn set_session(&mut self, session: Uint8Array) -> napi::Result<()> { + return self + .0 + .set_session(&session) + .or_else(|err| Err(napi::Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn recv(&mut self, mut data: Uint8Array, recv_info: RecvInfo) -> napi::Result { + let recv_info = quiche::RecvInfo { + from: recv_info + .from + .try_into() + .or_else(|err: io::Error| Err(napi::Error::from_reason(err.to_string())))?, + to: recv_info + .to + .try_into() + .or_else(|err: io::Error| Err(napi::Error::from_reason(err.to_string())))?, + }; + let read = match self.0.recv(&mut data, recv_info) { + Ok(v) => v, + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + return Ok(read as i64); + } + + /// Sends a QUIC packet + /// + /// This writes to the data buffer passed in. + /// The buffer must be allocated to the size of MAX_DATAGRAM_SIZE. + /// This will return a JS array of `[length, send_info]`. + /// It is possible for the length to be 0. + /// You may then send a 0-length buffer. + /// If there is nothing to be sent a Done error will be thrown. + #[napi(ts_return_type = "[number, SendInfo]")] + pub fn send(&mut self, env: Env, mut data: Uint8Array) -> napi::Result> { + let (write, send_info) = match self.0.send(&mut data) { + Ok((write, send_info)) => (write, send_info), + Err(quiche::Error::Done) => return Ok(None), + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + let send_info = { + let from = HostPort { + host: send_info.from.ip().to_string(), + port: send_info.from.port(), + }; + let to = HostPort { + host: send_info.to.ip().to_string(), + port: send_info.to.port(), + }; + let at = External::new(send_info.at); + SendInfo { from, to, at } + }; + let mut write_and_send_info = env.create_array(2)?; + write_and_send_info.set(0, write as i64)?; + write_and_send_info.set(1, send_info)?; + return Ok(Some(write_and_send_info)); + } + + #[napi(ts_return_type = "[number, SendInfo | null]")] + pub fn send_on_path( + &mut self, + env: Env, + mut data: Uint8Array, + from: Option, + to: Option, + ) -> napi::Result> { + let from: Option = match from { + Some(host) => Some(host.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?), + _ => None, + }; + let to: Option = match to { + Some(host) => Some(host.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?), + _ => None, + }; + let (write, send_info) = match self.0.send_on_path(&mut data, from, to) { + Ok((write, send_info)) => (write, send_info), + Err(quiche::Error::Done) => return Ok(None), + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + let send_info = { + let from = HostPort { + host: send_info.from.ip().to_string(), + port: send_info.from.port(), + }; + let to = HostPort { + host: send_info.to.ip().to_string(), + port: send_info.to.port(), + }; + let at = External::new(send_info.at); + SendInfo { from, to, at } + }; + let mut write_and_send_info = env.create_array(2)?; + write_and_send_info.set(0, write as i64)?; + write_and_send_info.set(1, send_info)?; + return Ok(Some(write_and_send_info)); + } + + #[napi] + pub fn send_quantum(&self) -> i64 { + return self.0.send_quantum() as i64; + } + + #[napi] + pub fn send_quantum_on_path( + &self, + local_host: HostPort, + peer_host: HostPort, + ) -> napi::Result { + let local_addr: SocketAddr = local_host.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + let remote_addr: SocketAddr = peer_host.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + return Ok(self.0.send_quantum_on_path(local_addr, remote_addr) as i64); + } + + #[napi(ts_return_type = "[number, boolean]")] + pub fn stream_recv( + &mut self, + env: Env, + stream_id: i64, + mut data: Uint8Array, + ) -> napi::Result> { + let (read, fin) = match self.0.stream_recv(stream_id as u64, &mut data) { + Ok((read, fin)) => (read, fin), + Err(quiche::Error::Done) => return Ok(None), + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + let mut read_and_fin = env.create_array(2)?; + read_and_fin.set(0, read as i64)?; + read_and_fin.set(1, fin)?; + return Ok(Some(read_and_fin)); + } + + #[napi] + pub fn stream_send( + &mut self, + stream_id: i64, + data: Uint8Array, + fin: bool, + ) -> napi::Result> { + match self.0.stream_send(stream_id as u64, &data, fin) { + Ok(v) => return Ok(Some(v as i64)), + Err(quiche::Error::Done) => return Ok(None), + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + } + + #[napi] + pub fn stream_priority( + &mut self, + stream_id: i64, + urgency: u8, + incremental: bool, + ) -> napi::Result<()> { + return self + .0 + .stream_priority(stream_id as u64, urgency, incremental) + .map_err(|e| napi::Error::from_reason(e.to_string())); + } + + #[napi] + pub fn stream_shutdown( + &mut self, + stream_id: i64, + direction: Shutdown, + err: i64, + ) -> napi::Result> { + return match self + .0 + .stream_shutdown(stream_id as u64, direction.into(), err as u64) + { + Ok(()) => Ok(Some(())), + Err(quiche::Error::Done) => Ok(None), + Err(e) => Err(napi::Error::from_reason(e.to_string())), + }; + } + + #[napi] + pub fn stream_capacity(&self, stream_id: i64) -> napi::Result { + return self + .0 + .stream_capacity(stream_id as u64) + .or_else(|err| Err(napi::Error::from_reason(err.to_string()))) + .map(|v| v as i64); + } + + #[napi] + pub fn stream_readable(&self, stream_id: i64) -> bool { + return self.0.stream_readable(stream_id as u64); + } + + #[napi] + pub fn stream_writable(&mut self, stream_id: i64, len: i64) -> napi::Result { + return self + .0 + .stream_writable(stream_id as u64, len as usize) + .or_else(|err| Err(napi::Error::from_reason(err.to_string()))); + } + + #[napi] + pub fn stream_finished(&self, stream_id: i64) -> bool { + return self.0.stream_finished(stream_id as u64); + } + + #[napi] + pub fn peer_streams_left_bidi(&self) -> i64 { + return self.0.peer_streams_left_bidi() as i64; + } + + #[napi] + pub fn peer_streams_left_uni(&self) -> i64 { + return self.0.peer_streams_left_uni() as i64; + } + + #[napi] + pub fn readable(&self) -> stream::StreamIter { + return stream::StreamIter(self.0.readable()); + } + + #[napi] + pub fn writable(&self) -> stream::StreamIter { + return stream::StreamIter(self.0.writable()); + } + + #[napi] + pub fn max_send_udp_payload_size(&self) -> i64 { + return self.0.max_send_udp_payload_size() as i64; + } + + #[napi] + pub fn dgram_recv(&mut self, mut data: Uint8Array) -> napi::Result> { + match self.0.dgram_recv(&mut data) { + Ok(v) => return Ok(Some(v as i64)), + Err(quiche::Error::Done) => return Ok(None), + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + } + + #[napi] + pub fn dgram_recv_vec(&mut self) -> napi::Result> { + match self.0.dgram_recv_vec() { + Ok(v) => return Ok(Some(v.into())), + Err(quiche::Error::Done) => return Ok(None), + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + } + + #[napi] + pub fn dgram_recv_peek(&self, mut data: Uint8Array, len: i64) -> napi::Result> { + match self.0.dgram_recv_peek(&mut data, len as usize) { + Ok(v) => return Ok(Some(v as i64)), + Err(quiche::Error::Done) => return Ok(None), + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + } + + #[napi] + pub fn dgram_recv_front_len(&self) -> Option { + return self.0.dgram_recv_front_len().map(|v| v as i64); + } + + #[napi] + pub fn dgram_recv_queue_len(&self) -> i64 { + return self.0.dgram_recv_queue_len() as i64; + } + + #[napi] + pub fn dgram_recv_queue_byte_size(&self) -> i64 { + return self.0.dgram_recv_queue_byte_size() as i64; + } + + #[napi] + pub fn dgram_send_queue_len(&self) -> i64 { + return self.0.dgram_send_queue_len() as i64; + } + + #[napi] + pub fn dgram_send_queue_byte_size(&self) -> i64 { + return self.0.dgram_send_queue_byte_size() as i64; + } + + #[napi] + pub fn is_dgram_send_queue_full(&self) -> bool { + return self.0.is_dgram_send_queue_full(); + } + + #[napi] + pub fn is_dgram_recv_queue_full(&self) -> bool { + return self.0.is_dgram_recv_queue_full(); + } + + #[napi] + pub fn dgram_send(&mut self, data: Uint8Array) -> napi::Result> { + match self.0.dgram_send(&data) { + Ok(v) => return Ok(Some(v)), + Err(quiche::Error::Done) => return Ok(None), + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + } + + #[napi] + pub fn dgram_send_vec(&mut self, data: Uint8Array) -> napi::Result> { + match self.0.dgram_send_vec(data.to_vec()) { + Ok(v) => return Ok(Some(v)), + Err(quiche::Error::Done) => return Ok(None), + Err(e) => return Err(napi::Error::from_reason(e.to_string())), + }; + } + + #[napi] + pub fn dgram_purge_outgoing napi::Result>(&mut self, f: F) -> () { + return self + .0 + .dgram_purge_outgoing(|data: &[u8]| match f(data.into()) { + Ok(v) => v, + // If error occurs, this must return false + _ => false, + }); + } + + #[napi] + pub fn dgram_max_writable_len(&mut self) -> Option { + return self.0.dgram_max_writable_len().map(|v| v as i64); + } + + #[napi] + pub fn timeout(&self) -> Option { + return self.0.timeout().map(|t| t.as_millis() as i64); + } + + #[napi] + pub fn on_timeout(&mut self) -> () { + return self.0.on_timeout(); + } + + #[napi] + pub fn probe_path(&mut self, local_host: HostPort, peer_host: HostPort) -> napi::Result { + let local_addr: SocketAddr = local_host.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + let peer_addr: SocketAddr = peer_host.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + return self + .0 + .probe_path(local_addr, peer_addr) + .map(|v| v as i64) + .or_else(|e| Err(napi::Error::from_reason(e.to_string()))); + } + + #[napi] + pub fn migrate_source(&mut self, local_host: HostPort) -> napi::Result { + let local_addr: SocketAddr = local_host.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + return self + .0 + .migrate_source(local_addr) + .map(|v| v as i64) + .or_else(|e| Err(napi::Error::from_reason(e.to_string()))); + } + + #[napi] + pub fn migrate(&mut self, local_host: HostPort, peer_host: HostPort) -> napi::Result { + let local_addr: SocketAddr = local_host.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + let peer_addr: SocketAddr = peer_host.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + return self + .0 + .migrate(local_addr, peer_addr) + .map(|v| v as i64) + .or_else(|e| Err(napi::Error::from_reason(e.to_string()))); + } + + #[napi] + pub fn new_source_cid( + &mut self, + scid: Uint8Array, + reset_token: BigInt, + retire_if_needed: bool, + ) -> napi::Result { + return self + .0 + .new_source_cid( + &quiche::ConnectionId::from_ref(&scid), + reset_token.get_u128().1, + retire_if_needed, + ) + .map(|v| v as i64) + .or_else(|e| Err(napi::Error::from_reason(e.to_string()))); + } + + #[napi] + pub fn active_source_cids(&self) -> i64 { + return self.0.active_source_cids() as i64; + } + + #[napi] + pub fn source_cids_left(&self) -> i64 { + return self.0.source_cids_left() as i64; + } + + #[napi] + pub fn retire_destination_cid(&mut self, dcid_seq: i64) -> napi::Result<()> { + return self + .0 + .retire_destination_cid(dcid_seq as u64) + .or_else(|e| Err(napi::Error::from_reason(e.to_string()))); + } + + #[napi(ts_return_type = "object")] + pub fn path_event_next(&mut self, env: Env) -> napi::Result> { + let path_event: Option = self.0.path_event_next().map(|v| v.into()); + return path_event.map(|v| env.to_js_value(&v)).transpose(); + } + + #[napi] + pub fn retired_scid_next(&mut self) -> Option { + return self.0.retired_scid_next().map(|v| v.into()); + } + + #[napi] + pub fn available_dcids(&self) -> i64 { + return self.0.available_dcids() as i64; + } + + #[napi] + pub fn paths_iter(&self, from: HostPort) -> napi::Result { + let from_addr: SocketAddr = from.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + let socket_addr_iter = self.0.paths_iter(from_addr); + return Ok(path::HostIter(socket_addr_iter)); + } + + #[napi] + pub fn close(&mut self, app: bool, err: i64, reason: Uint8Array) -> napi::Result> { + return match self.0.close(app, err as u64, &reason) { + Ok(_) => Ok(Some(())), + Err(quiche::Error::Done) => Ok(None), + Err(e) => Err(napi::Error::from_reason(e.to_string())), + }; + } + + #[napi] + pub fn trace_id(&self) -> String { + return self.0.trace_id().to_string(); + } + + #[napi] + pub fn application_proto(&self) -> Uint8Array { + return self.0.application_proto().to_vec().into(); + } + + #[napi] + pub fn server_name(&self) -> Option { + return self.0.server_name().map(|v| v.to_string()); + } + + #[napi] + pub fn peer_cert_chain(&self) -> Option> { + return self + .0 + .peer_cert_chain() + .map(|certs| certs.iter().map(|cert| cert.to_vec().into()).collect()); + } + + #[napi] + pub fn session(&self) -> Option { + return self.0.session().map(|s| s.to_vec().into()); + } + + #[napi] + pub fn source_id(&self) -> Uint8Array { + return self.0.source_id().as_ref().into(); + } + + #[napi] + pub fn destination_id(&self) -> Uint8Array { + return self.0.destination_id().as_ref().into(); + } + + #[napi] + pub fn is_established(&self) -> bool { + return self.0.is_established(); + } + + #[napi] + pub fn is_resumed(&self) -> bool { + return self.0.is_resumed(); + } + + #[napi] + pub fn is_in_early_data(&self) -> bool { + return self.0.is_in_early_data(); + } + + #[napi] + pub fn is_readable(&self) -> bool { + return self.0.is_readable(); + } + + #[napi] + pub fn is_path_validated(&self, from: HostPort, to: HostPort) -> napi::Result { + let from_addr: SocketAddr = from.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + let to_addr: SocketAddr = to.try_into().or_else(|err: io::Error| { + Err(napi::Error::new(napi::Status::InvalidArg, err.to_string())) + })?; + return self + .0 + .is_path_validated(from_addr, to_addr) + .or_else(|e| Err(napi::Error::from_reason(e.to_string()))); + } + + #[napi] + pub fn is_draining(&self) -> bool { + return self.0.is_draining(); + } + + #[napi] + pub fn is_closed(&self) -> bool { + let x = self.0.is_closed(); + return x; + } + + #[napi] + pub fn is_timed_out(&self) -> bool { + return self.0.is_timed_out(); + } + + #[napi] + pub fn peer_error(&self) -> Option { + return self.0.peer_error().map(|e| e.clone().into()); + } + + #[napi] + pub fn local_error(&self) -> Option { + return self.0.local_error().map(|e| e.clone().into()); + } + + #[napi] + pub fn stats(&self) -> Stats { + return self.0.stats().into(); + } + + /// Path stats as an array + /// + /// Normally this would be an iterator. + /// However the iterator can only exist in the lifetime of the connection. + /// This collects the all the data, converts them to our PathStats + /// Then returns it all as 1 giant array. + /// + /// https://stackoverflow.com/q/74609430/582917 + /// https://stackoverflow.com/q/50343130/582917 + #[napi] + pub fn path_stats(&self) -> Vec { + return self.0.path_stats().map(|s| s.into()).collect(); + } + + #[napi] + pub fn send_ack_eliciting(&mut self) -> napi::Result<()> { + return self + .0 + .send_ack_eliciting() + .or_else(|err| Err(Error::from_reason(err.to_string()))); + } } diff --git a/src/native/napi/lib.rs b/src/native/napi/lib.rs index 541e479c..df83647f 100644 --- a/src/native/napi/lib.rs +++ b/src/native/napi/lib.rs @@ -2,14 +2,14 @@ extern crate core; use napi_derive::napi; -mod constants; mod config; mod connection; -mod stream; -mod path; +mod constants; mod packet; +mod path; +mod stream; #[napi] pub fn version_is_supported(version: u32) -> bool { - return quiche::version_is_supported(version); + return quiche::version_is_supported(version); } diff --git a/src/native/napi/packet.rs b/src/native/napi/packet.rs index 3cd21394..919ec25a 100644 --- a/src/native/napi/packet.rs +++ b/src/native/napi/packet.rs @@ -1,113 +1,102 @@ #![allow(dead_code)] -use napi_derive::napi; use napi::bindgen_prelude::*; - +use napi_derive::napi; #[napi] pub enum Type { - Initial, - Retry, - Handshake, - ZeroRTT, - VersionNegotiation, - Short + Initial, + Retry, + Handshake, + ZeroRTT, + VersionNegotiation, + Short, } impl From for quiche::Type { - fn from(ty: Type) -> Self { - match ty { - Type::Initial => quiche::Type::Initial, - Type::Retry => quiche::Type::Retry, - Type::Handshake => quiche::Type::Handshake, - Type::ZeroRTT => quiche::Type::ZeroRTT, - Type::VersionNegotiation => quiche::Type::VersionNegotiation, - Type::Short => quiche::Type::Short, + fn from(ty: Type) -> Self { + match ty { + Type::Initial => quiche::Type::Initial, + Type::Retry => quiche::Type::Retry, + Type::Handshake => quiche::Type::Handshake, + Type::ZeroRTT => quiche::Type::ZeroRTT, + Type::VersionNegotiation => quiche::Type::VersionNegotiation, + Type::Short => quiche::Type::Short, + } } - } } impl From for Type { - fn from(item: quiche::Type) -> Self { - match item { - quiche::Type::Initial => Type::Initial, - quiche::Type::Retry => Type::Retry, - quiche::Type::Handshake => Type::Handshake, - quiche::Type::ZeroRTT => Type::ZeroRTT, - quiche::Type::VersionNegotiation => Type::VersionNegotiation, - quiche::Type::Short => Type::Short, + fn from(item: quiche::Type) -> Self { + match item { + quiche::Type::Initial => Type::Initial, + quiche::Type::Retry => Type::Retry, + quiche::Type::Handshake => Type::Handshake, + quiche::Type::ZeroRTT => Type::ZeroRTT, + quiche::Type::VersionNegotiation => Type::VersionNegotiation, + quiche::Type::Short => Type::Short, + } } - } } #[napi] pub struct Header { - pub ty: Type, - pub version: u32, - pub dcid: Uint8Array, - pub scid: Uint8Array, - pub token: Option, - pub versions: Option>, + pub ty: Type, + pub version: u32, + pub dcid: Uint8Array, + pub scid: Uint8Array, + pub token: Option, + pub versions: Option>, } impl From> for Header { - fn from(header: quiche::Header) -> Self { - Header { - ty: header.ty.into(), - version: header.version, - dcid: header.dcid.as_ref().into(), - scid: header.scid.as_ref().into(), - token: header.token.map(|token| token.into()), - versions: header.versions.map(|versions| versions.to_vec()), + fn from(header: quiche::Header) -> Self { + Header { + ty: header.ty.into(), + version: header.version, + dcid: header.dcid.as_ref().into(), + scid: header.scid.as_ref().into(), + token: header.token.map(|token| token.into()), + versions: header.versions.map(|versions| versions.to_vec()), + } } - } } #[napi] impl Header { - #[napi(factory)] - pub fn from_slice(mut data: Uint8Array, dcid_len: i64) -> napi::Result { - return quiche::Header::from_slice( - &mut data, - dcid_len as usize - ).or_else( - |e| Err(Error::from_reason(e.to_string())) - ).map(|header| header.into()); - } + #[napi(factory)] + pub fn from_slice(mut data: Uint8Array, dcid_len: i64) -> napi::Result { + return quiche::Header::from_slice(&mut data, dcid_len as usize) + .or_else(|e| Err(Error::from_reason(e.to_string()))) + .map(|header| header.into()); + } } #[napi] pub fn negotiate_version( - scid: Uint8Array, - dcid: Uint8Array, - mut data: Uint8Array, + scid: Uint8Array, + dcid: Uint8Array, + mut data: Uint8Array, ) -> napi::Result { - let scid = quiche::ConnectionId::from_ref(&scid); - let dcid = quiche::ConnectionId::from_ref(&dcid); - return quiche::negotiate_version(&scid, &dcid, &mut data).or_else( - |e| Err(Error::from_reason(e.to_string())) - ).map(|v| v as i64); + let scid = quiche::ConnectionId::from_ref(&scid); + let dcid = quiche::ConnectionId::from_ref(&dcid); + return quiche::negotiate_version(&scid, &dcid, &mut data) + .or_else(|e| Err(Error::from_reason(e.to_string()))) + .map(|v| v as i64); } #[napi] pub fn retry( - scid: Uint8Array, - dcid: Uint8Array, - new_scid: Uint8Array, - token: Uint8Array, - version: u32, - mut out: Uint8Array + scid: Uint8Array, + dcid: Uint8Array, + new_scid: Uint8Array, + token: Uint8Array, + version: u32, + mut out: Uint8Array, ) -> napi::Result { - let scid = quiche::ConnectionId::from_ref(&scid); - let dcid = quiche::ConnectionId::from_ref(&dcid); - let new_scid = quiche::ConnectionId::from_ref(&new_scid); - return quiche::retry( - &scid, - &dcid, - &new_scid, - &token, - version, - &mut out - ).or_else( - |e| Err(Error::from_reason(e.to_string())) - ).map(|v| v as i64); + let scid = quiche::ConnectionId::from_ref(&scid); + let dcid = quiche::ConnectionId::from_ref(&dcid); + let new_scid = quiche::ConnectionId::from_ref(&new_scid); + return quiche::retry(&scid, &dcid, &new_scid, &token, version, &mut out) + .or_else(|e| Err(Error::from_reason(e.to_string()))) + .map(|v| v as i64); } diff --git a/src/native/napi/path.rs b/src/native/napi/path.rs index 3b959729..e7cbdc64 100644 --- a/src/native/napi/path.rs +++ b/src/native/napi/path.rs @@ -1,129 +1,145 @@ -use napi_derive::napi; -use napi::bindgen_prelude::*; -use serde::{Serialize, Deserialize}; use crate::connection; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] #[serde(tag = "type")] pub enum PathEvent { - New { local: connection::HostPort, peer: connection::HostPort }, - Validated { local: connection::HostPort, peer: connection::HostPort }, - FailedValidation { local: connection::HostPort, peer: connection::HostPort }, - Closed { local: connection::HostPort, peer: connection::HostPort }, - ReusedSourceConnectionId { - seq: u64, - old: (connection::HostPort, connection::HostPort), - new: (connection::HostPort, connection::HostPort), - }, - PeerMigrated { - old: connection::HostPort, - new: connection::HostPort, - } + New { + local: connection::HostPort, + peer: connection::HostPort, + }, + Validated { + local: connection::HostPort, + peer: connection::HostPort, + }, + FailedValidation { + local: connection::HostPort, + peer: connection::HostPort, + }, + Closed { + local: connection::HostPort, + peer: connection::HostPort, + }, + ReusedSourceConnectionId { + seq: u64, + old: (connection::HostPort, connection::HostPort), + new: (connection::HostPort, connection::HostPort), + }, + PeerMigrated { + old: connection::HostPort, + new: connection::HostPort, + }, } impl From for PathEvent { - fn from(path_event: quiche::PathEvent) -> Self { - match path_event { - quiche::PathEvent::New(local, peer) => PathEvent::New { - local: connection::HostPort::from(local), - peer: connection::HostPort::from(peer), - }, - quiche::PathEvent::Validated(local, peer) => PathEvent::Validated { - local: connection::HostPort::from(local), - peer: connection::HostPort::from(peer), - }, - quiche::PathEvent::FailedValidation(local, peer) => PathEvent::FailedValidation { - local: connection::HostPort::from(local), - peer: connection::HostPort::from(peer), - }, - quiche::PathEvent::Closed(local, peer) => PathEvent::Closed { - local: connection::HostPort::from(local), - peer: connection::HostPort::from(peer), - }, - quiche::PathEvent::ReusedSourceConnectionId(seq, old, new) => PathEvent::ReusedSourceConnectionId { - seq, - old: (connection::HostPort::from(old.0), connection::HostPort::from(old.1)), - new: (connection::HostPort::from(new.0), connection::HostPort::from(new.1)), - }, - quiche::PathEvent::PeerMigrated(old, new) => PathEvent::PeerMigrated { - old: connection::HostPort::from(old), - new: connection::HostPort::from(new), - }, + fn from(path_event: quiche::PathEvent) -> Self { + match path_event { + quiche::PathEvent::New(local, peer) => PathEvent::New { + local: connection::HostPort::from(local), + peer: connection::HostPort::from(peer), + }, + quiche::PathEvent::Validated(local, peer) => PathEvent::Validated { + local: connection::HostPort::from(local), + peer: connection::HostPort::from(peer), + }, + quiche::PathEvent::FailedValidation(local, peer) => PathEvent::FailedValidation { + local: connection::HostPort::from(local), + peer: connection::HostPort::from(peer), + }, + quiche::PathEvent::Closed(local, peer) => PathEvent::Closed { + local: connection::HostPort::from(local), + peer: connection::HostPort::from(peer), + }, + quiche::PathEvent::ReusedSourceConnectionId(seq, old, new) => { + PathEvent::ReusedSourceConnectionId { + seq, + old: ( + connection::HostPort::from(old.0), + connection::HostPort::from(old.1), + ), + new: ( + connection::HostPort::from(new.0), + connection::HostPort::from(new.1), + ), + } + } + quiche::PathEvent::PeerMigrated(old, new) => PathEvent::PeerMigrated { + old: connection::HostPort::from(old), + new: connection::HostPort::from(new), + }, + } } - } } // This is an iterator of the host #[napi(iterator)] -pub struct HostIter(pub (crate) quiche::SocketAddrIter); +pub struct HostIter(pub(crate) quiche::SocketAddrIter); #[napi] impl Generator for HostIter { - type Yield = connection::HostPort; - type Next = (); - type Return = (); + type Yield = connection::HostPort; + type Next = (); + type Return = (); - fn next(&mut self, _value: Option) -> Option { - return self.0.next().map( - |socket_addr| socket_addr.into() - ); - } + fn next(&mut self, _value: Option) -> Option { + return self.0.next().map(|socket_addr| socket_addr.into()); + } } /// Equivalent to quiche::PathStats #[napi(object)] pub struct PathStats { - pub local_host: connection::HostPort, - pub peer_host: connection::HostPort, - pub active: bool, - pub recv: i64, - pub sent: i64, - pub lost: i64, - pub retrans: i64, - pub rtt: i64, - pub cwnd: i64, - pub sent_bytes: i64, - pub recv_bytes: i64, - pub lost_bytes: i64, - pub stream_retrans_bytes: i64, - pub pmtu: i64, - pub delivery_rate: i64, + pub local_host: connection::HostPort, + pub peer_host: connection::HostPort, + pub active: bool, + pub recv: i64, + pub sent: i64, + pub lost: i64, + pub retrans: i64, + pub rtt: i64, + pub cwnd: i64, + pub sent_bytes: i64, + pub recv_bytes: i64, + pub lost_bytes: i64, + pub stream_retrans_bytes: i64, + pub pmtu: i64, + pub delivery_rate: i64, } impl From for PathStats { - fn from(path_stats: quiche::PathStats) -> Self { - PathStats { - local_host: connection::HostPort::from(path_stats.local_addr), - peer_host: connection::HostPort::from(path_stats.peer_addr), - active: path_stats.active, - recv: path_stats.recv as i64, - sent: path_stats.sent as i64, - lost: path_stats.lost as i64, - retrans: path_stats.retrans as i64, - rtt: path_stats.rtt.as_millis() as i64, - cwnd: path_stats.cwnd as i64, - sent_bytes: path_stats.sent_bytes as i64, - recv_bytes: path_stats.recv_bytes as i64, - lost_bytes: path_stats.lost_bytes as i64, - stream_retrans_bytes: path_stats.stream_retrans_bytes as i64, - pmtu: path_stats.pmtu as i64, - delivery_rate: path_stats.delivery_rate as i64, + fn from(path_stats: quiche::PathStats) -> Self { + PathStats { + local_host: connection::HostPort::from(path_stats.local_addr), + peer_host: connection::HostPort::from(path_stats.peer_addr), + active: path_stats.active, + recv: path_stats.recv as i64, + sent: path_stats.sent as i64, + lost: path_stats.lost as i64, + retrans: path_stats.retrans as i64, + rtt: path_stats.rtt.as_millis() as i64, + cwnd: path_stats.cwnd as i64, + sent_bytes: path_stats.sent_bytes as i64, + recv_bytes: path_stats.recv_bytes as i64, + lost_bytes: path_stats.lost_bytes as i64, + stream_retrans_bytes: path_stats.stream_retrans_bytes as i64, + pmtu: path_stats.pmtu as i64, + delivery_rate: path_stats.delivery_rate as i64, + } } - } } #[napi(iterator)] -pub struct PathStatsIter(pub (crate) Box>); +pub struct PathStatsIter(pub(crate) Box>); #[napi] impl Generator for PathStatsIter { - type Yield = PathStats; - type Next = (); - type Return = (); + type Yield = PathStats; + type Next = (); + type Return = (); - fn next(&mut self, _value: Option) -> Option { - return self.0.next().map( - |path_stats| path_stats.into() - ); - } + fn next(&mut self, _value: Option) -> Option { + return self.0.next().map(|path_stats| path_stats.into()); + } } diff --git a/src/native/napi/stream.rs b/src/native/napi/stream.rs index 1f8f676e..cfbd9fee 100644 --- a/src/native/napi/stream.rs +++ b/src/native/napi/stream.rs @@ -1,18 +1,16 @@ +use napi::bindgen_prelude::Generator; use napi_derive::napi; -use napi::bindgen_prelude::{Generator}; #[napi(iterator)] -pub struct StreamIter(pub (crate) quiche::StreamIter); +pub struct StreamIter(pub(crate) quiche::StreamIter); #[napi] impl Generator for StreamIter { - type Yield = i64; - type Next = (); - type Return = (); + type Yield = i64; + type Next = (); + type Return = (); - fn next(&mut self, _value: Option) -> Option { - return self.0.next().map( - |stream_id| stream_id as i64 - ); - } + fn next(&mut self, _value: Option) -> Option { + return self.0.next().map(|stream_id| stream_id as i64); + } } diff --git a/src/types.ts b/src/types.ts index aad435e0..d469193d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,8 @@ import type QUICStream from './QUICStream.js'; import type { CryptoError } from './native/types.js'; +type POJO = { [key: string]: any }; + /** * Opaque types are wrappers of existing types * that require smart constructors @@ -350,6 +352,7 @@ type StreamCodeToReason = (type: 'read' | 'write', code: number) => any; type QUICStreamMap = Map; export type { + POJO, Opaque, Class, Callback,