Add tests for message encoding and working message and network encoding

parent 9b1f65a6
......@@ -4,8 +4,6 @@ import Message from './message';
import Property, { elementHeaderSize } from './property';
// import { CFLBinaryPListParser } from './cflbinary';
import struct from 'python-struct';
export default class Client {
constructor(host, port, password) {
......@@ -74,8 +72,8 @@ export default class Client {
const propData = await this.receive(size);
console.debug('prop data', propData);
if (flags) {
const [errorCode] = struct.unpack('>I', Buffer.from(propData, 'binary'));
if (flags & 1) {
const errorCode = Buffer.from(propData, 'binary').readInt32BE(0);
console.log('error requesting value for property', name, '-', errorCode);
continue;
}
......@@ -122,7 +120,7 @@ export default class Client {
console.debug('prop data', propData);
if (flags) {
const [errorCode] = struct.unpack('>I', Buffer.from(propData, 'binary'));
const [errorCode] = Buffer.from(propData, 'binary').readUInt32BE(0);
console.log('error setting value for property', name, '-', errorCode);
return;
}
......
......@@ -42,7 +42,7 @@ export default class Message {
this.bodyChecksum = 1;
} else {
this.bodySize = typeof bodySize !== 'undefined' ? bodySize : body.length;
this.bodyChecksum = adler32.sum(body);
this.bodyChecksum = adler32.sum(Buffer.from(body, 'binary'));
}
this.key = key;
......@@ -70,16 +70,16 @@ export default class Message {
const magic = buffer.slice(0, 4).toString();
const version = buffer.readInt32BE(4);
const header_checksum = buffer.readInt32BE(8);
const header_checksum = buffer.readUInt32BE(8);
const body_checksum = buffer.readInt32BE(12);
const body_size = buffer.readInt32BE(16);
const flags = buffer.readInt32BE(20);
const unused = buffer.readInt32BE(24);
const command = buffer.readInt32BE(28);
const error_code = buffer.readInt32BE(32);
const pad1 = buffer.slice(36, 36 + 12).toString();
const key = buffer.slice(48, 48 + 32).toString();
const pad2 = buffer.slice(80, 80 + 48).toString();
const pad1 = buffer.slice(36, 36 + 12).toString('binary');
const key = buffer.slice(48, 48 + 32).toString('binary');
const pad2 = buffer.slice(80, 80 + 48).toString('binary');
const unpacked = {packed: header_data, magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key, pad1, pad2};
console.log('Unpacked', unpacked);
......@@ -100,9 +100,9 @@ export default class Message {
buffer.writeInt32BE(unused, 24);
buffer.writeInt32BE(command, 28);
buffer.writeInt32BE(error_code, 32);
buffer.write(''.padEnd(12, '\u0000'), 36, 36 + 12);
buffer.write(key, 48, 48 + 12);
buffer.write(''.padEnd(48, '\u0000'), 80, 80 + 48);
buffer.write(header_data.pad1 || ''.padEnd(12, '\u0000'), 36, 36 + 12, 'binary');
buffer.write(key, 48, 48 + 32, 'binary');
buffer.write(header_data.pad2 || ''.padEnd(48, '\u0000'), 80, 80 + 48, 'binary');
const packed = buffer.toString('binary');
console.log('Packed', packed);
......@@ -129,10 +129,10 @@ export default class Message {
magic, version,
header_checksum: 0, body_checksum, body_size,
flags, unused, command, error_code, key,
// pad1, pad2
pad1, pad2
});
const expectedHeaderChecksum = adler32.sum(tmphdr);
const expectedHeaderChecksum = adler32.sum(Buffer.from(tmphdr, 'binary'));
if (header_checksum !== expectedHeaderChecksum)
throw new Error('Header checksum does not match');
......@@ -142,7 +142,7 @@ export default class Message {
if (body_data && body_size !== body_data.length)
throw new Error('Message body size does not match available data');
if (body_data && body_checksum !== adler32.sum(body_data))
if (body_data && body_checksum !== adler32.sum(Buffer.from(body_data, 'binary')))
throw new Error('Body checksum does not match');
// TODO: check flags
......@@ -234,17 +234,13 @@ export default class Message {
const header = this.constructor.packHeader({
magic: headerMagic, version: this.version,
header_checksum: adler32.sum(tmphdr), body_checksum: this.bodyChecksum, body_size: this.bodySize,
header_checksum: adler32.sum(Buffer.from(tmphdr, 'binary')), body_checksum: this.bodyChecksum, body_size: this.bodySize,
flags: this.flags, unused: this.unused, command: this.command, error_code: this.errorCode, key: this.key
});
return header;
}
static get headerFormat() {
return headerFormat;
}
static get headerMagic() {
return headerMagic;
}
......
......@@ -26,25 +26,22 @@ export default class Property {
value = undefined;
}
if (name && !Property.getSupportedPropertyNames().includes(name))
if (name && !this.constructor.getSupportedPropertyNames().includes(name))
throw new Error('Invalid property name passed to Property constructor: ' + name);
if (value) {
const propType = Property.getPropertyInfoString(name, 'type');
const propType = this.constructor.getPropertyInfoString(name, 'type');
const initHandlerName = `__init_${propType}`;
if (!this[initHandlerName]) throw new Error(`Missing handler for ${propType} property type`);
const initHandler = this[initHandlerName];
console.debug('Old value:', value, '- type:', typeof value);
try {
value = initHandler(value);
value = this[initHandlerName](value);
} catch (err) {
throw new Error(JSON.stringify(err, null, 4) + ' provided for ' + propType + ' property type ' + value);
}
console.debug('New value:', value, '- type:', typeof value);
const validate = Property.getPropertyInfoString(name, 'validate');
const validate = this.constructor.getPropertyInfoString(name, 'validate');
if (validate && !validate(value))
throw new Error('Invalid value passed to validator for property ' + name + ' - type: ' + typeof value);
}
......@@ -117,14 +114,49 @@ export default class Property {
}
}
toString() {
format() {
if (!this.name || !this.value) return '';
const propType = this.getPropertyInfoString(this.name, 'type');
const propType = this.constructor.getPropertyInfoString(this.name, 'type');
const formatHandlerName = `__format_${propType}`;
// For now just return string value
return this.value.toString();
if (!this[formatHandlerName]) throw new Error(`Missing format handler for ${propType} property type`);
return this[formatHandlerName](this.value);
}
__format_dec(value) {
return value.toString();
}
__format_hex(value) {
return '0x' + value.toString(16);
}
__format_mac(value) {
const mac_bytes = [];
for (let i = 0; i < 6; i++) {
mac_bytes.push(value.substr(i, 1));
}
return implode(':', mac_bytes);
}
__format_bin(value) {
return value.toString();
}
__format_cfb(value) {
return value.toString();
}
__format_log(value) {
return value.toString();
}
toString() {
return this.format();
}
static getSupportedPropertyNames() {
......@@ -152,7 +184,7 @@ export default class Property {
static async parseRawElement(data) {
const { name, flags, size } = await this.parseRawElementHeader(data.substr(0, elementHeaderSize));
// TODO: handle flags
return new Property(name, data.substr(elementHeaderSize));
return new this(name, data.substr(elementHeaderSize));
}
static async parseRawElementHeader(data) {
......@@ -180,11 +212,11 @@ export default class Property {
return this.packHeader({name, flags, size});
} catch (err) {
console.error('Error packing', name, flags, size, '- :', err);
throw err;
}
}
static packHeader(header_data) {
console.log('Packing element header data', header_data);
const {name, flags, size} = header_data;
const buffer = Buffer.alloc(12);
......@@ -192,24 +224,20 @@ export default class Property {
buffer.writeUInt32BE(flags, 4);
buffer.writeUInt32BE(size, 8);
const packed = buffer.toString('binary');
console.log('Packed', packed);
return packed;
return buffer.toString('binary');
}
static unpackHeader(header_data) {
if (header_data.length !== elementHeaderSize) throw new Error('Header data must be 12 characters');
if (header_data.length !== elementHeaderSize)
throw new Error('Header data must be 12 characters');
console.log('Unpacking element header data', header_data);
const buffer = Buffer.from(header_data, 'binary');
const name = buffer.slice(0, 4).toString();
const flags = buffer.readUInt32BE(4);
const size = buffer.readUInt32BE(8);
const unpacked = {name, flags, size};
console.log('Unpacked', unpacked);
return unpacked;
return {name, flags, size};
}
}
......@@ -38,9 +38,9 @@ export default class Session {
});
this.socket.on('data', data => {
console.debug(0, 'Receiving data', data.toString());
console.debug(0, 'Receiving data', data);
if (this.reading) return;
this.buffer += data.toString();
this.buffer += data.toString('binary');
});
});
}
......@@ -66,6 +66,9 @@ export default class Session {
}
send(data) {
if (!Buffer.isBuffer(data))
data = Buffer.from(data, 'binary');
if (this.encryptionMethod)
data = this.encryptionMethod(data);
......@@ -73,7 +76,7 @@ export default class Session {
return new Promise((resolve, reject) => {
console.info(0, 'Sending data', data);
this.socket.write(data, 'utf-8', err => {
this.socket.write(data, 'binary', err => {
if (err) reject(err);
else resolve();
});
......@@ -81,12 +84,16 @@ export default class Session {
}
receiveSize(size, _timeout = 10000) {
const receivedChunks = [this.buffer];
this.buffer = '';
const receivedChunks = [this.buffer.substr(0, size)];
this.buffer = this.buffer.substr(size);
this.reading++;
let receivedSize = this.buffer.length;
let receivedSize = receivedChunks[0].length;
let waitingFor = size - receivedSize;
if (waitingFor <= 0) {
return Promise.resolve(receivedChunks.join(''));
}
let updated = Date.now();
let timeout;
......@@ -99,14 +106,16 @@ export default class Session {
timeout = setTimeout(defer, _timeout);
const listener = data => {
data = data.toString('binary');
if (data.length > waitingFor) {
this.buffer += data.substr(waitingFor);
data = data.substr(0, waitingFor);
}
receivedChunks.push(data.toString());
receivedSize += data.toString().length;
waitingFor = waitingFor - data.toString().length;
receivedChunks.push(data);
receivedSize += data.length;
waitingFor = waitingFor - data.length;
clearTimeout(timeout);
......
const {default: Message, generateACPHeaderKey} = require('../dist/message');
const QUnit = require('qunit');
const adler32 = require('adler32');
QUnit.test('Pack header', function (assert) {
const expected_hex = '61637070000300010000000000000000ffffffff000000040000000000000014000000000000000000000000000000007a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa507000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
const message = new Message(0x00030001, 4, 0, 0x14, 0, generateACPHeaderKey('testing'));
const packed = Message.packHeader({
magic: Message.headerMagic,
version: message.version,
header_checksum: 0,
body_checksum: 0,
body_size: message.bodySize,
flags: message.flags,
unused: message.unused,
command: message.command,
error_code: message.errorCode,
key: message.key,
});
const message_hex = Buffer.from(packed, 'binary').toString('hex');
assert.equal(message_hex, expected_hex);
});
QUnit.test('Pack header with checksum', function (assert) {
const expected_hex = '6163707000030001214613e500000000ffffffff000000040000000000000014000000000000000000000000000000007a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa507000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
const message = new Message(0x00030001, 4, 0, 0x14, 0, generateACPHeaderKey('testing'));
const packed = Message.packHeader({
magic: Message.headerMagic,
version: message.version,
header_checksum: 0,
body_checksum: 0,
body_size: message.bodySize,
flags: message.flags,
unused: message.unused,
command: message.command,
error_code: message.errorCode,
key: message.key,
});
const header_checksum = adler32.sum(Buffer.from(packed, 'binary'));
console.log('Header checksum', header_checksum);
assert.equal(header_checksum, 558240741);
const packed_checksum = Message.packHeader({
magic: Message.headerMagic,
version: message.version,
header_checksum,
body_checksum: 0,
body_size: message.bodySize,
flags: message.flags,
unused: message.unused,
command: message.command,
error_code: message.errorCode,
key: message.key,
});
const message_hex = Buffer.from(packed_checksum, 'binary').toString('hex');
assert.equal(message_hex, expected_hex);
});
QUnit.test('Compose get prop command', function (assert) {
// Property.composeRawElement(0, new Property('dbug'))
const payload_hex = '64627567000000000000000400000000';
const payload = Buffer.from(payload_hex, 'hex').toString('binary');
const expected_hex = '61637070000300011bef117b17c301a700000010000000040000000000000014000000000000000000000000000000007a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa50700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064627567000000000000000400000000';
const message = Message.composeGetPropCommand(4, 'testing', payload);
const message_hex = Buffer.from(message, 'binary').toString('hex');
assert.equal(message_hex, expected_hex);
});
QUnit.test('Parse raw command', async function (assert) {
const raw_message_hex = '61637070000300011bef117b17c301a700000010000000040000000000000014000000000000000000000000000000007a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa50700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064627567000000000000000400000000';
const raw_message = Buffer.from(raw_message_hex, 'hex').toString('binary');
const message = await Message.parseRaw(raw_message, false);
assert.equal(message.version, 196609);
assert.equal(message.flags, 4);
assert.equal(message.unused, 0);
assert.equal(message.command, 20);
assert.equal(message.errorCode, 0);
assert.equal(message.key, Buffer.from('7a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa507', 'hex').toString('binary'));
assert.equal(message.body, Buffer.from('64627567000000000000000400000000', 'hex').toString('binary'));
assert.equal(message.bodySize, 16);
assert.equal(message.bodyChecksum, 398655911);
});
......@@ -10,8 +10,8 @@ const client = new Client('192.168.2.251', 5009, 'testing');
console.log('Connected to', client.host, client.port);
try {
const value = await client.getProperties(['dbug']);
console.log('Value:', value);
const props = await client.getProperties(['dbug']);
console.log('Value:', props, props[0].format());
} catch (err) {
console.error('Caught error:', err);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment