From 480d492795662ab525a40ace390aa8dbfd07dcb4 Mon Sep 17 00:00:00 2001 From: Gordon MacPherson Date: Wed, 26 Nov 2025 00:56:00 +0000 Subject: [PATCH 1/6] Update reactor.ts This is a fix for crashing when sending bad data over TCP. Likely the proper fix is to swap to exceptions, and process them OR pass them to an error handler. ```bash # 1 run netcat nc localhost 9980 # 2 send bad data /213/213.23.12/.3421 # 3 no crash with this fix, or crash without the fix ``` --- trimsock.js/packages/trimsock-js/lib/reactor.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/trimsock.js/packages/trimsock-js/lib/reactor.ts b/trimsock.js/packages/trimsock-js/lib/reactor.ts index 2ceb8d9..9ddb0b8 100644 --- a/trimsock.js/packages/trimsock-js/lib/reactor.ts +++ b/trimsock.js/packages/trimsock-js/lib/reactor.ts @@ -740,9 +740,13 @@ export abstract class Reactor { this.errorHandler(command, exchange, error); } } else { - const exchange = - exchangeId !== undefined && this.exchanges.get(exchangeId, source); - assert(exchange, `Unknown exchange id: ${exchangeId}!`); + const exchange = exchangeId !== undefined && this.exchanges.get(exchangeId, source); + if (!exchange) { + console.error(`Unknown exchange id: ${exchangeId}`); + // note: temp swapped this to an error, will discuss with @elementbound how these errors should be processed + // this used to crash the program when bad data came over TCP, this means things like botnets would crash the application. + return; + } exchange.push(command); } } From a3918e72cc47bfd5b20f4722e940cf86b7598bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Wed, 26 Nov 2025 20:11:18 +0100 Subject: [PATCH 2/6] catch exceptions --- .../packages/trimsock-js/lib/reactor.ts | 35 ++++++++++--------- .../trimsock-js/spec/reactor/reactor.spec.ts | 26 ++++++++++++++ 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/trimsock.js/packages/trimsock-js/lib/reactor.ts b/trimsock.js/packages/trimsock-js/lib/reactor.ts index 9ddb0b8..13d5369 100644 --- a/trimsock.js/packages/trimsock-js/lib/reactor.ts +++ b/trimsock.js/packages/trimsock-js/lib/reactor.ts @@ -683,15 +683,19 @@ export abstract class Reactor { * @param data incoming data * @param source source connection */ - public ingest(data: Buffer | string, source: T): void { - // TODO: Invoke error handler when ingest fails? - const reader = this.ensureReaderFor(source); - - if (typeof data === "string") reader.ingest(Buffer.from(data, "utf8")); - else reader.ingest(data); - - for (const item of reader.commands()) { - this.handle(new Command(item), source); + public async ingest(data: Buffer | string, source: T): Promise { + try { + const reader = this.ensureReaderFor(source); + + if (typeof data === "string") reader.ingest(Buffer.from(data, "utf8")); + else reader.ingest(data); + + await Promise.all(reader.commands() + .map(it => this.handle(new Command(it), source)) + ); + } catch (e) { + // TODO: Error handler + console.error("Ingest failed!", e) } } @@ -727,7 +731,8 @@ export abstract class Reactor { let filterIdx = 0; const next = async () => { - if (filterIdx >= this.filters.length) await handler(command, exchange); + if (filterIdx >= this.filters.length) + await handler(command, exchange); else { filterIdx += 1; await this.filters[filterIdx - 1](next, command, exchange); @@ -740,13 +745,9 @@ export abstract class Reactor { this.errorHandler(command, exchange, error); } } else { - const exchange = exchangeId !== undefined && this.exchanges.get(exchangeId, source); - if (!exchange) { - console.error(`Unknown exchange id: ${exchangeId}`); - // note: temp swapped this to an error, will discuss with @elementbound how these errors should be processed - // this used to crash the program when bad data came over TCP, this means things like botnets would crash the application. - return; - } + const exchange = + exchangeId !== undefined && this.exchanges.get(exchangeId, source); + assert(exchange, `Unknown exchange id: ${exchangeId}!`); exchange.push(command); } } diff --git a/trimsock.js/packages/trimsock-js/spec/reactor/reactor.spec.ts b/trimsock.js/packages/trimsock-js/spec/reactor/reactor.spec.ts index 3ee9234..df6b708 100644 --- a/trimsock.js/packages/trimsock-js/spec/reactor/reactor.spec.ts +++ b/trimsock.js/packages/trimsock-js/spec/reactor/reactor.spec.ts @@ -23,6 +23,32 @@ describe("Reactor", () => { // Outbox should be empty expect(reactor.outbox).toBeEmpty(); }); + + test("should not throw on unknown exchange", async () => { + expect( + async () => + await reactor.ingest(".1234 foo\n", "0") + ).not.toThrow() + }) + + test("should never throw", async () => { + // Throw random strings at the reactor and see if it fails + const count = 1024 + const length = 32 + const charset = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=,./<>?" + + const inputs = [...new Array(count)] + .map(() => [...new Array(length)] + .map(() => ~~(Math.random() * charset.length)) + .map(idx => charset.charAt(idx)) + .join("") + "\n" + ) + + const promise = Promise.all(inputs.map(it => reactor.ingest(it, "0"))) + + console.log("Randomized inputs: ", inputs) + expect(async () => await promise).not.toThrow() + }) }); describe("knownCommands", () => { From bf275b4405bc4e8b582240c6b694fc6dfa501963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Wed, 26 Nov 2025 20:18:25 +0100 Subject: [PATCH 3/6] add ingest error handler --- .../packages/trimsock-js/lib/reactor.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/trimsock.js/packages/trimsock-js/lib/reactor.ts b/trimsock.js/packages/trimsock-js/lib/reactor.ts index 13d5369..d47d31e 100644 --- a/trimsock.js/packages/trimsock-js/lib/reactor.ts +++ b/trimsock.js/packages/trimsock-js/lib/reactor.ts @@ -31,6 +31,8 @@ export type CommandErrorHandler = ( error: unknown, ) => void; +export type IngestErrorHandler = (error: unknown, input: Buffer) => void; + /** * Callback type for generating exchange ID's * @@ -551,6 +553,7 @@ export abstract class Reactor { private handlers: Map> = new Map(); private defaultHandler: CommandHandler = () => {}; private errorHandler: CommandErrorHandler = () => {}; + private ingestErrorHandler: IngestErrorHandler = () => {}; private filters: CommandFilter[] = []; private exchanges = new ExchangeMap>(); @@ -563,6 +566,8 @@ export abstract class Reactor { /** * Register a command handler * +* Note that one command can only have one handler active at a time. Calling +* this method will replace the currently active handler, if it exists. * * @param commandName command name * @param handler callback function @@ -577,6 +582,9 @@ export abstract class Reactor { * * Whenever a command is received that has no associated handler, the unknown * command handler is called. + * + * Note that there's only one handler at any time, calling this method will + * replace the currently active handler. * * @param handler callback function */ @@ -590,6 +598,9 @@ export abstract class Reactor { * * Whenever an error occurs during command processing ( e.g. in one of the * registered handlers ), the error handler is called. + * + * Note that there's only one handler at any time, calling this method will + * replace the currently active handler. * * @param handler callback function */ @@ -598,6 +609,23 @@ export abstract class Reactor { return this; } + /** + * Register an ingest error handler + * + * If the reactor receives input that it can't manage ( e.g. it's malformed or + * the command is too long ), it rejects the command and calls the ingest error + * handler. + * + * Note that there's only one handler at any time, calling this method will + * replace the currently active handler. + * + * @param handler callback function +*/ + public onIngestError(handler: IngestErrorHandler): this { + this.ingestErrorHandler = handler; + return this; + } + /** * Register a new command filter * @@ -694,8 +722,7 @@ export abstract class Reactor { .map(it => this.handle(new Command(it), source)) ); } catch (e) { - // TODO: Error handler - console.error("Ingest failed!", e) + this.ingestErrorHandler(e, data) } } From 43a94cb723a01809b3d5dcdcd0ae47c7ef5f51a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Wed, 26 Nov 2025 20:19:02 +0100 Subject: [PATCH 4/6] bv --- trimsock.js/package.json | 2 +- .../packages/trimsock-bun/package.json | 2 +- .../packages/trimsock-js/lib/reactor.ts | 47 +++++++++---------- trimsock.js/packages/trimsock-js/package.json | 2 +- .../trimsock-js/spec/reactor/reactor.spec.ts | 41 ++++++++-------- .../packages/trimsock-node/package.json | 2 +- 6 files changed, 48 insertions(+), 48 deletions(-) diff --git a/trimsock.js/package.json b/trimsock.js/package.json index 72001fe..e8a0cb0 100644 --- a/trimsock.js/package.json +++ b/trimsock.js/package.json @@ -1,7 +1,7 @@ { "name": "trimsock", "private": true, - "version": "0.18.2", + "version": "0.18.3", "type": "module", "scripts": { "docs": "typedoc", diff --git a/trimsock.js/packages/trimsock-bun/package.json b/trimsock.js/packages/trimsock-bun/package.json index f345475..dc59cc4 100644 --- a/trimsock.js/packages/trimsock-bun/package.json +++ b/trimsock.js/packages/trimsock-bun/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-bun", - "version": "0.18.2", + "version": "0.18.3", "module": "dist/index.js", "type": "module", "author": "Tamás Gálffy", diff --git a/trimsock.js/packages/trimsock-js/lib/reactor.ts b/trimsock.js/packages/trimsock-js/lib/reactor.ts index d47d31e..8498f5a 100644 --- a/trimsock.js/packages/trimsock-js/lib/reactor.ts +++ b/trimsock.js/packages/trimsock-js/lib/reactor.ts @@ -566,8 +566,8 @@ export abstract class Reactor { /** * Register a command handler * -* Note that one command can only have one handler active at a time. Calling -* this method will replace the currently active handler, if it exists. + * Note that one command can only have one handler active at a time. Calling + * this method will replace the currently active handler, if it exists. * * @param commandName command name * @param handler callback function @@ -582,9 +582,9 @@ export abstract class Reactor { * * Whenever a command is received that has no associated handler, the unknown * command handler is called. - * - * Note that there's only one handler at any time, calling this method will - * replace the currently active handler. + * + * Note that there's only one handler at any time, calling this method will + * replace the currently active handler. * * @param handler callback function */ @@ -598,9 +598,9 @@ export abstract class Reactor { * * Whenever an error occurs during command processing ( e.g. in one of the * registered handlers ), the error handler is called. - * - * Note that there's only one handler at any time, calling this method will - * replace the currently active handler. + * + * Note that there's only one handler at any time, calling this method will + * replace the currently active handler. * * @param handler callback function */ @@ -610,17 +610,17 @@ export abstract class Reactor { } /** - * Register an ingest error handler - * - * If the reactor receives input that it can't manage ( e.g. it's malformed or - * the command is too long ), it rejects the command and calls the ingest error - * handler. - * - * Note that there's only one handler at any time, calling this method will - * replace the currently active handler. - * - * @param handler callback function -*/ + * Register an ingest error handler + * + * If the reactor receives input that it can't manage ( e.g. it's malformed or + * the command is too long ), it rejects the command and calls the ingest error + * handler. + * + * Note that there's only one handler at any time, calling this method will + * replace the currently active handler. + * + * @param handler callback function + */ public onIngestError(handler: IngestErrorHandler): this { this.ingestErrorHandler = handler; return this; @@ -718,11 +718,11 @@ export abstract class Reactor { if (typeof data === "string") reader.ingest(Buffer.from(data, "utf8")); else reader.ingest(data); - await Promise.all(reader.commands() - .map(it => this.handle(new Command(it), source)) + await Promise.all( + reader.commands().map((it) => this.handle(new Command(it), source)), ); } catch (e) { - this.ingestErrorHandler(e, data) + this.ingestErrorHandler(e, data); } } @@ -758,8 +758,7 @@ export abstract class Reactor { let filterIdx = 0; const next = async () => { - if (filterIdx >= this.filters.length) - await handler(command, exchange); + if (filterIdx >= this.filters.length) await handler(command, exchange); else { filterIdx += 1; await this.filters[filterIdx - 1](next, command, exchange); diff --git a/trimsock.js/packages/trimsock-js/package.json b/trimsock.js/packages/trimsock-js/package.json index 72d0196..8f0d8f4 100644 --- a/trimsock.js/packages/trimsock-js/package.json +++ b/trimsock.js/packages/trimsock-js/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-js", - "version": "0.18.2", + "version": "0.18.3", "module": "dist/index.js", "type": "module", "author": "Tamás Gálffy", diff --git a/trimsock.js/packages/trimsock-js/spec/reactor/reactor.spec.ts b/trimsock.js/packages/trimsock-js/spec/reactor/reactor.spec.ts index df6b708..6e0948e 100644 --- a/trimsock.js/packages/trimsock-js/spec/reactor/reactor.spec.ts +++ b/trimsock.js/packages/trimsock-js/spec/reactor/reactor.spec.ts @@ -26,29 +26,30 @@ describe("Reactor", () => { test("should not throw on unknown exchange", async () => { expect( - async () => - await reactor.ingest(".1234 foo\n", "0") - ).not.toThrow() - }) + async () => await reactor.ingest(".1234 foo\n", "0"), + ).not.toThrow(); + }); test("should never throw", async () => { // Throw random strings at the reactor and see if it fails - const count = 1024 - const length = 32 - const charset = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=,./<>?" - - const inputs = [...new Array(count)] - .map(() => [...new Array(length)] - .map(() => ~~(Math.random() * charset.length)) - .map(idx => charset.charAt(idx)) - .join("") + "\n" - ) - - const promise = Promise.all(inputs.map(it => reactor.ingest(it, "0"))) - - console.log("Randomized inputs: ", inputs) - expect(async () => await promise).not.toThrow() - }) + const count = 1024; + const length = 32; + const charset = + "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=,./<>?"; + + const inputs = [...new Array(count)].map( + () => + `${[...new Array(length)] + .map(() => ~~(Math.random() * charset.length)) + .map((idx) => charset.charAt(idx)) + .join("")}\n`, + ); + + const promise = Promise.all(inputs.map((it) => reactor.ingest(it, "0"))); + + console.log("Randomized inputs: ", inputs); + expect(async () => await promise).not.toThrow(); + }); }); describe("knownCommands", () => { diff --git a/trimsock.js/packages/trimsock-node/package.json b/trimsock.js/packages/trimsock-node/package.json index 2688c8d..d9d50c0 100644 --- a/trimsock.js/packages/trimsock-node/package.json +++ b/trimsock.js/packages/trimsock-node/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-node", - "version": "0.18.2", + "version": "0.18.3", "module": "./dist/index.js", "type": "module", "author": "Tamás Gálffy", From 33930749774e603f512a64bec53df17a82a1327a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Wed, 26 Nov 2025 20:19:27 +0100 Subject: [PATCH 5/6] minor bv --- trimsock.js/package.json | 2 +- trimsock.js/packages/trimsock-bun/package.json | 2 +- trimsock.js/packages/trimsock-js/package.json | 2 +- trimsock.js/packages/trimsock-node/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/trimsock.js/package.json b/trimsock.js/package.json index e8a0cb0..010dda2 100644 --- a/trimsock.js/package.json +++ b/trimsock.js/package.json @@ -1,7 +1,7 @@ { "name": "trimsock", "private": true, - "version": "0.18.3", + "version": "0.19.0", "type": "module", "scripts": { "docs": "typedoc", diff --git a/trimsock.js/packages/trimsock-bun/package.json b/trimsock.js/packages/trimsock-bun/package.json index dc59cc4..099f1de 100644 --- a/trimsock.js/packages/trimsock-bun/package.json +++ b/trimsock.js/packages/trimsock-bun/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-bun", - "version": "0.18.3", + "version": "0.19.0", "module": "dist/index.js", "type": "module", "author": "Tamás Gálffy", diff --git a/trimsock.js/packages/trimsock-js/package.json b/trimsock.js/packages/trimsock-js/package.json index 8f0d8f4..a6ff895 100644 --- a/trimsock.js/packages/trimsock-js/package.json +++ b/trimsock.js/packages/trimsock-js/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-js", - "version": "0.18.3", + "version": "0.19.0", "module": "dist/index.js", "type": "module", "author": "Tamás Gálffy", diff --git a/trimsock.js/packages/trimsock-node/package.json b/trimsock.js/packages/trimsock-node/package.json index d9d50c0..0ac06ae 100644 --- a/trimsock.js/packages/trimsock-node/package.json +++ b/trimsock.js/packages/trimsock-node/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-node", - "version": "0.18.3", + "version": "0.19.0", "module": "./dist/index.js", "type": "module", "author": "Tamás Gálffy", From 9c5b9d9df296d0456dc130b0493c4840a4582d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Wed, 26 Nov 2025 20:21:59 +0100 Subject: [PATCH 6/6] fxs --- trimsock.js/packages/trimsock-js/lib/reactor.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/trimsock.js/packages/trimsock-js/lib/reactor.ts b/trimsock.js/packages/trimsock-js/lib/reactor.ts index 8498f5a..190c629 100644 --- a/trimsock.js/packages/trimsock-js/lib/reactor.ts +++ b/trimsock.js/packages/trimsock-js/lib/reactor.ts @@ -31,7 +31,10 @@ export type CommandErrorHandler = ( error: unknown, ) => void; -export type IngestErrorHandler = (error: unknown, input: Buffer) => void; +export type IngestErrorHandler = ( + error: unknown, + input: string | Buffer, +) => void; /** * Callback type for generating exchange ID's