Clean up

parent 5880a6eb
import Session from './session';
import Message from './message';
import Property, {elementHeaderSize} from './property';
// import { CFLBinaryPListParser } from './cflbinary';
import Message, {HEADER_SIZE as MESSAGE_HEADER_SIZE} from './message';
import Property, {HEADER_SIZE as ELEMENT_HEADER_SIZE} from './property';
import CFLBinaryPList from './cflbinary';
export default class Client {
constructor(host, port, password) {
......@@ -30,57 +30,61 @@ export default class Client {
}
receiveMessageHeader() {
return this.receive(Message.headerSize);
return this.receive(MESSAGE_HEADER_SIZE);
}
receivePropertyElementHeader() {
return this.receive(elementHeaderSize);
return this.receive(ELEMENT_HEADER_SIZE);
}
/**
* Gets properties from the AirPort device.
*
* Client: GetProp {...Property}
* Server: GetProp
* Server: ...Property
*/
async getProperties(prop_names) {
let payload = '';
for (let name of prop_names) {
payload += Property.composeRawElement(0, new Property(name));
payload += Property.composeRawElement(0, name instanceof Property ? name : new Property(name));
}
const request = Message.composeGetPropCommand(4, this.password, payload);
await this.send(request);
const reply = await this.receiveMessageHeader();
const replyHeader = await Message.parseRaw(reply);
console.debug('Reply:', {
reply, replyHeader,
});
const reply_header = await Message.parseRaw(reply);
if (replyHeader.errorCode !== 0) {
console.log('Client.getProperties error code:', replyHeader.errorCode);
return [];
if (reply_header.error_code !== 0) {
throw new Error('Error ' . reply_header.error_code);
}
const props = [];
while (true) {
const propHeader = await this.receivePropertyElementHeader();
console.debug('Received property element header:', propHeader);
const {name, flags, size} = await Property.parseRawElementHeader(propHeader);
const prop_header = await this.receivePropertyElementHeader();
console.debug('Received property element header:', prop_header);
const data = await Property.parseRawElementHeader(prop_header);
console.debug(data);
const {name, flags, size} = data;
const propData = await this.receive(size);
const value = await this.receive(size);
if (flags & 1) {
const errorCode = Buffer.from(propData, 'binary').readInt32BE(0);
console.log('error requesting value for property', name, '-', errorCode);
continue;
const error_code = Buffer.from(value, 'binary').readInt32BE(0);
throw new Error('Error requesting value for property "' + name + '": ' + error_code);
}
const prop = new Property(name, propData);
console.debug('prop', prop);
const prop = new Property(name, value);
if (typeof prop.name === 'undefined' && typeof prop.value === 'undefined') {
console.debug('found empty prop end marker');
break;
}
console.debug('Prop', prop);
props.push(prop);
}
......@@ -97,27 +101,26 @@ export default class Client {
const request = Message.composeSetPropCommand(0, this.password, payload);
await this.send(request);
const rawReply = await this.receiveMessageHeader();
const replyHeader = await Message.parseRaw(rawReply);
const raw_reply = await this.receiveMessageHeader();
const reply_header = await Message.parseRaw(raw_reply);
if (replyHeader.errorCode !== 0) {
console.log('set properties error code', replyHeader.errorCode);
if (reply_header.error_code !== 0) {
console.log('set properties error code', reply_header.error_code);
return;
}
const propHeader = await this.receivePropertyElementHeader();
const {name, flags, size} = await Property.parseRawElementHeader(propHeader);
const prop_header = await this.receivePropertyElementHeader();
const {name, flags, size} = await Property.parseRawElementHeader(prop_header);
const propData = await this.receive(size);
const value = await this.receive(size);
if (flags) {
const errorCode = Buffer.from(propData, 'binary').readUInt32BE(0);
console.log('error setting value for property', name, '-', errorCode);
return;
if (flags & 1) {
const error_code = Buffer.from(value, 'binary').readUInt32BE(0);
throw new Error('Error setting value for property "' + name + '": ' + error_code);
}
const prop = new Property(name, propData);
console.debug('prop', prop);
const prop = new Property(name, value);
console.debug('Prop', prop);
if (typeof prop.name === 'undefined' && typeof prop.value === 'undefined') {
console.debug('found empty prop end marker');
......@@ -125,15 +128,15 @@ export default class Client {
}
async getFeatures() {
this.send(Message.composeFeatCommand(0));
const replyHeader = await Message.parseRaw(this.receiveMessageHeader());
const reply = await this.receive(replyHeader.bodySize);
return CFLBinaryPListParser.parse(reply);
await this.send(Message.composeFeatCommand(0));
const reply_header = await Message.parseRaw(await this.receiveMessageHeader());
const reply = await this.receive(reply_header.body_size);
return CFLBinaryPList.parse(reply);
}
async flashPrimary(payload) {
this.send(Message.composeFlashPrimaryCommand(0, this.password, payload));
const replyHeader = await Message.parseRaw(this.receiveMessageHeader());
return await this.receive(replyHeader.bodySize);
const reply_header = await Message.parseRaw(this.receiveMessageHeader());
return await this.receive(reply_header.body_size);
}
}
......@@ -7,15 +7,15 @@ export const ACP_STATIC_KEY = Buffer.from('5b6faf5d9d5b0e1351f2da1de7e8d673', 'h
export function generateACPKeystream(length) {
let key = '';
let key_idx = 0;
let idx = 0;
while (key_idx < length) {
while (idx < length) {
key += String.fromCharCode(
(key_idx + 0x55 & 0xFF) ^
ACP_STATIC_KEY.charCodeAt(key_idx % ACP_STATIC_KEY.length)
(idx + 0x55 & 0xFF) ^
ACP_STATIC_KEY.charCodeAt(idx % ACP_STATIC_KEY.length)
);
key_idx++;
idx++;
}
return key;
......
......@@ -3,7 +3,9 @@
* ACP message composition and parsing
*/
import Property from './property';
import {generateACPKeystream} from './keystream';
import adler32 from 'adler32';
/**
......@@ -13,35 +15,36 @@ import adler32 from 'adler32';
* @return {String} Encrypted password of proper length for the header field
*/
export function generateACPHeaderKey(password) {
const passwordLength = 0x20;
const passwordKey = generateACPKeystream(passwordLength);
const password_length = 0x20;
const password_key = generateACPKeystream(password_length);
const password_buffer = password.substr(0, password_length).padEnd(password_length, '\x00');
let encrypted_password_buffer = '';
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));
for (let i = 0; i < password_length; i++) {
encrypted_password_buffer += String.fromCharCode(password_key.charCodeAt(i) ^ password_buffer.charCodeAt(i));
}
return encryptedPasswordBuffer;
return encrypted_password_buffer;
}
export const headerMagic = 'acpp';
export const headerSize = 128;
export const HEADER_MAGIC = 'acpp';
export const HEADER_SIZE = 128;
export default class Message {
constructor(version, flags, unused, command, errorCode, key, body, bodySize) {
constructor(version, flags, unused, command, error_code, key, body, body_size) {
this.version = version;
this.flags = flags;
this.unused = unused;
this.command = command;
this.errorCode = errorCode;
this.error_code = error_code;
if (typeof body === 'undefined') {
this.bodySize = typeof bodySize !== 'undefined' ? bodySize : -1;
this.bodyChecksum = 1;
this.body_size = typeof body_size !== 'undefined' ? body_size : -1;
this.body_checksum = 1;
} else {
this.bodySize = typeof bodySize !== 'undefined' ? bodySize : body.length;
this.bodyChecksum = adler32.sum(Buffer.from(body, 'binary'));
this.body_size = typeof body_size !== 'undefined' ? body_size : body.length;
this.body_checksum = adler32.sum(Buffer.from(body, 'binary'));
}
this.key = key;
......@@ -49,20 +52,18 @@ export default class Message {
}
toString() {
let s
= 'ACP message:\n'
+ 'Body checksum: ' + this.bodyChecksum
+ 'Body size: ' + this.bodySize
return 'ACP message:\n'
+ 'Body checksum: ' + this.body_checksum
+ 'Body size: ' + this.body_size
+ 'Flags: ' + this.flags
+ 'Unused: ' + this.unused
+ 'Command: ' + this.command
+ 'Error code: ' + this.errorCode
+ 'Error code: ' + this.error_code
+ 'Key: ' + this.key;
return s;
}
static unpackHeader(header_data) {
if (header_data.length !== headerSize) throw new Error('Header data must be 128 characters');
if (header_data.length !== HEADER_SIZE) throw new Error('Header data must be 128 characters');
const buffer = Buffer.from(header_data, 'binary');
......@@ -88,7 +89,7 @@ export default class Message {
buffer.write(magic, 0, 4);
buffer.writeInt32BE(version, 4);
buffer.writeInt32BE(header_checksum, 8);
buffer.writeUInt32BE(header_checksum, 8);
buffer.writeInt32BE(body_checksum, 12);
buffer.writeInt32BE(body_size, 16);
buffer.writeInt32BE(flags, 20);
......@@ -99,22 +100,20 @@ export default class Message {
buffer.write(key, 48, 48 + 32, 'binary');
buffer.write(pad2 || ''.padEnd(48, '\u0000'), 80, 80 + 48, 'binary');
const packed = buffer.toString('binary');
console.log('Packed', packed);
return packed;
return buffer.toString('binary');
}
static async parseRaw(data) {
if (headerSize > data.length) {
throw new Error(`Need to pass at least ${headerSize} bytes`);
if (HEADER_SIZE > data.length) {
throw new Error(`Need to pass at least ${HEADER_SIZE} bytes`);
}
const header_data = data.substr(0, headerSize);
const body_data = data.length > headerSize ? data.substr(headerSize) : undefined;
const header_data = data.substr(0, HEADER_SIZE);
const body_data = data.length > HEADER_SIZE ? data.substr(HEADER_SIZE) : undefined;
const {magic, version, header_checksum, body_checksum, body_size, flags, unused, command, error_code, key, pad1, pad2} = this.unpackHeader(header_data);
if (magic !== headerMagic) {
if (magic !== HEADER_MAGIC) {
throw new Error('Bad header magic');
}
......@@ -130,8 +129,8 @@ export default class Message {
pad1, pad2,
});
const expectedHeaderChecksum = adler32.sum(Buffer.from(tmphdr, 'binary'));
if (header_checksum !== expectedHeaderChecksum) {
const expected_header_checksum = adler32.sum(Buffer.from(tmphdr, 'binary'));
if (header_checksum !== expected_header_checksum) {
throw new Error('Header checksum does not match');
}
......@@ -215,8 +214,8 @@ export default class Message {
return message.composeRawPacket();
}
static composeMessageEx(cls, version, flags, unused, command, errorCode, password, payload, payloadSize) {
const message = new Message(version, flags, unused, command, errorCode, generateACPHeaderKey(password), payload, payloadSize);
static composeMessageEx(version, flags, unused, command, error_code, password, payload, payload_size) {
const message = new Message(version, flags, unused, command, error_code, generateACPHeaderKey(password), payload, payload_size);
return message.composeRawPacket();
}
......@@ -230,26 +229,26 @@ export default class Message {
composeHeader() {
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,
magic: HEADER_MAGIC, version: this.version,
header_checksum: 0, body_checksum: this.body_checksum, body_size: this.body_size,
flags: this.flags, unused: this.unused, command: this.command, error_code: this.error_code, key: this.key,
});
const header = this.constructor.packHeader({
magic: headerMagic, version: this.version,
magic: HEADER_MAGIC, version: this.version,
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,
body_checksum: this.body_checksum, body_size: this.body_size,
flags: this.flags, unused: this.unused, command: this.command, error_code: this.error_code, key: this.key,
});
return header;
}
static get headerMagic() {
return headerMagic;
static get HEADER_MAGIC() {
return HEADER_MAGIC;
}
static get headerSize() {
return headerSize;
static get HEADER_SIZE() {
return HEADER_SIZE;
}
}
import acpProperties from './properties';
import CFLBinaryPList from './cflbinary';
import acp_properties from './properties';
export function generateACPProperties() {
const props = [];
for (let prop of acpProperties) {
const [name, type, description, validate] = prop;
for (let prop of acp_properties) {
const [name, type, description, validator] = prop;
if (name.length !== 4) throw new Error('Bad name in ACP properties list: ' + name);
......@@ -14,7 +15,7 @@ export function generateACPProperties() {
if (!description) throw new Error('Missing description in ACP properties list for name: ' + name);
props.push({name, type, description, validate});
props.push({name, type, description, validator});
}
return props;
......@@ -22,7 +23,7 @@ export function generateACPProperties() {
export const props = generateACPProperties();
export const elementHeaderSize = 12;
export const HEADER_SIZE = 12;
export default class Property {
constructor(name, value) {
......@@ -36,19 +37,15 @@ export default class Property {
}
if (value) {
const propType = this.constructor.getPropertyInfoString(name, 'type');
const initHandlerName = `__init_${propType}`;
const prop_type = this.constructor.getPropertyInfoString(name, 'type');
const init_handler_name = `__init_${prop_type}`;
if (!this[initHandlerName]) throw new Error(`Missing handler for ${propType} property type`);
if (!this[init_handler_name]) throw new Error(`Missing handler for ${prop_type} property type`);
try {
value = this[initHandlerName](value);
} catch (err) {
throw new Error(JSON.stringify(err, null, 4) + ' provided for ' + propType + ' property type ' + value);
}
value = this[init_handler_name](value);
const validate = this.constructor.getPropertyInfoString(name, 'validate');
if (validate && !validate(value)) {
const validator = this.constructor.getPropertyInfoString(name, 'validator');
if (validator && !validator(value, name)) {
throw new Error('Invalid value passed to validator for property ' + name + ' - type: ' + typeof value);
}
}
......@@ -61,7 +58,7 @@ export default class Property {
if (typeof value === 'number') {
return value;
} else if (typeof value === 'string') {
return Buffer.from(value, 'binary').readUInt32BE(0);
return Buffer.from(value, 'binary').readUIntBE(0, value.length);
} else {
throw new Error('Invalid number value: ' + value);
}
......@@ -144,24 +141,29 @@ export default class Property {
__format_mac(value) {
const mac_bytes = [];
value = Buffer.from(value, 'binary').toString('hex');
for (let i = 0; i < 6; i++) {
mac_bytes.push(value.substr(i, 1));
mac_bytes.push(value.substr(i, 2));
}
return implode(':', mac_bytes);
return mac_bytes.join(':');
}
__format_bin(value) {
return value.toString();
return Buffer.from(value, 'binary').toString('hex');
}
__format_cfb(value) {
return value.toString();
return CFLBinaryPList.parse(value);
}
__format_log(value) {
return value.toString();
return value.split('\x00').map(line => line.trim() + '\n').join('');
}
__format_str(value) {
return Buffer.from(value, 'binary').toString('utf8');
}
toString() {
......@@ -192,9 +194,9 @@ export default class Property {
static async parseRawElement(data) {
// eslint-disable-next-line no-unused-vars
const {name, flags, size} = await this.parseRawElementHeader(data.substr(0, elementHeaderSize));
const {name, flags, size} = await this.parseRawElementHeader(data.substr(0, HEADER_SIZE));
// TODO: handle flags
return new this(name, data.substr(elementHeaderSize));
return new this(name, data.substr(HEADER_SIZE));
}
static async parseRawElementHeader(data) {
......@@ -238,7 +240,7 @@ export default class Property {
}
static unpackHeader(header_data) {
if (header_data.length !== elementHeaderSize) {
if (header_data.length !== HEADER_SIZE) {
throw new Error('Header data must be 12 characters');
}
......
// import Encryption from './encryption';
// import {ClientEncryption, ServerEncryption} from './encryption';
import net from 'net';
export default class Session {
......@@ -12,9 +13,7 @@ export default class Session {
this.buffer = '';
this.reading = 0;
this.encryptionContext = undefined;
this.encryptionMethod = undefined;
this.decryptionMethod = undefined;
this.encryption = undefined;
}
connect(_timeout = 10000) {
......@@ -48,6 +47,7 @@ export default class Session {
if (!this.socket) return;
this.socket.end();
return new Promise((resolve, reject) => {
this.socket.on('close', resolve);
});
......@@ -56,13 +56,7 @@ export default class Session {
async sendAndReceive(data, size, timeout = 10000) {
await this.send(data);
data = await this.receiveSize(size, timeout);
if (this.decryptionMethod) {
data = this.decryptionMethod(data);
}
return data;
return await this.receive(size, timeout);
}
send(data) {
......@@ -70,8 +64,8 @@ export default class Session {
data = Buffer.from(data, 'binary');
}
if (this.encryptionMethod) {
data = this.encryptionMethod(data);
if (this.encryption) {
data = this.encryption.encrypt(data);
}
if (!this.socket) return;
......@@ -143,28 +137,18 @@ export default class Session {
async receive(size, timeout = 10000) {
let data = await this.receiveSize(size, timeout);
if (this.decryptionMethod) {
data = this.decryptionMethod(data);
if (this.encryption) {
data = this.encryption.decrypt(data);
}
return data;
}
}
export class ClientSession extends Session {
enableEncryption(key, client_iv, server_iv) {
this.encryptionContext = new Encryption(key, client_iv, server_iv);
this.encryptionMethod = this.encryptionContext.clientEncrypt;
this.decryptionMethod = this.encryptionContext.serverDecrypt;
this.encryption_context = new ClientEncryption(key, client_iv, server_iv);
}
}
export class ServerSession extends Session {
enableEncryption(key, client_iv, server_iv) {
this.encryptionContext = new Encryption(key, client_iv, server_iv);
this.encryptionMethod = this.encryptionContext.serverEncrypt;
this.decryptionMethod = this.encryptionContext.clientDecrypt;
enableServerEncryption(key, client_iv, server_iv) {
this.encryption_context = new ServerEncryption(key, client_iv, server_iv);
}
}
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