diff --git a/bin/genieacs-ext b/bin/genieacs-ext
index 92405e8..a634fcc 100755
--- a/bin/genieacs-ext
+++ b/bin/genieacs-ext
@@ -40,13 +40,17 @@ function errorToFault(err: Error): Fault {
};
if (err.stack) {
- fault.detail.stack = err.stack;
+ fault.detail["stack"] = err.stack;
// Trim the stack trace
- const stackTrimIndex = fault.detail.stack.match(
+ const stackTrimIndex = fault.detail["stack"].match(
/\s+at\s[^\s]+\s\(.*genieacs-ext:.+\)/
);
- if (stackTrimIndex)
- fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index);
+ if (stackTrimIndex) {
+ fault.detail["stack"] = fault.detail["stack"].slice(
+ 0,
+ stackTrimIndex.index
+ );
+ }
}
return fault;
diff --git a/lib/common.ts b/lib/common.ts
index 23e9865..2752ae5 100644
--- a/lib/common.ts
+++ b/lib/common.ts
@@ -17,8 +17,6 @@
* along with GenieACS. If not, see .
*/
-import { ParamPath } from "./types";
-
export const UNDEFINED_TYPE = "[object Undefined]";
export const NULL_TYPE = "[object Null]";
export const BOOLEAN_TYPE = "[object Boolean]";
@@ -55,138 +53,6 @@ export function generateDeviceId(deviceIdStruct): string {
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
export function escapeRegExp(str): string {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
diff --git a/lib/path-set.ts b/lib/common/path-set.ts
similarity index 73%
rename from lib/path-set.ts
rename to lib/common/path-set.ts
index 6df6b72..4edad4c 100644
--- a/lib/path-set.ts
+++ b/lib/common/path-set.ts
@@ -17,11 +17,11 @@
* along with GenieACS. If not, see .
*/
-import { ParamPath } from "./types";
+import Path from "./path";
export default class PathSet {
- private lengthIndex: Set[];
- private fragmentIndex: Map>[];
+ private lengthIndex: Set[];
+ private fragmentIndex: Map>[];
public constructor() {
this.lengthIndex = [];
@@ -32,7 +32,8 @@ export default class PathSet {
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);
if (p) return p;
@@ -45,43 +46,36 @@ export default class PathSet {
const lengthIndex = this.lengthIndex[path.length];
lengthIndex.add(path);
- let wildcard = 0;
for (let i = 0; i < path.length; ++i) {
- const fragment = path[i];
- if (fragment === "*") wildcard = wildcard | (1 << i);
-
+ const fragment = path.segments[i] as string;
const fragmentIndex = this.fragmentIndex[i];
let fragmentIndexSet = fragmentIndex.get(fragment);
if (!fragmentIndexSet) {
- fragmentIndexSet = new Set();
+ fragmentIndexSet = new Set();
fragmentIndex.set(fragment, fragmentIndexSet);
}
fragmentIndexSet.add(path);
}
- if (path.wildcard == null) {
- path.alias = 0;
- path.wildcard = wildcard;
- Object.freeze(path);
- }
return path;
}
- public get(pattern: ParamPath): ParamPath {
- const lengthIndex = this.lengthIndex[pattern.length];
+ public get(path: Path): Path {
+ 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.has(pattern)) return pattern;
+ if (lengthIndex.has(path)) return path;
const sets = [lengthIndex];
- for (let i = 0; i < pattern.length; ++i) {
+ for (let i = 0; i < path.length; ++i) {
const fragmentIndex = this.fragmentIndex[i];
if (!fragmentIndex || !fragmentIndex.size) return null;
- const fragment = pattern[i];
+ const fragment = path.segments[i] as string;
const fragmentIndexSet = fragmentIndex.get(fragment);
if (!fragmentIndexSet) return null;
@@ -92,39 +86,40 @@ export default class PathSet {
sets.sort((a, b) => a.size - b.size);
const smallestSet = sets.shift();
- for (let path of smallestSet) {
+ for (let p of smallestSet) {
for (const s of sets) {
- if (!s.has(path)) {
- path = null;
+ if (!s.has(p)) {
+ p = null;
break;
}
}
- if (path) return path;
+ if (p) return p;
}
return null;
}
public find(
- pattern: ParamPath,
+ path: Path,
superset: boolean = false,
subset: boolean = false,
- depth: number = pattern.length
- ): ParamPath[] {
+ depth: number = path.length
+ ): Path[] {
+ if (path.alias) throw new Error("PathSet does not support aliased paths");
const res = [];
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 (!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];
if (!fragmentIndex) return res;
- const fragment = pattern[i];
+ const fragment = path.segments[i] as string;
if (fragment === "*" && subset) continue;
@@ -149,22 +144,22 @@ export default class PathSet {
const smallestGroup = groups.shift();
for (const set of smallestGroup) {
- for (let path of set) {
+ for (let p of set) {
for (const group of groups) {
let found = false;
for (const s of group) {
- if (s.has(path)) {
+ if (s.has(p)) {
found = true;
break;
}
}
if (!found) {
- path = null;
+ p = null;
break;
}
}
- if (path) res.push(path);
+ if (p) res.push(p);
}
}
return res;
diff --git a/lib/common/path.ts b/lib/common/path.ts
index dbf2574..60fd16f 100644
--- a/lib/common/path.ts
+++ b/lib/common/path.ts
@@ -172,6 +172,12 @@ export default class Path {
const alias = this.alias | (path.alias << this.segments.length);
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(() => {
diff --git a/lib/cwmp.ts b/lib/cwmp.ts
index 06d6aa2..e3d4ba2 100644
--- a/lib/cwmp.ts
+++ b/lib/cwmp.ts
@@ -32,13 +32,14 @@ import * as localCache from "./local-cache";
import * as db from "./db";
import * as logger from "./logger";
import * as scheduling from "./scheduling";
+import Path from "./common/path";
import {
SessionContext,
- ParamPath,
AcsRequest,
SetAcsRequest,
SessionFault,
- Operation
+ Operation,
+ Fault
} from "./types";
const MAX_CYCLES = 4;
@@ -144,9 +145,19 @@ function writeResponse(
}
}
-function recordFault(sessionContext, fault, provisions, channels): void;
-function recordFault(sessionContext, fault): void;
-function recordFault(sessionContext, fault, provisions?, channels?): void {
+function recordFault(
+ sessionContext: SessionContext,
+ fault: Fault,
+ provisions,
+ channels
+): void;
+function recordFault(sessionContext: SessionContext, fault: Fault): void;
+function recordFault(
+ sessionContext: SessionContext,
+ fault: Fault,
+ provisions?,
+ channels?
+): void {
if (!provisions) {
provisions = sessionContext.provisions;
channels = sessionContext.channels;
@@ -157,7 +168,10 @@ function recordFault(sessionContext, fault, provisions?, channels?): void {
const provs = sessionContext.faults[channel]
? 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_")) {
const taskId = channel.slice(5);
for (const t of sessionContext.tasks)
@@ -334,17 +348,13 @@ function applyPresets(sessionContext: SessionContext): void {
deviceData.attributes.revision = 1;
const deviceEvents = {};
- for (const p of deviceData.paths.find(
- ["Events", "*"] as ParamPath,
- false,
- true
- )) {
+ for (const p of deviceData.paths.find(Path.parse("Events.*"), false, true)) {
const attrs = deviceData.attributes.get(p);
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 = [];
for (const preset of presets) {
@@ -377,7 +387,7 @@ function applyPresets(sessionContext: SessionContext): void {
for (const k of Object.keys(preset.precondition)) {
sessionContext.channels[preset.channel] = 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 = {};
for (const [k, v] of Object.entries(parameters)) {
const unpacked = device.unpack(deviceData, v);
- if (!unpacked[0]) continue;
+ if (!unpacked.length) continue;
const attrs = deviceData.attributes.get(unpacked[0]);
if (attrs && attrs.value && attrs.value[1])
parameterValues[k] = attrs.value[1][0];
diff --git a/lib/db.ts b/lib/db.ts
index bc6f4b8..7d87d6a 100644
--- a/lib/db.ts
+++ b/lib/db.ts
@@ -21,6 +21,8 @@ import { MongoClient, ObjectID } from "mongodb";
import { get } from "./config";
import { escapeRegExp } from "./common";
import { parse } from "./common/expression-parser";
+import { DeviceData, Attributes } from "./types";
+import Path from "./common/path";
export let tasksCollection,
devicesCollection,
@@ -87,55 +89,66 @@ function optimizeProjection(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);
- if (!patterns || !patterns.length) patterns = [[[], (1 << MAX_DEPTH) - 1]];
+ if (!patterns || !patterns.length)
+ patterns = [[Path.parse(""), (1 << MAX_DEPTH) - 1]];
let projection = {};
const projectionTree = {};
- function func(path, pats, projTree): void {
- const children = {};
+ function func(
+ path: string,
+ pathLength: number,
+ pats: [Path, number][],
+ projTree
+ ): void {
+ const children: { [fragment: string]: [Path, number][] } = {};
for (const pat of pats) {
- const fragment = pat[0][path.length] || "*";
+ const fragment = (pat[0].segments[pathLength] as string) || "*";
if (fragment === "*") {
- if (pat[1] << path.length) projection[path.join(".")] = 1;
+ if (pat[1] << pathLength) projection[path.slice(0, -1)] = 1;
return;
}
if (!projTree[fragment]) projTree[fragment] = {};
- if (pat[1] & (1 << path.length)) {
- projection[path.concat("_timestamp").join(".")] = 1;
- projection[path.concat("_object").join(".")] = 1;
- projection[path.concat("_instance").join(".")] = 1;
- projection[path.concat([fragment, "_timestamp"]).join(".")] = 1;
- projection[path.concat([fragment, "_value"]).join(".")] = 1;
- projection[path.concat([fragment, "_type"]).join(".")] = 1;
- projection[path.concat([fragment, "_object"]).join(".")] = 1;
- projection[path.concat([fragment, "_instance"]).join(".")] = 1;
- projection[path.concat([fragment, "_writable"]).join(".")] = 1;
- projection[path.concat([fragment, "_orig"]).join(".")] = 1;
+ if (pat[1] & (1 << pathLength)) {
+ projection[path + "_timestamp"] = 1;
+ projection[path + "_object"] = 1;
+ projection[path + "_instance"] = 1;
+ projection[path + fragment + "._timestamp"] = 1;
+ projection[path + fragment + "._value"] = 1;
+ projection[path + fragment + "._type"] = 1;
+ projection[path + fragment + "._object"] = 1;
+ projection[path + fragment + "._instance"] = 1;
+ projection[path + fragment + "._writable"] = 1;
+ projection[path + fragment + "._orig"] = 1;
}
- if (pat[1] >> (path.length + 1)) {
+ if (pat[1] >> (pathLength + 1)) {
if (!children[fragment]) children[fragment] = [];
children[fragment].push(pat);
}
}
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["Events"];
delete projectionTree["Tags"];
- const res = [];
- const loaded = [];
+ const res: [Path, number, Attributes?][] = [];
+ const loaded: [Path, number][] = [];
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 === "Events._writable") {
res.push([
- ["Events"],
+ Path.parse("Events"),
timestamp,
{ 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") {
projection["_lastInform"] = 1;
projection["_lastBoot"] = 1;
projection["_lastBootstrap"] = 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") {
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") {
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 (
k === "Events.0_BOOTSTRAP._writable" ||
k === "Events.0_BOOTSTRAP"
) {
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 (
k === "Events.Registered._writable" ||
k === "Events.Registered"
) {
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") {
- 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];
}
@@ -182,42 +204,58 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (k === "" || k === "DeviceID" || k.startsWith("DeviceID.")) {
if (k === "" || k === "DeviceID" || k === "DeviceID._writable") {
res.push([
- ["DeviceID"],
+ Path.parse("DeviceID"),
timestamp,
{ 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") {
projection["_id"] = 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") {
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 (
k === "DeviceID.Manufacturer._writable" ||
k === "DeviceID.DeManufacturer"
) {
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 (
k === "DeviceID.ProductClass._writable" ||
k === "DeviceID.ProductClass"
) {
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") {
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 (
k === "DeviceID.SerialNumber._writable" ||
k === "DeviceID.SerialNumber"
) {
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") {
- 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];
}
@@ -225,7 +263,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (k === "Tags" || k.startsWith("Tags.")) {
if (!projection["_tags"]) {
projection["_tags"] = 1;
- loaded.push([["Tags"], (1 << MAX_DEPTH) - 1]);
+ loaded.push([Path.parse("Tags"), (1 << MAX_DEPTH) - 1]);
}
delete projection[k];
}
@@ -240,34 +278,39 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
devicesCollection.findOne({ _id: id }, options, (err, device) => {
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;
if (descendantsFetched) {
thisFetched = true;
- } else if (projection[path.join(".")]) {
+ } else if (projection[path.slice(0, -1)]) {
descendantsFetched = true;
if (
- path.length &&
+ pathLength &&
projection[
- path
- .slice(0, -1)
- .concat("_timestamp")
- .join(".")
+ path.slice(0, path.lastIndexOf(".", path.length - 2) + 1) +
+ "_timestamp"
]
) {
thisFetched = true;
loaded.push([
- path,
- ((1 << (path.length - 1)) - 1) ^ ((1 << MAX_DEPTH) - 1)
+ Path.parse(path.slice(0, -1)),
+ ((1 << (pathLength - 1)) - 1) ^ ((1 << MAX_DEPTH) - 1)
]);
} else {
loaded.push([
- path,
- ((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1)
+ Path.parse(path.slice(0, -1)),
+ ((1 << pathLength) - 1) ^ ((1 << MAX_DEPTH) - 1)
]);
}
- } else if (projection[path.concat("_writable").join(".")]) {
- loaded.push([path, 1 << (path.length - 1)]);
+ } else if (projection[path + "_writable"]) {
+ loaded.push([Path.parse(path.slice(0, -1)), 1 << (pathLength - 1)]);
thisFetched = true;
}
@@ -277,11 +320,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (obj["_instance"] && obj["_object"] == null) obj["_object"] = true;
if (thisFetched) {
- const attrs: {
- object?: [number, number];
- writable?: [number, number];
- value?: [number, [string | number | boolean, string]];
- } = {};
+ const attrs: Attributes = {};
let t = obj["_timestamp"] || 1;
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];
- res.push([path, t, attrs]);
+ res.push([Path.parse(path.slice(0, -1)), t, attrs]);
}
for (const [k, v] of Object.entries(obj)) {
@@ -305,7 +344,8 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
obj["_object"] = true;
storeParams(
v,
- path.concat(k),
+ path + k + ".",
+ pathLength + 1,
obj["_timestamp"],
descendantsFetched,
projTree ? projTree[k] : null
@@ -317,14 +357,14 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (!descendantsFetched) {
if (projTree) {
for (const k of Object.keys(projTree)) {
- const p = path.concat(k);
- loaded.push([p, ((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1)]);
- if ((obj["_object"] || !path.length) && obj["_timestamp"])
+ const p = Path.parse(path + k);
+ loaded.push([p, ((1 << pathLength) - 1) ^ ((1 << MAX_DEPTH) - 1)]);
+ if ((obj["_object"] || !pathLength) && obj["_timestamp"])
res.push([p, obj["_timestamp"]]);
}
}
- } else if ((obj["_object"] || !path.length) && obj["_timestamp"]) {
- res.push([path.concat("*"), obj["_timestamp"]]);
+ } else if ((obj["_object"] || !pathLength) && obj["_timestamp"]) {
+ res.push([Path.parse(path + "*"), obj["_timestamp"]]);
}
}
@@ -332,7 +372,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
switch (k) {
case "_lastInform":
res.push([
- ["Events", "Inform"],
+ Path.parse("Events.Inform"),
+v,
{
object: [+v, 0],
@@ -344,7 +384,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
break;
case "_lastBoot":
res.push([
- ["Events", "1_BOOT"],
+ Path.parse("Events.1_BOOT"),
+v,
{
object: [+v, 0],
@@ -356,7 +396,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
break;
case "_lastBootstrap":
res.push([
- ["Events", "0_BOOTSTRAP"],
+ Path.parse("Events.0_BOOTSTRAP"),
+v,
{
object: [+v, 0],
@@ -369,7 +409,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
case "_registered":
// Use current timestamp for registered event attribute timestamps
res.push([
- ["Events", "Registered"],
+ Path.parse("Events.Registered"),
timestamp,
{
object: [timestamp, 0],
@@ -382,12 +422,12 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
case "_id":
if (projection[""] || projection["_id"]) {
res.push([
- ["DeviceID", "ID"],
+ Path.parse("DeviceID.ID"),
timestamp,
{
object: [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":
if ((v as string[]).length) {
res.push([
- ["Tags"],
+ Path.parse("Tags"),
timestamp,
{ 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[]) {
t = t.replace(/[^a-zA-Z0-9-]+/g, "_");
res.push([
- ["Tags", t],
+ Path.parse("Tags." + t),
timestamp,
{
object: [timestamp, 0],
@@ -420,7 +460,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
case "_deviceId":
if (v["_Manufacturer"] != null) {
res.push([
- ["DeviceID", "Manufacturer"],
+ Path.parse("DeviceID.Manufacturer"),
timestamp,
{
object: [timestamp, 0],
@@ -432,7 +472,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (v["_OUI"] != null) {
res.push([
- ["DeviceID", "OUI"],
+ Path.parse("DeviceID.OUI"),
timestamp,
{
object: [timestamp, 0],
@@ -444,7 +484,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (v["_ProductClass"] != null) {
res.push([
- ["DeviceID", "ProductClass"],
+ Path.parse("DeviceID.ProductClass"),
timestamp,
{
object: [timestamp, 0],
@@ -456,7 +496,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
if (v["_SerialNumber"] != null) {
res.push([
- ["DeviceID", "SerialNumber"],
+ Path.parse("DeviceID.SerialNumber"),
timestamp,
{
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);
});
}
export function saveDevice(
- deviceId,
- deviceData,
- isNew,
- sessionTimestamp,
+ deviceId: string,
+ deviceData: DeviceData,
+ isNew: boolean,
+ sessionTimestamp: number,
callback
): void {
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][0] === "Events" ||
- diff[0][0] === "DeviceID" ||
- diff[0][0] === "Tags"
+ diff[0].segments[0] === "Events" ||
+ diff[0].segments[0] === "DeviceID" ||
+ diff[0].segments[0] === "Tags"
)
continue;
+ const parent = deviceData.paths.get(diff[0].slice(0, -1));
+
// Param timestamps may be greater than session timestamp to track revisions
if (diff[2] > sessionTimestamp) diff[2] = sessionTimestamp;
if (diff[2] == null && diff[1] != null) {
update["$unset"][
- diff[0]
- .slice(0, -1)
- .concat("_timestamp")
- .join(".")
+ parent.length ? parent.toString() + "._timestamp" : "_timestamp"
] = 1;
} else {
- const parent = deviceData.paths.get(diff[0].slice(0, -1));
if (parent && (!parent.length || deviceData.attributes.has(parent))) {
update["$set"][
- diff[0]
- .slice(0, -1)
- .concat("_timestamp")
- .join(".")
+ parent.length ? parent.toString() + "._timestamp" : "_timestamp"
] = new Date(diff[2]);
}
}
@@ -530,11 +565,11 @@ export function saveDevice(
const writable2 = ((diff[2] || {}).writable || [])[1];
const writable1 = ((diff[1] || {}).writable || [])[1];
- switch (path[0]) {
+ switch (path.segments[0]) {
case "Events":
- if (diff[0].length === 2 && value2 !== value1) {
+ if (path.length === 2 && value2 !== value1) {
if (!diff[2]) {
- switch (path[1]) {
+ switch (path.segments[1]) {
case "Inform":
update["$unset"]["_lastInform"] = 1;
break;
@@ -548,8 +583,8 @@ export function saveDevice(
update["$unset"]["_registered"] = 1;
}
} else {
- const t = new Date(diff[2].value[1][0]);
- switch (path[1]) {
+ const t = new Date(diff[2].value[1][0] as number);
+ switch (path.segments[1]) {
case "Inform":
update["$set"]["_lastInform"] = t;
break;
@@ -569,7 +604,7 @@ export function saveDevice(
case "DeviceID":
if (value2 !== value1) {
const v = diff[2].value[1][0];
- switch (path[1]) {
+ switch (path.segments[1]) {
case "ID":
update["$set"]["_id"] = v;
break;
@@ -592,21 +627,21 @@ export function saveDevice(
if (value2 != null) {
if (!update["$addToSet"]["_tags"])
update["$addToSet"]["_tags"] = { $each: [] };
- update["$addToSet"]["_tags"]["$each"].push(diff[0][1]);
+ update["$addToSet"]["_tags"]["$each"].push(path.segments[1]);
} else {
if (!update["$pull"]["_tags"]) {
update["$pull"]["_tags"] = {
$in: []
};
}
- update["$pull"]["_tags"]["$in"].push(diff[0][1]);
+ update["$pull"]["_tags"]["$in"].push(path.segments[1]);
}
}
break;
default:
if (!diff[2]) {
- update["$unset"][diff[0].join(".")] = 1;
+ update["$unset"][path.toString()] = 1;
continue;
}
@@ -621,35 +656,38 @@ export function saveDevice(
if (value2 !== value1) {
if (
valueType2 === "xsd:dateTime" &&
- Number.isInteger(value2)
+ Number.isInteger(value2 as number)
) {
- update["$set"][path.concat("_value").join(".")] = new Date(
- value2
+ update["$set"][path.toString() + "._value"] = new Date(
+ value2 as number
);
} else {
- update["$set"][path.concat("_value").join(".")] = value2;
+ update["$set"][path.toString() + "._value"] = value2;
}
}
if (valueType2 !== valueType1)
- update["$set"][path.concat("_type").join(".")] = valueType2;
+ update["$set"][path.toString() + "._type"] = valueType2;
if (valueTimestamp2 !== valueTimestamp1) {
- update["$set"][
- path.concat("_timestamp").join(".")
- ] = new Date(valueTimestamp2);
+ update["$set"][path.toString() + "._timestamp"] = new Date(
+ valueTimestamp2
+ );
}
break;
case "object":
- if (!diff[1] || !diff[1].object || object2 !== object1)
- update["$set"][path.concat("_object").join(".")] = !!object2;
+ if (!diff[1] || !diff[1].object || object2 !== object1) {
+ update["$set"][
+ path.length ? path.toString() + "._object" : "_object"
+ ] = !!object2;
+ }
break;
case "writable":
if (!diff[1] || !diff[1].writable || writable2 !== writable1) {
update["$set"][
- path.concat("_writable").join(".")
+ path.length ? path.toString() + "._writable" : "_writable"
] = !!writable2;
}
}
@@ -662,10 +700,11 @@ export function saveDevice(
diff[1][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") {
- update["$unset"][path.concat("_type").join(".")] = 1;
- update["$unset"][path.concat("_timestamp").join(".")] = 1;
+ update["$unset"][p + "_type"] = 1;
+ update["$unset"][p + "_timestamp"] = 1;
}
}
}
diff --git a/lib/default-provisions.ts b/lib/default-provisions.ts
index edcf359..fe74590 100644
--- a/lib/default-provisions.ts
+++ b/lib/default-provisions.ts
@@ -17,18 +17,19 @@
* along with GenieACS. If not, see .
*/
+import Path from "./common/path";
import * as config from "./config";
-import * as common from "./common";
import * as device from "./device";
import * as scheduling from "./scheduling";
const MAX_DEPTH = +config.get("MAX_DEPTH");
export function refresh(sessionContext, provision, declarations): boolean {
- const path = common.parsePath(provision[1]).slice();
- const l = path.length;
- path.length = MAX_DEPTH;
- path.fill("*", l);
+ const segments = Path.parse(provision[1]).segments.slice();
+ const l = segments.length;
+ segments.length = MAX_DEPTH;
+ segments.fill("*", l);
+ const path = Path.parse(segments.join("."));
const every = 1000 * (provision[2] || 1);
const offset = scheduling.variance(sessionContext.deviceId, every);
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 {
- const path = common.parsePath(provision[1]);
declarations.push({
- path: path,
+ path: Path.parse(provision[1]),
pathGet: 1,
pathSet: null,
attrGet: { value: 1 },
@@ -62,10 +62,8 @@ export function value(sessionContext, provision, declarations): boolean {
}
export function tag(sessionContext, provision, declarations): boolean {
- const path = ["Tags", provision[1]];
-
declarations.push({
- path: path,
+ path: Path.parse(`Tags.${provision[1]}`),
pathGet: 1,
pathSet: null,
attrGet: { value: 1 },
@@ -78,7 +76,7 @@ export function tag(sessionContext, provision, declarations): boolean {
export function reboot(sessionContext, provision, declarations): boolean {
declarations.push({
- path: ["Reboot"],
+ path: Path.parse("Reboot"),
pathGet: 1,
pathSet: null,
attrGet: { value: 1 },
@@ -91,7 +89,7 @@ export function reboot(sessionContext, provision, declarations): boolean {
export function reset(sessionContext, provision, declarations): boolean {
declarations.push({
- path: ["FactoryReset"],
+ path: Path.parse("FactoryReset"),
pathGet: 1,
pathSet: null,
attrGet: { value: 1 },
@@ -104,16 +102,13 @@ export function reset(sessionContext, provision, declarations): boolean {
export function download(sessionContext, provision, declarations): boolean {
const alias = [
- ["FileType"],
- provision[1] || "",
- ["FileName"],
- provision[2] || "",
- ["TargetFileName"],
- provision[3] || ""
- ];
+ `FileType:${JSON.stringify(provision[1] || "")}`,
+ `FileName:${JSON.stringify(provision[2] || "")}`,
+ `TargetFileName:${JSON.stringify(provision[3] || "")}`
+ ].join(",");
declarations.push({
- path: ["Downloads", alias],
+ path: Path.parse(`Downloads.[${alias}]`),
pathGet: 1,
pathSet: 1,
attrGet: null,
@@ -122,7 +117,7 @@ export function download(sessionContext, provision, declarations): boolean {
});
declarations.push({
- path: ["Downloads", alias, "Download"],
+ path: Path.parse(`Downloads.[${alias}].Download`),
pathGet: 1,
pathSet: null,
attrGet: { value: 1 },
@@ -144,7 +139,7 @@ export function instances(
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] === "-") {
declarations.push({
diff --git a/lib/device.ts b/lib/device.ts
index 21df586..7f4fc3e 100644
--- a/lib/device.ts
+++ b/lib/device.ts
@@ -17,7 +17,8 @@
* along with GenieACS. If not, see .
*/
-import { ParamPath, DeviceData, Declaration, Clear } from "./types";
+import Path from "./common/path";
+import { DeviceData, Declaration, Clear } from "./types";
const CHANGE_FLAGS = {
object: 2,
@@ -34,7 +35,7 @@ function parseBool(v): boolean {
}
export function sanitizeParameterValue(
- parameterValue
+ parameterValue: [string | number | boolean, string]
): [string | number | boolean, string?] {
if (parameterValue[0] != null) {
switch (parameterValue[1]) {
@@ -55,7 +56,7 @@ export function sanitizeParameterValue(
case "xsd:int":
case "xsd:unsignedInt":
if (typeof parameterValue[0] !== "number") {
- const i = parseInt(parameterValue[0]);
+ const i = parseInt(parameterValue[0] as string);
if (isNaN(i)) parameterValue[0] = "" + parameterValue[0];
else parameterValue[0] = i;
}
@@ -65,7 +66,7 @@ export function sanitizeParameterValue(
// Don't use parseInt because it reads date string as a number
let i = +parameterValue[0];
if (isNaN(i)) {
- i = Date.parse(parameterValue[0]);
+ i = Date.parse(parameterValue[0] as string);
if (isNaN(i)) parameterValue[0] = "" + parameterValue[0];
else parameterValue[0] = i;
} else {
@@ -88,87 +89,75 @@ export function sanitizeParameterValue(
return parameterValue;
}
-export function getAliasDeclarations(path, timestamp): Declaration[] {
- const decs = [] as Declaration[];
-
- function recursive(p, prefix): void {
- const pattern = prefix.concat(p);
-
- for (let i = prefix.length; i < pattern.length; ++i) {
- if (Array.isArray(pattern[i])) {
- const pat = pattern[i];
- pattern[i] = "*";
- for (let j = 0; j < pat.length; j += 2)
- recursive(pat[j], pattern.slice(0, i + 1));
- }
- }
-
- decs.push({
- path: pattern,
+export function getAliasDeclarations(
+ path: Path,
+ timestamp: number,
+ attrGet = null
+): Declaration[] {
+ const stripped = path.stripAlias();
+ let decs: Declaration[] = [
+ {
+ path: stripped,
pathGet: timestamp,
pathSet: null,
- attrGet: prefix.length ? { value: timestamp } : null,
+ attrGet: attrGet,
attrSet: null,
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;
}
export function unpack(
- deviceData,
- path: ParamPath,
+ deviceData: DeviceData,
+ path: Path,
revision?: number
-): ParamPath[] {
- let allMatches = [] as ParamPath[];
+): Path[] {
+ let allMatches = [] as Path[];
if (!path.alias) {
for (const p of deviceData.paths.find(path, false, true))
if (deviceData.attributes.has(p, revision)) allMatches.push(p);
} else {
- const wildcardPath = path.slice() as ParamPath;
- wildcardPath.wildcard = path.wildcard;
- for (let i = 0; i < wildcardPath.length; ++i) {
- if (Array.isArray(wildcardPath[i])) {
- wildcardPath[i] = "*";
- wildcardPath.wildcard |= 1 << i;
- }
- }
+ const wildcardPath = path.stripAlias();
for (const p of deviceData.paths.find(wildcardPath, false, true))
if (deviceData.attributes.has(p, revision)) allMatches.push(p);
for (let i = path.length - 1; i >= 0; --i) {
- if (Array.isArray(path[i])) {
- for (let j = 0; j < path[i].length; j += 2) {
- const p = wildcardPath
- .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);
+ if (path.alias & (i << i)) {
+ for (const [param, val] of path.segments[i] as [Path, string][]) {
+ const p = wildcardPath.slice(0, i + 1).concat(param);
const unpacked = unpack(deviceData, p, revision);
- const filtered = [] as ParamPath[];
+ const filtered: Path[] = [];
for (const up of unpacked) {
const 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]
+ sanitizeParameterValue([val, 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].segments[k] !== up.segments[k]) break;
if (k < 0) {
filtered.push(allMatches[m]);
@@ -185,8 +174,8 @@ export function unpack(
allMatches.sort((p1, p2) => {
for (let i = 0; i < p1.length; ++i) {
- const a = p1[i];
- const b = p2[i];
+ const a = p1.segments[i] as string;
+ const b = p2.segments[i] as string;
if (a !== b) {
// Use numeric sorting for numbers
const ia = parseInt(a);
@@ -285,7 +274,7 @@ function compareEquality(a, b): boolean {
export function set(
deviceData: DeviceData,
- path: ParamPath,
+ path: Path,
timestamp,
attributes,
toClear?: Clear[]
@@ -345,7 +334,7 @@ export function set(
if (path.length > 1) {
toClear = set(
deviceData,
- path.slice(0, path.length - 1) as ParamPath,
+ path.slice(0, path.length - 1),
timestamp,
{ object: [timestamp, 1] },
toClear
@@ -389,7 +378,7 @@ export function set(
export function track(
deviceData: DeviceData,
- path: ParamPath,
+ path: Path,
marker: string,
attributes?
): void {
@@ -408,7 +397,7 @@ export function track(
cur[marker] |= f;
}
-export function clearTrackers(deviceData, tracker): void {
+export function clearTrackers(deviceData: DeviceData, tracker): void {
if (Array.isArray(tracker)) {
for (const v of deviceData.trackers.values())
for (const t of tracker) delete v[t];
diff --git a/lib/sandbox.ts b/lib/sandbox.ts
index 66c0798..f1cbb36 100644
--- a/lib/sandbox.ts
+++ b/lib/sandbox.ts
@@ -19,12 +19,12 @@
import * as vm from "vm";
import seedrandom from "seedrandom";
-import * as common from "./common";
import * as device from "./device";
import * as extensions from "./extensions";
import * as logger from "./logger";
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
const COMMIT = Symbol();
@@ -112,7 +112,7 @@ random.seed = function(s) {
};
class ParameterWrapper {
- public constructor(path, attributes, unpacked?, unpackedRevision?) {
+ public constructor(path: Path, attributes, unpacked?, unpackedRevision?) {
for (const attrName of attributes) {
Object.defineProperty(this, attrName, {
get: function() {
@@ -156,7 +156,7 @@ class ParameterWrapper {
if (!unpacked.length) return UNDEFINED;
- return unpacked[0].join(".");
+ return unpacked[0].toString();
}
});
@@ -198,7 +198,7 @@ class ParameterWrapper {
}
function declare(
- path: ParamPath,
+ path: string,
timestamps: { [attr: string]: number },
values: { [attr: string]: any }
): ParameterWrapper {
@@ -207,7 +207,7 @@ function declare(
if (!values) values = {};
- const parsedPath = common.parsePath(path);
+ const parsedPath = Path.parse(path);
const declaration = {
path: parsedPath,
@@ -249,11 +249,11 @@ function declare(
return new ParameterWrapper(parsedPath, attrs);
}
-function clear(path, timestamp, attributes): void {
+function clear(path: string, timestamp: number, attributes?): void {
state.uncommitted = true;
if (state.revision === state.maxRevision)
- state.clear.push([common.parsePath(path), timestamp, attributes]);
+ state.clear.push([Path.parse(path), timestamp, attributes]);
}
function commit(): void {
@@ -282,19 +282,19 @@ function ext(): any {
throw EXT;
}
-function log(msg, meta): void {
+function log(msg: string, meta: {}): void {
if (state.revision === state.maxRevision && state.extCounter >= 0) {
const details = Object.assign({}, meta, {
sessionContext: state.sessionContext,
message: `Script: ${msg}`
});
- delete details.hostname;
- delete details.pid;
- delete details.name;
- delete details.version;
- delete details.deviceId;
- delete details.remoteAddress;
+ delete details["hostname"];
+ delete details["pid"];
+ delete details["name"];
+ delete details["version"];
+ delete details["deviceId"];
+ delete details["remoteAddress"];
logger.accessInfo(details);
}
@@ -312,7 +312,7 @@ context.random = random;
vm.runInContext("Math.random = random;", context);
delete context.random;
-function errorToFault(err): Fault {
+function errorToFault(err: Error): Fault {
if (!err) return null;
if (!err.name) return { code: "script", message: `${err}` };
@@ -327,24 +327,28 @@ function errorToFault(err): Fault {
};
if (err.stack) {
- fault.detail.stack = err.stack;
+ fault.detail["stack"] = err.stack;
// 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.+\)/
);
- if (stackTrimIndex)
- fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index);
+ if (stackTrimIndex) {
+ fault.detail["stack"] = fault.detail["stack"].slice(
+ 0,
+ stackTrimIndex.index
+ );
+ }
}
return fault;
}
export function run(
- script,
+ script: vm.Script,
globals,
- sessionContext,
- startRevision,
- maxRevision,
+ sessionContext: SessionContext,
+ startRevision: number,
+ maxRevision: number,
callback,
extCounter = 0
): void {
diff --git a/lib/session.ts b/lib/session.ts
index c2f1817..6e9d58e 100644
--- a/lib/session.ts
+++ b/lib/session.ts
@@ -18,27 +18,32 @@
*/
import * as config from "./config";
-import * as common from "./common";
import * as db from "./db";
import * as device from "./device";
import * as sandbox from "./sandbox";
import * as localCache from "./local-cache";
import * as defaultProvisions from "./default-provisions";
import { estimateGpnCount } from "./gpn-heuristic";
-import PathSet from "./path-set";
+import Path from "./common/path";
+import PathSet from "./common/path-set";
import VersionedMap from "./versioned-map";
import InstanceSet from "./instance-set";
import {
Attributes,
SessionContext,
- ParamPath,
DeviceData,
VirtualParameterDeclaration,
GetAcsRequest,
SetAcsRequest,
AttributeTimestamps,
AttributeValues,
- Fault
+ Fault,
+ Declaration,
+ Clear,
+ CpeResponse,
+ CpeFault,
+ CpeSetResponse,
+ CpeGetResponse
} from "./types";
function initDeviceData(): DeviceData {
@@ -95,7 +100,10 @@ function loadParameters(sessionContext: SessionContext, callback): void {
// Device not available in database, mark as new
sessionContext.new = true;
loaded = [
- [[], (1 << +config.get("MAX_DEPTH", sessionContext.deviceId)) - 1]
+ [
+ Path.parse(""),
+ (1 << +config.get("MAX_DEPTH", sessionContext.deviceId)) - 1
+ ]
];
parameters = [];
}
@@ -130,9 +138,9 @@ function generateRpcId(sessionContext: SessionContext): string {
export function inform(sessionContext: SessionContext, rpcReq, callback): void {
const timestamp = sessionContext.timestamp + sessionContext.iteration + 1;
- const params: [ParamPath, number, Attributes][] = [
+ const params: [Path, number, Attributes][] = [
[
- ["DeviceID", "Manufacturer"] as ParamPath,
+ Path.parse("DeviceID.Manufacturer"),
timestamp,
{
object: [timestamp, 0],
@@ -142,7 +150,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
],
[
- ["DeviceID", "OUI"] as ParamPath,
+ Path.parse("DeviceID.OUI"),
timestamp,
{
object: [timestamp, 0],
@@ -152,7 +160,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
],
[
- ["DeviceID", "ProductClass"] as ParamPath,
+ Path.parse("DeviceID.ProductClass"),
timestamp,
{
object: [timestamp, 0],
@@ -162,7 +170,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
],
[
- ["DeviceID", "SerialNumber"] as ParamPath,
+ Path.parse("DeviceID.SerialNumber"),
timestamp,
{
object: [timestamp, 0],
@@ -173,7 +181,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
];
for (const p of rpcReq.parameterList) {
- const path = common.parsePath(p[0]);
+ const path = Path.parse(p[0]);
params.push([
path,
timestamp,
@@ -185,7 +193,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
}
params.push([
- ["Events", "Inform"] as ParamPath,
+ Path.parse("Events.Inform"),
timestamp,
{
object: [timestamp, 0],
@@ -196,7 +204,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
for (const e of rpcReq.event) {
params.push([
- ["Events", e.replace(/\s+/g, "_")] as ParamPath,
+ Path.parse(`Events.${e.replace(/\s+/g, "_")}`),
timestamp,
{
object: [timestamp, 0],
@@ -207,7 +215,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
}
// Preload DeviceID params
- loadPath(sessionContext, ["DeviceID", "*"] as ParamPath);
+ loadPath(sessionContext, Path.parse("DeviceID.*"));
for (const p of params) loadPath(sessionContext, p[0]);
@@ -216,7 +224,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
if (sessionContext.new) {
params.push([
- ["DeviceID", "ID"] as ParamPath,
+ Path.parse("DeviceID.ID"),
timestamp,
{
object: [timestamp, 0],
@@ -225,7 +233,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
}
]);
params.push([
- ["Events", "Registered"] as ParamPath,
+ Path.parse("Events.Registered"),
timestamp,
{
object: [timestamp, 0],
@@ -241,7 +249,7 @@ export function inform(sessionContext: SessionContext, rpcReq, callback): void {
let toClear = null;
for (const p of params) {
// Don't need to clear wildcards for Events
- if (p[0][0] === "Events") {
+ if (p[0].segments[0] === "Events") {
device.set(sessionContext.deviceData, p[0], p[1], p[2]);
} else {
toClear = device.set(
@@ -298,7 +306,7 @@ export function transferComplete(
);
}
- loadPath(sessionContext, ["Downloads", instance, "*"] as ParamPath);
+ loadPath(sessionContext, Path.parse(`Downloads.${instance}.*`));
loadParameters(sessionContext, err => {
if (err) return void callback(err);
@@ -308,11 +316,9 @@ export function transferComplete(
let p;
- p = sessionContext.deviceData.paths.add([
- "Downloads",
- instance,
- "LastDownload"
- ] as ParamPath);
+ p = sessionContext.deviceData.paths.add(
+ Path.parse(`Downloads.${instance}.LastDownload`)
+ );
toClear = device.set(
sessionContext.deviceData,
p,
@@ -321,11 +327,9 @@ export function transferComplete(
toClear
);
- p = sessionContext.deviceData.paths.add([
- "Downloads",
- instance,
- "LastFileType"
- ] as ParamPath);
+ p = sessionContext.deviceData.paths.add(
+ Path.parse(`Downloads.${instance}.LastFileType`)
+ );
toClear = device.set(
sessionContext.deviceData,
p,
@@ -334,11 +338,9 @@ export function transferComplete(
toClear
);
- p = sessionContext.deviceData.paths.add([
- "Downloads",
- instance,
- "LastFileName"
- ] as ParamPath);
+ p = sessionContext.deviceData.paths.add(
+ Path.parse(`Downloads.${instance}.LastFileName`)
+ );
toClear = device.set(
sessionContext.deviceData,
p,
@@ -347,11 +349,9 @@ export function transferComplete(
toClear
);
- p = sessionContext.deviceData.paths.add([
- "Downloads",
- instance,
- "LastTargetFileName"
- ] as ParamPath);
+ p = sessionContext.deviceData.paths.add(
+ Path.parse(`Downloads.${instance}.LastTargetFileName`)
+ );
toClear = device.set(
sessionContext.deviceData,
p,
@@ -360,11 +360,9 @@ export function transferComplete(
toClear
);
- p = sessionContext.deviceData.paths.add([
- "Downloads",
- instance,
- "StartTime"
- ] as ParamPath);
+ p = sessionContext.deviceData.paths.add(
+ Path.parse(`Downloads.${instance}.StartTime`)
+ );
toClear = device.set(
sessionContext.deviceData,
p,
@@ -373,11 +371,9 @@ export function transferComplete(
toClear
);
- p = sessionContext.deviceData.paths.add([
- "Downloads",
- instance,
- "CompleteTime"
- ] as ParamPath);
+ p = sessionContext.deviceData.paths.add(
+ Path.parse(`Downloads.${instance}.CompleteTime`)
+ );
toClear = device.set(
sessionContext.deviceData,
p,
@@ -397,7 +393,7 @@ function revertDownloadParameters(
instance,
callback
): void {
- loadPath(sessionContext, ["Downloads", instance, "*"] as ParamPath);
+ loadPath(sessionContext, Path.parse(`Downloads.${instance}.*`));
loadParameters(sessionContext, err => {
if (err) return void callback(err);
@@ -406,19 +402,15 @@ function revertDownloadParameters(
let p;
- p = sessionContext.deviceData.paths.add([
- "Downloads",
- instance,
- "LastDownload"
- ] as ParamPath);
+ p = sessionContext.deviceData.paths.add(
+ Path.parse(`Downloads.${instance}.LastDownload`)
+ );
const lastDownload = sessionContext.deviceData.attributes.get(p);
- p = sessionContext.deviceData.paths.add([
- "Downloads",
- instance,
- "Download"
- ] as ParamPath);
+ p = sessionContext.deviceData.paths.add(
+ Path.parse(`Downloads.${instance}.Download`)
+ );
const toClear = device.set(sessionContext.deviceData, p, timestamp, {
value: [
@@ -577,11 +569,17 @@ export function clearProvisions(sessionContext: SessionContext): void {
}
function runProvisions(
- sessionContext,
- provisions,
- startRevision,
- endRevision,
- callback
+ sessionContext: SessionContext,
+ provisions: any[][],
+ startRevision: number,
+ endRevision: number,
+ callback: (
+ err: Error,
+ fault?: Fault,
+ ret?: any,
+ decs?: Declaration[],
+ toClear?: Clear[]
+ ) => void
): void {
let done = true;
let allDeclarations = [];
@@ -642,11 +640,17 @@ function runProvisions(
}
function runVirtualParameters(
- sessionContext,
- provisions,
- startRevision,
- endRevision,
- callback
+ sessionContext: SessionContext,
+ provisions: any[][],
+ startRevision: number,
+ endRevision: number,
+ callback: (
+ err: Error,
+ fault?: Fault,
+ ret?: any,
+ decs?: Declaration[],
+ toClear?: Clear[]
+ ) => void
): void {
let done = true;
const virtualParameterUpdates = [];
@@ -795,7 +799,7 @@ function runVirtualParameters(
function runDeclarations(
sessionContext: SessionContext,
- declarations
+ declarations: Declaration[]
): VirtualParameterDeclaration[] {
if (!sessionContext.syncState) {
sessionContext.syncState = {
@@ -806,7 +810,7 @@ function runDeclarations(
value: new Set()
},
spv: new Map(),
- gpn: new Set(),
+ gpn: new Set(),
gpnPatterns: new Map(),
tags: new Map(),
virtualParameterDeclarations: [],
@@ -815,25 +819,21 @@ function runDeclarations(
downloadsToDelete: new Set(),
downloadsToCreate: new InstanceSet(),
downloadsValues: new Map(),
- downloadsDownload: new Map()
+ downloadsDownload: new Map(),
+ reboot: 0,
+ factoryReset: 0
};
}
- const allDeclareTimestamps = new Map();
- const allDeclareAttributeTimestamps = new Map<
- ParamPath,
- AttributeTimestamps
- >();
- const allDeclareAttributeValues = new Map();
+ const allDeclareTimestamps = new Map();
+ const allDeclareAttributeTimestamps = new Map();
+ const allDeclareAttributeValues = new Map();
const allVirtualParameters = localCache.getVirtualParameters(
sessionContext.cacheSnapshot
);
- function mergeAttributeTimestamps(
- p: ParamPath,
- attrs: AttributeTimestamps
- ): void {
+ function mergeAttributeTimestamps(p: Path, attrs: AttributeTimestamps): void {
let cur = allDeclareAttributeTimestamps.get(p);
if (!cur) {
allDeclareAttributeTimestamps.set(p, attrs);
@@ -846,7 +846,7 @@ function runDeclarations(
}
function mergeAttributeValues(
- p: ParamPath,
+ p: Path,
attrs: AttributeValues,
defer: boolean
): void {
@@ -860,30 +860,29 @@ function runDeclarations(
}
for (const declaration of declarations) {
- let path = common.addPathMeta(declaration.path);
- let unpacked;
+ let path = declaration.path;
+ let unpacked: Path[];
- if ((path.alias | path.wildcard) & 1 || path[0] === "VirtualParameters") {
- sessionContext.deviceData.paths.add(["VirtualParameters"] as ParamPath);
+ if (
+ (path.alias | path.wildcard) & 1 ||
+ path.segments[0] === "VirtualParameters"
+ ) {
+ sessionContext.deviceData.paths.add(Path.parse("VirtualParameters"));
if ((path.alias | path.wildcard) & 2) {
- sessionContext.deviceData.paths.add([
- "VirtualParameters",
- "*"
- ] as ParamPath);
+ sessionContext.deviceData.paths.add(Path.parse("VirtualParameters.*"));
for (const k of Object.keys(allVirtualParameters)) {
- sessionContext.deviceData.paths.add([
- "VirtualParameters",
- k
- ] as ParamPath);
+ sessionContext.deviceData.paths.add(
+ Path.parse(`VirtualParameters.${k}`)
+ );
}
}
}
- if ((path.alias | path.wildcard) & 1 || path[0] === "Reboot")
- sessionContext.deviceData.paths.add(["Reboot"] as ParamPath);
+ if ((path.alias | path.wildcard) & 1 || path.segments[0] === "Reboot")
+ sessionContext.deviceData.paths.add(Path.parse("Reboot"));
- if ((path.alias | path.wildcard) & 1 || path[0] === "FactoryReset")
- sessionContext.deviceData.paths.add(["FactoryReset"] as ParamPath);
+ if ((path.alias | path.wildcard) & 1 || path.segments[0] === "FactoryReset")
+ sessionContext.deviceData.paths.add(Path.parse("FactoryReset"));
if (path.alias) {
const aliasDecs = device.getAliasDeclarations(
@@ -951,16 +950,14 @@ function runDeclarations(
minInstances = maxInstances = declaration.pathSet;
}
- let parent = common.addPathMeta(path.slice(0, -1));
+ let parent = path.slice(0, -1);
let keys;
- if (Array.isArray(path[path.length - 1])) {
+ if (Array.isArray(path.segments[path.length - 1])) {
keys = {};
- for (let i = 0; i < path[path.length - 1].length; i += 2) {
- keys[path[path.length - 1][i].join(".")] =
- path[path.length - 1][i + 1];
- }
- } else if (path[path.length - 1] === "*") {
+ for (const [p, v] of path.segments[path.length - 1])
+ keys[p.toString()] = v;
+ } else if (path.segments[path.length - 1] === "*") {
keys = {};
}
@@ -986,14 +983,13 @@ function runDeclarations(
sessionContext.deviceData,
parent
);
- for (let par of parentsUnpacked) {
- par = sessionContext.deviceData.paths.add(par);
+ for (const par of parentsUnpacked) {
processInstances(
sessionContext,
par,
device.unpack(
sessionContext.deviceData,
- common.addPathMeta(par.concat([path[par.length]]))
+ par.concat(path.slice(-1))
),
keys,
minInstances,
@@ -1015,7 +1011,7 @@ function runDeclarations(
export function rpcRequest(
sessionContext: SessionContext,
- _declarations,
+ _declarations: Declaration[],
callback
): void {
if (sessionContext.rpcRequest != null) {
@@ -1217,11 +1213,11 @@ export function rpcRequest(
return false;
} else if (declaration[0].length === 2) {
- if (declaration[0][1] === "*") {
+ if (declaration[0].segments[1] === "*") {
for (const k of Object.keys(allVirtualParameters)) {
toClear = device.set(
sessionContext.deviceData,
- ["VirtualParameters", k] as ParamPath,
+ Path.parse(`VirtualParameters.${k}`),
timestamp,
{
object: [timestamp, 0]
@@ -1237,7 +1233,9 @@ export function rpcRequest(
toClear
);
return false;
- } else if (allVirtualParameters[declaration[0][1]]) {
+ } else if (
+ allVirtualParameters[declaration[0].segments[1] as string]
+ ) {
// Avoid setting on every inform as "exist" timestamp
// is not saved in DB
if (!sessionContext.deviceData.attributes.has(declaration[0])) {
@@ -1331,12 +1329,15 @@ export function rpcRequest(
if (index == null) {
index = 0;
for (const p of sessionContext.deviceData.paths.find(
- ["Downloads", "*"] as ParamPath,
+ Path.parse("Downloads.*"),
false,
true
)) {
- if (+p[1] > index && sessionContext.deviceData.attributes.has(p))
- index = +p[1];
+ if (
+ +p.segments[1] > index &&
+ sessionContext.deviceData.attributes.has(p)
+ )
+ index = +p.segments[1];
}
}
@@ -1344,7 +1345,7 @@ export function rpcRequest(
toClear = device.set(
sessionContext.deviceData,
- ["Downloads"] as ParamPath,
+ Path.parse("Downloads"),
timestamp,
{ object: [timestamp, 1], writable: [timestamp, 1] },
toClear
@@ -1352,7 +1353,7 @@ export function rpcRequest(
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", "" + index] as ParamPath,
+ Path.parse(`Downloads.${index}`),
timestamp,
{ object: [timestamp, 1], writable: [timestamp, 1] },
toClear
@@ -1386,7 +1387,7 @@ export function rpcRequest(
for (const [k, v] of Object.entries(params)) {
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", `${index}`, k] as ParamPath,
+ Path.parse(`Downloads.${index}.${k}`),
timestamp,
{
object: [timestamp, 0],
@@ -1399,7 +1400,7 @@ export function rpcRequest(
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", `${index}`, "*"] as ParamPath,
+ Path.parse(`Downloads.${index}.*`),
timestamp,
null,
toClear
@@ -1417,7 +1418,7 @@ export function rpcRequest(
toClear
);
for (const p of sessionContext.syncState.downloadsValues.keys()) {
- if (p[1] === instance[1])
+ if (p.segments[1] === instance.segments[1])
sessionContext.syncState.downloadsValues.delete(p);
}
}
@@ -1504,7 +1505,7 @@ export function rpcRequest(
toClear = device.set(
sessionContext.deviceData,
- ["VirtualParameters", vparams[i][0]] as ParamPath,
+ Path.parse(`VirtualParameters.${vparams[i][0]}`),
timestamp,
vpu,
toClear
@@ -1541,10 +1542,7 @@ function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
}
if (!found) {
- const p = sessionContext.deviceData.paths.add(path.slice(
- 0,
- -1
- ) as ParamPath);
+ const p = sessionContext.deviceData.paths.add(path.slice(0, -1));
syncState.gpn.add(p);
const f = 1 << p.length;
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
@@ -1572,10 +1570,7 @@ function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
}
if (!found) {
- const p = sessionContext.deviceData.paths.add(path.slice(
- 0,
- -1
- ) as ParamPath);
+ const p = sessionContext.deviceData.paths.add(path.slice(0, -1));
syncState.gpn.add(p);
const f = 1 << p.length;
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
@@ -1584,10 +1579,7 @@ function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
syncState.refreshAttributes.object.clear();
for (const path of syncState.refreshAttributes.writable) {
- const p = sessionContext.deviceData.paths.add(path.slice(
- 0,
- -1
- ) as ParamPath);
+ const p = sessionContext.deviceData.paths.add(path.slice(0, -1));
syncState.gpn.add(p);
const f = 1 << p.length;
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
@@ -1647,7 +1639,7 @@ function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
return {
name: "GetParameterNames",
- parameterPath: path.concat("").join("."),
+ parameterPath: path.length ? path.toString() + "." : "",
nextLevel: nextLevel
};
}
@@ -1660,13 +1652,13 @@ function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
sessionContext.configContext
);
- const parameterNames = [];
+ const parameterNames: string[] = [];
for (const path of syncState.refreshAttributes.value) {
syncState.refreshAttributes.value.delete(path);
// Need to check in case param is deleted or changed to object
const attrs = sessionContext.deviceData.attributes.get(path);
if (attrs && attrs.object && attrs.object[1] === 0) {
- parameterNames.push(path);
+ parameterNames.push(path.toString());
if (parameterNames.length >= GPV_BATCH_SIZE) break;
}
}
@@ -1674,14 +1666,14 @@ function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
if (parameterNames.length) {
return {
name: "GetParameterValues",
- parameterNames: parameterNames.map(p => p.join("."))
+ parameterNames: parameterNames
};
}
}
return null;
}
-function generateSetRpcRequest(sessionContext): SetAcsRequest {
+function generateSetRpcRequest(sessionContext: SessionContext): SetAcsRequest {
const syncState = sessionContext.syncState;
if (!syncState) return null;
@@ -1693,7 +1685,7 @@ function generateSetRpcRequest(sessionContext): SetAcsRequest {
if (instance && sessionContext.deviceData.attributes.has(instance)) {
return {
name: "DeleteObject",
- objectName: instance.concat("").join(".")
+ objectName: instance.toString() + "."
};
}
}
@@ -1706,7 +1698,7 @@ function generateSetRpcRequest(sessionContext): SetAcsRequest {
instances.delete(instance);
return {
name: "AddObject",
- objectName: param.concat("").join("."),
+ objectName: param.toString() + ".",
instanceValues: instance,
next: "getInstanceKeys"
};
@@ -1733,17 +1725,13 @@ function generateSetRpcRequest(sessionContext): SetAcsRequest {
sessionContext.configContext
);
- const parameterValues = [] as [
- ParamPath,
- string | number | boolean,
- string
- ][];
+ const parameterValues: [Path, string | number | boolean, string][] = [];
for (const [k, v] of syncState.spv) {
syncState.spv.delete(k);
const attrs = sessionContext.deviceData.attributes.get(k);
const curVal = attrs.value ? attrs.value[1] : null;
if (curVal && attrs.writable && attrs.writable[1]) {
- const val = v.slice();
+ const val = v.slice() as [string | number | boolean, string];
if (!val[1]) val[1] = curVal[1];
device.sanitizeParameterValue(val);
@@ -1767,7 +1755,7 @@ function generateSetRpcRequest(sessionContext): SetAcsRequest {
name: "SetParameterValues",
parameterList: parameterValues.map(
p =>
- [p[0].join("."), p[1], p[2]] as [
+ [p[0].toString(), p[1], p[2]] as [
string,
string | number | boolean,
string
@@ -1783,32 +1771,34 @@ function generateSetRpcRequest(sessionContext): SetAcsRequest {
const attrs = deviceData.attributes.get(p);
if (!(attrs && attrs.value && t <= attrs.value[1][0])) {
const fileTypeAttrs = deviceData.attributes.get(
- deviceData.paths.get(p.slice(0, -1).concat("FileType"))
+ deviceData.paths.get(p.slice(0, -1).concat(Path.parse("FileType")))
);
const fileNameAttrs = deviceData.attributes.get(
- deviceData.paths.get(p.slice(0, -1).concat("FileName"))
+ deviceData.paths.get(p.slice(0, -1).concat(Path.parse("FileName")))
);
const targetFileNameAttrs = deviceData.attributes.get(
- deviceData.paths.get(p.slice(0, -1).concat("TargetFileName"))
+ deviceData.paths.get(
+ p.slice(0, -1).concat(Path.parse("TargetFileName"))
+ )
);
return {
name: "Download",
commandKey: generateRpcId(sessionContext),
- instance: p[1],
+ instance: p.segments[1] as string,
fileType: fileTypeAttrs
? fileTypeAttrs.value
- ? fileTypeAttrs.value[1][0]
+ ? (fileTypeAttrs.value[1][0] as string)
: null
: null,
fileName: fileNameAttrs
? fileNameAttrs.value
- ? fileNameAttrs.value[1][0]
+ ? (fileNameAttrs.value[1][0] as string)
: null
: null,
targetFileName: targetFileNameAttrs
? targetFileNameAttrs.value
- ? targetFileNameAttrs.value[1][0]
+ ? (targetFileNameAttrs.value[1][0] as string)
: null
: null
};
@@ -1817,7 +1807,7 @@ function generateSetRpcRequest(sessionContext): SetAcsRequest {
// Reboot
if (syncState.reboot) {
- const p = sessionContext.deviceData.paths.get(["Reboot"]);
+ const p = sessionContext.deviceData.paths.get(Path.parse("Reboot"));
const attrs = p ? sessionContext.deviceData.attributes.get(p) : null;
if (!(attrs && attrs.value && attrs.value[1][0] >= syncState.reboot)) {
delete syncState.reboot;
@@ -1827,7 +1817,7 @@ function generateSetRpcRequest(sessionContext): SetAcsRequest {
// Factory reset
if (syncState.factoryReset) {
- const p = sessionContext.deviceData.paths.get(["FactoryReset"]);
+ const p = sessionContext.deviceData.paths.get(Path.parse("FactoryReset"));
const attrs = p ? sessionContext.deviceData.attributes.get(p) : null;
if (
!(attrs && attrs.value && attrs.value[1][0] >= syncState.factoryReset)
@@ -1841,10 +1831,10 @@ function generateSetRpcRequest(sessionContext): SetAcsRequest {
}
function generateGetVirtualParameterProvisions(
- sessionContext,
- virtualParameterDeclarations
+ sessionContext: SessionContext,
+ virtualParameterDeclarations: VirtualParameterDeclaration[]
): [
- ParamPath,
+ string,
AttributeTimestamps,
AttributeValues,
AttributeTimestamps,
@@ -1873,7 +1863,7 @@ function generateGetVirtualParameterProvisions(
if (Object.keys(dec).length) {
if (!provisions) provisions = [];
provisions.push([
- declaration[0][1],
+ declaration[0].segments[1],
dec,
{},
currentTimestamps,
@@ -1887,10 +1877,10 @@ function generateGetVirtualParameterProvisions(
}
function generateSetVirtualParameterProvisions(
- sessionContext,
- virtualParameterDeclarations
+ sessionContext: SessionContext,
+ virtualParameterDeclarations: VirtualParameterDeclaration[]
): [
- ParamPath,
+ string,
AttributeTimestamps,
AttributeValues,
AttributeTimestamps,
@@ -1908,7 +1898,10 @@ function generateSetVirtualParameterProvisions(
attrs.value &&
attrs.value[1] != null
) {
- const val = declaration[2].value.slice();
+ const val = declaration[2].value.slice() as [
+ string | number | boolean,
+ string
+ ];
if (val[1] == null) val[1] = attrs.value[1][1];
device.sanitizeParameterValue(val);
@@ -1923,7 +1916,7 @@ function generateSetVirtualParameterProvisions(
}
provisions.push([
- declaration[0][1],
+ declaration[0].segments[1],
{},
{ value: val },
currentTimestamps,
@@ -1939,16 +1932,16 @@ function generateSetVirtualParameterProvisions(
}
function processDeclarations(
- sessionContext,
+ sessionContext: SessionContext,
allDeclareTimestamps,
- allDeclareAttributeTimestamps: Map,
- allDeclareAttributeValues: Map
+ allDeclareAttributeTimestamps: Map,
+ allDeclareAttributeValues: Map
): VirtualParameterDeclaration[] {
const deviceData = sessionContext.deviceData;
const syncState = sessionContext.syncState;
- const root = sessionContext.deviceData.paths.add([]);
- const paths = deviceData.paths.find([], false, true, 99);
+ const root = sessionContext.deviceData.paths.add(Path.parse(""));
+ const paths = deviceData.paths.find(root, false, true, 99);
paths.sort(
(a, b): number =>
a.wildcard === b.wildcard ? a.length - b.length : a.wildcard - b.wildcard
@@ -1957,13 +1950,13 @@ function processDeclarations(
const virtualParameterDeclarations = [] as VirtualParameterDeclaration[];
function func(
- leafParam: ParamPath,
+ leafParam: Path,
leafIsObject: number,
leafTimestamp: number,
- _paths: ParamPath[]
+ _paths: Path[]
): void {
const currentPath = _paths[0];
- const children = new Map();
+ const children = new Map();
let declareTimestamp = 0;
let declareAttributeTimestamps;
let declareAttributeValues;
@@ -1975,12 +1968,12 @@ function processDeclarations(
for (const path of _paths) {
if (path.length > currentPath.length) {
- const fragment = path[currentPath.length];
+ const fragment = path.segments[currentPath.length] as string;
let child = children.get(fragment);
if (!child) {
if (path.length > currentPath.length + 1) {
// This is to ensure we don't descend more than one step at a time
- const p = common.addPathMeta(path.slice(0, currentPath.length + 1));
+ const p = path.slice(0, currentPath.length + 1);
child = [p];
} else {
child = [];
@@ -2036,7 +2029,11 @@ function processDeclarations(
leafTimestamp = Math.max(leafTimestamp, currentTimestamp);
}
- switch (currentPath[0] !== "*" ? currentPath[0] : leafParam[0]) {
+ switch (
+ currentPath.segments[0] !== "*"
+ ? currentPath.segments[0]
+ : leafParam.segments[0]
+ ) {
case "Reboot":
if (currentPath.length === 1) {
if (declareAttributeValues && declareAttributeValues.value)
@@ -2056,13 +2053,10 @@ function processDeclarations(
declareAttributeValues &&
declareAttributeValues.value
) {
- syncState.tags.set(
- currentPath,
- device.sanitizeParameterValue([
- declareAttributeValues.value[0],
- "xsd:boolean"
- ])[0]
- );
+ syncState.tags.set(currentPath, device.sanitizeParameterValue([
+ declareAttributeValues.value[0],
+ "xsd:boolean"
+ ])[0] as boolean);
}
break;
@@ -2077,7 +2071,7 @@ function processDeclarations(
declareAttributeValues &&
declareAttributeValues.value
) {
- if (currentPath[2] === "Download") {
+ if (currentPath.segments[2] === "Download") {
syncState.downloadsDownload.set(
currentPath,
declareAttributeValues.value[0]
@@ -2150,7 +2144,7 @@ function processDeclarations(
syncState.refreshAttributes.object.add(leafParam);
if (leafIsObject == null) {
const f =
- ((1 << syncState.gpnPatterns.length) - 1) ^
+ ((1 << syncState.gpnPatterns.size) - 1) ^
((1 << leafParam.length) - 1);
syncState.gpnPatterns.set(
currentPath,
@@ -2221,7 +2215,7 @@ function processDeclarations(
function loadPath(
sessionContext: SessionContext,
- path: ParamPath,
+ path: Path,
depth?: number
): boolean {
depth = depth || (1 << path.length) - 1;
@@ -2231,15 +2225,15 @@ function loadPath(
// Trim trailing wildcards
let trimWildcard = path.length;
- while (trimWildcard && path[trimWildcard - 1] === "*") --trimWildcard;
+ while (trimWildcard && path.segments[trimWildcard - 1] === "*")
+ --trimWildcard;
- if (trimWildcard < path.length)
- path = path.slice(0, trimWildcard) as ParamPath;
+ if (trimWildcard < path.length) path = path.slice(0, trimWildcard);
for (let i = 0; i <= path.length; ++i) {
const d = i === path.length ? 99 : i;
for (const sup of sessionContext.deviceData.paths.find(
- path.slice(0, i) as ParamPath,
+ path.slice(0, i),
true,
false,
d
@@ -2260,16 +2254,17 @@ function loadPath(
}
function processInstances(
- sessionContext,
- parent,
- parameters,
- keys,
- minInstances,
- maxInstances,
- defer
+ sessionContext: SessionContext,
+ parent: Path,
+ parameters: Path[],
+ keys: {},
+ minInstances: number,
+ maxInstances: number,
+ defer: boolean
): void {
+ parent = sessionContext.deviceData.paths.add(parent);
let instancesToCreate, instancesToDelete;
- if (parent[0] === "Downloads") {
+ if (parent.segments[0] === "Downloads") {
if (parent.length !== 1) return;
instancesToDelete = sessionContext.syncState.downloadsToDelete;
instancesToCreate = sessionContext.syncState.downloadsToCreate;
@@ -2319,7 +2314,11 @@ function processInstances(
}
}
-function clear(sessionContext, toClear, callback): void {
+function clear(
+ sessionContext: SessionContext,
+ toClear: Clear[],
+ callback
+): void {
if (!toClear || !toClear.length) return void callback();
const MAX_DEPTH = +config.get("MAX_DEPTH", sessionContext.deviceId);
@@ -2352,13 +2351,19 @@ function clear(sessionContext, toClear, callback): void {
});
}
-export function rpcResponse(sessionContext, id, rpcRes, callback): void {
+export function rpcResponse(
+ sessionContext: SessionContext,
+ id: string,
+ _rpcRes: CpeResponse,
+ callback
+): void {
if (id !== generateRpcId(sessionContext))
return void callback(new Error("Request ID not recognized"));
++sessionContext.rpcCount;
- const rpcReq = sessionContext.rpcRequest;
+ const rpcRes = _rpcRes as CpeGetResponse & CpeSetResponse;
+ const rpcReq = sessionContext.rpcRequest as GetAcsRequest & SetAcsRequest;
if (!rpcReq.next) {
sessionContext.rpcRequest = null;
@@ -2374,32 +2379,34 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
if (!parameterNames.length) {
sessionContext.rpcRequest = null;
} else {
- sessionContext.rpcRequest = {
+ const r: GetAcsRequest = {
name: "GetParameterValues",
parameterNames: parameterNames,
next: "setInstanceKeys",
instanceValues: instanceValues
};
+ sessionContext.rpcRequest = r;
}
} else if (rpcReq.next === "setInstanceKeys") {
- const parameterList = [];
+ const parameterList: [string, string | number | boolean, string][] = [];
for (const p of rpcRes.parameterList) {
if (p[1] !== rpcReq.instanceValues[p[0]]) {
- parameterList.push(
- [p[0]].concat(
- device.sanitizeParameterValue([rpcReq.instanceValues[p[0]], p[2]])
- )
- );
+ const v = device.sanitizeParameterValue([
+ rpcReq.instanceValues[p[0]],
+ p[2]
+ ]);
+ parameterList.push([p[0], v[0], v[1]]);
}
}
if (!parameterList.length) {
sessionContext.rpcRequest = null;
} else {
- sessionContext.rpcRequest = {
+ const r: SetAcsRequest = {
name: "SetParameterValues",
parameterList: parameterList
};
+ sessionContext.rpcRequest = r;
}
}
@@ -2410,7 +2417,11 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
sessionContext.deviceData.timestamps.revision = revision;
sessionContext.deviceData.attributes.revision = revision;
- let toClear, root, missing, params;
+ let toClear,
+ root: Path,
+ missing,
+ params: [Path, number, Attributes?][],
+ wildcardPath: Path;
switch (rpcRes.name) {
case "GetParameterValuesResponse":
@@ -2423,7 +2434,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
for (const p of rpcRes.parameterList) {
toClear = device.set(
sessionContext.deviceData,
- common.parsePath(p[0]),
+ Path.parse(p[0]),
timestamp,
{ object: [timestamp, 0], value: [timestamp, p.slice(1)] },
toClear
@@ -2439,10 +2450,11 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
}
if (rpcReq.parameterPath.endsWith("."))
- root = common.parsePath(rpcReq.parameterPath.slice(0, -1));
- else root = common.parsePath(rpcReq.parameterPath);
+ root = Path.parse(rpcReq.parameterPath.slice(0, -1));
+ else root = Path.parse(rpcReq.parameterPath);
- params = [[root.concat("*"), timestamp]];
+ wildcardPath = Path.parse("*");
+ params = [[root.concat(wildcardPath), timestamp]];
// Some clients don't report all ancestors explicitly
missing = {};
@@ -2454,8 +2466,9 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
if (p[0].endsWith(".")) {
missing[p[0].slice(0, -1)] |= 1;
- const path = common.parsePath(p[0].slice(0, -1));
- if (!rpcReq.nextLevel) params.push([path.concat("*"), timestamp]);
+ const path = Path.parse(p[0].slice(0, -1));
+ if (!rpcReq.nextLevel)
+ params.push([path.concat(wildcardPath), timestamp]);
params.push([
path,
@@ -2465,7 +2478,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
} else {
missing[p[0]] |= 1;
params.push([
- common.parsePath(p[0]),
+ Path.parse(p[0]),
timestamp,
{ object: [timestamp, 0], writable: [timestamp, p[1] ? 1 : 0] }
]);
@@ -2475,13 +2488,13 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
for (const [k, v] of Object.entries(missing)) {
if (v === 0) {
// TODO consider showing a warning
- const path = common.parsePath(k);
+ const path = Path.parse(k);
params.push([
path,
timestamp,
{ object: [timestamp, 1], writable: [timestamp, 0] }
]);
- params.push([path.concat("*"), timestamp]);
+ params.push([path.concat(wildcardPath), timestamp]);
}
}
@@ -2492,8 +2505,8 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
params.sort((a, b) => {
let al = a[0].length;
let bl = b[0].length;
- if (b[0][bl - 1] === "*") bl *= -1;
- if (a[0][al - 1] === "*") al *= -1;
+ if (b[0].segments[bl - 1] === "*") bl *= -1;
+ if (a[0].segments[al - 1] === "*") al *= -1;
return bl - al;
});
@@ -2517,7 +2530,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
"VirtualParameters",
"Downloads"
]) {
- const p = sessionContext.deviceData.paths.get([n]);
+ const p = sessionContext.deviceData.paths.get(Path.parse(n));
if (p && sessionContext.deviceData.attributes.has(p))
sessionContext.deviceData.timestamps.set(p, timestamp);
}
@@ -2546,7 +2559,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
for (const p of rpcReq.parameterList) {
toClear = device.set(
sessionContext.deviceData,
- common.parsePath(p[0]),
+ Path.parse(p[0]),
timestamp + 1,
{
object: [timestamp + 1, 0],
@@ -2561,7 +2574,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
case "AddObjectResponse":
toClear = device.set(
sessionContext.deviceData,
- common.parsePath(rpcReq.objectName + rpcRes.instanceNumber),
+ Path.parse(rpcReq.objectName + rpcRes.instanceNumber),
timestamp + 1,
{ object: [timestamp + 1, 1] },
toClear
@@ -2570,7 +2583,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
case "DeleteObjectResponse":
toClear = device.set(
sessionContext.deviceData,
- common.parsePath(rpcReq.objectName.slice(0, -1)),
+ Path.parse(rpcReq.objectName.slice(0, -1)),
timestamp + 1,
null,
toClear
@@ -2579,7 +2592,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
case "RebootResponse":
toClear = device.set(
sessionContext.deviceData,
- common.parsePath("Reboot"),
+ Path.parse("Reboot"),
timestamp + 1,
{ value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]] },
toClear
@@ -2588,7 +2601,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
case "FactoryResetResponse":
toClear = device.set(
sessionContext.deviceData,
- common.parsePath("FactoryReset"),
+ Path.parse("FactoryReset"),
timestamp + 1,
{ value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]] },
toClear
@@ -2597,7 +2610,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
case "DownloadResponse":
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", rpcReq.instance, "Download"] as ParamPath,
+ Path.parse(`Download.${rpcReq.instance}.Download`),
timestamp + 1,
{ value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]] },
toClear
@@ -2606,7 +2619,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
if (rpcRes.status === 0) {
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", rpcReq.instance, "LastDownload"] as ParamPath,
+ Path.parse(`Downloads.${rpcReq.instance}.LastDownload`),
timestamp + 1,
{
value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]]
@@ -2616,7 +2629,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", rpcReq.instance, "LastFileType"] as ParamPath,
+ Path.parse(`Downloads.${rpcReq.instance}.LastFileType`),
timestamp + 1,
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
toClear
@@ -2624,7 +2637,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", rpcReq.instance, "LastFileName"] as ParamPath,
+ Path.parse(`Downloads.${rpcReq.instance}.LastFileName`),
timestamp + 1,
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
toClear
@@ -2632,7 +2645,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", rpcReq.instance, "LastTargetFileName"] as ParamPath,
+ Path.parse(`Downloads.${rpcReq.instance}.LastTargetFileName`),
timestamp + 1,
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
toClear
@@ -2640,7 +2653,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", rpcReq.instance, "StartTime"] as ParamPath,
+ Path.parse(`Downloads.${rpcReq.instance}.StartTime`),
timestamp + 1,
{ value: [timestamp + 1, [+rpcRes.startTime, "xsd:dateTime"]] },
toClear
@@ -2648,7 +2661,7 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
toClear = device.set(
sessionContext.deviceData,
- ["Downloads", rpcReq.instance, "CompleteTime"] as ParamPath,
+ Path.parse(`Downloads.${rpcReq.instance}.CompleteTime`),
timestamp + 1,
{ value: [timestamp + 1, [+rpcRes.completeTime, "xsd:dateTime"]] },
toClear
@@ -2686,49 +2699,46 @@ export function rpcResponse(sessionContext, id, rpcRes, callback): void {
}
export function rpcFault(
- sessionContext,
- id,
- faultResponse,
+ sessionContext: SessionContext,
+ id: string,
+ faultResponse: CpeFault,
callback: (err: Error, fault?: Fault) => void
): void {
- const rpcReq = sessionContext.rpcRequest;
+ const rpcReq = sessionContext.rpcRequest as GetAcsRequest & SetAcsRequest;
delete sessionContext.syncState;
delete sessionContext.rpcRequest;
// Recover from invalid parameter name faults
if (faultResponse.detail.faultCode === "9005") {
const timestamp = sessionContext.timestamp + sessionContext.iteration;
- let toClear;
+ let toClear: Clear[];
if (rpcReq.name === "GetParameterNames") {
if (rpcReq.parameterPath) {
toClear = [
- [rpcReq.parameterPath.replace(/\.$/, "").split("."), timestamp]
+ [Path.parse(rpcReq.parameterPath.replace(/\.$/, "")), timestamp]
];
}
} else if (rpcReq.name === "GetParameterValues") {
- toClear = rpcReq.parameterNames.map(p => [
- p.replace(/\.$/, "").split("."),
- timestamp
- ]);
+ toClear = rpcReq.parameterNames.map(
+ p => [Path.parse(p.replace(/\.$/, "")), timestamp] as Clear
+ );
} else if (rpcReq.name === "SetParameterValues") {
- toClear = rpcReq.parameterList.map(p => [
- p[0].replace(/\.$/, "").split("."),
- timestamp
- ]);
+ toClear = rpcReq.parameterList.map(
+ p => [Path.parse(p[0].replace(/\.$/, "")), timestamp] as Clear
+ );
} else if (rpcReq.name === "AddObject") {
- toClear = [[rpcReq.objectName.replace(/\.$/, "").split("."), timestamp]];
+ toClear = [[Path.parse(rpcReq.objectName.replace(/\.$/, "")), timestamp]];
} else if (rpcReq.name === "DeleteObject") {
- toClear = [[rpcReq.objectName.replace(/\.$/, "").split("."), timestamp]];
+ toClear = [[Path.parse(rpcReq.objectName.replace(/\.$/, "")), timestamp]];
}
if (toClear) return void clear(sessionContext, toClear, callback);
}
- const fault = {
+ const fault: Fault = {
code: `cwmp.${faultResponse.detail.faultCode}`,
message: faultResponse.detail.faultString,
- detail: faultResponse.detail,
- timestamp: sessionContext.timestamp
+ detail: faultResponse.detail
};
callback(null, fault);
@@ -2738,11 +2748,11 @@ export function deserialize(sessionContextString, callback): void {
const sessionContext = JSON.parse(sessionContextString);
for (const decs of sessionContext.declarations)
- for (const d of decs) common.addPathMeta(d.path);
+ for (const d of decs) d.path = Path.parse(d.path);
const deviceData = initDeviceData();
for (const r of sessionContext.deviceData) {
- const path = deviceData.paths.add(r[0]);
+ const path = deviceData.paths.add(Path.parse(r[0]));
if (r[1]) deviceData.loaded.set(path, r[1]);
@@ -2761,31 +2771,34 @@ export function deserialize(sessionContextString, callback): void {
});
}
-export function serialize(sessionContext, callback): void {
+export function serialize(sessionContext: SessionContext, callback): void {
const deviceData = [];
for (const path of sessionContext.deviceData.paths.find(
- [],
+ Path.parse(""),
false,
false,
99
)) {
- const e = [path];
- e[1] = sessionContext.deviceData.loaded.get(path) || 0;
- e[2] = sessionContext.deviceData.trackers.get(path) || null;
- e[3] = sessionContext.deviceData.timestamps.getRevisions(path) || null;
- e[4] = sessionContext.deviceData.attributes.getRevisions(path) || null;
+ const e = [
+ path.toString(),
+ sessionContext.deviceData.loaded.get(path) || 0,
+ sessionContext.deviceData.trackers.get(path) || null,
+ sessionContext.deviceData.timestamps.getRevisions(path) || null,
+ sessionContext.deviceData.attributes.getRevisions(path) || null
+ ];
deviceData.push(e);
}
- sessionContext = Object.assign({}, sessionContext);
- sessionContext.deviceData = deviceData;
- delete sessionContext.syncState;
- delete sessionContext.toLoad;
- delete sessionContext.httpRequest;
- delete sessionContext.httpResponse;
+ const jsonSessionContext = Object.assign({}, sessionContext, {
+ deviceData: deviceData,
+ syncState: null,
+ toLoad: null,
+ httpRequest: null,
+ httpResponse: null
+ });
- const sessionContextString = JSON.stringify(sessionContext);
+ const sessionContextString = JSON.stringify(jsonSessionContext);
callback(null, sessionContextString);
}
diff --git a/lib/types.ts b/lib/types.ts
index 4c726cb..3323ee6 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -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 InstanceSet from "./instance-set";
import { IncomingMessage, ServerResponse } from "http";
-export interface ParamPath extends Array {
- wildcard: number;
- alias: number;
-}
-
export type Expression = string | number | boolean | null | any[];
export interface Fault {
code: string;
message: string;
- detail?: {
- name: string;
- message: string;
- stack?: string;
- };
+ detail?:
+ | FaultStruct
+ | {
+ name: string;
+ message: string;
+ stack?: string;
+ };
}
export interface SessionFault extends Fault {
- channel: string;
timestamp: number;
provisions: string[][];
retryNow?: boolean;
@@ -50,15 +47,15 @@ export interface AttributeValues {
export interface DeviceData {
paths: PathSet;
- timestamps: VersionedMap;
- attributes: VersionedMap;
- loaded: Map;
- trackers: Map;
+ timestamps: VersionedMap;
+ attributes: VersionedMap;
+ loaded: Map;
+ trackers: Map;
changes: Set;
}
export type VirtualParameterDeclaration = [
- ParamPath,
+ Path,
{ path?: number; object?: number; writable?: number; value?: number }?,
{
path?: [number, number];
@@ -70,22 +67,24 @@ export type VirtualParameterDeclaration = [
export interface SyncState {
refreshAttributes: {
- exist: Set;
- object: Set;
- writable: Set;
- value: Set;
+ exist: Set;
+ object: Set;
+ writable: Set;
+ value: Set;
};
- spv: Map;
- gpn: Set;
- gpnPatterns: Map;
- tags: Map;
+ spv: Map;
+ gpn: Set;
+ gpnPatterns: Map;
+ tags: Map;
virtualParameterDeclarations: VirtualParameterDeclaration[][];
- instancesToDelete: Map>;
- instancesToCreate: Map;
- downloadsToDelete: Set;
+ instancesToDelete: Map>;
+ instancesToCreate: Map;
+ downloadsToDelete: Set;
downloadsToCreate: InstanceSet;
- downloadsValues: Map;
- downloadsDownload: Map;
+ downloadsValues: Map;
+ downloadsDownload: Map;
+ reboot: number;
+ factoryReset: number;
}
export interface SessionContext {
@@ -97,13 +96,19 @@ export interface SessionContext {
timeout: number;
provisions: any[];
channels: { [channel: string]: number };
- virtualParameters: any[];
+ virtualParameters: [
+ string,
+ AttributeTimestamps,
+ AttributeValues,
+ AttributeTimestamps,
+ AttributeValues
+ ][][];
revisions: number[];
rpcCount: number;
iteration: number;
cycle: number;
extensionsCache: any;
- declarations: any[];
+ declarations: Declaration[][];
faults?: { [channel: string]: SessionFault };
retries?: { [channel: string]: number };
cacheSnapshot?: string;
@@ -112,7 +117,7 @@ export interface SessionContext {
httpRequest?: IncomingMessage;
faultsTouched?: { [channel: string]: boolean };
presetCycles?: number;
- toLoad?: Map;
+ toLoad?: Map;
new?: boolean;
debug?: boolean;
tasks?: Task[];
@@ -154,6 +159,7 @@ export interface Operation {
export interface AcsRequest {
name: string;
+ next?: string;
}
export interface GetAcsRequest extends AcsRequest {
@@ -162,6 +168,7 @@ export interface GetAcsRequest extends AcsRequest {
parameterNames?: string[];
parameterPath?: string;
nextLevel?: boolean;
+ instanceValues?: { [name: string]: string };
}
export interface SetAcsRequest extends AcsRequest {
@@ -175,7 +182,6 @@ export interface SetAcsRequest extends AcsRequest {
parameterList?: [string, string | number | boolean, string][];
instanceValues?: { [param: string]: string | number | boolean };
objectName?: string;
- next?: string;
DATETIME_MILLISECONDS?: boolean;
BOOLEAN_LITERAL?: boolean;
commandKey?: string;
@@ -266,7 +272,7 @@ export interface QueryOptions {
}
export interface Declaration {
- path: ParamPath;
+ path: Path;
pathGet: number;
pathSet?: [number, number];
attrGet?: { object?: number; writable?: number; value?: number };
@@ -279,7 +285,7 @@ export interface Declaration {
}
export type Clear = [
- ParamPath,
+ Path,
number,
{ object?: number; writable?: number; value?: number }?,
number?
diff --git a/test/common.ts b/test/common.ts
index 5af7c57..c08978d 100644
--- a/test/common.ts
+++ b/test/common.ts
@@ -1,6 +1,5 @@
import ava from "ava";
import * as common from "../lib/common";
-import { ParamPath } from "../lib/types";
ava("typeOf", t => {
const cases = [
@@ -63,73 +62,6 @@ ava("generateDeviceId", t => {
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 => {
t.is(
common.escapeRegExp("\\ ^ $ * + ? . ( ) | { } [ ]"),
diff --git a/test/device.ts b/test/device.ts
new file mode 100644
index 0000000..59e603c
--- /dev/null
+++ b/test/device.ts
@@ -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);
+ }
+});