From 84de4318724d0deed2c2968ef38e401bb36573e4 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Fri, 27 Aug 2021 15:02:04 -0700 Subject: [PATCH] Complete implementation of ChangeDUState --- lib/cwmp.ts | 43 ++++++++++++++++++++ lib/session.ts | 105 ++++++++++++++++++++++++++++++++++++++++++++----- lib/soap.ts | 6 +-- lib/types.ts | 8 ++-- 4 files changed, 145 insertions(+), 17 deletions(-) diff --git a/lib/cwmp.ts b/lib/cwmp.ts index 4776687..e36119b 100644 --- a/lib/cwmp.ts +++ b/lib/cwmp.ts @@ -361,6 +361,39 @@ async function transferComplete(sessionContext, rpc): Promise { return writeResponse(sessionContext, res); } +async function duStateChangeComplete(sessionContext, rpc): Promise { + const { acsResponse, operation, fault } = await session.duStateChangeComplete( + sessionContext, + rpc.cpeRequest + ); + + if (!operation) { + logger.accessWarn({ + sessionContext: sessionContext, + message: "Unrecognized command key", + rpc: rpc, + }); + } + + if (fault) { + Object.assign(sessionContext.retries, operation.retries); + recordFault( + sessionContext, + fault, + operation.provisions, + operation.channels + ); + } + + const res = soap.response({ + id: rpc.id, + acsResponse: acsResponse, + cwmpVersion: sessionContext.cwmpVersion, + }); + + return writeResponse(sessionContext, res); +} + // Append provisions and remove duplicates function appendProvisions(original, toAppend): boolean { let modified = false; @@ -1239,6 +1272,16 @@ async function processRequest( cwmpVersion: sessionContext.cwmpVersion, }); return writeResponse(sessionContext, res); + } else if (rpc.cpeRequest.name === "DUStateChangeComplete") { + if (sessionContext.state !== 1) return reportBadState(sessionContext); + + logger.accessInfo({ + sessionContext: sessionContext, + message: "CPE request", + rpc: rpc, + }); + + return duStateChangeComplete(sessionContext, rpc); } else { if (sessionContext.state !== 1 || rpc.cpeRequest.name === "Inform") return void reportBadState(sessionContext); diff --git a/lib/session.ts b/lib/session.ts index 45bab72..5cf863e 100644 --- a/lib/session.ts +++ b/lib/session.ts @@ -60,6 +60,7 @@ import { ChangeDUStateOperation, DownloadOperation, DUStateChangeCompleteRequest, + ChangeDUState, } from "./types"; import { getRequestOrigin } from "./forwarded"; import * as logger from "./logger"; @@ -452,8 +453,6 @@ export async function duStateChangeComplete( sessionContext.deviceData.attributes.revision = revision; const commandKey = rpcReq.commandKey; const operation = sessionContext.operations[commandKey]; - if (operation.name !== "ChangeDUState") - throw new Error("Unexpected operation name"); if (!operation) { return { @@ -463,6 +462,21 @@ export async function duStateChangeComplete( }; } + if (operation.name !== "ChangeDUState") + throw new Error("Unexpected operation name"); + + if (operation.args.operations.length !== rpcReq.results.length) { + return { + acsResponse: { name: "DUStateChangeCompleteResponse" }, + operation: operation, + fault: { + code: "invalid_response", + message: "Unexpected number of results in DUStateChangeComplete", + timestamp: operation.timestamp, + }, + }; + } + delete sessionContext.operations[commandKey]; if (!sessionContext.operationsTouched) sessionContext.operationsTouched = {}; sessionContext.operationsTouched[commandKey] = 1; @@ -1577,7 +1591,7 @@ export async function rpcRequest( if (index == null) { index = 0; for (const p of sessionContext.deviceData.paths.find( - Path.parse("DUState.*"), + Path.parse("DUStates.*"), false, true )) { @@ -1593,7 +1607,7 @@ export async function rpcRequest( toClear = device.set( sessionContext.deviceData, - Path.parse("DUState"), + Path.parse("DUStates"), timestamp, { object: [timestamp, 1], writable: [timestamp, 1] }, toClear @@ -1601,7 +1615,7 @@ export async function rpcRequest( toClear = device.set( sessionContext.deviceData, - Path.parse(`DUState.${index}`), + Path.parse(`DUStates.${index}`), timestamp, { object: [timestamp, 1], writable: [timestamp, 1] }, toClear @@ -1653,7 +1667,7 @@ export async function rpcRequest( for (const [k, v] of Object.entries(params)) { toClear = device.set( sessionContext.deviceData, - Path.parse(`DUState.${index}.${k}`), + Path.parse(`DUStates.${index}.${k}`), timestamp, { object: [timestamp, 0], @@ -1669,7 +1683,7 @@ export async function rpcRequest( toClear = device.set( sessionContext.deviceData, - Path.parse(`DUState.${index}.*`), + Path.parse(`DUStates.${index}.*`), timestamp, null, toClear @@ -2014,6 +2028,7 @@ function generateSetRpcRequest( | FactoryReset | Reboot | Download + | ChangeDUState ) & { next?: string } { const syncState = sessionContext.syncState; if (!syncState) return null; @@ -2192,6 +2207,76 @@ function generateSetRpcRequest( } } + // ChangeDUState + const duStateOps: ChangeDUState["operations"] = []; + for (const [p, t] of syncState.duStatesChangeDUState) { + const attrs = deviceData.attributes.get(p); + if (attrs && attrs.value && t <= attrs.value[1][0]) continue; + const urlAttrs = deviceData.attributes.get( + deviceData.paths.get(p.slice(0, -1).concat(Path.parse("URL"))) + ); + const operationTypeAttrs = deviceData.attributes.get( + deviceData.paths.get(p.slice(0, -1).concat(Path.parse("OperationType"))) + ); + const uuidAttrs = deviceData.attributes.get( + deviceData.paths.get(p.slice(0, -1).concat(Path.parse("UUID"))) + ); + const executionEnvRefAttrs = deviceData.attributes.get( + deviceData.paths.get(p.slice(0, -1).concat(Path.parse("ExecutionEnvRef"))) + ); + const versionAttrs = deviceData.attributes.get( + deviceData.paths.get(p.slice(0, -1).concat(Path.parse("Version"))) + ); + const usernameAttrs = deviceData.attributes.get( + deviceData.paths.get(p.slice(0, -1).concat(Path.parse("Username"))) + ); + const passwordAttrs = deviceData.attributes.get( + deviceData.paths.get(p.slice(0, -1).concat(Path.parse("Password"))) + ); + + const operationType = operationTypeAttrs + ? (operationTypeAttrs.value[1][0] as string) + : null; + const url = urlAttrs ? (urlAttrs.value[1][0] as string) : null; + const uuid = uuidAttrs ? (uuidAttrs.value[1][0] as string) : null; + const executionEnvRef = executionEnvRefAttrs + ? (executionEnvRefAttrs.value[1][0] as string) + : null; + const version = versionAttrs ? (versionAttrs.value[1][0] as string) : null; + const username = usernameAttrs + ? (usernameAttrs.value[1][0] as string) + : null; + const password = passwordAttrs + ? (passwordAttrs.value[1][0] as string) + : null; + + if ( + operationType !== "install" && + operationType !== "update" && + operationType !== "uninstall" + ) + continue; + + duStateOps.push({ + operationType: operationType, + instance: p.segments[1] as string, + url: url, + uuid: uuid, + executionEnvRef: executionEnvRef, + version: version, + username: username, + password: password, + }); + } + + if (duStateOps.length) { + return { + name: "ChangeDUState", + commandKey: generateRpcId(sessionContext), + operations: duStateOps, + }; + } + // Reboot if (syncState.reboot) { const p = sessionContext.deviceData.paths.get(Path.parse("Reboot")); @@ -2473,7 +2558,7 @@ function processDeclarations( } } break; - case "DUState": + case "DUStates": if ( currentPath.length === 3 && currentPath.wildcard === 0 && @@ -2955,7 +3040,7 @@ export async function rpcResponse( "FactoryReset", "VirtualParameters", "Downloads", - "DUState", + "DUStates", ]) { const p = sessionContext.deviceData.paths.get(Path.parse(n)); if (p && sessionContext.deviceData.attributes.has(p)) @@ -3186,7 +3271,7 @@ export async function rpcResponse( for (const op of rpcReq.operations) { toClear = device.set( sessionContext.deviceData, - Path.parse(`DuStates.${op.instance}.ChangeDUState`), + Path.parse(`DUStates.${op.instance}.ChangeDUState`), timestamp + 1, { value: [timestamp + 1, [sessionContext.timestamp, "xsd:dateTime"]] }, toClear diff --git a/lib/soap.ts b/lib/soap.ts index 5ad3ada..17f75ad 100644 --- a/lib/soap.ts +++ b/lib/soap.ts @@ -788,10 +788,10 @@ function DUStateChangeComplete(xml: Element): DUStateChangeCompleteRequest { res.executionUnitRefList = cc.text; break; case "StartTime": - res.startTime = Date.parse(c.text); + if (c.text) res.startTime = Date.parse(c.text); break; case "CompleteTime": - res.completeTime = Date.parse(c.text); + if (c.text) res.completeTime = Date.parse(c.text); break; case "Fault": res.fault = { @@ -803,8 +803,8 @@ function DUStateChangeComplete(xml: Element): DUStateChangeCompleteRequest { }; break; } - results.push(res); } + results.push(res); break; } } diff --git a/lib/types.ts b/lib/types.ts index 6eaccfb..dd8ca34 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -304,10 +304,10 @@ export interface ChangeDUState { instance: string; url: string; uuid: string; - version?: string; - executionEnvRef?: string; - username?: string; - password?: string; + version: string; + executionEnvRef: string; + username: string; + password: string; }[]; }