...
 
Commits (3)
...@@ -25,3 +25,14 @@ test: ...@@ -25,3 +25,14 @@ test:
lint: lint:
script: script:
- npx eslint src test tests - npx eslint src test tests
publish:
script:
- echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
- npm publish
dependencies:
- build
only:
- /^v.*$/
except:
- branches
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"acp": "bin/acp.js" "acp": "bin/acp.js"
}, },
"files": [ "files": [
"dist" "dist",
"bin"
], ],
"dependencies": { "dependencies": {
"adler32": "^0.1.7", "adler32": "^0.1.7",
......
...@@ -5,10 +5,22 @@ export const HEADER_SIZE = HEADER_MAGIC.length; ...@@ -5,10 +5,22 @@ export const HEADER_SIZE = HEADER_MAGIC.length;
export const FOOTER_SIZE = FOOTER_MAGIC.length; export const FOOTER_SIZE = FOOTER_MAGIC.length;
export default class CFLBinaryPList { export default class CFLBinaryPList {
/**
* Compose JavaScript object into equivalent plist.
*
* @param {*} object
* @return {string} data
*/
static compose(object) { static compose(object) {
return CFLBinaryPListComposer.compose(object); return CFLBinaryPListComposer.compose(object);
} }
/**
* Parse plist data into equivalent JavaScript built in.
*
* @param {string} data
* @return {*}
*/
static parse(data) { static parse(data) {
return CFLBinaryPListParser.parse(data); return CFLBinaryPListParser.parse(data);
} }
...@@ -18,7 +30,8 @@ export class CFLBinaryPListComposer { ...@@ -18,7 +30,8 @@ export class CFLBinaryPListComposer {
/** /**
* Compose JavaScript object into equivalent plist. * Compose JavaScript object into equivalent plist.
* *
* @return string data * @param {*} object
* @return {string} data
*/ */
static compose(object) { static compose(object) {
let data = HEADER_MAGIC; let data = HEADER_MAGIC;
...@@ -33,7 +46,9 @@ export class CFLBinaryPListComposer { ...@@ -33,7 +46,9 @@ export class CFLBinaryPListComposer {
/** /**
* Pack a supported JavaScript built in object. * Pack a supported JavaScript built in object.
* *
* @return string data * @param {*} object
* @param {number} depth
* @return {string} data
*/ */
static packObject(object, depth = 1) { static packObject(object, depth = 1) {
let data = ''; let data = '';
...@@ -43,7 +58,8 @@ export class CFLBinaryPListComposer { ...@@ -43,7 +58,8 @@ export class CFLBinaryPListComposer {
} else if (typeof object === 'boolean') { } else if (typeof object === 'boolean') {
data += object ? '\x09' : '\x08'; data += object ? '\x09' : '\x08';
} else if (typeof object === 'number' && object % 1 !== 0) { } else if (typeof object === 'number' && object % 1 !== 0) {
let string = '', size = null; let string = '';
let size = null;
const sizes = {4: 'FloatBE', 8: 'DoubleBE'}; const sizes = {4: 'FloatBE', 8: 'DoubleBE'};
for (size of Object.keys(sizes)) { for (size of Object.keys(sizes)) {
...@@ -69,7 +85,8 @@ export class CFLBinaryPListComposer { ...@@ -69,7 +85,8 @@ export class CFLBinaryPListComposer {
data += String.fromCharCode(marker); data += String.fromCharCode(marker);
data += string; data += string;
} else if (typeof object === 'number') { } else if (typeof object === 'number') {
let string = '', size = null; let string = '';
let size = null;
const sizes = {1: '8', 2: '16BE', 4: '32BE'}; const sizes = {1: '8', 2: '16BE', 4: '32BE'};
for (size of Object.keys(sizes)) { for (size of Object.keys(sizes)) {
...@@ -104,8 +121,8 @@ export class CFLBinaryPListComposer { ...@@ -104,8 +121,8 @@ export class CFLBinaryPListComposer {
data += object.toString('binary'); data += object.toString('binary');
} else if (typeof object === 'string') { } else if (typeof object === 'string') {
data += '\x70'; data += '\x70';
data += Buffer.from(object, 'utf8').toString('binary'); data += Buffer.from(object, 'utf8').toString('binary');
data += '\x00'; data += '\x00';
} else if (typeof object === 'object' && object instanceof Array || object instanceof Set) { } else if (typeof object === 'object' && object instanceof Array || object instanceof Set) {
data += '\xa0'; data += '\xa0';
...@@ -143,11 +160,12 @@ export class CFLBinaryPListComposer { ...@@ -143,11 +160,12 @@ export class CFLBinaryPListComposer {
} }
} }
class CFLBinaryPListParser { export class CFLBinaryPListParser {
/** /**
* Parse plist data into equivalent JavaScript built in. * Parse plist data into equivalent JavaScript built in.
* *
* @return any * @param {string} data
* @return {*}
*/ */
static parse(data) { static parse(data) {
if (data.length < HEADER_SIZE + FOOTER_SIZE + 1) { if (data.length < HEADER_SIZE + FOOTER_SIZE + 1) {
...@@ -175,14 +193,17 @@ class CFLBinaryPListParser { ...@@ -175,14 +193,17 @@ class CFLBinaryPListParser {
/** /**
* Unpack an object from the provided data. * Unpack an object from the provided data.
* *
* @return array * @param {string} data
* @param {number} depth
* @return {Array}
*/ */
static unpackObject(data, depth = 1) { static unpackObject(data, depth = 1) {
if (depth > 10) { if (depth > 10) {
throw new Error('Max depth reached'); throw new Error('Max depth reached');
} }
let object = null, marker; // let object = null;
let marker;
[marker, data] = this.unpackObjectMarker(data); [marker, data] = this.unpackObjectMarker(data);
const object_type = marker & 0xf0; const object_type = marker & 0xf0;
...@@ -269,7 +290,8 @@ class CFLBinaryPListParser { ...@@ -269,7 +290,8 @@ class CFLBinaryPListParser {
const object = {}; const object = {};
while (true) { while (true) {
let key, value; let key;
let value;
[key, data] = this.unpackObject(data, depth + 1); [key, data] = this.unpackObject(data, depth + 1);
if (key === null) break; if (key === null) break;
...@@ -288,7 +310,8 @@ class CFLBinaryPListParser { ...@@ -288,7 +310,8 @@ class CFLBinaryPListParser {
/** /**
* Unpack an object marker from the provided data. * Unpack an object marker from the provided data.
* *
* @return array * @param {string} data
* @return {Array}
*/ */
static unpackObjectMarker(data) { static unpackObjectMarker(data) {
const marker_byte = data.substr(0, 1); const marker_byte = data.substr(0, 1);
...@@ -301,7 +324,9 @@ class CFLBinaryPListParser { ...@@ -301,7 +324,9 @@ class CFLBinaryPListParser {
/** /**
* Unpack an int object as a JavaScript number from the provided data. * Unpack an int object as a JavaScript number from the provided data.
* *
* @return array * @param {number} size_exponent
* @param {string} data
* @return {Array}
*/ */
static unpackInt(size_exponent, data) { static unpackInt(size_exponent, data) {
const int_size = 2 ** size_exponent; const int_size = 2 ** size_exponent;
...@@ -314,7 +339,9 @@ class CFLBinaryPListParser { ...@@ -314,7 +339,9 @@ class CFLBinaryPListParser {
/** /**
* Unpack a real object as a JavaScript number from the provided data. * Unpack a real object as a JavaScript number from the provided data.
* *
* @return array * @param {number} size_exponent
* @param {string} data
* @return {Array}
*/ */
static unpackReal(size_exponent, data) { static unpackReal(size_exponent, data) {
const real_size = 2 ** size_exponent; const real_size = 2 ** size_exponent;
...@@ -333,7 +360,9 @@ class CFLBinaryPListParser { ...@@ -333,7 +360,9 @@ class CFLBinaryPListParser {
/** /**
* Unpack count from object info nibble and/or packed int value. * Unpack count from object info nibble and/or packed int value.
* *
* @return array * @param {number} object_info
* @param {string} data
* @return {Array}
*/ */
static unpackCount(object_info, data) { static unpackCount(object_info, data) {
if (object_info === 0x0f) { if (object_info === 0x0f) {
......
...@@ -126,4 +126,11 @@ yargs.command('setprop <prop> <value>', 'Set an ACP property', yargs => { ...@@ -126,4 +126,11 @@ yargs.command('setprop <prop> <value>', 'Set an ACP property', yargs => {
console.log(props); console.log(props);
})); }));
yargs.command('features', 'Get supported features', yargs => {}, commandHandler(async (client, argv) => {
const features = await client.getFeatures();
console.log(features);
}));
// eslint-disable-next-line no-unused-vars
const argv = yargs.argv; const argv = yargs.argv;
...@@ -5,6 +5,14 @@ import Property, {HEADER_SIZE as ELEMENT_HEADER_SIZE} from './property'; ...@@ -5,6 +5,14 @@ import Property, {HEADER_SIZE as ELEMENT_HEADER_SIZE} from './property';
import CFLBinaryPList from './cflbinary'; import CFLBinaryPList from './cflbinary';
export default class Client { export default class Client {
/**
* Creates an ACP Client.
*
* @param {string} host
* @param {number} port
* @param {string} password
* @return {undefined}
*/
constructor(host, port, password) { constructor(host, port, password) {
this.host = host; this.host = host;
this.port = port; this.port = port;
...@@ -13,26 +21,59 @@ export default class Client { ...@@ -13,26 +21,59 @@ export default class Client {
this.session = new Session(host, port, password); this.session = new Session(host, port, password);
} }
/**
* Connects to the ACP server.
*
* @param {number} timeout
* @return {Promise}
*/
connect(timeout) { connect(timeout) {
return this.session.connect(timeout); return this.session.connect(timeout);
} }
/**
* Disconnects from the ACP server.
*
* @return {Promise}
*/
disconnect() { disconnect() {
return this.session.close(); return this.session.close();
} }
/**
* Sends a Message to the ACP server.
*
* @param {Message|Buffer|string} data
* @return {Promise}
*/
send(data) { send(data) {
return this.session.send(data); return this.session.send(data);
} }
/**
* Receives data from the ACP server.
*
* @param {number} size
* @return {Promise<string>}
*/
receive(size) { receive(size) {
return this.session.receive(size); return this.session.receive(size);
} }
/**
* Receives a message header from the ACP server.
*
* @return {Promise<string>}
*/
receiveMessageHeader() { receiveMessageHeader() {
return this.receive(MESSAGE_HEADER_SIZE); return this.receive(MESSAGE_HEADER_SIZE);
} }
/**
* Receives a property element header from the ACP server.
*
* @return {Promise<string>}
*/
receivePropertyElementHeader() { receivePropertyElementHeader() {
return this.receive(ELEMENT_HEADER_SIZE); return this.receive(ELEMENT_HEADER_SIZE);
} }
...@@ -43,11 +84,14 @@ export default class Client { ...@@ -43,11 +84,14 @@ export default class Client {
* Client: GetProp {...Property} * Client: GetProp {...Property}
* Server: GetProp * Server: GetProp
* Server: ...Property * Server: ...Property
*
* @param {Array} props
* @return {Array}
*/ */
async getProperties(prop_names) { async getProperties(props) {
let payload = ''; let payload = '';
for (let name of prop_names) { for (let name of props) {
payload += Property.composeRawElement(0, name instanceof Property ? name : new Property(name)); payload += Property.composeRawElement(0, name instanceof Property ? name : new Property(name));
} }
...@@ -61,7 +105,7 @@ export default class Client { ...@@ -61,7 +105,7 @@ export default class Client {
throw new Error('Error ' . reply_header.error_code); throw new Error('Error ' . reply_header.error_code);
} }
const props = []; const props_with_values = [];
while (true) { while (true) {
const prop_header = await this.receivePropertyElementHeader(); const prop_header = await this.receivePropertyElementHeader();
...@@ -85,12 +129,18 @@ export default class Client { ...@@ -85,12 +129,18 @@ export default class Client {
console.debug('Prop', prop); console.debug('Prop', prop);
props.push(prop); props_with_values.push(prop);
} }
return props; return props_with_values;
} }
/**
* Sets properties on the AirPort device.
*
* @param {Array} props
* @return {undefined}
*/
async setProperties(props) { async setProperties(props) {
let payload = ''; let payload = '';
...@@ -127,6 +177,11 @@ export default class Client { ...@@ -127,6 +177,11 @@ export default class Client {
} }
} }
/**
* Gets the supported features on the AirPort device.
*
* @return {Array}
*/
async getFeatures() { async getFeatures() {
await this.send(Message.composeFeatCommand(0)); await this.send(Message.composeFeatCommand(0));
const reply_header = await Message.parseRaw(await this.receiveMessageHeader()); const reply_header = await Message.parseRaw(await this.receiveMessageHeader());
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
* ACP message composition and parsing * ACP message composition and parsing
*/ */
import Property from './property';
import {generateACPKeystream} from './keystream'; import {generateACPKeystream} from './keystream';
import adler32 from 'adler32'; import adler32 from 'adler32';
...@@ -11,8 +10,9 @@ import adler32 from 'adler32'; ...@@ -11,8 +10,9 @@ import adler32 from 'adler32';
/** /**
* Encrypt password for ACP message header key field. * Encrypt password for ACP message header key field.
* Truncates the password at 0x20 bytes, not sure if this is the right thing to use in all cases. * Truncates the password at 0x20 bytes, not sure if this is the right thing to use in all cases.
* @param {String} password Base station password *
* @return {String} Encrypted password of proper length for the header field * @param {string} password Base station password
* @return {string} Encrypted password of proper length for the header field
*/ */
export function generateACPHeaderKey(password) { export function generateACPHeaderKey(password) {
const password_length = 0x20; const password_length = 0x20;
...@@ -32,6 +32,19 @@ export const HEADER_MAGIC = 'acpp'; ...@@ -32,6 +32,19 @@ export const HEADER_MAGIC = 'acpp';
export const HEADER_SIZE = 128; export const HEADER_SIZE = 128;
export default class Message { export default class Message {
/**
* Creates a Message.
*
* @param {number} version
* @param {number} flags
* @param {number} unused
* @param {number} command
* @param {number} error_code
* @param {string} key
* @param {string} body
* @param {number} body_size
* @return {undefined}
*/
constructor(version, flags, unused, command, error_code, key, body, body_size) { constructor(version, flags, unused, command, error_code, key, body, body_size) {
this.version = version; this.version = version;
this.flags = flags; this.flags = flags;
...@@ -62,8 +75,16 @@ export default class Message { ...@@ -62,8 +75,16 @@ export default class Message {
+ 'Key: ' + this.key; + 'Key: ' + this.key;
} }
/**
* Unpacks an ACP message header.
*
* @param {string} header_data
* @return {object}
*/
static unpackHeader(header_data) { static unpackHeader(header_data) {
if (header_data.length !== HEADER_SIZE) throw new Error('Header data must be 128 characters'); if (header_data.length !== HEADER_SIZE) {
throw new Error('Header data must be 128 bytes');
}
const buffer = Buffer.from(header_data, 'binary'); const buffer = Buffer.from(header_data, 'binary');
...@@ -83,6 +104,12 @@ export default class Message { ...@@ -83,6 +104,12 @@ export default class Message {
return {magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key, pad1, pad2}; return {magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key, pad1, pad2};
} }
/**
* Packs an ACP message header.
*
* @param {object} header_data
* @return {string}
*/
static packHeader(header_data) { static packHeader(header_data) {
const {magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key, pad1 = '', pad2 = ''} = 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); const buffer = Buffer.alloc(128);
...@@ -103,8 +130,15 @@ export default class Message { ...@@ -103,8 +130,15 @@ export default class Message {
return buffer.toString('binary'); return buffer.toString('binary');
} }
static async parseRaw(data, return_remaining) { /**
if (HEADER_SIZE > data.length) { * Parses a full ACP message.
*
* @param {string} data
* @param {boolean} return_remaining Whether to return any additional data
* @return {Message|Array}
*/
static parseRaw(data, return_remaining) {
if (data.length < HEADER_SIZE) {
throw new Error(`Need to pass at least ${HEADER_SIZE} bytes`); throw new Error(`Need to pass at least ${HEADER_SIZE} bytes`);
} }
...@@ -215,6 +249,11 @@ export default class Message { ...@@ -215,6 +249,11 @@ export default class Message {
return new Message(version, flags, unused, command, error_code, generateACPHeaderKey(password), payload, payload_size); return new Message(version, flags, unused, command, error_code, generateACPHeaderKey(password), payload, payload_size);
} }
/**
* Composes a full ACP message.
*
* @return {string}
*/
composeRawPacket() { composeRawPacket() {
let reply = this.composeHeader(); let reply = this.composeHeader();
...@@ -223,6 +262,13 @@ export default class Message { ...@@ -223,6 +262,13 @@ export default class Message {
return reply; return reply;
} }
/**
* Composes an ACP message header.
*
* @param {number} size
* @param {number} timeout
* @return {Promise<string>}
*/
composeHeader() { composeHeader() {
const tmphdr = this.constructor.packHeader({ const tmphdr = this.constructor.packHeader({
magic: HEADER_MAGIC, version: this.version, magic: HEADER_MAGIC, version: this.version,
......
...@@ -26,6 +26,13 @@ export const props = generateACPProperties(); ...@@ -26,6 +26,13 @@ export const props = generateACPProperties();
export const HEADER_SIZE = 12; export const HEADER_SIZE = 12;
export default class Property { export default class Property {
/**
* Creates a Property.
*
* @param {string} name
* @param {string} value
* @return {undefined}
*/
constructor(name, value) { constructor(name, value) {
if (name === '\x00\x00\x00\x00' && value === '\x00\x00\x00\x00') { if (name === '\x00\x00\x00\x00' && value === '\x00\x00\x00\x00') {
name = undefined; name = undefined;
...@@ -120,6 +127,11 @@ export default class Property { ...@@ -120,6 +127,11 @@ export default class Property {
} }
} }
/**
* Convert the property's value to a JavaScript built in type.
*
* @return {*}
*/
format() { format() {
if (!this.name || !this.value) return ''; if (!this.name || !this.value) return '';
...@@ -167,9 +179,14 @@ export default class Property { ...@@ -167,9 +179,14 @@ export default class Property {
} }
toString() { toString() {
return this.format(); return JSON.stringify(this.format());
} }
/**
* Returns the names of known properties.
*
* @return {Array}
*/
static getSupportedPropertyNames() { static getSupportedPropertyNames() {
return props.map(prop => prop.name); return props.map(prop => prop.name);
} }
...@@ -192,17 +209,27 @@ export default class Property { ...@@ -192,17 +209,27 @@ export default class Property {
return prop[key]; return prop[key];
} }
static async parseRawElement(data) { /**
* Parses an ACP property.
*
* @param {string} data
* @return {Property}
*/
static parseRawElement(data) {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {name, flags, size} = await this.parseRawElementHeader(data.substr(0, HEADER_SIZE)); const {name, flags, size} = this.unpackHeader(data.substr(0, HEADER_SIZE));
// TODO: handle flags // TODO: handle flags
return new this(name, data.substr(HEADER_SIZE)); return new this(name, data.substr(HEADER_SIZE));
} }
static async parseRawElementHeader(data) { /**
return this.unpackHeader(data); * Composes an ACP property.
} *
* @param {number} flags
* @param {Property} property
* @return {string}
*/
static composeRawElement(flags, property) { static composeRawElement(flags, property) {
const name = property.name ? property.name : '\x00\x00\x00\x00'; const name = property.name ? property.name : '\x00\x00\x00\x00';
const value = property.value ? property.value : '\x00\x00\x00\x00'; const value = property.value ? property.value : '\x00\x00\x00\x00';
...@@ -228,6 +255,12 @@ export default class Property { ...@@ -228,6 +255,12 @@ export default class Property {
} }
} }
/**
* Packs an ACP property header.
*
* @param {object} header_data
* @return {string}
*/
static packHeader(header_data) { static packHeader(header_data) {
const {name, flags, size} = header_data; const {name, flags, size} = header_data;
const buffer = Buffer.alloc(12); const buffer = Buffer.alloc(12);
...@@ -239,6 +272,12 @@ export default class Property { ...@@ -239,6 +272,12 @@ export default class Property {
return buffer.toString('binary'); return buffer.toString('binary');
} }
/**
* Unpacks an ACP property header.
*
* @param {string} header_data
* @return {object}
*/
static unpackHeader(header_data) { static unpackHeader(header_data) {
if (header_data.length !== HEADER_SIZE) { if (header_data.length !== HEADER_SIZE) {
throw new Error('Header data must be 12 characters'); throw new Error('Header data must be 12 characters');
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import Session from './session'; import Session from './session';
import Message, {HEADER_SIZE as MESSAGE_HEADER_SIZE} from './message'; import Message, {HEADER_SIZE as MESSAGE_HEADER_SIZE} from './message';
import Property, {HEADER_SIZE as ELEMENT_HEADER_SIZE} from './property'; import Property, {HEADER_SIZE as ELEMENT_HEADER_SIZE} from './property';
import CFLBinaryPList from './cflbinary'; // import CFLBinaryPList from './cflbinary';
import net from 'net'; import net from 'net';
...@@ -96,44 +96,46 @@ export default class Server { ...@@ -96,44 +96,46 @@ export default class Server {
console.log('Received message', message); console.log('Received message', message);
switch (message.command) { switch (message.command) {
// Get prop // Get prop
case 0x14: { case 0x14: {
let data = message.body; let data = message.body;
const props = []; const props = [];
// Read the requested props into an array of Propertys // Read the requested props into an array of Propertys
while (data.length) { while (data.length) {
const prop_header = data.substr(0, ELEMENT_HEADER_SIZE); const prop_header = data.substr(0, ELEMENT_HEADER_SIZE);
const prop_data = await Property.parseRawElementHeader(prop_header); const prop_data = await Property.parseRawElementHeader(prop_header);
console.debug(prop_data); console.debug(prop_data);
const {name, flags, size} = prop_data; const {name, size} = prop_data;
const value = data.substr(0, ELEMENT_HEADER_SIZE + size); const value = data.substr(0, ELEMENT_HEADER_SIZE + size);
data = data.substr(ELEMENT_HEADER_SIZE + size); data = data.substr(ELEMENT_HEADER_SIZE + size);
const prop = new Property(name, value); const prop = new Property(name, value);
if (typeof prop.name === 'undefined' && typeof prop.value === 'undefined') { if (typeof prop.name === 'undefined' && typeof prop.value === 'undefined') {
break; break;
}
props.push(prop);
} }
// Send back an array of Propertys props.push(prop);
const ret = this.getProperties(props); }
let payload = '', i = 0;
for (let prop of ret) { // Send back an array of Propertys
payload += Property.composeRawElement(0, prop instanceof Property ? prop : new Property(props[i], prop)); const ret = this.getProperties(props);
i++;
}
const response = Message.composeGetPropCommand(4, this.password, payload); let payload = '';
let i = 0;
return; for (let prop of ret) {
payload += Property.composeRawElement(0, prop instanceof Property ? prop : new Property(props[i], prop));
i++;
} }
// eslint-disable-next-line no-unused-vars
const response = Message.composeGetPropCommand(4, this.password, payload);
return;
}
} }
} }
......
import Message, {HEADER_SIZE as MESSAGE_HEADER_SIZE} from './message'; import Message, {HEADER_SIZE as MESSAGE_HEADER_SIZE} from './message';
import Property, {HEADER_SIZE as ELEMENT_HEADER_SIZE} from './property'; import {HEADER_SIZE as ELEMENT_HEADER_SIZE} from './property';
// import {ClientEncryption, ServerEncryption} from './encryption'; // import {ClientEncryption, ServerEncryption} from './encryption';
import net from 'net'; import net from 'net';
export default class Session { export default class Session {
/**
* Creates a Session.
*
* @param {string} host
* @param {number} port
* @param {string} password
* @return {undefined}
*/
constructor(host, port, password) { constructor(host, port, password) {
this.host = host; this.host = host;
this.port = port; this.port = port;
...@@ -18,14 +26,20 @@ export default class Session { ...@@ -18,14 +26,20 @@ export default class Session {
this.encryption = undefined; this.encryption = undefined;
} }
connect(_timeout = 10000) { /**
* Connects to the ACP server.
*
* @param {number} timeout
* @return {Promise}
*/
connect(timeout = 10000) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.socket = new net.Socket(); this.socket = new net.Socket();
setTimeout(() => { setTimeout(() => {
this.reading -= 1; this.reading -= 1;
reject('Timeout'); reject('Timeout');
}, _timeout); }, timeout);
this.socket.connect(this.port, this.host, err => { this.socket.connect(this.port, this.host, err => {
console.log('Connected', err); console.log('Connected', err);
...@@ -45,16 +59,30 @@ export default class Session { ...@@ -45,16 +59,30 @@ export default class Session {
}); });
} }
/**
* Disconnects from the ACP server.
*
* @return {Promise}
*/
close() { close() {
if (!this.socket) return; if (!this.socket) return;
this.socket.end(); this.socket.end();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.socket.on('close', resolve); this.socket.on('close', () => {
this.socket = undefined;
resolve();
});
}); });
} }
/**
* Receives and parses a Message from the ACP server.
*
* @param {number} timeout
* @return {Promise<Message>}
*/
async receiveMessage(timeout) { async receiveMessage(timeout) {
const raw_header = await this.receiveMessageHeader(timeout); const raw_header = await this.receiveMessageHeader(timeout);
const message = await Message.parseRaw(raw_header); const message = await Message.parseRaw(raw_header);
...@@ -66,20 +94,46 @@ export default class Session { ...@@ -66,20 +94,46 @@ export default class Session {
return message; return message;
} }
/**
* Receives a message header from the ACP server.
*
* @param {number} timeout
* @return {Promise<string>}
*/
receiveMessageHeader(timeout) { receiveMessageHeader(timeout) {
return this.receive(MESSAGE_HEADER_SIZE, timeout); return this.receive(MESSAGE_HEADER_SIZE, timeout);
} }
/**
* Receives a property element header from the ACP server.
*
* @param {number} timeout
* @return {Promise<string>}
*/
receivePropertyElementHeader(timeout) { receivePropertyElementHeader(timeout) {
return this.receive(ELEMENT_HEADER_SIZE, timeout); return this.receive(ELEMENT_HEADER_SIZE, timeout);
} }
/**
* Sends and receives data to/from the ACP server.
*
* @param {Message|Buffer|string} data
* @param {number} size
* @param {number} timeout
* @return {Promise<string>}
*/
async sendAndReceive(data, size, timeout = 10000) { async sendAndReceive(data, size, timeout = 10000) {
await this.send(data); await this.send(data);
return await this.receive(size, timeout); return await this.receive(size, timeout);
} }
/**
* Sends data to the ACP server.
*
* @param {Message|Buffer|string} data
* @return {Promise}
*/
send(data) { send(data) {
if (data instanceof Message) { if (data instanceof Message) {
data = data.composeRawPacket(); data = data.composeRawPacket();
...@@ -104,7 +158,14 @@ export default class Session { ...@@ -104,7 +158,14 @@ export default class Session {
}); });
} }
receiveSize(size, _timeout = 10000) { /**
* Receives raw data from the ACP server.
*
* @param {number} size
* @param {number} timeout
* @return {Promise<string>}
*/
receiveSize(size, timeout = 10000) {
const receivedChunks = [this.buffer.substr(0, size)]; const receivedChunks = [this.buffer.substr(0, size)];
this.buffer = this.buffer.substr(size); this.buffer = this.buffer.substr(size);
this.reading++; this.reading++;
...@@ -115,7 +176,7 @@ export default class Session { ...@@ -115,7 +176,7 @@ export default class Session {
return Promise.resolve(receivedChunks.join('')); return Promise.resolve(receivedChunks.join(''));
} }
let timeout; let _timeout = timeout;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const defer = () => { const defer = () => {
...@@ -123,7 +184,7 @@ export default class Session { ...@@ -123,7 +184,7 @@ export default class Session {
reject('Timeout'); reject('Timeout');
}; };
timeout = setTimeout(defer, _timeout); let timeout = setTimeout(defer, _timeout);
const listener = data => { const listener = data => {
data = data.toString('binary'); data = data.toString('binary');
...@@ -159,6 +220,13 @@ export default class Session { ...@@ -159,6 +220,13 @@ export default class Session {
}); });
} }
/**
* Receives and decrypts data from the ACP server.
*
* @param {number} size
* @param {number} timeout
* @return {Promise<string>}
*/
async receive(size, timeout = 10000) { async receive(size, timeout = 10000) {
let data = await this.receiveSize(size, timeout); let data = await this.receiveSize(size, timeout);
......
...@@ -9,15 +9,15 @@ QUnit.test('Pack header', function (assert) { ...@@ -9,15 +9,15 @@ QUnit.test('Pack header', function (assert) {
const message = new Message(0x00030001, 4, 0, 0x14, 0, generateACPHeaderKey('testing')); const message = new Message(0x00030001, 4, 0, 0x14, 0, generateACPHeaderKey('testing'));
const packed = Message.packHeader({ const packed = Message.packHeader({
magic: Message.headerMagic, magic: Message.HEADER_MAGIC,
version: message.version, version: message.version,
header_checksum: 0, header_checksum: 0,
body_checksum: 0, body_checksum: 0,
body_size: message.bodySize, body_size: message.body_size,
flags: message.flags, flags: message.flags,
unused: message.unused, unused: message.unused,
command: message.command, command: message.command,
error_code: message.errorCode, error_code: message.error_code,
key: message.key, key: message.key,
}); });
...@@ -32,15 +32,15 @@ QUnit.test('Pack header with checksum', function (assert) { ...@@ -32,15 +32,15 @@ QUnit.test('Pack header with checksum', function (assert) {
const message = new Message(0x00030001, 4, 0, 0x14, 0, generateACPHeaderKey('testing')); const message = new Message(0x00030001, 4, 0, 0x14, 0, generateACPHeaderKey('testing'));
const packed = Message.packHeader({ const packed = Message.packHeader({
magic: Message.headerMagic, magic: Message.HEADER_MAGIC,
version: message.version, version: message.version,
header_checksum: 0, header_checksum: 0,
body_checksum: 0, body_checksum: 0,
body_size: message.bodySize, body_size: message.body_size,
flags: message.flags, flags: message.flags,
unused: message.unused, unused: message.unused,
command: message.command, command: message.command,
error_code: message.errorCode, error_code: message.error_code,
key: message.key, key: message.key,
}); });
...@@ -51,15 +51,15 @@ QUnit.test('Pack header with checksum', function (assert) { ...@@ -51,15 +51,15 @@ QUnit.test('Pack header with checksum', function (assert) {
assert.equal(header_checksum, 558240741); assert.equal(header_checksum, 558240741);
const packed_checksum = Message.packHeader({ const packed_checksum = Message.packHeader({
magic: Message.headerMagic, magic: Message.HEADER_MAGIC,
version: message.version, version: message.version,
header_checksum, header_checksum,
body_checksum: 0, body_checksum: 0,
body_size: message.bodySize, body_size: message.body_size,
flags: message.flags, flags: message.flags,
unused: message.unused, unused: message.unused,
command: message.command, command: message.command,
error_code: message.errorCode, error_code: message.error_code,
key: message.key, key: message.key,
}); });
...@@ -76,7 +76,7 @@ QUnit.test('Compose get prop command', function (assert) { ...@@ -76,7 +76,7 @@ QUnit.test('Compose get prop command', function (assert) {
const expected_hex = '61637070000300011bef117b17c301a700000010000000040000000000000014000000000000000000000000000000007a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa50700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064627567000000000000000400000000'; const expected_hex = '61637070000300011bef117b17c301a700000010000000040000000000000014000000000000000000000000000000007a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa50700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064627567000000000000000400000000';
const message = Message.composeGetPropCommand(4, 'testing', payload); const message = Message.composeGetPropCommand(4, 'testing', payload);
const message_hex = Buffer.from(message, 'binary').toString('hex'); const message_hex = Buffer.from(message.composeRawPacket(), 'binary').toString('hex');
assert.equal(message_hex, expected_hex); assert.equal(message_hex, expected_hex);
}); });
...@@ -91,9 +91,9 @@ QUnit.test('Parse raw command', async function (assert) { ...@@ -91,9 +91,9 @@ QUnit.test('Parse raw command', async function (assert) {
assert.equal(message.flags, 4); assert.equal(message.flags, 4);
assert.equal(message.unused, 0); assert.equal(message.unused, 0);
assert.equal(message.command, 20); assert.equal(message.command, 20);
assert.equal(message.errorCode, 0); assert.equal(message.error_code, 0);
assert.equal(message.key, Buffer.from('7a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa507', 'hex').toString('binary')); assert.equal(message.key, Buffer.from('7a5c8b71ad6f324f0cac857d868ab5173e09c835f431657f3c9cb56d969aa507', 'hex').toString('binary'));
assert.equal(message.body, Buffer.from('64627567000000000000000400000000', 'hex').toString('binary')); assert.equal(message.body, Buffer.from('64627567000000000000000400000000', 'hex').toString('binary'));
assert.equal(message.bodySize, 16); assert.equal(message.body_size, 16);
assert.equal(message.bodyChecksum, 398655911); assert.equal(message.body_checksum, 398655911);
}); });
...@@ -9,7 +9,7 @@ const object = CFLBinaryPList.parse(plist.toString('binary')); ...@@ -9,7 +9,7 @@ const object = CFLBinaryPList.parse(plist.toString('binary'));
console.log(object); console.log(object);
const object2 = {something: {lol: ['i', 'd', 'k']}, const object2 = {something: {lol: ['i', 'd', 'k']},
brillant: [1, 2, 3, 4, 5, 0x10000000]}; brillant: [1, 2, 3, 4, 5, 0x10000000]};
const plist2 = CFLBinaryPList.compose(object2); const plist2 = CFLBinaryPList.compose(object2);
console.log(object2, plist2); console.log(object2, plist2);
......