From 44a0b4ca1bc6b588fb6428c64d7af5b1cdf1ad66 Mon Sep 17 00:00:00 2001 From: myst Date: Sat, 14 Sep 2024 03:52:04 +0200 Subject: [PATCH] refactor(backend): extracted telnet options from client - negotiations are now optional since they need to be set one-by-one (server and client) - added TelnetOptionHandler to define seperate telnet options - re-implemented charset as handle-charset-option - re-implemented echo as handle-echo-option - all other options are answered with a negative DONT or WONT - refactored telnet-client --- .../src/core/middleware/use-rest-endpoints.ts | 24 +- backend/src/features/telnet/telnet-client.ts | 367 ++++++++---------- .../types/telnet-charset-subnogiation.ts | 5 - .../telnet/types/telnet-negotiations.ts | 39 +- .../telnet/types/telnet-option-handler.ts | 49 +++ .../types/telnet-subnegotiation-result.ts | 15 + .../telnet/utils/handle-charset-option.ts | 117 ++++++ .../telnet/utils/handle-echo-option.ts | 38 ++ 8 files changed, 426 insertions(+), 228 deletions(-) delete mode 100644 backend/src/features/telnet/types/telnet-charset-subnogiation.ts create mode 100644 backend/src/features/telnet/types/telnet-option-handler.ts create mode 100644 backend/src/features/telnet/types/telnet-subnegotiation-result.ts create mode 100644 backend/src/features/telnet/utils/handle-charset-option.ts create mode 100644 backend/src/features/telnet/utils/handle-echo-option.ts diff --git a/backend/src/core/middleware/use-rest-endpoints.ts b/backend/src/core/middleware/use-rest-endpoints.ts index 6c2c42f3..dd195718 100644 --- a/backend/src/core/middleware/use-rest-endpoints.ts +++ b/backend/src/core/middleware/use-rest-endpoints.ts @@ -1,5 +1,7 @@ import { Express, Request, Response } from 'express'; +import { TelnetControlSequences } from '../../features/telnet/types/telnet-control-sequences.js'; +import { TelnetOptions } from '../../features/telnet/types/telnet-options.js'; import { logger } from '../../shared/utils/logger.js'; import { SocketManager } from '../sockets/socket-manager.js'; @@ -13,10 +15,24 @@ export const useRestEndpoints = ( const connections = Object.entries(socketManager.mudConnections).flatMap( ([connectionKey, con]) => { const negotiations = Object.entries(con.telnet?.negotiations || {}).map( - ([negotiationKey, negotiations]) => ({ - code: negotiationKey, - ...negotiations, - }), + ([negotiationKey, negotiations]) => { + const key = Number(negotiationKey); + + return { + code: TelnetOptions[key], + ...{ + server: negotiations?.server + ? TelnetControlSequences[negotiations?.server] + : {}, + }, + ...{ + client: negotiations?.client + ? TelnetControlSequences[negotiations?.client] + : {}, + }, + ...negotiations?.subnegotiation, + }; + }, ); return { diff --git a/backend/src/features/telnet/telnet-client.ts b/backend/src/features/telnet/telnet-client.ts index 47759a8b..25702380 100644 --- a/backend/src/features/telnet/telnet-client.ts +++ b/backend/src/features/telnet/telnet-client.ts @@ -9,10 +9,12 @@ import { TelnetSocket } from 'telnet-stream'; import tls from 'tls'; import { logger } from '../../shared/utils/logger.js'; -import { TelnetCharsetSubnogiation } from './types/telnet-charset-subnogiation.js'; import { TelnetControlSequences } from './types/telnet-control-sequences.js'; import { TelnetNegotiations } from './types/telnet-negotiations.js'; +import { TelnetOptionHandler } from './types/telnet-option-handler.js'; import { TelnetOptions } from './types/telnet-options.js'; +import { handleCharsetOption } from './utils/handle-charset-option.js'; +import { handleEchoOption } from './utils/handle-echo-option.js'; import { TelnetSocketWrapper } from './utils/telnet-socket-wrapper.js'; type TelnetClientEvents = { @@ -21,8 +23,8 @@ type TelnetClientEvents = { negotiationChanged: [ { option: TelnetOptions; - server: TelnetControlSequences; - client: TelnetControlSequences; + server?: TelnetControlSequences; + client?: TelnetControlSequences; }, ]; }; @@ -37,6 +39,8 @@ export class TelnetClient extends EventEmitter { private connected: boolean = false; + private optionsHandler: Map; + public get isConnected(): boolean { return this.connected; } @@ -56,7 +60,7 @@ export class TelnetClient extends EventEmitter { telnetHost: string, telnetPort: number, useTls: boolean, - private readonly encoding: BufferEncoding, + encoding: BufferEncoding, ) { super(); @@ -71,6 +75,16 @@ export class TelnetClient extends EventEmitter { bufferSize: 65536, }); + this.optionsHandler = new Map([ + [ + TelnetOptions.TELOPT_CHARSET, + handleCharsetOption(this.telnetSocket, encoding), + ], + [TelnetOptions.TELOPT_ECHO, handleEchoOption(this.telnetSocket)], + ]); + + this.telnetSocket.on('connect', () => this.handleConnect()); + this.telnetSocket.on('close', (hadErrors) => this.handleClose(hadErrors)); this.telnetSocket.on('do', (option) => this.handleDo(option)); @@ -106,6 +120,10 @@ export class TelnetClient extends EventEmitter { this.connected = false; } + private handleConnect(): void { + logger.info(`[Telnet-Client] Connected`); + } + private handleClose(hadErrors: boolean): void { this.connected = false; @@ -113,277 +131,208 @@ export class TelnetClient extends EventEmitter { } private handleDo(option: TelnetOptions): void { - switch (option) { - case TelnetOptions.TELOPT_CHARSET: { - this.telnetSocket.writeWill(option); + this.updateNegotiations(option, { + server: TelnetControlSequences.DO, + }); + + const handler = this.optionsHandler.get(option); - this.updateNegotiations(option, { - server: TelnetControlSequences.DO, - client: TelnetControlSequences.WILL, - }); + const handlerResult = handler?.handleDo(); - return; - } + if (handlerResult !== undefined) { + this.updateNegotiations(option, { + client: handlerResult, + }); + } else { + this.telnetSocket.writeWont(option); - case TelnetOptions.TELOPT_TM: { - this.updateNegotiations(option, { - server: TelnetControlSequences.DO, - client: TelnetControlSequences.WILL, - }); + this.updateNegotiations(option, { + client: TelnetControlSequences.WONT, // we answer negatively but we should WILL everything possible + }); + } - this.telnetSocket.writeWill(option); + // switch (option) { - return; - } + // case TelnetOptions.TELOPT_TM: { + // this.updateNegotiations(option, { + // server: TelnetControlSequences.DO, + // client: TelnetControlSequences.WILL, + // }); - case TelnetOptions.TELOPT_NAWS: { - this.updateNegotiations(option, { - server: TelnetControlSequences.DO, - client: TelnetControlSequences.WILL, - }); + // this.telnetSocket.writeWill(option); - this.telnetSocket.writeWill(option); + // return; + // } - return; - } - } + // case TelnetOptions.TELOPT_NAWS: { + // this.updateNegotiations(option, { + // server: TelnetControlSequences.DO, + // client: TelnetControlSequences.WILL, + // }); - this.updateNegotiations(option, { - server: TelnetControlSequences.DO, - client: TelnetControlSequences.WONT, - }); + // this.telnetSocket.writeWill(option); - this.telnetSocket.writeWont(option); + // return; + // } + // } return; } private handleDont(option: TelnetOptions): void { - this.telnetSocket.writeWont(option); - this.updateNegotiations(option, { server: TelnetControlSequences.DONT, - client: TelnetControlSequences.WONT, }); - } - private handleWill(option: TelnetOptions): void { - switch (option) { - case TelnetOptions.TELOPT_CHARSET: { - this.telnetSocket.writeDo(option); - - this.updateNegotiations(option, { - server: TelnetControlSequences.WILL, - client: TelnetControlSequences.DO, - }); - - return; - } - - case TelnetOptions.TELOPT_ECHO: { - this.telnetSocket.writeDo(option); - - this.updateNegotiations(option, { - server: TelnetControlSequences.WILL, - client: TelnetControlSequences.DO, - }); + const handler = this.optionsHandler.get(option); - // socket_io.emit('mud-signal', { - // signal: 'NOECHO-START', - // id: this.mudOptions?.id, - // }); + const handlerResult = handler?.handleDont(); - return; - } - - case TelnetOptions.TELOPT_GMCP: { - this.telnetSocket.writeDo(option); - - this.updateNegotiations(option, { - server: TelnetControlSequences.WILL, - client: TelnetControlSequences.DO, - }); + if (handlerResult !== undefined) { + this.updateNegotiations(option, { + client: handlerResult, + }); + } else { + this.telnetSocket.writeWont(option); - return; - } + this.updateNegotiations(option, { + client: TelnetControlSequences.WONT, + }); } + } + private handleWill(option: TelnetOptions): void { this.updateNegotiations(option, { server: TelnetControlSequences.WILL, - client: TelnetControlSequences.DONT, }); - this.telnetSocket.writeDont(option); - } + const handler = this.optionsHandler.get(option); - private handleWont(option: TelnetOptions): void { - switch (option) { - case TelnetOptions.TELOPT_ECHO: { - this.telnetSocket.writeDont(option); + const handlerResult = handler?.handleWill(); - this.updateNegotiations(option, { - server: TelnetControlSequences.WONT, - client: TelnetControlSequences.DONT, - }); + if (handlerResult !== undefined) { + this.updateNegotiations(option, { + client: handlerResult, + }); + } else { + this.telnetSocket.writeDont(option); - return; - } + this.updateNegotiations(option, { + client: TelnetControlSequences.DONT, // we answer negatively but we should DO everything possible + }); } - this.telnetSocket.writeDo(option); - - this.updateNegotiations(option, { - server: TelnetControlSequences.WONT, - client: TelnetControlSequences.DO, - }); - } - - private handleSub(option: TelnetOptions, serverChunk: Buffer): void { - if ( - option === TelnetOptions.TELOPT_TTYPE && - new Uint8Array(serverChunk)[0] === 1 - ) { - const nullBuf = Buffer.alloc(1, 0); // TELQUAL_IS + // switch (option) { - const buf = Buffer.from('WebMud3a'); + // case TelnetOptions.TELOPT_GMCP: { + // this.telnetSocket.writeDo(option); - const sendBuf = Buffer.concat([nullBuf, buf], buf.length + 1); + // this.updateNegotiations(option, { + // server: TelnetControlSequences.WILL, + // client: TelnetControlSequences.DO, + // }); - this.telnetSocket.writeSub(option, sendBuf); - - return; - } - - if ( - option === TelnetOptions.TELOPT_CHARSET && - new Uint8Array(serverChunk)[0] === - TelnetCharsetSubnogiation.CHARSET_REQUEST - ) { - const command = Buffer.alloc( - 1, - TelnetCharsetSubnogiation.CHARSET_ACCEPTED, - ); - - const serverCharsets = serverChunk.toString().split(' '); - - /** - * This function remappes the given charset from the environment to the one supported by the Telnet server - */ - const matchCharset = ( - charset: BufferEncoding, - serverCharsets: string[], - ): string => { - if (charset === 'utf-8') { - if (serverCharsets.includes('UTF-8')) { - return 'UTF-8'; - } - } - - if (charset === 'ascii') { - if (serverCharsets.includes('US-ASCII')) { - return 'US-ASCII'; - } - } - - if (charset === 'latin1') { - if (serverCharsets.includes('ISO-8859-1')) { - return 'ISO-8859-1'; - } - } - - logger.warn( - `[Socket-Manager] [Client] charset ${charset} is not supported by the Telnet server. Only ${serverCharsets.join(', ')} are supported. Default to utf-8`, - ); - - return 'UTF-8'; - }; + // return; + // } + // } + } - const charset = matchCharset(this.encoding, serverCharsets); + private handleWont(option: TelnetOptions): void { + this.updateNegotiations(option, { + server: TelnetControlSequences.WONT, + }); - const data = Buffer.from(charset); + const handler = this.optionsHandler.get(option); - const message = Buffer.concat([command, data], data.length + 1); + const handlerResult = handler?.handleWont(); - this.updateSubNegotiation(TelnetOptions.TELOPT_CHARSET, { - clientChunk: message.toString(), - serverChunk: serverChunk.toString(), - clientOption: data.toString().toLocaleLowerCase(), + if (handlerResult !== undefined) { + this.updateNegotiations(option, { + client: handlerResult, }); + } else { + this.telnetSocket.writeDont(option); - this.telnetSocket.writeSub(option, message); - - return; + this.updateNegotiations(option, { + client: TelnetControlSequences.DONT, // we answer negatively but we should DO everything possible + }); } + } - if (option === TelnetOptions.TELOPT_GMCP) { - const tmpstr = serverChunk.toString(); - - const ix = tmpstr.indexOf(' '); - - // const jx = tmpstr.indexOf('.'); + private handleSub(option: TelnetOptions, serverChunk: Buffer): void { + this.updateNegotiations(option, { + serverChunk, + }); - let jsdata = tmpstr.substr(ix + 1); - if (ix < 0 || jsdata === '') jsdata = '{}'; + const handler = this.optionsHandler.get(option); - // socket_io.emit( - // 'mud-gmcp-incoming', - // this.mudOptions?.id, - // tmpstr.substr(0, jx), - // tmpstr.substr(jx + 1, ix - jx), - // JSON.parse(jsdata), - // ); + const handlerResult = handler?.handleSub?.(serverChunk); - return; + if (handlerResult !== undefined && handlerResult !== null) { + this.updateNegotiations(option, handlerResult); } } /** - * Updates the negotiations for a given Telnet option with the provided server and client control sequences. - * This function is special because it maps all enum values to its keys, making it easier to observe. + * Updates the negotiations and subnegotiations for a given Telnet option. + * This method allows updating both the control sequences and the subnegotiations. * - * @param {TelnetOptions} option - The Telnet option to update the negotiations for. - * @param {Object} negotiations - An object containing the server and client control sequences for the option. - * @param {TelnetControlSequences} negotiations.server - The server control sequence for the option. - * @param {TelnetControlSequences} negotiations.client - The client control sequence for the option. + * @param {TelnetOptions} option - The Telnet option to update. + * @param {Object} negotiations - An object containing the server and client control sequences, as well as subnegotiations. + * @param {TelnetControlSequences} [negotiations.server] - The server control sequence for the option. + * @param {TelnetControlSequences} [negotiations.client] - The client control sequence for the option. + * @param {Buffer} [negotiations.serverChunk] - The server chunk for subnegotiations (optional). + * @param {Buffer} [negotiations.clientChunk] - The client chunk for subnegotiations (optional). + * @param {string} [negotiations.clientOption] - The client option for subnegotiations (optional). */ private updateNegotiations( option: TelnetOptions, negotiations: { - server: TelnetControlSequences; - client: TelnetControlSequences; + server?: TelnetControlSequences; + client?: TelnetControlSequences; + serverChunk?: Buffer; + clientChunk?: Buffer; + clientOption?: string; }, ): void { - this._negotiations[option] = { - server: TelnetControlSequences[ - negotiations.server - ] as keyof typeof TelnetControlSequences, - client: TelnetControlSequences[ - negotiations.client - ] as keyof typeof TelnetControlSequences, + const existing = this._negotiations[option] || {}; + + // Update the control sequences for server and client + const updatedNegotiation = { + ...existing, + ...{ + server: negotiations.server ?? existing.server, + client: negotiations.client ?? existing.client, + }, }; + // Update the subnegotiation properties if they exist + if (existing) { + updatedNegotiation.subnegotiation = { + ...existing.subnegotiation, + ...{ + serverChunk: negotiations.serverChunk + ? negotiations.serverChunk.toString() + : existing.subnegotiation?.serverChunk, + clientChunk: negotiations.clientChunk + ? negotiations.clientChunk.toString() + : existing.subnegotiation?.clientChunk, + clientOption: + negotiations.clientOption ?? existing.subnegotiation?.clientOption, + }, + }; + } + + // Update the negotiations object + this._negotiations[option] = updatedNegotiation; + + // Emit event with the updated negotiation values this.emit('negotiationChanged', { option, client: negotiations.client, server: negotiations.server, }); } - - private updateSubNegotiation( - option: TelnetOptions, - subnegotiation: { - serverChunk: string; - clientChunk: string; - clientOption: string; - }, - ): void { - const existing = this._negotiations[option]; - - // It is not allowed to discuss subnegotiations without a negotiation beforehand - if (existing) { - existing.subnegotiation = subnegotiation; - } - } } function createTelnetConnection( diff --git a/backend/src/features/telnet/types/telnet-charset-subnogiation.ts b/backend/src/features/telnet/types/telnet-charset-subnogiation.ts deleted file mode 100644 index 7f6a47ea..00000000 --- a/backend/src/features/telnet/types/telnet-charset-subnogiation.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum TelnetCharsetSubnogiation { - CHARSET_REJECTED = 0, - CHARSET_REQUEST = 1, - CHARSET_ACCEPTED = 2, -} diff --git a/backend/src/features/telnet/types/telnet-negotiations.ts b/backend/src/features/telnet/types/telnet-negotiations.ts index dad15751..1f589ba6 100644 --- a/backend/src/features/telnet/types/telnet-negotiations.ts +++ b/backend/src/features/telnet/types/telnet-negotiations.ts @@ -2,20 +2,39 @@ import { TelnetControlSequences } from './telnet-control-sequences.js'; import { TelnetOptions } from './telnet-options.js'; /** - * Represents the negotiation status of Telnet options between a server and a client. - * Each key in the object represents a Telnet option and its corresponding negotiation on the server and client sides. - * But the keys are the string representation of the enum values for ease of observation. - * The value of each key is an object with `server` and `client` properties, which are of type `TelnetControlSequences`. - * The optional `?` in the type definition indicates that not all Telnet options may be negotiated. + * Represents the state of negotiations for Telnet options, including both server + * and client control sequences, as well as any subnegotiation data. */ export type TelnetNegotiations = { -readonly [key in keyof typeof TelnetOptions]?: { - server: keyof typeof TelnetControlSequences; - client: keyof typeof TelnetControlSequences; + /** + * The control sequence received from the server (DO, DON'T, WILL, WON'T). + */ + server?: TelnetControlSequences; + + /** + * The control sequence sent by the client (DO, DON'T, WILL, WON'T). + */ + client?: TelnetControlSequences; + + /** + * Optional subnegotiation data exchanged between the server and client. + */ subnegotiation?: { - serverChunk: string; - clientChunk: string; - clientOption: string; + /** + * The data chunk sent by the server during subnegotiation. + */ + serverChunk?: string; + + /** + * The data chunk sent by the client during subnegotiation. + */ + clientChunk?: string; + + /** + * The client option used during subnegotiation (e.g., a charset or mode). + */ + clientOption?: string; }; }; }; diff --git a/backend/src/features/telnet/types/telnet-option-handler.ts b/backend/src/features/telnet/types/telnet-option-handler.ts new file mode 100644 index 00000000..9887c833 --- /dev/null +++ b/backend/src/features/telnet/types/telnet-option-handler.ts @@ -0,0 +1,49 @@ +import { TelnetControlSequences } from './telnet-control-sequences.js'; +import { TelnetSubnegotiationResult } from './telnet-subnegotiation-result.js'; + +/** + * A set of handler functions for managing Telnet option negotiation. + * Each handler responds to a specific Telnet negotiation command (DO, DON'T, WILL, WON'T), + * and optionally handles subnegotiation. + */ +export type TelnetOptionHandler = { + /** + * Handles the "DO" command sent by the server, indicating that the server + * wants the client to enable a particular option. + * + * @returns {TelnetControlSequences} The control sequence (WILL, WONT) sent back to the server. + */ + handleDo: () => TelnetControlSequences; + + /** + * Handles the "DON'T" command sent by the server, indicating that the server + * wants the client to disable a particular option. + * + * @returns {TelnetControlSequences} The control sequence (WILL, WONT) sent back to the server. + */ + handleDont: () => TelnetControlSequences; + + /** + * Handles the "WILL" command sent by the server, indicating that the server + * is willing to enable a particular option. + * + * @returns {TelnetControlSequences} The control sequence (DO, DONT) sent back to the server. + */ + handleWill: () => TelnetControlSequences; + + /** + * Handles the "WON'T" command sent by the server, indicating that the server + * is unwilling to enable a particular option. + * + * @returns {TelnetControlSequences} The control sequence (DO, DONT) sent back to the server. + */ + handleWont: () => TelnetControlSequences; + + /** + * Handles the subnegotiation message sent by the server. + * + * @param {Buffer} serverChunk - The data chunk sent by the server during subnegotiation. + * @returns {TelnetSubnegotiationResult | null} The subnegotiation result, or null if not applicable. + */ + handleSub?: (serverChunk: Buffer) => TelnetSubnegotiationResult; +}; diff --git a/backend/src/features/telnet/types/telnet-subnegotiation-result.ts b/backend/src/features/telnet/types/telnet-subnegotiation-result.ts new file mode 100644 index 00000000..ca4203c7 --- /dev/null +++ b/backend/src/features/telnet/types/telnet-subnegotiation-result.ts @@ -0,0 +1,15 @@ +/** + * Represents the result of a Telnet subnegotiation. + * It contains the data that the client sends back to the server during subnegotiation. + * The client option may return null if the subnegotiation is not applicable. + */ +export type TelnetSubnegotiationResult = { + /** + * The chunk of data that the client sends during subnegotiation. + */ + clientChunk: Buffer; + /** + * The client option used during subnegotiation (e.g., a charset or mode). + */ + clientOption: string; +} | null; diff --git a/backend/src/features/telnet/utils/handle-charset-option.ts b/backend/src/features/telnet/utils/handle-charset-option.ts new file mode 100644 index 00000000..50e3e1db --- /dev/null +++ b/backend/src/features/telnet/utils/handle-charset-option.ts @@ -0,0 +1,117 @@ +import { TelnetSocket } from 'telnet-stream'; + +import { logger } from '../../../shared/utils/logger.js'; +import { TelnetControlSequences } from '../types/telnet-control-sequences.js'; +import { TelnetOptionHandler } from '../types/telnet-option-handler.js'; +import { TelnetOptions } from '../types/telnet-options.js'; +import { TelnetSubnegotiationResult } from '../types/telnet-subnegotiation-result.js'; + +enum TelnetCharsetSubnogiation { + CHARSET_REJECTED = 0, + CHARSET_REQUEST = 1, + CHARSET_ACCEPTED = 2, +} + +const handleCharsetDo = + (socket: TelnetSocket) => (): TelnetControlSequences => { + socket.writeWill(TelnetOptions.TELOPT_CHARSET); + + return TelnetControlSequences.WILL; + }; + +const handleCharsetDont = + (socket: TelnetSocket) => (): TelnetControlSequences => { + socket.writeWont(TelnetOptions.TELOPT_CHARSET); + + return TelnetControlSequences.WONT; + }; + +const handleCharsetWill = + (socket: TelnetSocket) => (): TelnetControlSequences => { + socket.writeDo(TelnetOptions.TELOPT_CHARSET); + + return TelnetControlSequences.DO; + }; + +const handleCharsetWont = + (socket: TelnetSocket) => (): TelnetControlSequences => { + socket.writeDont(TelnetOptions.TELOPT_CHARSET); + + return TelnetControlSequences.DONT; + }; + +const handleCharsetSub = + (socket: TelnetSocket, encoding: BufferEncoding) => + (serverChunk: Buffer): TelnetSubnegotiationResult => { + if ( + new Uint8Array(serverChunk)[0] === + TelnetCharsetSubnogiation.CHARSET_REQUEST + ) { + const command = Buffer.alloc( + 1, + TelnetCharsetSubnogiation.CHARSET_ACCEPTED, + ); + + /** + * This function remappes the given charset from the environment to the one supported by the Telnet server + */ + const matchCharset = ( + charset: BufferEncoding, + serverCharsets: string[], + ): string => { + if (charset === 'utf-8') { + if (serverCharsets.includes('UTF-8')) { + return 'UTF-8'; + } + } + + if (charset === 'ascii') { + if (serverCharsets.includes('US-ASCII')) { + return 'US-ASCII'; + } + } + + if (charset === 'latin1') { + if (serverCharsets.includes('ISO-8859-1')) { + return 'ISO-8859-1'; + } + } + + logger.warn( + `[Socket-Manager] [Client] charset ${charset} is not supported by the Telnet server. Only ${serverCharsets.join(', ')} are supported. Default to utf-8`, + ); + + return 'UTF-8'; + }; + + const serverCharsets = serverChunk.toString().split(' '); + + const charset = matchCharset(encoding, serverCharsets); + + const data = Buffer.from(charset); + + const message = Buffer.concat([command, data], data.length + 1); + + socket.writeSub(TelnetOptions.TELOPT_CHARSET, message); + + return { + clientChunk: message, + clientOption: charset.toLocaleLowerCase(), + }; + } + + return null; + }; + +export const handleCharsetOption = ( + socket: TelnetSocket, + encoding: BufferEncoding, +): TelnetOptionHandler => { + return { + handleDo: handleCharsetDo(socket), + handleDont: handleCharsetDont(socket), + handleWill: handleCharsetWill(socket), + handleWont: handleCharsetWont(socket), + handleSub: handleCharsetSub(socket, encoding), + }; +}; diff --git a/backend/src/features/telnet/utils/handle-echo-option.ts b/backend/src/features/telnet/utils/handle-echo-option.ts new file mode 100644 index 00000000..340f7907 --- /dev/null +++ b/backend/src/features/telnet/utils/handle-echo-option.ts @@ -0,0 +1,38 @@ +import { TelnetSocket } from 'telnet-stream'; + +import { TelnetControlSequences } from '../types/telnet-control-sequences.js'; +import { TelnetOptionHandler } from '../types/telnet-option-handler.js'; +import { TelnetOptions } from '../types/telnet-options.js'; + +const handleEchoDo = (socket: TelnetSocket) => (): TelnetControlSequences => { + socket.writeWill(TelnetOptions.TELOPT_ECHO); + + return TelnetControlSequences.WILL; +}; + +const handleEchoDont = (socket: TelnetSocket) => (): TelnetControlSequences => { + socket.writeWont(TelnetOptions.TELOPT_ECHO); + + return TelnetControlSequences.WONT; +}; + +const handleEchoWill = (socket: TelnetSocket) => (): TelnetControlSequences => { + socket.writeDo(TelnetOptions.TELOPT_ECHO); + + return TelnetControlSequences.DO; +}; + +const handleEchoWont = (socket: TelnetSocket) => (): TelnetControlSequences => { + socket.writeDont(TelnetOptions.TELOPT_ECHO); + + return TelnetControlSequences.DONT; +}; + +export const handleEchoOption = (socket: TelnetSocket): TelnetOptionHandler => { + return { + handleDo: handleEchoDo(socket), + handleDont: handleEchoDont(socket), + handleWill: handleEchoWill(socket), + handleWont: handleEchoWont(socket), + }; +};