Compare commits

...
5 Commits
10 changed files with 521 additions and 60 deletions

No files matched your search

+12
View File
@@ -241,6 +241,18 @@ function sanitizeTask(task): void {
) )
throw new Error("Invalid 'targetFileName' property"); throw new Error("Invalid 'targetFileName' property");
break; break;
case "provisions":
if (
!Array.isArray(task.provisions) ||
!task.provisions.every(arr =>
arr.every(
s => s == null || ["boolean", "number", "string"].includes(typeof s)
)
)
)
throw new Error("Invalid 'provisions' property");
break;
} }
return task; return task;
+7
View File
@@ -686,6 +686,13 @@ async function nextRpc(sessionContext: SessionContext): Promise<void> {
["instances", task.objectName, 0] ["instances", task.objectName, 0]
]); ]);
break; break;
case "provisions":
session.addProvisions(
sessionContext,
`task_${task._id}`,
task.provisions
);
break;
default: default:
throw new Error("Task name not recognized"); throw new Error("Task name not recognized");
} }
+70
View File
@@ -47,6 +47,12 @@ export let tasksCollection: Collection,
let clientPromise: Promise<MongoClient>; let clientPromise: Promise<MongoClient>;
export let client: MongoClient; export let client: MongoClient;
function compareAccessLists(list1: string[], list2: string[]): boolean {
if (list1.length !== list2.length) return false;
for (const [i, v] of list1.entries()) if (v !== list2[i]) return false;
return true;
}
export async function connect(): Promise<void> { export async function connect(): Promise<void> {
clientPromise = MongoClient.connect("" + get("MONGODB_CONNECTION_URL"), { clientPromise = MongoClient.connect("" + get("MONGODB_CONNECTION_URL"), {
useNewUrlParser: true useNewUrlParser: true
@@ -118,6 +124,8 @@ export async function fetchDevice(
function storeParams(obj, path: string, pathLength: number, ts): void { function storeParams(obj, path: string, pathLength: number, ts): void {
if (obj["_timestamp"]) obj["_timestamp"] = +obj["_timestamp"]; if (obj["_timestamp"]) obj["_timestamp"] = +obj["_timestamp"];
if (obj["_attributesTimestamp"])
obj["_attributesTimestamp"] = +obj["_attributesTimestamp"];
const attrs: Attributes = {}; const attrs: Attributes = {};
let t = obj["_timestamp"] || 1; let t = obj["_timestamp"] || 1;
@@ -135,6 +143,16 @@ export async function fetchDevice(
if (obj["_object"] != null) attrs.object = [t, obj["_object"] ? 1 : 0]; if (obj["_object"] != null) attrs.object = [t, obj["_object"] ? 1 : 0];
if (obj["_notification"] != null) {
attrs.notification = [
obj["_attributesTimestamp"] || 1,
obj["_notification"]
];
}
if (obj["_accessList"] != null)
attrs.accessList = [obj["_attributesTimestamp"] || 1, obj["_accessList"]];
res.push([Path.parse(path.slice(0, -1)), t, attrs]); res.push([Path.parse(path.slice(0, -1)), t, attrs]);
for (const [k, v] of Object.entries(obj)) { for (const [k, v] of Object.entries(obj)) {
@@ -331,6 +349,12 @@ export async function saveDevice(
const object2 = ((diff[2] || {}).object || [])[1]; const object2 = ((diff[2] || {}).object || [])[1];
const writable2 = ((diff[2] || {}).writable || [])[1]; const writable2 = ((diff[2] || {}).writable || [])[1];
const writable1 = ((diff[1] || {}).writable || [])[1]; const writable1 = ((diff[1] || {}).writable || [])[1];
const attributesTimestamp1 = ((diff[1] || {}).notification || [])[0];
const attributesTimestamp2 = ((diff[2] || {}).notification || [])[0];
const notification1 = ((diff[1] || {}).notification || [])[1];
const notification2 = ((diff[2] || {}).notification || [])[1];
const accessList1 = ((diff[1] || {}).accessList || [])[1];
const accessList2 = ((diff[2] || {}).accessList || [])[1];
switch (path.segments[0]) { switch (path.segments[0]) {
case "Events": case "Events":
@@ -457,6 +481,46 @@ export async function saveDevice(
path.length ? path.toString() + "._writable" : "_writable" path.length ? path.toString() + "._writable" : "_writable"
] = !!writable2; ] = !!writable2;
} }
break;
case "notification":
if (
!diff[1] ||
!diff[1].notification ||
notification2 !== notification1
) {
update["$set"][
path.length
? path.toString() + "._notification"
: "_notification"
] = notification2;
}
if (attributesTimestamp2 !== attributesTimestamp1) {
update["$set"][
path.toString() + "._attributesTimestamp"
] = new Date(attributesTimestamp2);
}
break;
case "accessList":
if (
!diff[1] ||
!diff[1].accessList ||
!compareAccessLists(accessList2, accessList1)
) {
update["$set"][
path.length
? path.toString() + "._accessList"
: "_accessList"
] = accessList2;
}
if (attributesTimestamp2 !== attributesTimestamp1) {
update["$set"][
path.toString() + "._attributesTimestamp"
] = new Date(attributesTimestamp2);
}
} }
} }
} }
@@ -472,6 +536,12 @@ export async function saveDevice(
if (attrName === "value") { if (attrName === "value") {
update["$unset"][p + "_type"] = 1; update["$unset"][p + "_type"] = 1;
update["$unset"][p + "_timestamp"] = 1; update["$unset"][p + "_timestamp"] = 1;
} else if (attrName === "notification") {
if (accessList2 == null)
update["$unset"][`${p}_attributesTimestamp`] = 1;
} else if (attrName === "accessList") {
if (notification2 == null)
update["$unset"][`${p}_attributesTimestamp`] = 1;
} }
} }
} }
+54 -11
View File
@@ -29,25 +29,45 @@ export function refresh(sessionContext, provision, declarations): boolean {
(provision.length !== 2 || typeof provision[1] !== "string") && (provision.length !== 2 || typeof provision[1] !== "string") &&
(provision.length !== 3 || (provision.length !== 3 ||
typeof provision[1] !== "string" || typeof provision[1] !== "string" ||
typeof provision[2] !== "number") typeof provision[2] !== "number") &&
(provision.length < 4 ||
typeof provision[1] !== "string" ||
typeof provision[2] !== "number" ||
typeof provision[3] !== "boolean")
) )
throw new Error("Invalid arguments"); throw new Error("Invalid arguments");
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 every = 1000 * (provision[2] || 1);
const offset = scheduling.variance(sessionContext.deviceId, every); const offset = scheduling.variance(sessionContext.deviceId, every);
const t = scheduling.interval(sessionContext.timestamp, every, offset); const t = scheduling.interval(sessionContext.timestamp, every, offset);
for (let i = l; i < path.length; ++i) { let attrGet;
let refreshChildren;
if (provision[3] == null) {
refreshChildren = true;
attrGet = { object: 1, writable: 1, value: t };
} else {
attrGet = {};
refreshChildren = !!provision[3];
for (const a of provision.slice(4)) attrGet[a] = t;
}
let path = Path.parse(provision[1]);
let l = path.length;
if (refreshChildren) {
const segments = path.segments.slice();
l = segments.length;
segments.length = MAX_DEPTH;
segments.fill("*", l);
path = Path.parse(segments.join("."));
}
for (let i = l; i <= path.length; ++i) {
declarations.push({ declarations.push({
path: path.slice(0, i), path: path.slice(0, i),
pathGet: t, pathGet: t,
pathSet: null, pathSet: null,
attrGet: { object: 1, writable: 1, value: t }, attrGet: attrGet,
attrSet: null, attrSet: null,
defer: true defer: true
}); });
@@ -57,15 +77,38 @@ export function refresh(sessionContext, provision, declarations): boolean {
} }
export function value(sessionContext, provision, declarations): boolean { export function value(sessionContext, provision, declarations): boolean {
if (provision.length !== 3 || typeof provision[1] !== "string") if (
provision.length < 3 ||
provision.length > 4 ||
typeof provision[1] !== "string"
)
throw new Error("Invalid arguments"); throw new Error("Invalid arguments");
let attr: string, val: any;
if (provision.length === 3) {
attr = "value";
val = provision[2];
} else {
attr = provision[2] || "";
val = provision[3];
}
if (attr === "accessList") {
val = (val || "")
.split(",")
.map(s => s.trim())
.filter(s => !!s);
} else if (attr === "value") {
val = [val];
}
declarations.push({ declarations.push({
path: Path.parse(provision[1]), path: Path.parse(provision[1]),
pathGet: 1, pathGet: 1,
pathSet: null, pathSet: null,
attrGet: { value: 1 }, attrGet: { [attr]: 1 },
attrSet: { value: [provision[2]] }, attrSet: { [attr]: val },
defer: true defer: true
}); });
+3 -1
View File
@@ -23,7 +23,9 @@ import { DeviceData, Declaration, Clear } from "./types";
const CHANGE_FLAGS = { const CHANGE_FLAGS = {
object: 2, object: 2,
writable: 4, writable: 4,
value: 8 value: 8,
notification: 16,
accessList: 32
}; };
function parseBool(v): boolean { function parseBool(v): boolean {
+42 -21
View File
@@ -285,19 +285,30 @@ function parseDate(d): number | string {
return isNaN(n) ? "" + d : n; return isNaN(n) ? "" + d : n;
} }
interface FlatAttributes {
object?: boolean;
objectTimestamp?: number;
writable?: boolean;
writableTimestamp?: number;
value?: [string | number | boolean, string];
valueTimestamp?: number;
notification?: number;
notificationTimestamp?: number;
accessList?: string[];
accessListTimestamp?: number;
}
interface FlatDevice { interface FlatDevice {
[param: string]: { [param: string]: FlatAttributes;
object: boolean;
objectTimestamp: number;
writable: boolean;
writableTimestamp: number;
value: [string | number | boolean, string];
valueTimestamp: number;
};
} }
export function flattenDevice(device): FlatDevice { export function flattenDevice(device): FlatDevice {
function recursive(input, root, output, timestamp): void { function recursive(
input,
root: string,
output: FlatDevice,
timestamp: number
): void {
for (const [name, tree] of Object.entries(input)) { for (const [name, tree] of Object.entries(input)) {
if (!root) { if (!root) {
if (name === "_lastInform") { if (name === "_lastInform") {
@@ -338,7 +349,7 @@ export function flattenDevice(device): FlatDevice {
}; };
} else if (name === "_id") { } else if (name === "_id") {
output["DeviceID.ID"] = { output["DeviceID.ID"] = {
value: [tree, "xsd:string"], value: [tree as string, "xsd:string"],
valueTimestamp: timestamp, valueTimestamp: timestamp,
writable: false, writable: false,
writableTimestamp: timestamp, writableTimestamp: timestamp,
@@ -407,34 +418,44 @@ export function flattenDevice(device): FlatDevice {
else if (+input["_timestamp"] > timestamp) else if (+input["_timestamp"] > timestamp)
childrenTimestamp = +input["_timestamp"]; childrenTimestamp = +input["_timestamp"];
const attrs = {}; const attrs: FlatAttributes = {};
if (tree["_value"] != null) { if (tree["_value"] != null) {
attrs["value"] = [ attrs.value = [
tree["_value"] instanceof Date ? +tree["_value"] : tree["_value"], tree["_value"] instanceof Date ? +tree["_value"] : tree["_value"],
tree["_type"] tree["_type"]
]; ];
attrs["valueTimestamp"] = +(tree["_timestamp"] || childrenTimestamp); attrs.valueTimestamp = +(tree["_timestamp"] || childrenTimestamp);
attrs["object"] = false; attrs.object = false;
attrs["objectTimestamp"] = childrenTimestamp; attrs.objectTimestamp = childrenTimestamp;
} else if (tree["_object"] != null) { } else if (tree["_object"] != null) {
attrs["object"] = tree["_object"]; attrs.object = tree["_object"];
attrs["objectTimestamp"] = childrenTimestamp; attrs.objectTimestamp = childrenTimestamp;
} }
if (tree["_writable"] != null) { if (tree["_writable"] != null) {
attrs["writable"] = tree["_writable"]; attrs.writable = tree["_writable"];
attrs["writableTimestamp"] = childrenTimestamp; attrs.writableTimestamp = childrenTimestamp;
}
if (tree["_notification"] != null) {
attrs.notification = tree["_notification"];
attrs.notificationTimestamp = +tree["_attributesTimestamp"] || 1;
}
if (tree["_accessList"] != null) {
attrs.accessList = tree["_accessList"];
attrs.accessListTimestamp = +tree["_attributesTimestamp"] || 1;
} }
const r = root ? `${root}.${name}` : name; const r = root ? `${root}.${name}` : name;
output[r] = attrs; output[r] = attrs;
if (attrs["object"] || tree["object"] == null) if (attrs.object || tree["object"] == null)
recursive(tree, r, output, childrenTimestamp); recursive(tree, r, output, childrenTimestamp);
} }
} }
const newDevice = {}; const newDevice: FlatDevice = {};
const timestamp = new Date(device["_lastInform"] || 1).getTime(); const timestamp = new Date(device["_lastInform"] || 1).getTime();
recursive(device, "", newDevice, timestamp); recursive(device, "", newDevice, timestamp);
return newDevice; return newDevice;
+181 -18
View File
@@ -729,9 +729,12 @@ function runDeclarations(
exist: new Set(), exist: new Set(),
object: new Set(), object: new Set(),
writable: new Set(), writable: new Set(),
value: new Set() value: new Set(),
notification: new Set(),
accessList: new Set()
}, },
spv: new Map(), spv: new Map(),
spa: new Map(),
gpn: new Set<Path>(), gpn: new Set<Path>(),
gpnPatterns: new Map(), gpnPatterns: new Map(),
tags: new Map(), tags: new Map(),
@@ -1463,6 +1466,8 @@ function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
syncState.refreshAttributes.value.has(p) || syncState.refreshAttributes.value.has(p) ||
syncState.refreshAttributes.object.has(p) || syncState.refreshAttributes.object.has(p) ||
syncState.refreshAttributes.writable.has(p) || syncState.refreshAttributes.writable.has(p) ||
syncState.refreshAttributes.notification.has(p) ||
syncState.refreshAttributes.accessList.has(p) ||
syncState.gpn.has(p) syncState.gpn.has(p)
) { ) {
found = true; found = true;
@@ -1491,7 +1496,9 @@ function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
syncState.refreshAttributes.value.has(p) || syncState.refreshAttributes.value.has(p) ||
(p.length > path.length && (p.length > path.length &&
(syncState.refreshAttributes.object.has(p) || (syncState.refreshAttributes.object.has(p) ||
syncState.refreshAttributes.writable.has(p))) syncState.refreshAttributes.writable.has(p) ||
syncState.refreshAttributes.notification.has(p) ||
syncState.refreshAttributes.accessList.has(p)))
) { ) {
found = true; found = true;
break; break;
@@ -1599,9 +1606,57 @@ function generateGetRpcRequest(sessionContext: SessionContext): GetAcsRequest {
}; };
} }
} }
if (
syncState.refreshAttributes.notification.size ||
syncState.refreshAttributes.accessList.size
) {
const GPV_BATCH_SIZE = localCache.getConfig(
sessionContext.cacheSnapshot,
"cwmp.gpvBatchSize",
sessionContext.configContext
);
const parameterNames: string[] = [];
for (const path of syncState.refreshAttributes.notification) {
syncState.refreshAttributes.notification.delete(path);
syncState.refreshAttributes.accessList.delete(path);
// Need to check in case param is deleted
const attrs = sessionContext.deviceData.attributes.get(path);
if (attrs) {
parameterNames.push(path.toString());
if (parameterNames.length >= GPV_BATCH_SIZE) break;
}
}
if (parameterNames.length < GPV_BATCH_SIZE) {
for (const path of syncState.refreshAttributes.accessList) {
syncState.refreshAttributes.accessList.delete(path);
const attrs = sessionContext.deviceData.attributes.get(path);
if (attrs) {
parameterNames.push(path.toString());
if (parameterNames.length >= GPV_BATCH_SIZE) break;
}
}
}
if (parameterNames.length) {
return {
name: "GetParameterAttributes",
parameterNames: parameterNames
};
}
}
return null; return null;
} }
function compareAccessLists(list1: string[], list2: string[]): boolean {
if (list1.length !== list2.length) return false;
for (const [i, v] of list1.entries()) if (v !== list2[i]) return false;
return true;
}
function generateSetRpcRequest(sessionContext: SessionContext): SetAcsRequest { function generateSetRpcRequest(sessionContext: SessionContext): SetAcsRequest {
const syncState = sessionContext.syncState; const syncState = sessionContext.syncState;
if (!syncState) return null; if (!syncState) return null;
@@ -1654,7 +1709,7 @@ function generateSetRpcRequest(sessionContext: SessionContext): SetAcsRequest {
sessionContext.configContext sessionContext.configContext
); );
const parameterValues: [Path, string | number | boolean, string][] = []; const parameterValues: [string, string | number | boolean, string][] = [];
for (const [k, v] of syncState.spv) { for (const [k, v] of syncState.spv) {
syncState.spv.delete(k); syncState.spv.delete(k);
const attrs = sessionContext.deviceData.attributes.get(k); const attrs = sessionContext.deviceData.attributes.get(k);
@@ -1673,7 +1728,7 @@ function generateSetRpcRequest(sessionContext: SessionContext): SetAcsRequest {
val[0] -= val[0] % 1000; val[0] -= val[0] % 1000;
if (val[0] !== curVal[0] || val[1] !== curVal[1]) if (val[0] !== curVal[0] || val[1] !== curVal[1])
parameterValues.push([k, val[0], val[1]]); parameterValues.push([k.toString(), val[0], val[1]]);
if (parameterValues.length >= GPV_BATCH_SIZE) break; if (parameterValues.length >= GPV_BATCH_SIZE) break;
} }
@@ -1682,19 +1737,44 @@ function generateSetRpcRequest(sessionContext: SessionContext): SetAcsRequest {
if (parameterValues.length) { if (parameterValues.length) {
return { return {
name: "SetParameterValues", name: "SetParameterValues",
parameterList: parameterValues.map( parameterList: parameterValues,
p =>
[p[0].toString(), p[1], p[2]] as [
string,
string | number | boolean,
string
]
),
DATETIME_MILLISECONDS: DATETIME_MILLISECONDS, DATETIME_MILLISECONDS: DATETIME_MILLISECONDS,
BOOLEAN_LITERAL: BOOLEAN_LITERAL BOOLEAN_LITERAL: BOOLEAN_LITERAL
}; };
} }
// Set attributes
const parameterAttributes: [string, number, string[]][] = [];
for (const [k, v] of syncState.spa) {
syncState.spa.delete(k);
const attrs = sessionContext.deviceData.attributes.get(k);
if (
v.notification != null &&
(!attrs.notification || v.notification === attrs.notification[1])
)
v.notification = null;
if (
v.accessList != null &&
(!attrs.accessList ||
compareAccessLists(v.accessList, attrs.accessList[1]))
)
v.accessList = null;
if (v.notification != null || v.accessList != null)
parameterAttributes.push([k.toString(), v.notification, v.accessList]);
if (parameterAttributes.length >= GPV_BATCH_SIZE) break;
}
if (parameterAttributes.length) {
return {
name: "SetParameterAttributes",
parameterList: parameterAttributes
};
}
// Downloads // Downloads
for (const [p, t] of syncState.downloadsDownload) { for (const [p, t] of syncState.downloadsDownload) {
const attrs = deviceData.attributes.get(p); const attrs = deviceData.attributes.get(p);
@@ -2109,8 +2189,34 @@ function processDeclarations(
} }
} }
} }
if (declareAttributeValues && declareAttributeValues.value != null) if (declareAttributeValues) {
if (declareAttributeValues.value != null)
syncState.spv.set(currentPath, declareAttributeValues.value); syncState.spv.set(currentPath, declareAttributeValues.value);
if (declareAttributeValues.notification != null) {
const spa = syncState.spa.get(currentPath);
if (spa) {
spa.notification = declareAttributeValues.notification;
} else {
syncState.spa.set(currentPath, {
notification: declareAttributeValues.notification,
accessList: null
});
}
}
if (declareAttributeValues.accessList != null) {
const spa = syncState.spa.get(currentPath);
if (spa) {
spa.accessList = declareAttributeValues.accessList;
} else {
syncState.spa.set(currentPath, {
notification: null,
accessList: declareAttributeValues.accessList
});
}
}
}
} }
} }
@@ -2243,7 +2349,7 @@ export async function rpcResponse(
if (p[1] !== rpcReq.instanceValues[p[0]]) { if (p[1] !== rpcReq.instanceValues[p[0]]) {
const v = device.sanitizeParameterValue([ const v = device.sanitizeParameterValue([
rpcReq.instanceValues[p[0]], rpcReq.instanceValues[p[0]],
p[2] p[2] as string
]); ]);
parameterList.push([p[0], v[0], v[1]]); parameterList.push([p[0], v[0], v[1]]);
} }
@@ -2288,6 +2394,21 @@ export async function rpcResponse(
); );
} }
break;
case "GetParameterAttributesResponse":
if (rpcReq.name !== "GetParameterAttributes")
throw new Error("Response name does not match request name");
for (const p of rpcRes.parameterList) {
toClear = device.set(
sessionContext.deviceData,
Path.parse(p[0]),
timestamp,
{ notification: [timestamp, p[1]], accessList: [timestamp, p[2]] },
toClear
);
}
break; break;
case "GetParameterNamesResponse": case "GetParameterNamesResponse":
if (rpcReq.name !== "GetParameterNames") if (rpcReq.name !== "GetParameterNames")
@@ -2404,6 +2525,38 @@ export async function rpcResponse(
); );
} }
break;
case "SetParameterAttributesResponse":
if (rpcReq.name !== "SetParameterAttributes")
throw new Error("Response name does not match request name");
for (const p of rpcReq.parameterList) {
let attrs;
if (p[1] != null && p[2] != null) {
attrs = {
notification: [timestamp + 1, p[1]],
accessList: [timestamp + 1, p[2]]
};
} else if (p[1] != null) {
attrs = {
notification: [timestamp + 1, p[1]]
};
} else if (p[2] != null) {
attrs = {
accessList: [timestamp + 1, p[2]]
};
}
toClear = device.set(
sessionContext.deviceData,
Path.parse(p[0]),
timestamp + 1,
attrs,
toClear
);
}
break; break;
case "AddObjectResponse": case "AddObjectResponse":
toClear = device.set( toClear = device.set(
@@ -2548,7 +2701,7 @@ export async function rpcFault(
// Recover from invalid parameter name faults // Recover from invalid parameter name faults
if (faultResponse.detail.faultCode === "9005") { if (faultResponse.detail.faultCode === "9005") {
const timestamp = sessionContext.timestamp + sessionContext.iteration; const timestamp = sessionContext.timestamp + sessionContext.iteration + 1;
const revision = const revision =
(sessionContext.revisions[sessionContext.revisions.length - 1] || 0) + 1; (sessionContext.revisions[sessionContext.revisions.length - 1] || 0) + 1;
sessionContext.deviceData.timestamps.revision = revision; sessionContext.deviceData.timestamps.revision = revision;
@@ -2566,13 +2719,23 @@ export async function rpcFault(
p => [Path.parse(p.replace(/\.$/, "")), timestamp] as Clear p => [Path.parse(p.replace(/\.$/, "")), timestamp] as Clear
); );
} else if (rpcReq.name === "SetParameterValues") { } else if (rpcReq.name === "SetParameterValues") {
toClear = rpcReq.parameterList.map( toClear = (rpcReq.parameterList as [
p => [Path.parse(p[0].replace(/\.$/, "")), timestamp] as Clear string,
); string | number | boolean,
string
][]).map(p => [Path.parse(p[0].replace(/\.$/, "")), timestamp] as Clear);
} else if (rpcReq.name === "AddObject") { } else if (rpcReq.name === "AddObject") {
toClear = [[Path.parse(rpcReq.objectName.replace(/\.$/, "")), timestamp]]; toClear = [[Path.parse(rpcReq.objectName.replace(/\.$/, "")), timestamp]];
} else if (rpcReq.name === "DeleteObject") { } else if (rpcReq.name === "DeleteObject") {
toClear = [[Path.parse(rpcReq.objectName.replace(/\.$/, "")), timestamp]]; toClear = [[Path.parse(rpcReq.objectName.replace(/\.$/, "")), timestamp]];
} else if (rpcReq.name === "GetParameterAttributes") {
toClear = rpcReq.parameterNames.map(
p => [Path.parse(p.replace(/\.$/, "")), timestamp] as Clear
);
} else if (rpcReq.name === "SetParameterAttributes") {
toClear = (rpcReq.parameterList as [string, number, string[]][]).map(
p => [Path.parse(p[0].replace(/\.$/, "")), timestamp] as Clear
);
} }
if (toClear) { if (toClear) {
+98
View File
@@ -188,6 +188,45 @@ function parameterValueList(
}); });
} }
function parameterAttributeList(xml: Element): [string, number, string[]][] {
return xml.children
.filter(e => e.localName === "ParameterAttributeStruct")
.map<[string, number, string[]]>(e => {
let notificationElement: Element,
accessListElement: Element,
param: string;
for (const c of e.children) {
switch (c.localName) {
case "Name":
param = c.text;
break;
case "Notification":
notificationElement = c;
break;
case "AccessList":
accessListElement = c;
break;
}
}
const notification = decodeEntities(notificationElement.text);
const notif = +notification;
if (notif !== parseInt(notification)) {
warnings.push({
message: "Invalid notification attribute",
parameter: param
});
}
const accessList = accessListElement.children
.filter(c => c.localName === "string")
.map(c => decodeEntities(c.text));
return [param, notif, accessList];
});
}
function GetParameterNames(methodRequest): string { function GetParameterNames(methodRequest): string {
return `<cwmp:GetParameterNames><ParameterPath>${ return `<cwmp:GetParameterNames><ParameterPath>${
methodRequest.parameterPath methodRequest.parameterPath
@@ -220,6 +259,23 @@ function GetParameterValuesResponse(xml: Element): CpeGetResponse {
}; };
} }
function GetParameterAttributes(methodRequest): string {
return `<cwmp:GetParameterAttributes><ParameterNames soap-enc:arrayType="xsd:string[${
methodRequest.parameterNames.length
}]">${methodRequest.parameterNames
.map(p => `<string>${p}</string>`)
.join("")}</ParameterNames></cwmp:GetParameterAttributes>`;
}
function GetParameterAttributesResponse(xml: Element): CpeGetResponse {
return {
name: "GetParameterAttributesResponse",
parameterList: parameterAttributeList(
xml.children.find(n => n.localName === "ParameterList")
)
};
}
function SetParameterValues(methodRequest): string { function SetParameterValues(methodRequest): string {
const params = methodRequest.parameterList.map(p => { const params = methodRequest.parameterList.map(p => {
let val = p[1]; let val = p[1];
@@ -250,6 +306,36 @@ function SetParameterValuesResponse(xml: Element): CpeSetResponse {
}; };
} }
function SetParameterAttributes(methodRequest): string {
const params = methodRequest.parameterList.map(p => {
return `<SetParameterAttributesStruct><Name>${
p[0]
}</Name><NotificationChange>${
p[1] == null ? "false" : "true"
}</NotificationChange><Notification>${
p[1] == null ? "" : p[1]
}</Notification><AccessListChange>${
p[2] == null ? "false" : "true"
}</AccessListChange><AccessList soap-enc:arrayType="xsd:string[${
(p[2] || []).length
}]">${
p[2] == null
? ""
: p[2].map(s => `<string>${encodeEntities(s)}</string>`).join("")
}</AccessList></SetParameterAttributesStruct>`;
});
return `<cwmp:SetParameterAttributes><ParameterList soap-enc:arrayType="cwmp:SetParameterAttributesStruct[${
methodRequest.parameterList.length
}]">${params.join("")}</ParameterList></cwmp:SetParameterAttributes>`;
}
function SetParameterAttributesResponse(): CpeSetResponse {
return {
name: "SetParameterAttributesResponse"
};
}
function AddObject(methodRequest): string { function AddObject(methodRequest): string {
return `<cwmp:AddObject><ObjectName>${ return `<cwmp:AddObject><ObjectName>${
methodRequest.objectName methodRequest.objectName
@@ -610,9 +696,15 @@ export function request(body: string, cwmpVersion, warn): SoapMessage {
case "GetParameterValuesResponse": case "GetParameterValuesResponse":
rpc.cpeResponse = GetParameterValuesResponse(methodElement); rpc.cpeResponse = GetParameterValuesResponse(methodElement);
break; break;
case "GetParameterAttributesResponse":
rpc.cpeResponse = GetParameterAttributesResponse(methodElement);
break;
case "SetParameterValuesResponse": case "SetParameterValuesResponse":
rpc.cpeResponse = SetParameterValuesResponse(methodElement); rpc.cpeResponse = SetParameterValuesResponse(methodElement);
break; break;
case "SetParameterAttributesResponse":
rpc.cpeResponse = SetParameterAttributesResponse();
break;
case "AddObjectResponse": case "AddObjectResponse":
rpc.cpeResponse = AddObjectResponse(methodElement); rpc.cpeResponse = AddObjectResponse(methodElement);
break; break;
@@ -690,9 +782,15 @@ export function response(rpc): { code: number; headers: {}; data: string } {
case "GetParameterValues": case "GetParameterValues":
body = GetParameterValues(rpc.acsRequest); body = GetParameterValues(rpc.acsRequest);
break; break;
case "GetParameterAttributes":
body = GetParameterAttributes(rpc.acsRequest);
break;
case "SetParameterValues": case "SetParameterValues":
body = SetParameterValues(rpc.acsRequest); body = SetParameterValues(rpc.acsRequest);
break; break;
case "SetParameterAttributes":
body = SetParameterAttributes(rpc.acsRequest);
break;
case "AddObject": case "AddObject":
body = AddObject(rpc.acsRequest); body = AddObject(rpc.acsRequest);
break; break;
+48 -7
View File
@@ -51,18 +51,24 @@ export interface Attributes {
object?: [number, 1 | 0]; object?: [number, 1 | 0];
writable?: [number, 1 | 0]; writable?: [number, 1 | 0];
value?: [number, [string | number | boolean, string]]; value?: [number, [string | number | boolean, string]];
notification?: [number, number];
accessList?: [number, string[]];
} }
export interface AttributeTimestamps { export interface AttributeTimestamps {
object?: number; object?: number;
writable?: number; writable?: number;
value?: number; value?: number;
notification?: number;
accessList?: number;
} }
export interface AttributeValues { export interface AttributeValues {
object?: boolean; object?: boolean;
writable?: boolean; writable?: boolean;
value?: [string | number | boolean, string?]; value?: [string | number | boolean, string?];
notification?: number;
accessList?: string[];
} }
export interface DeviceData { export interface DeviceData {
@@ -75,12 +81,21 @@ export interface DeviceData {
export type VirtualParameterDeclaration = [ export type VirtualParameterDeclaration = [
Path, Path,
{ path?: number; object?: number; writable?: number; value?: number }?, {
path?: number;
object?: number;
writable?: number;
value?: number;
notification?: number;
accessList?: number;
}?,
{ {
path?: [number, number]; path?: [number, number];
object?: boolean; object?: boolean;
writable?: boolean; writable?: boolean;
value?: [string | number | boolean, string?]; value?: [string | number | boolean, string?];
notification?: number;
accessList?: string[];
}? }?
]; ];
@@ -90,8 +105,11 @@ export interface SyncState {
object: Set<Path>; object: Set<Path>;
writable: Set<Path>; writable: Set<Path>;
value: Set<Path>; value: Set<Path>;
notification: Set<Path>;
accessList: Set<Path>;
}; };
spv: Map<Path, [string | number | boolean, string]>; spv: Map<Path, [string | number | boolean, string]>;
spa: Map<Path, { notification: number; accessList: string[] }>;
gpn: Set<Path>; gpn: Set<Path>;
gpnPatterns: Map<Path, number>; gpnPatterns: Map<Path, number>;
tags: Map<Path, boolean>; tags: Map<Path, boolean>;
@@ -160,6 +178,7 @@ export interface Task {
fileName?: string; fileName?: string;
targetFileName?: string; targetFileName?: string;
expiry?: number; expiry?: number;
provisions?: (string | number | boolean)[][];
} }
export interface Operation { export interface Operation {
@@ -182,7 +201,7 @@ export interface AcsRequest {
} }
export interface GetAcsRequest extends AcsRequest { export interface GetAcsRequest extends AcsRequest {
name: "GetParameterNames" | "GetParameterValues"; name: "GetParameterNames" | "GetParameterValues" | "GetParameterAttributes";
objectName?: string; objectName?: string;
parameterNames?: string[]; parameterNames?: string[];
parameterPath?: string; parameterPath?: string;
@@ -193,12 +212,15 @@ export interface GetAcsRequest extends AcsRequest {
export interface SetAcsRequest extends AcsRequest { export interface SetAcsRequest extends AcsRequest {
name: name:
| "SetParameterValues" | "SetParameterValues"
| "SetParameterAttributes"
| "AddObject" | "AddObject"
| "DeleteObject" | "DeleteObject"
| "FactoryReset" | "FactoryReset"
| "Reboot" | "Reboot"
| "Download"; | "Download";
parameterList?: [string, string | number | boolean, string][]; parameterList?:
| [string, string | number | boolean, string][]
| [string, number, string[]][];
instanceValues?: { [param: string]: string | number | boolean }; instanceValues?: { [param: string]: string | number | boolean };
objectName?: string; objectName?: string;
DATETIME_MILLISECONDS?: boolean; DATETIME_MILLISECONDS?: boolean;
@@ -235,15 +257,20 @@ export interface CpeFault {
} }
export interface CpeGetResponse extends CpeResponse { export interface CpeGetResponse extends CpeResponse {
name: "GetParameterNamesResponse" | "GetParameterValuesResponse"; name:
| "GetParameterNamesResponse"
| "GetParameterValuesResponse"
| "GetParameterAttributesResponse";
parameterList?: parameterList?:
| [string, boolean][] | [string, boolean][]
| [string, string | number | boolean, string][]; | [string, string | number | boolean, string][]
| [string, number, string[]][];
} }
export interface CpeSetResponse extends CpeResponse { export interface CpeSetResponse extends CpeResponse {
name: name:
| "SetParameterValuesResponse" | "SetParameterValuesResponse"
| "SetParameterAttributesResponse"
| "AddObjectResponse" | "AddObjectResponse"
| "DeleteObjectResponse" | "DeleteObjectResponse"
| "RebootResponse" | "RebootResponse"
@@ -300,11 +327,19 @@ export interface Declaration {
path: Path; path: Path;
pathGet: number; pathGet: number;
pathSet?: [number, number]; pathSet?: [number, number];
attrGet?: { object?: number; writable?: number; value?: number }; attrGet?: {
object?: number;
writable?: number;
value?: number;
notification?: number;
accessList?: number;
};
attrSet?: { attrSet?: {
object?: boolean; object?: boolean;
writable?: boolean; writable?: boolean;
value?: [string | number | boolean, string?]; value?: [string | number | boolean, string?];
notification?: number;
accessList?: string[];
}; };
defer: boolean; defer: boolean;
} }
@@ -312,7 +347,13 @@ export interface Declaration {
export type Clear = [ export type Clear = [
Path, Path,
number, number,
{ object?: number; writable?: number; value?: number }?, {
object?: number;
writable?: number;
value?: number;
notification?: number;
accessList?: number;
}?,
number? number?
]; ];
+6 -2
View File
@@ -99,7 +99,7 @@ router.get(`/devices/:id.csv`, async ctx => {
ctx.body = new stream.PassThrough(); ctx.body = new stream.PassThrough();
ctx.body.write( ctx.body.write(
"Parameter,Object,Object timestamp,Writable,Writable timestamp,Value,Value type,Value timestamp\n" "Parameter,Object,Object timestamp,Writable,Writable timestamp,Value,Value type,Value timestamp,Notification,Notification timestamp,Access list,Access list timestamp\n"
); );
for (const k of Object.keys(res[0]).sort()) { for (const k of Object.keys(res[0]).sort()) {
@@ -112,7 +112,11 @@ router.get(`/devices/:id.csv`, async ctx => {
p.writableTimestamp, p.writableTimestamp,
p.value != null ? `"${p.value[0].toString().replace(/"/g, '""')}"` : "", p.value != null ? `"${p.value[0].toString().replace(/"/g, '""')}"` : "",
p.value != null ? p.value[1] : "", p.value != null ? p.value[1] : "",
p.valueTimestamp p.valueTimestamp,
p.notification,
p.notificationTimestamp,
p.accessList ? p.accessList.join(", ") : "",
p.accessListTimestamp
]; ];
ctx.body.write(row.map(r => (r != null ? r : "")).join(",") + "\n"); ctx.body.write(row.map(r => (r != null ? r : "")).join(",") + "\n");
} }