1
0
mirror of https://github.com/Ylianst/MeshAgent synced 2026-01-04 09:33:30 +00:00

Much improved, better stability, lots of fixes

This commit is contained in:
Ylian Saint-Hilaire
2018-01-12 11:50:04 -08:00
parent becf71557f
commit 508646044e
69 changed files with 11803 additions and 4088 deletions

281
Debug/amt_heci.js Normal file
View File

@@ -0,0 +1,281 @@
var Q = require('queue');
function amt_heci() {
var emitterUtils = require('events').inherits(this);
emitterUtils.createEvent('error');
emitterUtils.createEvent('connect');
var heci = require('heci');
this._amt = heci.create();
this._amt.BiosVersionLen = 65;
this._amt.UnicodeStringLen = 20;
this._amt.rq = new Q();
this._amt.Parent = this;
this._amt.on('error', function (e) { this.Parent.emit('error', e); });
this._amt.on('connect', function () {
this.Parent.emit('connect');
this.on('data', function (chunk) {
//console.log("Received: " + chunk.length + " bytes");
var header = this.Parent.getCommand(chunk);
//console.log("CMD = " + header.Command + " (Status: " + header.Status + ") Response = " + header.IsResponse);
var user = this.rq.deQueue();
var params = user.optional;
var callback = user.func;
params.unshift(header);
callback.apply(this.Parent, params);
});
});
this._amt.connect(heci.GUIDS.AMT, { noPipeline: 1 });
this.getCommand = function (chunk) {
var command = chunk.length == 0 ? (this._amt.rq.peekQueue().cmd | 0x800000) : chunk.readUInt32LE(4);
var ret = { IsResponse: (command & 0x800000) == 0x800000 ? true : false, Command: (command & 0x7FFFFF), Status: chunk.length != 0 ? chunk.readUInt32LE(12) : -1, Data: chunk.length != 0 ? chunk.slice(16) : null };
return (ret);
};
this.sendCommand = function () {
if (arguments.length < 3 || typeof (arguments[0]) != 'number' || typeof (arguments[1]) != 'object' || typeof (arguments[2]) != 'function') { throw ('invalid parameters'); }
var args = [];
for (var i = 3; i < arguments.length; ++i) { args.push(arguments[i]); }
this._amt.rq.enQueue({ cmd: arguments[0], func: arguments[2], optional: args });
var header = Buffer.from('010100000000000000000000', 'hex');
header.writeUInt32LE(arguments[0] | 0x04000000, 4);
header.writeUInt32LE(arguments[1] == null ? 0 : arguments[1].length, 8);
this._amt.write(arguments[1] == null ? header : Buffer.concat([header, arguments[1]]));
}
this.getVersion = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(26, null, function (header, fn, opt) {
if (header.Status == 0) {
var i, CodeVersion = header.Data, val = { BiosVersion: CodeVersion.slice(0, this._amt.BiosVersionLen), Versions: [] }, v = CodeVersion.slice(this._amt.BiosVersionLen + 4);
for (i = 0; i < CodeVersion.readUInt32LE(this._amt.BiosVersionLen) ; ++i) {
val.Versions[i] = { Description: v.slice(2, v.readUInt16LE(0) + 2).toString(), Version: v.slice(4 + this._amt.UnicodeStringLen, 4 + this._amt.UnicodeStringLen + v.readUInt16LE(2 + this._amt.UnicodeStringLen)).toString() };
v = v.slice(4 + (2 * this._amt.UnicodeStringLen));
}
opt.unshift(val);
} else {
opt.unshift(null);
}
fn.apply(this, opt);
}, callback, optional);
};
this.getProvisioningState = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(17, null, function (header, fn, opt) {
if (header.Status == 0) {
var result = {};
result.state = header.Data.readUInt32LE(0);
if (result.state < 3) { result.stateStr = ["PRE", "IN", "POST"][result.state]; }
opt.unshift(result);
} else {
opt.unshift(null);
}
fn.apply(this, opt);
}, callback, optional);
};
this.getProvisioningMode = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(8, null, function (header, fn, opt) {
if (header.Status == 0) {
var result = {};
result.mode = header.Data.readUInt32LE(0);
if (result.mode < 4) { result.modeStr = ["NONE", "ENTERPRISE", "SMALL_BUSINESS", "REMOTE_ASSISTANCE"][result.mode]; }
result.legacy = header.Data.readUInt32LE(4) == 0 ? false : true;
opt.unshift(result);
} else {
opt.unshift(null);
}
fn.apply(this, opt);
}, callback, optional);
};
this.getEHBCState = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(132, null, function (header, fn, opt) {
if (header.Status == 0) {
opt.unshift({ EHBC: header.Data.readUInt32LE(0) != 0 });
} else {
opt.unshift(null);
}
fn.apply(this, opt);
}, callback, optional);
};
this.getControlMode = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(107, null, function (header, fn, opt) {
if (header.Status == 0) {
var result = {};
result.controlMode = header.Data.readUInt32LE(0);
if (result.controlMode < 3) { result.controlModeStr = ["NONE_RPAT", "CLIENT", "ADMIN", "REMOTE_ASSISTANCE"][result.controlMode]; }
opt.unshift(result);
} else {
opt.unshift(null);
}
fn.apply(this, opt);
}, callback, optional);
};
this.getMACAddresses = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(37, null, function (header, fn, opt) {
if (header.Status == 0) {
opt.unshift({ DedicatedMAC: header.Data.slice(0, 6).toString('hex:'), HostMAC: header.Data.slice(6, 12).toString('hex:') });
} else { opt.unshift({ DedicatedMAC: null, HostMAC: null }); }
fn.apply(this, opt);
}, callback, optional);
};
this.getDnsSuffix = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(54, null, function (header, fn, opt) {
if (header.Status == 0) {
var resultLen = header.Data.readUInt16LE(0);
if (resultLen > 0) { opt.unshift(header.Data.slice(2, 2 + resultLen).toString()); } else { opt.unshift(null); }
} else {
opt.unshift(null);
}
fn.apply(this, opt);
}, callback, optional);
};
this.getHashHandles = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(0x2C, null, function (header, fn, opt) {
var result = [];
if (header.Status == 0) {
var resultLen = header.Data.readUInt32LE(0);
for (var i = 0; i < resultLen; ++i) {
result.push(header.Data.readUInt32LE(4 + (4 * i)));
}
}
opt.unshift(result);
fn.apply(this, opt);
}, callback, optional);
};
this.getCertHashEntry = function (handle, callback) {
var optional = [];
for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
var data = new Buffer(4);
data.writeUInt32LE(handle, 0);
this.sendCommand(0x2D, data, function (header, fn, opt) {
if (header.Status == 0) {
var result = {};
result.isDefault = header.Data.readUInt32LE(0);
result.isActive = header.Data.readUInt32LE(4);
result.hashAlgorithm = header.Data.readUInt8(72);
if (result.hashAlgorithm < 4) {
result.hashAlgorithmStr = ["MD5", "SHA1", "SHA256", "SHA512"][result.hashAlgorithm];
result.hashAlgorithmSize = [16, 20, 32, 64][result.hashAlgorithm];
result.certificateHash = header.Data.slice(8, 8 + result.hashAlgorithmSize).toString('hex');
}
result.name = header.Data.slice(73 + 2, 73 + 2 + header.Data.readUInt16LE(73)).toString();
opt.unshift(result);
} else {
opt.unshift(null);
}
fn.apply(this, opt);
}, callback, optional);
};
this.getCertHashEntries = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.getHashHandles(function (handles, fn, opt) {
var entries = [];
this.getCertHashEntry(handles.shift(), this._getHashEntrySink, fn, opt, entries, handles);
}, callback, optional);
};
this._getHashEntrySink = function (result, fn, opt, entries, handles) {
entries.push(result);
if (handles.length > 0) {
this.getCertHashEntry(handles.shift(), this._getHashEntrySink, fn, opt, entries, handles);
} else {
opt.unshift(entries);
fn.apply(this, opt);
}
}
this.getLocalSystemAccount = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(103, Buffer.alloc(40), function (header, fn, opt) {
if (header.Data.length == 68) { opt.unshift({ user: header.Data.slice(0, 34).toString(), pass: header.Data.slice(34, 67).toString(), raw: header.Data }); } else { opt.unshift(null); }
fn.apply(this, opt);
}, callback, optional);
}
this.unprovision = function (mode, callback) {
var optional = [];
for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
var data = new Buffer(4);
data.writeUInt32LE(mode, 0);
this.sendCommand(16, data, function (header, fn, opt) {
opt.unshift(header.Status);
fn.apply(this, opt);
}, callback, optional);
}
this.startConfiguration = function () {
var optional = [];
for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(0x29, data, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
}
this.stopConfiguration = function () {
var optional = [];
for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(0x5E, data, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
}
this.openUserInitiatedConnection = function () {
var optional = [];
for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(0x44, data, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
}
this.closeUserInitiatedConnection = function () {
var optional = [];
for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(0x45, data, function (header, fn, opt) { opt.unshift(header.Status); fn.apply(this, opt); }, callback, optional);
}
this.getRemoteAccessConnectionStatus = function () {
var optional = [];
for (var i = 2; i < arguments.length; ++i) { optional.push(arguments[i]); }
this.sendCommand(0x46, data, function (header, fn, opt) {
if (header.Status == 0) {
var hostname = v.slice(14, header.Data.readUInt16LE(12) + 14).toString()
opt.unshift({ status: header.Status, networkStatus: header.Data.readUInt32LE(0), remoteAccessStatus: header.Data.readUInt32LE(4), remoteAccessTrigger: header.Data.readUInt32LE(8), mpsHostname: hostname, raw: header.Data });
} else {
opt.unshift({ status: header.Status });
}
fn.apply(this, opt);
}, callback, optional);
}
this.getProtocolVersion = function (callback) {
var optional = [];
for (var i = 1; i < arguments.length; ++i) { opt.push(arguments[i]); }
heci.doIoctl(heci.IOCTL.HECI_VERSION, Buffer.alloc(5), Buffer.alloc(5), function (status, buffer, self, fn, opt) {
if (status == 0) {
var result = buffer.readUInt8(0).toString() + '.' + buffer.readUInt8(1).toString() + '.' + buffer.readUInt8(2).toString() + '.' + buffer.readUInt16BE(3).toString();
opt.unshift(result);
fn.apply(self, opt);
}
else {
opt.unshift(null);
fn.apply(self, opt);
}
}, this, callback, optional);
}
}
module.exports = amt_heci;

61
Debug/amt_test.js Normal file
View File

@@ -0,0 +1,61 @@
var amt_heci = require('amt_heci');
var amt = new amt_heci();
amt.getProtocolVersion(function (result)
{
console.log('protocol version = ' + result);
});
amt.on('error', function (e) { console.log(e);});
amt.on('connect', function()
{
console.log("Connected!");
this.getVersion(OnVersion);
this.getProvisioningState(OnProvisioningState);
this.getProvisioningMode(OnProvisioningMode);
this.getEHBCState(OnEHBC);
this.getControlMode(OnEHBC);
this.getMACAddresses(OnEHBC);
this.getDnsSuffix(OnDns);
this.getCertHashEntries(OnHashEntries);
//this.getHashHandles(OnGetHashHandles);
});
function OnGetHashHandles(handles)
{
console.log(handles.length + " HashHandles");
for (var i = 0; i < handles.length; ++i)
{
amt.getCertHashEntry(handles[i], OnEHBC);
}
}
function OnHashEntries(entries)
{
for(var i=0;i<entries.length;++i)
{
console.log(entries[i]);
}
}
function OnDns(dns)
{
console.log("Dns Suffix = " + dns);
}
function OnVersion(val)
{
console.log("Bios Version = " + val.BiosVersion.toString());
for(var version in val.Versions)
{
console.log(" " + val.Versions[version].Description + " = " + val.Versions[version].Version);
}
}
function OnProvisioningState(state)
{
console.log("ProvisioningState = " + state);
}
function OnProvisioningMode(result)
{
console.log("ProvisioningMode = " + result.mode + " [Legacy = " + result.legacy + "]");
}
function OnEHBC(result)
{
console.log(result);
}

View File

@@ -5,6 +5,39 @@ var js;
var sz = new Buffer(8);
var exeLen = 0;
var i;
var dependency = [];
var addOn = null;
for (i = 1; i < process.argv.length; ++i)
{
if(process.argv[i].startsWith('-i'))
{
try
{
dependency.push({ name:process.argv[i].slice(2,process.argv[i].indexOf('.js')), base64: fs.readFileSync(process.argv[i].slice(2)).toString('base64') });
process._argv.splice(i, 1);
i = 0;
}
catch(e)
{
console.log(e);
process.exit();
}
}
}
if (dependency.length > 0)
{
console.log("\nIntegrating Dependencies:")
addOn = "";
for(i=0;i<dependency.length;++i)
{
addOn += ("addModule('" + dependency[i].name + "', Buffer.from('" + dependency[i].base64 + "', 'base64'));\n");
console.log(" " + dependency[i].name);
}
console.log("");
}
if (process.argv0.endsWith('.js'))
{
console.log("Non-integrated executable");
@@ -47,6 +80,7 @@ else
console.log("Original Binary Size: " + exeLen);
}
if (addOn != null) { js = Buffer.concat([Buffer.from(addOn), js]); }
console.log("JavaScript Length: " + js.length);
w.write(exe.slice(0, exeLen), OnWroteExe);

36
Debug/heci.js Normal file
View File

@@ -0,0 +1,36 @@
var heci = require('heci');
var amt = null;
console.log("Starting HECI test...");
console.log("LME GUID = " + heci.GUIDS.LME.toString('hex'));
console.log("AMT GUID = " + heci.GUIDS.AMT.toString('hex'));
heci.doIoctl(heci.IOCTL.HECI_VERSION, null, new Buffer(16), OnVersion);
function OnVersion(status, buffer, arg)
{
if(status == 0)
{
console.log("HECI Driver Version = " + buffer[0] + "." + buffer[1]);
console.log("Attempting to create AMT/HECI connection");
amt = heci.create();
amt.connect(heci.GUIDS.AMT);
amt.on('connect', OnAMT);
amt.on('error', function (e) { console.log(e); });
}
else {
console.log("Could not determine HECI Driver Version");
}
}
function OnAMT()
{
console.log('AMT Connected');
amt.on('data', OnAMTData);
var header = Buffer.from('010100001A00000400000000', 'hex');
amt.write(header);
}
function OnAMTData(chunk)
{
console.log('Received ' + chunk.length + ' bytes of AMT Data');
}

56
Debug/httptest.js Normal file
View File

@@ -0,0 +1,56 @@
var http = require('http');
var https = require('https');
var WS;
console.log("Starting HTTP (Rewrite) Test");
var cert = https.generateCertificate('test');
var server = https.createServer();
server.on('request', function (imsg, rsp)
{
console.log('Received inbound request: ' + imsg.method + ' ' + imsg.url);
rsp.writeHead(200, 'OK', {'Content-Length': 0});
});
server.on('upgrade', function (imsg, sck, head)
{
console.log('Server On Upgrade');
WS = sck.upgradeWebSocket();
WS.on('pong', function () { console.log('Server received PONG'); WS.write('this is test'); WS.write(Buffer.from("This is a good day")); WS.end();});
WS.on('data', function (chunk) { console.log('Server received: ' + chunk); });
WS.ping();
});
server.listen({ port: 9095, pfx: cert, passphrase: 'test' });
//var req = http.get("http://127.0.0.1:9095/test.html");
//var req = http.get("ws://127.0.0.1:9095/test.html");
var req = http.request({ protocol: 'wss:', host: '127.0.0.1', port: 9095, method: 'GET', path: '/test.html', rejectUnauthorized: false})
req.end();
var req2 = http.request({ protocol: 'https:', host: '127.0.0.1', port: 9095, method: 'GET', path: '/test.html', rejectUnauthorized: false })
req2.end();
req.on('upgrade', function (imsg, sck, head)
{
console.log('client upgraded to WebSocket');
sck.on('ping', function () { console.log('Client received ping'); this.write('Client says hello');});
sck.on('data', function (chunk) { console.log('client received: ' + chunk, typeof (chunk)); });
sck.on('end', function () { console.log('Client side closed'); });
});
req.on('response', function (imsg)
{
console.log('received response', imsg.statusCode, imsg.statusMessage);
imsg.on('end', function () {
console.log('Done reading IncomingMessageStream');
});
})
req.on('error', function (err) { console.log('error received', err); });
req2.on('response', function (imsg) {
console.log('received response', imsg.statusCode, imsg.statusMessage);
imsg.on('end', function () {
console.log('Done reading IncomingMessageStream');
});
})
req2.on('error', function (err) { console.log('error received', err); });

355
Debug/lme_heci.js Normal file
View File

@@ -0,0 +1,355 @@
var MemoryStream = require('MemoryStream');
var lme_id = 0;
var APF_DISCONNECT = 1;
var APF_SERVICE_REQUEST = 5;
var APF_SERVICE_ACCEPT = 6;
var APF_USERAUTH_REQUEST = 50;
var APF_USERAUTH_FAILURE = 51;
var APF_USERAUTH_SUCCESS = 52;
var APF_GLOBAL_REQUEST = 80;
var APF_REQUEST_SUCCESS = 81;
var APF_REQUEST_FAILURE = 82;
var APF_CHANNEL_OPEN = 90;
var APF_CHANNEL_OPEN_CONFIRMATION = 91;
var APF_CHANNEL_OPEN_FAILURE = 92;
var APF_CHANNEL_WINDOW_ADJUST = 93;
var APF_CHANNEL_DATA = 94;
var APF_CHANNEL_CLOSE = 97;
var APF_PROTOCOLVERSION = 192;
function lme_object()
{
this.ourId = ++lme_id;
this.amtId = -1;
this.LME_CHANNEL_STATUS = 'LME_CS_FREE';
this.txWindow = 0;
this.rxWindow = 0;
this.localPort = 0;
this.errorCount = 0;
}
function stream_bufferedWrite()
{
var emitterUtils = require('events').inherits(this);
this.buffer = [];
this._readCheckImmediate = undefined;
// Writable Events
emitterUtils.createEvent('close');
emitterUtils.createEvent('drain');
emitterUtils.createEvent('error');
emitterUtils.createEvent('finish');
emitterUtils.createEvent('pipe');
emitterUtils.createEvent('unpipe');
// Readable Events
emitterUtils.createEvent('readable');
this.isEmpty = function ()
{
return (this.buffer.length == 0);
};
this.isWaiting = function ()
{
return (this._readCheckImmediate == undefined);
};
this.write = function (chunk)
{
for (var args in arguments)
{
if (typeof (arguments[args]) == 'function') { this.once('drain', arguments[args]); break; }
}
var tmp = Buffer.alloc(chunk.length);
chunk.copy(tmp);
this.buffer.push({ offset: 0, data: tmp });
this.emit('readable');
return (this.buffer.length == 0 ? true : false);
};
this.read = function ()
{
var size = arguments.length == 0 ? undefined : arguments[0];
var bytesRead = 0;
var list = [];
while((size == undefined || bytesRead < size) && this.buffer.length > 0)
{
var len = this.buffer[0].data.length - this.buffer[0].offset;
var offset = this.buffer[0].offset;
if(len > (size - bytesRead))
{
// Only reading a subset
list.push(this.buffer[0].data.slice(offset, offset + size - bytesRead));
this.buffer[0].offset += (size - bytesRead);
bytesRead += (size - bytesRead);
}
else
{
// Reading the entire thing
list.push(this.buffer[0].data.slice(offset));
bytesRead += len;
this.buffer.shift();
}
}
this._readCheckImmediate = setImmediate(function (buffered)
{
buffered._readCheckImmediate = undefined;
if(buffered.buffer.length == 0)
{
// drained
buffered.emit('drain');
}
else
{
// not drained
buffered.emit('readable');
}
}, this);
return (Buffer.concat(list));
};
}
function lme_heci()
{
var emitterUtils = require('events').inherits(this);
emitterUtils.createEvent('error');
emitterUtils.createEvent('connect');
var heci = require('heci');
this.INITIAL_RXWINDOW_SIZE = 4096;
this._LME = heci.create();
this._LME.LMS = this;
this._LME.on('error', function (e) { this.Parent.emit('error', e); });
this._LME.on('connect', function ()
{
console.log('emitting connect');
this.LMS.emit('connect');
this.on('data', function (chunk)
{
// this = HECI
var cmd = chunk.readUInt8(0);
switch(cmd)
{
default:
//console.log('Received ' + chunk.length + ' bytes of data for LMS');
//console.log('Command = ' + cmd);
break;
case APF_SERVICE_REQUEST:
var nameLen = chunk.readUInt32BE(1);
var name = chunk.slice(5, nameLen + 5);
//console.log("Service Request for: " + name);
if (name == 'pfwd@amt.intel.com' || name == 'auth@amt.intel.com')
{
var outBuffer = Buffer.alloc(5 + nameLen);
outBuffer.writeUInt8(6, 0);
outBuffer.writeUInt32BE(nameLen, 1);
outBuffer.write(name.toString(), 5);
this.write(outBuffer);
//console.log('Answering APF_SERVICE_REQUEST');
}
else
{
//console.log('UNKNOWN APF_SERVICE_REQUEST');
}
break;
case APF_GLOBAL_REQUEST:
var nameLen = chunk.readUInt32BE(1);
var name = chunk.slice(5, nameLen + 5).toString();
switch(name)
{
case 'tcpip-forward':
var len = chunk.readUInt32BE(nameLen + 6);
var port = chunk.readUInt32BE(nameLen + 10 + len);
//console.log("[" + chunk.length + "/" + len + "] APF_GLOBAL_REQUEST for: " + name + " on port " + port);
if (this[name] == undefined)
{
this[name] = {};
}
this[name][port] = require('net').createServer();
this[name][port].HECI = this;
this[name][port].listen({ port: port });
this[name][port].on('connection', function (socket)
{
//console.log('New [' + socket.remoteFamily + '] TCP Connection on: ' + socket.remoteAddress + ' :' + socket.localPort);
this.HECI.LMS.bindDuplexStream(socket, socket.remoteFamily, socket.localPort);
});
var outBuffer = Buffer.alloc(5);
outBuffer.writeUInt8(81, 0);
outBuffer.writeUInt32BE(port, 1);
this.write(outBuffer);
break;
case 'cancel-tcpip-forward':
break;
case 'udp-send-to@amt.intel.com':
break;
default:
//console.log("Unknown APF_GLOBAL_REQUEST for: " + name);
break;
}
break;
case APF_CHANNEL_OPEN_CONFIRMATION:
var rChannel = chunk.readUInt32BE(1);
var sChannel = chunk.readUInt32BE(5);
var wSize = chunk.readUInt32BE(9);
//console.log('rChannel/' + rChannel + ', sChannel/' + sChannel + ', wSize/' + wSize);
if (this.sockets[rChannel] != undefined)
{
this.sockets[rChannel].lme.amtId = sChannel;
this.sockets[rChannel].lme.rxWindow = wSize;
this.sockets[rChannel].lme.txWindow = wSize;
this.sockets[rChannel].lme.LME_CHANNEL_STATUS = 'LME_CS_CONNECTED';
//console.log('LME_CS_CONNECTED');
this.sockets[rChannel].bufferedStream = new stream_bufferedWrite();
this.sockets[rChannel].bufferedStream.socket = this.sockets[rChannel];
this.sockets[rChannel].bufferedStream.on('readable', function ()
{
if(this.socket.lme.txWindow > 0)
{
var buffer = this.read(this.socket.lme.txWindow);
var packet = Buffer.alloc(9 + buffer.length);
packet.writeUInt8(APF_CHANNEL_DATA, 0);
packet.writeUInt32BE(this.socket.lme.amtId, 1);
packet.writeUInt32BE(buffer.length, 5);
buffer.copy(packet, 9);
this.socket.lme.txWindow -= buffer.length;
this.socket.HECI.write(packet);
}
});
this.sockets[rChannel].bufferedStream.on('drain', function ()
{
this.socket.resume();
});
this.sockets[rChannel].on('data', function (chunk)
{
if (!this.bufferedStream.write(chunk)) { this.pause(); }
});
this.sockets[rChannel].on('end', function ()
{
var outBuffer = Buffer.alloc(5);
outBuffer.writeUInt8(APF_CHANNEL_CLOSE, 0);
outBuffer.writeUInt32BE(this.lme.amtId, 1);
this.HECI.write(outBuffer);
});
this.sockets[rChannel].resume();
}
break;
case APF_PROTOCOLVERSION:
var major = chunk.readUInt32BE(1);
var minor = chunk.readUInt32BE(5);
var reason = chunk.readUInt32BE(9);
var outBuffer = Buffer.alloc(93);
outBuffer.writeUInt8(192, 0);
outBuffer.writeUInt32BE(1, 1);
outBuffer.writeUInt32BE(0, 5);
outBuffer.writeUInt32BE(reason, 9);
//console.log('Answering PROTOCOL_VERSION');
this.write(outBuffer);
break;
case APF_CHANNEL_WINDOW_ADJUST:
var rChannelId = chunk.readUInt32BE(1);
var bytesToAdd = chunk.readUInt32BE(5);
if (this.sockets[rChannelId] != undefined)
{
this.sockets[rChannelId].lme.txWindow += bytesToAdd;
if (!this.sockets[rChannelId].bufferedStream.isEmpty() && this.sockets[rChannelId].bufferedStream.isWaiting())
{
this.sockets[rChannelId].bufferedStream.emit('readable');
}
}
else
{
//console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_WINDOW_ADJUST');
}
break;
case APF_CHANNEL_DATA:
var rChannelId = chunk.readUInt32BE(1);
var dataLen = chunk.readUInt32BE(5);
var data = chunk.slice(9, 9 + dataLen);
if (this.sockets[rChannelId] != undefined)
{
this.sockets[rChannelId].pendingBytes.push(data.length);
this.sockets[rChannelId].write(data, function ()
{
var written = this.pendingBytes.shift();
var outBuffer = Buffer.alloc(9);
outBuffer.writeUInt8(APF_CHANNEL_WINDOW_ADJUST, 0);
outBuffer.writeUInt32BE(this.lme.amtId, 1);
outBuffer.writeUInt32BE(written, 5);
this.HECI.write(outBuffer);
});
}
else
{
//console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_DATA');
}
break;
case APF_CHANNEL_CLOSE:
var rChannelId = chunk.readUInt32BE(1);
if (this.sockets[rChannelId] != undefined)
{
this.sockets[rChannelId].end();
var amtId = this.sockets[rChannelId].lme.amtId;
var buffer = Buffer.alloc(5);
delete this.sockets[rChannelId];
buffer.writeUInt8(APF_CHANNEL_CLOSE, 0);
buffer.writeUInt32BE(amtId, 1);
this.write(buffer);
}
else
{
//console.log('Unknown Recipient ID/' + rChannelId + ' for APF_CHANNEL_CLOSE');
}
break;
}
});
});
this.bindDuplexStream = function (duplexStream, remoteFamily, localPort)
{
var socket = duplexStream;
//console.log('New [' + remoteFamily + '] Virtual Connection/' + socket.localPort);
socket.pendingBytes = [];
socket.HECI = this._LME;
socket.LMS = this;
socket.lme = new lme_object();
socket.lme.Socket = socket;
var buffer = new MemoryStream();
buffer.writeUInt8(0x5A);
buffer.writeUInt32BE(15);
buffer.write('forwarded-tcpip');
buffer.writeUInt32BE(socket.lme.ourId);
buffer.writeUInt32BE(this.INITIAL_RXWINDOW_SIZE);
buffer.writeUInt32BE(0xFFFFFFFF);
for (var i = 0; i < 2; ++i)
{
if (remoteFamily == 'IPv6')
{
buffer.writeUInt32BE(3);
buffer.write('::1');
}
else
{
buffer.writeUInt32BE(9);
buffer.write('127.0.0.1');
}
buffer.writeUInt32BE(localPort);
}
this._LME.write(buffer.buffer);
if (this._LME.sockets == undefined) { this._LME.sockets = {}; }
this._LME.sockets[socket.lme.ourId] = socket;
socket.pause();
};
this._LME.connect(heci.GUIDS.LME, { noPipeline: 0 });
}
module.exports = lme_heci;

37
Debug/lme_test.js Normal file

File diff suppressed because one or more lines are too long

18
Debug/newstringtest.js Normal file
View File

@@ -0,0 +1,18 @@
var http = require('http');
console.log('starting client test');
console.log('Sending Request');
var req = http.request({host: '127.0.0.1', port: 9093, protocol: 'ws:'});
req.on('upgrade', function (res, sk, h)
{
sk.on('ping', function () { console.log('received ping'); });
sk.on('pong', function () { console.log('received pong'); });
this.websocket = sk;
console.log("Upgraded to WebSocket!"); sk.write(JSON.stringify({ a: 'hello' }));
});
//req.end();

205
Debug/parseXml.js Normal file
View File

@@ -0,0 +1,205 @@
Object.defineProperty(Array.prototype, "peek",
{
value: function ()
{
return (this.length > 0 ? this[this.length - 1] : undefined);
}
});
function _treeBuilder()
{
this.tree = [];
this.push = function (element)
{
this.tree.push(element);
};
this.pop = function ()
{
var element = this.tree.pop();
if(this.tree.length>0)
{
this.tree.peek().childNodes.push(element);
}
return (element);
};
this.peek = function()
{
return (this.tree.peek());
}
this.addNamespace = function(prefix, namespace)
{
this.tree.peek().nsTable[prefix] = namespace;
if(this.tree.peek().attributes.length > 0)
{
for(var i = 0; i<this.tree.peek().attributes; ++i)
{
var a = this.tree.peek().attributes[i];
if(prefix == '*' && a.name == a.localName)
{
a.namespace = namespace;
}
else if(prefix != '*' && a.name != a.localName)
{
var pfx = a.name.split(':')[0];
if(pfx == prefix)
{
a.namespace = namespace;
}
}
}
}
}
this.getNamespace = function(prefix)
{
for(var i=this.tree.length-1;i>=0;--i)
{
if(this.tree[i].nsTable[prefix] != undefined)
{
return (this.tree[i].nsTable[prefix]);
}
}
return ('undefined');
}
}
// This is a drop-in replacement to _turnToXml() that works without xml parser dependency.
function _turnToXml(text)
{
if (text == null) return null;
return ({ childNodes: [_turnToXmlRec(text)], getElementsByTagName: _getElementsByTagName, getChildElementsByTagName: _getChildElementsByTagName, getElementsByTagNameNS: _getElementsByTagNameNS });
}
function _getElementsByTagNameNS(ns, name)
{
var ret = []; _xmlTraverseAllRec(this.childNodes, function (node)
{
if (node.localName == name && (node.namespace == ns || ns == '*')) { ret.push(node); }
}); return ret;
}
function _getElementsByTagName(name)
{
var ret = []; _xmlTraverseAllRec(this.childNodes, function (node)
{
if (node.localName == name) { ret.push(node); }
}); return ret;
}
function _getChildElementsByTagName(name)
{
var ret = [];
if (this.childNodes != undefined)
{
for (var node in this.childNodes) {
if (this.childNodes[node].localName == name) { ret.push(this.childNodes[node]); }
}
}
return (ret);
}
function _getChildElementsByTagNameNS(ns, name)
{
var ret = [];
if (this.childNodes != undefined)
{
for (var node in this.childNodes)
{
if (this.childNodes[node].localName == name && (ns == '*' || this.childNodes[node].namespace == ns)) { ret.push(this.childNodes[node]); }
}
}
return (ret);
}
function _xmlTraverseAllRec(nodes, func) { for (var i in nodes) { func(nodes[i]); if (nodes[i].childNodes) { _xmlTraverseAllRec(nodes[i].childNodes, func); } } }
function _turnToXmlRec(text)
{
var elementStack = new _treeBuilder();
var lastElement = null;
var x1 = text.split('<'), ret = [], element = null, currentElementName = null;
for (var i in x1)
{
var x2 = x1[i].split('>'), x3 = x2[0].split(' '), elementName = x3[0];
if ((elementName.length > 0) && (elementName[0] != '?'))
{
if (elementName[0] != '/')
{
var localName;
var localname2 = elementName.split(' ')[0].split(':'), localName = (localname2.length > 1) ? localname2[1] : localname2[0];
var attributes = [];
Object.defineProperty(attributes, "get",
{
value: function ()
{
if (arguments.length == 1)
{
for (var a in this) { if (this[a].name == arguments[0]) { return (this[a]); } }
}
else if (arguments.length == 2)
{
for (var a in this) { if (this[a].name == arguments[1] && (arguments[0] == '*' || this[a].namespace == arguments[0])) { return (this[a]); } }
}
else
{
throw ('attributes.get(): Invalid number of parameters');
}
}
});
elementStack.push({ name: elementName, localName: localName, getChildElementsByTagName: _getChildElementsByTagName, getElementsByTagNameNS: _getElementsByTagNameNS, getChildElementsByTagNameNS: _getChildElementsByTagNameNS, attributes: attributes, childNodes: [], nsTable: {} });
// Parse Attributes
if (x3.length > 0)
{
var skip = false;
for (var j in x3)
{
if (x3[j] == '/')
{
// This is an empty Element
elementStack.peek().namespace = elementStack.peek().name == elementStack.peek().localName ? elementStack.getNamespace('*') : elementStack.getNamespace(elementStack.peek().name.substring(0, elementStack.peek().name.indexOf(':')));
elementStack.peek().textContent = '';
lastElement = elementStack.pop();
skip = true;
break;
}
var k = x3[j].indexOf('=');
if (k > 0)
{
var attrName = x3[j].substring(0, k);
var attrValue = x3[j].substring(k + 2, x3[j].length - 1);
var attrNS = elementStack.getNamespace('*');
if (attrName == 'xmlns')
{
elementStack.addNamespace('*', attrValue);
attrNS = attrValue;
}
else if (attrName.startsWith('xmlns:'))
{
elementStack.addNamespace(attrName.substring(6), attrValue);
}
else
{
var ax = attrName.split(':');
if (ax.length == 2) { attrName = ax[1]; attrNS = elementStack.getNamespace(ax[0]); }
}
elementStack.peek().attributes.push({ name: attrName, value: attrValue, namespace: attrNS });
}
}
if (skip) { continue; }
}
elementStack.peek().namespace = elementStack.peek().name == elementStack.peek().localName ? elementStack.getNamespace('*') : elementStack.getNamespace(elementStack.peek().name.substring(0, elementStack.peek().name.indexOf(':')));
if (x2[1]) { elementStack.peek().textContent = x2[1]; }
}
else
{
lastElement = elementStack.pop();
}
}
}
return lastElement;
}
module.exports = _turnToXml;

243
Debug/serviceManager.js Normal file
View File

@@ -0,0 +1,243 @@
function parseServiceStatus(token)
{
var j = {};
var serviceType = token.Deref(0, 4).IntVal;
j.isFileSystemDriver = ((serviceType & 0x00000002) == 0x00000002);
j.isKernelDriver = ((serviceType & 0x00000001) == 0x00000001);
j.isSharedProcess = ((serviceType & 0x00000020) == 0x00000020);
j.isOwnProcess = ((serviceType & 0x00000010) == 0x00000010);
j.isInteractive = ((serviceType & 0x00000100) == 0x00000100);
switch (token.Deref((1 * 4), 4).IntVal)
{
case 0x00000005:
j.state = 'CONTINUE_PENDING';
break;
case 0x00000006:
j.state = 'PAUSE_PENDING';
break;
case 0x00000007:
j.state = 'PAUSED';
break;
case 0x00000004:
j.state = 'RUNNING';
break;
case 0x00000002:
j.state = 'START_PENDING';
break;
case 0x00000003:
j.state = 'STOP_PENDING';
break;
case 0x00000001:
j.state = 'STOPPED';
break;
}
var controlsAccepted = token.Deref((2 * 4), 4).IntVal
j.controlsAccepted = [];
if ((controlsAccepted & 0x00000010) == 0x00000010)
{
j.controlsAccepted.push('SERVICE_CONTROL_NETBINDADD');
j.controlsAccepted.push('SERVICE_CONTROL_NETBINDREMOVE');
j.controlsAccepted.push('SERVICE_CONTROL_NETBINDENABLE');
j.controlsAccepted.push('SERVICE_CONTROL_NETBINDDISABLE');
}
if ((controlsAccepted & 0x00000008) == 0x00000008) { j.controlsAccepted.push('SERVICE_CONTROL_PARAMCHANGE'); }
if ((controlsAccepted & 0x00000002) == 0x00000002) { j.controlsAccepted.push('SERVICE_CONTROL_PAUSE'); j.controlsAccepted.push('SERVICE_CONTROL_CONTINUE'); }
if ((controlsAccepted & 0x00000100) == 0x00000100) { j.controlsAccepted.push('SERVICE_CONTROL_PRESHUTDOWN'); }
if ((controlsAccepted & 0x00000004) == 0x00000004) { j.controlsAccepted.push('SERVICE_CONTROL_SHUTDOWN'); }
if ((controlsAccepted & 0x00000001) == 0x00000001) { j.controlsAccepted.push('SERVICE_CONTROL_STOP'); }
if ((controlsAccepted & 0x00000020) == 0x00000020) { j.controlsAccepted.push('SERVICE_CONTROL_HARDWAREPROFILECHANGE'); }
if ((controlsAccepted & 0x00000040) == 0x00000040) { j.controlsAccepted.push('SERVICE_CONTROL_POWEREVENT'); }
if ((controlsAccepted & 0x00000080) == 0x00000080) { j.controlsAccepted.push('SERVICE_CONTROL_SESSIONCHANGE'); }
j.pid = token.Deref((7 * 4), 4).IntVal
return (j);
}
function serviceManager()
{
this.GM = require('_GenericMarshal');
this.proxy = this.GM.CreateNativeProxy('Advapi32.dll');
this.proxy.CreateMethod('OpenSCManagerA');
this.proxy.CreateMethod('EnumServicesStatusExA');
this.proxy.CreateMethod('OpenServiceA');
this.proxy.CreateMethod('QueryServiceStatusEx');
this.proxy.CreateMethod('ControlService');
this.proxy.CreateMethod('StartServiceA');
this.proxy.CreateMethod('CloseServiceHandle');
this.proxy.CreateMethod('CreateServiceA');
this.proxy.CreateMethod('ChangeServiceConfig2A');
this.proxy.CreateMethod('DeleteService');
this.proxy2 = this.GM.CreateNativeProxy('Kernel32.dll');
this.proxy2.CreateMethod('GetLastError');
this.enumerateService = function()
{
var machineName = this.GM.CreatePointer();
var dbName = this.GM.CreatePointer();
var handle = this.proxy.OpenSCManagerA(0x00, 0x00, 0x0001 | 0x0004);
var bytesNeeded = this.GM.CreateVariable(4);
var servicesReturned = this.GM.CreateVariable(4);
var resumeHandle = this.GM.CreateVariable(4);
//var services = this.proxy.CreateVariable(262144);
var success = this.proxy.EnumServicesStatusExA(handle, 0, 0x00000030, 0x00000003, 0x00, 0x00, bytesNeeded, servicesReturned, resumeHandle, 0x00);
if(bytesNeeded.IntVal <= 0)
{
throw ('error enumerating services');
}
var sz = bytesNeeded.IntVal;
var services = this.GM.CreateVariable(sz);
this.proxy.EnumServicesStatusExA(handle, 0, 0x00000030, 0x00000003, services, sz, bytesNeeded, servicesReturned, resumeHandle, 0x00);
console.log("servicesReturned", servicesReturned.IntVal, 'PtrSize = ' + dbName._size);
var ptrSize = dbName._size;
var blockSize = 36 + (2 * ptrSize);
console.log('blockSize', blockSize);
var retVal = [];
for (var i = 0; i < servicesReturned.IntVal; ++i)
{
var token = services.Deref(i * blockSize, blockSize);
var j = {};
j.name = token.Deref(0, ptrSize).Deref().String;
j.displayName = token.Deref(ptrSize, ptrSize).Deref().String;
j.status = parseServiceStatus(token.Deref(2 * ptrSize, 36));
retVal.push(j);
}
this.proxy.CloseServiceHandle(handle);
return (retVal);
}
this.getService = function(name)
{
var serviceName = this.GM.CreateVariable(name);
var ptr = this.GM.CreatePointer();
var bytesNeeded = this.GM.CreateVariable(ptr._size);
var handle = this.proxy.OpenSCManagerA(0x00, 0x00, 0x0001 | 0x0004 | 0x0020 | 0x0010);
if (handle == 0) { throw ('could not open ServiceManager'); }
var h = this.proxy.OpenServiceA(handle, serviceName, 0x0004 | 0x0020 | 0x0010 | 0x00010000);
if (h != 0)
{
var success = this.proxy.QueryServiceStatusEx(h, 0, 0, 0, bytesNeeded);
var status = this.GM.CreateVariable(bytesNeeded.IntVal);
success = this.proxy.QueryServiceStatusEx(h, 0, status, status._size, bytesNeeded);
if (success != 0)
{
retVal = {};
retVal.status = parseServiceStatus(status);
retVal._scm = handle;
retVal._service = h;
retVal._GM = this.GM;
retVal._proxy = this.proxy;
require('events').inherits(retVal);
retVal.on('~', function () { this._proxy.CloseServiceHandle(this); this._proxy.CloseServiceHandle(this._scm); });
retVal.name = name;
retVal.stop = function()
{
if(this.status.state == 'RUNNING')
{
var newstate = this._GM.CreateVariable(36);
var success = this._proxy.ControlService(this._service, 0x00000001, newstate);
if(success == 0)
{
throw (this.name + '.stop() failed');
}
}
else
{
throw ('cannot call ' + this.name + '.stop(), when current state is: ' + this.status.state);
}
}
retVal.start = function()
{
if(this.status.state == 'STOPPED')
{
var success = this._proxy.StartServiceA(this._service, 0, 0);
if(success == 0)
{
throw (this.name + '.start() failed');
}
}
else
{
throw ('cannot call ' + this.name + '.start(), when current state is: ' + this.status.state);
}
}
return (retVal);
}
else
{
}
}
this.proxy.CloseServiceHandle(handle);
throw ('could not find service: ' + name);
}
this.installService = function(options)
{
var handle = this.proxy.OpenSCManagerA(0x00, 0x00, 0x0002);
if (handle == 0) { throw ('error opening SCManager'); }
var serviceName = this.GM.CreateVariable(options.name);
var displayName = this.GM.CreateVariable(options.displayName);
var allAccess = 0x000F01FF;
var serviceType;
var servicePath = this.GM.CreateVariable(options.servicePath);
switch(options.startType)
{
case 'BOOT_START':
serviceType = 0x00;
break;
case 'SYSTEM_START':
serviceType = 0x01;
break;
case 'AUTO_START':
serviceType = 0x02;
break;
case 'DEMAND_START':
serviceType = 0x03;
break;
default:
serviceType = 0x04; // Disabled
break;
}
var h = this.proxy.CreateServiceA(handle, serviceName, displayName, allAccess, 0x10 | 0x100, serviceType, 0, servicePath, 0, 0, 0, 0, 0);
if (h == 0) { this.proxy.CloseServiceHandle(handle); throw ('Error Creating Service'); }
if(options.description)
{
console.log(options.description);
var dscPtr = this.GM.CreatePointer();
dscPtr.Val = this.GM.CreateVariable(options.description);
if(this.proxy.ChangeServiceConfig2A(h, 1, dscPtr)==0)
{
this.proxy.CloseServiceHandle(h);
this.proxy.CloseServiceHandle(handle);
throw ('Unable to set description');
}
}
this.proxy.CloseServiceHandle(h);
this.proxy.CloseServiceHandle(handle);
return (this.getService(options.name));
}
this.uninstallService = function(name)
{
var service = this.getService(name);
if(service.status.state == 'STOPPED')
{
if (this.proxy.DeleteService(service._service) == 0) { throw ('Uninstall Service for: ' + name + ', failed with error: ' + this.proxy2.GetLastError()); }
}
else
{
throw ('Cannot uninstall service: ' + name + ', because it is: ' + service.status.state);
}
}
}
module.exports = serviceManager;

655
Debug/upnpcp.js Normal file
View File

@@ -0,0 +1,655 @@
var parseXml = require('parseXml');
var http = require('http');
var dgram = require('dgram');
var os = require('os');
var MemoryStream = require('MemoryStream');
var net = require('net');
//var networkMonitor = require('NetworkMonitor');
function upnpdevice(descriptionUrl, usn, cp)
{
var d = descriptionUrl.split('/');
this.BaseURL = d[0] + '//' + d[2];
var emitterUtils = require('events').inherits(this);
emitterUtils.createEvent('bye');
emitterUtils.createEvent('error');
emitterUtils.createEvent('alive');
emitterUtils.createEvent('serviceLoaded');
this.pendingServices = 0;
this.usn = usn;
this.cp = cp;
this.req = http.get(descriptionUrl);
this.req.device = this;
this.req.on('error', function ()
{
this.device.emit('error', 'Error fetching Description Document from ' + this.device.BaseURL);
});
this.req.on('response', function (msg)
{
if (msg.statusCode == 200)
{
msg.device = this.device;
this.device.dd = new MemoryStream();
this.device.dd.device = this.device;
msg.pipe(this.device.dd);
this.device.dd.on('end', function ()
{
upnpdevice_parseXml.apply(this.device, [this.buffer.toString()]);
});
}
else
{
this.device.emit('error', 'Error (' + msg.statusCode + ') Fetching Description Document from: ' + this.device.BaseURL);
}
});
this.loadAllServices = function () { this.rootDevice.loadAllServices(); };
this.makeUrl = function (url)
{
if(url.startsWith('/'))
{
if (this.BaseURL.endsWith('/'))
{
return (this.BaseURL + url.substring(1));
}
else
{
return (this.BaseURL + url);
}
}
else
{
if (this.BaseURL.endsWith('/'))
{
return (this.BaseURL + url);
}
else
{
return (this.BaseURL + '/' + url);
}
}
};
this.on('~', upnpdevice_Cleanup);
this.on('serviceLoaded', function (svc)
{
if(--this.pendingServices == 0)
{
// All Services have been loaded
this.cp.emit('device', this);
}
});
this.getDevice = function (udn)
{
return (this.rootDevice.getDevice(udn));
};
}
function upnpdevice_Cleanup()
{
console.log('Finalizing: ' + this.rootDevice.friendlyName);
}
function upnpservice(parentDevice, xmlDoc)
{
var emitterUtils = require('events').inherits(this);
this.device = parentDevice;
this.serviceType = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'serviceType')[0].textContent;
this.serviceId = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'serviceId')[0].textContent;
this.controlURL = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'controlURL')[0].textContent;
this.eventSubURL = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'eventSubURL')[0].textContent;
this.SCPDURL = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'SCPDURL')[0].textContent;
if (!this.controlURL.startsWith('http:') && !this.controlURL.startsWith('https:')) { this.controlURL = this.device.rootDevice.makeUrl(this.controlURL); }
if (!this.eventSubURL.startsWith('http:') && !this.eventSubURL.startsWith('https:')) { this.eventSubURL = this.device.rootDevice.makeUrl(this.eventSubURL); }
if (!this.SCPDURL.startsWith('http:') && !this.SCPDURL.startsWith('https:')) { this.SCPDURL = this.device.rootDevice.makeUrl(this.SCPDURL); }
this.load = function ()
{
++this.device.rootDevice.pendingServices;
this.req = http.get(this.SCPDURL);
this.req.service = this;
this.req.on('error', function () { this.service.device.rootDevice.emit('error', 'Error fetching SCPD from: ' + this.service.SCPDURL); });
this.req.on('response', function (msg)
{
if (msg.statusCode == 200)
{
msg.service = this.service;
this.service.scpdxml = new MemoryStream();
this.service.scpdxml.service = this.service;
msg.pipe(this.service.scpdxml);
this.service.scpdxml.on('end', function ()
{
try
{
upnpservice_parseScpd.apply(this.service, [this.buffer.toString()]);
}
catch(e)
{
this.service.device.rootDevice.emit('error', 'error parsing SCPD: ' + e);
}
});
}
else
{
this.service.device.rootDevice.emit('error', 'Error loading SCPD from: ' + this.service.SCPDURL);
}
});
}
this.getAction = function(name)
{
for(var a in this.actions)
{
if (this.actions[a].name == name) { return (this.actions[a]); }
}
return (undefined);
}
}
function upnpargument(action, xmlDoc)
{
this.action = action;
this.name = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'name')[0].textContent;
this.direction = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'direction')[0].textContent;
this.relatedStateVariable = action.service.stateVariables.get(xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'relatedStateVariable')[0].textContent);
}
function post_response(msg)
{
if (msg.statusCode != 200)
{
var userArgs = this.args;
if (userArgs.length > 0 && typeof (userArgs[0] == 'function'))
{
var fn = userArgs.shift();
userArgs.unshift(msg.StatusCode ? msg.StatusCode : msg.url);
fn.apply(this.action, userArgs);
}
return;
}
var buff = new MemoryStream();
buff.req = this;
msg.pipe(buff);
buff.on('end', function ()
{
var body = {};
var xml = parseXml(this.buffer.toString());
var action = this.req.action;
var userArgs = this.req.args;
var actionResponse = xml.getElementsByTagNameNS(action.service.serviceType, action.name + 'Response')[0];
if(actionResponse)
{
for(var child in actionResponse.childNodes)
{
if(action.arguments.get(actionResponse.childNodes[child].name))
{
body[actionResponse.childNodes[child].name] = actionResponse.childNodes[child].textContent;
}
}
if(userArgs.length > 0 && typeof(userArgs[0]) == 'function')
{
var fn = userArgs.shift();
userArgs.unshift(body);
fn.apply(action, userArgs);
}
}
});
}
function upnpaction(service, xmlDoc)
{
this.pendingPosts = [];
this.arguments = []; Object.defineProperty(this.arguments, "get", { value: function (name) { for (var i in this) { if (this[i].name == name) { return (this[i]); } } return (undefined); } });
this.service = service;
this.name = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'name')[0].textContent;
var argumentList = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'argumentList')[0];
if (argumentList)
{
var arguments = argumentList.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'argument');
for (var i in arguments)
{
this.arguments.push(new upnpargument(this, arguments[i]));
}
}
else
{
//console.log(this.service.scpdxml.buffer.toString());
}
this.invoke = function (args)
{
var parameters = '';
for (var i in this.arguments)
{
if(this.arguments[i].direction == 'in' && args[this.arguments[i].name])
{
parameters += ('<u:' + this.arguments[i].name + '>' + args[this.arguments[i].name] + '</u:' + this.arguments[i].name + '>');
}
else if(this.arguments.direction == 'in')
{
throw ('missing parameter: [' + this.arguments[i].name + '] when invoking Action: ' + this.name);
}
}
var controlUri = http.parseUri(this.service.controlURL);
console.log(controlUri);
var headers = { HOST: (controlUri.host + ':' + controlUri.port), SOAPACTION: '"' + this.service.serviceType + '#' + this.name + '"', 'Content-Type': 'text/xml; charset="utf-8"' };
this.pendingPosts.push(http.request({ protocol: 'http', host: controlUri.host, port: controlUri.port, method: 'POST', path: controlUri.path, headers: headers }));
this.pendingPosts.peek().action = this;
this.pendingPosts.peek().args = [];
for (var i = 1; i < arguments.length; ++i)
{
this.pendingPosts.peek().args.push(arguments[i]);
}
this.pendingPosts.peek().on('response', post_response);
var txt = '<?xml version="1.0" encoding="utf-8"?>\r\n<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body>';
this.pendingPosts.peek().write(txt);
console.log(txt);
txt = '<u:' + this.name + ' xmlns:u="' + this.service.serviceType + '">';
this.pendingPosts.peek().write(txt);
console.log(txt);
if (parameters != '')
{
this.pendingPosts.peek().write(parameters);
console.log(parameters);
}
this.pendingPosts.peek().write('</u:' + this.name + '>');
this.pendingPosts.peek().write('</s:Body></s:Envelope>');
console.log('</u:' + this.name + '>' + '</s:Body></s:Envelope>');
this.pendingPosts.peek().end();
};
this.invokeLegacy = function (args)
{
var controlUri = http.parseUri(this.service.controlURL);
var parameters = '';
for (var i in this.arguments) {
if (this.arguments[i].direction == 'in' && args[this.arguments[i].name]) {
parameters += ('<u:' + this.arguments[i].name + '>' + args[this.arguments[i].name] + '</u:' + this.arguments[i].name + '>');
}
else if (this.arguments.direction == 'in') {
throw ('missing parameter: [' + this.arguments[i].name + '] when invoking Action: ' + this.name);
}
}
this.pendingPosts.push(net.connect({ host: controlUri.host, port: controlUri.port }));
this.pendingPosts.peek().path = controlUri.path;
this.pendingPosts.peek().args = args;
this.pendingPosts.peek().parameters = parameters;
this.pendingPosts.peek().action = this;
this.pendingPosts.peek().headers = { HOST: (controlUri.host + ':' + controlUri.port), SOAPACTION: '"' + this.service.serviceType + '#' + this.name + '"', 'Content-Type': 'text/xml; charset="utf-8"' };
this.pendingPosts.peek().on('connect', function ()
{
console.log('legacy connected');
this.write('POST ' + this.path + ' HTTP/1.1\r\n');
var headers = this.headers;
this.write('HOST: ' + headers.HOST + '\r\n');
this.write('SOAPACTION: ' + headers.SOAPACTION + '\r\n');
this.write('Content-Type: ' + headers['Content-Type'] + '\r\n');
var txt = '<?xml version="1.0" encoding="utf-8"?>\r\n<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body>';
txt += '<u:' + this.action.name + ' xmlns:u="' + this.action.service.serviceType + '">';
txt += this.parameters;
txt += ('</u:' + this.name + '>' + '</s:Body></s:Envelope>');
var b = Buffer.from(txt);
this.write('Content-Length: ' + b.length + '\r\n\r\n');
this.write(b);
});
this.pendingPosts.peek().http = http.createStream();
this.pendingPosts.peek().pipe(this.pendingPosts.peek().http);
this.pendingPosts.peek().http.on('response', post_response);
};
}
function upnpvariable(service, xmlDoc)
{
this.service = service;
this.name = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'name')[0].textContent;
this.dataType = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'dataType')[0].textContent;
this.evented = xmlDoc.attributes.get('sendEvents').value;
if (this.evented == 'yes')
{
this.currentValue = null;
}
}
function upnpservice_parseScpd(scpd)
{
this.stateVariables = []; Object.defineProperty(this.stateVariables, "get", { value: function (name) { for (var i in this) { if (this[i].name == name) { return (this[i]); } } return (undefined); } });
this.actions = []; Object.defineProperty(this.actions, "get", { value: function (name) { for (var i in this) { if (this[i].name == name) { return (this[i]); } } return (undefined); } });
var doc = parseXml(scpd);
var stateTable = doc.getElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'serviceStateTable')[0];
var variables = stateTable.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'stateVariable');
for (var i in variables)
{
this.stateVariables.push(new upnpvariable(this, variables[i]));
}
var actionList = doc.getElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'actionList')[0];
var actions = actionList.getChildElementsByTagNameNS('urn:schemas-upnp-org:service-1-0', 'action');
for (var i in actions)
{
try
{
this.actions.push(new upnpaction(this, actions[i]));
}
catch(e)
{
this.device.rootDevice.emit('error', 'error parsing SCPD/Action: ' + e);
return;
}
}
this.subscribe = function ()
{
};
this.device.rootDevice.emit('serviceLoaded', this);
}
function upnpdevice_child(rootDevice, xmlDoc)
{
this.rootDevice = rootDevice;
this.services = []; Object.defineProperty(this.services, "get", { value: function (id) { for (var i in this) { if (this[i].serviceType == id || this[i].serviceId == id) { return (this[i]); } } return (undefined); } });
this.embeddedDevices = []; Object.defineProperty(this.embeddedDevices, "get", { value: function (id) { for (var i in this) { if (this[i].UDN == id || this[i].deviceType == id) { return (this[i]); } } return (undefined); } });
var emitterUtils = require('events').inherits(this);
emitterUtils.createEvent('bye');
emitterUtils.createEvent('error');
this.friendlyName = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'friendlyName')[0].textContent;
this.deviceType = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'deviceType')[0].textContent;
this.UDN = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'UDN')[0].textContent;
this.manufacturer = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'manufacturer')[0].textContent;
var serviceList = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'serviceList')[0]
for (var i in serviceList.childNodes)
{
if(serviceList.childNodes[i].namespace == 'urn:schemas-upnp-org:device-1-0')
{
this.services.push(new upnpservice(this, serviceList.childNodes[i]));
}
}
var deviceList = xmlDoc.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'deviceList')[0]
if (deviceList != null)
{
var devices = deviceList.getChildElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'device')
for (var device in devices)
{
this.embeddedDevices.push(new upnpdevice_child(rootDevice, devices[device]));
}
//console.log(devices);
}
this.loadAllServices = function () { for (var i in this.services) { this.services[i].load();} for (var i in this.embeddedDevices) { this.embeddedDevices[i].loadAllServices(); } };
this.getDevice = function (udn)
{
if (this.UDN == udn) { return (this); }
for(var ed in this.embeddedDevices)
{
var ret = this.embeddedDevices[ed].getDevice(udn);
if (ret) { return (ret); }
}
return (undefined);
};
this.getService = function(id)
{
for (var s in this.services)
{
if (this.services[s].serviceId == id) { return (this.services[s]); }
}
return (undefined);
}
}
function upnpdevice_parseXml(xml)
{
this.dd = null;
var doc = parseXml(xml);
//var URLBase = doc.getElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'URLBase')[0];
//if (URLBase != null) { console.log("old base: " + this.BaseURL); this.BaseURL = URLBase.textContent; }
var root = doc.getElementsByTagNameNS('urn:schemas-upnp-org:device-1-0', 'device')[0];
if (root != null)
{
this.rootDevice = new upnpdevice_child(this, root);
if (!this.cp.searchString.startsWith('ssdp:') && !this.cp.searchString.startsWith('upnp:') && !this.cp.searchString.startsWith('urn:') && !this.cp.searchString.startsWith('uuid:'))
{
// Friendly Name Search
if(this.rootDevice.friendlyName == this.cp.searchString)
{
//console.log(xml);
this.rootDevice.loadAllServices();
}
}
else
{
console.log(this.cp.searchString.split(':')[0]);
switch(this.cp.searchString.split(':')[0])
{
case 'ssdp':
break;
case 'upnp':
this.rootDevice.loadAllServices();
break;
case 'uuid':
break;
case 'urn':
break;
}
}
//console.log(this.rootDevice.friendlyName);
}
}
function upnpcp_onSearch(msg, rinfo)
{
var header = require('http-headers')(msg);
if (header.statusCode != 200) { return; }
var usn = header.headers.usn.split('::')[0];
if(this.cp.deviceTable[usn] == null)
{
this.cp.deviceTable[usn] = new upnpdevice(header.headers.location, usn, this.cp);
this.cp.deviceTable[usn].on('error', function (e) { console.log('Removing Device/' + this.usn + ' due to error: ' + e); this.cp.deviceTable[this.usn] = null; });
this.cp.deviceTable[usn].on('alive', function () { this.cp.emit('device', this); });
}
}
function upnpcp(search)
{
this.searchString = search;
if (!search.startsWith('ssdp:') && !search.startsWith('upnp:') && !search.startsWith('uuid:') && !search.startsWith('urn:'))
{
// Search by Friendly Name
search = 'upnp:rootdevice';
}
var MSEARCH = 'M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nST: ' + search + '\r\nMAN: "ssdp:discover"\r\nMX: 5\r\nContent-Length: 0\r\n\r\n';
var emitterUtils = require('events').inherits(this);
emitterUtils.createEvent('device');
this.searchSocket = dgram.createSocket({ type: 'udp4' });
this.searchSocket.cp = this;
this.searchSocket.bind({ port: 0, address:'0.0.0.0' });
this.deviceTable = {};
this.searchSocket.on('message', upnpcp_onSearch);
var interfaces = os.networkInterfaces();
for(var name in interfaces)
{
for (var i in interfaces[name])
{
if (interfaces[name][i].family == 'IPv4' && interfaces[name][i].status == 'up')
{
console.log('Sending Multicast on: ' + interfaces[name][i].address + ' to: 239.255.255.250:1900');
this.searchSocket.setMulticastTTL(1);
this.searchSocket.setMulticastLoopback(true);
this.searchSocket.setMulticastInterface(interfaces[name][i].address);
this.searchSocket.send(MSEARCH, 1900, '239.255.255.250');
}
}
}
}
//var testCP = new upnpcp("Samsung CLX-3300 Series (10.128.125.118)");
function display_device(dv)
{
if (!dv) { console.log('No match'); return; }
console.log('FriendlyName/ ' + dv.friendlyName);
console.log(' DeviceType/ ' + dv.deviceType);
console.log(' DeviceUDN/ ' + dv.UDN);
for (var svc in dv.services)
{
console.log(' ServiceID/ ' + dv.services[svc].serviceId);
}
for (var ed in dv.embeddedDevices)
{
console.log(' Embedded Device: ' + dv.embeddedDevices[ed].friendlyName + ' [' + dv.embeddedDevices[ed].UDN + ']');
}
}
function display_action(action)
{
var argString = null;
for (var arg in action.arguments)
{
if (action.arguments[arg].direction == 'in')
{
if (argString)
{
argString += (', ' + action.arguments[arg].name);
}
else
{
argString = action.arguments[arg].name;
}
}
}
console.log(' ' + action.name + '(' + (argString?argString:'') + ')');
}
function display_actionDetail(action)
{
if (!action) { console.log('no match'); return; }
console.log('Action: ' + action.name);
console.log(' Input Parameters:');
console.log(' {');
for (var arg in action.arguments)
{
if (action.arguments[arg].direction == 'in')
{
console.log(' [' + action.arguments[arg].relatedStateVariable.dataType + '] ' + action.arguments[arg].name);
}
}
console.log(' }');
console.log(' Output Parameters:');
console.log(' {');
for (var arg in action.arguments)
{
if (action.arguments[arg].direction == 'out')
{
console.log(' [' + action.arguments[arg].relatedStateVariable.dataType + '] ' + action.arguments[arg].name);
}
}
console.log(' }');
}
function display_service(svc)
{
if (!svc) { console.log('No match'); return; }
console.log('ServiceID/ ' + svc.serviceId);
console.log('ServiceType/ ' + svc.serviceType);
console.log('Actions:');
for(var action in svc.actions)
{
display_action(svc.actions[action]);
}
}
var testCP;
if (process.argv.length > 1)
{
if(process.argv[1].startsWith('discover='))
{
testCP = new upnpcp(process.argv[1].split('=')[1]);
}
}
if (!testCP) { process.exit(); }
testCP.on('device', function (dv)
{
var selectedDevice = null;
var selectedService = null;
var selectedAction = null;
console.log('');
console.log('Device Added: ');
display_device(dv.rootDevice);
console.log('');
for (var arg in process.argv)
{
if(process.argv[arg].startsWith('dv='))
{
var i = parseInt(process.argv[arg].split('=')[1]);
console.log('Selected Embedded Device: ' + i);
display_device(dv.rootDevice.embeddedDevices[i]);
selectedDevice = dv.rootDevice.embeddedDevices[i];
}
if(process.argv[arg].startsWith('udn='))
{
console.log('Selected Device: ' + process.argv[arg].split('=')[1]);
selectedDevice = dv.getDevice(process.argv[arg].split('=')[1]);
display_device(selectedDevice);
}
if(selectedDevice && process.argv[arg].startsWith('serviceId='))
{
selectedService = selectedDevice.getService(process.argv[arg].split('=')[1]);
display_service(selectedService);
}
if(selectedService && process.argv[arg].startsWith('action='))
{
selectedAction = selectedService.getAction(process.argv[arg].split('=')[1]);
display_actionDetail(selectedAction);
}
if(selectedAction && process.argv[arg].startsWith('invoke='))
{
var txt = process.argv[arg].split('=')[1];
console.log('Invoking with: ' + txt);
selectedAction.invoke(JSON.parse(process.argv[arg].split('=')[1]), function ()
{
console.log('Response: ', arguments[0]);
process.exit();
});
}
if (selectedAction && process.argv[arg].startsWith('invokeLegacy=')) {
var txt = process.argv[arg].split('=')[1];
console.log('Invoking with: ' + txt);
selectedAction.invokeLegacy(JSON.parse(process.argv[arg].split('=')[1]), function () {
console.log('Response: ', arguments[0]);
process.exit();
});
}
}
});