Refactor code to use Path class

This commit is contained in:
Zaid Abdulla committed 2019-02-26 01:38:23 -08:00
1 parent 006f83a478
commit f878d828eb
13 files changed
+663 -784

No files matched your search

+8 -4
View File
@@ -40,13 +40,17 @@ function errorToFault(err: Error): Fault {
}; };
if (err.stack) { if (err.stack) {
fault.detail.stack = err.stack; fault.detail["stack"] = err.stack;
// Trim the stack trace // Trim the stack trace
const stackTrimIndex = fault.detail.stack.match( const stackTrimIndex = fault.detail["stack"].match(
/\s+at\s[^\s]+\s\(.*genieacs-ext:.+\)/ /\s+at\s[^\s]+\s\(.*genieacs-ext:.+\)/
); );
if (stackTrimIndex) if (stackTrimIndex) {
fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index); fault.detail["stack"] = fault.detail["stack"].slice(
0,
stackTrimIndex.index
);
}
} }
return fault; return fault;
-134
View File
@@ -17,8 +17,6 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ParamPath } from "./types";
export const UNDEFINED_TYPE = "[object Undefined]"; export const UNDEFINED_TYPE = "[object Undefined]";
export const NULL_TYPE = "[object Null]"; export const NULL_TYPE = "[object Null]";
export const BOOLEAN_TYPE = "[object Boolean]"; export const BOOLEAN_TYPE = "[object Boolean]";
@@ -55,138 +53,6 @@ export function generateDeviceId(deviceIdStruct): string {
return esc(deviceIdStruct["OUI"]) + "-" + esc(deviceIdStruct["SerialNumber"]); return esc(deviceIdStruct["OUI"]) + "-" + esc(deviceIdStruct["SerialNumber"]);
} }
export function parseAlias(pattern, start, res): number {
const aliases = [];
let i = start;
while (i < pattern.length && pattern[i] !== "]") {
const alias = [];
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
let j = (i = parsePath(pattern, i, alias) + 1);
while (pattern[j] !== "]" && pattern[j] !== ",") {
if (pattern[j] === '"' && i === j) {
++j;
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");
}
let value = pattern.slice(i, j).trim();
i = j;
if (value[0] === '"') {
try {
value = JSON.parse(value);
} catch (error) {
throw new Error("Invalid alias expression");
}
}
alias.push(value);
aliases.push(alias);
if (pattern[i] === ",") ++i;
}
// Sort to ensure identical expressions have idential string representation
function srt(a, b): number {
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) {
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;
} 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])) {
return -1;
} else if (a[j][k] > b[j][k]) {
return 1;
} else if (a[j][k] < b[j][k]) {
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.length > b.length) return -1;
else if (a.length < b.length) return 1;
return 0;
}
aliases.sort(srt);
res.push([].concat.apply([], aliases));
return i;
}
/* eslint-disable @typescript-eslint/no-use-before-define */
export function parsePath(pattern, start, res): number;
export function parsePath(pattern, start?): ParamPath;
export function parsePath(pattern, start?, res?): number | ParamPath {
const path = [] as ParamPath;
path.wildcard = 0;
path.alias = 0;
let i = start || 0;
// Colon separator is needed for parseAlias
if (i < pattern.length && pattern[i] !== ":") {
for (;;) {
if (pattern[i] === "[") {
path.alias |= 1 << path.length;
i = parseAlias(pattern, i + 1, path) + 1;
} else {
const j = i;
while (i < pattern.length && pattern[i] !== ":" && pattern[i] !== ".")
++i;
const n = pattern.slice(j, i);
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");
++i;
}
}
Object.freeze(path);
if (res == null) return path;
res.push(path);
return i;
}
export function addPathMeta(path): ParamPath {
if (path.alias != null && path.wildcard != null) return path;
path.alias = 0;
path.wildcard = 0;
for (const [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]);
} else if (p === "*") {
path.wildcard |= 1 << i;
}
}
Object.freeze(path);
return path;
}
// Source: http://stackoverflow.com/a/6969486 // Source: http://stackoverflow.com/a/6969486
export function escapeRegExp(str): string { export function escapeRegExp(str): string {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&"); return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
+28 -33
View File
@@ -17,11 +17,11 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ParamPath } from "./types"; import Path from "./path";
export default class PathSet { export default class PathSet {
private lengthIndex: Set<ParamPath>[]; private lengthIndex: Set<Path>[];
private fragmentIndex: Map<string, Set<ParamPath>>[]; private fragmentIndex: Map<string, Set<Path>>[];
public constructor() { public constructor() {
this.lengthIndex = []; this.lengthIndex = [];
@@ -32,7 +32,8 @@ export default class PathSet {
return this.lengthIndex.length; return this.lengthIndex.length;
} }
public add(path: ParamPath): ParamPath { public add(path: Path): Path {
if (path.alias) throw new Error("PathSet does not support aliased paths");
const p = this.get(path); const p = this.get(path);
if (p) return p; if (p) return p;
@@ -45,43 +46,36 @@ export default class PathSet {
const lengthIndex = this.lengthIndex[path.length]; const lengthIndex = this.lengthIndex[path.length];
lengthIndex.add(path); lengthIndex.add(path);
let wildcard = 0;
for (let i = 0; i < path.length; ++i) { for (let i = 0; i < path.length; ++i) {
const fragment = path[i]; const fragment = path.segments[i] as string;
if (fragment === "*") wildcard = wildcard | (1 << i);
const fragmentIndex = this.fragmentIndex[i]; const fragmentIndex = this.fragmentIndex[i];
let fragmentIndexSet = fragmentIndex.get(fragment); let fragmentIndexSet = fragmentIndex.get(fragment);
if (!fragmentIndexSet) { if (!fragmentIndexSet) {
fragmentIndexSet = new Set<ParamPath>(); fragmentIndexSet = new Set<Path>();
fragmentIndex.set(fragment, fragmentIndexSet); fragmentIndex.set(fragment, fragmentIndexSet);
} }
fragmentIndexSet.add(path); fragmentIndexSet.add(path);
} }
if (path.wildcard == null) {
path.alias = 0;
path.wildcard = wildcard;
Object.freeze(path);
}
return path; return path;
} }
public get(pattern: ParamPath): ParamPath { public get(path: Path): Path {
const lengthIndex = this.lengthIndex[pattern.length]; if (path.alias) throw new Error("PathSet does not support aliased paths");
const lengthIndex = this.lengthIndex[path.length];
if (!lengthIndex || !lengthIndex.size) return null; if (!lengthIndex || !lengthIndex.size) return null;
if (lengthIndex.has(pattern)) return pattern; if (lengthIndex.has(path)) return path;
const sets = [lengthIndex]; const sets = [lengthIndex];
for (let i = 0; i < pattern.length; ++i) { for (let i = 0; i < path.length; ++i) {
const fragmentIndex = this.fragmentIndex[i]; const fragmentIndex = this.fragmentIndex[i];
if (!fragmentIndex || !fragmentIndex.size) return null; if (!fragmentIndex || !fragmentIndex.size) return null;
const fragment = pattern[i]; const fragment = path.segments[i] as string;
const fragmentIndexSet = fragmentIndex.get(fragment); const fragmentIndexSet = fragmentIndex.get(fragment);
if (!fragmentIndexSet) return null; if (!fragmentIndexSet) return null;
@@ -92,39 +86,40 @@ export default class PathSet {
sets.sort((a, b) => a.size - b.size); sets.sort((a, b) => a.size - b.size);
const smallestSet = sets.shift(); const smallestSet = sets.shift();
for (let path of smallestSet) { for (let p of smallestSet) {
for (const s of sets) { for (const s of sets) {
if (!s.has(path)) { if (!s.has(p)) {
path = null; p = null;
break; break;
} }
} }
if (path) return path; if (p) return p;
} }
return null; return null;
} }
public find( public find(
pattern: ParamPath, path: Path,
superset: boolean = false, superset: boolean = false,
subset: boolean = false, subset: boolean = false,
depth: number = pattern.length depth: number = path.length
): ParamPath[] { ): Path[] {
if (path.alias) throw new Error("PathSet does not support aliased paths");
const res = []; const res = [];
const groups = [[]]; const groups = [[]];
for (let i = pattern.length; i <= depth; ++i) for (let i = path.length; i <= depth; ++i)
if (this.lengthIndex[i]) groups[0].push(this.lengthIndex[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) { for (let i = 0; i < path.length; ++i) {
const fragmentIndex = this.fragmentIndex[i]; const fragmentIndex = this.fragmentIndex[i];
if (!fragmentIndex) return res; if (!fragmentIndex) return res;
const fragment = pattern[i]; const fragment = path.segments[i] as string;
if (fragment === "*" && subset) continue; if (fragment === "*" && subset) continue;
@@ -149,22 +144,22 @@ export default class PathSet {
const smallestGroup = groups.shift(); const smallestGroup = groups.shift();
for (const set of smallestGroup) { for (const set of smallestGroup) {
for (let path of set) { for (let p of set) {
for (const group of groups) { for (const group of groups) {
let found = false; let found = false;
for (const s of group) { for (const s of group) {
if (s.has(path)) { if (s.has(p)) {
found = true; found = true;
break; break;
} }
} }
if (!found) { if (!found) {
path = null; p = null;
break; break;
} }
} }
if (path) res.push(path); if (p) res.push(p);
} }
} }
return res; return res;
+6
View File
@@ -172,6 +172,12 @@ export default class Path {
const alias = this.alias | (path.alias << this.segments.length); const alias = this.alias | (path.alias << this.segments.length);
return new Path(segments, wildcard, alias); return new Path(segments, wildcard, alias);
} }
public stripAlias(): Path {
if (!this.alias) return this;
const segments = this.segments.map(s => (Array.isArray(s) ? "*" : s));
return new Path(segments, this.wildcard | this.alias, 0);
}
} }
const interval = setInterval(() => { const interval = setInterval(() => {
+25 -15
View File
@@ -32,13 +32,14 @@ import * as localCache from "./local-cache";
import * as db from "./db"; import * as db from "./db";
import * as logger from "./logger"; import * as logger from "./logger";
import * as scheduling from "./scheduling"; import * as scheduling from "./scheduling";
import Path from "./common/path";
import { import {
SessionContext, SessionContext,
ParamPath,
AcsRequest, AcsRequest,
SetAcsRequest, SetAcsRequest,
SessionFault, SessionFault,
Operation Operation,
Fault
} from "./types"; } from "./types";
const MAX_CYCLES = 4; const MAX_CYCLES = 4;
@@ -144,9 +145,19 @@ function writeResponse(
} }
} }
function recordFault(sessionContext, fault, provisions, channels): void; function recordFault(
function recordFault(sessionContext, fault): void; sessionContext: SessionContext,
function recordFault(sessionContext, fault, provisions?, channels?): void { fault: Fault,
provisions,
channels
): void;
function recordFault(sessionContext: SessionContext, fault: Fault): void;
function recordFault(
sessionContext: SessionContext,
fault: Fault,
provisions?,
channels?
): void {
if (!provisions) { if (!provisions) {
provisions = sessionContext.provisions; provisions = sessionContext.provisions;
channels = sessionContext.channels; channels = sessionContext.channels;
@@ -157,7 +168,10 @@ function recordFault(sessionContext, fault, provisions?, channels?): void {
const provs = sessionContext.faults[channel] const provs = sessionContext.faults[channel]
? sessionContext.faults[channel].provisions ? sessionContext.faults[channel].provisions
: []; : [];
faults[channel] = Object.assign({ provisions: provs }, fault); faults[channel] = Object.assign(
{ provisions: provs, timestamp: sessionContext.timestamp },
fault
) as SessionFault;
if (channel.startsWith("task_")) { if (channel.startsWith("task_")) {
const taskId = channel.slice(5); const taskId = channel.slice(5);
for (const t of sessionContext.tasks) for (const t of sessionContext.tasks)
@@ -334,17 +348,13 @@ function applyPresets(sessionContext: SessionContext): void {
deviceData.attributes.revision = 1; deviceData.attributes.revision = 1;
const deviceEvents = {}; const deviceEvents = {};
for (const p of deviceData.paths.find( for (const p of deviceData.paths.find(Path.parse("Events.*"), false, true)) {
["Events", "*"] as ParamPath,
false,
true
)) {
const attrs = deviceData.attributes.get(p); const attrs = deviceData.attributes.get(p);
if (attrs && attrs.value && attrs.value[1][0] >= sessionContext.timestamp) if (attrs && attrs.value && attrs.value[1][0] >= sessionContext.timestamp)
deviceEvents[p[1]] = true; deviceEvents[p.segments[1] as string] = true;
} }
const parameters: { [name: string]: ParamPath } = {}; const parameters: { [name: string]: Path } = {};
const filteredPresets = []; const filteredPresets = [];
for (const preset of presets) { for (const preset of presets) {
@@ -377,7 +387,7 @@ function applyPresets(sessionContext: SessionContext): void {
for (const k of Object.keys(preset.precondition)) { for (const k of Object.keys(preset.precondition)) {
sessionContext.channels[preset.channel] = 0; sessionContext.channels[preset.channel] = 0;
const p = k.split(/([^a-zA-Z0-9\-_.].*)/, 1)[0]; const p = k.split(/([^a-zA-Z0-9\-_.].*)/, 1)[0];
parameters[p] = common.parsePath(p); parameters[p] = Path.parse(p);
} }
} }
@@ -409,7 +419,7 @@ function applyPresets(sessionContext: SessionContext): void {
const parameterValues = {}; const parameterValues = {};
for (const [k, v] of Object.entries(parameters)) { for (const [k, v] of Object.entries(parameters)) {
const unpacked = device.unpack(deviceData, v); const unpacked = device.unpack(deviceData, v);
if (!unpacked[0]) continue; if (!unpacked.length) continue;
const attrs = deviceData.attributes.get(unpacked[0]); const attrs = deviceData.attributes.get(unpacked[0]);
if (attrs && attrs.value && attrs.value[1]) if (attrs && attrs.value && attrs.value[1])
parameterValues[k] = attrs.value[1][0]; parameterValues[k] = attrs.value[1][0];
+156 -117
View File
@@ -21,6 +21,8 @@ import { MongoClient, ObjectID } from "mongodb";
import { get } from "./config"; import { get } from "./config";
import { escapeRegExp } from "./common"; import { escapeRegExp } from "./common";
import { parse } from "./common/expression-parser"; import { parse } from "./common/expression-parser";
import { DeviceData, Attributes } from "./types";
import Path from "./common/path";
export let tasksCollection, export let tasksCollection,
devicesCollection, devicesCollection,
@@ -87,55 +89,66 @@ function optimizeProjection(obj: {}): {} {
return obj; return obj;
} }
export function fetchDevice(id, timestamp, patterns, callback): void { export function fetchDevice(
id: string,
timestamp: number,
patterns: [Path, number][],
callback
): void {
const MAX_DEPTH = +get("MAX_DEPTH", id); const MAX_DEPTH = +get("MAX_DEPTH", id);
if (!patterns || !patterns.length) patterns = [[[], (1 << MAX_DEPTH) - 1]]; if (!patterns || !patterns.length)
patterns = [[Path.parse(""), (1 << MAX_DEPTH) - 1]];
let projection = {}; let projection = {};
const projectionTree = {}; const projectionTree = {};
function func(path, pats, projTree): void { function func(
const children = {}; path: string,
pathLength: number,
pats: [Path, number][],
projTree
): void {
const children: { [fragment: string]: [Path, number][] } = {};
for (const pat of pats) { for (const pat of pats) {
const fragment = pat[0][path.length] || "*"; const fragment = (pat[0].segments[pathLength] as string) || "*";
if (fragment === "*") { if (fragment === "*") {
if (pat[1] << path.length) projection[path.join(".")] = 1; if (pat[1] << pathLength) projection[path.slice(0, -1)] = 1;
return; return;
} }
if (!projTree[fragment]) projTree[fragment] = {}; if (!projTree[fragment]) projTree[fragment] = {};
if (pat[1] & (1 << path.length)) { if (pat[1] & (1 << pathLength)) {
projection[path.concat("_timestamp").join(".")] = 1; projection[path + "_timestamp"] = 1;
projection[path.concat("_object").join(".")] = 1; projection[path + "_object"] = 1;
projection[path.concat("_instance").join(".")] = 1; projection[path + "_instance"] = 1;
projection[path.concat([fragment, "_timestamp"]).join(".")] = 1; projection[path + fragment + "._timestamp"] = 1;
projection[path.concat([fragment, "_value"]).join(".")] = 1; projection[path + fragment + "._value"] = 1;
projection[path.concat([fragment, "_type"]).join(".")] = 1; projection[path + fragment + "._type"] = 1;
projection[path.concat([fragment, "_object"]).join(".")] = 1; projection[path + fragment + "._object"] = 1;
projection[path.concat([fragment, "_instance"]).join(".")] = 1; projection[path + fragment + "._instance"] = 1;
projection[path.concat([fragment, "_writable"]).join(".")] = 1; projection[path + fragment + "._writable"] = 1;
projection[path.concat([fragment, "_orig"]).join(".")] = 1; projection[path + fragment + "._orig"] = 1;
} }
if (pat[1] >> (path.length + 1)) { if (pat[1] >> (pathLength + 1)) {
if (!children[fragment]) children[fragment] = []; if (!children[fragment]) children[fragment] = [];
children[fragment].push(pat); children[fragment].push(pat);
} }
} }
for (const [k, v] of Object.entries(children)) for (const [k, v] of Object.entries(children))
func(path.concat(k), v, projTree[k]); func(path + k + ".", pathLength + 1, v, projTree[k]);
} }
func([], patterns, projectionTree); func("", 0, patterns, projectionTree);
delete projectionTree["DeviceID"]; delete projectionTree["DeviceID"];
delete projectionTree["Events"]; delete projectionTree["Events"];
delete projectionTree["Tags"]; delete projectionTree["Tags"];
const res = []; const res: [Path, number, Attributes?][] = [];
const loaded = []; const loaded: [Path, number][] = [];
projection = optimizeProjection(projection); projection = optimizeProjection(projection);
@@ -143,38 +156,47 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (k === "" || k === "Events" || k.startsWith("Events.")) { if (k === "" || k === "Events" || k.startsWith("Events.")) {
if (k === "" || k === "Events" || k === "Events._writable") { if (k === "" || k === "Events" || k === "Events._writable") {
res.push([ res.push([
["Events"], Path.parse("Events"),
timestamp, timestamp,
{ object: [timestamp, 1], writable: [timestamp, 0] } { object: [timestamp, 1], writable: [timestamp, 0] }
]); ]);
if (k === "Events._writable") loaded.push([["Events"], 1]); if (k === "Events._writable") loaded.push([Path.parse("Events"), 1]);
} }
if (k === "Events") { if (k === "Events") {
projection["_lastInform"] = 1; projection["_lastInform"] = 1;
projection["_lastBoot"] = 1; projection["_lastBoot"] = 1;
projection["_lastBootstrap"] = 1; projection["_lastBootstrap"] = 1;
projection["_registered"] = 1; projection["_registered"] = 1;
loaded.push([["Events"], (1 << MAX_DEPTH) - 1]); loaded.push([Path.parse("Events"), (1 << MAX_DEPTH) - 1]);
} else if (k === "Events.Inform._writable" || k === "Events.Inform") { } else if (k === "Events.Inform._writable" || k === "Events.Inform") {
projection["_lastInform"] = 1; projection["_lastInform"] = 1;
loaded.push([["Events", "Inform"], 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([Path.parse("Events.Inform"), 1 ^ ((1 << MAX_DEPTH) - 1)]);
} else if (k === "Events.1_BOOT._writable" || k === "Events.1_BOOT") { } else if (k === "Events.1_BOOT._writable" || k === "Events.1_BOOT") {
projection["_lastBoot"] = 1; projection["_lastBoot"] = 1;
loaded.push([["Events", "1_BOOT"], 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([Path.parse("Events.1_BOOT"), 1 ^ ((1 << MAX_DEPTH) - 1)]);
} else if ( } else if (
k === "Events.0_BOOTSTRAP._writable" || k === "Events.0_BOOTSTRAP._writable" ||
k === "Events.0_BOOTSTRAP" k === "Events.0_BOOTSTRAP"
) { ) {
projection["_lastBootstrap"] = 1; projection["_lastBootstrap"] = 1;
loaded.push([["Events", "0_BOOTSTRAP"], 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([
Path.parse("Events.0_BOOTSTRAP"),
1 ^ ((1 << MAX_DEPTH) - 1)
]);
} else if ( } else if (
k === "Events.Registered._writable" || k === "Events.Registered._writable" ||
k === "Events.Registered" k === "Events.Registered"
) { ) {
projection["_registered"] = 1; projection["_registered"] = 1;
loaded.push([["Events", "Registered"], 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([
Path.parse("Events.Registered"),
1 ^ ((1 << MAX_DEPTH) - 1)
]);
} else if (k.endsWith("._writable") && k !== "Events._writable") { } else if (k.endsWith("._writable") && k !== "Events._writable") {
loaded.push([k.split(".").slice(0, 2), 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([
Path.parse(k.split(".", 2).join(".")),
1 ^ ((1 << MAX_DEPTH) - 1)
]);
} }
if (k !== "") delete projection[k]; if (k !== "") delete projection[k];
} }
@@ -182,42 +204,58 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (k === "" || k === "DeviceID" || k.startsWith("DeviceID.")) { if (k === "" || k === "DeviceID" || k.startsWith("DeviceID.")) {
if (k === "" || k === "DeviceID" || k === "DeviceID._writable") { if (k === "" || k === "DeviceID" || k === "DeviceID._writable") {
res.push([ res.push([
["DeviceID"], Path.parse("DeviceID"),
timestamp, timestamp,
{ object: [timestamp, 1], writable: [timestamp, 0] } { object: [timestamp, 1], writable: [timestamp, 0] }
]); ]);
if (k === "DeviceID._writable") loaded.push([["DeviceID"], 1]); if (k === "DeviceID._writable")
loaded.push([Path.parse("DeviceID"), 1]);
} }
if (k === "DeviceID") { if (k === "DeviceID") {
projection["_id"] = 1; projection["_id"] = 1;
projection["_deviceId"] = 1; projection["_deviceId"] = 1;
loaded.push([["DeviceID"], (1 << MAX_DEPTH) - 1]); loaded.push([Path.parse("DeviceID"), (1 << MAX_DEPTH) - 1]);
} else if (k === "DeviceID.ID._writable" || k === "DeviceID.ID") { } else if (k === "DeviceID.ID._writable" || k === "DeviceID.ID") {
projection["_id"] = 1; projection["_id"] = 1;
loaded.push([["DeviceID", "ID"], 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([Path.parse("DeviceID.ID"), 1 ^ ((1 << MAX_DEPTH) - 1)]);
} else if ( } else if (
k === "DeviceID.Manufacturer._writable" || k === "DeviceID.Manufacturer._writable" ||
k === "DeviceID.DeManufacturer" k === "DeviceID.DeManufacturer"
) { ) {
projection["_deviceId._Manufacturer"] = 1; projection["_deviceId._Manufacturer"] = 1;
loaded.push([["DeviceID", "Manufacturer"], 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([
Path.parse("DeviceID.Manufacturer"),
1 ^ ((1 << MAX_DEPTH) - 1)
]);
} else if ( } else if (
k === "DeviceID.ProductClass._writable" || k === "DeviceID.ProductClass._writable" ||
k === "DeviceID.ProductClass" k === "DeviceID.ProductClass"
) { ) {
projection["_deviceId._ProductClass"] = 1; projection["_deviceId._ProductClass"] = 1;
loaded.push([["DeviceID", "ProductClass"], 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([
Path.parse("DeviceID.ProductClass"),
1 ^ ((1 << MAX_DEPTH) - 1)
]);
} else if (k === "DeviceID.OUI._writable" || k === "DeviceID.OUI") { } else if (k === "DeviceID.OUI._writable" || k === "DeviceID.OUI") {
projection["_deviceId._OUI"] = 1; projection["_deviceId._OUI"] = 1;
loaded.push([["DeviceID", "ProductClass"], 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([
Path.parse("DeviceID.ProductClass"),
1 ^ ((1 << MAX_DEPTH) - 1)
]);
} else if ( } else if (
k === "DeviceID.SerialNumber._writable" || k === "DeviceID.SerialNumber._writable" ||
k === "DeviceID.SerialNumber" k === "DeviceID.SerialNumber"
) { ) {
projection["_deviceId._SerialNumber"] = 1; projection["_deviceId._SerialNumber"] = 1;
loaded.push([["DeviceID", "SerialNumber"], 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([
Path.parse("DeviceID.SerialNumber"),
1 ^ ((1 << MAX_DEPTH) - 1)
]);
} else if (k.endsWith("._writable") && k !== "DeviceID._writable") { } else if (k.endsWith("._writable") && k !== "DeviceID._writable") {
loaded.push([k.split(".").slice(0, 2), 1 ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([
Path.parse(k.split(".", 2).join(".")),
1 ^ ((1 << MAX_DEPTH) - 1)
]);
} }
if (k !== "") delete projection[k]; if (k !== "") delete projection[k];
} }
@@ -225,7 +263,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (k === "Tags" || k.startsWith("Tags.")) { if (k === "Tags" || k.startsWith("Tags.")) {
if (!projection["_tags"]) { if (!projection["_tags"]) {
projection["_tags"] = 1; projection["_tags"] = 1;
loaded.push([["Tags"], (1 << MAX_DEPTH) - 1]); loaded.push([Path.parse("Tags"), (1 << MAX_DEPTH) - 1]);
} }
delete projection[k]; delete projection[k];
} }
@@ -240,34 +278,39 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
devicesCollection.findOne({ _id: id }, options, (err, device) => { devicesCollection.findOne({ _id: id }, options, (err, device) => {
if (err || !device) return void callback(err); if (err || !device) return void callback(err);
function storeParams(obj, path, ts, descendantsFetched, projTree): void { function storeParams(
obj,
path: string,
pathLength: number,
ts,
descendantsFetched,
projTree
): void {
let thisFetched = false; let thisFetched = false;
if (descendantsFetched) { if (descendantsFetched) {
thisFetched = true; thisFetched = true;
} else if (projection[path.join(".")]) { } else if (projection[path.slice(0, -1)]) {
descendantsFetched = true; descendantsFetched = true;
if ( if (
path.length && pathLength &&
projection[ projection[
path path.slice(0, path.lastIndexOf(".", path.length - 2) + 1) +
.slice(0, -1) "_timestamp"
.concat("_timestamp")
.join(".")
] ]
) { ) {
thisFetched = true; thisFetched = true;
loaded.push([ loaded.push([
path, Path.parse(path.slice(0, -1)),
((1 << (path.length - 1)) - 1) ^ ((1 << MAX_DEPTH) - 1) ((1 << (pathLength - 1)) - 1) ^ ((1 << MAX_DEPTH) - 1)
]); ]);
} else { } else {
loaded.push([ loaded.push([
path, Path.parse(path.slice(0, -1)),
((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1) ((1 << pathLength) - 1) ^ ((1 << MAX_DEPTH) - 1)
]); ]);
} }
} else if (projection[path.concat("_writable").join(".")]) { } else if (projection[path + "_writable"]) {
loaded.push([path, 1 << (path.length - 1)]); loaded.push([Path.parse(path.slice(0, -1)), 1 << (pathLength - 1)]);
thisFetched = true; thisFetched = true;
} }
@@ -277,11 +320,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (obj["_instance"] && obj["_object"] == null) obj["_object"] = true; if (obj["_instance"] && obj["_object"] == null) obj["_object"] = true;
if (thisFetched) { if (thisFetched) {
const attrs: { const attrs: Attributes = {};
object?: [number, number];
writable?: [number, number];
value?: [number, [string | number | boolean, string]];
} = {};
let t = obj["_timestamp"] || 1; let t = obj["_timestamp"] || 1;
if (ts > t) t = ts; if (ts > t) t = ts;
@@ -297,7 +336,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (obj["_object"] != null) attrs.object = [t, obj["_object"] ? 1 : 0]; if (obj["_object"] != null) attrs.object = [t, obj["_object"] ? 1 : 0];
res.push([path, t, attrs]); res.push([Path.parse(path.slice(0, -1)), t, attrs]);
} }
for (const [k, v] of Object.entries(obj)) { for (const [k, v] of Object.entries(obj)) {
@@ -305,7 +344,8 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
obj["_object"] = true; obj["_object"] = true;
storeParams( storeParams(
v, v,
path.concat(k), path + k + ".",
pathLength + 1,
obj["_timestamp"], obj["_timestamp"],
descendantsFetched, descendantsFetched,
projTree ? projTree[k] : null projTree ? projTree[k] : null
@@ -317,14 +357,14 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (!descendantsFetched) { if (!descendantsFetched) {
if (projTree) { if (projTree) {
for (const k of Object.keys(projTree)) { for (const k of Object.keys(projTree)) {
const p = path.concat(k); const p = Path.parse(path + k);
loaded.push([p, ((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1)]); loaded.push([p, ((1 << pathLength) - 1) ^ ((1 << MAX_DEPTH) - 1)]);
if ((obj["_object"] || !path.length) && obj["_timestamp"]) if ((obj["_object"] || !pathLength) && obj["_timestamp"])
res.push([p, obj["_timestamp"]]); res.push([p, obj["_timestamp"]]);
} }
} }
} else if ((obj["_object"] || !path.length) && obj["_timestamp"]) { } else if ((obj["_object"] || !pathLength) && obj["_timestamp"]) {
res.push([path.concat("*"), obj["_timestamp"]]); res.push([Path.parse(path + "*"), obj["_timestamp"]]);
} }
} }
@@ -332,7 +372,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
switch (k) { switch (k) {
case "_lastInform": case "_lastInform":
res.push([ res.push([
["Events", "Inform"], Path.parse("Events.Inform"),
+v, +v,
{ {
object: [+v, 0], object: [+v, 0],
@@ -344,7 +384,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
break; break;
case "_lastBoot": case "_lastBoot":
res.push([ res.push([
["Events", "1_BOOT"], Path.parse("Events.1_BOOT"),
+v, +v,
{ {
object: [+v, 0], object: [+v, 0],
@@ -356,7 +396,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
break; break;
case "_lastBootstrap": case "_lastBootstrap":
res.push([ res.push([
["Events", "0_BOOTSTRAP"], Path.parse("Events.0_BOOTSTRAP"),
+v, +v,
{ {
object: [+v, 0], object: [+v, 0],
@@ -369,7 +409,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
case "_registered": case "_registered":
// Use current timestamp for registered event attribute timestamps // Use current timestamp for registered event attribute timestamps
res.push([ res.push([
["Events", "Registered"], Path.parse("Events.Registered"),
timestamp, timestamp,
{ {
object: [timestamp, 0], object: [timestamp, 0],
@@ -382,12 +422,12 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
case "_id": case "_id":
if (projection[""] || projection["_id"]) { if (projection[""] || projection["_id"]) {
res.push([ res.push([
["DeviceID", "ID"], Path.parse("DeviceID.ID"),
timestamp, timestamp,
{ {
object: [timestamp, 0], object: [timestamp, 0],
writable: [timestamp, 0], writable: [timestamp, 0],
value: [timestamp, [v, "xsd:string"]] value: [timestamp, [v as string, "xsd:string"]]
} }
]); ]);
} }
@@ -397,7 +437,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
case "_tags": case "_tags":
if ((v as string[]).length) { if ((v as string[]).length) {
res.push([ res.push([
["Tags"], Path.parse("Tags"),
timestamp, timestamp,
{ object: [timestamp, 1], writable: [timestamp, 0] } { object: [timestamp, 1], writable: [timestamp, 0] }
]); ]);
@@ -406,7 +446,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
for (let t of v as string[]) { for (let t of v as string[]) {
t = t.replace(/[^a-zA-Z0-9-]+/g, "_"); t = t.replace(/[^a-zA-Z0-9-]+/g, "_");
res.push([ res.push([
["Tags", t], Path.parse("Tags." + t),
timestamp, timestamp,
{ {
object: [timestamp, 0], object: [timestamp, 0],
@@ -420,7 +460,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
case "_deviceId": case "_deviceId":
if (v["_Manufacturer"] != null) { if (v["_Manufacturer"] != null) {
res.push([ res.push([
["DeviceID", "Manufacturer"], Path.parse("DeviceID.Manufacturer"),
timestamp, timestamp,
{ {
object: [timestamp, 0], object: [timestamp, 0],
@@ -432,7 +472,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (v["_OUI"] != null) { if (v["_OUI"] != null) {
res.push([ res.push([
["DeviceID", "OUI"], Path.parse("DeviceID.OUI"),
timestamp, timestamp,
{ {
object: [timestamp, 0], object: [timestamp, 0],
@@ -444,7 +484,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (v["_ProductClass"] != null) { if (v["_ProductClass"] != null) {
res.push([ res.push([
["DeviceID", "ProductClass"], Path.parse("DeviceID.ProductClass"),
timestamp, timestamp,
{ {
object: [timestamp, 0], object: [timestamp, 0],
@@ -456,7 +496,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (v["_SerialNumber"] != null) { if (v["_SerialNumber"] != null) {
res.push([ res.push([
["DeviceID", "SerialNumber"], Path.parse("DeviceID.SerialNumber"),
timestamp, timestamp,
{ {
object: [timestamp, 0], object: [timestamp, 0],
@@ -470,16 +510,16 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
} }
} }
storeParams(device, [], 0, false, projectionTree); storeParams(device, "", 0, 0, false, projectionTree);
callback(null, res, loaded); callback(null, res, loaded);
}); });
} }
export function saveDevice( export function saveDevice(
deviceId, deviceId: string,
deviceData, deviceData: DeviceData,
isNew, isNew: boolean,
sessionTimestamp, sessionTimestamp: number,
callback callback
): void { ): void {
const update = { $set: {}, $unset: {}, $addToSet: {}, $pull: {} }; const update = { $set: {}, $unset: {}, $addToSet: {}, $pull: {} };
@@ -488,30 +528,25 @@ export function saveDevice(
if (diff[0].wildcard !== 1 << (diff[0].length - 1)) continue; if (diff[0].wildcard !== 1 << (diff[0].length - 1)) continue;
if ( if (
diff[0][0] === "Events" || diff[0].segments[0] === "Events" ||
diff[0][0] === "DeviceID" || diff[0].segments[0] === "DeviceID" ||
diff[0][0] === "Tags" diff[0].segments[0] === "Tags"
) )
continue; continue;
const parent = deviceData.paths.get(diff[0].slice(0, -1));
// Param timestamps may be greater than session timestamp to track revisions // Param timestamps may be greater than session timestamp to track revisions
if (diff[2] > sessionTimestamp) diff[2] = sessionTimestamp; if (diff[2] > sessionTimestamp) diff[2] = sessionTimestamp;
if (diff[2] == null && diff[1] != null) { if (diff[2] == null && diff[1] != null) {
update["$unset"][ update["$unset"][
diff[0] parent.length ? parent.toString() + "._timestamp" : "_timestamp"
.slice(0, -1)
.concat("_timestamp")
.join(".")
] = 1; ] = 1;
} else { } else {
const parent = deviceData.paths.get(diff[0].slice(0, -1));
if (parent && (!parent.length || deviceData.attributes.has(parent))) { if (parent && (!parent.length || deviceData.attributes.has(parent))) {
update["$set"][ update["$set"][
diff[0] parent.length ? parent.toString() + "._timestamp" : "_timestamp"
.slice(0, -1)
.concat("_timestamp")
.join(".")
] = new Date(diff[2]); ] = new Date(diff[2]);
} }
} }
@@ -530,11 +565,11 @@ export function saveDevice(
const writable2 = ((diff[2] || {}).writable || [])[1]; const writable2 = ((diff[2] || {}).writable || [])[1];
const writable1 = ((diff[1] || {}).writable || [])[1]; const writable1 = ((diff[1] || {}).writable || [])[1];
switch (path[0]) { switch (path.segments[0]) {
case "Events": case "Events":
if (diff[0].length === 2 && value2 !== value1) { if (path.length === 2 && value2 !== value1) {
if (!diff[2]) { if (!diff[2]) {
switch (path[1]) { switch (path.segments[1]) {
case "Inform": case "Inform":
update["$unset"]["_lastInform"] = 1; update["$unset"]["_lastInform"] = 1;
break; break;
@@ -548,8 +583,8 @@ export function saveDevice(
update["$unset"]["_registered"] = 1; update["$unset"]["_registered"] = 1;
} }
} else { } else {
const t = new Date(diff[2].value[1][0]); const t = new Date(diff[2].value[1][0] as number);
switch (path[1]) { switch (path.segments[1]) {
case "Inform": case "Inform":
update["$set"]["_lastInform"] = t; update["$set"]["_lastInform"] = t;
break; break;
@@ -569,7 +604,7 @@ export function saveDevice(
case "DeviceID": case "DeviceID":
if (value2 !== value1) { if (value2 !== value1) {
const v = diff[2].value[1][0]; const v = diff[2].value[1][0];
switch (path[1]) { switch (path.segments[1]) {
case "ID": case "ID":
update["$set"]["_id"] = v; update["$set"]["_id"] = v;
break; break;
@@ -592,21 +627,21 @@ export function saveDevice(
if (value2 != null) { if (value2 != null) {
if (!update["$addToSet"]["_tags"]) if (!update["$addToSet"]["_tags"])
update["$addToSet"]["_tags"] = { $each: [] }; update["$addToSet"]["_tags"] = { $each: [] };
update["$addToSet"]["_tags"]["$each"].push(diff[0][1]); update["$addToSet"]["_tags"]["$each"].push(path.segments[1]);
} else { } else {
if (!update["$pull"]["_tags"]) { if (!update["$pull"]["_tags"]) {
update["$pull"]["_tags"] = { update["$pull"]["_tags"] = {
$in: [] $in: []
}; };
} }
update["$pull"]["_tags"]["$in"].push(diff[0][1]); update["$pull"]["_tags"]["$in"].push(path.segments[1]);
} }
} }
break; break;
default: default:
if (!diff[2]) { if (!diff[2]) {
update["$unset"][diff[0].join(".")] = 1; update["$unset"][path.toString()] = 1;
continue; continue;
} }
@@ -621,35 +656,38 @@ export function saveDevice(
if (value2 !== value1) { if (value2 !== value1) {
if ( if (
valueType2 === "xsd:dateTime" && valueType2 === "xsd:dateTime" &&
Number.isInteger(value2) Number.isInteger(value2 as number)
) { ) {
update["$set"][path.concat("_value").join(".")] = new Date( update["$set"][path.toString() + "._value"] = new Date(
value2 value2 as number
); );
} else { } else {
update["$set"][path.concat("_value").join(".")] = value2; update["$set"][path.toString() + "._value"] = value2;
} }
} }
if (valueType2 !== valueType1) if (valueType2 !== valueType1)
update["$set"][path.concat("_type").join(".")] = valueType2; update["$set"][path.toString() + "._type"] = valueType2;
if (valueTimestamp2 !== valueTimestamp1) { if (valueTimestamp2 !== valueTimestamp1) {
update["$set"][ update["$set"][path.toString() + "._timestamp"] = new Date(
path.concat("_timestamp").join(".") valueTimestamp2
] = new Date(valueTimestamp2); );
} }
break; break;
case "object": case "object":
if (!diff[1] || !diff[1].object || object2 !== object1) if (!diff[1] || !diff[1].object || object2 !== object1) {
update["$set"][path.concat("_object").join(".")] = !!object2; update["$set"][
path.length ? path.toString() + "._object" : "_object"
] = !!object2;
}
break; break;
case "writable": case "writable":
if (!diff[1] || !diff[1].writable || writable2 !== writable1) { if (!diff[1] || !diff[1].writable || writable2 !== writable1) {
update["$set"][ update["$set"][
path.concat("_writable").join(".") path.length ? path.toString() + "._writable" : "_writable"
] = !!writable2; ] = !!writable2;
} }
} }
@@ -662,10 +700,11 @@ export function saveDevice(
diff[1][attrName][1] != null && diff[1][attrName][1] != null &&
(!diff[2] || !diff[2][attrName] || diff[2][attrName][1] == null) (!diff[2] || !diff[2][attrName] || diff[2][attrName][1] == null)
) { ) {
update["$unset"][path.concat(`_${attrName}`).join(".")] = 1; const p = path.length ? path.toString() + "." : "";
update["$unset"][`${p}_${attrName}`] = 1;
if (attrName === "value") { if (attrName === "value") {
update["$unset"][path.concat("_type").join(".")] = 1; update["$unset"][p + "_type"] = 1;
update["$unset"][path.concat("_timestamp").join(".")] = 1; update["$unset"][p + "_timestamp"] = 1;
} }
} }
} }
+17 -22
View File
@@ -17,18 +17,19 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import Path from "./common/path";
import * as config from "./config"; import * as config from "./config";
import * as common from "./common";
import * as device from "./device"; import * as device from "./device";
import * as scheduling from "./scheduling"; import * as scheduling from "./scheduling";
const MAX_DEPTH = +config.get("MAX_DEPTH"); const MAX_DEPTH = +config.get("MAX_DEPTH");
export function refresh(sessionContext, provision, declarations): boolean { export function refresh(sessionContext, provision, declarations): boolean {
const path = common.parsePath(provision[1]).slice(); const segments = Path.parse(provision[1]).segments.slice();
const l = path.length; const l = segments.length;
path.length = MAX_DEPTH; segments.length = MAX_DEPTH;
path.fill("*", l); segments.fill("*", l);
const path = Path.parse(segments.join("."));
const every = 1000 * (provision[2] || 1); const every = 1000 * (provision[2] || 1);
const offset = scheduling.variance(sessionContext.deviceId, every); const offset = scheduling.variance(sessionContext.deviceId, every);
const t = scheduling.interval(sessionContext.timestamp, every, offset); const t = scheduling.interval(sessionContext.timestamp, every, offset);
@@ -48,9 +49,8 @@ export function refresh(sessionContext, provision, declarations): boolean {
} }
export function value(sessionContext, provision, declarations): boolean { export function value(sessionContext, provision, declarations): boolean {
const path = common.parsePath(provision[1]);
declarations.push({ declarations.push({
path: path, path: Path.parse(provision[1]),
pathGet: 1, pathGet: 1,
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
@@ -62,10 +62,8 @@ export function value(sessionContext, provision, declarations): boolean {
} }
export function tag(sessionContext, provision, declarations): boolean { export function tag(sessionContext, provision, declarations): boolean {
const path = ["Tags", provision[1]];
declarations.push({ declarations.push({
path: path, path: Path.parse(`Tags.${provision[1]}`),
pathGet: 1, pathGet: 1,
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
@@ -78,7 +76,7 @@ export function tag(sessionContext, provision, declarations): boolean {
export function reboot(sessionContext, provision, declarations): boolean { export function reboot(sessionContext, provision, declarations): boolean {
declarations.push({ declarations.push({
path: ["Reboot"], path: Path.parse("Reboot"),
pathGet: 1, pathGet: 1,
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
@@ -91,7 +89,7 @@ export function reboot(sessionContext, provision, declarations): boolean {
export function reset(sessionContext, provision, declarations): boolean { export function reset(sessionContext, provision, declarations): boolean {
declarations.push({ declarations.push({
path: ["FactoryReset"], path: Path.parse("FactoryReset"),
pathGet: 1, pathGet: 1,
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
@@ -104,16 +102,13 @@ export function reset(sessionContext, provision, declarations): boolean {
export function download(sessionContext, provision, declarations): boolean { export function download(sessionContext, provision, declarations): boolean {
const alias = [ const alias = [
["FileType"], `FileType:${JSON.stringify(provision[1] || "")}`,
provision[1] || "", `FileName:${JSON.stringify(provision[2] || "")}`,
["FileName"], `TargetFileName:${JSON.stringify(provision[3] || "")}`
provision[2] || "", ].join(",");
["TargetFileName"],
provision[3] || ""
];
declarations.push({ declarations.push({
path: ["Downloads", alias], path: Path.parse(`Downloads.[${alias}]`),
pathGet: 1, pathGet: 1,
pathSet: 1, pathSet: 1,
attrGet: null, attrGet: null,
@@ -122,7 +117,7 @@ export function download(sessionContext, provision, declarations): boolean {
}); });
declarations.push({ declarations.push({
path: ["Downloads", alias, "Download"], path: Path.parse(`Downloads.[${alias}].Download`),
pathGet: 1, pathGet: 1,
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { value: 1 },
@@ -144,7 +139,7 @@ export function instances(
if (Number.isNaN(count)) return true; if (Number.isNaN(count)) return true;
const path = common.parsePath(provision[1]); const path = Path.parse(provision[1]);
if (provision[2][0] === "+" || provision[2][0] === "-") { if (provision[2][0] === "+" || provision[2][0] === "-") {
declarations.push({ declarations.push({
+50 -61
View File
@@ -17,7 +17,8 @@
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>. * along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ParamPath, DeviceData, Declaration, Clear } from "./types"; import Path from "./common/path";
import { DeviceData, Declaration, Clear } from "./types";
const CHANGE_FLAGS = { const CHANGE_FLAGS = {
object: 2, object: 2,
@@ -34,7 +35,7 @@ function parseBool(v): boolean {
} }
export function sanitizeParameterValue( export function sanitizeParameterValue(
parameterValue parameterValue: [string | number | boolean, string]
): [string | number | boolean, string?] { ): [string | number | boolean, string?] {
if (parameterValue[0] != null) { if (parameterValue[0] != null) {
switch (parameterValue[1]) { switch (parameterValue[1]) {
@@ -55,7 +56,7 @@ export function sanitizeParameterValue(
case "xsd:int": case "xsd:int":
case "xsd:unsignedInt": case "xsd:unsignedInt":
if (typeof parameterValue[0] !== "number") { if (typeof parameterValue[0] !== "number") {
const i = parseInt(parameterValue[0]); const i = parseInt(parameterValue[0] as string);
if (isNaN(i)) parameterValue[0] = "" + parameterValue[0]; if (isNaN(i)) parameterValue[0] = "" + parameterValue[0];
else parameterValue[0] = i; else parameterValue[0] = i;
} }
@@ -65,7 +66,7 @@ export function sanitizeParameterValue(
// Don't use parseInt because it reads date string as a number // Don't use parseInt because it reads date string as a number
let i = +parameterValue[0]; let i = +parameterValue[0];
if (isNaN(i)) { if (isNaN(i)) {
i = Date.parse(parameterValue[0]); i = Date.parse(parameterValue[0] as string);
if (isNaN(i)) parameterValue[0] = "" + parameterValue[0]; if (isNaN(i)) parameterValue[0] = "" + parameterValue[0];
else parameterValue[0] = i; else parameterValue[0] = i;
} else { } else {
@@ -88,87 +89,75 @@ export function sanitizeParameterValue(
return parameterValue; return parameterValue;
} }
export function getAliasDeclarations(path, timestamp): Declaration[] { export function getAliasDeclarations(
const decs = [] as Declaration[]; path: Path,
timestamp: number,
function recursive(p, prefix): void { attrGet = null
const pattern = prefix.concat(p); ): Declaration[] {
const stripped = path.stripAlias();
for (let i = prefix.length; i < pattern.length; ++i) { let decs: Declaration[] = [
if (Array.isArray(pattern[i])) { {
const pat = pattern[i]; path: stripped,
pattern[i] = "*";
for (let j = 0; j < pat.length; j += 2)
recursive(pat[j], pattern.slice(0, i + 1));
}
}
decs.push({
path: pattern,
pathGet: timestamp, pathGet: timestamp,
pathSet: null, pathSet: null,
attrGet: prefix.length ? { value: timestamp } : null, attrGet: attrGet,
attrSet: null, attrSet: null,
defer: true defer: true
}); }
} ];
recursive(path, []); if (path.alias) {
for (const [i, alias] of path.segments.entries()) {
if (Array.isArray(alias)) {
const parent = stripped.slice(0, i + 1);
for (const [p] of alias as [Path, string][]) {
decs = decs.concat(
getAliasDeclarations(parent.concat(p), timestamp, {
value: timestamp
})
);
}
}
}
}
return decs; return decs;
} }
export function unpack( export function unpack(
deviceData, deviceData: DeviceData,
path: ParamPath, path: Path,
revision?: number revision?: number
): ParamPath[] { ): Path[] {
let allMatches = [] as ParamPath[]; let allMatches = [] as Path[];
if (!path.alias) { if (!path.alias) {
for (const p of deviceData.paths.find(path, false, true)) for (const p of deviceData.paths.find(path, false, true))
if (deviceData.attributes.has(p, revision)) allMatches.push(p); if (deviceData.attributes.has(p, revision)) allMatches.push(p);
} else { } else {
const wildcardPath = path.slice() as ParamPath; const wildcardPath = path.stripAlias();
wildcardPath.wildcard = path.wildcard;
for (let i = 0; i < wildcardPath.length; ++i) {
if (Array.isArray(wildcardPath[i])) {
wildcardPath[i] = "*";
wildcardPath.wildcard |= 1 << i;
}
}
for (const p of deviceData.paths.find(wildcardPath, false, true)) for (const 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])) { if (path.alias & (i << i)) {
for (let j = 0; j < path[i].length; j += 2) { for (const [param, val] of path.segments[i] as [Path, string][]) {
const p = wildcardPath const p = wildcardPath.slice(0, i + 1).concat(param);
.slice(0, i + 1)
.concat(path[i][j]) as ParamPath;
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);
const unpacked = unpack(deviceData, p, revision); const unpacked = unpack(deviceData, p, revision);
const filtered = [] as ParamPath[]; const filtered: Path[] = [];
for (const up of unpacked) { for (const up of unpacked) {
const attributes = deviceData.attributes.get(up, revision); const attributes = deviceData.attributes.get(up, revision);
if ( if (
attributes && attributes &&
attributes.value && attributes.value &&
attributes.value[1] && attributes.value[1] &&
sanitizeParameterValue([ sanitizeParameterValue([val, attributes.value[1][1]])[0] ===
path[i][j + 1], attributes.value[1][0]
attributes.value[1][1]
])[0] === attributes.value[1][0]
) { ) {
for (let m = 0; m < allMatches.length; ++m) { for (let m = 0; m < allMatches.length; ++m) {
let k; let k;
for (k = i; k >= 0; --k) if (allMatches[m][k] !== up[k]) break; for (k = i; k >= 0; --k)
if (allMatches[m].segments[k] !== up.segments[k]) break;
if (k < 0) { if (k < 0) {
filtered.push(allMatches[m]); filtered.push(allMatches[m]);
@@ -185,8 +174,8 @@ export function unpack(
allMatches.sort((p1, p2) => { allMatches.sort((p1, p2) => {
for (let i = 0; i < p1.length; ++i) { for (let i = 0; i < p1.length; ++i) {
const a = p1[i]; const a = p1.segments[i] as string;
const b = p2[i]; const b = p2.segments[i] as string;
if (a !== b) { if (a !== b) {
// Use numeric sorting for numbers // Use numeric sorting for numbers
const ia = parseInt(a); const ia = parseInt(a);
@@ -285,7 +274,7 @@ function compareEquality(a, b): boolean {
export function set( export function set(
deviceData: DeviceData, deviceData: DeviceData,
path: ParamPath, path: Path,
timestamp, timestamp,
attributes, attributes,
toClear?: Clear[] toClear?: Clear[]
@@ -345,7 +334,7 @@ export function set(
if (path.length > 1) { if (path.length > 1) {
toClear = set( toClear = set(
deviceData, deviceData,
path.slice(0, path.length - 1) as ParamPath, path.slice(0, path.length - 1),
timestamp, timestamp,
{ object: [timestamp, 1] }, { object: [timestamp, 1] },
toClear toClear
@@ -389,7 +378,7 @@ export function set(
export function track( export function track(
deviceData: DeviceData, deviceData: DeviceData,
path: ParamPath, path: Path,
marker: string, marker: string,
attributes? attributes?
): void { ): void {
@@ -408,7 +397,7 @@ export function track(
cur[marker] |= f; cur[marker] |= f;
} }
export function clearTrackers(deviceData, tracker): void { export function clearTrackers(deviceData: DeviceData, tracker): void {
if (Array.isArray(tracker)) { if (Array.isArray(tracker)) {
for (const v of deviceData.trackers.values()) for (const v of deviceData.trackers.values())
for (const t of tracker) delete v[t]; for (const t of tracker) delete v[t];
+28 -24
View File
@@ -19,12 +19,12 @@
import * as vm from "vm"; import * as vm from "vm";
import seedrandom from "seedrandom"; import seedrandom from "seedrandom";
import * as common from "./common";
import * as device from "./device"; import * as device from "./device";
import * as extensions from "./extensions"; import * as extensions from "./extensions";
import * as logger from "./logger"; import * as logger from "./logger";
import * as scheduling from "./scheduling"; import * as scheduling from "./scheduling";
import { ParamPath, Fault } from "./types"; import Path from "./common/path";
import { Fault, SessionContext } from "./types";
// Used for throwing to exit user script and commit // Used for throwing to exit user script and commit
const COMMIT = Symbol(); const COMMIT = Symbol();
@@ -112,7 +112,7 @@ random.seed = function(s) {
}; };
class ParameterWrapper { class ParameterWrapper {
public constructor(path, attributes, unpacked?, unpackedRevision?) { public constructor(path: Path, attributes, unpacked?, unpackedRevision?) {
for (const attrName of attributes) { for (const attrName of attributes) {
Object.defineProperty(this, attrName, { Object.defineProperty(this, attrName, {
get: function() { get: function() {
@@ -156,7 +156,7 @@ class ParameterWrapper {
if (!unpacked.length) return UNDEFINED; if (!unpacked.length) return UNDEFINED;
return unpacked[0].join("."); return unpacked[0].toString();
} }
}); });
@@ -198,7 +198,7 @@ class ParameterWrapper {
} }
function declare( function declare(
path: ParamPath, path: string,
timestamps: { [attr: string]: number }, timestamps: { [attr: string]: number },
values: { [attr: string]: any } values: { [attr: string]: any }
): ParameterWrapper { ): ParameterWrapper {
@@ -207,7 +207,7 @@ function declare(
if (!values) values = {}; if (!values) values = {};
const parsedPath = common.parsePath(path); const parsedPath = Path.parse(path);
const declaration = { const declaration = {
path: parsedPath, path: parsedPath,
@@ -249,11 +249,11 @@ function declare(
return new ParameterWrapper(parsedPath, attrs); return new ParameterWrapper(parsedPath, attrs);
} }
function clear(path, timestamp, attributes): void { function clear(path: string, timestamp: number, attributes?): void {
state.uncommitted = true; state.uncommitted = true;
if (state.revision === state.maxRevision) if (state.revision === state.maxRevision)
state.clear.push([common.parsePath(path), timestamp, attributes]); state.clear.push([Path.parse(path), timestamp, attributes]);
} }
function commit(): void { function commit(): void {
@@ -282,19 +282,19 @@ function ext(): any {
throw EXT; throw EXT;
} }
function log(msg, meta): void { function log(msg: string, meta: {}): void {
if (state.revision === state.maxRevision && state.extCounter >= 0) { if (state.revision === state.maxRevision && state.extCounter >= 0) {
const details = Object.assign({}, meta, { const details = Object.assign({}, meta, {
sessionContext: state.sessionContext, sessionContext: state.sessionContext,
message: `Script: ${msg}` message: `Script: ${msg}`
}); });
delete details.hostname; delete details["hostname"];
delete details.pid; delete details["pid"];
delete details.name; delete details["name"];
delete details.version; delete details["version"];
delete details.deviceId; delete details["deviceId"];
delete details.remoteAddress; delete details["remoteAddress"];
logger.accessInfo(details); logger.accessInfo(details);
} }
@@ -312,7 +312,7 @@ context.random = random;
vm.runInContext("Math.random = random;", context); vm.runInContext("Math.random = random;", context);
delete context.random; delete context.random;
function errorToFault(err): Fault { function errorToFault(err: Error): Fault {
if (!err) return null; if (!err) return null;
if (!err.name) return { code: "script", message: `${err}` }; if (!err.name) return { code: "script", message: `${err}` };
@@ -327,24 +327,28 @@ function errorToFault(err): Fault {
}; };
if (err.stack) { if (err.stack) {
fault.detail.stack = err.stack; fault.detail["stack"] = err.stack;
// Trim the stack trace at the self-executing anonymous wrapper function // Trim the stack trace at the self-executing anonymous wrapper function
const stackTrimIndex = fault.detail.stack.match( const stackTrimIndex = fault.detail["stack"].match(
/\s+at\s[^\s]+\s+at\s[^\s]+\s\(vm\.js.+\)/ /\s+at\s[^\s]+\s+at\s[^\s]+\s\(vm\.js.+\)/
); );
if (stackTrimIndex) if (stackTrimIndex) {
fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index); fault.detail["stack"] = fault.detail["stack"].slice(
0,
stackTrimIndex.index
);
}
} }
return fault; return fault;
} }
export function run( export function run(
script, script: vm.Script,
globals, globals,
sessionContext, sessionContext: SessionContext,
startRevision, startRevision: number,
maxRevision, maxRevision: number,
callback, callback,
extCounter = 0 extCounter = 0
): void { ): void {
+283 -270
View File
File diff suppressed because it is too large. Load diff
+42 -36
View File
@@ -1,27 +1,24 @@
import PathSet from "./path-set"; import Path from "./common/path";
import PathSet from "./common/path-set";
import VersionedMap from "./versioned-map"; import VersionedMap from "./versioned-map";
import InstanceSet from "./instance-set"; import InstanceSet from "./instance-set";
import { IncomingMessage, ServerResponse } from "http"; import { IncomingMessage, ServerResponse } from "http";
export interface ParamPath extends Array<any> {
wildcard: number;
alias: number;
}
export type Expression = string | number | boolean | null | any[]; export type Expression = string | number | boolean | null | any[];
export interface Fault { export interface Fault {
code: string; code: string;
message: string; message: string;
detail?: { detail?:
name: string; | FaultStruct
message: string; | {
stack?: string; name: string;
}; message: string;
stack?: string;
};
} }
export interface SessionFault extends Fault { export interface SessionFault extends Fault {
channel: string;
timestamp: number; timestamp: number;
provisions: string[][]; provisions: string[][];
retryNow?: boolean; retryNow?: boolean;
@@ -50,15 +47,15 @@ export interface AttributeValues {
export interface DeviceData { export interface DeviceData {
paths: PathSet; paths: PathSet;
timestamps: VersionedMap<ParamPath, number>; timestamps: VersionedMap<Path, number>;
attributes: VersionedMap<ParamPath, Attributes>; attributes: VersionedMap<Path, Attributes>;
loaded: Map<ParamPath, number>; loaded: Map<Path, number>;
trackers: Map<ParamPath, { [name: string]: number }>; trackers: Map<Path, { [name: string]: number }>;
changes: Set<string>; changes: Set<string>;
} }
export type VirtualParameterDeclaration = [ export type VirtualParameterDeclaration = [
ParamPath, Path,
{ path?: number; object?: number; writable?: number; value?: number }?, { path?: number; object?: number; writable?: number; value?: number }?,
{ {
path?: [number, number]; path?: [number, number];
@@ -70,22 +67,24 @@ export type VirtualParameterDeclaration = [
export interface SyncState { export interface SyncState {
refreshAttributes: { refreshAttributes: {
exist: Set<ParamPath>; exist: Set<Path>;
object: Set<ParamPath>; object: Set<Path>;
writable: Set<ParamPath>; writable: Set<Path>;
value: Set<ParamPath>; value: Set<Path>;
}; };
spv: Map<ParamPath, [string | number | boolean, string]>; spv: Map<Path, [string | number | boolean, string]>;
gpn: Set<ParamPath>; gpn: Set<Path>;
gpnPatterns: Map<ParamPath, number>; gpnPatterns: Map<Path, number>;
tags: Map<ParamPath, [string | number | boolean, string]>; tags: Map<Path, boolean>;
virtualParameterDeclarations: VirtualParameterDeclaration[][]; virtualParameterDeclarations: VirtualParameterDeclaration[][];
instancesToDelete: Map<ParamPath, Set<ParamPath>>; instancesToDelete: Map<Path, Set<Path>>;
instancesToCreate: Map<ParamPath, InstanceSet>; instancesToCreate: Map<Path, InstanceSet>;
downloadsToDelete: Set<ParamPath>; downloadsToDelete: Set<Path>;
downloadsToCreate: InstanceSet; downloadsToCreate: InstanceSet;
downloadsValues: Map<ParamPath, string | number>; downloadsValues: Map<Path, string | number>;
downloadsDownload: Map<ParamPath, number>; downloadsDownload: Map<Path, number>;
reboot: number;
factoryReset: number;
} }
export interface SessionContext { export interface SessionContext {
@@ -97,13 +96,19 @@ export interface SessionContext {
timeout: number; timeout: number;
provisions: any[]; provisions: any[];
channels: { [channel: string]: number }; channels: { [channel: string]: number };
virtualParameters: any[]; virtualParameters: [
string,
AttributeTimestamps,
AttributeValues,
AttributeTimestamps,
AttributeValues
][][];
revisions: number[]; revisions: number[];
rpcCount: number; rpcCount: number;
iteration: number; iteration: number;
cycle: number; cycle: number;
extensionsCache: any; extensionsCache: any;
declarations: any[]; declarations: Declaration[][];
faults?: { [channel: string]: SessionFault }; faults?: { [channel: string]: SessionFault };
retries?: { [channel: string]: number }; retries?: { [channel: string]: number };
cacheSnapshot?: string; cacheSnapshot?: string;
@@ -112,7 +117,7 @@ export interface SessionContext {
httpRequest?: IncomingMessage; httpRequest?: IncomingMessage;
faultsTouched?: { [channel: string]: boolean }; faultsTouched?: { [channel: string]: boolean };
presetCycles?: number; presetCycles?: number;
toLoad?: Map<ParamPath, number>; toLoad?: Map<Path, number>;
new?: boolean; new?: boolean;
debug?: boolean; debug?: boolean;
tasks?: Task[]; tasks?: Task[];
@@ -154,6 +159,7 @@ export interface Operation {
export interface AcsRequest { export interface AcsRequest {
name: string; name: string;
next?: string;
} }
export interface GetAcsRequest extends AcsRequest { export interface GetAcsRequest extends AcsRequest {
@@ -162,6 +168,7 @@ export interface GetAcsRequest extends AcsRequest {
parameterNames?: string[]; parameterNames?: string[];
parameterPath?: string; parameterPath?: string;
nextLevel?: boolean; nextLevel?: boolean;
instanceValues?: { [name: string]: string };
} }
export interface SetAcsRequest extends AcsRequest { export interface SetAcsRequest extends AcsRequest {
@@ -175,7 +182,6 @@ export interface SetAcsRequest extends AcsRequest {
parameterList?: [string, string | number | boolean, string][]; parameterList?: [string, string | number | boolean, string][];
instanceValues?: { [param: string]: string | number | boolean }; instanceValues?: { [param: string]: string | number | boolean };
objectName?: string; objectName?: string;
next?: string;
DATETIME_MILLISECONDS?: boolean; DATETIME_MILLISECONDS?: boolean;
BOOLEAN_LITERAL?: boolean; BOOLEAN_LITERAL?: boolean;
commandKey?: string; commandKey?: string;
@@ -266,7 +272,7 @@ export interface QueryOptions {
} }
export interface Declaration { export interface Declaration {
path: ParamPath; path: Path;
pathGet: number; pathGet: number;
pathSet?: [number, number]; pathSet?: [number, number];
attrGet?: { object?: number; writable?: number; value?: number }; attrGet?: { object?: number; writable?: number; value?: number };
@@ -279,7 +285,7 @@ export interface Declaration {
} }
export type Clear = [ export type Clear = [
ParamPath, Path,
number, number,
{ object?: number; writable?: number; value?: number }?, { object?: number; writable?: number; value?: number }?,
number? number?
-68
View File
@@ -1,6 +1,5 @@
import ava from "ava"; import ava from "ava";
import * as common from "../lib/common"; import * as common from "../lib/common";
import { ParamPath } from "../lib/types";
ava("typeOf", t => { ava("typeOf", t => {
const cases = [ const cases = [
@@ -63,73 +62,6 @@ ava("generateDeviceId", t => {
for (const c of cases) t.is(common.generateDeviceId(c[0]), c[1]); for (const c of cases) t.is(common.generateDeviceId(c[0]), c[1]);
}); });
ava("parsePath", t => {
const case1 = ["Seg1.Seg2.Seg3", ["Seg1", "Seg2", "Seg3"]] as ParamPath;
case1[1].alias = 0;
case1[1].wildcard = 0;
const case2 = ["Seg1.*.*.Seg4", ["Seg1", "*", "*", "Seg4"]] as ParamPath;
case2[1].alias = 0;
case2[1].wildcard = 6;
const case3 = [
"Seg1.Seg2.[Seg6:Seg9].Seg3",
["Seg1", "Seg2", [["Seg6"], "Seg9"], "Seg3"]
] as ParamPath;
case3[1].alias = 4;
case3[1].wildcard = 0;
case3[1][2][0].alias = 0;
case3[1][2][0].wildcard = 0;
const case4 = ["Seg1.[*:SegAny]", ["Seg1", [["*"], "SegAny"]]] as ParamPath;
case4[1].alias = 2;
case4[1].wildcard = 0;
case4[1][1][0].alias = 0;
case4[1][1][0].wildcard = 1;
const cases = [case1, case2, case3, case4];
for (const c of cases) t.deepEqual(common.parsePath(c[0]), c[1]);
});
ava("addPathMeta", t => {
const case1 = [
["Seg1", "Seg2", "Seg3"],
["Seg1", "Seg2", "Seg3"]
] as ParamPath;
case1[1].alias = 0;
case1[1].wildcard = 0;
const case2 = [
["Seg1", "*", "*", "Seg4"],
["Seg1", "*", "*", "Seg4"]
] as ParamPath;
case2[1].alias = 0;
case2[1].wildcard = 6;
const case3 = [
["Seg1", "Seg2", [["Seg6"], "Seg9"], "Seg3"],
["Seg1", "Seg2", [["Seg6"], "Seg9"], "Seg3"]
] as ParamPath;
case3[1].alias = 4;
case3[1].wildcard = 0;
case3[1][2][0].alias = 0;
case3[1][2][0].wildcard = 0;
const case4 = [
["Seg1", [["*"], "SegAny"]],
["Seg1", [["*"], "SegAny"]]
] as ParamPath;
case4[1].alias = 2;
case4[1].wildcard = 0;
case4[1][1][0].alias = 0;
case4[1][1][0].wildcard = 1;
const cases = [case1, case2, case3, case4];
t.plan(cases.length);
for (const c of cases) t.deepEqual(common.addPathMeta(c[0]), c[1]);
});
ava("escapeRegExp", t => { ava("escapeRegExp", t => {
t.is( t.is(
common.escapeRegExp("\\ ^ $ * + ? . ( ) | { } [ ]"), common.escapeRegExp("\\ ^ $ * + ? . ( ) | { } [ ]"),
+20
View File
@@ -0,0 +1,20 @@
import ava from "ava";
import Path from "../lib/common/path";
import * as device from "../lib/device";
ava("getAliasDeclarations", t => {
const path = Path.parse("a.[aa:10,bb.[aaa:100].cc:1].b");
const decs = device.getAliasDeclarations(path, 99);
const expected = ["a.*.b", "a.*.aa", "a.*.bb.*.cc", "a.*.bb.*.aaa"];
t.plan(24);
for (const [i, d] of decs.entries()) {
t.is(d.path.toString(), expected[i]);
t.is(d.pathGet, 99);
t.is(d.pathSet, null);
t.deepEqual(d.attrGet, i ? { value: 99 } : null);
t.is(d.attrSet, null);
t.is(d.defer, true);
}
});