refactor: replace array-based expressions with typed class hierarchy

Replace the old Lisp-like array expression representation
(["AND", ["PARAM", ...], ...]) with a class hierarchy rooted in
an abstract Expression class (Literal, Parameter, Binary, Unary,
FunctionCall, Conditional). This gives compile-time type safety
to expression construction, evaluation, and pattern matching.

Key changes:
- New lib/common/expression.ts defines the class hierarchy with
  map/evaluate/toString methods
- Rewrite parser as hand-rolled recursive descent, removing the
  parsimmon dependency
- Extend Path with a colon separator to unify parameter identity
  and attributes into a single address space (e.g.
  "Device.WiFi.SSID.1.Name:writable"). The colon field tracks
  how many trailing segments are attribute qualifiers. PathSet
  rewritten with separate param/attr segment indexes
- Flatten device attribute representation in the UI to
  colon-delimited keys mirroring the new path syntax
This commit is contained in:
Zaid Humaimidi committed 2026-03-12 14:58:20 -07:00
1 parent 75908e624f
commit 6f3ce6f7c8
71 files changed
+4045 -3449

No files matched your search

+147 -184
View File
@@ -25,7 +25,6 @@ import {
TransferCompleteRequest,
Operation,
ScriptResult,
Expression,
GetParameterValues,
GetParameterAttributes,
GetParameterNames,
@@ -42,6 +41,7 @@ import {
import { getRequestOrigin } from "./forwarded.ts";
import * as logger from "./logger.ts";
import { encodeTag } from "./util.ts";
import Expression, { Value } from "./common/expression.ts";
const VALID_PARAM_TYPES = new Set([
"xsd:int",
@@ -102,29 +102,25 @@ function generateRpcId(sessionContext: SessionContext): string {
export function configContextCallback(
sessionContext: SessionContext,
exp: Expression,
): Expression {
if (!Array.isArray(exp)) return exp;
if (exp[0] === "PARAM" && typeof exp[1] === "string") {
let name = exp[1];
if (name === "id") name = "DeviceID.ID";
else if (name === "serialNumber") name = "DeviceID.SerialNumber";
else if (name === "productClass") name = "DeviceID.ProductClass";
else if (name === "oui") name = "DeviceID.OUI";
else if (name === "remoteAddress")
return getRequestOrigin(sessionContext.httpRequest).remoteAddress;
): Expression.Literal {
if (exp instanceof Expression.Literal) return exp;
else if (exp instanceof Expression.FunctionCall) {
if (exp.name === "NOW")
return new Expression.Literal(sessionContext.timestamp);
if (exp.name === "REMOTE_ADDRESS")
return new Expression.Literal(
getRequestOrigin(sessionContext.httpRequest).remoteAddress,
);
} else if (exp instanceof Expression.Parameter) {
const deviceData = sessionContext.deviceData;
const paths = deviceData.paths;
const path = paths.get(Path.parse(name));
const path = paths.get(exp.path.toString());
if (path) {
const attrs = deviceData.attributes.get(path, 1);
if (attrs?.value?.[1]) return attrs.value[1][0];
if (attrs?.value?.[1]) return new Expression.Literal(attrs.value[1][0]);
}
} else if (exp[0] === "FUNC") {
if (exp[1] === "REMOTE_ADDRESS")
return getRequestOrigin(sessionContext.httpRequest).remoteAddress;
}
return exp;
return new Expression.Literal(null);
}
export async function inform(
@@ -132,9 +128,9 @@ export async function inform(
rpcReq: InformRequest,
): Promise<AcsResponse> {
const timestamp = sessionContext.timestamp + sessionContext.iteration + 1;
const params: [Path, number, Attributes][] = [
const params: [string, number, Attributes][] = [
[
Path.parse("DeviceID.Manufacturer"),
"DeviceID.Manufacturer",
timestamp,
{
object: [timestamp, 0],
@@ -144,7 +140,7 @@ export async function inform(
],
[
Path.parse("DeviceID.OUI"),
"DeviceID.OUI",
timestamp,
{
object: [timestamp, 0],
@@ -154,7 +150,7 @@ export async function inform(
],
[
Path.parse("DeviceID.ProductClass"),
"DeviceID.ProductClass",
timestamp,
{
object: [timestamp, 0],
@@ -164,7 +160,7 @@ export async function inform(
],
[
Path.parse("DeviceID.SerialNumber"),
"DeviceID.SerialNumber",
timestamp,
{
object: [timestamp, 0],
@@ -177,7 +173,7 @@ export async function inform(
for (const p of rpcReq.parameterList) {
const path = p[0];
params.push([
path,
path.toString(),
timestamp,
{
object: [timestamp, 0],
@@ -187,7 +183,7 @@ export async function inform(
}
params.push([
Path.parse("Events.Inform"),
"Events.Inform",
timestamp,
{
object: [timestamp, 0],
@@ -198,7 +194,7 @@ export async function inform(
for (const e of rpcReq.event) {
params.push([
Path.parse(`Events.${encodeTag(e.replace(/\s+/g, "_"))}`),
`Events.${encodeTag(e.replace(/\s+/g, "_"))}`,
timestamp,
{
object: [timestamp, 0],
@@ -210,7 +206,7 @@ export async function inform(
if (sessionContext.new) {
params.push([
Path.parse("DeviceID.ID"),
"DeviceID.ID",
timestamp,
{
object: [timestamp, 0],
@@ -219,7 +215,7 @@ export async function inform(
},
]);
params.push([
Path.parse("Events.Registered"),
"Events.Registered",
timestamp,
{
object: [timestamp, 0],
@@ -235,7 +231,7 @@ export async function inform(
let toClear = null;
for (const p of params) {
// Don't need to clear wildcards for Events
if (p[0].segments[0] === "Events") {
if (p[0].startsWith("Events.")) {
device.set(sessionContext.deviceData, p[0], p[1], p[2]);
} else {
toClear = device.set(
@@ -301,69 +297,49 @@ export async function transferComplete(
let toClear = null;
const timestamp = sessionContext.timestamp + sessionContext.iteration + 1;
let p;
p = sessionContext.deviceData.paths.add(
Path.parse(`Downloads.${instance}.LastDownload`),
);
toClear = device.set(
sessionContext.deviceData,
p,
`Downloads.${instance}.LastDownload`,
timestamp,
{ value: [timestamp, [operation.timestamp, "xsd:dateTime"]] },
toClear,
);
p = sessionContext.deviceData.paths.add(
Path.parse(`Downloads.${instance}.LastFileType`),
);
toClear = device.set(
sessionContext.deviceData,
p,
`Downloads.${instance}.LastFileType`,
timestamp,
{ value: [timestamp, [operation.args.fileType, "xsd:string"]] },
toClear,
);
p = sessionContext.deviceData.paths.add(
Path.parse(`Downloads.${instance}.LastFileName`),
);
toClear = device.set(
sessionContext.deviceData,
p,
`Downloads.${instance}.LastFileName`,
timestamp,
{ value: [timestamp, [operation.args.fileName, "xsd:string"]] },
toClear,
);
p = sessionContext.deviceData.paths.add(
Path.parse(`Downloads.${instance}.LastTargetFileName`),
);
toClear = device.set(
sessionContext.deviceData,
p,
`Downloads.${instance}.LastTargetFileName`,
timestamp,
{ value: [timestamp, [operation.args.targetFileName, "xsd:string"]] },
toClear,
);
p = sessionContext.deviceData.paths.add(
Path.parse(`Downloads.${instance}.StartTime`),
);
toClear = device.set(
sessionContext.deviceData,
p,
`Downloads.${instance}.StartTime`,
timestamp,
{ value: [timestamp, [+rpcReq.startTime, "xsd:dateTime"]] },
toClear,
);
p = sessionContext.deviceData.paths.add(
Path.parse(`Downloads.${instance}.CompleteTime`),
);
toClear = device.set(
sessionContext.deviceData,
p,
`Downloads.${instance}.CompleteTime`,
timestamp,
{ value: [timestamp, [+rpcReq.completeTime, "xsd:dateTime"]] },
toClear,
@@ -387,22 +363,22 @@ function revertDownloadParameters(
): void {
const timestamp = sessionContext.timestamp + sessionContext.iteration + 1;
let p;
p = sessionContext.deviceData.paths.add(
Path.parse(`Downloads.${instance}.LastDownload`),
const lastDownloadPath = sessionContext.deviceData.paths.add(
`Downloads.${instance}.LastDownload`,
);
const lastDownload = sessionContext.deviceData.attributes.get(p);
const lastDownload =
sessionContext.deviceData.attributes.get(lastDownloadPath);
p = sessionContext.deviceData.paths.add(
Path.parse(`Downloads.${instance}.Download`),
const toClear = device.set(
sessionContext.deviceData,
`Downloads.${instance}.Download`,
timestamp,
{
value: [timestamp, [lastDownload?.value[1]?.[0] || 0, "xsd:dateTime"]],
},
);
const toClear = device.set(sessionContext.deviceData, p, timestamp, {
value: [timestamp, [lastDownload?.value[1]?.[0] || 0, "xsd:dateTime"]],
});
if (toClear) {
for (const c of toClear)
device.clear(sessionContext.deviceData, c[0], c[1], c[2], c[3]);
@@ -426,11 +402,10 @@ export async function timeoutOperations(
throw new Error(`Unknown operation name ${operation.name}`);
const DOWNLOAD_TIMEOUT =
+localCache.getConfig(
localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.downloadTimeout",
{},
sessionContext.timestamp,
3600,
(e) => configContextCallback(sessionContext, e),
) * 1000;
@@ -443,11 +418,10 @@ export async function timeoutOperations(
commandKey: commandKey,
});
const SUCCESS_ON_TIMEOUT = +localCache.getConfig(
const SUCCESS_ON_TIMEOUT = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.downloadSuccessOnTimeout",
{},
sessionContext.timestamp,
false,
(e) => configContextCallback(sessionContext, e),
);
@@ -486,16 +460,15 @@ export async function timeoutOperations(
export function addProvisions(
sessionContext: SessionContext,
channel: string,
provisions: [string, ...Expression[]][],
provisions: [string, ...Value[]][],
): void {
// Multiply by two because every iteration is two
// phases: read and update
const MAX_ITERATIONS =
+localCache.getConfig(
localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.maxCommitIterations",
{},
sessionContext.timestamp,
32,
(e) => configContextCallback(sessionContext, e),
) * 2;
@@ -549,8 +522,7 @@ export function clearProvisions(sessionContext: SessionContext): void {
+localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.maxCommitIterations",
{},
sessionContext.timestamp,
32,
(e) => configContextCallback(sessionContext, e),
) * 2;
@@ -848,22 +820,20 @@ function runDeclarations(
(path.alias | path.wildcard) & 1 ||
path.segments[0] === "VirtualParameters"
) {
sessionContext.deviceData.paths.add(Path.parse("VirtualParameters"));
sessionContext.deviceData.paths.add("VirtualParameters");
if ((path.alias | path.wildcard) & 2) {
sessionContext.deviceData.paths.add(Path.parse("VirtualParameters.*"));
sessionContext.deviceData.paths.add("VirtualParameters.*");
for (const k of Object.keys(allVirtualParameters)) {
sessionContext.deviceData.paths.add(
Path.parse(`VirtualParameters.${k}`),
);
sessionContext.deviceData.paths.add(`VirtualParameters.${k}`);
}
}
}
if ((path.alias | path.wildcard) & 1 || path.segments[0] === "Reboot")
sessionContext.deviceData.paths.add(Path.parse("Reboot"));
sessionContext.deviceData.paths.add("Reboot");
if ((path.alias | path.wildcard) & 1 || path.segments[0] === "FactoryReset")
sessionContext.deviceData.paths.add(Path.parse("FactoryReset"));
sessionContext.deviceData.paths.add("FactoryReset");
if (path.alias) {
const aliasDecs = device.getAliasDeclarations(
@@ -871,12 +841,12 @@ function runDeclarations(
declaration.pathGet || 1,
);
for (const ad of aliasDecs) {
const p = sessionContext.deviceData.paths.add(ad.path);
const p = sessionContext.deviceData.paths.add(ad.path.toString());
allDeclareTimestamps.set(
p,
Math.max(ad.pathGet || 1, allDeclareTimestamps.get(p) || 0),
);
let attrTrackers;
let attrTrackers: string[];
if (ad.attrGet) {
attrTrackers = Object.keys(ad.attrGet);
mergeAttributeTimestamps(p, ad.attrGet);
@@ -884,7 +854,7 @@ function runDeclarations(
device.track(
sessionContext.deviceData,
p,
p.toString(),
"prerequisite",
attrTrackers,
);
@@ -900,14 +870,14 @@ function runDeclarations(
mergeAttributeTimestamps(u, declaration.attrGet);
}
} else {
path = sessionContext.deviceData.paths.add(path);
path = sessionContext.deviceData.paths.add(path.toString());
allDeclareTimestamps.set(
path,
Math.max(declaration.pathGet || 1, allDeclareTimestamps.get(path) || 0),
);
if (declaration.attrGet)
mergeAttributeTimestamps(path, declaration.attrGet);
device.track(sessionContext.deviceData, path, "prerequisite");
device.track(sessionContext.deviceData, path.toString(), "prerequisite");
}
if (declaration.attrSet) {
@@ -928,7 +898,7 @@ function runDeclarations(
}
if (declaration.pathSet != null) {
let minInstances, maxInstances;
let minInstances: number, maxInstances: number;
if (Array.isArray(declaration.pathSet)) {
minInstances = declaration.pathSet[0];
maxInstances = declaration.pathSet[1];
@@ -938,17 +908,19 @@ function runDeclarations(
let parent = path.slice(0, -1);
let keys;
if (Array.isArray(path.segments[path.length - 1])) {
let keys: Record<string, string>;
if (path.segments[path.length - 1] instanceof Expression) {
keys = {};
for (const [p, v] of path.segments[path.length - 1])
keys[p.toString()] = v;
for (const [p, v] of device.expressionToAlias(
path.segments[path.length - 1] as Expression,
))
keys[p.toString()] = v as string;
} else if (path.segments[path.length - 1] === "*") {
keys = {};
}
if (!parent.wildcard && !parent.alias) {
parent = sessionContext.deviceData.paths.add(parent);
parent = sessionContext.deviceData.paths.add(parent.toString());
if (!unpacked)
unpacked = device.unpack(sessionContext.deviceData, path);
@@ -1143,8 +1115,7 @@ export async function rpcRequest(
+localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.maxCommitIterations",
{},
sessionContext.timestamp,
32,
(e) => configContextCallback(sessionContext, e),
) * 2;
@@ -1184,7 +1155,7 @@ export async function rpcRequest(
);
const timestamp = sessionContext.timestamp + sessionContext.iteration;
let toClear;
let toClear: Clear[];
const allVirtualParameters = localCache.getVirtualParameters(
sessionContext.cacheSnapshot,
@@ -1198,7 +1169,7 @@ export async function rpcRequest(
if (!sessionContext.deviceData.attributes.has(declaration[0])) {
toClear = device.set(
sessionContext.deviceData,
declaration[0],
declaration[0].toString(),
timestamp,
{ object: [timestamp, 1], writable: [timestamp, 0] },
toClear,
@@ -1211,7 +1182,7 @@ export async function rpcRequest(
for (const k of Object.keys(allVirtualParameters)) {
toClear = device.set(
sessionContext.deviceData,
Path.parse(`VirtualParameters.${k}`),
`VirtualParameters.${k}`,
timestamp,
{
object: [timestamp, 0],
@@ -1221,7 +1192,7 @@ export async function rpcRequest(
}
toClear = device.set(
sessionContext.deviceData,
declaration[0],
declaration[0].toString(),
timestamp,
null,
toClear,
@@ -1235,7 +1206,7 @@ export async function rpcRequest(
if (!sessionContext.deviceData.attributes.has(declaration[0])) {
toClear = device.set(
sessionContext.deviceData,
declaration[0],
declaration[0].toString(),
timestamp,
{ object: [timestamp, 0] },
toClear,
@@ -1247,7 +1218,7 @@ export async function rpcRequest(
}
}
for (const p of sessionContext.deviceData.paths.find(
for (const p of sessionContext.deviceData.paths.findCompat(
declaration[0],
false,
true,
@@ -1290,7 +1261,7 @@ export async function rpcRequest(
return rpcRequest(sessionContext, null);
}
let toClear;
let toClear: Clear[];
const timestamp = sessionContext.timestamp + sessionContext.iteration + 1;
// Update tags
@@ -1299,7 +1270,7 @@ export async function rpcRequest(
if (v && !c) {
toClear = device.set(
sessionContext.deviceData,
p,
p.toString(),
timestamp,
{
object: [timestamp, 0],
@@ -1311,7 +1282,7 @@ export async function rpcRequest(
} else if (c && !v) {
toClear = device.set(
sessionContext.deviceData,
p,
p.toString(),
timestamp,
null,
toClear,
@@ -1320,11 +1291,11 @@ export async function rpcRequest(
}
// Downloads
let index;
let index: number;
for (const instance of sessionContext.syncState.downloadsToCreate) {
if (index == null) {
index = 0;
for (const p of sessionContext.deviceData.paths.find(
for (const p of sessionContext.deviceData.paths.findCompat(
Path.parse("Downloads.*"),
false,
true,
@@ -1341,7 +1312,7 @@ export async function rpcRequest(
toClear = device.set(
sessionContext.deviceData,
Path.parse("Downloads"),
"Downloads",
timestamp,
{ object: [timestamp, 1], writable: [timestamp, 1] },
toClear,
@@ -1349,7 +1320,7 @@ export async function rpcRequest(
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${index}`),
`Downloads.${index}`,
timestamp,
{ object: [timestamp, 1], writable: [timestamp, 1] },
toClear,
@@ -1383,7 +1354,7 @@ export async function rpcRequest(
for (const [k, v] of Object.entries(params)) {
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${index}.${k}`),
`Downloads.${index}.${k}`,
timestamp,
{
object: [timestamp, 0],
@@ -1399,7 +1370,7 @@ export async function rpcRequest(
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${index}.*`),
`Downloads.${index}.*`,
timestamp,
null,
toClear,
@@ -1411,7 +1382,7 @@ export async function rpcRequest(
for (const instance of sessionContext.syncState.downloadsToDelete) {
toClear = device.set(
sessionContext.deviceData,
instance,
instance.toString(),
timestamp,
null,
toClear,
@@ -1432,7 +1403,7 @@ export async function rpcRequest(
if (val[0] !== attrs.value[1][0]) {
toClear = device.set(
sessionContext.deviceData,
p,
p.toString(),
timestamp,
{ value: [timestamp, val] },
toClear,
@@ -1504,7 +1475,7 @@ export async function rpcRequest(
toClear = device.set(
sessionContext.deviceData,
Path.parse(`VirtualParameters.${vparams[i][0]}`),
`VirtualParameters.${vparams[i][0]}`,
timestamp,
vpu,
toClear,
@@ -1527,7 +1498,7 @@ function generateGetRpcRequest(
for (const path of syncState.refreshAttributes.exist) {
let found = false;
for (const p of sessionContext.deviceData.paths.find(
for (const p of sessionContext.deviceData.paths.findCompat(
path,
false,
true,
@@ -1547,7 +1518,9 @@ function generateGetRpcRequest(
}
if (!found) {
const p = sessionContext.deviceData.paths.add(path.slice(0, -1));
const p = sessionContext.deviceData.paths.add(
path.slice(0, -1).toString(),
);
syncState.gpn.add(p);
const f = 1 << p.length;
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
@@ -1557,7 +1530,7 @@ function generateGetRpcRequest(
for (const path of syncState.refreshAttributes.object) {
let found = false;
for (const p of sessionContext.deviceData.paths.find(
for (const p of sessionContext.deviceData.paths.findCompat(
path,
false,
true,
@@ -1577,7 +1550,9 @@ function generateGetRpcRequest(
}
if (!found) {
const p = sessionContext.deviceData.paths.add(path.slice(0, -1));
const p = sessionContext.deviceData.paths.add(
path.slice(0, -1).toString(),
);
syncState.gpn.add(p);
const f = 1 << p.length;
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
@@ -1586,7 +1561,7 @@ function generateGetRpcRequest(
syncState.refreshAttributes.object.clear();
for (const path of syncState.refreshAttributes.writable) {
const p = sessionContext.deviceData.paths.add(path.slice(0, -1));
const p = sessionContext.deviceData.paths.add(path.slice(0, -1).toString());
syncState.gpn.add(p);
const f = 1 << p.length;
syncState.gpnPatterns.set(p, f | syncState.gpnPatterns.get(p));
@@ -1597,8 +1572,7 @@ function generateGetRpcRequest(
const GPN_NEXT_LEVEL = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.gpnNextLevel",
{},
sessionContext.timestamp,
0,
(e) => configContextCallback(sessionContext, e),
) as number;
@@ -1609,11 +1583,10 @@ function generateGetRpcRequest(
// Skip root GPN workaround
if (path && !path.length) {
const SKIP_ROOT_GPN = !!localCache.getConfig(
const SKIP_ROOT_GPN = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.skipRootGpn",
{},
sessionContext.timestamp,
false,
(e) => configContextCallback(sessionContext, e),
);
@@ -1634,7 +1607,7 @@ function generateGetRpcRequest(
let est = 0;
if (path.length >= GPN_NEXT_LEVEL) {
const patterns: [Path, number][] = [[path, 0]];
for (const p of sessionContext.deviceData.paths.find(
for (const p of sessionContext.deviceData.paths.findCompat(
path,
true,
false,
@@ -1651,7 +1624,7 @@ function generateGetRpcRequest(
syncState.gpn.delete(path);
} else {
nextLevel = false;
for (const p of sessionContext.deviceData.paths.find(
for (const p of sessionContext.deviceData.paths.findCompat(
path,
false,
true,
@@ -1672,8 +1645,7 @@ function generateGetRpcRequest(
const GPV_BATCH_SIZE = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.gpvBatchSize",
{},
sessionContext.timestamp,
32,
(e) => configContextCallback(sessionContext, e),
) as number;
@@ -1703,8 +1675,7 @@ function generateGetRpcRequest(
const GPV_BATCH_SIZE = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.gpvBatchSize",
{},
sessionContext.timestamp,
32,
(e) => configContextCallback(sessionContext, e),
) as number;
@@ -1764,11 +1735,10 @@ function generateSetRpcRequest(
const deviceData = sessionContext.deviceData;
const SKIP_WRITABLE_CHECK = !!localCache.getConfig(
const SKIP_WRITABLE_CHECK = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.skipWritableCheck",
{},
sessionContext.timestamp,
false,
(e) => configContextCallback(sessionContext, e),
);
@@ -1810,24 +1780,21 @@ function generateSetRpcRequest(
const GPV_BATCH_SIZE = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.gpvBatchSize",
{},
sessionContext.timestamp,
32,
(e) => configContextCallback(sessionContext, e),
) as number;
const DATETIME_MILLISECONDS = !!localCache.getConfig(
const DATETIME_MILLISECONDS = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.datetimeMilliseconds",
{},
sessionContext.timestamp,
true,
(e) => configContextCallback(sessionContext, e),
);
const BOOLEAN_LITERAL = !!localCache.getConfig(
const BOOLEAN_LITERAL = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.booleanLiteral",
{},
sessionContext.timestamp,
true,
(e) => configContextCallback(sessionContext, e),
);
@@ -1904,15 +1871,13 @@ function generateSetRpcRequest(
const t2 = attrs?.value?.[1]?.[0] as number;
if (!(t <= t2)) {
const fileTypeAttrs = deviceData.attributes.get(
deviceData.paths.get(p.slice(0, -1).concat(Path.parse("FileType"))),
deviceData.paths.get(p.slice(0, -1).toString() + ".FileType"),
);
const fileNameAttrs = deviceData.attributes.get(
deviceData.paths.get(p.slice(0, -1).concat(Path.parse("FileName"))),
deviceData.paths.get(p.slice(0, -1).toString() + ".FileName"),
);
const targetFileNameAttrs = deviceData.attributes.get(
deviceData.paths.get(
p.slice(0, -1).concat(Path.parse("TargetFileName")),
),
deviceData.paths.get(p.slice(0, -1).toString() + ".TargetFileName"),
);
return {
@@ -1928,7 +1893,7 @@ function generateSetRpcRequest(
// Reboot
if (syncState.reboot > 0 && syncState.reboot <= sessionContext.timestamp) {
const p = sessionContext.deviceData.paths.get(Path.parse("Reboot"));
const p = sessionContext.deviceData.paths.get("Reboot");
const attrs = p ? sessionContext.deviceData.attributes.get(p) : null;
const t = attrs?.value?.[1][0] as number;
if (!(t >= syncState.reboot)) {
@@ -1942,7 +1907,7 @@ function generateSetRpcRequest(
syncState.factoryReset > 0 &&
syncState.factoryReset <= sessionContext.timestamp
) {
const p = sessionContext.deviceData.paths.get(Path.parse("FactoryReset"));
const p = sessionContext.deviceData.paths.get("FactoryReset");
const attrs = p ? sessionContext.deviceData.attributes.get(p) : null;
const t = attrs?.value?.[1][0] as number;
if (!(t >= syncState.factoryReset)) {
@@ -2064,8 +2029,7 @@ function processDeclarations(
const deviceData = sessionContext.deviceData;
const syncState = sessionContext.syncState;
const root = sessionContext.deviceData.paths.add(Path.parse(""));
const paths = deviceData.paths.find(root, false, true, 99);
const paths = deviceData.paths.findCompat(Path.root, false, true, 99);
paths.sort((a, b): number =>
a.wildcard === b.wildcard ? a.length - b.length : a.wildcard - b.wildcard,
);
@@ -2358,7 +2322,7 @@ function processDeclarations(
allDeclareAttributeTimestamps.size ||
allDeclareAttributeValues.size
)
func(root, 1, 0, paths);
func(Path.root, 1, 0, [Path.root, ...paths]);
return virtualParameterDeclarations;
}
@@ -2372,7 +2336,7 @@ function processInstances(
maxInstances: number,
defer: boolean,
): void {
parent = sessionContext.deviceData.paths.add(parent);
parent = sessionContext.deviceData.paths.add(parent.toString());
let instancesToCreate: InstanceSet, instancesToDelete: Set<Path>;
if (parent.segments[0] === "Downloads") {
if (parent.length !== 1) return;
@@ -2491,19 +2455,17 @@ export async function rpcResponse(
if (!parameterList.length) {
sessionContext.rpcRequest = null;
} else {
const DATETIME_MILLISECONDS = !!localCache.getConfig(
const DATETIME_MILLISECONDS = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.datetimeMilliseconds",
{},
sessionContext.timestamp,
true,
(e) => configContextCallback(sessionContext, e),
);
const BOOLEAN_LITERAL = !!localCache.getConfig(
const BOOLEAN_LITERAL = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.booleanLiteral",
{},
sessionContext.timestamp,
true,
(e) => configContextCallback(sessionContext, e),
);
@@ -2543,7 +2505,7 @@ export async function rpcResponse(
}
toClear = device.set(
sessionContext.deviceData,
path,
path.toString(),
timestamp,
{
object: [timestamp, 0],
@@ -2562,7 +2524,7 @@ export async function rpcResponse(
});
toClear = device.set(
sessionContext.deviceData,
Path.parse(p),
p,
timestamp,
{
object: [timestamp, 0],
@@ -2589,7 +2551,7 @@ export async function rpcResponse(
}
toClear = device.set(
sessionContext.deviceData,
path,
path.toString(),
timestamp,
{
notification: [timestamp, notification],
@@ -2608,7 +2570,7 @@ export async function rpcResponse(
});
toClear = device.set(
sessionContext.deviceData,
Path.parse(p),
p,
timestamp,
{
notification: [timestamp, 0],
@@ -2623,7 +2585,8 @@ export async function rpcResponse(
return invalidResponse("Response name does not match request name");
let root: Path;
if (rpcReq.parameterPath.endsWith("."))
if (!rpcReq.parameterPath) root = Path.root;
else if (rpcReq.parameterPath.endsWith("."))
root = Path.parse(rpcReq.parameterPath.slice(0, -1));
else root = Path.parse(rpcReq.parameterPath);
@@ -2667,7 +2630,7 @@ export async function rpcResponse(
"VirtualParameters",
"Downloads",
]) {
const p = sessionContext.deviceData.paths.get(Path.parse(n));
const p = sessionContext.deviceData.paths.get(n);
if (p && sessionContext.deviceData.attributes.has(p))
sessionContext.deviceData.timestamps.set(p, timestamp);
}
@@ -2693,7 +2656,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
path,
path.toString(),
timestamp,
{
object: [timestamp, object ? 1 : 0],
@@ -2706,7 +2669,7 @@ export async function rpcResponse(
for (const path of wildcardParams) {
toClear = device.set(
sessionContext.deviceData,
path,
path.toString(),
timestamp,
null,
toClear,
@@ -2719,7 +2682,7 @@ export async function rpcResponse(
for (const p of rpcReq.parameterList) {
toClear = device.set(
sessionContext.deviceData,
Path.parse(p[0]),
p[0],
timestamp + 1,
{
object: [timestamp + 1, 0],
@@ -2755,7 +2718,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse(p[0]),
p[0],
timestamp + 1,
attrs,
toClear,
@@ -2767,7 +2730,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse(rpcReq.objectName + rpcRes.instanceNumber),
rpcReq.objectName + rpcRes.instanceNumber,
timestamp + 1,
{ object: [timestamp + 1, 1] },
toClear,
@@ -2778,7 +2741,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse(rpcReq.objectName.slice(0, -1)),
rpcReq.objectName.slice(0, -1),
timestamp + 1,
null,
toClear,
@@ -2789,7 +2752,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse("Reboot"),
"Reboot",
timestamp + 1,
{ value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]] },
toClear,
@@ -2800,7 +2763,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse("FactoryReset"),
"FactoryReset",
timestamp + 1,
{ value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]] },
toClear,
@@ -2811,7 +2774,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${rpcReq.instance}.Download`),
`Downloads.${rpcReq.instance}.Download`,
timestamp + 1,
{ value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]] },
toClear,
@@ -2820,7 +2783,7 @@ export async function rpcResponse(
if (rpcRes.status === 0) {
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${rpcReq.instance}.LastDownload`),
`Downloads.${rpcReq.instance}.LastDownload`,
timestamp + 1,
{
value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]],
@@ -2830,7 +2793,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${rpcReq.instance}.LastFileType`),
`Downloads.${rpcReq.instance}.LastFileType`,
timestamp + 1,
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
toClear,
@@ -2838,7 +2801,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${rpcReq.instance}.LastFileName`),
`Downloads.${rpcReq.instance}.LastFileName`,
timestamp + 1,
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
toClear,
@@ -2846,7 +2809,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${rpcReq.instance}.LastTargetFileName`),
`Downloads.${rpcReq.instance}.LastTargetFileName`,
timestamp + 1,
{ value: [timestamp + 1, [rpcReq.fileType, "xsd:string"]] },
toClear,
@@ -2854,7 +2817,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${rpcReq.instance}.StartTime`),
`Downloads.${rpcReq.instance}.StartTime`,
timestamp + 1,
{ value: [timestamp + 1, [+rpcRes.startTime, "xsd:dateTime"]] },
toClear,
@@ -2862,7 +2825,7 @@ export async function rpcResponse(
toClear = device.set(
sessionContext.deviceData,
Path.parse(`Downloads.${rpcReq.instance}.CompleteTime`),
`Downloads.${rpcReq.instance}.CompleteTime`,
timestamp + 1,
{ value: [timestamp + 1, [+rpcRes.completeTime, "xsd:dateTime"]] },
toClear,
@@ -2977,7 +2940,7 @@ export async function deserialize(
const deviceData = initDeviceData();
for (const r of sessionContext.deviceData as unknown as any[]) {
const path = deviceData.paths.add(Path.parse(r[0]));
const path = deviceData.paths.add(r[0]);
if (r[1]) deviceData.trackers.set(path, r[1]);
@@ -2999,8 +2962,8 @@ export async function serialize(
): Promise<string> {
const deviceData = [];
for (const path of sessionContext.deviceData.paths.find(
Path.parse(""),
for (const path of sessionContext.deviceData.paths.findCompat(
Path.root,
false,
false,
99,