mirror of
https://github.com/amigan/aim-oscar-server.git
synced 2024-11-21 12:09:48 -05:00
modules, tests
This commit is contained in:
parent
b6ef343ead
commit
792526bdaf
4 changed files with 181 additions and 107 deletions
114
src/index.js
114
src/index.js
|
@ -1,109 +1,5 @@
|
|||
const net = require('net');
|
||||
const assert = require('assert');
|
||||
|
||||
function chunkString(str, len) {
|
||||
const size = Math.ceil(str.length/len)
|
||||
const r = Array(size)
|
||||
let offset = 0
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
r[i] = str.substr(offset, len)
|
||||
offset += len
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
function logDataStream(data){
|
||||
const strs = chunkString(data.toString('hex'), 16);
|
||||
return strs.map((str) => chunkString(str, 2).join(' ')).join('\n');
|
||||
}
|
||||
|
||||
class TLV {
|
||||
static fromBuffer(buf) {
|
||||
const type = buf.slice(0, 2).readInt16BE(0);
|
||||
const len = buf.slice(2, 4).readInt16BE(0)
|
||||
const payload = buf.slice(4, 4 + len);
|
||||
|
||||
return new TLV(type, payload);
|
||||
}
|
||||
|
||||
constructor(type, payload) {
|
||||
this.type = type;
|
||||
this.len = payload.length;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `TLV(${this.type}, ${this.len}, ${this.payload.toString('ascii')})`;
|
||||
}
|
||||
}
|
||||
|
||||
class SNAC {
|
||||
static fromBuffer(buf, payloadLength = 0) {
|
||||
const family = buf.slice(0,2).readInt16BE(0);
|
||||
const service = buf.slice(2,4).readInt16BE(0);
|
||||
const flags = buf.slice(4, 6);
|
||||
const requestID = buf.slice(6, 10).readInt32BE(0);
|
||||
const payload = []; // SNACs can have multiple TLVs
|
||||
|
||||
let payloadIdx = 10;
|
||||
let cb = 0, cbLimit = 10; //circuit breaker
|
||||
while (payloadIdx < payloadLength && cb < cbLimit) {
|
||||
const tlv = TLV.fromBuffer(buf.slice(payloadIdx));
|
||||
payload.push(tlv);
|
||||
payloadIdx += tlv.len + 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(family, service, flags, requestID, payload);
|
||||
}
|
||||
|
||||
constructor(family, service, flags, requestID, payload) {
|
||||
this.family = family;
|
||||
this.service = service;
|
||||
this.flags = flags;
|
||||
this.requestID = requestID;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `SNAC(${this.family.toString(16)},${this.service.toString(16)}) #${this.requestID}\n ${this.payload}`;
|
||||
}
|
||||
}
|
||||
|
||||
class FLAP {
|
||||
static fromBuffer(buf) {
|
||||
assert.equal(buf[0], 0x2a, 'Expected 0x2a FLAP header');
|
||||
const channel = parseInt(buf[1], 16);
|
||||
const datagramNumber = buf.slice(2,4).readInt16BE(0);
|
||||
const payloadLength = buf.slice(4, 6).readInt16BE(0);
|
||||
const payload = buf.slice(6, 6 + payloadLength);
|
||||
|
||||
return new FLAP(channel, datagramNumber, payload)
|
||||
}
|
||||
|
||||
constructor(channel, datagramNumber, payload) {
|
||||
this.channel = channel;
|
||||
this.datagramNumber = datagramNumber;
|
||||
this.payload = payload;
|
||||
this.payloadLength = this.payload.length;
|
||||
|
||||
if (channel === 2) {
|
||||
this.payload = SNAC.fromBuffer(this.payload, this.payloadLength);
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
const hasSnac = this.payload instanceof SNAC;
|
||||
const payload = hasSnac ? this.payload.toString() : logDataStream(this.payload).split('\n').join('\n ');
|
||||
return `ch:${this.channel}, dn: ${this.datagramNumber}, len: ${this.payloadLength}, payload:\n ${payload}`
|
||||
}
|
||||
}
|
||||
const { logDataStream } = require('./util');
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
console.log('client connected...');
|
||||
|
@ -115,6 +11,7 @@ const server = net.createServer((socket) => {
|
|||
|
||||
socket.on('data', (data) => {
|
||||
const flap = FLAP.fromBuffer(Buffer.from(data, 'hex'));
|
||||
console.log(logDataStream(flap.toBuffer()));
|
||||
console.log('RECV', flap.toString());
|
||||
});
|
||||
|
||||
|
@ -127,8 +24,11 @@ const server = net.createServer((socket) => {
|
|||
console.log('client disconnected...');
|
||||
});
|
||||
|
||||
const hello = Buffer.from(new Uint8Array([0x2a, 0x01, 0, 0x01, 0, 0x04, 0x00, 0x00, 0x00, 0x01]));
|
||||
socket.write(hello);
|
||||
// const hello = Buffer.from(new Uint8Array([0x2a, 0x01, 0, 0x01, 0, 0x04, 0x00, 0x00, 0x00, 0x01]));
|
||||
|
||||
const hello = new FLAP(0x01, 0, Buffer.from([0x00, 0x00, 0x00, 0x01]));
|
||||
// console.log(logDataStream(hello.toBuffer()));
|
||||
socket.write(hello.toBuffer());
|
||||
|
||||
/* 1. on connection, server sends
|
||||
|
||||
|
|
133
src/structures.js
Normal file
133
src/structures.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
const assert = require('assert');
|
||||
|
||||
const { logDataStream } = require('./util');
|
||||
|
||||
class TLV {
|
||||
static fromBuffer(buf) {
|
||||
const type = buf.slice(0, 2).readInt16BE(0);
|
||||
const len = buf.slice(2, 4).readInt16BE(0)
|
||||
const payload = buf.slice(4, 4 + len);
|
||||
|
||||
return new TLV(type, payload);
|
||||
}
|
||||
|
||||
constructor(type, payload) {
|
||||
this.type = type;
|
||||
this.len = payload.length;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `TLV(${this.type}, ${this.len}, ${this.payload.toString('ascii')})`;
|
||||
}
|
||||
|
||||
toBuffer() {
|
||||
const TLVHeader = Buffer.alloc(4, 0, 'hex');
|
||||
TLVHeader.writeUInt16BE(this.type);
|
||||
TLVHeader.writeUInt16BE(this.len, 2);
|
||||
return Buffer.concat([TLVHeader, this.payload]);
|
||||
}
|
||||
}
|
||||
|
||||
class SNAC {
|
||||
static fromBuffer(buf, payloadLength = 0) {
|
||||
const family = buf.slice(0,2).readInt16BE(0);
|
||||
const service = buf.slice(2,4).readInt16BE(0);
|
||||
const flags = buf.slice(4, 6);
|
||||
const requestID = buf.slice(6, 10).readInt32BE(0);
|
||||
const payload = []; // SNACs can have multiple TLVs
|
||||
|
||||
let payloadIdx = 10;
|
||||
let cb = 0, cbLimit = 10; //circuit breaker
|
||||
while (payloadIdx < payloadLength && cb < cbLimit) {
|
||||
const tlv = TLV.fromBuffer(buf.slice(payloadIdx));
|
||||
payload.push(tlv);
|
||||
payloadIdx += tlv.len + 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(family, service, flags, requestID, payload);
|
||||
}
|
||||
|
||||
constructor(family, service, flags, requestID, payload) {
|
||||
this.family = family;
|
||||
this.service = service;
|
||||
this.flags = flags;
|
||||
this.requestID = requestID;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `SNAC(${this.family.toString(16)},${this.service.toString(16)}) #${this.requestID}\n ${this.payload}`;
|
||||
}
|
||||
|
||||
toBuffer() {
|
||||
const SNACHeader = Buffer.alloc(10, 0, 'hex');
|
||||
SNACHeader.writeUInt16BE(this.family);
|
||||
SNACHeader.writeUInt16BE(this.service, 2);
|
||||
SNACHeader.writeUInt16BE(this.flags, 4);
|
||||
SNACHeader.writeUInt32BE(this.requestID, 6);
|
||||
|
||||
const payload = this.payload.map((tlv) => tlv.toBuffer());
|
||||
return Buffer.concat([SNACHeader, ...payload]);
|
||||
}
|
||||
}
|
||||
|
||||
class FLAP {
|
||||
static fromBuffer(buf) {
|
||||
assert.equal(buf[0], 0x2a, 'Expected 0x2a FLAP header');
|
||||
const channel = parseInt(buf[1], 16);
|
||||
const datagramNumber = buf.slice(2,4).readInt16BE(0);
|
||||
const payloadLength = buf.slice(4, 6).readInt16BE(0);
|
||||
const payload = buf.slice(6, 6 + payloadLength);
|
||||
|
||||
return new FLAP(channel, datagramNumber, payload)
|
||||
}
|
||||
|
||||
constructor(channel, datagramNumber, payload) {
|
||||
this.channel = channel;
|
||||
this.datagramNumber = datagramNumber;
|
||||
|
||||
this.payload = payload;
|
||||
this.payloadLength = this.payload.length;
|
||||
|
||||
if (payload instanceof SNAC) {
|
||||
this.payloadLength = payload.toBuffer().length;
|
||||
}
|
||||
|
||||
if (channel === 2 && !(payload instanceof SNAC)) {
|
||||
this.payload = SNAC.fromBuffer(this.payload, this.payloadLength);
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
const hasSnac = this.payload instanceof SNAC;
|
||||
const payload = hasSnac ? this.payload.toString() : logDataStream(this.payload).split('\n').join('\n ');
|
||||
return `ch:${this.channel}, dn: ${this.datagramNumber}, len: ${this.payloadLength}, payload:\n ${payload}`
|
||||
}
|
||||
|
||||
toBuffer() {
|
||||
const FLAPHeader = Buffer.alloc(6, 0, 'hex');
|
||||
FLAPHeader.writeInt8(0x2a, 0);
|
||||
FLAPHeader.writeInt8(this.channel, 1);
|
||||
FLAPHeader.writeInt16BE(this.datagramNumber, 2);
|
||||
FLAPHeader.writeInt16BE(this.payloadLength, 4);
|
||||
|
||||
let payload = this.payload;
|
||||
if (payload instanceof SNAC) {
|
||||
payload = payload.toBuffer();
|
||||
}
|
||||
|
||||
return Buffer.concat([FLAPHeader, payload]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TLV,
|
||||
SNAC,
|
||||
FLAP,
|
||||
};
|
21
src/util.js
Normal file
21
src/util.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
function chunkString(str, len) {
|
||||
const size = Math.ceil(str.length/len)
|
||||
const r = Array(size)
|
||||
let offset = 0
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
r[i] = str.substr(offset, len)
|
||||
offset += len
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
function logDataStream(data){
|
||||
const strs = chunkString(data.toString('hex'), 16);
|
||||
return strs.map((str) => chunkString(str, 2).join(' ')).join('\n');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logDataStream,
|
||||
};
|
20
tests/data-structures.js
Normal file
20
tests/data-structures.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
const assert = require('assert');
|
||||
|
||||
const { FLAP, SNAC, TLV } = require('../src/structures');
|
||||
|
||||
const tests = [
|
||||
() => {
|
||||
// Construct and test a CLI_AUTH_REQUEST
|
||||
const md5_auth_req = new FLAP(0x02, 0, new SNAC(0x17, 0x06, 0x0000, 0, [new TLV(0x0001, Buffer.from("toof"))]));
|
||||
assert(md5_auth_req.channel === 2);
|
||||
assert(md5_auth_req.payload instanceof SNAC);
|
||||
assert(md5_auth_req.payload.family === 23);
|
||||
assert(md5_auth_req.payload.service === 6);
|
||||
assert(md5_auth_req.payload.payload.length === 1);
|
||||
assert(md5_auth_req.payload.payload[0].len === 4);
|
||||
}
|
||||
];
|
||||
|
||||
tests.forEach((testFn) => {
|
||||
testFn();
|
||||
});
|
Loading…
Reference in a new issue