mirror of
https://github.com/amigan/aim-oscar-server.git
synced 2024-11-22 04:29:47 -05:00
ICBM - client can send message
This commit is contained in:
parent
099ea1a69b
commit
11d76158ce
22 changed files with 184 additions and 63 deletions
|
@ -37,8 +37,7 @@ export default class Communicator {
|
||||||
this.handleMessage(flap);
|
this.handleMessage(flap);
|
||||||
console.log('-----------------------DATA');
|
console.log('-----------------------DATA');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Couldn't make a FLAP
|
console.error("Error handling message:", e);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,20 +38,20 @@ const server = net.createServer((socket) => {
|
||||||
const comm = new Communicator(socket);
|
const comm = new Communicator(socket);
|
||||||
const services = [
|
const services = [
|
||||||
new GenericServiceControls(comm),
|
new GenericServiceControls(comm),
|
||||||
new LocationServices(comm),
|
// new LocationServices(comm),
|
||||||
new BuddyListManagement(comm),
|
// new BuddyListManagement(comm),
|
||||||
new ICBM(comm),
|
new ICBM(comm),
|
||||||
new Invitation(comm),
|
// new Invitation(comm),
|
||||||
new Administration(comm),
|
// new Administration(comm),
|
||||||
new Popups(comm),
|
// new Popups(comm),
|
||||||
new PrivacyManagement(comm),
|
// new PrivacyManagement(comm),
|
||||||
new UserLookup(comm),
|
new UserLookup(comm),
|
||||||
new UsageStats(comm),
|
// new UsageStats(comm),
|
||||||
// new ChatNavigation(comm),
|
// new ChatNavigation(comm),
|
||||||
// new Chat(comm),
|
// new Chat(comm),
|
||||||
// new DirectorySearch(comm),
|
// new DirectorySearch(comm),
|
||||||
new ServerStoredBuddyIcons(comm),
|
// new ServerStoredBuddyIcons(comm),
|
||||||
new SSI(comm),
|
// new SSI(comm),
|
||||||
];
|
];
|
||||||
comm.registerServices(services);
|
comm.registerServices(services);
|
||||||
comm.startListening();
|
comm.startListening();
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default class GenericServiceControls extends BaseService {
|
||||||
private allowViewMemberSince = false;
|
private allowViewMemberSince = false;
|
||||||
|
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x01, version: 0x03}, communicator)
|
super({service: 0x01, version: 0x03}, [0x06, 0x0e, 0x14, 0x17], communicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
override handleMessage(message : FLAP) {
|
override handleMessage(message : FLAP) {
|
||||||
|
@ -17,27 +17,15 @@ export default class GenericServiceControls extends BaseService {
|
||||||
throw new Error('Require SNAC');
|
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
|
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
|
// 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.
|
// make it set rate limits for everything under 0x21.
|
||||||
const pairs : RateGroupPair[] = [];
|
const pairs : RateGroupPair[] = [];
|
||||||
Object.values(this.communicator.services).forEach((service) => {
|
Object.values(this.communicator.services).forEach((service) => {
|
||||||
for (let i = 0; i < 0x21; i++) {
|
// for (let subtype of service.supportedSubtypes) {
|
||||||
pairs.push(new RateGroupPair(service.service, i));
|
for (let subtype = 0; subtype < 0x21; subtype++) {
|
||||||
|
pairs.push(new RateGroupPair(service.service, subtype));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -47,14 +35,14 @@ export default class GenericServiceControls extends BaseService {
|
||||||
new RateClass(1, 80, 2500, 2000, 1500, 800, 3400 /*fake*/, 6000, 0, 0),
|
new RateClass(1, 80, 2500, 2000, 1500, 800, 3400 /*fake*/, 6000, 0, 0),
|
||||||
new RatedServiceGroup(1, pairs),
|
new RatedServiceGroup(1, pairs),
|
||||||
)
|
)
|
||||||
]))
|
]));
|
||||||
this.send(resp);
|
this.send(resp);
|
||||||
|
|
||||||
const motd = new FLAP(0x02, this.nextReqID,
|
const motd = new FLAP(0x02, this.nextReqID,
|
||||||
new SNAC(0x01, 0x13, Buffer.concat([
|
new SNAC(0x01, 0x13, Buffer.concat([
|
||||||
word(0x0004),
|
word(0x0004),
|
||||||
(new TLV(0x0B, Buffer.from("Hello world!"))).toBuffer(),
|
(new TLV(0x0B, Buffer.from("Hello world!"))).toBuffer(),
|
||||||
])))
|
])));
|
||||||
this.send(motd);
|
this.send(motd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -102,6 +90,19 @@ export default class GenericServiceControls extends BaseService {
|
||||||
return;
|
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) {
|
if (message.payload.subtype === 0x17) {
|
||||||
const serviceVersions : Buffer[] = [];
|
const serviceVersions : Buffer[] = [];
|
||||||
Object.values(this.communicator.services).forEach((subtype) => {
|
Object.values(this.communicator.services).forEach((subtype) => {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { USER_STATUS, USER_STATUS_VARIOUS } from '../consts';
|
||||||
|
|
||||||
export default class LocationServices extends BaseService {
|
export default class LocationServices extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x02, version: 0x01}, communicator)
|
super({service: 0x02, version: 0x01}, [0x02, 0x04], communicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
override handleMessage(message : FLAP) {
|
override handleMessage(message : FLAP) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { word } from '../structures/bytes';
|
||||||
|
|
||||||
export default class BuddyListManagement extends BaseService {
|
export default class BuddyListManagement extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x03, version: 0x01}, communicator)
|
super({service: 0x03, version: 0x01}, [0x02], communicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
override handleMessage(message : FLAP) {
|
override handleMessage(message : FLAP) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Communicator from '../communicator';
|
||||||
|
|
||||||
import { FLAGS_EMPTY } from '../consts';
|
import { FLAGS_EMPTY } from '../consts';
|
||||||
import { FLAP, SNAC, TLV } from '../structures';
|
import { FLAP, SNAC, TLV } from '../structures';
|
||||||
import { dword } from '../structures/bytes';
|
import { char, dword, word } from '../structures/bytes';
|
||||||
|
|
||||||
interface ChannelSettings {
|
interface ChannelSettings {
|
||||||
channel: number,
|
channel: number,
|
||||||
|
@ -16,6 +16,11 @@ interface ChannelSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ICBM extends BaseService {
|
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 = {
|
private channel : ChannelSettings = {
|
||||||
channel: 0,
|
channel: 0,
|
||||||
messageFlags: 3,
|
messageFlags: 3,
|
||||||
|
@ -29,7 +34,7 @@ export default class ICBM extends BaseService {
|
||||||
private channels : ChannelSettings[] = [];
|
private channels : ChannelSettings[] = [];
|
||||||
|
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x04, version: 0x01}, communicator)
|
super({service: 0x04, version: 0x01}, [0x02, 0x04], communicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
override handleMessage(message : FLAP) {
|
override handleMessage(message : FLAP) {
|
||||||
|
@ -85,5 +90,63 @@ export default class ICBM extends BaseService {
|
||||||
this.send(resp);
|
this.send(resp);
|
||||||
return;
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@ import Communicator from '../communicator';
|
||||||
|
|
||||||
export default class Invitation extends BaseService {
|
export default class Invitation extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x06, version: 0x01}, communicator)
|
super({service: 0x06, version: 0x01}, [], communicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@ import Communicator from '../communicator';
|
||||||
|
|
||||||
export default class Administration extends BaseService {
|
export default class Administration extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x07, version: 0x01}, communicator)
|
super({service: 0x07, version: 0x01}, [], communicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@ import Communicator from '../communicator';
|
||||||
|
|
||||||
export default class Popups extends BaseService {
|
export default class Popups extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x08, version: 0x01}, communicator)
|
super({service: 0x08, version: 0x01}, [], communicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default class PrivacyManagement extends BaseService {
|
||||||
private permissionMask: number = 0xffff; // everyone
|
private permissionMask: number = 0xffff; // everyone
|
||||||
|
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x09, version: 0x01}, communicator)
|
super({service: 0x09, version: 0x01}, [0x02, 0x04], communicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
override handleMessage(message : FLAP) {
|
override handleMessage(message : FLAP) {
|
||||||
|
|
|
@ -1,8 +1,44 @@
|
||||||
import BaseService from './base';
|
import BaseService from './base';
|
||||||
import Communicator from '../communicator';
|
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 {
|
export default class UserLookup extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x0a, version: 0x01}, 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@ import Communicator from '../communicator';
|
||||||
|
|
||||||
export default class UsageStats extends BaseService {
|
export default class UsageStats extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x0b, version: 0x01}, communicator)
|
super({service: 0x0b, version: 0x01}, [], communicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@ import Communicator from '../communicator';
|
||||||
|
|
||||||
export default class ChatNavigation extends BaseService {
|
export default class ChatNavigation extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x0d, version: 0x02}, communicator)
|
super({service: 0x0d, version: 0x02}, [], communicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@ import Communicator from '../communicator';
|
||||||
|
|
||||||
export default class Chat extends BaseService {
|
export default class Chat extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x0e, version: 0x01}, communicator)
|
super({service: 0x0e, version: 0x01}, [], communicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@ import Communicator from '../communicator';
|
||||||
|
|
||||||
export default class DirectorySearch extends BaseService {
|
export default class DirectorySearch extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x0f, version: 0x01}, communicator)
|
super({service: 0x0f, version: 0x01}, [], communicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@ import Communicator from '../communicator';
|
||||||
|
|
||||||
export default class ServerStoredBuddyIcons extends BaseService {
|
export default class ServerStoredBuddyIcons extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x10, version: 0x01}, communicator)
|
super({service: 0x10, version: 0x01}, [], communicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@ import Communicator from '../communicator';
|
||||||
// SSI is Server Stored Information
|
// SSI is Server Stored Information
|
||||||
export default class SSI extends BaseService {
|
export default class SSI extends BaseService {
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({service: 0x13, version: 0x01}, communicator)
|
super({service: 0x13, version: 0x01}, [], communicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default class AuthorizationRegistrationService extends BaseService {
|
||||||
private cipher : string;
|
private cipher : string;
|
||||||
|
|
||||||
constructor(communicator : Communicator) {
|
constructor(communicator : Communicator) {
|
||||||
super({ service: 0x17, version: 0x01 }, communicator);
|
super({ service: 0x17, version: 0x01 }, [0x02, 0x06], communicator);
|
||||||
this.cipher = "HARDY";
|
this.cipher = "HARDY";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,11 @@ export default abstract class BaseService {
|
||||||
public service : number;
|
public service : number;
|
||||||
public version : number;
|
public version : number;
|
||||||
|
|
||||||
constructor({service, version} : ServiceFamilyVersion, public communicator : Communicator) {
|
constructor({service, version} : ServiceFamilyVersion, public supportedSubtypes : number[], public communicator : Communicator) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.communicator = communicator;
|
this.communicator = communicator;
|
||||||
|
this.supportedSubtypes = supportedSubtypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
send(message : FLAP) {
|
send(message : FLAP) {
|
||||||
|
|
|
@ -90,23 +90,12 @@ export class SNAC {
|
||||||
service === 0x01 && subtype === 0x08 ||
|
service === 0x01 && subtype === 0x08 ||
|
||||||
service === 0x01 && subtype === 0x0e ||
|
service === 0x01 && subtype === 0x0e ||
|
||||||
service === 0x04 && subtype === 0x02 ||
|
service === 0x04 && subtype === 0x02 ||
|
||||||
service === 0x09 && subtype === 0x04) {
|
service === 0x09 && subtype === 0x04 ||
|
||||||
|
service === 0x0a && subtype === 0x02 ||
|
||||||
|
service === 0x04 && subtype === 0x06) {
|
||||||
payload = buf.slice(10, 10 + payloadLength);
|
payload = buf.slice(10, 10 + payloadLength);
|
||||||
} else {
|
} else {
|
||||||
payload = [];
|
payload = TLV.fromBufferBlob(buf.slice(10));
|
||||||
// Try to parse TLVs
|
|
||||||
let payloadIdx = 10;
|
|
||||||
let cb = 0, cbLimit = 20; //circuit breaker
|
|
||||||
while (payloadIdx < payloadLength && cb < cbLimit) {
|
|
||||||
const tlv = TLV.fromBuffer(buf.slice(payloadIdx));
|
|
||||||
payload.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 new SNAC(service, subtype, payload, requestID, flags);
|
return new SNAC(service, subtype, payload, requestID, flags);
|
||||||
|
|
|
@ -16,13 +16,39 @@ export class TLV {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromBuffer(buf : Buffer) {
|
static fromBuffer(buf : Buffer) {
|
||||||
const type = buf.slice(0, 2).readInt16BE(0) as TLVType;
|
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);
|
||||||
|
|
||||||
return new TLV(type, payload);
|
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 {
|
static forUsername(username : string) : TLV {
|
||||||
return new TLV(0x01, Buffer.from(username));
|
return new TLV(0x01, Buffer.from(username));
|
||||||
}
|
}
|
||||||
|
@ -47,7 +73,7 @@ export class TLV {
|
||||||
return new TLV(0x06, Buffer.concat([varbuf, statbuf]));
|
return new TLV(0x06, Buffer.concat([varbuf, statbuf]));
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public type : TLVType, public payload : Buffer) {
|
constructor(public type : number, public payload : Buffer) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,12 @@ export function dword(num : number) : Buffer {
|
||||||
return buf;
|
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.
|
* Converts a string IP address to it's number representation.
|
||||||
* From: https://stackoverflow.com/a/8105740
|
* From: https://stackoverflow.com/a/8105740
|
||||||
|
|
Loading…
Reference in a new issue