From 2e40ef8622986ca48f5f6bca75bea7a0b01d8d62 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Thu, 12 Jan 2017 23:30:27 -0800 Subject: [PATCH] Better preset loop detection The RPC and iteration counts are now reset for every cycle. --- lib/cwmp.coffee | 76 ++++++++++++++++++++++++++++------------------ lib/session.coffee | 21 +++++++++++-- 2 files changed, 65 insertions(+), 32 deletions(-) diff --git a/lib/cwmp.coffee b/lib/cwmp.coffee index 428dee0..5223f39 100644 --- a/lib/cwmp.coffee +++ b/lib/cwmp.coffee @@ -51,6 +51,8 @@ device = require './device' cache = require './cache' db = require './db' +MAX_CYCLES = 4 + throwError = (err, httpResponse) -> if httpResponse @@ -213,6 +215,8 @@ appendProvisions = (original, toAppend) -> applyPresets = (currentRequest) -> + sessionData = currentRequest.sessionData + cache.getPresets((err, presetsHash, presets) -> return throwError(err, currentRequest.httpResponse) if err @@ -221,28 +225,28 @@ applyPresets = (currentRequest) -> whiteList = null whiteListProvisions = null - RETRY_DELAY = config.get('RETRY_DELAY', currentRequest.sessionData.deviceId) + RETRY_DELAY = config.get('RETRY_DELAY', sessionData.deviceId) - if currentRequest.sessionData.faults? - for channel, fault of currentRequest.sessionData.faults + if sessionData.faults? + for channel, fault of sessionData.faults if fault.retryNow retryTimestamp = 0 else - retryTimestamp = fault.timestamp + (RETRY_DELAY * Math.pow(2, currentRequest.sessionData.retries[channel])) * 1000 + retryTimestamp = fault.timestamp + (RETRY_DELAY * Math.pow(2, sessionData.retries[channel])) * 1000 - if retryTimestamp <= currentRequest.sessionData.timestamp + if retryTimestamp <= sessionData.timestamp whiteList = channel whiteListProvisions = fault.provisions break blackList[channel] = if fault.precondition then 1 else 2 - currentRequest.sessionData.presetsHash = presetsHash + sessionData.presetsHash = presetsHash deviceEvents = {} - iter = currentRequest.sessionData.deviceData.paths.subset(['Events', '*']) + iter = sessionData.deviceData.paths.subset(['Events', '*']) while (p = iter.next().value) - if currentRequest.sessionData.timestamp <= currentRequest.sessionData.deviceData.attributes.get(p)?.value?[1][0] + if sessionData.timestamp <= sessionData.deviceData.attributes.get(p)?.value?[1][0] deviceEvents[p[1]] = true parameters = {} @@ -251,7 +255,7 @@ applyPresets = (currentRequest) -> for preset in presets if whiteList? continue if preset.channel != whiteList - else if blackList[channel] == 1 + else if blackList[preset.channel] == 1 continue eventsMatch = true @@ -261,11 +265,11 @@ applyPresets = (currentRequest) -> break continue if (not eventsMatch) or (preset.schedule and not - (preset.schedule.schedule and testSchedule(preset.schedule, currentRequest.sessionData.timestamp)[0])) + (preset.schedule.schedule and testSchedule(preset.schedule, sessionData.timestamp)[0])) filteredPresets.push(preset) for k of preset.precondition - currentRequest.sessionData.channels[preset.channel] = 0 + sessionData.channels[preset.channel] = 0 p = k.split(/([^a-zA-Z0-9\-\_\.].*)/, 1)[0] parameters[p] = common.parsePath(p) @@ -273,28 +277,28 @@ applyPresets = (currentRequest) -> for k, v of parameters declarations.push([v, 1, {value: 1}]) - session.rpcRequest(currentRequest.sessionData, declarations, (err, fault, id, rpcRequest) -> + session.rpcRequest(sessionData, declarations, (err, fault, id, rpcRequest) -> return throwError(err, currentRequest.httpResponse) if err if fault - recordFault(currentRequest.sessionData, fault, currentRequest.sessionData.provisions, currentRequest.sessionData.channels) - session.clearProvisions(currentRequest.sessionData) + recordFault(sessionData, fault, sessionData.provisions, sessionData.channels) + session.clearProvisions(sessionData) return applyPresets(currentRequest) if rpcRequest? return sendRpcRequest(currentRequest, id, rpcRequest) - session.clearProvisions(currentRequest.sessionData) + session.clearProvisions(sessionData) for k, v of parameters - unpacked = device.unpack(currentRequest.sessionData.deviceData, v) - if unpacked[0] and (vv = currentRequest.sessionData.deviceData.attributes.get(unpacked[0]).value?[1])? + unpacked = device.unpack(sessionData.deviceData, v) + if unpacked[0] and (vv = sessionData.deviceData.attributes.get(unpacked[0]).value?[1])? parameters[k] = vv[0] else delete parameters[k] if whiteList? - session.addProvisions(currentRequest.sessionData, whiteList, whiteListProvisions) + session.addProvisions(sessionData, whiteList, whiteListProvisions) appendProvisionsToFaults = {} for p in filteredPresets @@ -303,27 +307,39 @@ applyPresets = (currentRequest) -> appendProvisionsToFaults[p.channel] = (appendProvisionsToFaults[p.channel] or []).concat(p.provisions) else - session.addProvisions(currentRequest.sessionData, p.channel, p.provisions) + session.addProvisions(sessionData, p.channel, p.provisions) for channel, provisions of appendProvisionsToFaults - if appendProvisions(currentRequest.sessionData.faults[channel].provisions, provisions) - currentRequest.sessionData.faultsTouched ?= {} - currentRequest.sessionData.faultsTouched[channel] = true + if appendProvisions(sessionData.faults[channel].provisions, provisions) + sessionData.faultsTouched ?= {} + sessionData.faultsTouched[channel] = true - session.rpcRequest(currentRequest.sessionData, null, (err, fault, id, rpcRequest) -> + sessionData.presetCycles = (sessionData.presetCycles or 0) + 1 + + if sessionData.presetCycles > MAX_CYCLES + fault = { + code: 'endless_cycle' + message: 'The provision seems to be repeating indefinitely' + timestamp: sessionData.timestamp + } + recordFault(sessionData, fault, sessionData.provisions, sessionData.channels) + session.clearProvisions(sessionData) + return sendRpcRequest(currentRequest) + + session.rpcRequest(sessionData, null, (err, fault, id, rpcRequest) -> return throwError(err, currentRequest.httpResponse) if err if fault - recordFault(currentRequest.sessionData, fault, currentRequest.sessionData.provisions, currentRequest.sessionData.channels) - session.clearProvisions(currentRequest.sessionData) + recordFault(sessionData, fault, sessionData.provisions, sessionData.channels) + session.clearProvisions(sessionData) return applyPresets(currentRequest) if not rpcRequest? - for channel, flags of currentRequest.sessionData.channels - if channel of currentRequest.sessionData.faults - delete currentRequest.sessionData.faults[channel] - currentRequest.sessionData.faultsTouched ?= {} - currentRequest.sessionData.faultsTouched[channel] = true + for channel, flags of sessionData.channels + if channel of sessionData.faults + delete sessionData.faults[channel] + sessionData.faultsTouched ?= {} + sessionData.faultsTouched[channel] = true if whiteList? return applyPresets(currentRequest) diff --git a/lib/session.coffee b/lib/session.coffee index 0c410c6..005c246 100644 --- a/lib/session.coffee +++ b/lib/session.coffee @@ -26,6 +26,8 @@ VersionedMap = require './versioned-map' InstanceSet = require './instance-set' defaultProvisions = require './default-provisions' +MAX_ITERATIONS = 64 + initDeviceData = () -> return { @@ -53,6 +55,7 @@ init = (deviceId, cwmpVersion, timeout, callback) -> revisions: [0] rpcCount: 0 iteration: 0 + cycle: 0 extensionsCache: {} declarations: [] } @@ -105,7 +108,9 @@ loadParameters = (sessionData, callback) -> generateRpcId = (sessionData) -> - return sessionData.timestamp.toString(16) + "0#{sessionData.rpcCount.toString(16)}".slice(-2) + return sessionData.timestamp.toString(16) + + sessionData.cycle.toString(16).slice(-1) + + "0#{sessionData.rpcCount.toString(16)}".slice(-2) inform = (sessionData, rpcReq, callback) -> @@ -282,6 +287,11 @@ addProvisions = (sessionData, channel, provisions) -> sessionData.revisions = [0] sessionData.extensionsCache = {} + if sessionData.iteration != sessionData.cycle * MAX_ITERATIONS + sessionData.cycle += 1 + sessionData.rpcCount = 0 + sessionData.iteration = sessionData.cycle * MAX_ITERATIONS + sessionData.channels[channel] |= 0 for provision, i in provisions @@ -307,9 +317,15 @@ clearProvisions = (sessionData) -> sessionData.deviceData.timestamps.collapse(1) sessionData.deviceData.attributes.collapse(1) + if sessionData.iteration != sessionData.cycle * MAX_ITERATIONS + sessionData.cycle += 1 + sessionData.rpcCount = 0 + sessionData.iteration = sessionData.cycle * MAX_ITERATIONS + delete sessionData.syncState delete sessionData.rpcRequest sessionData.provisions = [] + sessionData.virtualParameters = [] sessionData.channels = {} sessionData.declarations = [] sessionData.doneProvisions = 0 @@ -588,7 +604,8 @@ rpcRequest = (sessionData, _declarations, callback) -> if sessionData.rpcCount >= 255 or sessionData.revisions.length >= 8 or - sessionData.iteration >= 128 + sessionData.cycle >= 16 or + sessionData.iteration >= MAX_ITERATIONS * (sessionData.cycle + 1) fault = { code: 'endless_cycle'