mirror of
https://github.com/amigan/aim-oscar-server.git
synced 2024-11-21 12:09:48 -05:00
continuing setting up services
This commit is contained in:
parent
1fa6d06f7a
commit
aad0acfd15
13 changed files with 470 additions and 64 deletions
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
37
src/structures/bytes.ts
Normal file
37
src/structures/bytes.ts
Normal file
|
@ -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;
|
||||
}
|
140
src/util.ts
140
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);
|
||||
|
|
Loading…
Reference in a new issue