Tolerate responses with invalid/missing params
This commit is contained in:
1 parent
08cfe4171c
commit
cfe279b511
2 files changed
+134
-37
No files matched your search
+93
-25
@@ -59,6 +59,7 @@ import {
|
||||
GetParameterValuesResponse,
|
||||
} from "./types";
|
||||
import { getRequestOrigin } from "./forwarded";
|
||||
import * as logger from "./logger";
|
||||
|
||||
const VALID_PARAM_TYPES = new Set([
|
||||
"xsd:int",
|
||||
@@ -2420,8 +2421,15 @@ export async function rpcResponse(
|
||||
id: string,
|
||||
_rpcRes: CpeResponse
|
||||
): Promise<Fault> {
|
||||
function invalidResponse(message: string): Fault {
|
||||
return {
|
||||
code: "invalid_response",
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
if (id !== generateRpcId(sessionContext))
|
||||
throw new Error("Request ID not recognized");
|
||||
return invalidResponse("Request ID not recognized");
|
||||
|
||||
++sessionContext.rpcCount;
|
||||
|
||||
@@ -2512,47 +2520,99 @@ export async function rpcResponse(
|
||||
|
||||
if (rpcRes.name === "GetParameterValuesResponse") {
|
||||
if (rpcReq.name !== "GetParameterValues")
|
||||
throw new Error("Response name does not match request name");
|
||||
return invalidResponse("Response name does not match request name");
|
||||
|
||||
if (rpcRes.parameterList.length !== rpcReq.parameterNames.length) {
|
||||
return {
|
||||
code: `invalid_response`,
|
||||
message:
|
||||
"The list of parameters returned in the GetParameterValuesResponse message does not match the requested parameters",
|
||||
};
|
||||
}
|
||||
const requested = new Set(rpcReq.parameterNames);
|
||||
|
||||
for (const p of rpcRes.parameterList) {
|
||||
for (const [path, value, type] of rpcRes.parameterList) {
|
||||
if (!requested.delete(path.toString())) {
|
||||
logger.accessWarn({
|
||||
sessionContext: sessionContext,
|
||||
message: "Unexpected parameter in response",
|
||||
parameter: path.toString(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
toClear = device.set(
|
||||
sessionContext.deviceData,
|
||||
p[0],
|
||||
path,
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
value: [timestamp, p.slice(1) as [string | number | boolean, string]],
|
||||
value: [timestamp, [value, type]],
|
||||
},
|
||||
toClear
|
||||
);
|
||||
}
|
||||
|
||||
if (requested.size) {
|
||||
for (const p of requested) {
|
||||
logger.accessWarn({
|
||||
sessionContext: sessionContext,
|
||||
message: "Missing parameter in response",
|
||||
parameter: p,
|
||||
});
|
||||
toClear = device.set(
|
||||
sessionContext.deviceData,
|
||||
Path.parse(p),
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
value: [timestamp, ["", "xsd:string"]],
|
||||
},
|
||||
toClear
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (rpcRes.name === "GetParameterAttributesResponse") {
|
||||
if (rpcReq.name !== "GetParameterAttributes")
|
||||
throw new Error("Response name does not match request name");
|
||||
|
||||
for (const p of rpcRes.parameterList) {
|
||||
const requested = new Set(rpcReq.parameterNames);
|
||||
|
||||
for (const [path, notification, accessList] of rpcRes.parameterList) {
|
||||
if (!requested.delete(path.toString())) {
|
||||
logger.accessWarn({
|
||||
sessionContext: sessionContext,
|
||||
message: "Unexpected parameter in response",
|
||||
parameter: path.toString(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
toClear = device.set(
|
||||
sessionContext.deviceData,
|
||||
p[0],
|
||||
path,
|
||||
timestamp,
|
||||
{
|
||||
notification: [timestamp, p[1] as number],
|
||||
accessList: [timestamp, p[2] as string[]],
|
||||
notification: [timestamp, notification],
|
||||
accessList: [timestamp, accessList],
|
||||
},
|
||||
toClear
|
||||
);
|
||||
}
|
||||
|
||||
if (requested.size) {
|
||||
for (const p of requested) {
|
||||
logger.accessWarn({
|
||||
sessionContext: sessionContext,
|
||||
message: "Missing parameter in response",
|
||||
parameter: p,
|
||||
});
|
||||
toClear = device.set(
|
||||
sessionContext.deviceData,
|
||||
Path.parse(p),
|
||||
timestamp,
|
||||
{
|
||||
notification: [timestamp, 0],
|
||||
accessList: [timestamp, []],
|
||||
},
|
||||
toClear
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (rpcRes.name === "GetParameterNamesResponse") {
|
||||
if (rpcReq.name !== "GetParameterNames")
|
||||
throw new Error("Response name does not match request name");
|
||||
return invalidResponse("Response name does not match request name");
|
||||
|
||||
let root: Path;
|
||||
if (rpcReq.parameterPath.endsWith("."))
|
||||
@@ -2609,6 +2669,14 @@ export async function rpcResponse(
|
||||
const wildcardParams: Path[] = [root.concat(wildcardPath)];
|
||||
|
||||
for (const [path, object, writable] of rpcRes.parameterList) {
|
||||
if (!path.toString().startsWith(rpcReq.parameterPath)) {
|
||||
logger.accessWarn({
|
||||
sessionContext: sessionContext,
|
||||
message: "Unexpected parameter in response",
|
||||
parameter: path.toString(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (object && !rpcReq.nextLevel)
|
||||
wildcardParams.push(path.concat(wildcardPath));
|
||||
|
||||
@@ -2635,7 +2703,7 @@ export async function rpcResponse(
|
||||
}
|
||||
} else if (rpcRes.name === "SetParameterValuesResponse") {
|
||||
if (rpcReq.name !== "SetParameterValues")
|
||||
throw new Error("Response name does not match request name");
|
||||
return invalidResponse("Response name does not match request name");
|
||||
|
||||
for (const p of rpcReq.parameterList) {
|
||||
toClear = device.set(
|
||||
@@ -2654,7 +2722,7 @@ export async function rpcResponse(
|
||||
}
|
||||
} else if (rpcRes.name === "SetParameterAttributesResponse") {
|
||||
if (rpcReq.name !== "SetParameterAttributes")
|
||||
throw new Error("Response name does not match request name");
|
||||
return invalidResponse("Response name does not match request name");
|
||||
|
||||
for (const p of rpcReq.parameterList) {
|
||||
let attrs;
|
||||
@@ -2684,7 +2752,7 @@ export async function rpcResponse(
|
||||
}
|
||||
} else if (rpcRes.name === "AddObjectResponse") {
|
||||
if (rpcReq.name !== "AddObject")
|
||||
throw new Error("Response name does not match request name");
|
||||
return invalidResponse("Response name does not match request name");
|
||||
|
||||
toClear = device.set(
|
||||
sessionContext.deviceData,
|
||||
@@ -2695,7 +2763,7 @@ export async function rpcResponse(
|
||||
);
|
||||
} else if (rpcRes.name === "DeleteObjectResponse") {
|
||||
if (rpcReq.name !== "DeleteObject")
|
||||
throw new Error("Response name does not match request name");
|
||||
return invalidResponse("Response name does not match request name");
|
||||
|
||||
toClear = device.set(
|
||||
sessionContext.deviceData,
|
||||
@@ -2706,7 +2774,7 @@ export async function rpcResponse(
|
||||
);
|
||||
} else if (rpcRes.name === "RebootResponse") {
|
||||
if (rpcReq.name !== "Reboot")
|
||||
throw new Error("Response name does not match request name");
|
||||
return invalidResponse("Response name does not match request name");
|
||||
|
||||
toClear = device.set(
|
||||
sessionContext.deviceData,
|
||||
@@ -2717,7 +2785,7 @@ export async function rpcResponse(
|
||||
);
|
||||
} else if (rpcRes.name === "FactoryResetResponse") {
|
||||
if (rpcReq.name !== "FactoryReset")
|
||||
throw new Error("Response name does not match request name");
|
||||
return invalidResponse("Response name does not match request name");
|
||||
|
||||
toClear = device.set(
|
||||
sessionContext.deviceData,
|
||||
@@ -2728,7 +2796,7 @@ export async function rpcResponse(
|
||||
);
|
||||
} else if (rpcRes.name === "DownloadResponse") {
|
||||
if (rpcReq.name !== "Download")
|
||||
throw new Error("Response name does not match request name");
|
||||
return invalidResponse("Response name does not match request name");
|
||||
|
||||
toClear = device.set(
|
||||
sessionContext.deviceData,
|
||||
@@ -2814,7 +2882,7 @@ export async function rpcResponse(
|
||||
sessionContext.operationsTouched[rpcReq.commandKey] = 1;
|
||||
}
|
||||
} else {
|
||||
throw new Error("Response name not recognized");
|
||||
return invalidResponse("Response name not recognized");
|
||||
}
|
||||
|
||||
if (toClear) {
|
||||
|
||||
+41
-12
@@ -109,8 +109,8 @@ function event(xml: Element): string[] {
|
||||
|
||||
function parameterInfoList(xml: Element): [Path, boolean, boolean][] {
|
||||
return xml.children
|
||||
.filter((e) => e.localName === "ParameterInfoStruct")
|
||||
.map<[Path, boolean, boolean]>((e) => {
|
||||
if (e.localName !== "ParameterInfoStruct") return null;
|
||||
let param: string, value: string;
|
||||
for (const c of e.children) {
|
||||
switch (c.localName) {
|
||||
@@ -134,10 +134,20 @@ function parameterInfoList(xml: Element): [Path, boolean, boolean][] {
|
||||
parsed = false;
|
||||
}
|
||||
|
||||
if (param && !param.endsWith("."))
|
||||
return [Path.parse(param), false, parsed];
|
||||
else return [Path.parse(param.slice(0, -1)), true, parsed];
|
||||
});
|
||||
try {
|
||||
if (param && !param.endsWith("."))
|
||||
return [Path.parse(param), false, parsed];
|
||||
else return [Path.parse(param.slice(0, -1)), true, parsed];
|
||||
} catch (err) {
|
||||
warnings.push({
|
||||
message: "Missing or invalid XML node",
|
||||
element: "Name",
|
||||
parameter: param,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((e) => e != null);
|
||||
}
|
||||
|
||||
const getValueType = memoize((str: string) => {
|
||||
@@ -151,8 +161,8 @@ function parameterValueList(
|
||||
xml: Element
|
||||
): [Path, string | number | boolean, string][] {
|
||||
return xml.children
|
||||
.filter((e) => e.localName === "ParameterValueStruct")
|
||||
.map<[Path, string | number | boolean, string]>((e) => {
|
||||
if (e.localName !== "ParameterValueStruct") return null;
|
||||
let valueElement: Element, param: string;
|
||||
for (const c of e.children) {
|
||||
switch (c.localName) {
|
||||
@@ -208,15 +218,24 @@ function parameterValueList(
|
||||
parsed = value;
|
||||
}
|
||||
}
|
||||
|
||||
return [Path.parse(param), parsed, valueType];
|
||||
});
|
||||
try {
|
||||
return [Path.parse(param), parsed, valueType];
|
||||
} catch (err) {
|
||||
warnings.push({
|
||||
message: "Missing or invalid XML node",
|
||||
element: "Name",
|
||||
parameter: param,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((e) => e != null);
|
||||
}
|
||||
|
||||
function parameterAttributeList(xml: Element): [Path, number, string[]][] {
|
||||
return xml.children
|
||||
.filter((e) => e.localName === "ParameterAttributeStruct")
|
||||
.map<[Path, number, string[]]>((e) => {
|
||||
if (e.localName !== "ParameterAttributeStruct") return null;
|
||||
let notificationElement: Element,
|
||||
accessListElement: Element,
|
||||
param: string;
|
||||
@@ -248,8 +267,18 @@ function parameterAttributeList(xml: Element): [Path, number, string[]][] {
|
||||
.filter((c) => c.localName === "string")
|
||||
.map((c) => decodeEntities(c.text));
|
||||
|
||||
return [Path.parse(param), notification, accessList];
|
||||
});
|
||||
try {
|
||||
return [Path.parse(param), notification, accessList];
|
||||
} catch (err) {
|
||||
warnings.push({
|
||||
message: "Missing or invalid XML node",
|
||||
element: "Name",
|
||||
parameter: param,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((e) => e != null);
|
||||
}
|
||||
|
||||
function GetParameterNames(methodRequest): string {
|
||||
|
||||
Reference in new issue
Block a user