diff --git a/package.json b/package.json index bbf6ae9..dc4c257 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,12 @@ { "name": "aim-oscar-server", "version": "1.0.0", - "main": "dist/src/index.js", - "license": "MIT", + "license": "GPLv4", "scripts": { - "dev:tsc": "tsc --watch", - "dev:nodemon:auth-go": "nodemon --watch ./ -e go --ignore '*_test.go' --delay 200ms --exec 'go build && ./aim-oscar || exit 1' --signal SIGTERM", - "dev:nodemon:auth": "nodemon --watch ./dist --delay 200ms dist/src/main-auth.js", - "dev:nodemon:chat": "nodemon --watch ./dist --delay 200ms dist/src/main-chat.js", - "start": "tsc && node ./dist/index.js" + "dev:": "nodemon --watch ./ -e go --ignore '*_test.go' --delay 200ms --exec 'go build && ./aim-oscar || exit 1' --signal SIGTERM", + "start": "go build && ./aim-oscar" }, "devDependencies": { - "@tsconfig/node14": "^1.0.1", - "@types/node": "^16.7.13", - "nodemon": "^2.0.12", - "typescript": "^4.4.2" - }, - "dependencies": { - "table-layout": "^3.0.0" + "nodemon": "^2.0.12" } } diff --git a/src/communicator.ts b/src/communicator.ts deleted file mode 100644 index 59917c6..0000000 --- a/src/communicator.ts +++ /dev/null @@ -1,142 +0,0 @@ -import net from "net"; -import { FLAP, SNAC, TLV, TLVType } from './structures'; -import { logDataStream } from './util'; - -import BaseService from "./services/base"; - -export interface User { - uin: string, - username: string, - password: string, - memberSince: Date, -} - -export default class Communicator { - - private keepaliveInterval? : NodeJS.Timer; - private _sequenceNumber = 0; - private messageBuffer = Buffer.alloc(0); - public services : {[key: number]: BaseService} = {}; - public user? : User; - - constructor(public socket : net.Socket) {} - - startListening() { - this.socket.on('data', (data : Buffer) => { - // we could get multiple FLAP messages, keep a running buffer of incoming - // data and shift-off however many successful FLAPs we can make - this.messageBuffer = Buffer.concat([this.messageBuffer, data]); - - while (this.messageBuffer.length > 0) { - try { - const flap = FLAP.fromBuffer(this.messageBuffer); - console.log('DATA-----------------------'); - console.log('RAW\n' + logDataStream(flap.toBuffer())); - console.log('RECV', flap.toString()); - this.messageBuffer = this.messageBuffer.slice(flap.length); - this.handleMessage(flap); - console.log('-----------------------DATA'); - } catch (e) { - console.error("Error handling message:", e); - } - } - }); - - this.keepaliveInterval = setInterval(() => { - const keepaliveFlap = new FLAP(0x05, this.nextReqID, Buffer.from("")); - this.socket.write(keepaliveFlap.toBuffer()); - }, 4 * 60 * 1000); - - this.socket.on('close', () => { - if (this.keepaliveInterval) { - clearInterval(this.keepaliveInterval); - } - }); - - // Start negotiating a connection - const hello = new FLAP(0x01, 0, Buffer.from([0x00, 0x00, 0x00, 0x01])); - this.send(hello); - } - - registerServices(services : BaseService[] = []) { - // Make a map of the service number to the service handler - this.services = {}; - services.forEach((service) => { - this.services[service.service] = service; - }); - } - - get nextReqID() { - return ++this._sequenceNumber & 0xFFFF; - } - - send(message : FLAP) { - console.log('SEND', message.toString()); - console.log('RAW\n' + logDataStream(message.toBuffer())); - this.socket.write(message.toBuffer()); - } - - handleMessage(message : FLAP) { - switch (message.channel) { - case 1: - // No SNACs on channel 1 - if (!(message.payload instanceof Buffer)) { - return; - } - - const protocol = message.payload.readUInt32BE(); - - if (protocol !== 1) { - console.log('Unsupported protocol:', protocol); - this.socket.end(); - return; - } - - if (message.payload.length <= 4) { - return; - } - - const tlv = TLV.fromBuffer(message.payload.slice(4)); - console.log('thing sent to channel 1:'); - console.log(tlv.toString()); - - if (tlv.type === 0x06) { - // client sent us a cookie - const {cookie, user} = JSON.parse(tlv.payload.toString()); - console.log('cookie:', cookie); - this.user = user; - } - - if (tlv.type === TLVType.GetServices) { // Requesting available services - // this is just a dword list of subtype families - const servicesOffered : Buffer[] = []; - Object.values(this.services).forEach((subtype) => { - servicesOffered.push(Buffer.from([0x00, subtype.service])); - }); - const resp = new FLAP(2, this.nextReqID, - new SNAC(0x01, 0x03, Buffer.concat(servicesOffered))); - this.send(resp); - return; - } - - return; - case 2: - if (!(message.payload instanceof SNAC)) { - console.error('Expected SNAC payload'); - return; - } - - const familyService = this.services[message.payload.service]; - if (!familyService) { - console.warn('no handler for service', message.payload.service); - return; - } - - familyService.handleMessage(message); - return; - default: - console.warn('No handlers for channel', message.channel); - return; - } - } -} diff --git a/src/consts.ts b/src/consts.ts deleted file mode 100644 index 3eb8de2..0000000 --- a/src/consts.ts +++ /dev/null @@ -1,64 +0,0 @@ -export const AIM_MD5_STRING = "AOL Instant Messenger (SM)"; -export const FLAGS_EMPTY = Buffer.from([0x00, 0x00, 0x00, 0x00]); - -export const enum USER_STATUS_VARIOUS { - /** - * Status webaware flag - */ - WEBAWARE = 0x0001, - /** - * Status show ip flag - */ - SHOWIP = 0x0002, - /** - * User birthday flag - */ - BIRTHDAY = 0x0008, - /** - * User active webfront flag - */ - WEBFRONT = 0x0020, - /** - * Direct connection not supported - */ - DCDISABLED = 0x0100, - /** - * Direct connection upon authorization - */ - DCAUTH = 0x1000, - /** - * DC only with contact users - */ - DCCONT = 0x2000, -} - -export const enum USER_STATUS { - /** - * Status is online - */ - ONLINE = 0x0000, - /** - * Status is away - */ - AWAY = 0x0001, - /** - * Status is no not disturb (DND) - */ - DND = 0x0002, - /** - * Status is not available (N/A) - */ - NA = 0x0004, - /** - * Status is occupied (BISY) - */ - OCCUPIED = 0x0010, - /** - * Status is free for chat - */ - FREE4CHAT = 0x0020, - /** - * Status is invisible - */ - INVISIBLE = 0x0100, -} diff --git a/src/haxor-proxy.ts b/src/haxor-proxy.ts deleted file mode 100644 index cba9284..0000000 --- a/src/haxor-proxy.ts +++ /dev/null @@ -1,21 +0,0 @@ -import net from "net"; - -const server = net.createServer((socket) => { - socket.setTimeout(5 * 60 * 1000); // 5 minute timeout - socket.on('timeout', () => { - console.log('socket timeout'); - socket.end(); - }); - - socket.on('end', () => { - console.log('client disconnected...'); - }); -}); - -server.on('error', (err) => { - console.error(err); -}); - -server.listen(9999, () => { - console.log('proxy ready'); -}) diff --git a/src/main-auth.ts b/src/main-auth.ts deleted file mode 100644 index 1cece8b..0000000 --- a/src/main-auth.ts +++ /dev/null @@ -1,38 +0,0 @@ -import net from 'net'; -import Communicator from './communicator'; - -import AuthorizationRegistrationService from "./services/0x17-AuthorizationRegistration"; - -const server = net.createServer((socket) => { - console.log('client connected...'); - socket.setTimeout(5 * 60 * 1000); // 5 minute timeout - - socket.on('error', (e) => { - console.error('socket encountered an error:', e); - socket.end(); - }); - - socket.on('timeout', () => { - console.log('socket timeout'); - socket.end(); - }); - - socket.on('end', () => { - console.log('client disconnected...'); - }); - - const comm = new Communicator(socket); - const services = [ - new AuthorizationRegistrationService(comm), - ]; - comm.registerServices(services); - comm.startListening(); -}); - -server.on('error', (err) => { - throw err; -}); - -server.listen(5190, () => { - console.log('AUTH ready on :5190'); -}); diff --git a/src/main-chat.ts b/src/main-chat.ts deleted file mode 100644 index 0cc7f0c..0000000 --- a/src/main-chat.ts +++ /dev/null @@ -1,66 +0,0 @@ -import net from 'net'; -import Communicator from './communicator'; - -import GenericServiceControls from "./services/0x01-GenericServiceControls"; -import LocationServices from "./services/0x02-LocationSerices"; -import BuddyListManagement from "./services/0x03-BuddyListManagement"; -import ICBM from "./services/0x04-ICBM"; -import Invitation from "./services/0x06-Invitation"; -import Administration from "./services/0x07-Administration"; -import Popups from "./services/0x08-Popups"; -import PrivacyManagement from "./services/0x09-PrivacyManagement"; -import UserLookup from "./services/0x0a-UserLookup"; -import UsageStats from "./services/0x0b-UsageStats"; -import ChatNavigation from "./services/0x0d-ChatNavigation"; -import Chat from "./services/0x0e-Chat";; -import DirectorySearch from "./services/0x0f-DirectorySearch"; -import ServerStoredBuddyIcons from "./services/0x10-ServerStoredBuddyIcons"; -import SSI from "./services/0x13-SSI"; - -const server = net.createServer((socket) => { - console.log('client connected...'); - socket.setTimeout(5 * 60 * 1000); // 5 minute timeout - - socket.on('error', (e) => { - console.error('socket encountered an error:', e); - socket.end(); - }); - - socket.on('timeout', () => { - console.log('socket timeout'); - socket.end(); - }); - - socket.on('end', () => { - console.log('client disconnected...'); - }); - - const comm = new Communicator(socket); - const services = [ - new GenericServiceControls(comm), - // new LocationServices(comm), - // new BuddyListManagement(comm), - new ICBM(comm), - // new Invitation(comm), - // new Administration(comm), - // new Popups(comm), - // new PrivacyManagement(comm), - // new UserLookup(comm), - // new UsageStats(comm), - // new ChatNavigation(comm), - // new Chat(comm), - // new DirectorySearch(comm), - // new ServerStoredBuddyIcons(comm), - // new SSI(comm), - ]; - comm.registerServices(services); - comm.startListening(); -}); - -server.on('error', (err) => { - throw err; -}); - -server.listen(5191, () => { - console.log('CHAT ready on :5191'); -}); diff --git a/src/services/0x01-GenericServiceControls.ts b/src/services/0x01-GenericServiceControls.ts deleted file mode 100644 index 85258fb..0000000 --- a/src/services/0x01-GenericServiceControls.ts +++ /dev/null @@ -1,117 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; -import { FLAP, Rate, RateClass, RatedServiceGroup, RateGroupPair, SNAC, TLV } from '../structures'; -import { USER_STATUS_VARIOUS, USER_STATUS } from '../consts'; -import { char, word, dword, dot2num } from '../structures/bytes'; - -export default class GenericServiceControls extends BaseService { - private allowViewIdle = false; - private allowViewMemberSince = false; - - constructor(communicator : Communicator) { - super({service: 0x01, version: 0x03}, [0x06, 0x0e, 0x14, 0x17], communicator) - } - - override handleMessage(message : FLAP) { - if (!(message.payload instanceof SNAC)) { - throw new Error('Require SNAC'); - } - - if (message.payload.subtype === 0x06) { // Client ask server for rate limits info - - // HACK: set rate limits for all services. I can't tell which message subtypes they support so - // make it set rate limits for everything under 0x21. - const pairs : RateGroupPair[] = []; - Object.values(this.communicator.services).forEach((service) => { - // for (let subtype of service.supportedSubtypes) { - for (let subtype = 0; subtype < 0x21; subtype++) { - pairs.push(new RateGroupPair(service.service, subtype)); - } - }); - - const resp = new FLAP(0x02, this.nextReqID, - SNAC.forRateClass(0x01, 0x07, [ - new Rate( - new RateClass(1, 80, 2500, 2000, 1500, 800, 3400 /*fake*/, 6000, 0, 0), - new RatedServiceGroup(1, pairs), - ) - ])); - this.send(resp); - - const motd = new FLAP(0x02, this.nextReqID, - new SNAC(0x01, 0x13, Buffer.concat([ - word(0x0004), - (new TLV(0x0B, Buffer.from("Hello world!"))).toBuffer(), - ]))); - this.send(motd); - return; - } - - if (message.payload.subtype === 0x0e) { // Client requests own online information - const uin = this.communicator.user?.username || 'user'; - const warning = 0; - const since = +(new Date('December 17, 1998 03:24:00')); - const externalIP = dot2num(this.communicator.socket.remoteAddress!.split(':').pop()!); - - const tlvs : TLV[] = [ - new TLV(0x01, char(0x80)), - new TLV(0x06, dword(USER_STATUS_VARIOUS.WEBAWARE | USER_STATUS_VARIOUS.DCDISABLED << 2 + USER_STATUS.ONLINE)), - new TLV(0x0A, dword(externalIP)), - new TLV(0x0F, dword(0)), // TODO: track idle time, - new TLV(0x03, dword(Math.floor(Date.now() / 1000))), - new TLV(0x1E, dword(0)), // Unknown - new TLV(0x05, dword(Math.floor(since / 1000))), - new TLV(0x0C, Buffer.concat([ - dword(externalIP), - dword(5700), // DC TCP Port - dword(0x04000000), // DC Type, - word(0x0400), // DC Protocol Version - dword(0), // DC Auth Cookie - dword(0), // Web Front port - dword(0x300), // Client Features ? - dword(0), // Last Info Update Time - dword(0), // last EXT info update time, - dword(0), // last ext status update time - ])) - ]; - - const payloadHeader = Buffer.alloc(1 + uin.length + 2 + 2); - payloadHeader.writeInt8(uin.length); - payloadHeader.set(Buffer.from(uin), 1); - payloadHeader.writeInt16BE(warning, 1 + uin.length); - payloadHeader.writeInt16BE(tlvs.length, 1 + uin.length + 2); - - const buf = Buffer.concat([payloadHeader, ...tlvs.map((tlv) => tlv.toBuffer())]) - - const resp = new FLAP(0x02, this.nextReqID, - new SNAC(0x01, 0x0f, buf)); - - this.send(resp); - return; - } - - if (message.payload.subtype === 0x14) { - /* - Client setting privacy settings - Bit 1 - Allows other AIM users to see how long you've been idle. - Bit 2 - Allows other AIM users to see how long you've been a member. - */ - const mask = (message.payload.payload as Buffer).readUInt32BE(); - this.allowViewIdle = (mask & 0x01) > 0; - this.allowViewMemberSince = (mask & 0x02) > 0; - console.log('allowViewIdle:', this.allowViewIdle, 'allowViewMemberSince', this.allowViewMemberSince); - return; - } - - if (message.payload.subtype === 0x17) { - const serviceVersions : Buffer[] = []; - Object.values(this.communicator.services).forEach((subtype) => { - serviceVersions.push(Buffer.from([0x00, subtype.service, 0x00, subtype.version])); - }); - const resp = new FLAP(0x02, this.nextReqID, - new SNAC(0x01, 0x18, Buffer.concat(serviceVersions))); - this.send(resp); - return; - } - } -} diff --git a/src/services/0x02-LocationSerices.ts b/src/services/0x02-LocationSerices.ts deleted file mode 100644 index 3055297..0000000 --- a/src/services/0x02-LocationSerices.ts +++ /dev/null @@ -1,37 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -import { FLAP, SNAC, TLV } from '../structures'; -import { char, word, dword, dot2num } from '../structures/bytes'; -import { USER_STATUS, USER_STATUS_VARIOUS } from '../consts'; - -export default class LocationServices extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x02, version: 0x01}, [0x02, 0x04], communicator) - } - - override handleMessage(message : FLAP) { - if (!(message.payload instanceof SNAC)) { - throw new Error('Expecting SNACs for LocationServices') - } - - // request location service parameters and limitations - if (message.payload.subtype === 0x02) { - const resp = new FLAP(0x02, this.nextReqID, - new SNAC(0x02,0x03, [ - new TLV(0x01, word(0x400)), // max profile length - new TLV(0x02, word(0x10)), // max capabilities - new TLV(0x03, word(0xA)), // unknown - new TLV(0x04, word(0x1000)), - ])); - this.send(resp); - return; - } - - if (message.payload.subtype === 0x04) { - // Client use this snac to set its location information (like client - // profile string, client directory profile string, client capabilities). - return; - } - } -} diff --git a/src/services/0x03-BuddyListManagement.ts b/src/services/0x03-BuddyListManagement.ts deleted file mode 100644 index fee5cb5..0000000 --- a/src/services/0x03-BuddyListManagement.ts +++ /dev/null @@ -1,30 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -import { FLAGS_EMPTY } from '../consts'; -import { FLAP, SNAC, TLV } from '../structures'; -import { word } from '../structures/bytes'; - -export default class BuddyListManagement extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x03, version: 0x01}, [0x02], communicator) - } - - override handleMessage(message : FLAP) { - if (!(message.payload instanceof SNAC)) { - throw new Error('Expected SNACs') - } - - if (message.payload.subtype === 0x02) { - const resp = new FLAP(0x02, this.nextReqID, - new SNAC(0x03, 0x03, [ - new TLV(0x01, word(600)), // 600 max buddies - new TLV(0x02, word(750)), // 750 max watchers - new TLV(0x03, word(512)), // 512 max online notifications ? - ])); - - this.send(resp); - return; - } - } -} diff --git a/src/services/0x04-ICBM.ts b/src/services/0x04-ICBM.ts deleted file mode 100644 index e28e60b..0000000 --- a/src/services/0x04-ICBM.ts +++ /dev/null @@ -1,157 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -import { FLAGS_EMPTY } from '../consts'; -import { FLAP, SNAC, TLV } from '../structures'; -import { char, dword, word } from '../structures/bytes'; - -interface ChannelSettings { - channel: number, - messageFlags: number, - maxMessageSnacSize: number, - maxSenderWarningLevel: number, - maxReceiverWarningLevel: number, - minimumMessageInterval: number, - unknown : number -} - -export default class ICBM extends BaseService { - /* Inter-Client Basic Message - - This system passes messages from/to clients through the server - instead of directly between clients. - */ - private channel : ChannelSettings = { - channel: 0, - messageFlags: 3, - maxMessageSnacSize: 512, - maxSenderWarningLevel: 999, - maxReceiverWarningLevel: 999, - minimumMessageInterval: 0, - unknown: 1000, - }; - - private channels : ChannelSettings[] = []; - - constructor(communicator : Communicator) { - super({service: 0x04, version: 0x01}, [0x02, 0x04], communicator) - } - - override handleMessage(message : FLAP) { - if (!(message.payload instanceof SNAC)) { - throw new Error('Expected SNACs') - } - - if (message.payload.subtype === 0x02) { - // client is telling us about it's ICBM capabilities (whatever) - /* - xx xx word channel to setup - xx xx xx xx dword message flags - xx xx word max message snac size - xx xx word max sender warning level - xx xx word max receiver warning level - xx xx word minimum message interval (sec) - 00 00 word unknown parameter (also seen 03 E8) - */ - - if (!(message.payload.payload instanceof Buffer)) { - throw new Error('Expected Buffer payload for this SNAC'); - } - - const payload = message.payload.payload; - const channel = payload.readUInt16BE(0); - - // TODO: set settings based on channel provided - this.channel = { - channel, - messageFlags: payload.readUInt32BE(2), - maxMessageSnacSize: payload.readUInt16BE(6), - maxSenderWarningLevel: payload.readUInt16BE(8), - maxReceiverWarningLevel: payload.readUInt16BE(10), - minimumMessageInterval: payload.readUInt16BE(12), - unknown: 1000, //payload.readUInt16BE(14), - } - console.log("ICBM set channel", this.channel); - return; - } - - if (message.payload.subtype === 0x04) { - const payload = Buffer.alloc(16, 0x00); - payload.writeInt16BE(this.channel.channel, 0); - payload.writeInt32BE(this.channel.messageFlags, 2); - payload.writeInt16BE(this.channel.maxMessageSnacSize, 6); - payload.writeInt16BE(this.channel.maxSenderWarningLevel, 8); - payload.writeInt16BE(this.channel.maxReceiverWarningLevel, 10); - payload.writeInt16BE(this.channel.minimumMessageInterval, 12); - payload.writeInt16BE(this.channel.unknown, 14); - - const resp = new FLAP(0x02, this.nextReqID, - new SNAC(0x04, 0x05, payload)); - this.send(resp); - return; - } - - if (message.payload.subtype === 0x06) { - // Client sent us a message to deliver to another client - /* - Channel 1 is used for what would commonly be called an "instant message" (plain text messages). - Channel 2 is used for complex messages (rtf, utf8) and negotiating "rendezvous". These transactions end in something more complex happening, such as a chat invitation, or a file transfer. - Channel 3 is used for chat messages (not in the same family as these channels). - */ - - if (!(message.payload.payload instanceof Buffer)) { - throw new Error('this should be a buffer'); - } - - const msgId = message.payload.payload.readBigUInt64BE(0); - const channel = message.payload.payload.readUInt16BE(8); - const screenNameLength = message.payload.payload.readUInt8(10); - const screenName = message.payload.payload.slice(11, 11 + screenNameLength).toString(); - - console.log({ - msgId, channel, screenName, - }); - - if (channel === 1) { - const tlvs = TLV.fromBufferBlob(message.payload.payload.slice(11 + screenNameLength)); - console.log(tlvs); - - // does the client want us to acknowledge that we got the message? - const wantsAck = tlvs.find((tlv) => tlv.type === 3); - - // lets parse the message - const messageTLV = tlvs.find((tlv) => tlv.type === 2); - if (!messageTLV) { - // TODO: send back error response - throw new Error('need a message'); - } - - // Start parsing the message TLV payload - // first is the array of capabilities - const startOfMessageFragment = 2 + messageTLV.payload.readUInt16BE(2); - const lengthOfMessageText = messageTLV.payload.readUInt16BE(startOfMessageFragment + 2); - const messageText = messageTLV.payload.slice(startOfMessageFragment + 8, startOfMessageFragment + 8 + lengthOfMessageText).toString(); - console.log('The user said:', messageText); - - // The client usually wants a response that the server got the message. It checks that the message - // back has the same message ID that was sent and the user it was sent to. - if (wantsAck) { - const sender = this.communicator.user?.username || ""; - const msgIdBuffer = Buffer.alloc(32); - msgIdBuffer.writeBigUInt64BE(msgId); - const ackPayload = Buffer.from([ - ...msgIdBuffer, - ...word(0x02), - ...char(sender.length), - ...Buffer.from(sender), - ]); - const ackResp = new FLAP(2, this.nextReqID, new SNAC(0x04, 0x0c, ackPayload)); - this.send(ackResp); - } - - // TODO: find the connection that the receiver is connected to and send them the message - // this is the rest of the owl for this part - } - } - } -} diff --git a/src/services/0x06-Invitation.ts b/src/services/0x06-Invitation.ts deleted file mode 100644 index 5a02d69..0000000 --- a/src/services/0x06-Invitation.ts +++ /dev/null @@ -1,8 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -export default class Invitation extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x06, version: 0x01}, [], communicator) - } -} diff --git a/src/services/0x07-Administration.ts b/src/services/0x07-Administration.ts deleted file mode 100644 index f5a3483..0000000 --- a/src/services/0x07-Administration.ts +++ /dev/null @@ -1,8 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -export default class Administration extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x07, version: 0x01}, [], communicator) - } -} diff --git a/src/services/0x08-Popups.ts b/src/services/0x08-Popups.ts deleted file mode 100644 index 66edbd4..0000000 --- a/src/services/0x08-Popups.ts +++ /dev/null @@ -1,8 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -export default class Popups extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x08, version: 0x01}, [], communicator) - } -} diff --git a/src/services/0x09-PrivacyManagement.ts b/src/services/0x09-PrivacyManagement.ts deleted file mode 100644 index 52ede04..0000000 --- a/src/services/0x09-PrivacyManagement.ts +++ /dev/null @@ -1,38 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -import { FLAGS_EMPTY } from '../consts'; -import { FLAP, SNAC, TLV } from '../structures'; -import { word } from '../structures/bytes'; - - -export default class PrivacyManagement extends BaseService { - private permissionMask: number = 0xffff; // everyone - - constructor(communicator : Communicator) { - super({service: 0x09, version: 0x01}, [0x02, 0x04], communicator) - } - - override handleMessage(message : FLAP) { - if (!(message.payload instanceof SNAC)) { - throw new Error('Expected SNACs') - } - - if (message.payload.subtype === 0x02) { - const resp = new FLAP(0x02, this.nextReqID, - new SNAC(0x09, 0x03, [ - new TLV(0x01, word(200)), // max visible list size - new TLV(0x02, word(200)) // max invisible list size - ])); - this.send(resp); - return; - } - - if (message.payload.subtype === 0x04) { - // Client sends permission mask for classes of users that can talk to the client - this.permissionMask = (message.payload.payload as Buffer).readUInt32BE(); - console.log('set permission mask', this.permissionMask.toString(16)); - return; - } - } -} diff --git a/src/services/0x0a-UserLookup.ts b/src/services/0x0a-UserLookup.ts deleted file mode 100644 index 1383ede..0000000 --- a/src/services/0x0a-UserLookup.ts +++ /dev/null @@ -1,44 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; -import { FLAP, SNAC, TLV } from '../structures'; -import { word } from '../structures/bytes'; - -const emailToUin : {[key: string]: string[]} = { - 'bob@example.com': ['bobX0X0'], -}; - -export default class UserLookup extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x0a, version: 0x01}, [0x02], communicator) - } - - override handleMessage(message: FLAP) { - if (!(message.payload instanceof SNAC)) { - throw new Error('Require SNAC'); - } - - // Search for a user by email address - // TODO: don't return users that don't want to be found via email - if (message.payload.subtype === 0x02) { - if (!(message.payload instanceof Buffer)) { - // 0x0e: Incorrect SNAC format - const incorrectFormatResp = new FLAP(2, this.nextReqID, new SNAC(0x0a, 0x01, word(0x0e))); - this.send(incorrectFormatResp); - return; - } - - const email = message.payload.payload.toString(); - if (!emailToUin[email]) { - // 0x14: No Match - const noResult = new FLAP(2, this.nextReqID, new SNAC(0x0a, 0x01, word(0x14))); - this.send(noResult); - return; - } - - // Return list of TLVs of matching UINs - const results = emailToUin[email].map((uin) => new TLV(0x01, Buffer.from(uin))) - const resp = new FLAP(2, this.nextReqID, new SNAC(0x0a, 0x03, results)); - this.send(resp); - } - } -} diff --git a/src/services/0x0b-UsageStats.ts b/src/services/0x0b-UsageStats.ts deleted file mode 100644 index caa8090..0000000 --- a/src/services/0x0b-UsageStats.ts +++ /dev/null @@ -1,8 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -export default class UsageStats extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x0b, version: 0x01}, [], communicator) - } -} diff --git a/src/services/0x0d-ChatNavigation.ts b/src/services/0x0d-ChatNavigation.ts deleted file mode 100644 index 515ebba..0000000 --- a/src/services/0x0d-ChatNavigation.ts +++ /dev/null @@ -1,8 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -export default class ChatNavigation extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x0d, version: 0x02}, [], communicator) - } -} diff --git a/src/services/0x0e-Chat.ts b/src/services/0x0e-Chat.ts deleted file mode 100644 index 8e8cb79..0000000 --- a/src/services/0x0e-Chat.ts +++ /dev/null @@ -1,8 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -export default class Chat extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x0e, version: 0x01}, [], communicator) - } -} diff --git a/src/services/0x0f-DirectorySearch.ts b/src/services/0x0f-DirectorySearch.ts deleted file mode 100644 index 4527931..0000000 --- a/src/services/0x0f-DirectorySearch.ts +++ /dev/null @@ -1,8 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -export default class DirectorySearch extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x0f, version: 0x01}, [], communicator) - } -} diff --git a/src/services/0x10-ServerStoredBuddyIcons.ts b/src/services/0x10-ServerStoredBuddyIcons.ts deleted file mode 100644 index 30751bf..0000000 --- a/src/services/0x10-ServerStoredBuddyIcons.ts +++ /dev/null @@ -1,8 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -export default class ServerStoredBuddyIcons extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x10, version: 0x01}, [], communicator) - } -} diff --git a/src/services/0x13-SSI.ts b/src/services/0x13-SSI.ts deleted file mode 100644 index 608c16f..0000000 --- a/src/services/0x13-SSI.ts +++ /dev/null @@ -1,9 +0,0 @@ -import BaseService from './base'; -import Communicator from '../communicator'; - -// SSI is Server Stored Information -export default class SSI extends BaseService { - constructor(communicator : Communicator) { - super({service: 0x13, version: 0x01}, [], communicator) - } -} diff --git a/src/services/0x17-AuthorizationRegistration.ts b/src/services/0x17-AuthorizationRegistration.ts deleted file mode 100644 index 4d72cf0..0000000 --- a/src/services/0x17-AuthorizationRegistration.ts +++ /dev/null @@ -1,117 +0,0 @@ -import crypto from 'crypto'; -import BaseService from './base'; -import Communicator, { User } from '../communicator'; -import { FLAP, SNAC, TLV, ErrorCode, TLVType } from '../structures'; -import { word } from '../structures/bytes'; - -const { AIM_MD5_STRING, FLAGS_EMPTY } = require('../consts'); - -const users : {[key: string]: User} = { - 'toof': { - uin: '156089', - username: 'toof', - password: 'foo', - memberSince: new Date('December 17, 1998 03:24:00'), - } -}; - -export default class AuthorizationRegistrationService extends BaseService { - private cipher : string; - - constructor(communicator : Communicator) { - super({ service: 0x17, version: 0x01 }, [0x02, 0x06], communicator); - this.cipher = "HARDY"; - } - - override handleMessage(message : FLAP) { - if (message.payload instanceof Buffer) { - console.log('Wont handle Buffer payload'); - return; - } - - switch (message.payload.subtype) { - case 0x02: // Client login request (md5 login sequence) - const payload = message.payload.payload; - const clientNameTLV = payload.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.ClientName); - if (!clientNameTLV || !(clientNameTLV instanceof TLV)) { - return; - } - console.log("Attempting connection from", clientNameTLV.payload.toString('ascii')); - - const userTLV = payload.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.User); - if (!userTLV || !(userTLV instanceof TLV)) { - return; - } - - const username = userTLV.payload.toString('ascii'); - - if (!users[username]) { - const authResp = new FLAP(2, this.nextReqID, - new SNAC(0x17, 0x03, [ - TLV.forUsername(username), // username - TLV.forError(ErrorCode.IncorrectNick) // incorrect nick/password - ])); - - this.send(authResp); - return; - } - - const passwordHashTLV = payload.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.PasswordHash); - if (!passwordHashTLV || !(passwordHashTLV instanceof TLV)) { - return; - } - - const pwHash = crypto.createHash('md5'); - pwHash.update(this.cipher); - pwHash.update(users[username].password); - pwHash.update(AIM_MD5_STRING); - const digest = pwHash.digest('hex'); - - if (digest !== (passwordHashTLV as TLV).payload.toString('hex')) { - console.log('Invalid password for', username); - const authResp = new FLAP(2, this.nextReqID, - new SNAC(0x17, 0x03, [ - TLV.forUsername(username), // username - TLV.forError(ErrorCode.IncorrectNick) // incorrect nick/password - ])); - this.send(authResp); - - // Close this connection - const plsLeave = new FLAP(4, this.nextReqID, Buffer.from([])); - this.send(plsLeave); - return; - } - - const chatHost = this.communicator.socket.localAddress.split(':').pop() + ':5191'; - - const authResp = new FLAP(2, this.nextReqID, - new SNAC(0x17, 0x03, [ - TLV.forUsername(username), // username - TLV.forBOSAddress(chatHost), // BOS address - TLV.forCookie(JSON.stringify({cookie: 'uwu', user: users[username]})) // Authorization cookie - ])); - - this.communicator.user = Object.assign({username}, users[username]); - console.log(this.communicator.user); - - this.send(authResp); - - // tell them to leave - const disconnectResp = new FLAP(4, this.nextReqID, Buffer.alloc(0)); - this.send(disconnectResp); - - return; - case 0x06: // Request md5 authkey - const MD5AuthKeyHeader = Buffer.alloc(2, 0xFF, 'hex'); - MD5AuthKeyHeader.writeUInt16BE(this.cipher.length); - const md5ReqResp = new FLAP(2, this.nextReqID, - new SNAC(0x17, 0x07, - Buffer.concat([MD5AuthKeyHeader, Buffer.from(this.cipher, 'binary')]), - )); - this.send(md5ReqResp); - break; - } - } -} - -module.exports = AuthorizationRegistrationService; diff --git a/src/services/base.ts b/src/services/base.ts deleted file mode 100644 index 5f6225d..0000000 --- a/src/services/base.ts +++ /dev/null @@ -1,32 +0,0 @@ -import Communicator from "../communicator"; -import { FLAP } from "../structures"; - -interface ServiceFamilyVersion { - service : number, - version : number, -} - -export default abstract class BaseService { - public service : number; - public version : number; - - constructor({service, version} : ServiceFamilyVersion, public supportedSubtypes : number[], public communicator : Communicator) { - this.service = service; - this.version = version; - this.communicator = communicator; - this.supportedSubtypes = supportedSubtypes; - } - - send(message : FLAP) { - this.communicator.send(message); - } - - get nextReqID() { - return this.communicator.nextReqID; - } - - handleMessage(message : FLAP) : void { - throw new Error(''+ - `Unhandled message for service ${this.service.toString(16)} supporting version ${this.version.toString(16)}`); - } -} diff --git a/src/structures/ErrorCode.ts b/src/structures/ErrorCode.ts deleted file mode 100644 index b82a195..0000000 --- a/src/structures/ErrorCode.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const enum ErrorCode { - IncorrectNick = 0x04, -} diff --git a/src/structures/FLAP.ts b/src/structures/FLAP.ts deleted file mode 100644 index 06f9bb2..0000000 --- a/src/structures/FLAP.ts +++ /dev/null @@ -1,71 +0,0 @@ -import assert from "assert" - -import { SNAC } from "./SNAC"; -import { logDataStream } from '../util'; - -export class FLAP { - static fromBuffer(buf : Buffer) { - assert.equal(buf[0], 0x2a, 'Expected 0x2a at start of FLAP header'); - assert(buf.length >= 6, 'Expected at least 6 bytes for FLAP header'); - const channel = buf.readInt8(1); - const sequenceNumber = buf.slice(2,4).readInt16BE(0); - const payloadLength = buf.slice(4, 6).readInt16BE(0); - - if (buf.length < (6 + payloadLength)) { - throw new Error('FLAP payload larger than available buffer data'); - } - - let payload : Buffer | SNAC = buf.slice(6, 6 + payloadLength); - - if (channel === 2 && payloadLength > 0) { - payload = SNAC.fromBuffer(payload, payloadLength); - } - - return new FLAP(channel, sequenceNumber, payload) - } - - payloadLength: number; - - constructor(public channel: number, public sequenceNumber: number, public payload: Buffer | SNAC) { - this.channel = channel; - this.sequenceNumber = sequenceNumber; - - this.payload = payload; - - if (payload instanceof SNAC) { - this.payloadLength = payload.toBuffer().length; - } else { - this.payloadLength = payload.length; - } - } - - /** - * @returns Returns the byte length of this FLAP, includes header and payload - */ - get length() { - return 6 + this.payloadLength; - } - - toString() { - let payload = this.payload.toString(); - if (this.payload instanceof Buffer) { - payload = logDataStream(this.payload).split('\n').join('\n '); - } - return `ch:${this.channel}, dn: ${this.sequenceNumber}, len: ${this.payloadLength}, payload:\n ${payload}` - } - - toBuffer() { - const FLAPHeader = Buffer.alloc(6, 0, 'hex'); - FLAPHeader.writeInt8(0x2a, 0); - FLAPHeader.writeInt8(this.channel, 1); - FLAPHeader.writeInt16BE(this.sequenceNumber, 2); - FLAPHeader.writeInt16BE(this.payloadLength, 4); - - let payload = this.payload; - if (payload instanceof SNAC) { - payload = payload.toBuffer(); - } - - return Buffer.concat([FLAPHeader, payload]); - } -} diff --git a/src/structures/SNAC.ts b/src/structures/SNAC.ts deleted file mode 100644 index 580ffea..0000000 --- a/src/structures/SNAC.ts +++ /dev/null @@ -1,134 +0,0 @@ -import assert from "assert"; -import { FLAGS_EMPTY } from "../consts"; -import { TLV } from "./TLV"; - -export class RateClass { - constructor( - public ID: number, - public WindowSize: number, - public ClearLevel: number, - public AlertLevel: number, - public LimitLevel: number, - public DisconnectLevel: number, - public CurrentLevel: number, - public MaxLevel: number, - public LastTime: number, - public CurrentStat: number, - ){} - - toBuffer() : Buffer { - const buf = Buffer.alloc(35, 0x00); - buf.writeUInt16BE(this.ID, 0); - buf.writeUInt32BE(this.WindowSize, 2); - buf.writeUInt32BE(this.ClearLevel, 6); - buf.writeUInt32BE(this.AlertLevel, 10); - buf.writeUInt32BE(this.LimitLevel, 14); - buf.writeUInt32BE(this.DisconnectLevel,18); - buf.writeUInt32BE(this.CurrentLevel, 22); - buf.writeUInt32BE(this.MaxLevel, 26); - buf.writeUInt32BE(this.LastTime, 30); - buf.writeUInt8(this.CurrentStat, 34); - return buf; - } -} - -export class RateGroupPair { - constructor(public service : number, public subtype : number) {} - toBuffer() : Buffer { - const buf = Buffer.alloc(4, 0x00); - buf.writeInt16BE(this.service, 0); - buf.writeInt16BE(this.subtype, 2); - return buf; - } -} - -export class RatedServiceGroup { - constructor(public rateGroupID : number, public pairs : RateGroupPair[]){} - - toBuffer() : Buffer { - const ratedServiceGroupHeader = Buffer.alloc(4, 0x00); - ratedServiceGroupHeader.writeInt16BE(this.rateGroupID); - ratedServiceGroupHeader.writeInt16BE(this.pairs.length, 2); - const pairs = this.pairs.map((pair) => pair.toBuffer()); - return Buffer.concat([ratedServiceGroupHeader, ...pairs]); - } -} - -export class Rate { - constructor(public rateClass : RateClass, public ratedServiceGroup : RatedServiceGroup) {} - toBuffer() : Buffer { - return Buffer.concat([this.rateClass.toBuffer(), this.ratedServiceGroup.toBuffer()]); - } -} - -let snacID = 0x2000; - -export class SNAC { - constructor(public service : number, public subtype : number, public payload : (TLV[] | Buffer) = Buffer.alloc(0), public requestID : number = 0, public flags : Buffer = FLAGS_EMPTY) { - this.service = service; - this.subtype = subtype; - this.payload = payload; - - this.requestID = requestID || (snacID++); - this.flags = flags; - } - - static fromBuffer(buf : Buffer, payloadLength = 0) { - assert(buf.length >= 10, 'Expected 10 bytes for SNAC header'); - const service = buf.slice(0,2).readInt16BE(0); - const subtype = buf.slice(2,4).readInt16BE(0); - const flags = buf.slice(4, 6); - const requestID = buf.slice(6, 10).readInt32BE(0); - let payload : Buffer | TLV[]; // SNACs can have multiple payload - - // Some SNACs don't have TLV payloads - // Maybe this should be something that the service does itself when it - // wants to respond to a message; - if (service === 0x01 && subtype === 0x17 || - service === 0x01 && subtype === 0x14 || - service === 0x01 && subtype === 0x07 || - service === 0x01 && subtype === 0x08 || - service === 0x01 && subtype === 0x0e || - service === 0x04 && subtype === 0x02 || - service === 0x09 && subtype === 0x04 || - service === 0x0a && subtype === 0x02 || - service === 0x04 && subtype === 0x06) { - payload = buf.slice(10, 10 + payloadLength); - } else { - payload = TLV.fromBufferBlob(buf.slice(10)); - } - - return new SNAC(service, subtype, payload, requestID, flags); - } - - static forRateClass(service : number, subtype : number, rates : Rate[]) : SNAC { - const payloadHeader = Buffer.alloc(2, 0x00); - payloadHeader.writeUInt16BE(rates.length); - - const payloadBody = rates.map((rateClass) => rateClass.toBuffer()); - const payload = Buffer.concat([payloadHeader, ...payloadBody]); - - return new SNAC(service, subtype, payload); - } - - toString() { - return `SNAC(${this.service.toString(16)},${this.subtype.toString(16)}) #${this.requestID}\n ${this.payload}`; - } - - toBuffer() { - const SNACHeader = Buffer.alloc(10, 0, 'hex'); - SNACHeader.writeUInt16BE(this.service); - SNACHeader.writeUInt16BE(this.subtype, 2); - SNACHeader.set(this.flags, 4); - SNACHeader.writeUInt32BE(this.requestID, 6); - - let payload : Buffer[] = []; - if (this.payload instanceof Buffer) { - payload = [this.payload]; - } else if (this.payload.length && this.payload[0] instanceof TLV) { - payload = (this.payload as TLV[]).map((thing : TLV) => thing.toBuffer()); - } - - return Buffer.concat([SNACHeader, ...payload]); - } -} diff --git a/src/structures/TLV.ts b/src/structures/TLV.ts deleted file mode 100644 index 153413c..0000000 --- a/src/structures/TLV.ts +++ /dev/null @@ -1,92 +0,0 @@ -import {ErrorCode} from "./ErrorCode"; - -import { USER_STATUS_VARIOUS, USER_STATUS } from "../consts"; -import { char, word, dword } from './bytes'; - -export const enum TLVType { - User = 0x01, - ClientName = 0x03, - GetServices = 0x06, - PasswordHash = 0x25, -} - -export class TLV { - get length() : number { - return this.payload.length; - } - - static fromBuffer(buf : Buffer) { - const type = buf.slice(0, 2).readInt16BE(0); - const len = buf.slice(2, 4).readInt16BE(0) - const payload = buf.slice(4, 4 + len); - - return new TLV(type, payload); - } - - /** - * Extract all TLVs from a given Buffer - * @param buf Buffer that contains multiple TLVs - * @param payloadLength Total stated length of the payload - * @returns all TLVs found in the Buffer - */ - static fromBufferBlob(buf : Buffer) : TLV[] { - const tlvs : TLV[] = []; - - // Try to parse TLVs - let payloadIdx = 0; - let cb = 0, cbLimit = 20; //circuit breaker - while (payloadIdx < buf.length && cb < cbLimit) { - const tlv = TLV.fromBuffer(buf.slice(payloadIdx)); - tlvs.push(tlv); - payloadIdx += tlv.length + 4; // 4 bytes for TLV type + payload length - cb++; - } - if (cb === cbLimit) { - console.error('Application error, cb limit reached'); - process.exit(1); - } - - return tlvs; - } - - static forUsername(username : string) : TLV { - return new TLV(0x01, Buffer.from(username)); - } - - static forBOSAddress(address : string ) : TLV { - return new TLV(0x05, Buffer.from(address)); - } - - static forCookie(cookie : string) : TLV { - return new TLV(0x06, Buffer.from(cookie)); - } - - static forError(errorCode : ErrorCode) : TLV { - return new TLV(0x08, Buffer.from([0x00, errorCode])); - } - - static forStatus(various : USER_STATUS_VARIOUS, status: USER_STATUS) { - const varbuf = Buffer.alloc(2, 0x00); - varbuf.writeUInt16BE(various); - const statbuf = Buffer.alloc(2, 0x00); - statbuf.writeUInt16BE(status); - return new TLV(0x06, Buffer.concat([varbuf, statbuf])); - } - - constructor(public type : number, public payload : Buffer) { - this.type = type; - this.payload = payload; - } - - toString() { - return `TLV(0x${this.type.toString(16).padStart(2, '0')}, ${this.length}, ${this.payload.toString('ascii')})`; - } - - toBuffer() { - return Buffer.concat([ - word(this.type), - word(this.length), - this.payload, - ]); - } -} diff --git a/src/structures/bytes.ts b/src/structures/bytes.ts deleted file mode 100644 index e62655c..0000000 --- a/src/structures/bytes.ts +++ /dev/null @@ -1,43 +0,0 @@ -export function char(num : number) : Buffer { - const buf = Buffer.alloc(1, 0x00); - buf.writeUInt8(num); - return buf; -} - -export function word(num : number) : Buffer { - const buf = Buffer.alloc(2, 0x00); - buf.writeUInt16BE(num); - return buf; -} - -export function dword(num : number) : Buffer { - const buf = Buffer.alloc(4, 0x00); - buf.writeUInt32BE(num); - return buf; -} - -export function qword(num : number) : Buffer { - const buf = Buffer.alloc(8, 0x00); - buf.writeUInt32BE(num); - return buf; -} - -/** - * Converts a string IP address to it's number representation. - * From: https://stackoverflow.com/a/8105740 - * @param ip IP address string - * @returns IP address as a number - */ -export function dot2num(ip : string) : number { - const d = ip.split('.'); - return ((((((+d[0])*256)+(+d[1]))*256)+(+d[2]))*256)+(+d[3]); -} - -export function num2dot(num : number) : string { - let d = '' + num%256; - for (var i = 3; i > 0; i--) { - num = Math.floor(num/256); - d = num%256 + '.' + d; - } - return d; -} diff --git a/src/structures/index.ts b/src/structures/index.ts deleted file mode 100644 index 5724018..0000000 --- a/src/structures/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./ErrorCode"; -export * from "./TLV"; -export * from "./SNAC"; -export * from "./FLAP"; diff --git a/src/util.ts b/src/util.ts deleted file mode 100644 index 9416f9f..0000000 --- a/src/util.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { FLAP } from './structures' -import { dot2num, num2dot } from './structures/bytes' -export function chunkString(str : string, len : number) { - const size = Math.ceil(str.length/len) - const r = Array(size) - let offset = 0 - - for (let i = 0; i < size; i++) { - r[i] = str.substr(offset, len) - offset += len - } - - return r -} - -export function logDataStream(data : Buffer){ - const strs = chunkString(data.toString('hex'), 16); - return strs.map((str) => chunkString(str, 2).join(' ')).join('\n'); -} - -// Experiment to provide descriptive print-outs of the data structures -// @ts-ignore -import Table from 'table-layout'; - -type DataSize = -1 | 8 | 16 | 32; -interface Spec { - description: string, - size : DataSize, - isRepeat? : boolean, - isParam? : boolean, - isTLV? : boolean, - dump? : boolean, - repeatSpecs?: Spec[], -} - -function byte(description : string) : Spec { - return {size: 8, description} -} - -function word(description : string) : Spec { - return {size: 16, description}; -} - -function dword(description : string) : Spec { - return {size: 32, description}; -} - -function repeat(size: DataSize, description : string, specs : Spec[]) : Spec { - return {size, description, isRepeat: true, repeatSpecs: specs}; -} - -function param(size: DataSize, description: string) : Spec { - return {size, description, isParam: true}; -} - -function tlv(description : string) : Spec { - return {size : -1, description, isTLV: true}; -} - -function dump() : Spec { - return {size: -1, description: '', dump: true}; -} - -function parseBuffer(buf : Buffer, spec : Spec[], repeatTimes = 0) { - let offset = 0; - let rows = []; - let repeat = repeatTimes; - - for (let section of spec) { - let value : any = 0; - let bufStr : string = ''; - - if (section.dump) { - rows.push({raw: logDataStream(buf.slice(offset))}); - break; - } - - if (section.size === 8) { - bufStr = buf.slice(offset, offset + 1).toString('hex'); - value = buf.readInt8(offset); - offset += 1; - } else if (section.size === 16) { - bufStr = buf.slice(offset, offset + 2).toString('hex'); - value = buf.readUInt16BE(offset); - offset += 2; - } else if (section.size === 32) { - bufStr = buf.slice(offset, offset + 4).toString('hex'); - value = buf.readUInt32BE(offset); - offset += 4; - } - - if (section.description.includes("IP")) { - value = num2dot(value); - } - - - if (section.isParam) { - const paramBuf = buf.slice(offset, offset + value); - offset += value; - rows.push([chunkString(paramBuf.toString('hex'), 2), paramBuf.toString('ascii'), section.description]) - } else if (section.isTLV) { - const tlvType = buf.slice(offset, offset + 2).toString('hex'); - offset += 2; - const tlvLength = buf.slice(offset, offset + 2).readUInt16BE(0); - offset += 2; - const tlvData = buf.slice(offset, offset + tlvLength); - offset += tlvLength; - - let data = tlvData.toString('ascii') as string; - if (section.description.includes("IP")) { - data = num2dot(tlvData.readUInt32BE(0)); - } - - rows.push([ - chunkString(tlvData.toString('hex'), 2), - data, - tlvType + ':' + section.description, - ]); - } else if (section.isRepeat && section.repeatSpecs) { - if (section.size !== -1) { - repeat = value; - } - - let specs : Spec[] = []; - for (let i = 0; i < repeat; i++) { - specs.push(...section.repeatSpecs); - } - - const subrows : any[] = parseBuffer(buf.slice(offset), specs, repeat); - rows.push(...subrows); - } else { - rows.push([chunkString(bufStr, 2).join(' '), value, section.description]); - } - } - - return rows; -} - -function printBuffer(buf : Buffer, spec : Spec[]) { - const rows = parseBuffer(buf, spec); - - const lastRow = rows[rows.length - 1]; - - if (!!lastRow.raw) { - console.log((new Table(rows.slice(0, -1))).toString()); - console.log(lastRow.raw); - } else { - console.log((new Table(rows)).toString()); - } -} - -function bufferFromWebText(webtext : string) : Buffer { - return Buffer.from(webtext.replace(/\s/g, ''), 'hex'); -} - -const SNAC_01_0F = [ - byte("FLAP Header"), - byte("Channel"), - word("Sequence ID"), - word("Payload Length"), - word("SNAC Family"), - word("SNAC Subtype"), - word("SNAC Flags"), - dword("SNAC Request-ID"), - param(8, "UIN String Length"), - word("Warning Level"), - word("Number of TLV in list"), - - tlv("User Class"), - tlv("User Status"), - tlv("External IP Address"), - tlv("Client Idle Time"), - tlv("Signon Time"), - tlv("Unknown Value"), - tlv("Member Since"), - - word("DC Info"), - word("DC Info Length"), - dword("DC Internal IP Address"), - dword("DC TCP Port"), - dword("DC Type"), - word("DC Protocol Version"), - dword("DC Auth Cookie"), - dword("Web Front Port"), - dword("Client Features"), - dword("Last Info Update Time"), - dword("Last EXT info update time"), - dword("Last EXT status update time"), -]; - -const exSNAC_01_0F = ''+ -` -2a 02 00 05 00 71 00 01 -00 0f 00 00 00 00 00 00 -03 34 30 30 00 00 00 08 -00 01 00 01 80 00 06 00 -04 00 00 04 01 00 0a 00 -04 c0 a8 01 fe 00 0f 00 -04 00 00 00 00 00 03 00 -04 61 3f aa 53 00 1e 00 -04 00 00 00 00 00 05 00 -04 36 78 bf a0 00 0c 00 -26 c0 a8 01 fe 00 00 16 -44 04 00 00 00 04 00 00 -00 00 00 00 00 00 00 00 -00 03 00 00 00 00 00 00 -00 00 00 00 00 00 00 -`; - -const SNAC_01_07 = [ - byte("FLAP Header"), - byte("Channel"), - word("Sequence ID"), - word("Payload Length"), - word("SNAC Family"), - word("SNAC Subtype"), - word("SNAC Flags"), - dword("SNAC Request-ID"), - - repeat(16, "Number of Rate Classes", [ - word('Rate class ID'), - dword('Window size'), - dword('Clear level'), - dword('Alert level'), - dword('Limit level'), - dword('Disconnect level'), - dword('Current level'), - dword('Max level'), - dword('Last time'), - byte('Current State'), - ]), - - dump(), -]; - -const exSNAC_01_07 = ''+ -` -2a 02 00 05 03 3b 00 01 00 07 00 00 00 00 -00 00 00 05 00 01 00 00 00 50 00 00 09 c4 00 00 -07 d0 00 00 05 dc 00 00 03 20 00 00 16 dc 00 00 -17 70 00 00 00 00 00 00 02 00 00 00 50 00 00 0b -b8 00 00 07 d0 00 00 05 dc 00 00 03 e8 00 00 17 -70 00 00 17 70 00 00 00 7b 00 00 03 00 00 00 1e -00 00 0e 74 00 00 0f a0 00 00 05 dc 00 00 03 e8 -00 00 17 70 00 00 17 70 00 00 00 00 00 00 04 00 -00 00 14 00 00 15 7c 00 00 14 b4 00 00 10 68 00 -00 0b b8 00 00 17 70 00 00 1f 40 00 00 00 7b 00 -00 05 00 00 00 0a 00 00 15 7c 00 00 14 b4 00 00 -10 68 00 00 0b b8 00 00 17 70 00 00 1f 40 00 00 -00 7b 00 00 01 00 91 00 01 00 01 00 01 00 02 00 -01 00 03 00 01 00 04 00 01 00 05 00 01 00 06 00 -01 00 07 00 01 00 08 00 01 00 09 00 01 00 0a 00 -01 00 0b 00 01 00 0c 00 01 00 0d 00 01 00 0e 00 -01 00 0f 00 01 00 10 00 01 00 11 00 01 00 12 00 -01 00 13 00 01 00 14 00 01 00 15 00 01 00 16 00 -01 00 17 00 01 00 18 00 01 00 19 00 01 00 1a 00 -01 00 1b 00 01 00 1c 00 01 00 1d 00 01 00 1e 00 -01 00 1f 00 01 00 20 00 01 00 21 00 02 00 01 00 -02 00 02 00 02 00 03 00 02 00 04 00 02 00 06 00 -02 00 07 00 02 00 08 00 02 00 0a 00 02 00 0c 00 -02 00 0d 00 02 00 0e 00 02 00 0f 00 02 00 10 00 -02 00 11 00 02 00 12 00 02 00 13 00 02 00 14 00 -02 00 15 00 03 00 01 00 03 00 02 00 03 00 03 00 -03 00 06 00 03 00 07 00 03 00 08 00 03 00 09 00 -03 00 0a 00 03 00 0b 00 03 00 0c 00 04 00 01 00 -04 00 02 00 04 00 03 00 04 00 04 00 04 00 05 00 -04 00 07 00 04 00 08 00 04 00 09 00 04 00 0a 00 -04 00 0b 00 04 00 0c 00 04 00 0d 00 04 00 0e 00 -04 00 0f 00 04 00 10 00 04 00 11 00 04 00 12 00 -04 00 13 00 04 00 14 00 06 00 01 00 06 00 02 00 -06 00 03 00 08 00 01 00 08 00 02 00 09 00 01 00 -09 00 02 00 09 00 03 00 09 00 04 00 09 00 09 00 -09 00 0a 00 09 00 0b 00 0a 00 01 00 0a 00 02 00 -0a 00 03 00 0b 00 01 00 0b 00 02 00 0b 00 03 00 -0b 00 04 00 0c 00 01 00 0c 00 02 00 0c 00 03 00 -13 00 01 00 13 00 02 00 13 00 03 00 13 00 04 00 -13 00 05 00 13 00 06 00 13 00 07 00 13 00 08 00 -13 00 09 00 13 00 0a 00 13 00 0b 00 13 00 0c 00 -13 00 0d 00 13 00 0e 00 13 00 0f 00 13 00 10 00 -13 00 11 00 13 00 12 00 13 00 13 00 13 00 14 00 -13 00 15 00 13 00 16 00 13 00 17 00 13 00 18 00 -13 00 19 00 13 00 1a 00 13 00 1b 00 13 00 1c 00 -13 00 1d 00 13 00 1e 00 13 00 1f 00 13 00 20 00 -13 00 21 00 13 00 22 00 13 00 23 00 13 00 24 00 -13 00 25 00 13 00 26 00 13 00 27 00 13 00 28 00 -15 00 01 00 15 00 02 00 15 00 03 00 02 00 06 00 -03 00 04 00 03 00 05 00 09 00 05 00 09 00 06 00 -09 00 07 00 09 00 08 00 03 00 02 00 02 00 05 00 -04 00 06 00 04 00 02 00 02 00 09 00 02 00 0b 00 -05 00 00 -`; - -if (require.main === module) { - printBuffer(bufferFromWebText(exSNAC_01_07), SNAC_01_07); -} diff --git a/tests/data-structures.ts b/tests/data-structures.ts deleted file mode 100644 index 7dd1b5d..0000000 --- a/tests/data-structures.ts +++ /dev/null @@ -1,47 +0,0 @@ -import assert from 'assert'; - -import { FLAP, SNAC, TLV } from '../src/structures'; -import {FLAGS_EMPTY} from "../src/consts"; - -const tests = [ - () => { - // Construct and test a CLI_AUTH_REQUEST - const md5_auth_req = new FLAP(0x02, 0, new SNAC(0x17, 0x06, [new TLV(0x01, Buffer.from("toof"))])); - assert(md5_auth_req.channel === 2); - assert(md5_auth_req.payload instanceof SNAC); - assert(md5_auth_req.payload.service === 23); - assert(md5_auth_req.payload.subtype === 6); - assert(md5_auth_req.payload.payload.length === 1); - assert.equal(md5_auth_req.payload.payload.length, 1); - assert(md5_auth_req.payload.payload[0] instanceof TLV); - }, - () => { - // Test FLAP.length calculation and consuming multiple messages - const dataStr = ` - 2a 02 4b 11 00 0a 00 01 - 00 0e 00 00 00 00 00 00 - 2a 02 4b 12 00 0a 00 02 - 00 02 00 00 00 00 00 00 - `.trim().replace(/\s+/g, ''); - let data = Buffer.from(dataStr, 'hex'); - - const message = FLAP.fromBuffer(data); - assert(message.channel === 2); - assert(message.payloadLength === 10); - assert(message.length === 16); - assert(message.payload instanceof SNAC); - assert((message.payload as SNAC).service === 1); - assert((message.payload as SNAC).subtype === 0x0e); - - data = data.slice(message.length); - const secondMessage = FLAP.fromBuffer(data); - assert(secondMessage.length === 16); - assert(secondMessage.payload instanceof SNAC); - assert((secondMessage.payload as SNAC).service === 2); - assert((secondMessage.payload as SNAC).subtype === 0x02); - } -]; - -tests.forEach((testFn) => { - testFn(); -}); diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 417478a..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@tsconfig/node14/tsconfig.json", - "compilerOptions": { - "preserveConstEnums": true, - "esModuleInterop": true, - "outDir": "./dist", - }, - "include": ["src/**/*", "tests/**/*"], - "exclude": ["node_modules", "**/*.spec.ts"] -} diff --git a/yarn.lock b/yarn.lock index 3fe0547..c7b809c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,14 +2,6 @@ # yarn lockfile v1 -"@75lb/deep-merge@^1.1.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@75lb/deep-merge/-/deep-merge-1.1.1.tgz#3b06155b90d34f5f8cc2107d796f1853ba02fd6d" - integrity sha512-xvgv6pkMGBA6GwdyJbNAnDmfAIR/DfWhrj9jgWh3TY7gRm3KO46x/GPjRg6wJ0nOepwqrNxFfojebh0Df4h4Tw== - dependencies: - lodash.assignwith "^4.2.0" - typical "^7.1.1" - "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -22,16 +14,6 @@ dependencies: defer-to-connect "^1.0.1" -"@tsconfig/node14@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== - -"@types/node@^16.7.13": - version "16.7.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.13.tgz#86fae356b03b5a12f2506c6cf6cd9287b205973f" - integrity sha512-pLUPDn+YG3FYEt/pHI74HmnJOWzeR+tOIQzUx93pi9M7D8OE7PSLr97HboXwk5F+JS+TLtWuzCOW97AHjmOXXA== - abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -54,13 +36,6 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -76,21 +51,6 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -array-back@^3.0.1, array-back@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" - integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== - -array-back@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" - integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== - -array-back@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-6.2.0.tgz#83cc80fbef5a46269b1f6ecc82011cfc19cf1c1e" - integrity sha512-mixVv03GOOn/ubHE4STQ+uevX42ETdk0JoMVEjNkSOCT7WgERh7C8/+NyhWYNpE3BN69pxFyJIBcF7CxWz/+4A== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -148,15 +108,6 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -197,13 +148,6 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -211,36 +155,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -command-line-args@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.0.tgz#087b02748272169741f1fd7c785b295df079b9be" - integrity sha512-4zqtU1hYsSJzcJBOcNZIbW5Fbk9BkjCp1pZVhQKoRaWL5J7N4XphDLwo8aWwdQpTugxwu+jf9u2ZhkXiqp5Z6A== - dependencies: - array-back "^3.1.0" - find-replace "^3.0.0" - lodash.camelcase "^4.3.0" - typical "^4.0.0" - -command-line-usage@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.1.tgz#c908e28686108917758a49f45efb4f02f76bc03f" - integrity sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA== - dependencies: - array-back "^4.0.1" - chalk "^2.4.2" - table-layout "^1.0.1" - typical "^5.2.0" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -284,7 +203,7 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" -deep-extend@^0.6.0, deep-extend@~0.6.0: +deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== @@ -328,11 +247,6 @@ escape-goat@^2.0.0: resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -340,13 +254,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-replace@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" - integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== - dependencies: - array-back "^3.0.1" - fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" @@ -540,16 +447,6 @@ latest-version@^5.0.0: dependencies: package-json "^6.3.0" -lodash.assignwith@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz#127a97f02adc41751a954d24b0de17e100e038eb" - integrity sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs= - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -696,11 +593,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -reduce-flatten@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" - integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== - registry-auth-token@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" @@ -744,11 +636,6 @@ signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -stream-read-all@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/stream-read-all/-/stream-read-all-3.0.1.tgz#60762ae45e61d93ba0978cda7f3913790052ad96" - integrity sha512-EWZT9XOceBPlVJRrYcykW8jyRSZYbkb/0ZK36uLEmoWVO5gxBOnntNTseNzfREsqxqdfEGQrD8SXQ3QWbBmq8A== - string-width@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -786,7 +673,7 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -supports-color@^5.3.0, supports-color@^5.5.0: +supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -800,29 +687,6 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -table-layout@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" - integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== - dependencies: - array-back "^4.0.1" - deep-extend "~0.6.0" - typical "^5.2.0" - wordwrapjs "^4.0.0" - -table-layout@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-3.0.0.tgz#bd0f207ff73eb4dba79019e4f672e61874181b7f" - integrity sha512-chMCAvqzsw2k0WVZLQwyiNfHV3lwLdrmTKaofEt3PhedSVdEwOK8/nGetZFaQEg9HjPL9CrhkJWc4ZauFCihXw== - dependencies: - "@75lb/deep-merge" "^1.1.0" - array-back "^6.2.0" - command-line-args "^5.2.0" - command-line-usage "^6.1.1" - stream-read-all "^3.0.1" - typical "^7.1.1" - wordwrapjs "^5.1.0" - term-size@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" @@ -859,26 +723,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86" - integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ== - -typical@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" - integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== - -typical@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" - integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== - -typical@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/typical/-/typical-7.1.1.tgz#ba177ab7ab103b78534463ffa4c0c9754523ac1f" - integrity sha512-T+tKVNs6Wu7IWiAce5BgMd7OZfNYUndHwc5MknN+UHOudi7sGZzuHdCadllRuqJ3fPtgFtIH9+lt9qRv6lmpfA== - undefsafe@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" @@ -926,19 +770,6 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" -wordwrapjs@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" - integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== - dependencies: - reduce-flatten "^2.0.0" - typical "^5.2.0" - -wordwrapjs@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-5.1.0.tgz#4c4d20446dcc670b14fa115ef4f8fd9947af2b3a" - integrity sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg== - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"