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
+1485 -1494

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