modules, tests

This commit is contained in:
Artem Titoulenko 2021-09-07 19:08:00 -04:00
parent b6ef343ead
commit 792526bdaf
4 changed files with 181 additions and 107 deletions

View file

@ -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
View 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
View 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
View 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();
});