Convert everything to TypeScript

This commit is contained in:
Artem Titoulenko 2021-09-08 20:11:05 -04:00
parent 4c2492e348
commit 9d628233e1
12 changed files with 114 additions and 80 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
node_modules node_modules
built built
dist

View file

@ -4,11 +4,12 @@
"main": "src/index.js", "main": "src/index.js",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "nodemon ./src/index.js", "dev": "nodemon --ignore ./dist/ --watch ./src -e ts --exec 'tsc && node ./dist/index.js'",
"start": "node ./src/index.js" "start": "tsc && node ./dist/index.js"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/node14": "^1.0.1", "@tsconfig/node14": "^1.0.1",
"@types/node": "^16.7.13",
"nodemon": "^2.0.12", "nodemon": "^2.0.12",
"typescript": "^4.4.2" "typescript": "^4.4.2"
} }

View file

@ -1,26 +1,29 @@
const { FLAP, SNAC, TLV } = require('./structures'); import net from "net";
const { logDataStream } = require('./util'); import { FLAP, SNAC, TLV } from './structures';
const { FLAGS_EMPTY } = require('./consts'); import { logDataStream } from './util';
import { FLAGS_EMPTY } from './consts';
const AuthorizationRegistrationService = require("./services/authorization-registration"); import AuthorizationRegistrationService from "./services/authorization-registration";
import BaseService from "./services/base";
class Communicator { export default class Communicator {
constructor(socket) {
private _sequenceNumber = 0;
private services : {[key: number]: BaseService} = {};
constructor(public socket : net.Socket) {
// Hold on to the socket // Hold on to the socket
this.socket = socket; this.socket = socket;
this.socket.on('data', (data) => { this.socket.on('data', (data : Buffer) => {
console.log('DATA-----------------------'); console.log('DATA-----------------------');
const flap = FLAP.fromBuffer(Buffer.from(data, 'hex')); const flap = FLAP.fromBuffer(data);
console.log('RECV', flap.toString()); console.log('RECV', flap.toString());
console.log('RAW\n' + logDataStream(Buffer.from(data, 'hex'))); console.log('RAW\n' + logDataStream(data));
this.handleMessage(flap); this.handleMessage(flap);
}); });
this._sequenceNumber = 0;
this.registerServices(); this.registerServices();
this.start(); this.start();
} }
@ -45,16 +48,21 @@ class Communicator {
return ++this._sequenceNumber; return ++this._sequenceNumber;
} }
send(message) { send(message : FLAP) {
console.log('SEND', message.toString()); console.log('SEND', message.toString());
console.log('RAW\n' + logDataStream(message.toBuffer())); console.log('RAW\n' + logDataStream(message.toBuffer()));
console.log('-----------------------DATA'); console.log('-----------------------DATA');
this.socket.write(message.toBuffer()); this.socket.write(message.toBuffer());
} }
handleMessage(message) { handleMessage(message : FLAP) {
switch (message.channel) { switch (message.channel) {
case 1: case 1:
// No SNACs on channel 1
if (!(message.payload instanceof Buffer)) {
return;
}
const protocol = message.payload.readUInt32BE(); const protocol = message.payload.readUInt32BE();
if (protocol !== 1) { if (protocol !== 1) {
@ -73,7 +81,7 @@ class Communicator {
switch (tlv.type) { switch (tlv.type) {
case 0x06: // Requesting available services case 0x06: // Requesting available services
// this is just a dword list of service families // this is just a dword list of service families
const servicesOffered = []; const servicesOffered : Buffer[] = [];
Object.values(this.services).forEach((service) => { Object.values(this.services).forEach((service) => {
servicesOffered.push(Buffer.from([0x00, service.family])); servicesOffered.push(Buffer.from([0x00, service.family]));
}); });
@ -87,8 +95,8 @@ class Communicator {
return; return;
case 2: case 2:
if (!message.payload) { if (!(message.payload instanceof SNAC)) {
console.error('No SNAC'); console.error('Expected SNAC payload');
return; return;
} }
@ -105,5 +113,3 @@ class Communicator {
} }
} }
} }
module.exports = Communicator;

View file

@ -1,7 +1,2 @@
const AIM_MD5_STRING = "AOL Instant Messenger (SM)"; export const AIM_MD5_STRING = "AOL Instant Messenger (SM)";
const FLAGS_EMPTY = Buffer.from([0x00, 0x00, 0x00, 0x00]); export const FLAGS_EMPTY = Buffer.from([0x00, 0x00, 0x00, 0x00]);
module.exports = {
AIM_MD5_STRING,
FLAGS_EMPTY,
};

View file

@ -1,7 +1,5 @@
const net = require('net'); import net from 'net';
const Communicator = require('./communicator'); import Communicator from './communicator';
const communicators = {};
const server = net.createServer((socket) => { const server = net.createServer((socket) => {
console.log('client connected...'); console.log('client connected...');
@ -9,21 +7,19 @@ const server = net.createServer((socket) => {
socket.on('error', (e) => { socket.on('error', (e) => {
console.error('socket encountered an error:', e); console.error('socket encountered an error:', e);
delete communicators[socket]; socket.end();
}); });
socket.on('timeout', () => { socket.on('timeout', () => {
console.log('socket timeout'); console.log('socket timeout');
socket.end(); socket.end();
delete communicators[socket];
}); });
socket.on('end', () => { socket.on('end', () => {
console.log('client disconnected...'); console.log('client disconnected...');
}); });
const c = new Communicator(socket); new Communicator(socket);
communicators[socket] = c;
}); });
server.on('error', (err) => { server.on('error', (err) => {

View file

@ -1,27 +1,42 @@
const crypto = require('crypto'); import crypto from 'crypto';
const BaseService = require('./base'); import BaseService from './base';
const { FLAP, SNAC, TLV } = require('../structures'); import Communicator from '../communicator';
import { FLAP, SNAC, TLV } from '../structures';
const { AIM_MD5_STRING, FLAGS_EMPTY } = require('../consts'); const { AIM_MD5_STRING, FLAGS_EMPTY } = require('../consts');
const users = { const users : {[key: string]: string} = {
toof: 'foo', 'toof': 'foo',
}; };
class AuthorizationRegistrationService extends BaseService { export default class AuthorizationRegistrationService extends BaseService {
constructor(communicator) { private cipher : string;
constructor(communicator : Communicator) {
super({ family: 0x17, version: 0x01 }, communicator); super({ family: 0x17, version: 0x01 }, communicator);
this.cipher = "HARDY"; this.cipher = "HARDY";
} }
handleMessage(message) { override handleMessage(message : FLAP) {
if (message.payload instanceof Buffer) {
console.log('Wont handle Buffer payload');
return;
}
switch (message.payload.service) { switch (message.payload.service) {
case 0x02: // Client login request (md5 login sequence) case 0x02: // Client login request (md5 login sequence)
const tlvs = message.payload.tlvs; const tlvs = message.payload.tlvs;
const clientNameTLV = tlvs.find((tlv) => tlv.type === 0x03); const clientNameTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x03);
if (!clientNameTLV || !(clientNameTLV instanceof TLV)) {
return;
}
console.log("Attempting connection from", clientNameTLV.payload.toString('ascii')); console.log("Attempting connection from", clientNameTLV.payload.toString('ascii'));
const userTLV = tlvs.find((tlv) => tlv.type === 0x01); const userTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x01);
if (!userTLV || !(userTLV instanceof TLV)) {
return;
}
const username = userTLV.payload.toString('ascii'); const username = userTLV.payload.toString('ascii');
if (!users[username]) { if (!users[username]) {
@ -35,7 +50,10 @@ class AuthorizationRegistrationService extends BaseService {
return; return;
} }
const passwordHashTLV = tlvs.find((tlv) => tlv.type === 0x25); const passwordHashTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x25);
if (!passwordHashTLV || !(passwordHashTLV instanceof TLV)) {
return;
}
const pwHash = crypto.createHash('md5'); const pwHash = crypto.createHash('md5');
pwHash.update(this.cipher); pwHash.update(this.cipher);
@ -43,7 +61,7 @@ class AuthorizationRegistrationService extends BaseService {
pwHash.update(AIM_MD5_STRING); pwHash.update(AIM_MD5_STRING);
const digest = pwHash.digest('hex'); const digest = pwHash.digest('hex');
if (digest !== passwordHashTLV.payload.toString('hex')) { if (digest !== (passwordHashTLV as TLV).payload.toString('hex')) {
console.log('Invalid password for', username); console.log('Invalid password for', username);
const authResp = new FLAP(2, this._getNewSequenceNumber(), const authResp = new FLAP(2, this._getNewSequenceNumber(),
new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [ new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [

View file

@ -1,11 +1,22 @@
class BaseService { import Communicator from "../communicator";
constructor({family, version}, communicator) { import { FLAP } from "../structures";
interface ServiceFamilyVersion {
family : number,
version : number,
}
export default class BaseService {
public family : number;
public version : number;
constructor({family, version} : ServiceFamilyVersion, public communicator : Communicator) {
this.family = family; this.family = family;
this.version = version; this.version = version;
this.communicator = communicator; this.communicator = communicator;
} }
send(message) { send(message : FLAP) {
this.communicator.send(message); this.communicator.send(message);
} }
@ -13,9 +24,7 @@ class BaseService {
return this.communicator._getNewSequenceNumber(); return this.communicator._getNewSequenceNumber();
} }
handleMessage(message) { handleMessage(message : FLAP) : void {
return null; return;
} }
} }
module.exports = BaseService;

View file

@ -1,9 +1,9 @@
const assert = require('assert'); import assert from 'assert';
const { logDataStream } = require('./util'); import { logDataStream } from './util';
class TLV { export class TLV {
static fromBuffer(buf) { static fromBuffer(buf : Buffer) {
const type = buf.slice(0, 2).readInt16BE(0); const type = buf.slice(0, 2).readInt16BE(0);
const len = buf.slice(2, 4).readInt16BE(0) const len = buf.slice(2, 4).readInt16BE(0)
const payload = buf.slice(4, 4 + len); const payload = buf.slice(4, 4 + len);
@ -11,7 +11,9 @@ class TLV {
return new TLV(type, payload); return new TLV(type, payload);
} }
constructor(type, payload) { public len : number;
constructor(public type : number, public payload : Buffer) {
this.type = type; this.type = type;
this.len = payload.length; this.len = payload.length;
this.payload = payload; this.payload = payload;
@ -29,14 +31,14 @@ class TLV {
} }
} }
class SNAC { export class SNAC {
static fromBuffer(buf, payloadLength = 0) { static fromBuffer(buf : Buffer, payloadLength = 0) {
assert(buf.length >= 10, 'Expected 10 bytes for SNAC header'); assert(buf.length >= 10, 'Expected 10 bytes for SNAC header');
const family = buf.slice(0,2).readInt16BE(0); const family = buf.slice(0,2).readInt16BE(0);
const service = buf.slice(2,4).readInt16BE(0); const service = buf.slice(2,4).readInt16BE(0);
const flags = buf.slice(4, 6); const flags = buf.slice(4, 6);
const requestID = buf.slice(6, 10).readInt32BE(0); const requestID = buf.slice(6, 10).readInt32BE(0);
const tlvs = []; // SNACs can have multiple TLVs const tlvs : TLV[] = []; // SNACs can have multiple TLVs
let tlvsIdx = 10; let tlvsIdx = 10;
let cb = 0, cbLimit = 20; //circuit breaker let cb = 0, cbLimit = 20; //circuit breaker
@ -54,7 +56,7 @@ class SNAC {
return new SNAC(family, service, flags, requestID, tlvs); return new SNAC(family, service, flags, requestID, tlvs);
} }
constructor(family, service, flags, requestID, tlvs = []) { constructor(public family : number, public service : number, public flags : Buffer, public requestID : number , public tlvs : Array<TLV | Buffer> = []) {
this.family = family; this.family = family;
this.service = service; this.service = service;
this.flags = flags; this.flags = flags;
@ -70,7 +72,7 @@ class SNAC {
const SNACHeader = Buffer.alloc(10, 0, 'hex'); const SNACHeader = Buffer.alloc(10, 0, 'hex');
SNACHeader.writeUInt16BE(this.family); SNACHeader.writeUInt16BE(this.family);
SNACHeader.writeUInt16BE(this.service, 2); SNACHeader.writeUInt16BE(this.service, 2);
SNACHeader.writeUInt16BE(this.flags, 4); SNACHeader.set(this.flags, 4);
SNACHeader.writeUInt32BE(this.requestID, 6); SNACHeader.writeUInt32BE(this.requestID, 6);
const payload = this.tlvs.map((thing) => { const payload = this.tlvs.map((thing) => {
@ -84,14 +86,14 @@ class SNAC {
} }
} }
class FLAP { export class FLAP {
static fromBuffer(buf) { static fromBuffer(buf : Buffer) {
assert.equal(buf[0], 0x2a, 'Expected 0x2a at start of FLAP header'); assert.equal(buf[0], 0x2a, 'Expected 0x2a at start of FLAP header');
assert(buf.length >= 6, 'Expected at least 6 bytes for FLAP header'); assert(buf.length >= 6, 'Expected at least 6 bytes for FLAP header');
const channel = buf.readInt8(1); const channel = buf.readInt8(1);
const sequenceNumber = buf.slice(2,4).readInt16BE(0); const sequenceNumber = buf.slice(2,4).readInt16BE(0);
const payloadLength = buf.slice(4, 6).readInt16BE(0); const payloadLength = buf.slice(4, 6).readInt16BE(0);
let payload = buf.slice(6, 6 + payloadLength); let payload : Buffer | SNAC = buf.slice(6, 6 + payloadLength);
if (channel === 2) { if (channel === 2) {
payload = SNAC.fromBuffer(payload, payloadLength); payload = SNAC.fromBuffer(payload, payloadLength);
@ -100,21 +102,26 @@ class FLAP {
return new FLAP(channel, sequenceNumber, payload) return new FLAP(channel, sequenceNumber, payload)
} }
constructor(channel, sequenceNumber, payload) { payloadLength: number;
constructor(public channel: number, public sequenceNumber: number, public payload: Buffer | SNAC) {
this.channel = channel; this.channel = channel;
this.sequenceNumber = sequenceNumber; this.sequenceNumber = sequenceNumber;
this.payload = payload; this.payload = payload;
this.payloadLength = this.payload.length;
if (payload instanceof SNAC) { if (payload instanceof SNAC) {
this.payloadLength = payload.toBuffer().length; this.payloadLength = payload.toBuffer().length;
} else {
this.payloadLength = payload.length;
} }
} }
toString() { toString() {
const hasSnac = this.payload instanceof SNAC; let payload = this.payload.toString();
const payload = hasSnac ? this.payload.toString() : logDataStream(this.payload).split('\n').join('\n '); 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}` return `ch:${this.channel}, dn: ${this.sequenceNumber}, len: ${this.payloadLength}, payload:\n ${payload}`
} }

View file

@ -1,4 +1,4 @@
function chunkString(str, len) { function chunkString(str : string, len : number) {
const size = Math.ceil(str.length/len) const size = Math.ceil(str.length/len)
const r = Array(size) const r = Array(size)
let offset = 0 let offset = 0
@ -11,11 +11,7 @@ function chunkString(str, len) {
return r return r
} }
function logDataStream(data){ export function logDataStream(data : Buffer){
const strs = chunkString(data.toString('hex'), 16); const strs = chunkString(data.toString('hex'), 16);
return strs.map((str) => chunkString(str, 2).join(' ')).join('\n'); return strs.map((str) => chunkString(str, 2).join(' ')).join('\n');
} }
module.exports = {
logDataStream,
};

View file

@ -1,4 +1,4 @@
const assert = require('assert'); import assert from 'assert';
const { FLAP, SNAC, TLV } = require('../src/structures'); const { FLAP, SNAC, TLV } = require('../src/structures');

View file

@ -2,7 +2,7 @@
"extends": "@tsconfig/node14/tsconfig.json", "extends": "@tsconfig/node14/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"preserveConstEnums": true, "preserveConstEnums": true,
"outDir": "./built", "outDir": "./dist",
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"] "exclude": ["node_modules", "**/*.spec.ts"]

View file

@ -19,6 +19,11 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== 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: abbrev@1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"