Introduce ESLint and Prettier
This commit is contained in:
1 parent
9c943d0bbf
commit
e494bdca8a
26 files changed
+1585
-1594
No files matched your search
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -16,3 +16,5 @@ config/ext
|
||||
|
||||
debug/*
|
||||
!debug/.gitkeep
|
||||
|
||||
.eslintrc.json
|
||||
+199
-136
@@ -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,70 +103,96 @@ 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"]);
|
||||
if (authHeader.method === "Basic") {
|
||||
if (!allowBasicAuth) {
|
||||
request.abort();
|
||||
return callback(new Error("Basic HTTP authentication not allowed"));
|
||||
}
|
||||
options.headers = {
|
||||
"Authorization": auth.basic(username || "", password || "")
|
||||
};
|
||||
} else if (authHeader.method === "Digest") {
|
||||
options.headers = {
|
||||
"Authorization": auth.digest(username || "", password || "", options.path, "GET", null, authHeader)
|
||||
};
|
||||
}
|
||||
|
||||
const request = http.get(options, (res) => {
|
||||
if (res.statusCode === 0) {
|
||||
// Workaround for some devices unexpectedly closing the connection
|
||||
request = http.get(options, (res) => {
|
||||
callback(statusToError(res.statusCode));
|
||||
res.resume();
|
||||
}).on("error", () => {
|
||||
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 || "")
|
||||
};
|
||||
} else if (authHeader.method === "Digest") {
|
||||
options.headers = {
|
||||
Authorization: auth.digest(
|
||||
username || "",
|
||||
password || "",
|
||||
options.path,
|
||||
"GET",
|
||||
null,
|
||||
authHeader
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const req = http
|
||||
.get(options, res => {
|
||||
if (res.statusCode === 0) {
|
||||
// Workaround for some devices unexpectedly closing the connection
|
||||
let req2;
|
||||
req2 = http
|
||||
.get(options, res2 => {
|
||||
callback(statusToError(res2.statusCode));
|
||||
res2.resume();
|
||||
})
|
||||
.on("error", () => {
|
||||
req2.abort();
|
||||
callback(statusToError(0));
|
||||
})
|
||||
.on("socket", socket => {
|
||||
socket.setTimeout(timeout);
|
||||
socket.on("timeout", () => {
|
||||
req2.abort();
|
||||
});
|
||||
});
|
||||
req2();
|
||||
} else {
|
||||
callback(statusToError(res.statusCode));
|
||||
}
|
||||
res.resume();
|
||||
})
|
||||
.on("error", () => {
|
||||
req.abort();
|
||||
callback(statusToError(0));
|
||||
}).on("socket", (socket) => {
|
||||
})
|
||||
.on("socket", socket => {
|
||||
socket.setTimeout(timeout);
|
||||
socket.on("timeout", () => {
|
||||
request.abort();
|
||||
req.abort();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
callback(statusToError(res.statusCode));
|
||||
}
|
||||
res.resume();
|
||||
}).on("error", () => {
|
||||
request.abort();
|
||||
callback(statusToError(0));
|
||||
}).on("socket", (socket) => {
|
||||
socket.setTimeout(timeout);
|
||||
socket.on("timeout", () => {
|
||||
request.abort();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
callback(statusToError(res.statusCode));
|
||||
}
|
||||
// No listener for data so emit resume
|
||||
res.resume();
|
||||
}).on("error", (err) => {
|
||||
request.abort();
|
||||
callback(statusToError(0));
|
||||
}).on("socket", (socket) => {
|
||||
socket.setTimeout(timeout);
|
||||
socket.on("timeout", () => {
|
||||
} else {
|
||||
callback(statusToError(response.statusCode));
|
||||
}
|
||||
// No listener for data so emit resume
|
||||
response.resume();
|
||||
})
|
||||
.on("error", () => {
|
||||
request.abort();
|
||||
callback(statusToError(0));
|
||||
})
|
||||
.on("socket", socket => {
|
||||
socket.setTimeout(timeout);
|
||||
socket.on("timeout", () => {
|
||||
request.abort();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
callback(err);
|
||||
});
|
||||
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) => {
|
||||
username = u;
|
||||
password = p;
|
||||
conReq();
|
||||
});
|
||||
[username, password] = config.auth.connectionRequest(deviceId, connectionRequestUrl, username, password);
|
||||
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
|
||||
);
|
||||
}
|
||||
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)}\\:`
|
||||
}
|
||||
}, (err) => {
|
||||
if (err)
|
||||
return callback(err);
|
||||
db.operationsCollection.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);
|
||||
cache.del(`${deviceId}_tasks_faults_operations`, callback);
|
||||
});
|
||||
});
|
||||
},
|
||||
err => {
|
||||
if (err) return callback(err);
|
||||
db.operationsCollection.remove(
|
||||
{
|
||||
_id: {
|
||||
$regex: `^${common.escapeRegExp(deviceId)}\\:`
|
||||
}
|
||||
},
|
||||
err => {
|
||||
if (err) return callback(err);
|
||||
cache.del(`${deviceId}_tasks_faults_operations`, callback);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+34
-22
@@ -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;
|
||||
}
|
||||
|
||||
+104
-110
@@ -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,191 +43,184 @@ 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});
|
||||
mongoDb = db;
|
||||
mongoCollection = db.collection("cache");
|
||||
mongoCollection.ensureIndex({ expire: 1 }, { expireAfterSeconds: 0 });
|
||||
|
||||
const now = Date.now();
|
||||
mongoDb.command({hostInfo: 1}, function(err, res) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
mongoTimeOffset = res.system.currentTime.getTime() - now;
|
||||
exports.get = mongoGet;
|
||||
exports.set = mongoSet;
|
||||
exports.del = mongoDel;
|
||||
exports.pop = mongoPop;
|
||||
exports.lock = mongoLock;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
const now = Date.now();
|
||||
mongoDb.command({ hostInfo: 1 }, (err, res) => {
|
||||
if (err) return callback(err);
|
||||
mongoTimeOffset = res.system.currentTime.getTime() - now;
|
||||
exports.get = mongoGet;
|
||||
exports.set = mongoSet;
|
||||
exports.del = mongoDel;
|
||||
exports.pop = mongoPop;
|
||||
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");
|
||||
});
|
||||
}
|
||||
else {
|
||||
redisClient.eval(EXTEND_SCRIPT, 1, lockName, token, ttl, 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");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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"]);
|
||||
if (res["value"]["expire"] > new Date(Date.now() - mongoTimeOffset))
|
||||
return callback(null, res["value"]["value"]);
|
||||
|
||||
callback();
|
||||
});
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
+133
-134
@@ -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 ||
|
||||
a === undefined ||
|
||||
t === 'number' ||
|
||||
t === 'boolean' ||
|
||||
t === 'string' ||
|
||||
t === 'symbol')
|
||||
if (
|
||||
a === null ||
|
||||
a === undefined ||
|
||||
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,35 +298,45 @@ 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,18 +344,14 @@ function set(deviceData, path, timestamp, attributes, toClear) {
|
||||
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
@@ -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
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
+226
-216
@@ -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();
|
||||
});
|
||||
});
|
||||
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", {
|
||||
"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);
|
||||
if (status === "timeout") {
|
||||
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, task2) => {
|
||||
if (err) return throwError(err, response);
|
||||
|
||||
response.writeHead(202, "Task faulted", {
|
||||
response.writeHead(202, "Task faulted", {
|
||||
"Content-Type": "application/json"
|
||||
});
|
||||
response.end(JSON.stringify(task2));
|
||||
}
|
||||
);
|
||||
} else {
|
||||
response.writeHead(200, {
|
||||
"Content-Type": "application/json"
|
||||
});
|
||||
response.end(JSON.stringify(task));
|
||||
});
|
||||
} 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,63 +394,70 @@ 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);
|
||||
response.end("Task not found");
|
||||
return;
|
||||
if (!task) {
|
||||
response.writeHead(404);
|
||||
response.end("Task not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const deviceId = task.device;
|
||||
db.tasksCollection.remove(
|
||||
{ _id: new mongodb.ObjectID(taskId) },
|
||||
err => {
|
||||
if (err) return throwError(err, response);
|
||||
|
||||
db.faultsCollection.remove(
|
||||
{ _id: `${deviceId}:task_${taskId}` },
|
||||
err => {
|
||||
if (err) return throwError(err, response);
|
||||
|
||||
cache.del(`${deviceId}_tasks_faults_operations`, err => {
|
||||
if (err) return throwError(err, response);
|
||||
|
||||
response.writeHead(200);
|
||||
response.end();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const deviceId = task.device;
|
||||
db.tasksCollection.remove(
|
||||
{"_id": new mongodb.ObjectID(taskId)}, (err) => {
|
||||
if (err)
|
||||
return throwError(err, response);
|
||||
|
||||
db.faultsCollection.remove({_id: `${deviceId}:task_${taskId}`}, (err) => {
|
||||
if (err)
|
||||
return throwError(err, response);
|
||||
|
||||
cache.del(`${deviceId}_tasks_faults_operations`, (err) => {
|
||||
if (err)
|
||||
return throwError(err, response);
|
||||
|
||||
response.writeHead(200);
|
||||
response.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
);
|
||||
} else {
|
||||
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);
|
||||
const deviceId = task.device;
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+150
-129
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
if (!unpacked.length) return undefined;
|
||||
|
||||
let attr = state.sessionContext.deviceData.attributes.get(
|
||||
unpacked[0],
|
||||
state.revision
|
||||
)[attrName];
|
||||
|
||||
if (!attr) return undefined;
|
||||
|
||||
return attr[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Object.defineProperty(this, "path", {
|
||||
get: function() {
|
||||
if (state.uncommitted) commit();
|
||||
|
||||
if (state.revision !== unpackedRevision) {
|
||||
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];
|
||||
|
||||
if (!attr)
|
||||
return undefined;
|
||||
|
||||
return attr[1];
|
||||
}});
|
||||
}
|
||||
|
||||
Object.defineProperty(this, "path", {get: function() {
|
||||
if (state.uncommitted)
|
||||
commit();
|
||||
|
||||
if (state.revision !== unpackedRevision) {
|
||||
unpackedRevision = state.revision;
|
||||
unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
|
||||
return unpacked[0].join(".");
|
||||
}
|
||||
});
|
||||
|
||||
if (!unpacked.length)
|
||||
return undefined;
|
||||
Object.defineProperty(this, "size", {
|
||||
get: function() {
|
||||
if (state.uncommitted) commit();
|
||||
|
||||
return unpacked[0].join(".");
|
||||
}});
|
||||
if (state.revision !== unpackedRevision) {
|
||||
unpackedRevision = state.revision;
|
||||
unpacked = device.unpack(
|
||||
state.sessionContext.deviceData,
|
||||
path,
|
||||
state.revision
|
||||
);
|
||||
}
|
||||
|
||||
Object.defineProperty(this, "size", {get: function() {
|
||||
if (state.uncommitted)
|
||||
commit();
|
||||
if (!unpacked.length) return undefined;
|
||||
|
||||
if (state.revision !== unpackedRevision) {
|
||||
unpackedRevision = state.revision;
|
||||
unpacked = device.unpack(state.sessionContext.deviceData, path, state.revision);
|
||||
return unpacked.length;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (err || fault) {
|
||||
if (counter & 1)
|
||||
callback(err, fault);
|
||||
return counter = 0;
|
||||
runExtension(
|
||||
_state.sessionContext,
|
||||
key,
|
||||
_state.extensions[key],
|
||||
(err, fault) => {
|
||||
if (err || fault) {
|
||||
if (counter & 1) callback(err, fault);
|
||||
return (counter = 0);
|
||||
}
|
||||
if ((counter -= 2) === 1) {
|
||||
if (status === 2) {
|
||||
return run.apply(null, args);
|
||||
} else {
|
||||
return callback(
|
||||
null,
|
||||
null,
|
||||
_state.clear,
|
||||
_state.declarations,
|
||||
status === 0,
|
||||
ret
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((counter -= 2) === 1)
|
||||
if (status === 2)
|
||||
return run.apply(null, args);
|
||||
else
|
||||
return callback(null, null, _state.clear, _state.declarations, status === 0, ret);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
if ((counter -= 2) === 1)
|
||||
if (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
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
+110
-125
@@ -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({
|
||||
@@ -160,18 +154,16 @@ function parameterValueList(xml) {
|
||||
}
|
||||
}
|
||||
|
||||
return [param, parsed, valueType];
|
||||
return [param, parsed, valueType];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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({
|
||||
"soap-env:mustUnderstand": 1
|
||||
}).text(rpc.id);
|
||||
header
|
||||
.node("cwmp:ID")
|
||||
.attr({
|
||||
"soap-env:mustUnderstand": 1
|
||||
})
|
||||
.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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user