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