Add tests and working keys and property packing/unpacking

parent 2beb8eea
......@@ -4,7 +4,7 @@
"env",
{
"targets": {
"node": "6.10"
"node": "8.10"
}
}
]
......
import gulp from 'gulp';
import pump from 'pump';
import babel from 'gulp-babel';
gulp.task('build', function () {
return pump([
gulp.src('src/**.js'),
babel(),
gulp.dest('dist')
]);
});
gulp.task('watch', gulp.series('build', function () {
return gulp.watch('src/**.js', gulp.series('build'));
}));
const gulp = require('gulp');
const pump = require('pump');
const babel = require('gulp-babel');
const plumber = require('gulp-plumber');
gulp.task('build', function () {
return pump([
gulp.src('src/**/*.js'),
plumber(),
babel(),
gulp.dest('dist')
]);
});
gulp.task('build-tests', function () {
return pump([
gulp.src('tests/**/*.js'),
plumber(),
babel(),
gulp.dest('tests-dist')
]);
});
gulp.task('watch', gulp.series('build', function () {
return gulp.watch('src/**/*.js', gulp.series('build'));
}));
gulp.task('watch-tests', gulp.series('build-tests', function () {
return gulp.watch('src/**/*.js', gulp.series('build-tests'));
}));
gulp.task('build-all', gulp.parallel('build', 'build-tests'));
gulp.task('watch-all', gulp.parallel('watch', 'watch-tests'));
This diff is collapsed.
......@@ -10,8 +10,7 @@
},
"main": "dist/client.js",
"dependencies": {
"adler32": "^0.1.7",
"python-struct": "^1.0.6"
"adler32": "^0.1.7"
},
"devDependencies": {
"babel-core": "^6.26.3",
......@@ -21,7 +20,7 @@
"eslint-config-google": "^0.9.1",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.1",
"gulp-plumber": "^1.2.0",
"pump": "^3.0.0"
"pump": "^3.0.0",
"qunit": "^2.8.0"
}
}
import Session from './session';
import Message from './message';
import Property from './property';
import Property, { elementHeaderSize } from './property';
// import { CFLBinaryPListParser } from './cflbinary';
import struct from 'python-struct';
......@@ -37,7 +37,7 @@ export default class Client {
}
receivePropertyElementHeader() {
return this.receive(Property.elementHeaderSize);
return this.receive(elementHeaderSize);
}
async getProperties(prop_names) {
......@@ -75,7 +75,7 @@ export default class Client {
console.debug('prop data', propData);
if (flags) {
const { errorCode } = struct.unpack('>I', Buffer.from(propData, 'binary'));
const [errorCode] = struct.unpack('>I', Buffer.from(propData, 'binary'));
console.log('error requesting value for property', name, '-', errorCode);
continue;
}
......@@ -122,7 +122,7 @@ export default class Client {
console.debug('prop data', propData);
if (flags) {
const { errorCode } = struct.unpack('>I', Buffer.from(propData, 'binary'));
const [errorCode] = struct.unpack('>I', Buffer.from(propData, 'binary'));
console.log('error setting value for property', name, '-', errorCode);
return;
}
......
......@@ -3,7 +3,7 @@
* Static key/seed for keystream generation
*/
export const ACP_STATIC_KEY = Buffer.from('5b6faf5d9d5b0e1351f2da1de7e8d673', 'hex').toString('utf8');
export const ACP_STATIC_KEY = Buffer.from('5b6faf5d9d5b0e1351f2da1de7e8d673', 'hex').toString('binary');
export function generateACPKeystream(length) {
let key = '';
......@@ -11,6 +11,7 @@ export function generateACPKeystream(length) {
while (key_idx < length) {
key += String.fromCharCode((key_idx + 0x55 & 0xFF) ^ ACP_STATIC_KEY.charCodeAt(key_idx % ACP_STATIC_KEY.length));
key_idx++;
}
......
......@@ -4,7 +4,6 @@
*/
import { generateACPKeystream } from './keystream';
import struct from 'python-struct';
import adler32 from 'adler32';
/**
......@@ -17,7 +16,7 @@ export function generateACPHeaderKey(password) {
const passwordLength = 0x20;
const passwordKey = generateACPKeystream(passwordLength);
const passwordBuffer = password.substr(0, passwordLength).padStart(passwordLength, '\x00');
const passwordBuffer = password.substr(0, passwordLength).padEnd(passwordLength, '\x00');
let encryptedPasswordBuffer = '';
for (let i = 0; i < passwordLength; i++) {
encryptedPasswordBuffer += String.fromCharCode(passwordKey.charCodeAt(i) ^ passwordBuffer.charCodeAt(i));
......@@ -26,7 +25,6 @@ export function generateACPHeaderKey(password) {
return encryptedPasswordBuffer;
}
export const headerFormat = '!4s8i12x32s48x';
export const headerMagic = 'acpp';
export const headerSize = 128;
......@@ -64,6 +62,53 @@ export default class Message {
return s;
}
static unpackHeader(header_data) {
if (header_data.length !== headerSize) throw new Error('Header data must be 128 characters');
console.log('Unpacking header data', header_data);
const buffer = Buffer.from(header_data, 'binary');
const magic = buffer.slice(0, 4).toString();
const version = buffer.readInt32BE(4);
const header_checksum = buffer.readInt32BE(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 unpacked = {packed: header_data, magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key, pad1, pad2};
console.log('Unpacked', unpacked);
return unpacked;
}
static packHeader(header_data) {
console.log('Packing header data', header_data);
const {magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key, pad1 = '', pad2 = ''} = header_data;
const buffer = Buffer.alloc(128);
buffer.write(magic, 0, 4);
buffer.writeInt32BE(version, 4);
buffer.writeInt32BE(header_checksum, 8);
buffer.writeInt32BE(body_checksum, 12);
buffer.writeInt32BE(body_size, 16);
buffer.writeInt32BE(flags, 20);
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);
const packed = buffer.toString('binary');
console.log('Packed', packed);
return packed;
}
static async parseRaw(data) {
if (headerSize > data.length)
throw new Error(`Need to pass at least ${headerSize} bytes`);
......@@ -71,13 +116,7 @@ export default class Message {
const header_data = data.substr(0, headerSize);
const body_data = data.length > headerSize ? data.substr(headerSize) : undefined;
const unpackedData = struct.unpack(headerFormat, Buffer.from(header_data, 'binary'));
const [magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key] = unpackedData;
console.debug('ACP message header fields (parsed not validated):', {
magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key
});
const {magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key, pad1, pad2} = this.unpackHeader(header_data);
if (magic !== headerMagic)
throw new Error('Bad header magic');
......@@ -86,13 +125,15 @@ export default class Message {
if (!versions.includes(version))
throw new Error('Unknown version ' + version);
const tmphdr = struct.pack(headerFormat, [
const tmphdr = this.packHeader({
magic, version,
/* header_checksum: */ 0, body_checksum, body_size,
flags, unused, command, error_code, key
]).toString('binary');
header_checksum: 0, body_checksum, body_size,
flags, unused, command, error_code, key,
// pad1, pad2
});
if (header_checksum !== adler32.sum(tmphdr))
const expectedHeaderChecksum = adler32.sum(tmphdr);
if (header_checksum !== expectedHeaderChecksum)
throw new Error('Header checksum does not match');
if (body_data && body_size === -1)
......@@ -162,12 +203,12 @@ export default class Message {
}
static composeAuthCommand(flags, password, payload) {
const message = new Message(0x00030001, flags, 0, 0x1a, 0, generateACPHeaderKey(password), payload);
const message = new Message(0x00030001, flags, 0, 0x1a, 0, generateACPHeaderKey(''), payload);
return message.composeRawPacket();
}
static composeFeatCommand(flags, password, payload) {
const message = new Message(0x00030001, flags, 0, 0x1b, 0, generateACPHeaderKey(password), payload);
const message = new Message(0x00030001, flags, 0, 0x1b, 0, generateACPHeaderKey(''), payload);
return message.composeRawPacket();
}
......@@ -185,17 +226,17 @@ export default class Message {
}
composeHeader() {
const tmphdr = struct.pack(headerFormat, [
headerMagic, this.version,
0, this.bodyChecksum, this.bodySize,
this.flags, this.unused, this.command, this.errorCode, this.key
]).toString('binary');
const header = struct.pack(headerFormat, [
headerMagic, this.version,
adler32.sum(tmphdr), this.bodyChecksum, this.bodySize,
this.flags, this.unused, this.command, this.errorCode, this.key
]).toString('binary');
const tmphdr = this.constructor.packHeader({
magic: headerMagic, version: this.version,
header_checksum: 0, body_checksum: this.bodyChecksum, body_size: this.bodySize,
flags: this.flags, unused: this.unused, command: this.command, error_code: this.errorCode, key: this.key
});
const header = this.constructor.packHeader({
magic: headerMagic, version: this.version,
header_checksum: adler32.sum(tmphdr), 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;
}
......
import acpProperties from './properties';
const struct = require('python-struct');
export function generateACPProperties() {
const props = [];
for (let prop of acpProperties) {
......@@ -18,8 +16,7 @@ export function generateACPProperties() {
export const props = generateACPProperties();
export const elementHeaderFormat = '!4s2I';
export const elementHeaderSize = struct.sizeOf(elementHeaderFormat);
export const elementHeaderSize = 12;
export default class Property {
......@@ -33,7 +30,7 @@ export default class Property {
throw new Error('Invalid property name passed to Property constructor: ' + name);
if (value) {
const propType = this.getPropertyInfoString(name, 'type');
const propType = Property.getPropertyInfoString(name, 'type');
const initHandlerName = `__init_${propType}`;
if (!this[initHandlerName]) throw new Error(`Missing handler for ${propType} property type`);
const initHandler = this[initHandlerName];
......@@ -60,7 +57,7 @@ export default class Property {
if (typeof value === 'number') {
return value;
} else if (typeof value === 'string') {
return parseInt(value);
return Buffer.from(value, 'binary').readUInt32BE(0);
} else {
throw new Error('Invalid number value: ' + value);
}
......@@ -70,7 +67,7 @@ export default class Property {
if (typeof value === 'number') {
return value;
} else if (typeof value === 'string') {
return value;
return Buffer.from(value, 'binary').readUInt32BE(0);
} else {
throw new Error('Invalid hex value: ' + value);
}
......@@ -134,7 +131,7 @@ export default class Property {
return props.map(prop => prop.name);
}
static getPropertyInfoString(cls, propName, key) {
static getPropertyInfoString(propName, key) {
if (!propName) return;
const prop = props.find(p => p.name === propName);
......@@ -159,7 +156,7 @@ export default class Property {
}
static async parseRawElementHeader(data) {
return struct.unpack(elementHeaderFormat, Buffer.from(data, 'binary'));
return this.unpackHeader(data);
}
static composeRawElement(flags, property) {
......@@ -167,7 +164,10 @@ export default class Property {
const value = property.value ? property.value : '\x00\x00\x00\x00';
if (typeof value === 'number') {
return this.composeRawElementHeader(name, flags, struct.sizeOf('>I')) + struct.pack('>I', [value]);
const buffer = Buffer.alloc(4);
buffer.writeUInt32BE(value, 0);
return this.composeRawElementHeader(name, flags, 4) + buffer.toString('binary');
} else if (typeof value === 'string') {
return this.composeRawElementHeader(name, flags, value.length) + value;
} else {
......@@ -177,10 +177,39 @@ export default class Property {
static composeRawElementHeader(name, flags, size) {
try {
return struct.pack(elementHeaderFormat, [name, flags, size]).toString('binary');
return this.packHeader({name, flags, size});
} catch (err) {
console.error('Error packing', name, flags, size, '- :', err);
}
}
static packHeader(header_data) {
console.log('Packing element header data', header_data);
const {name, flags, size} = header_data;
const buffer = Buffer.alloc(12);
buffer.write(name, 0, 4);
buffer.writeUInt32BE(flags, 4);
buffer.writeUInt32BE(size, 8);
const packed = buffer.toString('binary');
console.log('Packed', packed);
return packed;
}
static unpackHeader(header_data) {
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;
}
}
const {generateACPHeaderKey} = require('../dist/message');
const {generateACPKeystream, ACP_STATIC_KEY} = require('../dist/keystream');
const QUnit = require('qunit');
QUnit.test('Generate keystream', function (assert) {
const expected_hex = '0e39f805c401554f0cac857d868ab5173e09c835';
const key = generateACPKeystream(20);
const key_hex = Buffer.from(key, 'binary').toString('hex');
assert.equal(expected_hex, key_hex);
});
QUnit.test('Generate message header key', function (assert) {
const expected_hex = '7a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa507';
const key = generateACPHeaderKey('testing');
const key_hex = Buffer.from(key, 'binary').toString('hex');
assert.equal(expected_hex, key_hex);
});
const {default: Property} = require('../dist/property');
const QUnit = require('qunit');
QUnit.test('Compose raw element header', function (assert) {
const expected_hex = '646275670000000000000004';
const header = Property.composeRawElementHeader('dbug', 0, 4);
const header_hex = Buffer.from(header, 'binary').toString('hex');
assert.equal(expected_hex, header_hex);
});
QUnit.test('Compose raw element', function (assert) {
const expected_hex = '64627567000000000000000400000000';
const raw_element = Property.composeRawElement(0, new Property('dbug'));
const raw_element_hex = Buffer.from(raw_element, 'binary').toString('hex');
assert.equal(expected_hex, raw_element_hex);
});
QUnit.test('Parse raw element', async function (assert) {
const raw_element_hex = '64627567000000000000000400003000';
const raw_element = Buffer.from(raw_element_hex, 'hex').toString('binary');
const property = await Property.parseRawElement(raw_element);
assert.equal(property.name, 'dbug');
assert.equal(property.value, 0x3000);
});
import adler32 from 'adler32';
import Client from '../';
const adler32 = require('adler32');
const {default: Client} = require('..');
const client = new Client('192.168.2.251', 5009, 'testing');
......
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