Introduce ESLint and Prettier

This commit is contained in:
Zaid Abdulla committed 2018-08-24 05:31:22 -07:00
1 parent 9c943d0bbf
commit e494bdca8a
26 files changed
+1585 -1594

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
{
"env": {
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "script",
"ecmaVersion": 8
},
"rules": {
"no-shadow": ["error", { "allow": ["err"] }],
"handle-callback-err": "error",
"prefer-arrow-callback": "error",
"curly": ["error", "multi-or-nest", "consistent"]
}
}
+2
View File
@@ -16,3 +16,5 @@ config/ext
debug/* debug/*
!debug/.gitkeep !debug/.gitkeep
.eslintrc.json
+199 -136
View File
@@ -29,8 +29,7 @@ const auth = require("./auth");
const cache = require("./cache"); const cache = require("./cache");
function udpConReq(address, un, key, callback) { function udpConReq(address, un, key, callback) {
if (!address) if (!address) return false;
return false;
let [host, port] = address.split(":", 2); let [host, port] = address.split(":", 2);
@@ -38,18 +37,22 @@ function udpConReq(address, un, key, callback) {
port = 80; port = 80;
} else { } else {
port = parseInt(port); port = parseInt(port);
if (isNaN(port)) if (isNaN(port)) return false;
return false;
} }
const ts = Math.trunc(Date.now() / 1000); const ts = Math.trunc(Date.now() / 1000);
const id = Math.trunc(Math.random() * 4294967295); const id = Math.trunc(Math.random() * 4294967295);
const cn = crypto.randomBytes(8).toString("hex"); const cn = crypto.randomBytes(8).toString("hex");
const sig = crypto.createHmac("sha1", key).update(`${ts}${id}${un}${cn}`).digest("hex"); const sig = crypto
.createHmac("sha1", key)
.update(`${ts}${id}${un}${cn}`)
.digest("hex");
const uri = `http://${address}?ts=${ts}&id=${id}&un=${un}&cn=${cn}&sig=${sig}`; const uri = `http://${address}?ts=${ts}&id=${id}&un=${un}&cn=${cn}&sig=${sig}`;
const message = Buffer.from(`GET ${uri} HTTP/1.1\r\nHost: ${address}\r\n\r\n`); const message = Buffer.from(
`GET ${uri} HTTP/1.1\r\nHost: ${address}\r\n\r\n`
);
const client = dgram.createSocket({type: "udp4", reuseAddr: true}); const client = dgram.createSocket({ type: "udp4", reuseAddr: true });
const UDP_CONNECTION_REQUEST_PORT = config.get("UDP_CONNECTION_REQUEST_PORT"); const UDP_CONNECTION_REQUEST_PORT = config.get("UDP_CONNECTION_REQUEST_PORT");
if (UDP_CONNECTION_REQUEST_PORT) if (UDP_CONNECTION_REQUEST_PORT)
@@ -59,10 +62,10 @@ function udpConReq(address, un, key, callback) {
// are allowed to bind to the same address and port. The STUN server needs // are allowed to bind to the same address and port. The STUN server needs
// to open its UDP port with the SO_REUSEADDR option, allowing the NBI to // to open its UDP port with the SO_REUSEADDR option, allowing the NBI to
// also bind to the same port. // also bind to the same port.
client.bind({port: UDP_CONNECTION_REQUEST_PORT, exclusive: true}); client.bind({ port: UDP_CONNECTION_REQUEST_PORT, exclusive: true });
let count = 3; let count = 3;
const func = (err) => { const func = err => {
if (err || --count <= 0) { if (err || --count <= 0) {
client.close(); client.close();
return callback(err); return callback(err);
@@ -73,11 +76,18 @@ function udpConReq(address, un, key, callback) {
return true; return true;
} }
function httpConReq(url, username, password, allowBasicAuth, timeout, callback) { function httpConReq(
url,
username,
password,
allowBasicAuth,
timeout,
callback
) {
const options = URL.parse(url); const options = URL.parse(url);
if (options.protocol !== "http:") if (options.protocol !== "http:")
return callback(new Error("Invalid connection request URL or protocol")); return callback(new Error("Invalid connection request URL or protocol"));
options.agent = new http.Agent({maxSockets: 1}); options.agent = new http.Agent({ maxSockets: 1 });
function statusToError(statusCode) { function statusToError(statusCode) {
switch (statusCode) { switch (statusCode) {
@@ -93,70 +103,96 @@ function httpConReq(url, username, password, allowBasicAuth, timeout, callback)
} }
} }
const request = http.get(options, (res) => { const request = http
if (res.statusCode === 401 && res.headers["www-authenticate"]) { .get(options, response => {
const authHeader = auth.parseAuthHeader(res.headers["www-authenticate"]); if (response.statusCode === 401 && response.headers["www-authenticate"]) {
if (authHeader.method === "Basic") { const authHeader = auth.parseAuthHeader(
if (!allowBasicAuth) { response.headers["www-authenticate"]
request.abort(); );
return callback(new Error("Basic HTTP authentication not allowed")); if (authHeader.method === "Basic") {
} if (!allowBasicAuth) {
options.headers = {
"Authorization": auth.basic(username || "", password || "")
};
} else if (authHeader.method === "Digest") {
options.headers = {
"Authorization": auth.digest(username || "", password || "", options.path, "GET", null, authHeader)
};
}
const request = http.get(options, (res) => {
if (res.statusCode === 0) {
// Workaround for some devices unexpectedly closing the connection
request = http.get(options, (res) => {
callback(statusToError(res.statusCode));
res.resume();
}).on("error", () => {
request.abort(); request.abort();
return callback(new Error("Basic HTTP authentication not allowed"));
}
options.headers = {
Authorization: auth.basic(username || "", password || "")
};
} else if (authHeader.method === "Digest") {
options.headers = {
Authorization: auth.digest(
username || "",
password || "",
options.path,
"GET",
null,
authHeader
)
};
}
const req = http
.get(options, res => {
if (res.statusCode === 0) {
// Workaround for some devices unexpectedly closing the connection
let req2;
req2 = http
.get(options, res2 => {
callback(statusToError(res2.statusCode));
res2.resume();
})
.on("error", () => {
req2.abort();
callback(statusToError(0));
})
.on("socket", socket => {
socket.setTimeout(timeout);
socket.on("timeout", () => {
req2.abort();
});
});
req2();
} else {
callback(statusToError(res.statusCode));
}
res.resume();
})
.on("error", () => {
req.abort();
callback(statusToError(0)); callback(statusToError(0));
}).on("socket", (socket) => { })
.on("socket", socket => {
socket.setTimeout(timeout); socket.setTimeout(timeout);
socket.on("timeout", () => { socket.on("timeout", () => {
request.abort(); req.abort();
}); });
}); });
} else { } else {
callback(statusToError(res.statusCode)); callback(statusToError(response.statusCode));
} }
res.resume(); // No listener for data so emit resume
}).on("error", () => { response.resume();
request.abort(); })
callback(statusToError(0)); .on("error", () => {
}).on("socket", (socket) => {
socket.setTimeout(timeout);
socket.on("timeout", () => {
request.abort();
});
});
} else {
callback(statusToError(res.statusCode));
}
// No listener for data so emit resume
res.resume();
}).on("error", (err) => {
request.abort();
callback(statusToError(0));
}).on("socket", (socket) => {
socket.setTimeout(timeout);
socket.on("timeout", () => {
request.abort(); request.abort();
callback(statusToError(0));
})
.on("socket", socket => {
socket.setTimeout(timeout);
socket.on("timeout", () => {
request.abort();
});
}); });
});
} }
function connectionRequest(deviceId, callback) { function connectionRequest(deviceId, callback) {
const CONNECTION_REQUEST_TIMEOUT = config.get("CONNECTION_REQUEST_TIMEOUT", deviceId); const CONNECTION_REQUEST_TIMEOUT = config.get(
const CONNECTION_REQUEST_ALLOW_BASIC_AUTH = config.get("CONNECTION_REQUEST_ALLOW_BASIC_AUTH", deviceId); "CONNECTION_REQUEST_TIMEOUT",
deviceId
);
const CONNECTION_REQUEST_ALLOW_BASIC_AUTH = config.get(
"CONNECTION_REQUEST_ALLOW_BASIC_AUTH",
deviceId
);
const proj = { const proj = {
"Device.ManagementServer.ConnectionRequestURL._value": 1, "Device.ManagementServer.ConnectionRequestURL._value": 1,
"Device.ManagementServer.UDPConnectionRequestAddress._value": 1, "Device.ManagementServer.UDPConnectionRequestAddress._value": 1,
@@ -168,46 +204,74 @@ function connectionRequest(deviceId, callback) {
"InternetGatewayDevice.ManagementServer.ConnectionRequestPassword._value": 1 "InternetGatewayDevice.ManagementServer.ConnectionRequestPassword._value": 1
}; };
db.devicesCollection.findOne({_id: deviceId}, proj, (err, device) => { db.devicesCollection.findOne({ _id: deviceId }, proj, (err, device) => {
if (err) if (err) return callback(err);
return callback(err);
let managementServer, connectionRequestUrl, udpConnectionRequestAddress, username, password; let managementServer,
if (device.Device) // TR-181 data model connectionRequestUrl,
udpConnectionRequestAddress,
username,
password;
if (device.Device)
// TR-181 data model
managementServer = device.Device.ManagementServer; managementServer = device.Device.ManagementServer;
else // TR-098 data model // TR-098 data model
managementServer = device.InternetGatewayDevice.ManagementServer; else managementServer = device.InternetGatewayDevice.ManagementServer;
connectionRequestUrl = managementServer.ConnectionRequestURL._value; connectionRequestUrl = managementServer.ConnectionRequestURL._value;
if (managementServer.UDPConnectionRequestAddress) if (managementServer.UDPConnectionRequestAddress) {
udpConnectionRequestAddress = managementServer.UDPConnectionRequestAddress._value; udpConnectionRequestAddress =
managementServer.UDPConnectionRequestAddress._value;
}
if (managementServer.ConnectionRequestUsername) if (managementServer.ConnectionRequestUsername)
username = managementServer.ConnectionRequestUsername._value; username = managementServer.ConnectionRequestUsername._value;
if (managementServer.ConnectionRequestPassword) if (managementServer.ConnectionRequestPassword)
password = managementServer.ConnectionRequestPassword._value; password = managementServer.ConnectionRequestPassword._value;
const conReq = () => { const conReq = () => {
const udpSent = udpConReq(udpConnectionRequestAddress, username, password, (err) => { const udpSent = udpConReq(
if (err) udpConnectionRequestAddress,
throw err; username,
}); password,
err => {
if (err) throw err;
}
);
httpConReq(connectionRequestUrl, username, password, CONNECTION_REQUEST_ALLOW_BASIC_AUTH, CONNECTION_REQUEST_TIMEOUT, (err) => { httpConReq(
if (udpSent) connectionRequestUrl,
return callback(); username,
callback(err); password,
}); CONNECTION_REQUEST_ALLOW_BASIC_AUTH,
CONNECTION_REQUEST_TIMEOUT,
err => {
if (udpSent) return callback();
callback(err);
}
);
}; };
if (config.auth && config.auth.connectionRequest) { if (config.auth && config.auth.connectionRequest) {
// Callback is optional for backward compatibility // Callback is optional for backward compatibility
if (config.auth.connectionRequest.length > 4) if (config.auth.connectionRequest.length > 4) {
return config.auth.connectionRequest(deviceId, connectionRequestUrl, username, password, (u, p) => { return config.auth.connectionRequest(
username = u; deviceId,
password = p; connectionRequestUrl,
conReq(); username,
}); password,
[username, password] = config.auth.connectionRequest(deviceId, connectionRequestUrl, username, password); (u, p) => {
username = u;
password = p;
conReq();
}
);
}
[username, password] = config.auth.connectionRequest(
deviceId,
connectionRequestUrl,
username,
password
);
} }
conReq(); conReq();
}); });
@@ -215,23 +279,18 @@ function connectionRequest(deviceId, callback) {
function watchTask(deviceId, taskId, timeout, callback) { function watchTask(deviceId, taskId, timeout, callback) {
setTimeout(() => { setTimeout(() => {
db.tasksCollection.findOne({_id: taskId}, {_id: 1}, (err, task) => { db.tasksCollection.findOne({ _id: taskId }, { _id: 1 }, (err, task) => {
if (err) if (err) return callback(err);
return callback(err);
if (!task) if (!task) return callback(null, "completed");
return callback(null, "completed");
const q = {_id: `${deviceId}:task_${taskId}`}; const q = { _id: `${deviceId}:task_${taskId}` };
db.faultsCollection.findOne(q, {_id: 1}, (err, fault) => { db.faultsCollection.findOne(q, { _id: 1 }, (err, fault) => {
if (err) if (err) return callback(err);
return callback(err);
if (fault) if (fault) return callback(null, "fault");
return callback(null, "fault");
if ((timeout -= 500) <= 0) if ((timeout -= 500) <= 0) return callback(null, "timeout");
return callback(null, "timeout");
watchTask(deviceId, taskId, timeout, callback); watchTask(deviceId, taskId, timeout, callback);
}); });
@@ -244,53 +303,57 @@ function sanitizeTask(task, callback) {
if (task.expiry) { if (task.expiry) {
if (common.typeOf(task.expiry) === common.DATE_TYPE || isNaN(task.expiry)) if (common.typeOf(task.expiry) === common.DATE_TYPE || isNaN(task.expiry))
task.expiry = new Date(task.expiry); task.expiry = new Date(task.expiry);
else else task.expiry = new Date(task.timestamp.getTime() + +task.expiry * 1000);
task.expiry = new Date(task.timestamp.getTime() + +task.expiry * 1000);
} }
callback(task); callback(task);
} }
function insertTasks(tasks, callback) { function insertTasks(tasks, callback) {
if (tasks && common.typeOf(tasks) !== common.ARRAY_TYPE) if (tasks && common.typeOf(tasks) !== common.ARRAY_TYPE) tasks = [tasks];
tasks = [tasks]; else if (!tasks || tasks.length === 0) return callback(null, tasks || []);
else if (!tasks || tasks.length === 0)
return callback(null, tasks || []);
let counter = tasks.length; let counter = tasks.length;
for (let task of tasks) for (let task of tasks) {
sanitizeTask(task, (t) => { sanitizeTask(task, t => {
if (t.uniqueKey) if (t.uniqueKey) {
db.tasksCollection.remove({device: t.device, uniqueKey: t.uniqueKey}, () => {}); db.tasksCollection.remove(
{ device: t.device, uniqueKey: t.uniqueKey },
() => {}
);
}
if (--counter === 0) if (--counter === 0) db.tasksCollection.insert(tasks, callback);
db.tasksCollection.insert(tasks, callback);
}); });
}
} }
function deleteDevice(deviceId, callback) { function deleteDevice(deviceId, callback) {
db.tasksCollection.remove({"device": deviceId}, (err) => { db.tasksCollection.remove({ device: deviceId }, err => {
if (err) if (err) return callback(err);
return callback(err); db.devicesCollection.remove({ _id: deviceId }, err => {
db.devicesCollection.remove({"_id": deviceId}, (err) => { if (err) return callback(err);
if (err) db.faultsCollection.remove(
return callback(err); {
db.faultsCollection.remove({ _id: {
"_id": { $regex: `^${common.escapeRegExp(deviceId)}\\:`
"$regex": `^${common.escapeRegExp(deviceId)}\\:`
}
}, (err) => {
if (err)
return callback(err);
db.operationsCollection.remove({
"_id": {
"$regex": `^${common.escapeRegExp(deviceId)}\\:`
} }
}, (err) => { },
if (err) err => {
return callback(err); if (err) return callback(err);
cache.del(`${deviceId}_tasks_faults_operations`, callback); db.operationsCollection.remove(
}); {
}); _id: {
$regex: `^${common.escapeRegExp(deviceId)}\\:`
}
},
err => {
if (err) return callback(err);
cache.del(`${deviceId}_tasks_faults_operations`, callback);
}
);
}
);
}); });
}); });
} }
+34 -22
View File
@@ -23,7 +23,7 @@ const crypto = require("crypto");
function parseAuthHeader(authHeader) { function parseAuthHeader(authHeader) {
authHeader = authHeader.trim(); authHeader = authHeader.trim();
const method = authHeader.split(" ", 1)[0]; const method = authHeader.split(" ", 1)[0];
const res = {method: method}; const res = { method: method };
const parts = authHeader.slice(method.length + 1).split(","); const parts = authHeader.slice(method.length + 1).split(",");
let part; let part;
@@ -38,8 +38,7 @@ function parseAuthHeader(authHeader) {
} else { } else {
while (!/[^\\]"\s*$/.test(value)) { while (!/[^\\]"\s*$/.test(value)) {
const p = parts.shift(); const p = parts.shift();
if (p == null) if (p == null) throw new Error("Unable to parse auth header");
throw new Error("Unable to parse auth header");
value += "," + p; value += "," + p;
} }
@@ -55,7 +54,7 @@ function parseAuthHeader(authHeader) {
} }
function basic(username, password) { function basic(username, password) {
return "Basic " + (new Buffer(`${username}:${password}`).toString("base64")); return "Basic " + new Buffer(`${username}:${password}`).toString("base64");
} }
function digest(username, password, uri, httpMethod, body, authHeader) { function digest(username, password, uri, httpMethod, body, authHeader) {
@@ -64,40 +63,53 @@ function digest(username, password, uri, httpMethod, body, authHeader) {
let qop; let qop;
if (authHeader.qop) { if (authHeader.qop) {
if (authHeader.qop.indexOf(",") !== -1) if (authHeader.qop.indexOf(",") !== -1) qop = "auth";
qop = "auth"; // Either auth or auth-int, prefer auth // Either auth or auth-int, prefer auth
else else qop = authHeader.qop;
qop = authHeader.qop;
} }
let ha1 = crypto.createHash("md5"); let ha1 = crypto.createHash("md5");
ha1.update(username).update(":").update(authHeader.realm).update(":").update(password); ha1
.update(username)
.update(":")
.update(authHeader.realm)
.update(":")
.update(password);
// TODO support "MD5-sess" algorithm directive // TODO support "MD5-sess" algorithm directive
ha1 = ha1.digest("hex"); ha1 = ha1.digest("hex");
let ha2 = crypto.createHash("md5"); let ha2 = crypto.createHash("md5");
ha2.update(httpMethod).update(":").update(uri); ha2
if (qop === "auth-int") .update(httpMethod)
ha2.update(":").update(body); .update(":")
.update(uri);
if (qop === "auth-int") ha2.update(":").update(body);
ha2 = ha2.digest("hex"); ha2 = ha2.digest("hex");
let hash = crypto.createHash("md5"); let hash = crypto.createHash("md5");
hash.update(ha1).update(":").update(authHeader.nonce); hash
if (qop) .update(ha1)
hash.update(":").update(nc).update(":").update(cnonce).update(":").update(qop); .update(":")
.update(authHeader.nonce);
if (qop) {
hash
.update(":")
.update(nc)
.update(":")
.update(cnonce)
.update(":")
.update(qop);
}
hash.update(":").update(ha2); hash.update(":").update(ha2);
let authString = `Digest username="${username}"`; let authString = `Digest username="${username}"`;
authString += `,realm="${authHeader.realm}"`; authString += `,realm="${authHeader.realm}"`;
authString += `,nonce="${authHeader.nonce}"`; authString += `,nonce="${authHeader.nonce}"`;
authString += `,uri="${url}"`; authString += `,uri="${uri}"`;
if (authHeader.algorithm) if (authHeader.algorithm) authString += `,algorithm=${authHeader.algorithm}`;
authString += `,algorithm=${authHeader.algorithm}`; if (qop) authString += `,qop=${qop},nc=${nc},cnonce="${cnonce}"`;
if (qop)
authString += `,qop=${qop},nc=${nc},cnonce="${cnonce}"`;
authString += `,response="${hash.digest("hex")}"`; authString += `,response="${hash.digest("hex")}"`;
if (authHeader.opaque) if (authHeader.opaque) authString += `,opaque="${authHeader.opaque}"`;
authString += `,opaque="${authHeader.opaque}"`;
return authString; return authString;
} }
+104 -110
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -27,7 +27,6 @@ let mongoDb;
let mongoCollection; let mongoCollection;
let mongoTimeOffset = 0; let mongoTimeOffset = 0;
function connect(callback) { function connect(callback) {
const REDIS_HOST = config.get("REDIS_HOST"); const REDIS_HOST = config.get("REDIS_HOST");
@@ -36,7 +35,7 @@ function connect(callback) {
const REDIS_PORT = config.get("REDIS_PORT"); const REDIS_PORT = config.get("REDIS_PORT");
const REDIS_DB = config.get("REDIS_DB"); const REDIS_DB = config.get("REDIS_DB");
redisClient = redis.createClient(REDIS_PORT, REDIS_HOST); redisClient = redis.createClient(REDIS_PORT, REDIS_HOST);
redisClient.select(REDIS_DB, function(err) { redisClient.select(REDIS_DB, err => {
exports.get = redisGet; exports.get = redisGet;
exports.set = redisSet; exports.set = redisSet;
exports.del = redisDel; exports.del = redisDel;
@@ -44,191 +43,184 @@ function connect(callback) {
exports.lock = redisLock; exports.lock = redisLock;
callback(err); callback(err);
}); });
} } else {
else {
const mongodb = require("mongodb"); const mongodb = require("mongodb");
const MONGODB_CONNECTION_URL = config.get("MONGODB_CONNECTION_URL"); const MONGODB_CONNECTION_URL = config.get("MONGODB_CONNECTION_URL");
mongodb.MongoClient.connect(MONGODB_CONNECTION_URL, function(err, db) { mongodb.MongoClient.connect(
if (err) MONGODB_CONNECTION_URL,
return callback(err); (err, db) => {
if (err) return callback(err);
mongoDb = db; mongoDb = db;
mongoCollection = db.collection("cache"); mongoCollection = db.collection("cache");
mongoCollection.ensureIndex({"expire": 1}, {expireAfterSeconds: 0}); mongoCollection.ensureIndex({ expire: 1 }, { expireAfterSeconds: 0 });
const now = Date.now(); const now = Date.now();
mongoDb.command({hostInfo: 1}, function(err, res) { mongoDb.command({ hostInfo: 1 }, (err, res) => {
if (err) if (err) return callback(err);
return callback(err); mongoTimeOffset = res.system.currentTime.getTime() - now;
mongoTimeOffset = res.system.currentTime.getTime() - now; exports.get = mongoGet;
exports.get = mongoGet; exports.set = mongoSet;
exports.set = mongoSet; exports.del = mongoDel;
exports.del = mongoDel; exports.pop = mongoPop;
exports.pop = mongoPop; exports.lock = mongoLock;
exports.lock = mongoLock; callback();
callback(); });
}); }
}); );
} }
} }
function disconnect() { function disconnect() {
if (redisClient) if (redisClient) redisClient.quit();
redisClient.quit();
if (mongoDb) if (mongoDb) mongoDb.close();
mongoDb.close();
} }
function redisGet(key, callback) { function redisGet(key, callback) {
if (Array.isArray(key)) if (Array.isArray(key)) redisClient.mget(key, callback);
redisClient.mget(key, callback); else redisClient.get(key, callback);
else
redisClient.get(key, callback);
} }
function redisDel(key, callback) { function redisDel(key, callback) {
redisClient.del(key, callback); redisClient.del(key, callback);
} }
function redisSet(key, value, ttl, callback) { function redisSet(key, value, ttl, callback) {
if (!callback && typeof v === "function") { if (!callback && typeof v === "function") {
callback = ttl; callback = ttl;
ttl = null; ttl = null;
} }
if (!ttl) if (!ttl) ttl = MAX_CACHE_TTL;
ttl = MAX_CACHE_TTL;
redisClient.setex(key, ttl, value, callback); redisClient.setex(key, ttl, value, callback);
} }
function redisPop(key, callback) { function redisPop(key, callback) {
const POP_SCRIPT = "local v=redis.call(\"get\",KEYS[1]);redis.call(\"del\",KEYS[1]);return v;"; const POP_SCRIPT =
'local v=redis.call("get",KEYS[1]);redis.call("del",KEYS[1]);return v;';
redisClient.eval(POP_SCRIPT, 1, key, callback); redisClient.eval(POP_SCRIPT, 1, key, callback);
} }
function redisLock(lockName, ttl, callback) { function redisLock(lockName, ttl, callback) {
const UNLOCK_SCRIPT = "if redis.call(\"get\", KEYS[1]) == ARGV[1] then return redis.call(\"del\", KEYS[1]) else return 0 end"; const UNLOCK_SCRIPT =
const EXTEND_SCRIPT = "if redis.call(\"get\", KEYS[1]) == ARGV[1] then return redis.call(\"pexpire\", KEYS[1], ARGV[2]) else return 0 end"; 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end';
const token = Math.random().toString(36).slice(2); const EXTEND_SCRIPT =
'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("pexpire", KEYS[1], ARGV[2]) else return 0 end';
const token = Math.random()
.toString(36)
.slice(2);
function unlockOrExtend(ttl) { function unlockOrExtend(extendTtl) {
if (!ttl) { if (!extendTtl) {
redisClient.eval(UNLOCK_SCRIPT, 1, lockName, token, function(err, res) { redisClient.eval(UNLOCK_SCRIPT, 1, lockName, token, (err, res) => {
if (err || !res) if (err || !res) throw err || new Error("Lock expired");
throw err || new Error("Lock expired");
});
}
else {
redisClient.eval(EXTEND_SCRIPT, 1, lockName, token, ttl, function(err, res) {
if (err || !res)
throw err || new Error("Lock expired");
}); });
} else {
redisClient.eval(
EXTEND_SCRIPT,
1,
lockName,
token,
extendTtl,
(err, res) => {
if (err || !res) throw err || new Error("Lock expired");
}
);
} }
} }
redisClient.set(lockName, token, "NX", "EX", ttl, function(err, res) { redisClient.set(lockName, token, "NX", "EX", ttl, (err, res) => {
if (err || res) if (err || res) return callback(err, unlockOrExtend);
return callback(err, unlockOrExtend);
setTimeout(function() { setTimeout(() => {
redisLock(lockName, ttl, callback); redisLock(lockName, ttl, callback);
}, 200); }, 200);
}); });
} }
function mongoGet(key, callback) { function mongoGet(key, callback) {
let expire = new Date(Date.now() - mongoTimeOffset); let expire = new Date(Date.now() - mongoTimeOffset);
if (Array.isArray(key)) if (Array.isArray(key)) {
mongoCollection.find({"_id": {"$in": key}}).toArray(function(err, res) { mongoCollection.find({ _id: { $in: key } }).toArray((err, res) => {
if (err) if (err) return callback(err);
return callback(err);
let indices = {}; let indices = {};
key.forEach(function(v, i) { key.forEach((v, i) => {
indices[v] = i; indices[v] = i;
}); });
let values = []; let values = [];
res.forEach(function(r) { res.forEach(r => {
if (r["expire"] > expire) if (r["expire"] > expire) values[indices[r["_id"]]] = r["value"];
values[indices[r["_id"]]] = r["value"];
}); });
callback(null, values); callback(null, values);
}); });
else } else {
mongoCollection.findOne({"_id": {"$in": [key]}}, function(err, res) { mongoCollection.findOne({ _id: { $in: [key] } }, (err, res) => {
if (err || !res) if (err || !res) return callback(err);
return callback(err);
if (res["expire"] > expire) if (res["expire"] > expire) return callback(null, res["value"]);
return callback(null, res["value"]);
callback(); callback();
}); });
}
} }
function mongoDel(key, callback) { function mongoDel(key, callback) {
if (Array.isArray(key)) if (Array.isArray(key))
mongoCollection.remove({"_id": {"$in": key}}, callback); mongoCollection.remove({ _id: { $in: key } }, callback);
else else mongoCollection.remove({ _id: key }, callback);
mongoCollection.remove({"_id": key}, callback);
} }
function mongoSet(key, value, ttl, callback) { function mongoSet(key, value, ttl, callback) {
if (!callback && typeof v === "function") { if (!callback && typeof v === "function") {
callback = ttl; callback = ttl;
ttl = null; ttl = null;
} }
if (!ttl) if (!ttl) ttl = MAX_CACHE_TTL;
ttl = MAX_CACHE_TTL;
let expire = new Date((Date.now() - mongoTimeOffset) + (ttl * 1000)); let expire = new Date(Date.now() - mongoTimeOffset + ttl * 1000);
mongoCollection.save({"_id": key, "value": value, "expire": expire}, callback); mongoCollection.save({ _id: key, value: value, expire: expire }, callback);
} }
function mongoPop(key, callback) { function mongoPop(key, callback) {
mongoCollection.findAndModify({"_id": key}, null, null, {remove: true}, function(err, res) { mongoCollection.findAndModify(
if (err || !res["value"]) { _id: key },
return callback(err); null,
null,
{ remove: true },
(err, res) => {
if (err || !res["value"]) return callback(err);
if (res["value"]["expire"] > new Date(Date.now() - mongoTimeOffset)) if (res["value"]["expire"] > new Date(Date.now() - mongoTimeOffset))
return callback(null, res["value"]["value"]); return callback(null, res["value"]["value"]);
callback(); callback();
}); }
);
} }
function mongoLock(lockName, ttl, callback) { function mongoLock(lockName, ttl, callback) {
const token = Math.random().toString(36).slice(2); const token = Math.random()
.toString(36)
.slice(2);
function unlockOrExtend(ttl) { function unlockOrExtend(extendTtl) {
if (!ttl) { if (!extendTtl) {
mongoCollection.remove({"_id": lockName, "value": token}, function(err, res) { mongoCollection.remove({ _id: lockName, value: token }, (err, res) => {
if (err || res["result"]["n"] !== 1) if (err || res["result"]["n"] !== 1)
throw err || new Error("Lock expired"); throw err || new Error("Lock expired");
}); });
} } else {
else { let expire = new Date(Date.now() - mongoTimeOffset + extendTtl * 1000);
let expire = new Date((Date.now() - mongoTimeOffset) + (ttl * 1000));
mongoCollection.update( mongoCollection.update(
{"_id": lockName, "value": token}, { _id: lockName, value: token },
{"expire": expire}, { expire: expire },
function(err, res) { (err, res) => {
if (err || res["result"]["n"] !== 1) if (err || res["result"]["n"] !== 1)
throw err | new Error("Lock expired"); throw err | new Error("Lock expired");
} }
@@ -237,21 +229,23 @@ function mongoLock(lockName, ttl, callback) {
} }
let expireTest = new Date(Date.now() - mongoTimeOffset); let expireTest = new Date(Date.now() - mongoTimeOffset);
let expireSet = new Date((Date.now() - mongoTimeOffset) + (ttl * 1000)); let expireSet = new Date(Date.now() - mongoTimeOffset + ttl * 1000);
mongoCollection.update( mongoCollection.update(
{"_id": lockName, "expire": {"$lte": expireTest}}, { _id: lockName, expire: { $lte: expireTest } },
{"$set": {"value" : token, "expire": expireSet}}, { $set: { value: token, expire: expireSet } },
{upsert: true}, { upsert: true },
function(err, res) { err => {
if (err && err.code === 11000) if (err && err.code === 11000) {
return setTimeout(function() {mongoLock(lockName, ttl, callback)}, 200); return setTimeout(() => {
mongoLock(lockName, ttl, callback);
}, 200);
}
return callback(err, unlockOrExtend); return callback(err, unlockOrExtend);
} }
); );
} }
exports.connect = connect; exports.connect = connect;
exports.disconnect = disconnect; exports.disconnect = disconnect;
+30 -36
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -30,43 +30,43 @@ let tooManyCrashesTimestamp = 0;
function getDependencyVersions() { function getDependencyVersions() {
let dependencies = [`node@${process.versions.node}`]; let dependencies = [`node@${process.versions.node}`];
try { try {
let npmls = JSON.parse(require("child_process").execSync("npm ls --prod --json --depth 0")); let npmls = JSON.parse(
require("child_process").execSync("npm ls --prod --json --depth 0")
);
for (let k in npmls.dependencies) for (let k in npmls.dependencies)
dependencies.push(`${k}@${npmls.dependencies[k].version}`); dependencies.push(`${k}@${npmls.dependencies[k].version}`);
} catch (err) {} } catch (err) {
// Continue regardless of error
}
return dependencies.join(","); return dependencies.join(",");
} }
function getConfig() { function getConfig() {
let conf = []; let conf = [];
const keys = Object.keys(process.env); const keys = Object.keys(process.env);
for (let k of keys) for (let k of keys) {
if (k.startsWith("GENIEACS_")) { if (k.startsWith("GENIEACS_")) {
const name = k.slice(9); const name = k.slice(9);
const value = process.env[k]; const value = process.env[k];
const def = config.getDefault(name); const def = config.getDefault(name);
if (def == null || value !== `${def}`) if (def == null || value !== `${def}`) conf.push(`${name}=${value}`);
conf.push(`${name}=${value}`);
} }
}
return conf.join(","); return conf.join(",");
} }
function restartWorker(worker, code, signal) { function restartWorker(worker, code, signal) {
let msg = { let msg = {
message: "Worker died", message: "Worker died",
pid: worker.process.pid pid: worker.process.pid
}; };
if (code != null) if (code != null) msg.exitCode = code;
msg.exitCode = code;
if (signal != null) if (signal != null) msg.signal = signal;
msg.signal = signal;
logger.error(msg); logger.error(msg);
@@ -75,14 +75,13 @@ function restartWorker(worker, code, signal) {
if (respawnTimestamp === now) { if (respawnTimestamp === now) {
tooManyCrashesTimestamp = now; tooManyCrashesTimestamp = now;
cluster.fork(); cluster.fork();
return return;
} }
if (now - tooManyCrashesTimestamp > 60000) { if (now - tooManyCrashesTimestamp > 60000) {
process.exitCode = 1; process.exitCode = 1;
cluster.removeListener("exit", restartWorker); cluster.removeListener("exit", restartWorker);
for (let pid in cluster.workers) for (let pid in cluster.workers) cluster.workers[pid].kill();
cluster.workers[pid].kill();
logger.error({ logger.error({
message: "Too many crashes, exiting", message: "Too many crashes, exiting",
@@ -91,18 +90,16 @@ function restartWorker(worker, code, signal) {
return; return;
} }
setTimeout(function() { setTimeout(() => {
if (process.exitCode) return; if (process.exitCode) return;
cluster.fork(); cluster.fork();
}, respawnTimestamp - now); }, respawnTimestamp - now);
} }
function start(service) { function start(service) {
logger.init(service, VERSION); logger.init(service, VERSION);
const config = require("./config"); const SERVICE_ADDRESS = config.get(`${service.toUpperCase()}_INTERFACE`);
const SERVICE_ADDRESS = config.get(`${service.toUpperCase()}_INTERFACE`) const SERVICE_PORT = config.get(`${service.toUpperCase()}_PORT`);
const SERVICE_PORT = config.get(`${service.toUpperCase()}_PORT`)
logger.info({ logger.info({
message: `GenieACS (genieacs-${service}) starting`, message: `GenieACS (genieacs-${service}) starting`,
@@ -112,9 +109,12 @@ function start(service) {
config: getConfig() config: getConfig()
}); });
cluster.on("listening", function(worker, address) { cluster.on("listening", (worker, address) => {
if ((address.addressType === 4 || address.addressType === 6) && if (
address.address === SERVICE_ADDRESS && address.port === SERVICE_PORT) { (address.addressType === 4 || address.addressType === 6) &&
address.address === SERVICE_ADDRESS &&
address.port === SERVICE_PORT
) {
logger.info({ logger.info({
message: "Worker listening", message: "Worker listening",
pid: worker.process.pid, pid: worker.process.pid,
@@ -126,7 +126,7 @@ function start(service) {
cluster.on("exit", restartWorker); cluster.on("exit", restartWorker);
process.on("SIGINT", function() { process.on("SIGINT", () => {
logger.info({ logger.info({
message: "Received signal SIGINT, exiting", message: "Received signal SIGINT, exiting",
pid: process.pid pid: process.pid
@@ -135,15 +135,14 @@ function start(service) {
cluster.removeListener("exit", restartWorker); cluster.removeListener("exit", restartWorker);
}); });
process.on("SIGTERM", function() { process.on("SIGTERM", () => {
logger.info({ logger.info({
message: "Received signal SIGTERM, exiting", message: "Received signal SIGTERM, exiting",
pid: process.pid pid: process.pid
}); });
cluster.removeListener("exit", restartWorker); cluster.removeListener("exit", restartWorker);
for (let pid in cluster.workers) for (let pid in cluster.workers) cluster.workers[pid].kill();
cluster.workers[pid].kill();
}); });
cluster.setupMaster({ cluster.setupMaster({
@@ -153,15 +152,12 @@ function start(service) {
let workerCount = config.get(`${service.toUpperCase()}_WORKER_PROCESSES`); let workerCount = config.get(`${service.toUpperCase()}_WORKER_PROCESSES`);
if (!workerCount) if (!workerCount) workerCount = Math.max(2, require("os").cpus().length);
workerCount = Math.max(2, require("os").cpus().length);
for (let i = 0; i < workerCount; ++ i) for (let i = 0; i < workerCount; ++i) cluster.fork();
cluster.fork();
} }
process.on("uncaughtException", err => {
process.on("uncaughtException", function(err) {
logger.error({ logger.error({
message: "Uncaught exception in master process, exiting", message: "Uncaught exception in master process, exiting",
exception: err, exception: err,
@@ -169,9 +165,7 @@ process.on("uncaughtException", function(err) {
}); });
process.exitCode = 1; process.exitCode = 1;
cluster.removeListener("exit", restartWorker); cluster.removeListener("exit", restartWorker);
for (let pid in cluster.workers) for (let pid in cluster.workers) cluster.workers[pid].kill();
cluster.workers[pid].kill();
}); });
exports.start = start; exports.start = start;
+40 -53
View File
@@ -28,24 +28,30 @@ const OBJECT_TYPE = "[object Object]";
const REGEXP_TYPE = "[object RegExp]"; const REGEXP_TYPE = "[object RegExp]";
const DATE_TYPE = "[object Date]"; const DATE_TYPE = "[object Date]";
const typeOf = (obj) => Object.prototype.toString.call(obj); const typeOf = obj => Object.prototype.toString.call(obj);
function generateDeviceId(deviceIdStruct) { function generateDeviceId(deviceIdStruct) {
// Escapes everything except alphanumerics and underscore // Escapes everything except alphanumerics and underscore
function esc(str) { function esc(str) {
return str.replace(/[^A-Za-z0-9_]/g, (chr) => { return str.replace(/[^A-Za-z0-9_]/g, chr => {
const buf = new Buffer(chr, "utf8"); const buf = new Buffer(chr, "utf8");
let rep = ""; let rep = "";
for (let b of buf) for (let b of buf) rep += "%" + b.toString(16).toUpperCase();
rep += "%" + (b.toString(16).toUpperCase());
return rep; return rep;
}); });
} }
// Guaranteeing globally unique id as defined in TR-069 // Guaranteeing globally unique id as defined in TR-069
if (deviceIdStruct["ProductClass"]) if (deviceIdStruct["ProductClass"]) {
return (esc(deviceIdStruct["OUI"])) + "-" + (esc(deviceIdStruct["ProductClass"])) + "-" + (esc(deviceIdStruct["SerialNumber"])); return (
return (esc(deviceIdStruct["OUI"])) + "-" + (esc(deviceIdStruct["SerialNumber"])); esc(deviceIdStruct["OUI"]) +
"-" +
esc(deviceIdStruct["ProductClass"]) +
"-" +
esc(deviceIdStruct["SerialNumber"])
);
}
return esc(deviceIdStruct["OUI"]) + "-" + esc(deviceIdStruct["SerialNumber"]);
} }
function parseAlias(pattern, start, res) { function parseAlias(pattern, start, res) {
@@ -53,22 +59,21 @@ function parseAlias(pattern, start, res) {
let i = start; let i = start;
while (i < pattern.length && pattern[i] !== "]") { while (i < pattern.length && pattern[i] !== "]") {
const alias = []; const alias = [];
let j = i = parsePath(pattern, i, alias) + 1; let j = (i = parsePath(pattern, i, alias) + 1);
while (pattern[j] !== "]" && pattern[j] !== ",") { while (pattern[j] !== "]" && pattern[j] !== ",") {
if (pattern[j] === "\"" && i === j) { if (pattern[j] === '"' && i === j) {
++j; ++j;
while (pattern[j] !== "\"" || pattern[j - 1] === "\\") { while (pattern[j] !== '"' || pattern[j - 1] === "\\") {
if (++j >= pattern.length) if (++j >= pattern.length)
throw new Error("Invalid alias expression"); throw new Error("Invalid alias expression");
} }
} }
if (++j >= pattern.length) if (++j >= pattern.length) throw new Error("Invalid alias expression");
throw new Error("Invalid alias expression");
} }
let value = pattern.slice(i, j).trim(); let value = pattern.slice(i, j).trim();
i = j; i = j;
if (value[0] === "\"") { if (value[0] === '"') {
try { try {
value = JSON.parse(value); value = JSON.parse(value);
} catch (error) { } catch (error) {
@@ -78,8 +83,7 @@ function parseAlias(pattern, start, res) {
alias.push(value); alias.push(value);
aliases.push(alias); aliases.push(alias);
if (pattern[i] === ",") if (pattern[i] === ",") ++i;
++i;
} }
// Sort to ensure identical expressions have idential string representation // Sort to ensure identical expressions have idential string representation
@@ -87,20 +91,15 @@ function parseAlias(pattern, start, res) {
const jMax = Math.min(a.length, b.length); const jMax = Math.min(a.length, b.length);
for (let j = 0; j < jMax; j += 2) { for (let j = 0; j < jMax; j += 2) {
const kMax = Math.min(a[j].length, b[j].length); const kMax = Math.min(a[j].length, b[j].length);
for (let k = 0; k < kMax; ++ k) { for (let k = 0; k < kMax; ++k) {
if (Array.isArray(a[j][k])) { if (Array.isArray(a[j][k])) {
if (Array.isArray(b[j][k])) if (Array.isArray(b[j][k])) return srt(a[j][k], b[j][k]);
return srt(a[j][k], b[j][k]); else if (b[j][k] == null) return -1;
else if (b[j][k] == null) else return 1;
return -1;
else
return 1;
} else if (a[j][k] == null) { } else if (a[j][k] == null) {
if (b[j][k] == null) if (b[j][k] == null) return 0;
return 0; else return 1;
else } else if (b[j][k] == null || Array.isArray(b[j][k])) {
return 1;
} else if ((b[j][k] == null) || Array.isArray(b[j][k])) {
return -1; return -1;
} else if (a[j][k] > b[j][k]) { } else if (a[j][k] > b[j][k]) {
return 1; return 1;
@@ -109,21 +108,15 @@ function parseAlias(pattern, start, res) {
} }
} }
if (a[j].length > b[j].length) if (a[j].length > b[j].length) return -1;
return -1; else if (a[j].length < b[j].length) return 1;
else if (a[j].length < b[j].length)
return 1;
if (a[j + 1] > b[j + 1]) if (a[j + 1] > b[j + 1]) return -1;
return -1; else if (a[j + 1] < b[j + 1]) return 1;
else if (a[j + 1] < b[j + 1])
return 1;
} }
if (a.length > b.length) if (a.length > b.length) return -1;
return -1; else if (a.length < b.length) return 1;
else if (a.length < b.length)
return 1;
return 0; return 0;
} }
@@ -141,7 +134,7 @@ function parsePath(pattern, start, res) {
// Colon separator is needed for parseAlias // Colon separator is needed for parseAlias
if (i < pattern.length && pattern[i] !== ":") { if (i < pattern.length && pattern[i] !== ":") {
while (true) { for (;;) {
if (pattern[i] === "[") { if (pattern[i] === "[") {
path.alias |= 1 << path.length; path.alias |= 1 << path.length;
i = parseAlias(pattern, i + 1, path) + 1; i = parseAlias(pattern, i + 1, path) + 1;
@@ -151,15 +144,12 @@ function parsePath(pattern, start, res) {
++i; ++i;
const n = pattern.slice(j, i); const n = pattern.slice(j, i);
if (n === "*") if (n === "*") path.wildcard |= 1 << path.length;
path.wildcard |= 1 << path.length;
path.push(n); path.push(n);
} }
if (i >= pattern.length || pattern[i] === ":") if (i >= pattern.length || pattern[i] === ":") break;
break; else if (pattern[i] !== ".") throw new Error("Invalid alias expression");
else if (pattern[i] !== ".")
throw new Error("Invalid alias expression");
++i; ++i;
} }
@@ -167,16 +157,14 @@ function parsePath(pattern, start, res) {
Object.freeze(path); Object.freeze(path);
if (res == null) if (res == null) return path;
return path;
res.push(path); res.push(path);
return i; return i;
} }
function addPathMeta(path) { function addPathMeta(path) {
if (path.alias != null && path.wildcard != null) if (path.alias != null && path.wildcard != null) return path;
return path;
path.alias = 0; path.alias = 0;
path.wildcard = 0; path.wildcard = 0;
@@ -184,8 +172,7 @@ function addPathMeta(path) {
for (let [i, p] of path.entries()) { for (let [i, p] of path.entries()) {
if (Array.isArray(p)) { if (Array.isArray(p)) {
path.alias |= 1 << i; path.alias |= 1 << i;
for (let j = 0; j < p.length; j += 2) for (let j = 0; j < p.length; j += 2) addPathMeta(p[j]);
addPathMeta(p[j]);
} else if (p === "*") { } else if (p === "*") {
path.wildcard |= 1 << i; path.wildcard |= 1 << i;
} }
@@ -197,7 +184,7 @@ function addPathMeta(path) {
// Source: http://stackoverflow.com/a/6969486 // Source: http://stackoverflow.com/a/6969486
function escapeRegExp(str) { function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
} }
exports.UNDEFINED_TYPE = UNDEFINED_TYPE; exports.UNDEFINED_TYPE = UNDEFINED_TYPE;
+86 -85
View File
@@ -21,81 +21,89 @@
const path = require("path"); const path = require("path");
const options = { const options = {
CONFIG_DIR: {type: "path", "default": "config"}, CONFIG_DIR: { type: "path", default: "config" },
MONGODB_CONNECTION_URL: {type: "string", "default": "mongodb://127.0.0.1/genieacs"}, MONGODB_CONNECTION_URL: {
REDIS_PORT: {type: "int", "default": 6379}, type: "string",
REDIS_HOST: {type: "string", "default": ""}, default: "mongodb://127.0.0.1/genieacs"
REDIS_DB: {type: "int", "default": 0}, },
REDIS_PORT: { type: "int", default: 6379 },
REDIS_HOST: { type: "string", default: "" },
REDIS_DB: { type: "int", default: 0 },
CWMP_WORKER_PROCESSES: {type: "int", "default": 0}, CWMP_WORKER_PROCESSES: { type: "int", default: 0 },
CWMP_PORT: {type: "int", "default": 7547}, CWMP_PORT: { type: "int", default: 7547 },
CWMP_INTERFACE: {type: "string", "default": "0.0.0.0"}, CWMP_INTERFACE: { type: "string", default: "0.0.0.0" },
CWMP_SSL: {type: "bool", "default": false}, CWMP_SSL: { type: "bool", default: false },
CWMP_LOG_FILE: {type: "path", "default": ""}, CWMP_LOG_FILE: { type: "path", default: "" },
CWMP_ACCESS_LOG_FILE: {type: "path", "default": ""}, CWMP_ACCESS_LOG_FILE: { type: "path", default: "" },
NBI_WORKER_PROCESSES: {type: "int", "default": 0}, NBI_WORKER_PROCESSES: { type: "int", default: 0 },
NBI_PORT: {type: "int", "default": 7557}, NBI_PORT: { type: "int", default: 7557 },
NBI_INTERFACE: {type: "string", "default": "0.0.0.0"}, NBI_INTERFACE: { type: "string", default: "0.0.0.0" },
NBI_SSL: {type: "bool", "default": false}, NBI_SSL: { type: "bool", default: false },
NBI_LOG_FILE: {type: "path", "default": ""}, NBI_LOG_FILE: { type: "path", default: "" },
NBI_ACCESS_LOG_FILE: {type: "path", "default": ""}, NBI_ACCESS_LOG_FILE: { type: "path", default: "" },
FS_WORKER_PROCESSES: {type: "int", "default": 0}, FS_WORKER_PROCESSES: { type: "int", default: 0 },
FS_PORT: {type: "int", "default": 7567}, FS_PORT: { type: "int", default: 7567 },
FS_INTERFACE: {type: "string", "default": "0.0.0.0"}, FS_INTERFACE: { type: "string", default: "0.0.0.0" },
FS_SSL: {type: "bool", "default": false}, FS_SSL: { type: "bool", default: false },
FS_HOSTNAME: {type: "string", "default": "acs.example.com"}, FS_HOSTNAME: { type: "string", default: "acs.example.com" },
FS_LOG_FILE: {type: "path", "default": ""}, FS_LOG_FILE: { type: "path", default: "" },
FS_ACCESS_LOG_FILE: {type: "path", "default": ""}, FS_ACCESS_LOG_FILE: { type: "path", default: "" },
UDP_CONNECTION_REQUEST_PORT: {type: "int", "default": 0}, UDP_CONNECTION_REQUEST_PORT: { type: "int", default: 0 },
DOWNLOAD_TIMEOUT: {type: "int", "default": 3600}, DOWNLOAD_TIMEOUT: { type: "int", default: 3600 },
EXT_TIMEOUT: {type: "int", "default": 3000}, EXT_TIMEOUT: { type: "int", default: 3000 },
MAX_CACHE_TTL: {type: "int", "default": 86400}, MAX_CACHE_TTL: { type: "int", default: 86400 },
DEBUG: {type: "bool", "default": false}, DEBUG: { type: "bool", default: false },
RETRY_DELAY: {type: "int", "default": 300}, RETRY_DELAY: { type: "int", default: 300 },
SESSION_TIMEOUT: {type: "int", "default": 30}, SESSION_TIMEOUT: { type: "int", default: 30 },
CONNECTION_REQUEST_TIMEOUT: {type: "int", "default": 2000}, CONNECTION_REQUEST_TIMEOUT: { type: "int", default: 2000 },
GPN_NEXT_LEVEL: {type: "int", "default": 0}, GPN_NEXT_LEVEL: { type: "int", default: 0 },
GPV_BATCH_SIZE: {type: "int", "default": 32}, GPV_BATCH_SIZE: { type: "int", default: 32 },
MAX_DEPTH: {type: "int", "default": 16}, MAX_DEPTH: { type: "int", default: 16 },
COOKIES_PATH: {type: "string"}, COOKIES_PATH: { type: "string" },
LOG_FORMAT: {type: "string", "default": "simple"}, LOG_FORMAT: { type: "string", default: "simple" },
ACCESS_LOG_FORMAT: {type: "string", "default": ""}, ACCESS_LOG_FORMAT: { type: "string", default: "" },
MAX_CONCURRENT_REQUESTS: {type: "int", "default": 20}, MAX_CONCURRENT_REQUESTS: { type: "int", default: 20 },
DATETIME_MILLISECONDS: {type: "bool", "default": true}, DATETIME_MILLISECONDS: { type: "bool", default: true },
BOOLEAN_LITERAL: {type: "bool", "default": true}, BOOLEAN_LITERAL: { type: "bool", default: true },
CONNECTION_REQUEST_ALLOW_BASIC_AUTH: {type: "bool", "default": false}, CONNECTION_REQUEST_ALLOW_BASIC_AUTH: { type: "bool", default: false },
MAX_COMMIT_ITERATIONS: {type: "int", "default": 32}, MAX_COMMIT_ITERATIONS: { type: "int", default: 32 },
// XML configuration // XML configuration
XML_RECOVER: {type: "bool", "default": false}, XML_RECOVER: { type: "bool", default: false },
XML_IGNORE_ENC: {type: "bool", "default": false}, XML_IGNORE_ENC: { type: "bool", default: false },
XML_FORMAT: {type: "bool", "default": false}, XML_FORMAT: { type: "bool", default: false },
XML_NO_DECL: {type: "bool", "default": false}, XML_NO_DECL: { type: "bool", default: false },
XML_NO_EMPTY: {type: "bool", "default": false}, XML_NO_EMPTY: { type: "bool", default: false },
XML_IGNORE_NAMESPACE: {type: "bool", "default": false}, XML_IGNORE_NAMESPACE: { type: "bool", default: false },
// Should probably never be changed // Should probably never be changed
DEVICE_ONLINE_THRESHOLD: {type: "int", "default": 4000} DEVICE_ONLINE_THRESHOLD: { type: "int", default: 4000 }
}; };
const allConfig = {}; const allConfig = {};
function setConfig(name, value, commandLineArgument) { function setConfig(name, value, commandLineArgument) {
if (allConfig[name] != null) if (allConfig[name] != null) return true;
return true;
// For compatibility with v1.0 // For compatibility with v1.0
if (name === "PRESETS_CACHE_DURATION" || name === "presets-cache-duration") if (name === "PRESETS_CACHE_DURATION" || name === "presets-cache-duration")
setConfig("MAX_CACHE_TTL", value); setConfig("MAX_CACHE_TTL", value);
if (name === "GET_PARAMETER_NAMES_DEPTH_THRESHOLD" || name === "get-parameter-names-depth-threshold") if (
name === "GET_PARAMETER_NAMES_DEPTH_THRESHOLD" ||
name === "get-parameter-names-depth-threshold"
)
setConfig("GPN_NEXT_LEVEL", value); setConfig("GPN_NEXT_LEVEL", value);
if (name === "TASK_PARAMETERS_BATCH_SIZE" || name === "task-parameters-batch-size") if (
name === "TASK_PARAMETERS_BATCH_SIZE" ||
name === "task-parameters-batch-size"
)
setConfig("GPV_BATCH_SIZE", value); setConfig("GPV_BATCH_SIZE", value);
if (name === "IGNORE_XML_NAMESPACES" || name === "ignore-xml-namespaces") if (name === "IGNORE_XML_NAMESPACES" || name === "ignore-xml-namespaces")
@@ -107,20 +115,22 @@ function setConfig(name, value, commandLineArgument) {
if (name === "XML_PARSE_RECOVER" || name === "xml-parse-recover") if (name === "XML_PARSE_RECOVER" || name === "xml-parse-recover")
setConfig("XML_RECOVER", value); setConfig("XML_RECOVER", value);
if (name === "FS_IP" || name === "fs-ip") if (name === "FS_IP" || name === "fs-ip") setConfig("FS_HOSTNAME", value);
setConfig("FS_HOSTNAME", value);
function cast(val, type) { function cast(val, type) {
switch (type) { switch (type) {
case "int": case "int":
return Number(val); return Number(val);
case "bool": case "bool":
return ["true", "on", "yes", "1"].includes(String(val).trim().toLowerCase()); return ["true", "on", "yes", "1"].includes(
String(val)
.trim()
.toLowerCase()
);
case "string": case "string":
return String(val); return String(val);
case "path": case "path":
if (!val) if (!val) return "";
return "";
return path.resolve(val); return path.resolve(val);
default: default:
return null; return null;
@@ -130,8 +140,7 @@ function setConfig(name, value, commandLineArgument) {
let _value = null; let _value = null;
for (let [optionName, optionDetails] of Object.entries(options)) { for (let [optionName, optionDetails] of Object.entries(options)) {
let n = optionName; let n = optionName;
if (commandLineArgument) if (commandLineArgument) n = n.toLowerCase().replace(/_/g, "-");
n = n.toLowerCase().replace(/_/g, "-");
if (name === n) { if (name === n) {
_value = cast(value, optionDetails.type); _value = cast(value, optionDetails.type);
@@ -163,55 +172,47 @@ while (argv.length) {
} }
// Environment variable // Environment variable
for (const [k, v] of Object.entries(process.env)) { for (const [k, v] of Object.entries(process.env))
if (k.startsWith("GENIEACS_")) if (k.startsWith("GENIEACS_")) setConfig(k.slice(9), v);
setConfig(k.slice(9), v);
}
// Use default config dir if none defined // Use default config dir if none defined
setConfig("CONFIG_DIR", options["CONFIG_DIR"]["default"]); setConfig("CONFIG_DIR", options["CONFIG_DIR"]["default"]);
// Configuration file // Configuration file
const configFile = require(path.resolve(allConfig.CONFIG_DIR, "config")); const configFile = require(path.resolve(allConfig.CONFIG_DIR, "config"));
for (const [k, v] of Object.entries(configFile)) for (const [k, v] of Object.entries(configFile)) {
if (!setConfig(k, v)) if (!setConfig(k, v))
// Pass as environment variable to be accessable by extensions // Pass as environment variable to be accessable by extensions
process.env[`GENIEACS_${k}`] = `${v}`; process.env[`GENIEACS_${k}`] = `${v}`;
}
// Defaults // Defaults
for (const [k, v] of Object.entries(options)) for (const [k, v] of Object.entries(options))
if (v["default"] != null) if (v["default"] != null) setConfig(k, v["default"]);
setConfig(k, v["default"]);
function get(optionName, deviceId) { function get(optionName, deviceId) {
if (!deviceId) if (!deviceId) return allConfig[optionName];
return allConfig[optionName];
optionName = `${optionName}-${deviceId}`; optionName = `${optionName}-${deviceId}`;
let v = allConfig[optionName]; let v = allConfig[optionName];
if (v != null) if (v != null) return v;
return v;
let i = optionName.lastIndexOf("-"); let i = optionName.lastIndexOf("-");
v = allConfig[optionName.slice(0, i)]; v = allConfig[optionName.slice(0, i)];
if (v != null) if (v != null) return v;
return v;
i = optionName.lastIndexOf("-", i - 1); i = optionName.lastIndexOf("-", i - 1);
v = allConfig[optionName.slice(0, i)]; v = allConfig[optionName.slice(0, i)];
if (v != null) if (v != null) return v;
return v;
i = optionName.lastIndexOf("-", i - 1); i = optionName.lastIndexOf("-", i - 1);
v = allConfig[optionName.slice(0, i)]; v = allConfig[optionName.slice(0, i)];
if (v != null) if (v != null) return v;
return v;
i = optionName.lastIndexOf("-", i - 1); i = optionName.lastIndexOf("-", i - 1);
if (i > 0) { if (i > 0) {
v = allConfig[optionName.slice(0, i)]; v = allConfig[optionName.slice(0, i)];
if (v != null) if (v != null) return v;
return v;
} }
return null; return null;
@@ -219,12 +220,10 @@ function get(optionName, deviceId) {
function getDefault(optionName) { function getDefault(optionName) {
const option = options[optionName]; const option = options[optionName];
if (!option) if (!option) return null;
return null;
let val = option["default"]; let val = option["default"];
if (val && option.type === "path") if (val && option.type === "path") val = path.resolve(val);
val = path.resolve(val);
return val; return val;
} }
@@ -232,7 +231,9 @@ function getDefault(optionName) {
// Load authentication scripts // Load authentication scripts
try { try {
exports.auth = require(path.resolve(allConfig.CONFIG_DIR, "auth")); exports.auth = require(path.resolve(allConfig.CONFIG_DIR, "auth"));
} catch (error) {} } catch (error) {
// No auth.js exists
}
exports.get = get; exports.get = get;
exports.getDefault = getDefault; exports.getDefault = getDefault;
+59 -31
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -25,8 +25,7 @@ const scheduling = require("./scheduling");
const MAX_DEPTH = config.get("MAX_DEPTH"); const MAX_DEPTH = config.get("MAX_DEPTH");
function refresh(sessionContext, provision, declarations) {
function refresh(sessionContext, provision, declarations, startRevision, endRevision) {
let path = common.parsePath(provision[1]).slice(); let path = common.parsePath(provision[1]).slice();
let l = path.length; let l = path.length;
path.length = MAX_DEPTH; path.length = MAX_DEPTH;
@@ -35,70 +34,100 @@ function refresh(sessionContext, provision, declarations, startRevision, endRevi
let offset = scheduling.variance(sessionContext.deviceId, every); let offset = scheduling.variance(sessionContext.deviceId, every);
let t = scheduling.interval(sessionContext.timestamp, every, offset); let t = scheduling.interval(sessionContext.timestamp, every, offset);
for (let i = l; i < path.length; ++ i) for (let i = l; i < path.length; ++i) {
declarations.push([path.slice(0, i), t, {object: 1, writable: 1, value: t}]); declarations.push([
path.slice(0, i),
t,
{ object: 1, writable: 1, value: t }
]);
}
return true; return true;
} }
function value(sessionContext, provision, declarations) {
function value(sessionContext, provision, declarations, startRevision, endRevision) {
let path = common.parsePath(provision[1]); let path = common.parsePath(provision[1]);
declarations.push([path, 1, {value: 1}, null, {value: [provision[2]]}]); declarations.push([path, 1, { value: 1 }, null, { value: [provision[2]] }]);
return true; return true;
} }
function tag(sessionContext, provision, declarations) {
function tag(sessionContext, provision, declarations, startRevision, endRevision) {
let path = ["Tags", provision[1]]; let path = ["Tags", provision[1]];
declarations.push([path, 1, {value: 1}, null, {value: [provision[2]]}]); declarations.push([path, 1, { value: 1 }, null, { value: [provision[2]] }]);
return true; return true;
} }
function reboot(sessionContext, provision, declarations) {
function reboot(sessionContext, provision, declarations, startRevision, endRevision) { declarations.push([
declarations.push([["Reboot"], 1, {value: 1}, null, {value: [sessionContext.timestamp]}]); ["Reboot"],
1,
{ value: 1 },
null,
{ value: [sessionContext.timestamp] }
]);
return true; return true;
} }
function reset(sessionContext, provision, declarations) {
function reset(sessionContext, provision, declarations, startRevision, endRevision) { declarations.push([
declarations.push([["FactoryReset"], 1, {value: 1}, null, {value: [sessionContext.timestamp]}]); ["FactoryReset"],
1,
{ value: 1 },
null,
{ value: [sessionContext.timestamp] }
]);
return true; return true;
} }
function download(sessionContext, provision, declarations) {
function download(sessionContext, provision, declarations, startRevision, endRevision) { let alias = [
let alias = [["FileType"], provision[1] || "", ["FileName"], provision[2] || "", ["FileType"],
["TargetFileName"], provision[3] || ""]; provision[1] || "",
["FileName"],
provision[2] || "",
["TargetFileName"],
provision[3] || ""
];
declarations.push([["Downloads", alias], 1, {}, 1]); declarations.push([["Downloads", alias], 1, {}, 1]);
declarations.push([["Downloads", alias, "Download"], 1, {value: 1}, null, declarations.push([
{value: [sessionContext.timestamp]}]); ["Downloads", alias, "Download"],
1,
{ value: 1 },
null,
{ value: [sessionContext.timestamp] }
]);
return true; return true;
} }
function instances(
function instances(sessionContext, provision, declarations, startRevision, endRevision) { sessionContext,
provision,
declarations,
startRevision,
endRevision
) {
let count = Number(provision[2]); let count = Number(provision[2]);
if (Number.isNaN(count)) if (Number.isNaN(count)) return true;
return true;
let path = common.parsePath(provision[1]); let path = common.parsePath(provision[1]);
if (provision[2][0] === "+" || provision[2][0] === "-") { if (provision[2][0] === "+" || provision[2][0] === "-") {
declarations.push([path, 1]); declarations.push([path, 1]);
if (endRevision === startRevision) if (endRevision === startRevision) return false;
return false;
let unpacked = device.unpack(sessionContext.deviceData, path, startRevision + 1); let unpacked = device.unpack(
sessionContext.deviceData,
path,
startRevision + 1
);
count = Math.max(0, unpacked.length + count); count = Math.max(0, unpacked.length + count);
} }
@@ -107,7 +136,6 @@ function instances(sessionContext, provision, declarations, startRevision, endRe
return true; return true;
} }
exports.refresh = refresh; exports.refresh = refresh;
exports.value = value; exports.value = value;
exports.tag = tag; exports.tag = tag;
+133 -134
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -18,97 +18,88 @@
*/ */
"use strict"; "use strict";
const common = require('./common');
const CHANGE_FLAGS = { const CHANGE_FLAGS = {
object: 2, object: 2,
writable: 4, writable: 4,
value: 8 value: 8
}; };
function parseBool(v) { function parseBool(v) {
v = "" + v; v = "" + v;
if (v === "true" || v === "TRUE" || v === "True" || v === "1") if (v === "true" || v === "TRUE" || v === "True" || v === "1") return true;
return true;
else if (v === "false" || v === "FALSE" || v === "False" || v === "0") else if (v === "false" || v === "FALSE" || v === "False" || v === "0")
return false; return false;
else else return null;
return null;
} }
function sanitizeParameterValue(parameterValue) { function sanitizeParameterValue(parameterValue) {
if (parameterValue[0] != null) { if (parameterValue[0] != null) {
switch (parameterValue[1]) { switch (parameterValue[1]) {
case 'xsd:boolean': case "xsd:boolean":
if (typeof parameterValue[0] !== 'boolean') { if (typeof parameterValue[0] !== "boolean") {
let v = parseBool(parameterValue[0]); let v = parseBool(parameterValue[0]);
if (v == null) if (v == null) v = "" + parameterValue[0];
v = '' + parameterValue[0];
parameterValue[0] = v; parameterValue[0] = v;
} }
break; break;
case 'xsd:string': case "xsd:string":
case 'xsd:base64': case "xsd:base64":
case 'xsd:hexBinary': case "xsd:hexBinary":
if (typeof parameterValue[0] !== 'string') { if (typeof parameterValue[0] !== "string")
parameterValue[0] = '' + parameterValue[0]; parameterValue[0] = "" + parameterValue[0];
}
break; break;
case 'xsd:int': case "xsd:int":
case 'xsd:unsignedInt': case "xsd:unsignedInt":
if (typeof parameterValue[0] !== 'number') { if (typeof parameterValue[0] !== "number") {
let v = parseInt(parameterValue[0]); let v = parseInt(parameterValue[0]);
if (isNaN(v)) if (isNaN(v)) v = "" + parameterValue[0];
v = '' + parameterValue[0];
parameterValue[0] = v; parameterValue[0] = v;
} }
break; break;
case 'xsd:dateTime': case "xsd:dateTime":
if (typeof parameterValue[0] !== 'number') { if (typeof parameterValue[0] !== "number") {
// Don't use parseInt because it reads date string as a number // Don't use parseInt because it reads date string as a number
let v = +parameterValue[0]; let v = +parameterValue[0];
if (isNaN(v)) { if (isNaN(v)) {
v = Date.parse(parameterValue[0]); v = Date.parse(parameterValue[0]);
if (isNaN(v)) if (isNaN(v)) v = "" + parameterValue[0];
v = '' + parameterValue[0];
} }
parameterValue[0] = v; parameterValue[0] = v;
} }
break; break;
default: default:
if (parameterValue[1] != null) if (parameterValue[1] != null) {
throw new Error(`Parameter value type "${parameterValue[1]}" not recognized.`); throw new Error(
`Parameter value type "${parameterValue[1]}" not recognized.`
);
}
parameterValue[0] = JSON.parse(JSON.stringify(parameterValue[0])); parameterValue[0] = JSON.parse(JSON.stringify(parameterValue[0]));
break; break;
} }
} }
return parameterValue; return parameterValue;
} }
function getAliasDeclarations(path, timestamp) { function getAliasDeclarations(path, timestamp) {
var decs = []; var decs = [];
function recursive(pattern, prefix) { function recursive(p, prefix) {
var pattern = prefix.concat(pattern); const pattern = prefix.concat(p);
for (let i = prefix.length; i < pattern.length; ++ i) { for (let i = prefix.length; i < pattern.length; ++i) {
if (Array.isArray(pattern[i])) { if (Array.isArray(pattern[i])) {
let pat = pattern[i]; let pat = pattern[i];
pattern[i] = '*'; pattern[i] = "*";
for (let j = 0; j < pat.length; j += 2) for (let j = 0; j < pat.length; j += 2)
recursive(pat[j], pattern.slice(0, i + 1)); recursive(pat[j], pattern.slice(0, i + 1));
} }
} }
if (prefix.length) if (prefix.length) decs.push([pattern, timestamp, { value: timestamp }]);
decs.push([pattern, timestamp, {value: timestamp}]); else decs.push([pattern, timestamp]);
else
decs.push([pattern, timestamp]);
} }
recursive(path, []); recursive(path, []);
@@ -116,44 +107,51 @@ function getAliasDeclarations(path, timestamp) {
return decs; return decs;
} }
function unpack(deviceData, path, revision) { function unpack(deviceData, path, revision) {
let allMatches = []; let allMatches = [];
if (!path.alias) { if (!path.alias) {
for (let p of deviceData.paths.find(path, false, true)) for (let p of deviceData.paths.find(path, false, true))
if (deviceData.attributes.has(p, revision)) if (deviceData.attributes.has(p, revision)) allMatches.push(p);
allMatches.push(p); } else {
}
else {
let wildcardPath = path.slice(); let wildcardPath = path.slice();
wildcardPath.wildcard = path.wildcard; wildcardPath.wildcard = path.wildcard;
for (let i = 0; i < wildcardPath.length; ++ i) for (let i = 0; i < wildcardPath.length; ++i) {
if (Array.isArray(wildcardPath[i])) { if (Array.isArray(wildcardPath[i])) {
wildcardPath[i] = '*'; wildcardPath[i] = "*";
wildcardPath.wildcard |= 1 << i; wildcardPath.wildcard |= 1 << i;
} }
}
for (let p of deviceData.paths.find(wildcardPath, false, true)) for (let p of deviceData.paths.find(wildcardPath, false, true))
if (deviceData.attributes.has(p, revision)) if (deviceData.attributes.has(p, revision)) allMatches.push(p);
allMatches.push(p);
for (let i = path.length - 1; i >= 0; -- i) { for (let i = path.length - 1; i >= 0; --i) {
if (Array.isArray(path[i])) { if (Array.isArray(path[i])) {
for (let j = 0; j < path[i].length; j += 2) { for (let j = 0; j < path[i].length; j += 2) {
let p = wildcardPath.slice(0, i + 1).concat(path[i][j]); let p = wildcardPath.slice(0, i + 1).concat(path[i][j]);
p.alias = (wildcardPath.alias & ((1 << (i + 1)) - 1)) + path[i][j].alias << (i + 1); p.alias =
p.wildcard = (wildcardPath.wildcard & ((1 << (i + 1)) - 1)) + path[i][j].wildcard << (i + 1); ((wildcardPath.alias & ((1 << (i + 1)) - 1)) + path[i][j].alias) <<
(i + 1);
p.wildcard =
((wildcardPath.wildcard & ((1 << (i + 1)) - 1)) +
path[i][j].wildcard) <<
(i + 1);
let unpacked = unpack(deviceData, p, revision); let unpacked = unpack(deviceData, p, revision);
let filtered = []; let filtered = [];
for (let up of unpacked) { for (let up of unpacked) {
let attributes = deviceData.attributes.get(up, revision); let attributes = deviceData.attributes.get(up, revision);
if (attributes && attributes.value && attributes.value[1] && if (
sanitizeParameterValue([path[i][j + 1], attributes.value[1][1]])[0] === attributes.value[1][0]) { attributes &&
for (let m = 0; m < allMatches.length; ++ m) { attributes.value &&
attributes.value[1] &&
sanitizeParameterValue([
path[i][j + 1],
attributes.value[1][1]
])[0] === attributes.value[1][0]
) {
for (let m = 0; m < allMatches.length; ++m) {
let k; let k;
for (k = i; k >= 0; -- k) for (k = i; k >= 0; --k) if (allMatches[m][k] != up[k]) break;
if (allMatches[m][k] != up[k])
break
if (k < 0) { if (k < 0) {
filtered.push(allMatches[m]); filtered.push(allMatches[m]);
@@ -168,22 +166,20 @@ function unpack(deviceData, path, revision) {
} }
} }
allMatches.sort(function(a, b) { allMatches.sort((a, b) => {
for (let i = 0; i < a.length; ++ i) { for (let i = 0; i < a.length; ++i) {
if (a === b) if (a === b) continue;
continue;
// Use numeric sorting for numbers // Use numeric sorting for numbers
let ia = parseInt(a), ib = parseInt(b); let ia = parseInt(a),
ib = parseInt(b);
if (ia.toString() === a && ib.toString() === b) { if (ia.toString() === a && ib.toString() === b) {
a = ia; a = ia;
b = ib; b = ib;
} }
if (a < b) if (a < b) return -1;
return -1; else return 1;
else
return 1;
} }
return 0; return 0;
}); });
@@ -191,7 +187,6 @@ function unpack(deviceData, path, revision) {
return allMatches; return allMatches;
} }
function clear(deviceData, path, timestamp, attributes, changeFlags = 0) { function clear(deviceData, path, timestamp, attributes, changeFlags = 0) {
let changeTrackers = {}; let changeTrackers = {};
@@ -205,28 +200,34 @@ function clear(deviceData, path, timestamp, attributes, changeFlags = 0) {
attributes.value = attributes.object; attributes.value = attributes.object;
} }
for (let p of deviceData.paths.find(path, true, true, descendantsTimestamp ? 99 : path.length)) { for (let p of deviceData.paths.find(
path,
true,
true,
descendantsTimestamp ? 99 : path.length
)) {
let tracker = deviceData.trackers.get(p); let tracker = deviceData.trackers.get(p);
for (let k in tracker) for (let k in tracker) changeTrackers[k] |= tracker[k];
changeTrackers[k] |= tracker[k];
let currentTimestamp = deviceData.timestamps.get(p); let currentTimestamp = deviceData.timestamps.get(p);
if (currentTimestamp === undefined) if (currentTimestamp === undefined) continue;
continue;
if (timestamp > currentTimestamp || if (
(descendantsTimestamp > currentTimestamp && p.length > path.length)) { timestamp > currentTimestamp ||
(descendantsTimestamp > currentTimestamp && p.length > path.length)
) {
deviceData.timestamps.delete(p); deviceData.timestamps.delete(p);
deviceData.attributes.delete(p); deviceData.attributes.delete(p);
changeFlags |= 1; changeFlags |= 1;
} } else if (attributes && p.length == path.length) {
else if (attributes && p.length == path.length) {
let currentAttributes = deviceData.attributes.get(p); let currentAttributes = deviceData.attributes.get(p);
if (currentAttributes) { if (currentAttributes) {
let newAttrs; let newAttrs;
for (let attrName in attributes) { for (let attrName in attributes) {
if (attrName in currentAttributes && if (
attributes[attrName] > currentAttributes[attrName][0]) { attrName in currentAttributes &&
attributes[attrName] > currentAttributes[attrName][0]
) {
changeFlags |= CHANGE_FLAGS[attrName]; changeFlags |= CHANGE_FLAGS[attrName];
if (!newAttrs) { if (!newAttrs) {
newAttrs = Object.assign({}, currentAttributes); newAttrs = Object.assign({}, currentAttributes);
@@ -241,27 +242,25 @@ function clear(deviceData, path, timestamp, attributes, changeFlags = 0) {
// Note: For performance, we're merging all changes together rather than // Note: For performance, we're merging all changes together rather than
// mark changes based the exact parameters affected. // mark changes based the exact parameters affected.
for (let k in changeTrackers) { for (let k in changeTrackers)
if (changeTrackers[k] & changeFlags) if (changeTrackers[k] & changeFlags) deviceData.changes.add(k);
deviceData.changes.add(k);
}
} }
function compareEquality(a, b) { function compareEquality(a, b) {
var t = typeof a; var t = typeof a;
if (a === null || if (
a === undefined || a === null ||
t === 'number' || a === undefined ||
t === 'boolean' || t === "number" ||
t === 'string' || t === "boolean" ||
t === 'symbol') t === "string" ||
t === "symbol"
)
return a === b; return a === b;
return JSON.stringify(a) === JSON.stringify(b); return JSON.stringify(a) === JSON.stringify(b);
} }
function set(deviceData, path, timestamp, attributes, toClear) { function set(deviceData, path, timestamp, attributes, toClear) {
path = deviceData.paths.add(path); path = deviceData.paths.add(path);
@@ -269,20 +268,25 @@ function set(deviceData, path, timestamp, attributes, toClear) {
let currentAttributes; let currentAttributes;
if (path.wildcard) if (path.wildcard) attributes = undefined;
attributes = undefined;
else if (currentTimestamp) else if (currentTimestamp)
currentAttributes = deviceData.attributes.get(path); currentAttributes = deviceData.attributes.get(path);
let changeFlags = 0; let changeFlags = 0;
if (attributes) { if (attributes) {
if (attributes.value && attributes.value[1] if (
&& attributes.value[0] >= (attributes.object ? attributes.object[0] : 0)) attributes.value &&
attributes.value[1] &&
attributes.value[0] >= (attributes.object ? attributes.object[0] : 0)
)
attributes.object = [attributes.value[0], 0]; attributes.object = [attributes.value[0], 0];
if (attributes.object && attributes.object[1] if (
&& attributes.object[0] >= (attributes.value ? attributes.value[0] : 0)) attributes.object &&
attributes.object[1] &&
attributes.object[0] >= (attributes.value ? attributes.value[0] : 0)
)
attributes.value = [attributes.object[0]]; attributes.value = [attributes.object[0]];
let newAttributes = Object.assign({}, currentAttributes, attributes); let newAttributes = Object.assign({}, currentAttributes, attributes);
@@ -294,35 +298,45 @@ function set(deviceData, path, timestamp, attributes, toClear) {
changeFlags |= CHANGE_FLAGS[attrName]; changeFlags |= CHANGE_FLAGS[attrName];
else if (attributes[attrName][0] <= currentAttributes[attrName][0]) else if (attributes[attrName][0] <= currentAttributes[attrName][0])
newAttributes[attrName] = currentAttributes[attrName]; newAttributes[attrName] = currentAttributes[attrName];
else if (!compareEquality(attributes[attrName][1], currentAttributes[attrName][1])) else if (
!compareEquality(
attributes[attrName][1],
currentAttributes[attrName][1]
)
)
changeFlags |= CHANGE_FLAGS[attrName]; changeFlags |= CHANGE_FLAGS[attrName];
} }
} } else {
else
changeFlags |= 1; changeFlags |= 1;
}
deviceData.attributes.set(path, newAttributes); deviceData.attributes.set(path, newAttributes);
if (!(timestamp <= currentTimestamp)) { if (!(timestamp <= currentTimestamp)) {
deviceData.timestamps.set(path, timestamp); deviceData.timestamps.set(path, timestamp);
if (path.length > 1) if (path.length > 1) {
toClear = set(deviceData, path.slice(0, path.length - 1), timestamp, toClear = set(
{object: [timestamp, 1]}, toClear); deviceData,
path.slice(0, path.length - 1),
timestamp,
{ object: [timestamp, 1] },
toClear
);
}
} }
} } else if (!(timestamp <= currentTimestamp)) {
else if (!(timestamp <= currentTimestamp)) {
deviceData.timestamps.set(path, timestamp); deviceData.timestamps.set(path, timestamp);
if (currentAttributes) { if (currentAttributes) {
deviceData.attributes.delete(path); deviceData.attributes.delete(path);
changeFlags |= 1; changeFlags |= 1;
} } else if (path.wildcard) {
else if (path.wildcard) { for (let p of deviceData.paths.find(path, false, true, path.length)) {
for (let p of deviceData.paths.find(path, false, true, path.length))
if (timestamp > deviceData.timestamps.get(p)) { if (timestamp > deviceData.timestamps.get(p)) {
toClear = toClear || []; toClear = toClear || [];
toClear.push([p, timestamp]); toClear.push([p, timestamp]);
} }
}
} }
} }
@@ -330,18 +344,14 @@ function set(deviceData, path, timestamp, attributes, toClear) {
if (changeFlags & 1) { if (changeFlags & 1) {
toClear = toClear || []; toClear = toClear || [];
toClear.push([path, timestamp, null, changeFlags]); toClear.push([path, timestamp, null, changeFlags]);
} } else if (changeFlags & CHANGE_FLAGS.object) {
else if (changeFlags & CHANGE_FLAGS.object) {
toClear = toClear || []; toClear = toClear || [];
toClear.push([path, 0, {object: attributes.object[0]}, changeFlags]); toClear.push([path, 0, { object: attributes.object[0] }, changeFlags]);
} } else {
else {
for (let p of deviceData.paths.find(path, true, false, path.length)) { for (let p of deviceData.paths.find(path, true, false, path.length)) {
let tracker = deviceData.trackers.get(p); let tracker = deviceData.trackers.get(p);
for (let k in tracker) { for (let k in tracker)
if (tracker[k] & changeFlags) if (tracker[k] & changeFlags) deviceData.changes.add(k);
deviceData.changes.add(k);
}
} }
} }
} }
@@ -349,14 +359,11 @@ function set(deviceData, path, timestamp, attributes, toClear) {
return toClear; return toClear;
} }
function track(deviceData, path, marker, attributes) { function track(deviceData, path, marker, attributes) {
path = deviceData.paths.add(path); path = deviceData.paths.add(path);
let f = 1; let f = 1;
if (attributes) if (attributes) for (let attrName of attributes) f |= CHANGE_FLAGS[attrName];
for (let attrName of attributes)
f |= CHANGE_FLAGS[attrName];
let cur = deviceData.trackers.get(path); let cur = deviceData.trackers.get(path);
if (!cur) { if (!cur) {
@@ -367,25 +374,17 @@ function track(deviceData, path, marker, attributes) {
cur[marker] |= f; cur[marker] |= f;
} }
function clearTrackers(deviceData, tracker) { function clearTrackers(deviceData, tracker) {
if (Array.isArray(tracker)) { if (Array.isArray(tracker)) {
deviceData.trackers.forEach(function(v, k) { for (let v of deviceData.trackers.values())
for (let t of tracker) for (let t of tracker) delete v[t];
delete v[t]; for (let t of tracker) deviceData.changes.delete(t);
}); } else {
for (let t of tracker) for (let v of deviceData.trackers.values()) delete v[tracker];
deviceData.changes.delete(t);
}
else {
deviceData.trackers.forEach(function(v, k) {
delete v[tracker];
});
deviceData.changes.delete(tracker); deviceData.changes.delete(tracker);
} }
} }
exports.sanitizeParameterValue = sanitizeParameterValue; exports.sanitizeParameterValue = sanitizeParameterValue;
exports.getAliasDeclarations = getAliasDeclarations; exports.getAliasDeclarations = getAliasDeclarations;
exports.unpack = unpack; exports.unpack = unpack;
+12 -13
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -23,11 +23,9 @@ const fileName = process.argv[2];
let script; let script;
function errorToFault(err) { function errorToFault(err) {
if (!err) if (!err) return null;
return null;
if (!err.name) if (!err.name) return { code: "ext", message: `${err}` };
return {code: "ext", message: `${err}`};
let fault = { let fault = {
code: `ext.${err.name}`, code: `ext.${err.name}`,
@@ -41,7 +39,9 @@ function errorToFault(err) {
if (err.stack) { if (err.stack) {
fault.detail.stack = err.stack; fault.detail.stack = err.stack;
// Trim the stack trace // Trim the stack trace
let stackTrimIndex = fault.detail.stack.match(/\s+at\s[^\s]+\s\(.*extension-wrapper\.js:.+\)/); let stackTrimIndex = fault.detail.stack.match(
/\s+at\s[^\s]+\s\(.*extension-wrapper\.js:.+\)/
);
if (stackTrimIndex) if (stackTrimIndex)
fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index); fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index);
} }
@@ -49,16 +49,16 @@ function errorToFault(err) {
return fault; return fault;
} }
process.on("uncaughtException", function(err) { process.on("uncaughtException", err => {
let fault = errorToFault(err); let fault = errorToFault(err);
jobs.forEach(function(jobId) { jobs.forEach(jobId => {
process.send([jobId, fault]); process.send([jobId, fault]);
}); });
jobs.clear(); jobs.clear();
process.disconnect(); process.disconnect();
}); });
process.on("message", function(message) { process.on("message", message => {
jobs.add(message[0]); jobs.add(message[0]);
if (!script) { if (!script) {
@@ -78,13 +78,12 @@ process.on("message", function(message) {
return; return;
} }
script[funcName](message[1].slice(1), function(err, res) { script[funcName](message[1].slice(1), (err, res) => {
if (!jobs.delete(message[0])) if (!jobs.delete(message[0])) return;
return;
process.send([message[0], errorToFault(err), res]); process.send([message[0], errorToFault(err), res]);
}); });
}); });
// Ignore SIGINT // Ignore SIGINT
process.on('SIGINT', function() {}); process.on("SIGINT", () => {});
+23 -30
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -19,11 +19,11 @@
"use strict"; "use strict";
const childProcess = require("child_process"); const childProcess = require("child_process");
const crypto = require ("crypto"); const crypto = require("crypto");
const config = require("./config"); const config = require("./config");
const TIMEOUT = config.get('EXT_TIMEOUT'); const TIMEOUT = config.get("EXT_TIMEOUT");
const processes = {}; const processes = {};
const jobs = {}; const jobs = {};
@@ -36,82 +36,75 @@ function messageHandler(message) {
} }
} }
function run(args, callback) { function run(args, callback) {
let scriptName = args[0]; let scriptName = args[0];
if (!processes[scriptName]) { if (!processes[scriptName]) {
let p = childProcess.fork('lib/extension-wrapper', [scriptName]); let p = childProcess.fork("lib/extension-wrapper", [scriptName]);
processes[scriptName] = p; processes[scriptName] = p;
p.on('error', function() { p.on("error", () => {
if (processes[scriptName] === p) { if (processes[scriptName] === p) {
kill(processes[scriptName]); kill(processes[scriptName]);
delete processes[scriptName]; delete processes[scriptName];
} }
}); });
p.on('disconnect', function() { p.on("disconnect", () => {
if (processes[scriptName] === p) { if (processes[scriptName] === p) {
kill(processes[scriptName]); kill(processes[scriptName]);
delete processes[scriptName]; delete processes[scriptName];
} }
}); });
p.on('message', messageHandler); p.on("message", messageHandler);
} }
let id = crypto.randomBytes(8).toString('hex'); let id = crypto.randomBytes(8).toString("hex");
jobs[id] = callback; jobs[id] = callback;
setTimeout(function() { setTimeout(() => {
if (id in jobs) { if (id in jobs) {
delete jobs[id]; delete jobs[id];
return callback(null, {code: "timeout", message: "Extension timed out"}); return callback(null, {
code: "timeout",
message: "Extension timed out"
});
} }
}, TIMEOUT); }, TIMEOUT);
return processes[scriptName].send([id, args.slice(1)]); return processes[scriptName].send([id, args.slice(1)]);
}; }
function kill(process, callback) { function kill(process, callback) {
if (process.signalCode !== null || process.exitCode !== null) if (process.signalCode !== null || process.exitCode !== null) return;
return;
let timeToKill = Date.now() + 5000; let timeToKill = Date.now() + 5000;
process.kill(); process.kill();
let t = setInterval(function() { let t = setInterval(() => {
if (process.signalCode !== null || process.exitCode !== null) { if (process.signalCode !== null || process.exitCode !== null) {
clearInterval(t); clearInterval(t);
if (callback) if (callback) callback();
callback(); } else if (Date.now() > timeToKill) {
} process.kill("SIGKILL");
else if (Date.now() > timeToKill) {
process.kill('SIGKILL');
clearInterval(t); clearInterval(t);
if (callback) if (callback) callback();
callback();
} }
}, 100); }, 100);
} }
function killAll(callback) { function killAll(callback) {
let counter = 1; let counter = 1;
for (let n in processes) { for (let n in processes) {
++counter; ++counter;
kill(processes[n], function() { kill(processes[n], () => {
if (--counter == 0 && callback) if (--counter == 0 && callback) callback();
callback();
}); });
delete processes[n]; delete processes[n];
} }
if (--counter == 0 && callback) if (--counter == 0 && callback) callback();
callback();
} }
exports.run = run; exports.run = run;
exports.killAll = killAll; exports.killAll = killAll;
+2 -4
View File
@@ -28,7 +28,7 @@ function listener(request, response) {
if (request.method === "GET") { if (request.method === "GET") {
const filename = querystring.unescape(urlParts.pathname.substring(1)); const filename = querystring.unescape(urlParts.pathname.substring(1));
const gs = new mongodb.GridStore(db.mongoDb, filename, "r", {}); const gs = new mongodb.GridStore(db.mongoDb, filename, "r", {});
gs.open((err, gs) => { gs.open(err => {
if (err) { if (err) {
response.writeHead(404); response.writeHead(404);
response.end(); response.end();
@@ -42,9 +42,7 @@ function listener(request, response) {
stream.pipe(response); stream.pipe(response);
}); });
} else { } else {
response.writeHead(405, { response.writeHead(405, { Allow: "GET" });
"Allow": "GET"
});
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} }
+10 -11
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -41,32 +41,31 @@ function estimateGpnCount(gpnPatterns, depth = 0) {
let k = path[depth]; let k = path[depth];
if (!k) { if (!k) {
if (flags & 1) if (flags & 1) gpnCount = 1;
gpnCount = 1;
continue; continue;
} }
if (flags & 1) { if (flags & 1) {
gpnCount = 1; gpnCount = 1;
if (depth > UNDISCOVERED_DEPTH) if (depth > UNDISCOVERED_DEPTH) continue;
continue; } else if (k === "*") {
}
else if (k === "*")
wildcardDiscovered = true; wildcardDiscovered = true;
}
if (k === "*") { if (k === "*") {
wildcardChildren.push(pattern); wildcardChildren.push(pattern);
} } else {
else {
children[k] = children[k] || []; children[k] = children[k] || [];
children[k].push(pattern); children[k].push(pattern);
} }
} }
let wildcardGpnCount = 0; let wildcardGpnCount = 0;
if (!wildcardDiscovered && wildcardChildren.length) if (!wildcardDiscovered && wildcardChildren.length) {
wildcardGpnCount += estimateGpnCount(wildcardChildren, depth + 1) * WILDCARD_MULTIPLIER; wildcardGpnCount +=
estimateGpnCount(wildcardChildren, depth + 1) * WILDCARD_MULTIPLIER;
}
for (let k of Object.keys(children)) { for (let k of Object.keys(children)) {
let c = estimateGpnCount(children[k].concat(wildcardChildren), depth + 1); let c = estimateGpnCount(children[k].concat(wildcardChildren), depth + 1);
+17 -31
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -18,9 +18,7 @@
*/ */
"use strict"; "use strict";
class InstanceSet { class InstanceSet {
constructor() { constructor() {
this.set = new Set(); this.set = new Set();
} }
@@ -44,29 +42,23 @@ class InstanceSet {
} }
} }
if (match) if (match) res.push(inst);
res.push(inst);
} }
res.sort(function(a, b) { res.sort((a, b) => {
let keysA = Object.keys(a); let keysA = Object.keys(a);
let keysB = Object.keys(b); let keysB = Object.keys(b);
if (keysA.length != keysB.length) if (keysA.length != keysB.length) return keysB.length - keysA.length;
return keysB.length - keysA.length;
keysA.sort(); keysA.sort();
keysB.sort(); keysB.sort();
for (let i = 0; i < keysA.length; ++ i) { for (let i = 0; i < keysA.length; ++i) {
if (keysA[i] > keysB[i]) if (keysA[i] > keysB[i]) return 1;
return 1; else if (keysA[i] < keysB[i]) return -1;
else if (keysA[i] < keysB[i]) else if (a[keysA[i]] > b[keysB[i]]) return 1;
return -1; else if (a[keysA[i]] < b[keysB[i]]) return -1;
else if (a[keysA[i]] > b[keysB[i]])
return 1;
else if (a[keysA[i]] < b[keysB[i]])
return -1;
} }
return 0; return 0;
@@ -87,29 +79,23 @@ class InstanceSet {
} }
} }
if (match) if (match) res.push(inst);
res.push(inst);
} }
res.sort(function(a, b) { res.sort((a, b) => {
let keysA = Object.keys(a); let keysA = Object.keys(a);
let keysB = Object.keys(b); let keysB = Object.keys(b);
if (keysA.length != keysB.length) if (keysA.length != keysB.length) return keysA.length - keysB.length;
return keysA.length - keysB.length;
keysA.sort(); keysA.sort();
keysB.sort(); keysB.sort();
for (let i = 0; i < keysA.length; ++ i) { for (let i = 0; i < keysA.length; ++i) {
if (keysA[i] > keysB[i]) if (keysA[i] > keysB[i]) return 1;
return 1; else if (keysA[i] < keysB[i]) return -1;
else if (keysA[i] < keysB[i]) else if (a[keysA[i]] > b[keysB[i]]) return 1;
return -1; else if (a[keysA[i]] < b[keysB[i]]) return -1;
else if (a[keysA[i]] > b[keysB[i]])
return 1;
else if (a[keysA[i]] < b[keysB[i]])
return -1;
} }
return 0; return 0;
+85 -76
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -33,7 +33,6 @@ let nextRefresh = 1;
let hash = null; let hash = null;
let presets, provisions, virtualParameters, files; let presets, provisions, virtualParameters, files;
function computeHash() { function computeHash() {
// MD5 hash for presets, provisions, virtual parameters for detecting changes // MD5 hash for presets, provisions, virtual parameters for detecting changes
let h = crypto.createHash("md5"); let h = crypto.createHash("md5");
@@ -50,13 +49,11 @@ function computeHash() {
keys = Object.keys(provisions).sort(); keys = Object.keys(provisions).sort();
h.update(JSON.stringify(keys)); h.update(JSON.stringify(keys));
for (let k of keys) for (let k of keys) h.update(provisions[k].md5);
h.update(provisions[k].md5);
keys = Object.keys(virtualParameters).sort(); keys = Object.keys(virtualParameters).sort();
h.update(JSON.stringify(keys)); h.update(JSON.stringify(keys));
for (let k of keys) for (let k of keys) h.update(virtualParameters[k].md5);
h.update(virtualParameters[k].md5);
hash = h.digest("hex"); hash = h.digest("hex");
} }
@@ -66,47 +63,45 @@ function flattenObject(src, prefix = "", dst = {}) {
let v = src[k]; let v = src[k];
if (typeof v === "object" && !Array.isArray(v)) if (typeof v === "object" && !Array.isArray(v))
flattenObject(v, `${prefix}${k}.`, dst); flattenObject(v, `${prefix}${k}.`, dst);
else else dst[`${prefix}${k}`] = v;
dst[`${prefix}${k}`] = v;
} }
return dst; return dst;
} }
function refresh(callback) { function refresh(callback) {
if (!nextRefresh) if (!nextRefresh) {
return setTimeout(function() {refresh(callback);}, 20); return setTimeout(() => {
refresh(callback);
}, 20);
}
nextRefresh = 0; nextRefresh = 0;
const now = Date.now(); const now = Date.now();
cache.get("presets_hash", function(err, res) { cache.get("presets_hash", (err, dbHash) => {
if (err) if (err) return callback(err);
return callback(err);
if (hash && res === hash) { if (hash && dbHash === hash) {
nextRefresh = now + (REFRESH - (now % REFRESH)); nextRefresh = now + (REFRESH - (now % REFRESH));
return callback(); return callback();
} }
cache.lock("presets_hash_lock", 3, function(err, unlockOrExtend) { cache.lock("presets_hash_lock", 3, (err, unlockOrExtend) => {
if (err) if (err) return callback(err);
return callback(err);
let counter = 3; let counter = 3;
counter += 2; counter += 2;
db.getPresets(function(err, res) { db.getPresets((err, res) => {
if (err) { if (err) {
if (counter & 1) if (counter & 1) callback(err);
callback(err); return (counter = 0);
return counter = 0;
} }
db.getObjects(function(err, objects) { db.getObjects((err, objects) => {
if (err) { if (err) {
if (counter & 1) if (counter & 1) callback(err);
callback(err); return (counter = 0);
return counter = 0;
} }
objects = objects.map(obj => { objects = objects.map(obj => {
@@ -118,14 +113,12 @@ function refresh(callback) {
if (!obj["_keys"] || !obj["_keys"].length) if (!obj["_keys"] || !obj["_keys"].length)
obj["_keys"] = Object.keys(obj).filter(k => !k.startsWith("_")); obj["_keys"] = Object.keys(obj).filter(k => !k.startsWith("_"));
return obj return obj;
}) });
res.sort((a, b) => { res.sort((a, b) => {
if (a.weight === b.weight) if (a.weight === b.weight) return a._id > b._id;
return a._id > b._id; else return a.weight - b.weight;
else
return a.weight - b.weight;
}); });
presets = []; presets = [];
@@ -134,14 +127,16 @@ function refresh(callback) {
if (preset.schedule) { if (preset.schedule) {
let parts = preset.schedule.trim().split(/\s+/); let parts = preset.schedule.trim().split(/\s+/);
schedule = { schedule = {
md5: crypto.createHash("md5").update(preset.schedule).digest("hex") md5: crypto
.createHash("md5")
.update(preset.schedule)
.digest("hex")
}; };
try { try {
schedule.duration = +(parts.shift()) * 1000; schedule.duration = +parts.shift() * 1000;
schedule.schedule = scheduling.parseCron(parts.join(" ")); schedule.schedule = scheduling.parseCron(parts.join(" "));
} } catch (err) {
catch (err) {
logger.warn({ logger.warn({
message: "Invalid preset schedule", message: "Invalid preset schedule",
preset: preset._id, preset: preset._id,
@@ -152,7 +147,9 @@ function refresh(callback) {
} }
let events = preset.events || {}; let events = preset.events || {};
let precondition = query.convertMongoQueryToFilters(JSON.parse(preset.precondition)); let precondition = query.convertMongoQueryToFilters(
JSON.parse(preset.precondition)
);
let _provisions = preset.provisions || []; let _provisions = preset.provisions || [];
// Generate provisions from the old configuration format // Generate provisions from the old configuration format
@@ -181,32 +178,41 @@ function refresh(callback) {
case "add_object": case "add_object":
for (let obj of objects) { for (let obj of objects) {
if (obj["_id"] === c.object) { if (obj["_id"] === c.object) {
let alias = obj["_keys"].map((k) => `${k}:${JSON.stringify(obj[k])}`).join(","); let alias = obj["_keys"]
.map(k => `${k}:${JSON.stringify(obj[k])}`)
.join(",");
let p = `${c.name}.[${alias}]`; let p = `${c.name}.[${alias}]`;
_provisions.push(["instances", p, 1]); _provisions.push(["instances", p, 1]);
for (let k in obj) for (let k in obj) {
if (!k.startsWith("_") && !(obj["_keys"].indexOf(k) !== -1)) if (
!k.startsWith("_") &&
!(obj["_keys"].indexOf(k) !== -1)
)
_provisions.push(["value", `${p}.${k}`, obj[k]]); _provisions.push(["value", `${p}.${k}`, obj[k]]);
}
} }
} }
break; break;
case "delete_object": case "delete_object":
for (let obj of objects) { for (let obj of objects) {
if (obj["_id"] === c.object) { if (obj["_id"] === c.object) {
let alias = obj["_keys"].map((k) => `${k}:${JSON.stringify(obj[k])}`).join(","); let alias = obj["_keys"]
.map(k => `${k}:${JSON.stringify(obj[k])}`)
.join(",");
let p = `${c.name}.[${alias}]`; let p = `${c.name}.[${alias}]`;
_provisions.push(["instances", p, 0]); _provisions.push(["instances", p, 0]);
} }
} }
break; break;
default: default:
if (counter & 1) if (counter & 1)
callback(new Error(`Unknown configuration type ${c.type}`)); callback(new Error(`Unknown configuration type ${c.type}`));
return counter = 0; return (counter = 0);
break;
} }
} }
@@ -222,7 +228,7 @@ function refresh(callback) {
if ((counter -= 2) === 1) { if ((counter -= 2) === 1) {
computeHash(); computeHash();
cache.set("presets_hash", hash, 300, function(err) { cache.set("presets_hash", hash, 300, err => {
unlockOrExtend(0); unlockOrExtend(0);
nextRefresh = now + (REFRESH - (now % REFRESH)); nextRefresh = now + (REFRESH - (now % REFRESH));
callback(err); callback(err);
@@ -232,23 +238,28 @@ function refresh(callback) {
}); });
counter += 2; counter += 2;
db.getProvisions(function(err, res) { db.getProvisions((err, res) => {
if (err) { if (err) {
if (counter & 1) if (counter & 1) callback(err);
callback(err); return (counter = 0);
return counter = 0;
} }
provisions = {}; provisions = {};
for (let r of res) { for (let r of res) {
provisions[r._id] = {}; provisions[r._id] = {};
provisions[r._id].md5 = crypto.createHash("md5").update(r.script).digest("hex"); provisions[r._id].md5 = crypto
provisions[r._id].script = new vm.Script(`"use strict";(function(){\n${r.script}\n})();`, {filename: r._id, lineOffset: -1, timeout: 50}); .createHash("md5")
.update(r.script)
.digest("hex");
provisions[r._id].script = new vm.Script(
`"use strict";(function(){\n${r.script}\n})();`,
{ filename: r._id, lineOffset: -1, timeout: 50 }
);
} }
if ((counter -= 2) === 1) { if ((counter -= 2) === 1) {
computeHash(); computeHash();
cache.set("presets_hash", hash, 300, function(err) { cache.set("presets_hash", hash, 300, err => {
unlockOrExtend(0); unlockOrExtend(0);
nextRefresh = now + (REFRESH - (now % REFRESH)); nextRefresh = now + (REFRESH - (now % REFRESH));
callback(err); callback(err);
@@ -257,23 +268,28 @@ function refresh(callback) {
}); });
counter += 2; counter += 2;
db.getVirtualParameters(function(err, res) { db.getVirtualParameters((err, res) => {
if (err) { if (err) {
if (counter & 1) if (counter & 1) callback(err);
callback(err); return (counter = 0);
return counter = 0;
} }
virtualParameters = {}; virtualParameters = {};
for (let r of res) { for (let r of res) {
virtualParameters[r._id] = {} virtualParameters[r._id] = {};
virtualParameters[r._id].md5 = crypto.createHash("md5").update(r.script).digest("hex"); virtualParameters[r._id].md5 = crypto
virtualParameters[r._id].script = new vm.Script(`"use strict";(function(){\n${r.script}\n})();`, {filename: r._id, lineOffset: -1, timeout: 50}); .createHash("md5")
.update(r.script)
.digest("hex");
virtualParameters[r._id].script = new vm.Script(
`"use strict";(function(){\n${r.script}\n})();`,
{ filename: r._id, lineOffset: -1, timeout: 50 }
);
} }
if ((counter -= 2) === 1) { if ((counter -= 2) === 1) {
computeHash(); computeHash();
cache.set("presets_hash", hash, 300, function(err) { cache.set("presets_hash", hash, 300, err => {
unlockOrExtend(0); unlockOrExtend(0);
nextRefresh = now + (REFRESH - (now % REFRESH)); nextRefresh = now + (REFRESH - (now % REFRESH));
callback(err); callback(err);
@@ -282,11 +298,10 @@ function refresh(callback) {
}); });
counter += 2; counter += 2;
db.getFiles(function(err, res) { db.getFiles((err, res) => {
if (err) { if (err) {
if (counter & 1) if (counter & 1) callback(err);
callback(err); return (counter = 0);
return counter = 0;
} }
files = {}; files = {};
@@ -300,7 +315,7 @@ function refresh(callback) {
if ((counter -= 2) === 1) { if ((counter -= 2) === 1) {
computeHash(); computeHash();
cache.set("presets_hash", hash, 300, function(err) { cache.set("presets_hash", hash, 300, err => {
unlockOrExtend(0); unlockOrExtend(0);
nextRefresh = now + (REFRESH - (now % REFRESH)); nextRefresh = now + (REFRESH - (now % REFRESH));
callback(err); callback(err);
@@ -310,7 +325,7 @@ function refresh(callback) {
if ((counter -= 2) === 1) { if ((counter -= 2) === 1) {
computeHash(); computeHash();
cache.set("presets_hash", hash, 300, function(err) { cache.set("presets_hash", hash, 300, err => {
unlockOrExtend(0); unlockOrExtend(0);
nextRefresh = now + (REFRESH - (now % REFRESH)); nextRefresh = now + (REFRESH - (now % REFRESH));
callback(err); callback(err);
@@ -320,37 +335,31 @@ function refresh(callback) {
}); });
} }
function getPresets(callback) { function getPresets(callback) {
if (Date.now() < nextRefresh) if (Date.now() < nextRefresh) return callback(null, hash, presets);
return callback(null, hash, presets);
refresh(function(err) { refresh(err => {
callback(err, hash, presets); callback(err, hash, presets);
}); });
} }
function getFiles(callback) { function getFiles(callback) {
if (Date.now() < nextRefresh) if (Date.now() < nextRefresh) return callback(null, hash, files);
return callback(null, hash, files);
refresh(function(err) { refresh(err => {
callback(err, hash, files); callback(err, hash, files);
}) });
} }
function getProvisionsAndVirtualParameters(callback) { function getProvisionsAndVirtualParameters(callback) {
if (Date.now() < nextRefresh) if (Date.now() < nextRefresh)
return callback(null, hash, provisions, virtualParameters); return callback(null, hash, provisions, virtualParameters);
refresh(function(err) { refresh(err => {
callback(err, hash, provisions, virtualParameters); callback(err, hash, provisions, virtualParameters);
}); });
} }
exports.getPresets = getPresets; exports.getPresets = getPresets;
exports.getFiles = getFiles; exports.getFiles = getFiles;
exports.getProvisionsAndVirtualParameters = getProvisionsAndVirtualParameters; exports.getProvisionsAndVirtualParameters = getProvisionsAndVirtualParameters;
+50 -68
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -35,57 +35,61 @@ let ACCESS_LOG_SYSTEMD = false;
let LOG_FILE, ACCESS_LOG_FILE; let LOG_FILE, ACCESS_LOG_FILE;
let logStream = fs.createWriteStream(null, {fd: process.stderr.fd}); let logStream = fs.createWriteStream(null, { fd: process.stderr.fd });
let logStat = fs.fstatSync(logStream.fd); let logStat = fs.fstatSync(logStream.fd);
let accessLogStream = fs.createWriteStream(null, {fd: process.stdout.fd}); let accessLogStream = fs.createWriteStream(null, { fd: process.stdout.fd });
let accessLogStat = fs.fstatSync(accessLogStream.fd); let accessLogStat = fs.fstatSync(accessLogStream.fd);
// Reopen if original files have been moved (e.g. logrotate) // Reopen if original files have been moved (e.g. logrotate)
function reopen() { function reopen() {
let counter = 1; let counter = 1;
if (LOG_FILE) { if (LOG_FILE) {
++ counter; ++counter;
fs.stat(LOG_FILE, function(err, stat) { fs.stat(LOG_FILE, (err, stat) => {
if (err && !err.message.startsWith("ENOENT:")) if (err && !err.message.startsWith("ENOENT:")) throw err;
throw err;
if (!(stat && stat.dev === logStat.dev && stat.ino === logStat.ino)) { if (!(stat && stat.dev === logStat.dev && stat.ino === logStat.ino)) {
logStream.end(); logStream.end();
logStream = fs.createWriteStream(null, logStream = fs.createWriteStream(null, {
{fd: fs.openSync(LOG_FILE, "a")}); fd: fs.openSync(LOG_FILE, "a")
});
logStat = fs.fstatSync(logStream.fd); logStat = fs.fstatSync(logStream.fd);
} }
if (-- counter === 0) if (--counter === 0)
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref(); setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
}); });
} }
if (ACCESS_LOG_FILE) { if (ACCESS_LOG_FILE) {
++ counter; ++counter;
fs.stat(ACCESS_LOG_FILE, function(err, stat) { fs.stat(ACCESS_LOG_FILE, (err, stat) => {
if (err && !err.message.startsWith("ENOENT:")) if (err && !err.message.startsWith("ENOENT:")) throw err;
throw err;
if (!(stat && stat.dev === accessLogStat.dev && stat.ino === accessLogStat.ino)) { if (
!(
stat &&
stat.dev === accessLogStat.dev &&
stat.ino === accessLogStat.ino
)
) {
accessLogStream.end(); accessLogStream.end();
accessLogStream = fs.createWriteStream(null, accessLogStream = fs.createWriteStream(null, {
{fd: fs.openSync(ACCESS_LOG_FILE, "a")}); fd: fs.openSync(ACCESS_LOG_FILE, "a")
});
accessLogStat = fs.fstatSync(accessLogStream.fd); accessLogStat = fs.fstatSync(accessLogStream.fd);
} }
if (-- counter === 0) if (--counter === 0)
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref(); setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
}); });
} }
if (-- counter === 0) if (--counter === 0)
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref(); setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
} }
function init(service, version) { function init(service, version) {
defaultMeta.hostname = os.hostname(); defaultMeta.hostname = os.hostname();
defaultMeta.pid = process.pid; defaultMeta.pid = process.pid;
@@ -96,14 +100,14 @@ function init(service, version) {
ACCESS_LOG_FILE = config.get(`${service.toUpperCase()}_ACCESS_LOG_FILE`); ACCESS_LOG_FILE = config.get(`${service.toUpperCase()}_ACCESS_LOG_FILE`);
if (LOG_FILE) { if (LOG_FILE) {
logStream = fs.createWriteStream(null, logStream = fs.createWriteStream(null, { fd: fs.openSync(LOG_FILE, "a") });
{fd: fs.openSync(LOG_FILE, "a")});
logStat = fs.fstatSync(logStream.fd); logStat = fs.fstatSync(logStream.fd);
} }
if (ACCESS_LOG_FILE) { if (ACCESS_LOG_FILE) {
accessLogStream = fs.createWriteStream(null, accessLogStream = fs.createWriteStream(null, {
{fd: fs.openSync(ACCESS_LOG_FILE, "a")}); fd: fs.openSync(ACCESS_LOG_FILE, "a")
});
accessLogStat = fs.fstatSync(accessLogStream.fd); accessLogStat = fs.fstatSync(accessLogStream.fd);
} }
@@ -115,27 +119,26 @@ function init(service, version) {
dev = parseInt(dev); dev = parseInt(dev);
inode = parseInt(inode); inode = parseInt(inode);
LOG_SYSTEMD = (logStat.dev === dev && logStat.ino === inode); LOG_SYSTEMD = logStat.dev === dev && logStat.ino === inode;
ACCESS_LOG_SYSTEMD = (accessLogStat.dev === dev && accessLogStat.ino === inode); ACCESS_LOG_SYSTEMD =
accessLogStat.dev === dev && accessLogStat.ino === inode;
} }
if (LOG_FILE || ACCESS_LOG_FILE) { if (LOG_FILE || ACCESS_LOG_FILE)
// Can't use setInterval as we need all workers to cehck at the same time // Can't use setInterval as we need all workers to cehck at the same time
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref(); setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
}
} }
function close() { function close() {
accessLogStream.end(); accessLogStream.end();
logStream.end(); logStream.end();
} }
function flatten(details) { function flatten(details) {
if (details.sessionContext) { if (details.sessionContext) {
details.deviceId = details.sessionContext.deviceId; details.deviceId = details.sessionContext.deviceId;
details.remoteAddress = details.sessionContext.httpRequest.connection.remoteAddress; details.remoteAddress =
details.sessionContext.httpRequest.connection.remoteAddress;
delete details.sessionContext; delete details.sessionContext;
} }
@@ -157,20 +160,17 @@ function flatten(details) {
details.acsRequestName = details.rpc.acsRequest.name; details.acsRequestName = details.rpc.acsRequest.name;
if (details.rpc.acsRequest.commandKey) if (details.rpc.acsRequest.commandKey)
details.acsRequestCommandKey = details.rpc.acsRequest.commandKey; details.acsRequestCommandKey = details.rpc.acsRequest.commandKey;
} } else if (details.rpc.cpeRequest) {
else if (details.rpc.cpeRequest) {
details.cpeRequestId = details.rpc.id; details.cpeRequestId = details.rpc.id;
if (details.rpc.cpeRequest.name === "Inform") { if (details.rpc.cpeRequest.name === "Inform") {
details.informEvent = details.rpc.cpeRequest.event.join(","); details.informEvent = details.rpc.cpeRequest.event.join(",");
details.informRetryCount = details.rpc.cpeRequest.retryCount; details.informRetryCount = details.rpc.cpeRequest.retryCount;
} } else {
else {
details.cpeRequestName = details.rpc.cpeRequest.name; details.cpeRequestName = details.rpc.cpeRequest.name;
if (details.rpc.cpeRequest.commandKey) if (details.rpc.cpeRequest.commandKey)
details.cpeRequestCommandKey = details.rpc.cpeRequest.commandKey; details.cpeRequestCommandKey = details.rpc.cpeRequest.commandKey;
} }
} } else if (details.rpc.cpeFault) {
else if (details.rpc.cpeFault) {
details.acsRequestId = details.rpc.id; details.acsRequestId = details.rpc.id;
details.cpeFaultCode = details.rpc.cpeFault.detail.faultCode; details.cpeFaultCode = details.rpc.cpeFault.detail.faultCode;
details.cpeFaultString = details.rpc.cpeFault.detail.faultString; details.cpeFaultString = details.rpc.cpeFault.detail.faultString;
@@ -187,16 +187,12 @@ function flatten(details) {
return details; return details;
} }
function formatJson(details, systemd) { function formatJson(details, systemd) {
if (systemd) { if (systemd) {
let severity = ""; let severity = "";
if (details.severity === "info") if (details.severity === "info") severity = "<6>";
severity = "<6>"; else if (details.severity === "warn") severity = "<4>";
else if (details.severity === "warn") else if (details.severity === "error") severity = "<3>";
severity = "<4>";
else if (details.severity === "error")
severity = "<3>";
return `${severity}${JSON.stringify(flatten(details))}${os.EOL}`; return `${severity}${JSON.stringify(flatten(details))}${os.EOL}`;
} }
@@ -204,7 +200,6 @@ function formatJson(details, systemd) {
return `${JSON.stringify(flatten(details))}${os.EOL}`; return `${JSON.stringify(flatten(details))}${os.EOL}`;
} }
function formatSimple(details, systemd) { function formatSimple(details, systemd) {
flatten(details); flatten(details);
@@ -212,8 +207,7 @@ function formatSimple(details, systemd) {
if (details.remoteAddress) { if (details.remoteAddress) {
if (details.deviceId) if (details.deviceId)
remote = `${details.remoteAddress} ${details.deviceId}: `; remote = `${details.remoteAddress} ${details.deviceId}: `;
else else remote = `${details.remoteAddress}: `;
remote = `${details.remoteAddress}: `;
} }
const keys = Object.keys(details); const keys = Object.keys(details);
@@ -229,28 +223,24 @@ function formatSimple(details, systemd) {
let kv = []; let kv = [];
for (let k of keys) for (let k of keys)
if (!skip[k]) if (!skip[k]) kv.push(`${k}=${JSON.stringify(details[k])}`);
kv.push(`${k}=${JSON.stringify(details[k])}`);
if (kv.length) if (kv.length) meta = `; ${kv.join(" ")}`;
meta = `; ${kv.join(" ")}`;
if (systemd) { if (systemd) {
let severity = ""; let severity = "";
if (details.severity === "info") if (details.severity === "info") severity = "<6>";
severity = "<6>"; else if (details.severity === "warn") severity = "<4>";
else if (details.severity === "warn") else if (details.severity === "error") severity = "<3>";
severity = "<4>";
else if (details.severity === "error")
severity = "<3>";
return `${severity}${remote}${details.message}${meta}${os.EOL}`; return `${severity}${remote}${details.message}${meta}${os.EOL}`;
} }
return `${details.timestamp} [${details.severity.toUpperCase()}] ${remote}${details.message}${meta}${os.EOL}`; return `${details.timestamp} [${details.severity.toUpperCase()}] ${remote}${
details.message
}${meta}${os.EOL}`;
} }
function log(details) { function log(details) {
details.timestamp = new Date().toISOString(); details.timestamp = new Date().toISOString();
if (LOG_FORMAT === "json") { if (LOG_FORMAT === "json") {
@@ -261,25 +251,21 @@ function log(details) {
} }
} }
function info(details) { function info(details) {
details.severity = "info"; details.severity = "info";
log(details); log(details);
} }
function warn(details) { function warn(details) {
details.severity = "warn"; details.severity = "warn";
log(details); log(details);
} }
function error(details) { function error(details) {
details.severity = "error"; details.severity = "error";
log(details); log(details);
} }
function accessLog(details) { function accessLog(details) {
details.timestamp = new Date().toISOString(); details.timestamp = new Date().toISOString();
if (ACCESS_LOG_FORMAT === "json") { if (ACCESS_LOG_FORMAT === "json") {
@@ -290,25 +276,21 @@ function accessLog(details) {
} }
} }
function accessInfo(details) { function accessInfo(details) {
details.severity = "info"; details.severity = "info";
accessLog(details); accessLog(details);
} }
function accessWarn(details) { function accessWarn(details) {
details.severity = "warn"; details.severity = "warn";
accessLog(details); accessLog(details);
} }
function accessError(details) { function accessError(details) {
details.severity = "error"; details.severity = "error";
accessLog(details); accessLog(details);
} }
exports.init = init; exports.init = init;
exports.close = close; exports.close = close;
exports.flatten = flatten; exports.flatten = flatten;
+226 -216
View File
@@ -31,22 +31,22 @@ const cache = require("./cache");
const VERSION = require("../package.json").version; const VERSION = require("../package.json").version;
const DEVICE_TASKS_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/tasks\/?$/; const DEVICE_TASKS_REGEX = /^\/devices\/([a-zA-Z0-9\-_%]+)\/tasks\/?$/;
const TASKS_REGEX = /^\/tasks\/([a-zA-Z0-9\-\_\%]+)(\/[a-zA-Z_]*)?$/; const TASKS_REGEX = /^\/tasks\/([a-zA-Z0-9\-_%]+)(\/[a-zA-Z_]*)?$/;
const TAGS_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/tags\/([a-zA-Z0-9\-\_\%]+)\/?$/; const TAGS_REGEX = /^\/devices\/([a-zA-Z0-9\-_%]+)\/tags\/([a-zA-Z0-9\-_%]+)\/?$/;
const PRESETS_REGEX = /^\/presets\/([a-zA-Z0-9\-\_\%]+)\/?$/; const PRESETS_REGEX = /^\/presets\/([a-zA-Z0-9\-_%]+)\/?$/;
const OBJECTS_REGEX = /^\/objects\/([a-zA-Z0-9\-\_\%]+)\/?$/; const OBJECTS_REGEX = /^\/objects\/([a-zA-Z0-9\-_%]+)\/?$/;
const FILES_REGEX = /^\/files\/([a-zA-Z0-9\%\!\*\'\(\)\;\:\@\&\=\+\$\,\?\#\[\]\-\_\.\~]+)\/?$/; const FILES_REGEX = /^\/files\/([a-zA-Z0-9%!*'();:@&=+$,?#[\]\-_.~]+)\/?$/;
const PING_REGEX = /^\/ping\/([a-zA-Z0-9\-\_\.\:]+)\/?$/; const PING_REGEX = /^\/ping\/([a-zA-Z0-9\-_.:]+)\/?$/;
const QUERY_REGEX = /^\/([a-zA-Z0-9_]+)\/?$/; const QUERY_REGEX = /^\/([a-zA-Z0-9_]+)\/?$/;
const DELETE_DEVICE_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/?$/; const DELETE_DEVICE_REGEX = /^\/devices\/([a-zA-Z0-9\-_%]+)\/?$/;
const PROVISIONS_REGEX = /^\/provisions\/([a-zA-Z0-9\-\_\%]+)\/?$/; const PROVISIONS_REGEX = /^\/provisions\/([a-zA-Z0-9\-_%]+)\/?$/;
const VIRTUAL_PARAMETERS_REGEX = /^\/virtual_parameters\/([a-zA-Z0-9\-\_\%]+)\/?$/; const VIRTUAL_PARAMETERS_REGEX = /^\/virtual_parameters\/([a-zA-Z0-9\-_%]+)\/?$/;
const FAULTS_REGEX = /^\/faults\/([a-zA-Z0-9\-\_\%\:]+)\/?$/; const FAULTS_REGEX = /^\/faults\/([a-zA-Z0-9\-_%:]+)\/?$/;
function throwError(err, httpResponse) { function throwError(err, httpResponse) {
if (httpResponse) { if (httpResponse) {
httpResponse.writeHead(500, {"Connection": "close"}); httpResponse.writeHead(500, { Connection: "close" });
httpResponse.end(`${err.name}: ${err.message}`); httpResponse.end(`${err.name}: ${err.message}`);
} }
throw err; throw err;
@@ -57,7 +57,7 @@ function listener(request, response) {
let bytes = 0; let bytes = 0;
response.setHeader("GenieACS-Version", VERSION); response.setHeader("GenieACS-Version", VERSION);
request.addListener("data", (chunk) => { request.addListener("data", chunk => {
chunks.push(chunk); chunks.push(chunk);
bytes += chunk.length; bytes += chunk.length;
}); });
@@ -77,75 +77,73 @@ function listener(request, response) {
const body = getBody(); const body = getBody();
const urlParts = url.parse(request.url, true); const urlParts = url.parse(request.url, true);
if (PRESETS_REGEX.test(urlParts.pathname)) { if (PRESETS_REGEX.test(urlParts.pathname)) {
const presetName = querystring.unescape(PRESETS_REGEX.exec(urlParts.pathname)[1]); const presetName = querystring.unescape(
PRESETS_REGEX.exec(urlParts.pathname)[1]
);
if (request.method === "PUT") { if (request.method === "PUT") {
const preset = JSON.parse(body); const preset = JSON.parse(body);
preset._id = presetName; preset._id = presetName;
db.presetsCollection.save(preset, (err) => { db.presetsCollection.save(preset, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
cache.del("presets_hash", (err) => { cache.del("presets_hash", err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); });
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
db.presetsCollection.remove({"_id": presetName}, (err) => { db.presetsCollection.remove({ _id: presetName }, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
cache.del("presets_hash", (err) => { cache.del("presets_hash", err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); });
} else { } else {
response.writeHead(405, {"Allow": "PUT, DELETE"}); response.writeHead(405, { Allow: "PUT, DELETE" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else if (OBJECTS_REGEX.test(urlParts.pathname)) { } else if (OBJECTS_REGEX.test(urlParts.pathname)) {
const objectName = querystring.unescape(OBJECTS_REGEX.exec(urlParts.pathname)[1]); const objectName = querystring.unescape(
OBJECTS_REGEX.exec(urlParts.pathname)[1]
);
if (request.method === "PUT") { if (request.method === "PUT") {
const object = JSON.parse(body); const object = JSON.parse(body);
object._id = objectName; object._id = objectName;
db.objectsCollection.save(object, (err) => { db.objectsCollection.save(object, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
cache.del("presets_hash", (err) => { cache.del("presets_hash", err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); });
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
db.objectsCollection.remove({"_id": objectName}, (err) => { db.objectsCollection.remove({ _id: objectName }, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
cache.del("presets_hash", (err) => { cache.del("presets_hash", err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); });
} else { } else {
response.writeHead(405, {"Allow": "PUT, DELETE"}); response.writeHead(405, { Allow: "PUT, DELETE" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else if (PROVISIONS_REGEX.test(urlParts.pathname)) { } else if (PROVISIONS_REGEX.test(urlParts.pathname)) {
const provisionName = querystring.unescape(PROVISIONS_REGEX.exec(urlParts.pathname)[1]); const provisionName = querystring.unescape(
PROVISIONS_REGEX.exec(urlParts.pathname)[1]
);
if (request.method === "PUT") { if (request.method === "PUT") {
const object = { const object = {
_id: provisionName, _id: provisionName,
@@ -160,37 +158,35 @@ function listener(request, response) {
return; return;
} }
db.provisionsCollection.save(object, (err) => { db.provisionsCollection.save(object, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
cache.del("presets_hash", (err) => { cache.del("presets_hash", err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); });
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
db.provisionsCollection.remove({"_id": provisionName}, (err) => { db.provisionsCollection.remove({ _id: provisionName }, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
cache.del("presets_hash", (err) => { cache.del("presets_hash", err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); });
} else { } else {
response.writeHead(405, {"Allow": "PUT, DELETE"}); response.writeHead(405, { Allow: "PUT, DELETE" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else if (VIRTUAL_PARAMETERS_REGEX.test(urlParts.pathname)) { } else if (VIRTUAL_PARAMETERS_REGEX.test(urlParts.pathname)) {
const virtualParameterName = querystring.unescape(VIRTUAL_PARAMETERS_REGEX.exec(urlParts.pathname)[1]); const virtualParameterName = querystring.unescape(
VIRTUAL_PARAMETERS_REGEX.exec(urlParts.pathname)[1]
);
if (request.method === "PUT") { if (request.method === "PUT") {
const object = { const object = {
_id: virtualParameterName, _id: virtualParameterName,
@@ -205,33 +201,32 @@ function listener(request, response) {
return; return;
} }
db.virtualParametersCollection.save(object, (err) => { db.virtualParametersCollection.save(object, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
cache.del("presets_hash", (err) => { cache.del("presets_hash", err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); });
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
db.virtualParametersCollection.remove({"_id": virtualParameterName}, (err) => { db.virtualParametersCollection.remove(
if (err) { _id: virtualParameterName },
return throwError(err, response); err => {
if (err) return throwError(err, response);
cache.del("presets_hash", (err) => { cache.del("presets_hash", err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); }
);
} else { } else {
response.writeHead(405, {"Allow": "PUT, DELETE"}); response.writeHead(405, { Allow: "PUT, DELETE" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else if (TAGS_REGEX.test(urlParts.pathname)) { } else if (TAGS_REGEX.test(urlParts.pathname)) {
@@ -240,51 +235,48 @@ function listener(request, response) {
const tag = querystring.unescape(r[2]); const tag = querystring.unescape(r[2]);
if (request.method === "POST") { if (request.method === "POST") {
db.devicesCollection.update( db.devicesCollection.update(
{"_id": deviceId}, { _id: deviceId },
{"$addToSet": {"_tags": tag}}, { $addToSet: { _tags: tag } },
{safe: true}, { safe: true },
(err) => { err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
} }
); );
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
db.devicesCollection.update( db.devicesCollection.update(
{"_id": deviceId}, { _id: deviceId },
{"$pull": {"_tags": tag}}, { $pull: { _tags: tag } },
{safe: true}, { safe: true },
(err) => { err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
} }
); );
} else { } else {
response.writeHead(405, {"Allow": "POST, DELETE"}); response.writeHead(405, { Allow: "POST, DELETE" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else if (FAULTS_REGEX.test(urlParts.pathname)) { } else if (FAULTS_REGEX.test(urlParts.pathname)) {
if (request.method === "DELETE") { if (request.method === "DELETE") {
const faultId = querystring.unescape(FAULTS_REGEX.exec(urlParts.pathname)[1]); const faultId = querystring.unescape(
FAULTS_REGEX.exec(urlParts.pathname)[1]
);
const deviceId = faultId.split(":", 1)[0]; const deviceId = faultId.split(":", 1)[0];
const channel = faultId.slice(deviceId.length + 1); const channel = faultId.slice(deviceId.length + 1);
db.faultsCollection.remove({_id: faultId}, (err) => { db.faultsCollection.remove({ _id: faultId }, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
if (channel.startsWith("task_")) { if (channel.startsWith("task_")) {
const objId = new mongodb.ObjectID(channel.slice(5)); const objId = new mongodb.ObjectID(channel.slice(5));
return db.tasksCollection.remove({_id: objId}, (err) => { return db.tasksCollection.remove({ _id: objId }, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
cache.del(`${deviceId}_tasks_faults_operations`, (err) => { cache.del(`${deviceId}_tasks_faults_operations`, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
@@ -292,67 +284,81 @@ function listener(request, response) {
}); });
} }
cache.del(`${deviceId}_tasks_faults_operations`, (err) => { cache.del(`${deviceId}_tasks_faults_operations`, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); });
} else { } else {
response.writeHead(405, {"Allow": "DELETE"}); response.writeHead(405, { Allow: "DELETE" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else if (DEVICE_TASKS_REGEX.test(urlParts.pathname)) { } else if (DEVICE_TASKS_REGEX.test(urlParts.pathname)) {
if (request.method === "POST") { if (request.method === "POST") {
const deviceId = querystring.unescape(DEVICE_TASKS_REGEX.exec(urlParts.pathname)[1]); const deviceId = querystring.unescape(
DEVICE_TASKS_REGEX.exec(urlParts.pathname)[1]
);
if (body.length) { if (body.length) {
const task = JSON.parse(body); const task = JSON.parse(body);
task.device = deviceId; task.device = deviceId;
apiFunctions.insertTasks(task, (err) => { apiFunctions.insertTasks(task, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
cache.del(`${deviceId}_tasks_faults_operations`, (err) => { cache.del(`${deviceId}_tasks_faults_operations`, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
if (urlParts.query.connection_request != null) { if (urlParts.query.connection_request != null) {
apiFunctions.connectionRequest(deviceId, (err) => { apiFunctions.connectionRequest(deviceId, err => {
if (err) { if (err) {
response.writeHead(202, err.message, {"Content-Type": "application/json"}); response.writeHead(202, err.message, {
"Content-Type": "application/json"
});
response.end(JSON.stringify(task)); response.end(JSON.stringify(task));
return; return;
} }
const DEVICE_ONLINE_THRESHOLD = config.get("DEVICE_ONLINE_THRESHOLD", deviceId); const DEVICE_ONLINE_THRESHOLD = config.get(
apiFunctions.watchTask(deviceId, task._id, DEVICE_ONLINE_THRESHOLD, (err, status) => { "DEVICE_ONLINE_THRESHOLD",
if (err) deviceId
return throwError(err, response); );
apiFunctions.watchTask(
deviceId,
task._id,
DEVICE_ONLINE_THRESHOLD,
(err, status) => {
if (err) return throwError(err, response);
if (status === "timeout") { if (status === "timeout") {
response.writeHead(202, "Task queued but not processed", { response.writeHead(
"Content-Type": "application/json" 202,
}); "Task queued but not processed",
response.end(JSON.stringify(task)); {
} else if (status === "fault") { "Content-Type": "application/json"
db.tasksCollection.findOne({_id: task._id}, (err, task) => { }
if (err) );
return throwError(err, response); response.end(JSON.stringify(task));
} else if (status === "fault") {
db.tasksCollection.findOne(
{ _id: task._id },
(err, task2) => {
if (err) return throwError(err, response);
response.writeHead(202, "Task faulted", { response.writeHead(202, "Task faulted", {
"Content-Type": "application/json"
});
response.end(JSON.stringify(task2));
}
);
} else {
response.writeHead(200, {
"Content-Type": "application/json" "Content-Type": "application/json"
}); });
response.end(JSON.stringify(task)); response.end(JSON.stringify(task));
}); }
} else {
response.writeHead(200, {
"Content-Type": "application/json"
});
response.end(JSON.stringify(task));
} }
}); );
}); });
} else { } else {
response.writeHead(202, { response.writeHead(202, {
@@ -364,7 +370,7 @@ function listener(request, response) {
}); });
} else if (urlParts.query.connection_request != null) { } else if (urlParts.query.connection_request != null) {
// No task, send connection request only // No task, send connection request only
apiFunctions.connectionRequest(deviceId, (err) => { apiFunctions.connectionRequest(deviceId, err => {
if (err) { if (err) {
response.writeHead(504); response.writeHead(504);
response.end(`${err.name}: ${err.message}`); response.end(`${err.name}: ${err.message}`);
@@ -378,7 +384,7 @@ function listener(request, response) {
response.end(); response.end();
} }
} else { } else {
response.writeHead(405, {"Allow": "POST"}); response.writeHead(405, { Allow: "POST" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else if (TASKS_REGEX.test(urlParts.pathname)) { } else if (TASKS_REGEX.test(urlParts.pathname)) {
@@ -388,63 +394,70 @@ function listener(request, response) {
if (!action || action === "/") { if (!action || action === "/") {
if (request.method === "DELETE") { if (request.method === "DELETE") {
db.tasksCollection.findOne( db.tasksCollection.findOne(
{"_id": new mongodb.ObjectID(taskId)}, { _id: new mongodb.ObjectID(taskId) },
{"device": 1}, (err, task) => { { device: 1 },
if (err) (err, task) => {
return throwError(err, response); if (err) return throwError(err, response);
if (!task) { if (!task) {
response.writeHead(404); response.writeHead(404);
response.end("Task not found"); response.end("Task not found");
return; return;
}
const deviceId = task.device;
db.tasksCollection.remove(
{ _id: new mongodb.ObjectID(taskId) },
err => {
if (err) return throwError(err, response);
db.faultsCollection.remove(
{ _id: `${deviceId}:task_${taskId}` },
err => {
if (err) return throwError(err, response);
cache.del(`${deviceId}_tasks_faults_operations`, err => {
if (err) return throwError(err, response);
response.writeHead(200);
response.end();
});
}
);
}
);
} }
);
const deviceId = task.device;
db.tasksCollection.remove(
{"_id": new mongodb.ObjectID(taskId)}, (err) => {
if (err)
return throwError(err, response);
db.faultsCollection.remove({_id: `${deviceId}:task_${taskId}`}, (err) => {
if (err)
return throwError(err, response);
cache.del(`${deviceId}_tasks_faults_operations`, (err) => {
if (err)
return throwError(err, response);
response.writeHead(200);
response.end();
});
});
});
});
} else { } else {
response.writeHead(405, {"Allow": "PUT DELETE"}); response.writeHead(405, { Allow: "PUT DELETE" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else if (action === "/retry") { } else if (action === "/retry") {
if (request.method === "POST") { if (request.method === "POST") {
db.tasksCollection.findOne({"_id": new mongodb.ObjectID(taskId)}, {"device": 1}, (err, task) => { db.tasksCollection.findOne(
if (err) { _id: new mongodb.ObjectID(taskId) },
return throwError(err, response); { device: 1 },
(err, task) => {
if (err) return throwError(err, response);
const deviceId = task.device; const deviceId = task.device;
db.faultsCollection.remove({_id: `${deviceId}:task_${taskId}`}, (err) => { db.faultsCollection.remove(
if (err) { _id: `${deviceId}:task_${taskId}` },
return throwError(err, response); err => {
if (err) return throwError(err, response);
cache.del(`${deviceId}_tasks_faults_operations`, (err) => { cache.del(`${deviceId}_tasks_faults_operations`, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
}); }
}); );
}
);
} else { } else {
response.writeHead(405, {"Allow": "POST"}); response.writeHead(405, { Allow: "POST" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else { } else {
@@ -452,7 +465,9 @@ function listener(request, response) {
response.end(); response.end();
} }
} else if (FILES_REGEX.test(urlParts.pathname)) { } else if (FILES_REGEX.test(urlParts.pathname)) {
const filename = querystring.unescape(FILES_REGEX.exec(urlParts.pathname)[1]); const filename = querystring.unescape(
FILES_REGEX.exec(urlParts.pathname)[1]
);
if (request.method === "PUT") { if (request.method === "PUT") {
const metadata = { const metadata = {
fileType: request.headers.filetype, fileType: request.headers.filetype,
@@ -463,17 +478,14 @@ function listener(request, response) {
const gs = new mongodb.GridStore(db.mongoDb, filename, filename, "w", { const gs = new mongodb.GridStore(db.mongoDb, filename, filename, "w", {
metadata: metadata metadata: metadata
}); });
gs.open((err, gs) => { gs.open(err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
gs.write(body, (err) => { gs.write(body, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
gs.close((err) => { gs.close(err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(201); response.writeHead(201);
response.end(); response.end();
@@ -481,22 +493,21 @@ function listener(request, response) {
}); });
}); });
} else if (request.method === "DELETE") { } else if (request.method === "DELETE") {
mongodb.GridStore.unlink(db.mongoDb, filename, (err) => { mongodb.GridStore.unlink(db.mongoDb, filename, err => {
if (err) if (err) return throwError(err, response);
return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
}); });
} else { } else {
response.writeHead(405, {"Allow": "PUT, DELETE"}); response.writeHead(405, { Allow: "PUT, DELETE" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
} }
} else if (PING_REGEX.test(urlParts.pathname)) { } else if (PING_REGEX.test(urlParts.pathname)) {
const host = querystring.unescape(PING_REGEX.exec(urlParts.pathname)[1]); const host = querystring.unescape(PING_REGEX.exec(urlParts.pathname)[1]);
childProcess.exec(`ping -w 1 -i 0.2 -c 3 ${host}`, (err, stdout) => { childProcess.exec(`ping -w 1 -i 0.2 -c 3 ${host}`, (err, stdout) => {
if (err) { if (err) {
response.writeHead(404, {"Cache-Control": "no-cache"}); response.writeHead(404, { "Cache-Control": "no-cache" });
response.end(`${err.name}: ${err.message}`); response.end(`${err.name}: ${err.message}`);
return; return;
} }
@@ -509,15 +520,16 @@ function listener(request, response) {
}); });
} else if (DELETE_DEVICE_REGEX.test(urlParts.pathname)) { } else if (DELETE_DEVICE_REGEX.test(urlParts.pathname)) {
if (request.method !== "DELETE") { if (request.method !== "DELETE") {
response.writeHead(405, {"Allow": "DELETE"}); response.writeHead(405, { Allow: "DELETE" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
return; return;
} }
const deviceId = querystring.unescape(DELETE_DEVICE_REGEX.exec(urlParts.pathname)[1]); const deviceId = querystring.unescape(
apiFunctions.deleteDevice(deviceId, (err) => { DELETE_DEVICE_REGEX.exec(urlParts.pathname)[1]
if (err) );
return throwError(err, response); apiFunctions.deleteDevice(deviceId, err => {
if (err) return throwError(err, response);
response.writeHead(200); response.writeHead(200);
response.end(); response.end();
@@ -528,13 +540,15 @@ function listener(request, response) {
// Convert to camel case // Convert to camel case
let i = collectionName.indexOf("_"); let i = collectionName.indexOf("_");
while (i++ >= 0) { while (i++ >= 0) {
const up = i < collectionName.length ? collectionName[i].toUpperCase() : ""; const up =
collectionName = collectionName.slice(0, i - 1) + up + collectionName.slice(i + 1); i < collectionName.length ? collectionName[i].toUpperCase() : "";
collectionName =
collectionName.slice(0, i - 1) + up + collectionName.slice(i + 1);
i = collectionName.indexOf("_", i); i = collectionName.indexOf("_", i);
} }
if (request.method !== "GET" && request.method !== "HEAD") { if (request.method !== "GET" && request.method !== "HEAD") {
response.writeHead(405, {"Allow": "GET, HEAD"}); response.writeHead(405, { Allow: "GET, HEAD" });
response.end("405 Method Not Allowed"); response.end("405 Method Not Allowed");
return; return;
} }
@@ -563,14 +577,14 @@ function listener(request, response) {
break; break;
case "tasks": case "tasks":
q = query.sanitizeQueryTypes(q, { q = query.sanitizeQueryTypes(q, {
_id: (v) => new mongodb.ObjectID(v), _id: v => new mongodb.ObjectID(v),
timestamp: (v) => new Date(v), timestamp: v => new Date(v),
retries: Number retries: Number
}); });
break; break;
case "faults": case "faults":
q = query.sanitizeQueryTypes(q, { q = query.sanitizeQueryTypes(q, {
timestamp: (v) => new Date(v), timestamp: v => new Date(v),
retries: Number retries: Number
}); });
} }
@@ -582,7 +596,7 @@ function listener(request, response) {
projection[p.trim()] = 1; projection[p.trim()] = 1;
} }
const cur = collection.find(q, projection, {batchSize: 50}); const cur = collection.find(q, projection, { batchSize: 50 });
if (urlParts.query.sort) { if (urlParts.query.sort) {
const s = JSON.parse(urlParts.query.sort); const s = JSON.parse(urlParts.query.sort);
@@ -590,27 +604,24 @@ function listener(request, response) {
for (let [k, v] of Object.entries(s)) { for (let [k, v] of Object.entries(s)) {
if (k[k.lastIndexOf(".") + 1] !== "_" && collectionName === "devices") if (k[k.lastIndexOf(".") + 1] !== "_" && collectionName === "devices")
sort[`${k}._value`] = v; sort[`${k}._value`] = v;
else else sort[k] = v;
sort[k] = v;
} }
cur.sort(sort); cur.sort(sort);
} }
if (urlParts.query.skip) if (urlParts.query.skip) cur.skip(parseInt(urlParts.query.skip));
cur.skip(parseInt(urlParts.query.skip));
let limit; let limit;
if (urlParts.query.limit) if (urlParts.query.limit)
cur.limit(limit = parseInt(urlParts.query.limit)); cur.limit((limit = parseInt(urlParts.query.limit)));
cur.count(false, (err, total) => { cur.count(false, (err, total) => {
if (err) if (err) return throwError(err);
return throwError(err);
response.writeHead(200, { response.writeHead(200, {
"Content-Type": "application/json", "Content-Type": "application/json",
"total": total total: total
}); });
if (request.method === "HEAD") { if (request.method === "HEAD") {
@@ -627,8 +638,7 @@ function listener(request, response) {
} }
if (item) { if (item) {
if (i++) if (i++) response.write(",\n");
response.write(",\n");
response.write(JSON.stringify(item)); response.write(JSON.stringify(item));
} }
+32 -46
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -18,25 +18,20 @@
*/ */
"use strict"; "use strict";
class PathSet { class PathSet {
constructor() { constructor() {
this.lengthIndex = []; this.lengthIndex = [];
this.fragmentIndex = []; this.fragmentIndex = [];
} }
get depth() { get depth() {
return this.lengthIndex.length; return this.lengthIndex.length;
} }
add(path) { add(path) {
let p = this.get(path); let p = this.get(path);
if (p) if (p) return p;
return p;
if (!this.lengthIndex[path.length]) if (!this.lengthIndex[path.length])
this.lengthIndex[path.length] = new Set(); this.lengthIndex[path.length] = new Set();
@@ -44,13 +39,11 @@ class PathSet {
this.lengthIndex[path.length].add(path); this.lengthIndex[path.length].add(path);
let wildcard = 0; let wildcard = 0;
for (let i = 0; i < path.length; ++ i) { for (let i = 0; i < path.length; ++i) {
let fragment = path[i]; let fragment = path[i];
if (fragment === "*") if (fragment === "*") wildcard = wildcard | (1 << i);
wildcard = wildcard | (1 << i);
if (!this.fragmentIndex[i]) if (!this.fragmentIndex[i]) this.fragmentIndex[i] = {};
this.fragmentIndex[i] = {};
if (!this.fragmentIndex[i][fragment]) if (!this.fragmentIndex[i][fragment])
this.fragmentIndex[i][fragment] = new Set(); this.fragmentIndex[i][fragment] = new Set();
@@ -66,31 +59,26 @@ class PathSet {
return path; return path;
} }
get(pattern) { get(pattern) {
if (!this.lengthIndex[pattern.length]) if (!this.lengthIndex[pattern.length]) return null;
return null;
if (this.lengthIndex[pattern.length].has(pattern)) if (this.lengthIndex[pattern.length].has(pattern)) return pattern;
return pattern;
let sets = []; let sets = [];
sets.push(this.lengthIndex[pattern.length]); sets.push(this.lengthIndex[pattern.length]);
for (let i = 0; i < pattern.length; ++ i) { for (let i = 0; i < pattern.length; ++i) {
if (!this.fragmentIndex[i]) if (!this.fragmentIndex[i]) return null;
return null;
let fragment = pattern[i]; let fragment = pattern[i];
if (!this.fragmentIndex[i][fragment]) if (!this.fragmentIndex[i][fragment]) return null;
return null;
sets.push(this.fragmentIndex[i][fragment]); sets.push(this.fragmentIndex[i][fragment]);
} }
sets.sort(function(a, b) { sets.sort((a, b) => {
return a.size - b.size; return a.size - b.size;
}); });
@@ -99,36 +87,31 @@ class PathSet {
for (let s of sets) { for (let s of sets) {
if (!s.has(path)) { if (!s.has(path)) {
path = null; path = null;
break break;
} }
} }
if (path)
return path; if (path) return path;
} }
return null; return null;
} }
find(pattern, superset = false, subset = false, depth = pattern.length) { find(pattern, superset = false, subset = false, depth = pattern.length) {
let res = []; let res = [];
let groups = [[]]; let groups = [[]];
for (let i = pattern.length; i <= depth; ++ i) for (let i = pattern.length; i <= depth; ++i)
if (this.lengthIndex[i]) if (this.lengthIndex[i]) groups[0].push(this.lengthIndex[i]);
groups[0].push(this.lengthIndex[i]);
if (!groups[0].length) if (!groups[0].length) return res;
return res;
for (let i = 0; i < pattern.length; ++ i) { for (let i = 0; i < pattern.length; ++i) {
if (!this.fragmentIndex[i]) if (!this.fragmentIndex[i]) return res;
return res;
let fragment = pattern[i]; let fragment = pattern[i];
if (fragment === "*" && subset) if (fragment === "*" && subset) continue;
continue;
let g = []; let g = [];
@@ -138,15 +121,20 @@ class PathSet {
if (fragment !== "*" && superset && this.fragmentIndex[i]["*"]) if (fragment !== "*" && superset && this.fragmentIndex[i]["*"])
g.push(this.fragmentIndex[i]["*"]); g.push(this.fragmentIndex[i]["*"]);
if (!g.length) if (!g.length) return res;
return res;
groups.push(g); groups.push(g);
} }
groups.sort(function(a, b) { groups.sort((a, b) => {
return a.reduce(function(prev, cur) {return prev + cur.size;}, 0) - return (
b.reduce(function(prev, cur) {return prev + cur.size;}, 0); a.reduce((prev, cur) => {
return prev + cur.size;
}, 0) -
b.reduce((prev, cur) => {
return prev + cur.size;
}, 0)
);
}); });
let smallestGroup = groups.shift(); let smallestGroup = groups.shift();
@@ -160,20 +148,18 @@ class PathSet {
break; break;
} }
} }
if (!found) { if (!found) {
path = null; path = null;
break; break;
} }
} }
if (path) if (path) res.push(path);
res.push(path);
} }
} }
return res; return res;
} }
} }
module.exports = PathSet; module.exports = PathSet;
+62 -98
View File
@@ -21,21 +21,16 @@
const common = require("./common"); const common = require("./common");
function stringToRegexp(input, flags) { function stringToRegexp(input, flags) {
if (input.indexOf("*") === -1) if (input.indexOf("*") === -1) return false;
return false;
let output = input.replace(/[\[\]\\\^\$\.\|\?\+\(\)]/, "\\$&"); let output = input.replace(/[[\]\\^$.|?+()]/, "\\$&");
if (output[0] === "*") if (output[0] === "*") output = output.replace(/^\*+/g, "");
output = output.replace(/^\*+/g, ""); else output = "^" + output;
else
output = "^" + output;
if (output[output.length - 1] === "*") if (output[output.length - 1] === "*") output = output.replace(/\*+$/g, "");
output = output.replace(/\*+$/g, ""); else output = output + "$";
else
output = output + "$";
output = output.replace(/[\*]/, ".*"); output = output.replace(/[*]/, ".*");
return new RegExp(output, flags); return new RegExp(output, flags);
} }
@@ -43,19 +38,15 @@ function normalize(input) {
if (common.typeOf(input) === common.STRING_TYPE) { if (common.typeOf(input) === common.STRING_TYPE) {
const vals = [input]; const vals = [input];
let m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input); let m = /^\/(.*?)\/(g?i?m?y?)$/.exec(input);
if (m) if (m) vals.push({ $regex: new RegExp(m[1], m[2]) });
vals.push({"$regex": new RegExp(m[1], m[2])});
if (+input === parseFloat(input)) if (+input === parseFloat(input)) vals.push(+input);
vals.push(+input);
let d = new Date(input); let d = new Date(input);
if (input.length >= 8 && d.getFullYear() > 1983) if (input.length >= 8 && d.getFullYear() > 1983) vals.push(d);
vals.push(d);
let r = stringToRegexp(input); let r = stringToRegexp(input);
if (r !== false) if (r !== false) vals.push({ $regex: r });
vals.push({"$regex": r});
return vals; return vals;
} }
@@ -64,16 +55,13 @@ function normalize(input) {
function expandValue(value) { function expandValue(value) {
if (common.typeOf(value) === common.ARRAY_TYPE) { if (common.typeOf(value) === common.ARRAY_TYPE) {
const a = []; let a = [];
for (let j of value) for (let j of value) a = a.conact(expandValue(j));
a = a.conact(expandValue(j));
return [a]; return [a];
} else if (common.typeOf(value) !== common.OBJECT_TYPE) { } else if (common.typeOf(value) !== common.OBJECT_TYPE) {
const n = normalize(value); const n = normalize(value);
if (common.typeOf(n) !== common.ARRAY_TYPE) if (common.typeOf(n) !== common.ARRAY_TYPE) return [n];
return [n]; else return n;
else
return n;
} }
const objs = []; const objs = [];
@@ -89,14 +77,12 @@ function expandValue(value) {
let i = 0; let i = 0;
while (i < indices.length) { while (i < indices.length) {
const obj = {}; const obj = {};
for (let j = 0; j < keys.length; ++ j) for (let j = 0; j < keys.length; ++j) obj[keys[j]] = values[j][indices[j]];
obj[keys[j]] = values[j][indices[j]];
objs.push(obj); objs.push(obj);
for (i = 0; i < indices.length; ++ i) { for (i = 0; i < indices.length; ++i) {
indices[i] += 1; indices[i] += 1;
if (indices[i] < values[i].length) if (indices[i] < values[i].length) break;
break;
indices[i] = 0; indices[i] = 0;
} }
} }
@@ -107,11 +93,10 @@ function permute(param, val) {
const conditions = []; const conditions = [];
const values = expandValue(val); const values = expandValue(val);
if (param[param.lastIndexOf(".") + 1] !== "_") if (param[param.lastIndexOf(".") + 1] !== "_") param += "._value";
param += "._value";
for (let v of values) { for (let v of values) {
const obj = {} const obj = {};
obj[param] = v; obj[param] = v;
conditions.push(obj); conditions.push(obj);
} }
@@ -122,7 +107,8 @@ function permute(param, val) {
function expand(query) { function expand(query) {
const newQuery = {}; const newQuery = {};
for (let [k, v] of Object.entries(query)) { for (let [k, v] of Object.entries(query)) {
if (k[0] === "$") { // Operator if (k[0] === "$") {
// Operator
newQuery[k] = v.map(e => expand(e)); newQuery[k] = v.map(e => expand(e));
} else { } else {
const conditions = permute(k, v); const conditions = permute(k, v);
@@ -131,16 +117,16 @@ function expand(query) {
if (v && (v["$ne"] != null || v["$not"] != null)) { if (v && (v["$ne"] != null || v["$not"] != null)) {
if (Object.keys(v).length > 1) if (Object.keys(v).length > 1)
throw new Error("Cannot mix $ne or $not with other operators"); throw new Error("Cannot mix $ne or $not with other operators");
for (let c of conditions) for (let c of conditions) newQuery["$an"].push(c);
newQuery["$an"].push(c);
} else { } else {
newQuery["$and"].push({"$or": conditions}); newQuery["$and"].push({ $or: conditions });
} }
} else { } else {
Object.assign(newQuery, conditions[0]); Object.assign(newQuery, conditions[0]);
} }
} }
} }
return newQuery; return newQuery;
} }
@@ -149,16 +135,13 @@ function testExpressions(params, expressions, lop) {
const res = test(params, f); const res = test(params, f);
switch (lop) { switch (lop) {
case "$and": case "$and":
if (!res) if (!res) return false;
return false;
break; break;
case "$or": case "$or":
if (res) if (res) return true;
return true;
break; break;
case "$nor": case "$nor":
if (res) if (res) return false;
return false;
} }
} }
@@ -177,20 +160,18 @@ function testExpressions(params, expressions, lop) {
function test(params, query) { function test(params, query) {
let res; let res;
for (let [k, v] of Object.entries(query)) { for (let [k, v] of Object.entries(query)) {
if (k.charAt(0) === "$") { // Logical operator if (k.charAt(0) === "$") {
// Logical operator
res = testExpressions(params, v, k); res = testExpressions(params, v, k);
} else { } else {
const value = params[k]; const value = params[k];
if (common.typeOf(v) !== common.OBJECT_TYPE) { if (common.typeOf(v) !== common.OBJECT_TYPE) {
// TODO comparing array to regex, array to array, and object to object
if (common.typeOf(value) === common.ARRAY_TYPE) { if (common.typeOf(value) === common.ARRAY_TYPE) {
// TODO comparing array to regex, array to array, and object to object
res = value.indexOf(v) !== -1; res = value.indexOf(v) !== -1;
} else { } else {
if (common.typeOf(v) === common.REGEXP_TYPE) { if (common.typeOf(v) === common.REGEXP_TYPE) res = v.test(value);
res = v.test(value); else res = v === value;
} else {
res = v === value;
}
} }
} else { } else {
for (let [k2, v2] of Object.entries(v)) { for (let [k2, v2] of Object.entries(v)) {
@@ -198,8 +179,7 @@ function test(params, query) {
case "$ne": case "$ne":
if (common.typeOf(value) === common.ARRAY_TYPE) if (common.typeOf(value) === common.ARRAY_TYPE)
res = value.indexOf(v2) === -1; res = value.indexOf(v2) === -1;
else else res = value !== v2;
res = value !== v2;
break; break;
case "$lt": case "$lt":
res = value < v2; res = value < v2;
@@ -218,16 +198,12 @@ function test(params, query) {
break; break;
case "$in": case "$in":
throw new Error("Operator not supported"); throw new Error("Operator not supported");
break;
case "$nin": case "$nin":
throw new Error("Operator not supported"); throw new Error("Operator not supported");
break;
case "$all": case "$all":
throw new Error("Operator not supported"); throw new Error("Operator not supported");
break;
case "$exists": case "$exists":
throw new Error("Operator not supported"); throw new Error("Operator not supported");
break;
default: default:
throw new Error("Operator not supported"); throw new Error("Operator not supported");
} }
@@ -235,8 +211,7 @@ function test(params, query) {
} }
} }
if (!res) if (!res) return false;
return false;
} }
return true; return true;
} }
@@ -246,49 +221,41 @@ function matchType(src, dst) {
if (type === "string") { if (type === "string") {
return String(dst); return String(dst);
} else if (type === "number") { } else if (type === "number") {
if (+dst === parseFloat(dst)) if (+dst === parseFloat(dst)) return +dst;
return +dst; else if (!isNaN(Date.parse(dst))) return Date.parse(dst);
else if (!isNaN(Date.parse(dst)))
return Date.parse(dst);
} else if (type === "boolean") { } else if (type === "boolean") {
const v = String(dst).trim().toLowerCase(); const v = String(dst)
if (v === "true" || v === "1") .trim()
return true; .toLowerCase();
else if (v === "false" || v === "0") if (v === "true" || v === "1") return true;
return false; else if (v === "false" || v === "0") return false;
} }
return dst; return dst;
} }
function testFilter(obj, filter) { function testFilter(obj, filter) {
for (let [k, v] of Object.entries(filter)) { for (let [k, v] of Object.entries(filter)) {
const [param, op] = k.split(/([^a-zA-Z0-9\-\_\.].*)/, 2) const [param, op] = k.split(/([^a-zA-Z0-9\-_.].*)/, 2);
const val = matchType(obj[param], v); const val = matchType(obj[param], v);
switch (op) { switch (op) {
case "=": case "=":
case undefined: case undefined:
if (obj[param] !== val) if (obj[param] !== val) return false;
return false;
break; break;
case ">": case ">":
if (!(obj[param] > val)) if (!(obj[param] > val)) return false;
return false;
break; break;
case ">=": case ">=":
if (!(obj[param] >= val)) if (!(obj[param] >= val)) return false;
return false;
break; break;
case "<": case "<":
if (!(obj[param] < val)) if (!(obj[param] < val)) return false;
return false;
break; break;
case "<=": case "<=":
if (!(obj[param] <= val)) if (!(obj[param] <= val)) return false;
return false;
break; break;
case "!": case "!":
if (obj[param] === val) if (obj[param] === val) return false;
return false;
break; break;
default: default:
throw new Error(`Unrecognized operator ${op}`); throw new Error(`Unrecognized operator ${op}`);
@@ -298,25 +265,21 @@ function testFilter(obj, filter) {
} }
function convertMongoQueryToFilters(query, filters) { function convertMongoQueryToFilters(query, filters) {
filters = filters || {} filters = filters || {};
for (let [k, v] of Object.entries(query)) { for (let [k, v] of Object.entries(query)) {
if (k[0] === "$") { if (k[0] === "$") {
if (k === "$and") if (k === "$and")
for (let vv of v) for (let vv of v) convertMongoQueryToFilters(vv, filters);
convertMongoQueryToFilters(vv, filters); else throw new Error(`Operator ${k} not supported`);
else
throw new Error(`Operator ${k} not supported`);
} else if (k === "_tags") { } else if (k === "_tags") {
if (common.typeOf(v) === common.OBJECT_TYPE) { if (common.typeOf(v) === common.OBJECT_TYPE) {
for (let [kk, vv] of Object.entries(v)) { for (let [kk, vv] of Object.entries(v)) {
vv = vv.replace(/[^a-zA-Z0-9\-]+/g, "_"); vv = vv.replace(/[^a-zA-Z0-9-]+/g, "_");
if (kk === "$ne") if (kk === "$ne") filters[`Tags.${vv}!`] = true;
filters[`Tags.${vv}!`] = true; else throw new Error(`Operator ${kk} not supported`);
else
throw new Error(`Operator ${kk} not supported`);
} }
} else { } else {
v = v.replace(/[^a-zA-Z0-9\-]+/g, "_"); v = v.replace(/[^a-zA-Z0-9-]+/g, "_");
filters[`Tags.${v}`] = true; filters[`Tags.${v}`] = true;
} }
} else { } else {
@@ -379,22 +342,22 @@ function convertMongoQueryToFilters(query, filters) {
} }
} }
} }
return filters; return filters;
} }
function sanitizeQueryTypes(query, types) { function sanitizeQueryTypes(query, types) {
for (let [k, v] of Object.entries(query)) { for (let [k, v] of Object.entries(query)) {
if (k[0] === "$") { // Logical operator if (k[0] === "$") {
for (let vv of v) // Logical operator
sanitizeQueryTypes(vv, types); for (let vv of v) sanitizeQueryTypes(vv, types);
} else if (k in types) { } else if (k in types) {
if (common.typeOf(v) === common.OBJECT_TYPE) { if (common.typeOf(v) === common.OBJECT_TYPE) {
for (let [kk, vv] of Object.entries(v)) { for (let [kk, vv] of Object.entries(v)) {
switch (kk) { switch (kk) {
case "$in": case "$in":
case "$nin": case "$nin":
for (let i = 0; i < vv.length; ++ i) for (let i = 0; i < vv.length; ++i) vv[i] = types[k](vv[i]);
vv[i] = types[k](vv[i]);
break; break;
case "$eq": case "$eq":
case "$gt": case "$gt":
@@ -417,6 +380,7 @@ function sanitizeQueryTypes(query, types) {
} }
} }
} }
return query; return query;
} }
+150 -129
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -28,7 +28,6 @@ const extensions = require("./extensions");
const logger = require("./logger"); const logger = require("./logger");
const scheduling = require("./scheduling"); const scheduling = require("./scheduling");
// Used for throwing to exit user script and commit // Used for throwing to exit user script and commit
const COMMIT = Symbol(); const COMMIT = Symbol();
@@ -49,24 +48,20 @@ function runExtension(sessionContext, key, extCall, callback) {
if (re[key]) { if (re[key]) {
re[key].push(callback); re[key].push(callback);
} } else {
else {
re[key] = [callback]; re[key] = [callback];
extensions.run(extCall, function(err, fault, res) { extensions.run(extCall, (err, fault, res) => {
const callbacks = re[key]; const callbacks = re[key];
delete re[key]; delete re[key];
if (!err && !fault) if (!err && !fault) sessionContext.extensionsCache[key] = res;
sessionContext.extensionsCache[key] = res; for (let c of callbacks) c(err, fault);
for (let c of callbacks)
c(err, fault);
}); });
} }
} }
class SandboxDate { class SandboxDate {
constructor() { constructor() {
if (arguments.length) if (arguments.length) return new Date(...arguments);
return new Date(...arguments);
return new Date(state.sessionContext.timestamp); return new Date(state.sessionContext.timestamp);
} }
@@ -75,120 +70,128 @@ class SandboxDate {
let t = state.sessionContext.timestamp; let t = state.sessionContext.timestamp;
if (typeof intervalOrCron === "number") { if (typeof intervalOrCron === "number") {
if (variance == null) variance = intervalOrCron;
if (variance == null)
variance = intervalOrCron;
let offset = 0; let offset = 0;
if (variance) if (variance)
offset = scheduling.variance(state.sessionContext.deviceId, variance); offset = scheduling.variance(state.sessionContext.deviceId, variance);
t = scheduling.interval(t, intervalOrCron, offset); t = scheduling.interval(t, intervalOrCron, offset);
} } else if (typeof intervalOrCron === "string") {
else if (typeof intervalOrCron === "string") {
let offset = 0; let offset = 0;
if (variance) if (variance)
offset = scheduling.variance(state.sessionContext.deviceId, variance); offset = scheduling.variance(state.sessionContext.deviceId, variance);
let cron = scheduling.parseCron(intervalOrCron); let cron = scheduling.parseCron(intervalOrCron);
t = scheduling.cron(t, cron, offset)[0]; t = scheduling.cron(t, cron, offset)[0];
} } else if (intervalOrCron) {
else if (intervalOrCron) {
throw new Error("Invalid Date.now() argument"); throw new Error("Invalid Date.now() argument");
} }
return t; return t;
} }
}; }
function random() { function random() {
if (!state.rng) if (!state.rng) state.rng = seedrandom(state.sessionContext.deviceId);
state.rng = seedrandom(state.sessionContext.deviceId);
return state.rng(); return state.rng();
} }
random.seed = function(s) { random.seed = function(s) {
state.rng = seedrandom(s); state.rng = seedrandom(s);
} };
class ParameterWrapper { class ParameterWrapper {
constructor(path, attributes, unpacked, unpackedRevision) { constructor(path, attributes, unpacked, unpackedRevision) {
for (let attrName of attributes) { for (let attrName of attributes) {
Object.defineProperty(this, attrName, {get: function() { Object.defineProperty(this, attrName, {
if (state.uncommitted) get: function() {
commit(); if (state.uncommitted) commit();
if (state.revision !== unpackedRevision) {
unpackedRevision = state.revision;
unpacked = device.unpack(
state.sessionContext.deviceData,
path,
state.revision
);
}
if (!unpacked.length) return undefined;
let attr = state.sessionContext.deviceData.attributes.get(
unpacked[0],
state.revision
)[attrName];
if (!attr) return undefined;
return attr[1];
}
});
}
Object.defineProperty(this, "path", {
get: function() {
if (state.uncommitted) commit();
if (state.revision !== unpackedRevision) { if (state.revision !== unpackedRevision) {
unpackedRevision = state.revision; unpackedRevision = state.revision;
unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision); unpacked = device.unpack(
state.sessionContext.deviceData,
path,
state.revision
);
} }
if (!unpacked.length) if (!unpacked.length) return undefined;
return undefined;
let attr = state.sessionContext.deviceData.attributes.get(unpacked[0], state.revision)[attrName]; return unpacked[0].join(".");
if (!attr)
return undefined;
return attr[1];
}});
}
Object.defineProperty(this, "path", {get: function() {
if (state.uncommitted)
commit();
if (state.revision !== unpackedRevision) {
unpackedRevision = state.revision;
unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
} }
});
if (!unpacked.length) Object.defineProperty(this, "size", {
return undefined; get: function() {
if (state.uncommitted) commit();
return unpacked[0].join("."); if (state.revision !== unpackedRevision) {
}}); unpackedRevision = state.revision;
unpacked = device.unpack(
state.sessionContext.deviceData,
path,
state.revision
);
}
Object.defineProperty(this, "size", {get: function() { if (!unpacked.length) return undefined;
if (state.uncommitted)
commit();
if (state.revision !== unpackedRevision) { return unpacked.length;
unpackedRevision = state.revision;
unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
} }
});
if (!unpacked.length)
return undefined;
return unpacked.length;
}});
this[Symbol.iterator] = function*() { this[Symbol.iterator] = function*() {
if (state.uncommitted) if (state.uncommitted) commit();
commit();
if (state.revision !== unpackedRevision) { if (state.revision !== unpackedRevision) {
unpackedRevision = state.revision; unpackedRevision = state.revision;
unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision); unpacked = device.unpack(
state.sessionContext.deviceData,
path,
state.revision
);
} }
for (let p of unpacked) for (let p of unpacked)
yield new ParameterWrapper(p, attributes, [p], state.revision); yield new ParameterWrapper(p, attributes, [p], state.revision);
} };
} }
} }
function declare(path, timestamps, values) { function declare(path, timestamps, values) {
state.uncommitted = true; state.uncommitted = true;
if (!timestamps) if (!timestamps) timestamps = {};
timestamps = {};
if (!values) if (!values) values = {};
values = {};
let parsedPath = common.parsePath(path); let parsedPath = common.parsePath(path);
@@ -196,25 +199,23 @@ function declare(path, timestamps, values) {
let attrs = {}; let attrs = {};
for (let attrName in values) { for (let attrName in values) {
if (!timestamps[attrName]) if (!timestamps[attrName]) timestamps[attrName] = 1;
timestamps[attrName] = 1;
if (attrName === "path") if (attrName === "path") {
declaration[3] = values.path; declaration[3] = values.path;
else { } else {
attrs[attrName] = 1; attrs[attrName] = 1;
declaration[4] = declaration[4] || {}; declaration[4] = declaration[4] || {};
if (attrName === "value" && !Array.isArray(values.value)) if (attrName === "value" && !Array.isArray(values.value))
declaration[4].value = [values.value]; declaration[4].value = [values.value];
else else declaration[4][attrName] = values[attrName];
declaration[4][attrName] = values[attrName];
} }
} }
for (let attrName in timestamps) { for (let attrName in timestamps) {
if (attrName === "path") if (attrName === "path") {
declaration[1] = timestamps.path; declaration[1] = timestamps.path;
else { } else {
attrs[attrName] = 1; attrs[attrName] = 1;
declaration[2] = declaration[2] || {}; declaration[2] = declaration[2] || {};
declaration[2][attrName] = timestamps[attrName]; declaration[2][attrName] = timestamps[attrName];
@@ -226,7 +227,6 @@ function declare(path, timestamps, values) {
return new ParameterWrapper(parsedPath, Object.keys(attrs)); return new ParameterWrapper(parsedPath, Object.keys(attrs));
} }
function clear(path, timestamp, attributes) { function clear(path, timestamp, attributes) {
state.uncommitted = true; state.uncommitted = true;
@@ -234,20 +234,21 @@ function clear(path, timestamp, attributes) {
state.clear.push([common.parsePath(path), timestamp, attributes]); state.clear.push([common.parsePath(path), timestamp, attributes]);
} }
function commit() { function commit() {
++ state.revision; ++state.revision;
state.uncommitted = false; state.uncommitted = false;
if (state.revision === state.maxRevision + 1) if (state.revision === state.maxRevision + 1) {
throw COMMIT; throw COMMIT;
else if (state.revision > state.maxRevision + 1) } else if (state.revision > state.maxRevision + 1) {
throw new Error("Declare function should not be called from within a try/catch block"); throw new Error(
"Declare function should not be called from within a try/catch block"
);
}
} }
function ext() { function ext() {
++ state.extCounter; ++state.extCounter;
let extCall = Array.from(arguments).map(String); let extCall = Array.from(arguments).map(String);
let key = `${state.revision}: ${JSON.stringify(extCall)}`; let key = `${state.revision}: ${JSON.stringify(extCall)}`;
@@ -258,7 +259,6 @@ function ext() {
throw EXT; throw EXT;
} }
function log(msg, meta) { function log(msg, meta) {
if (state.revision === state.maxRevision && state.extCounter >= 0) { if (state.revision === state.maxRevision && state.extCounter >= 0) {
const details = Object.assign({}, meta, { const details = Object.assign({}, meta, {
@@ -277,26 +277,22 @@ function log(msg, meta) {
} }
} }
Object.defineProperty(context, "Date", { value: SandboxDate });
Object.defineProperty(context, "Date", {value: SandboxDate}); Object.defineProperty(context, "declare", { value: declare });
Object.defineProperty(context, "declare", {value: declare}); Object.defineProperty(context, "clear", { value: clear });
Object.defineProperty(context, "clear", {value: clear}); Object.defineProperty(context, "commit", { value: commit });
Object.defineProperty(context, "commit", {value: commit}); Object.defineProperty(context, "ext", { value: ext });
Object.defineProperty(context, "ext", {value: ext}); Object.defineProperty(context, "log", { value: log });
Object.defineProperty(context, "log", {value: log});
// Monkey-patch Math.random() to make it deterministic // Monkey-patch Math.random() to make it deterministic
context.random = random; context.random = random;
vm.runInContext("Math.random = random;", context); vm.runInContext("Math.random = random;", context);
delete context.random; delete context.random;
function errorToFault(err) { function errorToFault(err) {
if (!err) if (!err) return null;
return null;
if (!err.name) if (!err.name) return { code: "script", message: `${err}` };
return {code: "script", message: `${err}`};
let fault = { let fault = {
code: `script.${err.name}`, code: `script.${err.name}`,
@@ -310,7 +306,9 @@ function errorToFault(err) {
if (err.stack) { if (err.stack) {
fault.detail.stack = err.stack; fault.detail.stack = err.stack;
// Trim the stack trace at the self-executing anonymous wrapper function // Trim the stack trace at the self-executing anonymous wrapper function
let stackTrimIndex = fault.detail.stack.match(/\s+at\s[^\s]+\s+at\s[^\s]+\s\(vm\.js.+\)/); let stackTrimIndex = fault.detail.stack.match(
/\s+at\s[^\s]+\s+at\s[^\s]+\s\(vm\.js.+\)/
);
if (stackTrimIndex) if (stackTrimIndex)
fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index); fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index);
} }
@@ -318,8 +316,15 @@ function errorToFault(err) {
return fault; return fault;
} }
function run(
function run(script, globals, sessionContext, startRevision, maxRevision, callback, extCounter = 0) { script,
globals,
sessionContext,
startRevision,
maxRevision,
callback,
extCounter = 0
) {
state = { state = {
sessionContext: sessionContext, sessionContext: sessionContext,
revision: startRevision, revision: startRevision,
@@ -332,24 +337,19 @@ function run(script, globals, sessionContext, startRevision, maxRevision, callba
extCounter: extCounter extCounter: extCounter
}; };
for (let n of Object.keys(context)) for (let n of Object.keys(context)) delete context[n];
delete context[n];
Object.assign(context, globals); Object.assign(context, globals);
let ret, status; let ret, status;
try { try {
ret = script.runInContext(context, {displayErrors: false}); ret = script.runInContext(context, { displayErrors: false });
status = 0; status = 0;
} } catch (err) {
catch (err) { if (err === COMMIT) status = 1;
if (err === COMMIT) else if (err === EXT) status = 2;
status = 1; else return callback(null, errorToFault(err));
else if (err === EXT)
status = 2;
else
return callback(null, errorToFault(err));
} }
let _state = state; let _state = state;
@@ -363,26 +363,47 @@ function run(script, globals, sessionContext, startRevision, maxRevision, callba
let counter = 3; let counter = 3;
for (let key of Object.keys(_state.extensions)) { for (let key of Object.keys(_state.extensions)) {
counter += 2; counter += 2;
runExtension(_state.sessionContext, key, _state.extensions[key], function(err, fault) { runExtension(
if (err || fault) { _state.sessionContext,
if (counter & 1) key,
callback(err, fault); _state.extensions[key],
return counter = 0; (err, fault) => {
if (err || fault) {
if (counter & 1) callback(err, fault);
return (counter = 0);
}
if ((counter -= 2) === 1) {
if (status === 2) {
return run.apply(null, args);
} else {
return callback(
null,
null,
_state.clear,
_state.declarations,
status === 0,
ret
);
}
}
} }
if ((counter -= 2) === 1) );
if (status === 2)
return run.apply(null, args);
else
return callback(null, null, _state.clear, _state.declarations, status === 0, ret);
});
} }
if ((counter -= 2) === 1) if ((counter -= 2) === 1) {
if (status === 2) if (status === 2) {
return run.apply(null, args); return run.apply(null, args);
else } else {
return callback(null, null, _state.clear, _state.declarations, status === 0, ret); return callback(
null,
null,
_state.clear,
_state.declarations,
status === 0,
ret
);
}
}
} }
exports.run = run; exports.run = run;
+8 -17
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -22,7 +22,6 @@ const crypto = require("crypto");
const later = require("later"); const later = require("later");
function md532(str) { function md532(str) {
const digest = crypto const digest = crypto
.createHash("md5") .createHash("md5")
@@ -36,42 +35,34 @@ function md532(str) {
); );
} }
function variance(deviceId, vrnc) {
function variance(deviceId, variance) { return (md532(deviceId) >>> 0) % vrnc;
return (md532(deviceId) >>> 0) % variance;
} }
function interval(timestamp, intrvl, offset = 0) {
function interval(timestamp, interval, offset = 0) { return Math.trunc((timestamp + offset) / intrvl) * intrvl - offset;
return Math.trunc((timestamp + offset) / interval) * interval - offset;
} }
function parseCron(cronExp) { function parseCron(cronExp) {
let parts = cronExp.trim().split(/\s+/); let parts = cronExp.trim().split(/\s+/);
if (parts.length === 5) if (parts.length === 5) parts.unshift("*");
parts.unshift("*");
return later.schedule(later.parse.cron(parts.join(" "), true)); return later.schedule(later.parse.cron(parts.join(" "), true));
} }
function cron(timestamp, schedule, offset = 0) { function cron(timestamp, schedule, offset = 0) {
// TODO later.js doesn't throw erorr if expression is invalid! // TODO later.js doesn't throw erorr if expression is invalid!
const ret = [0, 0]; const ret = [0, 0];
let prev = schedule.prev(1, new Date(timestamp + offset)); let prev = schedule.prev(1, new Date(timestamp + offset));
if (prev) if (prev) ret[0] = prev.setMilliseconds(0) - offset;
ret[0] = prev.setMilliseconds(0) - offset;
let next = schedule.next(1, new Date(timestamp + offset + 1000)); let next = schedule.next(1, new Date(timestamp + offset + 1000));
if (next) if (next) ret[1] = next.setMilliseconds(0) - offset;
ret[1] = next.setMilliseconds(0) - offset;
return ret; return ret;
} }
exports.variance = variance; exports.variance = variance;
exports.interval = interval; exports.interval = interval;
exports.parseCron = parseCron; exports.parseCron = parseCron;
+25 -26
View File
@@ -20,8 +20,7 @@
const service = process.argv[2]; const service = process.argv[2];
if (service == null) if (service == null) throw new Error("Missing argument cwmp, fs, or nbi");
throw new Error("Missing argument cwmp, fs, or nbi");
const cluster = require("cluster"); const cluster = require("cluster");
const path = require("path"); const path = require("path");
@@ -39,17 +38,15 @@ const networkInterface = config.get(`${service.toUpperCase()}_INTERFACE`);
const port = config.get(`${service.toUpperCase()}_PORT`); const port = config.get(`${service.toUpperCase()}_PORT`);
const useHttps = config.get(`${service.toUpperCase()}_SSL`); const useHttps = config.get(`${service.toUpperCase()}_SSL`);
const {listener, onConnection} = require(`./${service}`); const { listener, onConnection } = require(`./${service}`);
let server; let server;
function closeServer(timeout, callback) { function closeServer(timeout, callback) {
if (!server) if (!server) return callback();
return callback();
setTimeout(() => { setTimeout(() => {
if (!callback) if (!callback) return;
return;
// Ignore HTTP requests from connection that may still be open // Ignore HTTP requests from connection that may still be open
server.removeListener("request", listener); server.removeListener("request", listener);
@@ -60,9 +57,8 @@ function closeServer(timeout, callback) {
setTimeout(cb, 1000); setTimeout(cb, 1000);
}, timeout).unref(); }, timeout).unref();
server.close(function() { server.close(() => {
if (!callback) if (!callback) return;
return;
let cb = callback; let cb = callback;
callback = null; callback = null;
@@ -81,12 +77,11 @@ function exit() {
db.disconnect(); db.disconnect();
cache.disconnect(); cache.disconnect();
extensions.killAll(); extensions.killAll();
if (cluster.worker) if (cluster.worker) cluster.worker.disconnect();
cluster.worker.disconnect();
}); });
} }
process.on("uncaughtException", (err) => { process.on("uncaughtException", err => {
logger.error({ logger.error({
message: "Uncaught exception", message: "Uncaught exception",
exception: err, exception: err,
@@ -98,7 +93,10 @@ process.on("uncaughtException", (err) => {
if (useHttps) { if (useHttps) {
const httpsKey = path.resolve(config.get("CONFIG_DIR"), `${service}.key`); const httpsKey = path.resolve(config.get("CONFIG_DIR"), `${service}.key`);
const httpsCert = path.resolve(config.get("CONFIG_DIR"), `${service}.crt`); const httpsCert = path.resolve(config.get("CONFIG_DIR"), `${service}.crt`);
const httpsCa = path.resolve(config.get("CONFIG_DIR"), `${service}.ca-bundle`); const httpsCa = path.resolve(
config.get("CONFIG_DIR"),
`${service}.ca-bundle`
);
const options = { const options = {
key: fs.readFileSync(httpsKey), key: fs.readFileSync(httpsKey),
cert: fs.readFileSync(httpsCert) cert: fs.readFileSync(httpsCert)
@@ -106,24 +104,25 @@ if (useHttps) {
try { try {
// Use intermediate certificates if available // Use intermediate certificates if available
options.ca = fs.readFileSync(httpsCa).toString().match(/\-+BEGIN CERTIFICATE\-+[0-9a-zA-Z\+\-\/\=\s]+?\-+END CERTIFICATE\-+/g); options.ca = fs
} catch (error) {} .readFileSync(httpsCa)
.toString()
.match(/-+BEGIN CERTIFICATE-+[0-9a-zA-Z+\-/=\s]+?-+END CERTIFICATE-+/g);
} catch (error) {
// No intermediate certificate
}
server = https.createServer(options, listener); server = https.createServer(options, listener);
if (onConnection != null) if (onConnection != null) server.on("secureConnection", onConnection);
server.on("secureConnection", onConnection);
} else { } else {
server = http.createServer(listener); server = http.createServer(listener);
if (onConnection != null) if (onConnection != null) server.on("connection", onConnection);
server.on("connection", onConnection);
} }
db.connect((err) => { db.connect(err => {
if (err) if (err) throw err;
throw err;
cache.connect(function(err) { cache.connect(err => {
if (err) if (err) throw err;
throw err;
server.listen(port, networkInterface); server.listen(port, networkInterface);
}); });
+110 -125
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -28,50 +28,50 @@ const NAMESPACES = {
"1.0": { "1.0": {
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/", "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
"xsd": "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
"xsi": "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
"cwmp": "urn:dslforum-org:cwmp-1-0" cwmp: "urn:dslforum-org:cwmp-1-0"
}, },
"1.1": { "1.1": {
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/", "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
"xsd": "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
"xsi": "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
"cwmp": "urn:dslforum-org:cwmp-1-1" cwmp: "urn:dslforum-org:cwmp-1-1"
}, },
"1.2": { "1.2": {
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/", "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
"xsd": "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
"xsi": "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
"cwmp": "urn:dslforum-org:cwmp-1-2" cwmp: "urn:dslforum-org:cwmp-1-2"
}, },
"1.3": { "1.3": {
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/", "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
"xsd": "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
"xsi": "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
"cwmp": "urn:dslforum-org:cwmp-1-2" cwmp: "urn:dslforum-org:cwmp-1-2"
}, },
"1.4": { "1.4": {
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/", "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/", "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
"xsd": "http://www.w3.org/2001/XMLSchema", xsd: "http://www.w3.org/2001/XMLSchema",
"xsi": "http://www.w3.org/2001/XMLSchema-instance", xsi: "http://www.w3.org/2001/XMLSchema-instance",
"cwmp": "urn:dslforum-org:cwmp-1-3" cwmp: "urn:dslforum-org:cwmp-1-3"
} }
}; };
const LIBXMLJS_PARSE_OPTIONS = { const LIBXMLJS_PARSE_OPTIONS = {
"nocdata": true, nocdata: true,
"recover": config.get("XML_RECOVER"), recover: config.get("XML_RECOVER"),
"ignore_enc": config.get("XML_IGNORE_ENC") ignore_enc: config.get("XML_IGNORE_ENC")
}; };
const LIBXMLJS_SAVE_OPTIONS = { const LIBXMLJS_SAVE_OPTIONS = {
"format": config.get("XML_FORMAT"), format: config.get("XML_FORMAT"),
"declaration": !config.get("XML_NO_DECL"), declaration: !config.get("XML_NO_DECL"),
"selfCloseEmpty": !config.get("XML_NO_EMPTY") selfCloseEmpty: !config.get("XML_NO_EMPTY")
}; };
const XML_IGNORE_NAMESPACE = config.get("XML_IGNORE_NAMESPACE"); const XML_IGNORE_NAMESPACE = config.get("XML_IGNORE_NAMESPACE");
@@ -79,32 +79,29 @@ const XML_IGNORE_NAMESPACE = config.get("XML_IGNORE_NAMESPACE");
if (XML_IGNORE_NAMESPACE) { if (XML_IGNORE_NAMESPACE) {
libxmljs.Element.prototype.__find = libxmljs.Element.prototype.find; libxmljs.Element.prototype.__find = libxmljs.Element.prototype.find;
libxmljs.Element.prototype.find = function(xpath, namespaces) { libxmljs.Element.prototype.find = function(xpath, namespaces) {
const p = xpath.replace(/([^\/:]+:)?([^\/]+)(\/|$)/g, "$2$3").replace(/([a-zA-Z0-9_-]+)([^\/]*)(\/|$)/g, "*[local-name()=\"$1\"]$2$3"); const p = xpath
.replace(/([^/:]+:)?([^/]+)(\/|$)/g, "$2$3")
.replace(/([a-zA-Z0-9_-]+)([^/]*)(\/|$)/g, '*[local-name()="$1"]$2$3');
return libxmljs.Element.prototype.__find.call(this, p, namespaces); return libxmljs.Element.prototype.__find.call(this, p, namespaces);
}; };
} }
let warnings; let warnings;
function parseBool(v) { function parseBool(v) {
v = "" + v; v = "" + v;
if (v === "true" || v === "TRUE" || v === "True" || v === "1") if (v === "true" || v === "TRUE" || v === "True" || v === "1") return true;
return true;
else if (v === "false" || v === "FALSE" || v === "False" || v === "0") else if (v === "false" || v === "FALSE" || v === "False" || v === "0")
return false; return false;
else else return null;
return null;
} }
function event(xml) { function event(xml) {
return xml.find("EventStruct/EventCode").map(e => e.text().trim()); return xml.find("EventStruct/EventCode").map(e => e.text().trim());
} }
function parameterInfoList(xml) { function parameterInfoList(xml) {
return xml.find("ParameterInfoStruct").map(function(e) { return xml.find("ParameterInfoStruct").map(e => {
let param = e.get("Name").text(); let param = e.get("Name").text();
let value = e.get("Writable").text(); let value = e.get("Writable").text();
let parsed = parseBool(value); let parsed = parseBool(value);
@@ -121,9 +118,8 @@ function parameterInfoList(xml) {
}); });
} }
function parameterValueList(xml) { function parameterValueList(xml) {
return xml.find("ParameterValueStruct").map(function(e) { return xml.find("ParameterValueStruct").map(e => {
const valueElement = e.get("Value"); const valueElement = e.get("Value");
const valueType = valueElement.attr("type").value(); const valueType = valueElement.attr("type").value();
let param = e.get("Name").text(); let param = e.get("Name").text();
@@ -138,8 +134,7 @@ function parameterValueList(xml) {
}); });
parsed = value; parsed = value;
} }
} } else if (valueType === "xsd:int" || valueType === "xsd:unsignedInt") {
else if (valueType === "xsd:int" || valueType === "xsd:unsignedInt") {
parsed = parseInt(value); parsed = parseInt(value);
if (isNaN(parsed)) { if (isNaN(parsed)) {
warnings.push({ warnings.push({
@@ -148,8 +143,7 @@ function parameterValueList(xml) {
}); });
parsed = value; parsed = value;
} }
} } else if (valueType === "xsd:dateTime") {
else if (valueType === "xsd:dateTime") {
parsed = Date.parse(value); parsed = Date.parse(value);
if (isNaN(parsed)) { if (isNaN(parsed)) {
warnings.push({ warnings.push({
@@ -160,18 +154,16 @@ function parameterValueList(xml) {
} }
} }
return [param, parsed, valueType]; return [param, parsed, valueType];
}); });
} }
function GetParameterNames(xml, methodRequest) { function GetParameterNames(xml, methodRequest) {
const el = xml.node("cwmp:GetParameterNames"); const el = xml.node("cwmp:GetParameterNames");
el.node("ParameterPath").text(methodRequest.parameterPath); el.node("ParameterPath").text(methodRequest.parameterPath);
el.node("NextLevel").text(+methodRequest.nextLevel); el.node("NextLevel").text(+methodRequest.nextLevel);
} }
function GetParameterNamesResponse(xml) { function GetParameterNamesResponse(xml) {
return { return {
name: "GetParameterNamesResponse", name: "GetParameterNamesResponse",
@@ -179,18 +171,15 @@ function GetParameterNamesResponse(xml) {
}; };
} }
function GetParameterValues(xml, methodRequest) { function GetParameterValues(xml, methodRequest) {
const el = xml.node("cwmp:GetParameterValues").node("ParameterNames"); const el = xml.node("cwmp:GetParameterValues").node("ParameterNames");
el.attr({ el.attr({
"soap-enc:arrayType": `xsd:string[${methodRequest.parameterNames.length}]` "soap-enc:arrayType": `xsd:string[${methodRequest.parameterNames.length}]`
}); });
for (const p of methodRequest.parameterNames) for (const p of methodRequest.parameterNames) el.node("string").text(p);
el.node("string").text(p);
} }
function GetParameterValuesResponse(xml) { function GetParameterValuesResponse(xml) {
return { return {
name: "GetParameterValuesResponse", name: "GetParameterValuesResponse",
@@ -198,34 +187,36 @@ function GetParameterValuesResponse(xml) {
}; };
} }
function SetParameterValues(xml, methodRequest) { function SetParameterValues(xml, methodRequest) {
const el = xml.node("cwmp:SetParameterValues"); const el = xml.node("cwmp:SetParameterValues");
const paramList = el.node("ParameterList"); const paramList = el.node("ParameterList");
paramList.attr({ paramList.attr({
"soap-enc:arrayType": `cwmp:ParameterValueStruct[${methodRequest.parameterList.length}]` "soap-enc:arrayType": `cwmp:ParameterValueStruct[${
methodRequest.parameterList.length
}]`
}); });
for (const p of methodRequest.parameterList) { for (const p of methodRequest.parameterList) {
const pvs = paramList.node("ParameterValueStruct"); const pvs = paramList.node("ParameterValueStruct");
pvs.node("Name").text(p[0]); pvs.node("Name").text(p[0]);
let val = p[1]; let val = p[1];
if (p[2] === 'xsd:dateTime' && typeof val === 'number') { if (p[2] === "xsd:dateTime" && typeof val === "number") {
val = (new Date(val)).toISOString(); val = new Date(val).toISOString();
if (methodRequest.DATETIME_MILLISECONDS === false) if (methodRequest.DATETIME_MILLISECONDS === false)
val = val.replace('.000', ''); val = val.replace(".000", "");
} }
if (p[2] === 'xsd:boolean' && typeof val === 'boolean') { if (p[2] === "xsd:boolean" && typeof val === "boolean")
if (methodRequest.BOOLEAN_LITERAL === false) if (methodRequest.BOOLEAN_LITERAL === false) val = +val;
val = +val;
} pvs
pvs.node("Value").attr({"xsi:type": p[2]}).text(val); .node("Value")
.attr({ "xsi:type": p[2] })
.text(val);
} }
el.node("ParameterKey").text(methodRequest.parameterKey || ""); el.node("ParameterKey").text(methodRequest.parameterKey || "");
} }
function SetParameterValuesResponse(xml) { function SetParameterValuesResponse(xml) {
return { return {
name: "SetParameterValuesResponse", name: "SetParameterValuesResponse",
@@ -233,14 +224,12 @@ function SetParameterValuesResponse(xml) {
}; };
} }
function AddObject(xml, methodRequest) { function AddObject(xml, methodRequest) {
const el = xml.node("cwmp:AddObject"); const el = xml.node("cwmp:AddObject");
el.node("ObjectName").text(methodRequest.objectName); el.node("ObjectName").text(methodRequest.objectName);
el.node("ParameterKey").text(methodRequest.parameterKey || ""); el.node("ParameterKey").text(methodRequest.parameterKey || "");
} }
function AddObjectResponse(xml) { function AddObjectResponse(xml) {
return { return {
name: "AddObjectResponse", name: "AddObjectResponse",
@@ -249,14 +238,12 @@ function AddObjectResponse(xml) {
}; };
} }
function DeleteObject(xml, methodRequest) { function DeleteObject(xml, methodRequest) {
const el = xml.node("cwmp:DeleteObject"); const el = xml.node("cwmp:DeleteObject");
el.node("ObjectName").text(methodRequest.objectName); el.node("ObjectName").text(methodRequest.objectName);
el.node("ParameterKey").text(methodRequest.parameterKey || ""); el.node("ParameterKey").text(methodRequest.parameterKey || "");
} }
function DeleteObjectResponse(xml) { function DeleteObjectResponse(xml) {
return { return {
name: "DeleteObjectResponse", name: "DeleteObjectResponse",
@@ -264,32 +251,27 @@ function DeleteObjectResponse(xml) {
}; };
} }
function Reboot(xml, methodRequest) { function Reboot(xml, methodRequest) {
const el = xml.node("cwmp:Reboot"); const el = xml.node("cwmp:Reboot");
el.node("CommandKey").text(methodRequest.commandKey || ""); el.node("CommandKey").text(methodRequest.commandKey || "");
} }
function RebootResponse() {
function RebootResponse(xml) {
return { return {
name: "RebootResponse" name: "RebootResponse"
}; };
} }
function FactoryReset(xml) {
function FactoryReset(xml, methodRequest) {
xml.node("cwmp:FactoryReset"); xml.node("cwmp:FactoryReset");
} }
function FactoryResetResponse() {
function FactoryResetResponse(xml, methodRequest) {
return { return {
name: "FactoryResetResponse" name: "FactoryResetResponse"
}; };
} }
function Download(xml, methodRequest) { function Download(xml, methodRequest) {
const el = xml.node("cwmp:Download"); const el = xml.node("cwmp:Download");
el.node("CommandKey").text(methodRequest.commandKey || ""); el.node("CommandKey").text(methodRequest.commandKey || "");
@@ -304,7 +286,6 @@ function Download(xml, methodRequest) {
el.node("FailureURL").text(methodRequest.failureUrl || ""); el.node("FailureURL").text(methodRequest.failureUrl || "");
} }
function DownloadResponse(xml) { function DownloadResponse(xml) {
const res = { const res = {
name: "DownloadResponse", name: "DownloadResponse",
@@ -319,44 +300,41 @@ function DownloadResponse(xml) {
return res; return res;
} }
function Inform(xml) { function Inform(xml) {
return { return {
name: "Inform", name: "Inform",
parameterList: parameterValueList(xml.get("ParameterList")), parameterList: parameterValueList(xml.get("ParameterList")),
deviceId: { deviceId: {
"Manufacturer": xml.get("DeviceId/Manufacturer").text(), Manufacturer: xml.get("DeviceId/Manufacturer").text(),
"OUI": xml.get("DeviceId/OUI").text(), OUI: xml.get("DeviceId/OUI").text(),
"ProductClass": xml.get("DeviceId/ProductClass").text(), ProductClass: xml.get("DeviceId/ProductClass").text(),
"SerialNumber": xml.get("DeviceId/SerialNumber").text() SerialNumber: xml.get("DeviceId/SerialNumber").text()
}, },
event: event(xml.get("Event")), event: event(xml.get("Event")),
retryCount: parseInt(xml.get("RetryCount").text()) retryCount: parseInt(xml.get("RetryCount").text())
}; };
} }
function InformResponse(xml) {
function InformResponse(xml, methodResponse) { xml
xml.node("cwmp:InformResponse").node("MaxEnvelopes").text(1); .node("cwmp:InformResponse")
.node("MaxEnvelopes")
.text(1);
} }
function GetRPCMethods() {
function GetRPCMethods(xml) { return { name: "GetRPCMethods" };
return {name: "GetRPCMethods"};
} }
function GetRPCMethodsResponse(xml, methodResponse) { function GetRPCMethodsResponse(xml, methodResponse) {
const el = xml.node("cwmp:GetRPCMethodsResponse").node("MethodList"); const el = xml.node("cwmp:GetRPCMethodsResponse").node("MethodList");
el.attr({ el.attr({
"soap-enc:arrayType": `xsd:string[${methodResponse.methodList.length}]` "soap-enc:arrayType": `xsd:string[${methodResponse.methodList.length}]`
}); });
for (const m of methodResponse.methodList) for (const m of methodResponse.methodList) el.node("string").text(m);
el.node("string").text(m);
} }
function TransferComplete(xml) { function TransferComplete(xml) {
return { return {
name: "TransferComplete", name: "TransferComplete",
@@ -367,12 +345,10 @@ function TransferComplete(xml) {
}; };
} }
function TransferCompleteResponse(xml) {
function TransferCompleteResponse(xml, methodResponse) {
xml.node("cwmp:TransferCompleteResponse").text(""); xml.node("cwmp:TransferCompleteResponse").text("");
} }
function RequestDownload(xml) { function RequestDownload(xml) {
return { return {
name: "RequestDownload", name: "RequestDownload",
@@ -380,12 +356,10 @@ function RequestDownload(xml) {
}; };
} }
function RequestDownloadResponse(xml) {
function RequestDownloadResponse(xml, methodResponse) {
xml.node("cwmp:RequestDownloadResponse").text(""); xml.node("cwmp:RequestDownloadResponse").text("");
} }
function faultStruct(xml) { function faultStruct(xml) {
const f = { const f = {
faultCode: xml.get("FaultCode").text(), faultCode: xml.get("FaultCode").text(),
@@ -393,8 +367,7 @@ function faultStruct(xml) {
}; };
for (const e of xml.find("SetParameterValuesFault")) { for (const e of xml.find("SetParameterValuesFault")) {
if (!f.setParameterValuesFault) if (!f.setParameterValuesFault) f.setParameterValuesFault = [];
f.setParameterValuesFault = [];
f.setParameterValuesFault.push({ f.setParameterValuesFault.push({
parameterName: e.get("ParameterName").text(), parameterName: e.get("ParameterName").text(),
@@ -406,7 +379,6 @@ function faultStruct(xml) {
return f; return f;
} }
function fault(xml, cwmpVersion) { function fault(xml, cwmpVersion) {
return { return {
faultCode: xml.get("faultcode").text(), faultCode: xml.get("faultcode").text(),
@@ -415,7 +387,6 @@ function fault(xml, cwmpVersion) {
}; };
} }
function request(data, cwmpVersion, warn) { function request(data, cwmpVersion, warn) {
warnings = warn; warnings = warn;
@@ -423,19 +394,24 @@ function request(data, cwmpVersion, warn) {
cwmpVersion: cwmpVersion cwmpVersion: cwmpVersion
}; };
if (!data.length) if (!data.length) return rpc;
return rpc;
const xml = libxmljs.parseXml(data, LIBXMLJS_PARSE_OPTIONS); const xml = libxmljs.parseXml(data, LIBXMLJS_PARSE_OPTIONS);
let methodElement; let methodElement;
if (rpc.cwmpVersion) { if (rpc.cwmpVersion) {
methodElement = xml.get("/soap-env:Envelope/soap-env:Body/cwmp:*", NAMESPACES[rpc.cwmpVersion]); methodElement = xml.get(
} "/soap-env:Envelope/soap-env:Body/cwmp:*",
else { NAMESPACES[rpc.cwmpVersion]
);
} else {
// cwmpVersion not passed, thus it's an inform request // cwmpVersion not passed, thus it's an inform request
methodElement = xml.get("/soap-env:Envelope/soap-env:Body/*", NAMESPACES["1.0"]); methodElement = xml.get(
"/soap-env:Envelope/soap-env:Body/*",
NAMESPACES["1.0"]
);
let timeoutElement;
switch (methodElement.namespace().href()) { switch (methodElement.namespace().href()) {
case "urn:dslforum-org:cwmp-1-0": case "urn:dslforum-org:cwmp-1-0":
rpc.cwmpVersion = "1.0"; rpc.cwmpVersion = "1.0";
@@ -444,12 +420,14 @@ function request(data, cwmpVersion, warn) {
rpc.cwmpVersion = "1.1"; rpc.cwmpVersion = "1.1";
break; break;
case "urn:dslforum-org:cwmp-1-2": case "urn:dslforum-org:cwmp-1-2":
const timeoutElement = xml.get("/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout", NAMESPACES["1.2"]); timeoutElement = xml.get(
"/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout",
NAMESPACES["1.2"]
);
if (timeoutElement) { if (timeoutElement) {
rpc.sessionTimeout = parseInt(timeoutElement.text()); rpc.sessionTimeout = parseInt(timeoutElement.text());
rpc.cwmpVersion = "1.3"; rpc.cwmpVersion = "1.3";
} } else {
else {
rpc.cwmpVersion = "1.2"; rpc.cwmpVersion = "1.2";
} }
break; break;
@@ -461,17 +439,24 @@ function request(data, cwmpVersion, warn) {
} }
if (!rpc.sessionTimeout) { if (!rpc.sessionTimeout) {
const timeoutElement = xml.get("/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout", NAMESPACES[rpc.cwmpVersion]); timeoutElement = xml.get(
if (timeoutElement) "/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout",
rpc.sessionTimeout = parseInt(timeoutElement.text()); NAMESPACES[rpc.cwmpVersion]
);
if (timeoutElement) rpc.sessionTimeout = parseInt(timeoutElement.text());
} }
} }
let idElement = xml.get("/soap-env:Envelope/soap-env:Header/cwmp:ID", NAMESPACES[rpc.cwmpVersion]); let idElement = xml.get(
if (idElement) "/soap-env:Envelope/soap-env:Header/cwmp:ID",
rpc.id = idElement.text(); NAMESPACES[rpc.cwmpVersion]
);
if (idElement) rpc.id = idElement.text();
if (methodElement && !(XML_IGNORE_NAMESPACE && methodElement.name() === "Fault")) { if (
methodElement &&
!(XML_IGNORE_NAMESPACE && methodElement.name() === "Fault")
) {
switch (methodElement.name()) { switch (methodElement.name()) {
case "Inform": case "Inform":
rpc.cpeRequest = Inform(methodElement); rpc.cpeRequest = Inform(methodElement);
@@ -512,16 +497,17 @@ function request(data, cwmpVersion, warn) {
default: default:
throw new Error(`8000 Method not supported ${methodElement.name()}`); throw new Error(`8000 Method not supported ${methodElement.name()}`);
} }
} } else {
else { const faultElement = xml.get(
const faultElement = xml.get("/soap-env:Envelope/soap-env:Body/soap-env:Fault", NAMESPACES[rpc.cwmpVersion]); "/soap-env:Envelope/soap-env:Body/soap-env:Fault",
NAMESPACES[rpc.cwmpVersion]
);
rpc.cpeFault = fault(faultElement, rpc.cwmpVersion); rpc.cpeFault = fault(faultElement, rpc.cwmpVersion);
} }
return rpc; return rpc;
} }
function createSoapEnv(cwmpVersion) { function createSoapEnv(cwmpVersion) {
const xml = libxmljs.Document(); const xml = libxmljs.Document();
const env = xml.node("soap-env:Envelope"); const env = xml.node("soap-env:Envelope");
@@ -532,21 +518,22 @@ function createSoapEnv(cwmpVersion) {
return env; return env;
} }
function response(rpc) { function response(rpc) {
const headers = { const headers = {
"Server": SERVER_NAME, Server: SERVER_NAME,
"SOAPServer": SERVER_NAME SOAPServer: SERVER_NAME
}; };
if (!rpc) if (!rpc) return { code: 204, headers: headers, data: new Buffer(0) };
return {code: 204, headers: headers, data: new Buffer(0)};
const env = createSoapEnv(rpc.cwmpVersion); const env = createSoapEnv(rpc.cwmpVersion);
const header = env.node("soap-env:Header"); const header = env.node("soap-env:Header");
header.node("cwmp:ID").attr({ header
"soap-env:mustUnderstand": 1 .node("cwmp:ID")
}).text(rpc.id); .attr({
"soap-env:mustUnderstand": 1
})
.text(rpc.id);
const body = env.node("soap-env:Body"); const body = env.node("soap-env:Body");
@@ -567,8 +554,7 @@ function response(rpc) {
default: default:
throw new Error(`Unknown method response type ${rpc.acsResponse.name}`); throw new Error(`Unknown method response type ${rpc.acsResponse.name}`);
} }
} } else if (rpc.acsRequest) {
else if (rpc.acsRequest) {
switch (rpc.acsRequest.name) { switch (rpc.acsRequest.name) {
case "GetParameterNames": case "GetParameterNames":
GetParameterNames(body, rpc.acsRequest); GetParameterNames(body, rpc.acsRequest);
@@ -599,7 +585,7 @@ function response(rpc) {
} }
} }
headers["Content-Type"] = "text/xml; charset=\"utf-8\""; headers["Content-Type"] = 'text/xml; charset="utf-8"';
return { return {
code: 200, code: 200,
headers: headers, headers: headers,
@@ -607,6 +593,5 @@ function response(rpc) {
}; };
} }
exports.request = request; exports.request = request;
exports.response = response; exports.response = response;
+65 -96
View File
@@ -1,5 +1,5 @@
/** /**
* Copyright 2013-2017 Zaid Abdulla * Copyright 2013-2018 Zaid Abdulla
* *
* This file is part of GenieACS. * This file is part of GenieACS.
* *
@@ -20,18 +20,21 @@
const NONEXISTENT = new Object(); const NONEXISTENT = new Object();
function compareEquality(a, b) { function compareEquality(a, b) {
var t = typeof a; var t = typeof a;
if (t === 'number' || (a == null) || t === 'boolean' || t === 'string' || t === 'symbol') if (
t === "number" ||
a == null ||
t === "boolean" ||
t === "string" ||
t === "symbol"
)
return a === b; return a === b;
return JSON.stringify(a) === JSON.stringify(b); return JSON.stringify(a) === JSON.stringify(b);
} }
class VersionedMap { class VersionedMap {
constructor() { constructor() {
this._sizeDiff = [0]; this._sizeDiff = [0];
this._revision = 0; this._revision = 0;
@@ -39,71 +42,59 @@ class VersionedMap {
this.dirty = 0; this.dirty = 0;
} }
get size() { get size() {
return this.map.size + this._sizeDiff[this.revision]; return this.map.size + this._sizeDiff[this.revision];
} }
get revision() { get revision() {
return this._revision; return this._revision;
} }
set revision(rev) { set revision(rev) {
for (let i = this._sizeDiff.length; i <= rev; ++ i) for (let i = this._sizeDiff.length; i <= rev; ++i)
this._sizeDiff[i] = this._sizeDiff[i - 1]; this._sizeDiff[i] = this._sizeDiff[i - 1];
this._revision = rev; this._revision = rev;
} }
get(key, rev) { get(key, rev) {
if (rev == null) if (rev == null) rev = this._revision;
rev = this._revision
var v = this.map.get(key); var v = this.map.get(key);
if (!v) if (!v) return;
return;
for (let i = Math.min(v.length - 1, rev); i >= 0; -- i) for (let i = Math.min(v.length - 1, rev); i >= 0; --i) {
if (i in v) if (i in v) {
if (v[i] === NONEXISTENT) if (v[i] === NONEXISTENT) return undefined;
return undefined; else return v[i];
else }
return v[i]; }
} }
has(key, rev) { has(key, rev) {
if (rev == null) if (rev == null) rev = this._revision;
rev = this._revision
var v = this.map.get(key); var v = this.map.get(key);
if (v == null) if (v == null) return false;
return false;
for (let i = Math.min(v.length - 1, rev); i >= 0; -- i) for (let i = Math.min(v.length - 1, rev); i >= 0; --i) {
if (i in v) if (i in v) {
if (v[i] === NONEXISTENT) if (v[i] === NONEXISTENT) return false;
return false; else return true;
else }
return true; }
return false; return false;
} }
set(key, value, rev) { set(key, value, rev) {
if (rev == null) if (rev == null) rev = this._revision;
rev = this._revision;
var v = this.map.get(key); var v = this.map.get(key);
if (!v) { if (!v) {
this.dirty |= 1 << rev; this.dirty |= 1 << rev;
for (let i = 0; i < rev; ++ i) for (let i = 0; i < rev; ++i) this._sizeDiff[i] -= 1;
this._sizeDiff[i] -= 1;
v = []; v = [];
v[rev] = value; v[rev] = value;
@@ -114,71 +105,59 @@ class VersionedMap {
var old = v[Math.min(rev, v.length - 1)]; var old = v[Math.min(rev, v.length - 1)];
if (rev < v.length - 1) { if (rev < v.length - 1) {
if (compareEquality(value, old)) if (compareEquality(value, old)) return;
return else throw new Error("Cannot modify old revisions");
else
throw new Error('Cannot modify old revisions');
} }
this.dirty |= 1 << rev; this.dirty |= 1 << rev;
if (old === NONEXISTENT) if (old === NONEXISTENT) ++this._sizeDiff[rev];
++ this._sizeDiff[rev];
v[rev] = value; v[rev] = value;
} }
delete(key, rev) { delete(key, rev) {
if (rev == null) if (rev == null) rev = this._revision;
rev = this._revision;
var v = this.map.get(key); var v = this.map.get(key);
if (!v) if (!v) return false;
return false;
if (rev < v.length - 1) if (rev < v.length - 1) throw new Error("Cannot modify old revisions");
throw new Error('Cannot modify old revisions');
var old = v[v.length - 1]; var old = v[v.length - 1];
if (old === NONEXISTENT) if (old === NONEXISTENT) return false;
return false;
this.dirty |= 1 << rev; this.dirty |= 1 << rev;
-- this._sizeDiff[rev]; --this._sizeDiff[rev];
v[rev] = NONEXISTENT; v[rev] = NONEXISTENT;
return true; return true;
} }
getRevisions(key) { getRevisions(key) {
var v = this.map.get(key); var v = this.map.get(key);
if (!v) if (!v) return null;
return null;
var res = {}; var res = {};
for (let i in v) for (let i in v) {
if (v[i] === NONEXISTENT) if (v[i] === NONEXISTENT) res.delete |= 1 << i;
res.delete |= 1 << i; else res[i] = v[i];
else }
res[i] = v[i];
return res; return res;
} }
setRevisions(key, revisions) { setRevisions(key, revisions) {
var del = 0; var del = 0;
var rev = []; var rev = [];
var minKey = 999; var minKey = 999;
for (let k in revisions) { for (let k in revisions) {
if (k === 'delete') if (k === "delete") {
del = revisions[k]; del = revisions[k];
else { } else {
let r = parseInt(k); let r = parseInt(k);
minKey = Math.min(minKey, r); minKey = Math.min(minKey, r);
for (let i = this._sizeDiff.length; i <= r; ++ i) for (let i = this._sizeDiff.length; i <= r; ++i)
this._sizeDiff[i] = this._sizeDiff[i - 1]; this._sizeDiff[i] = this._sizeDiff[i - 1];
this.dirty |= 1 << r; this.dirty |= 1 << r;
@@ -186,63 +165,57 @@ class VersionedMap {
} }
} }
for (let i = 0; i < minKey; ++ i) for (let i = 0; i < minKey; ++i) --this._sizeDiff[i];
-- this._sizeDiff[i];
this.dirty |= del; this.dirty |= del;
for (let i = 0; del > 0; del >>= 1, ++ i) for (let i = 0; del > 0; del >>= 1, ++i) if (del & 1) rev[i] = NONEXISTENT;
if (del & 1)
rev[i] = NONEXISTENT;
this.map.set(key, rev); this.map.set(key, rev);
} }
getDiff(key) { getDiff(key) {
var revisions = this.map.get(key); var revisions = this.map.get(key);
if (!revisions) if (!revisions) return [];
return [];
var current = revisions[revisions.length - 1]; var current = revisions[revisions.length - 1];
if (current === NONEXISTENT) { if (current === NONEXISTENT) {
if (0 in revisions) if (0 in revisions) return [key, revisions[0]];
return [key, revisions[0]]; else return [key];
else } else if (0 in revisions) {
return [key];
} else if (0 in revisions)
return [key, revisions[0], current]; return [key, revisions[0], current];
else } else {
// TODO don't use sparse arrays?
// eslint-disable-next-line no-sparse-arrays
return [key, , current]; return [key, , current];
}
} }
*diff() { *diff() {
for (let pair of this.map) { for (let pair of this.map) {
let current = pair[1][pair[1].length - 1]; let current = pair[1][pair[1].length - 1];
if (current === NONEXISTENT) { if (current === NONEXISTENT) {
if (0 in pair[1]) if (0 in pair[1]) yield [pair[0], pair[1][0]];
yield [pair[0], pair[1][0]]; else yield [pair[0]];
else } else if (0 in pair[1]) {
yield [pair[0]];
} else if (0 in pair[1])
yield [pair[0], pair[1][0], current]; yield [pair[0], pair[1][0], current];
else } else {
// TODO don't use sparse arrays?
// eslint-disable-next-line no-sparse-arrays
yield [pair[0], , current]; yield [pair[0], , current];
}
} }
} }
collapse(revision) { collapse(revision) {
if (this._sizeDiff.length <= revision) if (this._sizeDiff.length <= revision) return;
return;
this._sizeDiff[revision] = this._sizeDiff[this._sizeDiff.length - 1]; this._sizeDiff[revision] = this._sizeDiff[this._sizeDiff.length - 1];
this._sizeDiff.splice(revision + 1, this._sizeDiff.length); this._sizeDiff.splice(revision + 1, this._sizeDiff.length);
let d = this.dirty >> revision; let d = this.dirty >> revision;
this.dirty = this.dirty ^ (d << revision); this.dirty = this.dirty ^ (d << revision);
this.dirty |= (+!!d) << revision; this.dirty |= +!!d << revision;
for (let pair of this.map) { for (let pair of this.map) {
let k = pair[0]; let k = pair[0];
@@ -250,18 +223,14 @@ class VersionedMap {
let last = v[v.length - 1]; let last = v[v.length - 1];
if (last === NONEXISTENT) { if (last === NONEXISTENT) {
v.splice(revision, v.length); v.splice(revision, v.length);
if (v.some(function(val) { return true; })) if (v.some(() => true)) v[revision] = NONEXISTENT;
v[revision] = NONEXISTENT; else this.map.delete(k);
else } else {
this.map.delete(k);
}
else {
v[revision] = last; v[revision] = last;
v.splice(revision + 1, v.length); v.splice(revision + 1, v.length);
} }
} }
} }
} }
module.exports = VersionedMap; module.exports = VersionedMap;
+4 -1
View File
@@ -28,6 +28,8 @@
"redis": "^2.6.5" "redis": "^2.6.5"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^5.4.0",
"prettier": "^1.14.2"
}, },
"engines": { "engines": {
"node": ">=6.9.2 <9.0" "node": ">=6.9.2 <9.0"
@@ -36,6 +38,7 @@
"compile": "echo 'Compile step no longer required, skipping.'", "compile": "echo 'Compile step no longer required, skipping.'",
"configure": "cp -n ./config/config-sample.json ./config/config.json", "configure": "cp -n ./config/config-sample.json ./config/config.json",
"install": "npm run configure", "install": "npm run configure",
"test": "node test" "test": "node test",
"lint": "eslint --fix lib/*.js; prettier --write lib/*.js"
} }
} }