@anlix-io/genieacs-sim
Version:
TR-069 client simulator for GenieACS
485 lines (418 loc) • 14.3 kB
JavaScript
;
const http = require("http");
const https = require("https");
const xmlParser = require("./xml-parser");
const xmlUtils = require("./xml-utils");
const diagnostics = require("./diagnostics");
const models = require('./models');
const INFORM_PARAMS = [
"Device.DeviceInfo.SpecVersion",
"InternetGatewayDevice.DeviceInfo.SpecVersion",
"Device.DeviceInfo.HardwareVersion",
"InternetGatewayDevice.DeviceInfo.HardwareVersion",
"Device.DeviceInfo.SoftwareVersion",
"InternetGatewayDevice.DeviceInfo.SoftwareVersion",
"Device.DeviceInfo.ProvisioningCode",
"InternetGatewayDevice.DeviceInfo.ProvisioningCode",
"Device.ManagementServer.ParameterKey",
"InternetGatewayDevice.ManagementServer.ParameterKey",
"Device.ManagementServer.ConnectionRequestURL",
"InternetGatewayDevice.ManagementServer.ConnectionRequestURL",
"Device.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.ExternalIPAddress",
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.ExternalIPAddress",
"Device.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress",
"InternetGatewayDevice.LANDevice.1.LANEthernetInterfaceConfig.1.MACAddress"
];
function inform(simulator, event) {
const device = simulator.device;
let v;
let manufacturer = "";
if (v = device.get("DeviceID.Manufacturer")) {
manufacturer = xmlUtils.node(
"Manufacturer",
{},
xmlParser.encodeEntities(v[1])
);
} else if (v = device.get("Device.DeviceInfo.Manufacturer")) {
manufacturer = xmlUtils.node(
"Manufacturer",
{},
xmlParser.encodeEntities(v[1])
);
} else if (v = device.get("InternetGatewayDevice.DeviceInfo.Manufacturer")) {
manufacturer = xmlUtils.node(
"Manufacturer",
{},
xmlParser.encodeEntities(v[1])
);
}
let oui = "";
if (v = device.get("DeviceID.OUI")) {
oui = xmlUtils.node(
"OUI",
{},
xmlParser.encodeEntities(v[1])
);
} else if (v = device.get("Device.DeviceInfo.ManufacturerOUI")) {
oui = xmlUtils.node(
"OUI",
{},
xmlParser.encodeEntities(v[1])
);
} else if (v = device.get("InternetGatewayDevice.DeviceInfo.ManufacturerOUI")) {
oui = xmlUtils.node(
"OUI",
{},
xmlParser.encodeEntities(v[1])
);
}
let productClass = "";
if (v = device.get("DeviceID.ProductClass")) {
productClass = xmlUtils.node(
"ProductClass",
{},
xmlParser.encodeEntities(v[1])
);
} else if (v = device.get("Device.DeviceInfo.ProductClass")) {
productClass = xmlUtils.node(
"ProductClass",
{},
xmlParser.encodeEntities(v[1])
);
} else if (v = device.get("InternetGatewayDevice.DeviceInfo.ProductClass")) {
productClass = xmlUtils.node(
"ProductClass",
{},
xmlParser.encodeEntities(v[1])
);
}
let serialNumber = "";
if (v = device.get("DeviceID.SerialNumber")) {
serialNumber = xmlUtils.node(
"SerialNumber",
{},
xmlParser.encodeEntities(v[1])
);
} else if (v = device.get("Device.DeviceInfo.SerialNumber")) {
serialNumber = xmlUtils.node(
"SerialNumber",
{},
xmlParser.encodeEntities(v[1])
);
} else if (v = device.get("InternetGatewayDevice.DeviceInfo.SerialNumber")) {
serialNumber = xmlUtils.node(
"SerialNumber",
{},
xmlParser.encodeEntities(v[1])
);
}
let macAddr = "";
if (v = device.get("InternetGatewayDevice.LANDevice.1.LANEthernetInterfaceConfig.1.MACAddress")) {
macAddr = xmlUtils.node(
"MACAddress",
{},
xmlParser.encodeEntities(v[1])
);
}
let deviceId = xmlUtils.node("DeviceId", {}, [manufacturer, oui, productClass, serialNumber]);
let eventStruct = xmlUtils.node(
"EventStruct",
{},
[
xmlUtils.node("EventCode", {}, event || "2 PERIODIC"),
xmlUtils.node("CommandKey")
]
);
let evnt = xmlUtils.node("Event", {
"soap-enc:arrayType": "cwmp:EventStruct[1]"
}, eventStruct);
let params = [];
for (let p of INFORM_PARAMS) {
let param = device.get(p);
if (!param)
continue;
params.push(xmlUtils.node("ParameterValueStruct", {}, [
xmlUtils.node("Name", {}, p),
xmlUtils.node("Value", {"xsi:type": param[2]}, xmlParser.encodeEntities(param[1]))
]));
}
let parameterList = xmlUtils.node("ParameterList", {
"soap-enc:arrayType": `cwmp:ParameterValueStruct[${INFORM_PARAMS.length}]`
}, params);
let inform = xmlUtils.node("cwmp:Inform", {}, [
deviceId,
evnt,
xmlUtils.node("MaxEnvelopes", {}, "1"),
xmlUtils.node("CurrentTime", {}, new Date().toISOString()),
xmlUtils.node("RetryCount", {}, "0"),
parameterList
]);
return inform;
}
function getSortedPaths(device) {
if (device._sortedPaths) return device._sortedPaths;
const ignore = new Set(["DeviceID", "Downloads", "Tags", "Events", "Reboot", "FactoryReset", "VirtalParameters"]);
device._sortedPaths = Array.from(device.keys()).filter(p => p[0] !== "_" && !ignore.has(p.split(".")[0])).sort();
return device._sortedPaths;
}
function GetParameterNames(simulator, request) {
const device = simulator.device;
let parameterNames = getSortedPaths(device);
let parameterPath, nextLevel;
for (let c of request.children) {
switch (c.name) {
case "ParameterPath":
parameterPath = c.text;
break;
case "NextLevel":
nextLevel = Boolean(JSON.parse(c.text));
break;
}
}
let parameterList = [];
if (nextLevel) {
for (let p of parameterNames) {
if (p.startsWith(parameterPath) && p.length > parameterPath.length + 1) {
let i = p.indexOf(".", parameterPath.length + 1);
if (i === -1 || i === p.length - 1)
parameterList.push(p);
}
}
} else {
for (let p of parameterNames) {
if (p.startsWith(parameterPath))
parameterList.push(p);
}
}
let params = [];
for (let p of parameterList) {
params.push(
xmlUtils.node("ParameterInfoStruct", {}, [
xmlUtils.node("Name", {}, p),
xmlUtils.node("Writable", {}, String(device.get(p)[0]))
])
);
}
let response = xmlUtils.node(
"cwmp:GetParameterNamesResponse",
{},
xmlUtils.node(
"ParameterList",
{ "soap-enc:arrayType": `cwmp:ParameterInfoStruct[${parameterList.length}]` },
params
)
);
return response;
}
function GetParameterValues(simulator, request) {
const device = simulator.device;
let parameterNames = request.children[0].children;
let params = [];
let parameterCount = 0;
for (let p of parameterNames) {
let name = p.text;
// Verifica se o parâmetro é um objeto (terminado em ".")
if (name.endsWith(".")) {
// Lista todas as folhas sob este objeto
const allPaths = getSortedPaths(device);
const descendantParams = allPaths.filter(path => {
// É descendente se começa com o caminho do objeto e não é um objeto
return path.startsWith(name) && !path.endsWith(".");
});
for (let descendant of descendantParams) {
let [_, value, type] = device.get(descendant);
let valueStruct = xmlUtils.node("ParameterValueStruct", {}, [
xmlUtils.node("Name", {}, descendant),
xmlUtils.node("Value", { "xsi:type": type }, xmlParser.encodeEntities(value))
]);
params.push(valueStruct);
parameterCount++;
}
} else {
// Comportamento original para parâmetros folha
let [_, value, type] = device.get(name);
let valueStruct = xmlUtils.node("ParameterValueStruct", {}, [
xmlUtils.node("Name", {}, name),
xmlUtils.node("Value", { "xsi:type": type }, xmlParser.encodeEntities(value))
]);
params.push(valueStruct);
parameterCount++;
}
}
let response = xmlUtils.node(
"cwmp:GetParameterValuesResponse",
{},
xmlUtils.node(
"ParameterList",
{ "soap-enc:arrayType": "cwmp:ParameterValueStruct[" + parameterCount + "]" },
params
)
);
return response;
}
function SetParameterValues(simulator, request) {
const device = simulator.device;
let parameterValues = request.children[0].children;
const modified = {}; // Received parameters to be read in each diagnostic.
for (let p of parameterValues) {
let name, value;
for (let c of p.children) {
switch (c.localName) {
case "Name":
name = c.text;
break;
case "Value":
value = c;
break;
}
}
const v = device.get(name);
v[1] = xmlParser.decodeEntities(value.text);
v[2] = xmlParser.parseAttrs(value.attrs).find(a => a.localName === "type").value;
modified[name] = true;
}
// running each diagnostic logic.
// their logic includes whether they have to be executed or not.
for (let key in diagnostics) {
diagnostics[key].run(simulator, modified);
}
let response = xmlUtils.node("cwmp:SetParameterValuesResponse", {}, xmlUtils.node("Status", {}, "0"));
return response;
}
function AddObject(simulator, request) {
const device = simulator.device;
let objectName = request.children[0].text;
let instanceNumber;
const deviceId = device.get('DeviceID.ID');
const model = models[deviceId && deviceId[1]];
// If model exists in ./models.js and 'objectName' is mapped inside
// 'addObject', we execute the function mapped to this 'objectName' and get
// the index of the new object created.
if (model && model.addObject && model.addObject[objectName]) {
instanceNumber = model.addObject[objectName](simulator, objectName);
} else {
// Original simulator behavior for an AddObject task.
instanceNumber = 1;
while (device.has(`${objectName}${instanceNumber}.`))
instanceNumber += 1;
device.set(`${objectName}${instanceNumber}.`, [true]);
const defaultValues = {
"xsd:boolean": "false",
"xsd:int": "0",
"xsd:unsignedInt": "0",
"xsd:dateTime": "0001-01-01T00:00:00Z"
};
for (let p of getSortedPaths(device)) {
if (p.startsWith(objectName) && p.length > objectName.length) {
let n = `${objectName}${instanceNumber}${p.slice(p.indexOf(".", objectName.length))}`;
if (!device.has(n)) {
const v = device.get(p);
device.set(n, [v[0], defaultValues[v[2]] || "", v[2]]);
}
}
}
}
let response = xmlUtils.node("cwmp:AddObjectResponse", {}, [
xmlUtils.node("InstanceNumber", {}, String(instanceNumber)),
xmlUtils.node("Status", {}, "0")
]);
delete device._sortedPaths;
return response;
}
function DeleteObject(simulator, request) {
const device = simulator.device;
let objectName = request.children[0].text;
for (let p of device.keys()) {
if (p.startsWith(objectName))
device.delete(p);
}
let response = xmlUtils.node("cwmp:DeleteObjectResponse", {}, xmlUtils.node("Status", {}, "0"));
delete device._sortedPaths;
return response;
}
function Download(simulator, request) {
let commandKey, url;
for (let c of request.children) {
switch (c.name) {
case "CommandKey":
commandKey = xmlParser.decodeEntities(c.text);
break;
case "URL":
url = xmlParser.decodeEntities(c.text);
break;
}
}
let faultCode = "9010";
let faultString = "Download timeout";
let client;
if (url.startsWith("http://")) client = http;
else if (url.startsWith("https://")) client = https;
if (client) {
client.get(url, (res) => {
if (res.statusCode === 200) {
faultCode = "0";
faultString = "";
} else {
faultCode = "9016";
faultString = `Unexpected response ${res.statusCode}`;
res.resume(); // Consume response data to free up memory.
return;
}
let body = [];
res.on("data", (chunk) => body.push(chunk));
res.on("end", async () => {
body = body.join('');
// If data is in JSON format containing the new software version,
// we update the 'SoftwareVersion' in the model tree.
let data;
try {
data = JSON.parse(body.toString('utf8'));
} catch (e) {
// in case of error, ignores content.
console.log('Error parsing Download body to json.', e)
console.log('File content:', body)
}
// console.log('download data', data);
if (data !== undefined && data.constructor === Object && data.version) {
simulator.device.get(simulator.TR === 'tr098'
? 'InternetGatewayDevice.DeviceInfo.SoftwareVersion'
: 'Device.DeviceInfo.SoftwareVersion'
)[1] = data.version;
}
// creating a new session where transfer complete message is sent.
// waiting 2 seconds before sending pending 'TransferComplete'.
setTimeout(() => simulator.runPendingActions(() => simulator.startSession()), 2000);
});
}).on("error", (err) => {
faultString = err.message;
});
}
const startTime = new Date();
simulator.pendingMessages.push(async (send) => {
let fault = xmlUtils.node("FaultStruct", {}, [
xmlUtils.node("FaultCode", {}, faultCode),
xmlUtils.node("FaultString", {}, xmlParser.encodeEntities(faultString))
]);
let request = xmlUtils.node("cwmp:TransferComplete", {}, [
xmlUtils.node("CommandKey", {}, commandKey),
xmlUtils.node("StartTime", {}, startTime.toISOString()),
xmlUtils.node("CompleteTime", {}, new Date().toISOString()),
fault
]);
return send(request);
});
let response = xmlUtils.node("cwmp:DownloadResponse", {}, [
xmlUtils.node("Status", {}, "1"),
xmlUtils.node("StartTime", {}, "0001-01-01T00:00:00Z"),
xmlUtils.node("CompleteTime", {}, "0001-01-01T00:00:00Z")
]);
return response;
}
exports.inform = inform;
exports.GetParameterNames = GetParameterNames;
exports.GetParameterValues = GetParameterValues;
exports.SetParameterValues = SetParameterValues;
exports.AddObject = AddObject;
exports.DeleteObject = DeleteObject;
exports.Download = Download;