From aad0acfd15ab2ec267ca64e07ebbbed29258a699 Mon Sep 17 00:00:00 2001 From: Artem Titoulenko Date: Mon, 13 Sep 2021 15:59:10 -0400 Subject: [PATCH] continuing setting up services --- src/communicator.ts | 18 ++- src/consts.ts | 62 ++++++++ src/services/0x01-GenericServiceControls.ts | 68 ++++++++- src/services/0x02-LocationSerices.ts | 29 ++++ src/services/0x03-BuddyListManagement.ts | 22 +++ src/services/0x04-ICBM.ts | 80 ++++++++++ src/services/0x09-PrivacyManagement.ts | 30 ++++ .../0x17-AuthorizationRegistration.ts | 19 ++- src/structures/FLAP.ts | 2 +- src/structures/SNAC.ts | 7 +- src/structures/TLV.ts | 20 ++- src/structures/bytes.ts | 37 +++++ src/util.ts | 140 ++++++++++++------ 13 files changed, 470 insertions(+), 64 deletions(-) create mode 100644 src/structures/bytes.ts diff --git a/src/communicator.ts b/src/communicator.ts index 90b3b47..4024299 100644 --- a/src/communicator.ts +++ b/src/communicator.ts @@ -22,20 +22,24 @@ import AuthorizationRegistrationService from "./services/0x17-AuthorizationRegis import BaseService from "./services/base"; +export interface User { + uin: string, + password: string, + memberSince: Date, +} + export default class Communicator { private _sequenceNumber = 0; private messageBuffer = Buffer.alloc(0); public services : {[key: number]: BaseService} = {}; + public user? : User; constructor(public socket : net.Socket) { // Hold on to the socket this.socket = socket; this.socket.on('data', (data : Buffer) => { - console.log('DATA-----------------------'); - console.log('RAW\n' + logDataStream(data)); - // 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]); @@ -43,9 +47,12 @@ export default class Communicator { 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) { // Couldn't make a FLAP break; @@ -65,7 +72,6 @@ export default class Communicator { registerServices() { const services = [ - new AuthorizationRegistrationService(this), new GenericServiceControls(this), new LocationServices(this), new BuddyListManagement(this), @@ -80,7 +86,8 @@ export default class Communicator { new Chat(this), new DirectorySearch(this), new ServerStoredBuddyIcons(this), - new SSI(this), + // new SSI(this), + new AuthorizationRegistrationService(this), ]; // Make a map of the service number to the service handler @@ -97,7 +104,6 @@ export default class Communicator { send(message : FLAP) { console.log('SEND', message.toString()); console.log('RAW\n' + logDataStream(message.toBuffer())); - console.log('-----------------------DATA'); this.socket.write(message.toBuffer()); } diff --git a/src/consts.ts b/src/consts.ts index ec8230c..3eb8de2 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -1,2 +1,64 @@ 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/services/0x01-GenericServiceControls.ts b/src/services/0x01-GenericServiceControls.ts index dac7427..f5f4481 100644 --- a/src/services/0x01-GenericServiceControls.ts +++ b/src/services/0x01-GenericServiceControls.ts @@ -1,9 +1,13 @@ import BaseService from './base'; import Communicator from '../communicator'; -import { FLAP, Rate, RateClass, RatedServiceGroup, RateGroupPair, SNAC } from '../structures'; -import { FLAGS_EMPTY } from '../consts'; +import { FLAP, Rate, RateClass, RatedServiceGroup, RateGroupPair, SNAC, TLV } from '../structures'; +import { FLAGS_EMPTY, 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}, communicator) } @@ -13,6 +17,19 @@ export default class GenericServiceControls extends BaseService { throw new Error('Require SNAC'); } + 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 === 0x06) { // Client ask server for rate limits info const resp = new FLAP(0x02, this._getNewSequenceNumber(), SNAC.forRateClass(0x01, 0x07, FLAGS_EMPTY, 0, [ @@ -22,11 +39,56 @@ export default class GenericServiceControls extends BaseService { ) ])) this.send(resp); + + const motd = new FLAP(0x02, this._getNewSequenceNumber(), + new SNAC(0x01, 0x13, FLAGS_EMPTY, 0, 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 - console.log('should send back online presence info'); + const uin = '400'; // this.communicator.user.uin; + 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._getNewSequenceNumber(), + new SNAC(0x01, 0x0f, FLAGS_EMPTY, 0, buf)); + + this.send(resp); return; } diff --git a/src/services/0x02-LocationSerices.ts b/src/services/0x02-LocationSerices.ts index c69a53c..3c34e46 100644 --- a/src/services/0x02-LocationSerices.ts +++ b/src/services/0x02-LocationSerices.ts @@ -1,8 +1,37 @@ import BaseService from './base'; import Communicator from '../communicator'; +import { FLAP, SNAC, TLV } from '../structures'; +import { char, word, dword, dot2num } from '../structures/bytes'; +import { FLAGS_EMPTY, USER_STATUS, USER_STATUS_VARIOUS } from '../consts'; + export default class LocationServices extends BaseService { constructor(communicator : Communicator) { super({service: 0x02, version: 0x01}, 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._getNewSequenceNumber(), + new SNAC(0x02,0x03, FLAGS_EMPTY, 0, [ + 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 index 5a437a2..1f61bc9 100644 --- a/src/services/0x03-BuddyListManagement.ts +++ b/src/services/0x03-BuddyListManagement.ts @@ -1,8 +1,30 @@ 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}, 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._getNewSequenceNumber(), + new SNAC(0x03, 0x03, FLAGS_EMPTY, 0, [ + 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 index d11839e..372ac2e 100644 --- a/src/services/0x04-ICBM.ts +++ b/src/services/0x04-ICBM.ts @@ -1,8 +1,88 @@ import BaseService from './base'; import Communicator from '../communicator'; +import { FLAGS_EMPTY } from '../consts'; +import { FLAP, SNAC, TLV } from '../structures'; +import { dword } 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 { + private channel : ChannelSettings = { + channel: 2, + messageFlags: 3, + maxMessageSnacSize: 512, + maxSenderWarningLevel: 999, + maxReceiverWarningLevel: 999, + minimumMessageInterval: 0, + unknown: 1000, + }; + constructor(communicator : Communicator) { super({service: 0x04, version: 0x01}, 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; + this.channel = { + channel: payload.readUInt16BE(0), + messageFlags: payload.readUInt32BE(2), + maxMessageSnacSize: payload.readUInt16BE(6), + maxSenderWarningLevel: payload.readUInt16BE(8), + maxReceiverWarningLevel: payload.readUInt16BE(10), + minimumMessageInterval: payload.readUInt16BE(12), + unknown: 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); + + // For some reason this response crashes the client? + // It's identical to the channel set request the client + // sends earlier. Also the 3.x client sends a channel set request + // so early + const resp = new FLAP(0x02, this._getNewSequenceNumber(), + new SNAC(0x04, 0x05, FLAGS_EMPTY, 0, payload)); + this.send(resp); + return; + } + } } diff --git a/src/services/0x09-PrivacyManagement.ts b/src/services/0x09-PrivacyManagement.ts index 92e63b4..34c7e88 100644 --- a/src/services/0x09-PrivacyManagement.ts +++ b/src/services/0x09-PrivacyManagement.ts @@ -1,8 +1,38 @@ 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}, 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._getNewSequenceNumber(), + new SNAC(0x09, 0x03, FLAGS_EMPTY, 0, [ + 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/0x17-AuthorizationRegistration.ts b/src/services/0x17-AuthorizationRegistration.ts index c49119d..9ef1b5d 100644 --- a/src/services/0x17-AuthorizationRegistration.ts +++ b/src/services/0x17-AuthorizationRegistration.ts @@ -1,12 +1,16 @@ import crypto from 'crypto'; import BaseService from './base'; -import Communicator from '../communicator'; +import Communicator, { User } from '../communicator'; import { FLAP, SNAC, TLV, ErrorCode, TLVType } from '../structures'; const { AIM_MD5_STRING, FLAGS_EMPTY } = require('../consts'); -const users : {[key: string]: string} = { - 'toof': 'foo', +const users : {[key: string]: User} = { + 'toof': { + uin: '156089', + password: 'foo', + memberSince: new Date('December 17, 1998 03:24:00'), + } }; export default class AuthorizationRegistrationService extends BaseService { @@ -57,7 +61,7 @@ export default class AuthorizationRegistrationService extends BaseService { const pwHash = crypto.createHash('md5'); pwHash.update(this.cipher); - pwHash.update(users[username]); + pwHash.update(users[username].password); pwHash.update(AIM_MD5_STRING); const digest = pwHash.digest('hex'); @@ -72,13 +76,18 @@ export default class AuthorizationRegistrationService extends BaseService { return; } + const host = this.communicator.socket.localAddress.split(':').pop(); + const port = this.communicator.socket.localPort; + const authResp = new FLAP(2, this._getNewSequenceNumber(), new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [ TLV.forUsername(username), // username - TLV.forBOSAddress('10.0.1.29:5190'), // BOS address + TLV.forBOSAddress(`${host}:${port}`), // BOS address TLV.forCookie(JSON.stringify({cookie: 'uwu', user: 'toof'})) // Authorization cookie ])); + this.communicator.user = Object.assign({}, users[username], {username}); + this.send(authResp); return; case 0x06: // Request md5 authkey diff --git a/src/structures/FLAP.ts b/src/structures/FLAP.ts index aa8816c..06f9bb2 100644 --- a/src/structures/FLAP.ts +++ b/src/structures/FLAP.ts @@ -17,7 +17,7 @@ export class FLAP { let payload : Buffer | SNAC = buf.slice(6, 6 + payloadLength); - if (channel === 2) { + if (channel === 2 && payloadLength > 0) { payload = SNAC.fromBuffer(payload, payloadLength); } diff --git a/src/structures/SNAC.ts b/src/structures/SNAC.ts index e83d43f..e1b986b 100644 --- a/src/structures/SNAC.ts +++ b/src/structures/SNAC.ts @@ -78,10 +78,15 @@ export class SNAC { 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 === 0x01 && subtype === 0x0e || + service === 0x04 && subtype === 0x02 || + service === 0x09 && subtype === 0x04) { payload = buf.slice(10, 10 + payloadLength); } else { payload = []; diff --git a/src/structures/TLV.ts b/src/structures/TLV.ts index 8c544c2..6b08d6e 100644 --- a/src/structures/TLV.ts +++ b/src/structures/TLV.ts @@ -1,5 +1,8 @@ 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, @@ -36,6 +39,14 @@ export class 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 : TLVType, public payload : Buffer) { this.type = type; this.payload = payload; @@ -46,9 +57,10 @@ export class TLV { } toBuffer() { - const TLVHeader = Buffer.alloc(4, 0, 'hex'); - TLVHeader.writeUInt16BE(this.type); - TLVHeader.writeUInt16BE(this.length, 2); - return Buffer.concat([TLVHeader, this.payload]); + return Buffer.concat([ + word(this.type), + word(this.length), + this.payload, + ]); } } diff --git a/src/structures/bytes.ts b/src/structures/bytes.ts new file mode 100644 index 0000000..f69fa79 --- /dev/null +++ b/src/structures/bytes.ts @@ -0,0 +1,37 @@ +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; +} + +/** + * 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/util.ts b/src/util.ts index 916f15e..c7e31d8 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,5 @@ 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) @@ -26,6 +27,8 @@ interface Spec { description: string, size : DataSize, isRepeat? : boolean, + isParam? : boolean, + isTLV? : boolean, repeatSpecs?: Spec[], } @@ -45,34 +48,13 @@ function repeat(size: DataSize, description : string, specs : Spec[]) : Spec { return {size, description, isRepeat: true, repeatSpecs: specs}; } -const FLAPSpec = [ - 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") - ]), - repeat(-1, "", [ - word("Rate Group ID"), - repeat(16, "Number of pairs in group", [ - dword("Family/Subtype pair"), - ]), - ]), -]; +function param(size: DataSize, description: string) : Spec { + return {size, description, isParam: true}; +} + +function tlv(description : string) : Spec { + return {size : -1, description, isTLV: true}; +} function parseBuffer(buf : Buffer, spec : Spec[], repeatTimes = 0) { let offset = 0; @@ -80,25 +62,50 @@ function parseBuffer(buf : Buffer, spec : Spec[], repeatTimes = 0) { let repeat = repeatTimes; for (let section of spec) { - let value : number = 0; + let value : any = 0; + let bufStr : string = ''; if (section.size === 8) { - const bufStr = buf.slice(offset, offset + 1).toString('hex'); + bufStr = buf.slice(offset, offset + 1).toString('hex'); value = buf.readInt8(offset); - rows.push([chunkString(bufStr, 2).join(' '), value, section.description]); offset += 1; } else if (section.size === 16) { - const bufStr = buf.slice(offset, offset + 2).toString('hex'); + bufStr = buf.slice(offset, offset + 2).toString('hex'); value = buf.readUInt16BE(offset); - rows.push([chunkString(bufStr, 2).join(' '), value, section.description]); offset += 2; } else if (section.size === 32) { - const bufStr = buf.slice(offset, offset + 4).toString('hex'); + bufStr = buf.slice(offset, offset + 4).toString('hex'); value = buf.readUInt32BE(offset); - rows.push([chunkString(bufStr, 2).join(' '), value, section.description]); offset += 4; } - if (section.isRepeat && section.repeatSpecs) { + 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; } @@ -110,6 +117,8 @@ function parseBuffer(buf : Buffer, spec : Spec[], repeatTimes = 0) { const subrows : any[] = parseBuffer(buf.slice(offset), specs, repeat); rows.push(...subrows); + } else { + rows.push([chunkString(bufStr, 2).join(' '), value, section.description]); } } @@ -125,16 +134,59 @@ function bufferFromWebText(webtext : string) : Buffer { return Buffer.from(webtext.replace(/\s/g, ''), 'hex'); } +const FLAPSpec = [ + 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 exampleWebText = ''+ ` -2a 02 00 03 00 37 00 01 -00 07 00 00 00 00 00 00 -00 01 00 01 00 00 00 50 -00 00 09 c4 00 00 07 d0 -00 00 05 dc 00 00 03 20 -00 00 0d 48 00 00 17 70 -00 00 00 00 00 00 01 00 -01 00 00 00 00 ` +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 +` if (require.main === module) { printBuffer(bufferFromWebText(exampleWebText), FLAPSpec);