Unify currentRequest and sessionData structures
This commit is contained in:
1 parent
bdcb856655
commit
59d412155b
6 files changed
+1282
-1186
No files matched your search
+259
-261
@@ -63,16 +63,16 @@ throwError = (err, httpResponse) ->
|
|||||||
throw err
|
throw err
|
||||||
|
|
||||||
|
|
||||||
writeResponse = (currentRequest, res) ->
|
writeResponse = (sessionContext, res) ->
|
||||||
if config.get('DEBUG', currentRequest.sessionData.deviceId)
|
if config.get('DEBUG', sessionContext.deviceId)
|
||||||
dump = "# RESPONSE #{new Date(Date.now())}\n" + JSON.stringify(res.headers) + "\n#{res.data}\n\n"
|
dump = "# RESPONSE #{new Date(Date.now())}\n" + JSON.stringify(res.headers) + "\n#{res.data}\n\n"
|
||||||
require('fs').appendFile("./debug/#{currentRequest.sessionData.deviceId}.dump", dump, (err) ->
|
require('fs').appendFile("./debug/#{sessionContext.deviceId}.dump", dump, (err) ->
|
||||||
throwError(err) if err
|
throwError(err) if err
|
||||||
)
|
)
|
||||||
|
|
||||||
# respond using the same content-encoding as the request
|
# respond using the same content-encoding as the request
|
||||||
if currentRequest.httpRequest.headers['content-encoding']? and res.data.length > 0
|
if sessionContext.httpRequest.headers['content-encoding']? and res.data.length > 0
|
||||||
switch currentRequest.httpRequest.headers['content-encoding']
|
switch sessionContext.httpRequest.headers['content-encoding']
|
||||||
when 'gzip'
|
when 'gzip'
|
||||||
res.headers['Content-Encoding'] = 'gzip'
|
res.headers['Content-Encoding'] = 'gzip'
|
||||||
compress = zlib.gzip
|
compress = zlib.gzip
|
||||||
@@ -82,44 +82,50 @@ writeResponse = (currentRequest, res) ->
|
|||||||
|
|
||||||
if compress?
|
if compress?
|
||||||
compress(res.data, (err, data) ->
|
compress(res.data, (err, data) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
res.headers['Content-Length'] = data.length
|
res.headers['Content-Length'] = data.length
|
||||||
currentRequest.httpResponse.writeHead(res.code, res.headers)
|
sessionContext.httpResponse.writeHead(res.code, res.headers)
|
||||||
currentRequest.httpResponse.end(data)
|
sessionContext.httpResponse.end(data)
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
res.headers['Content-Length'] = res.data.length
|
res.headers['Content-Length'] = res.data.length
|
||||||
currentRequest.httpResponse.writeHead(res.code, res.headers)
|
sessionContext.httpResponse.writeHead(res.code, res.headers)
|
||||||
currentRequest.httpResponse.end(res.data)
|
sessionContext.httpResponse.end(res.data)
|
||||||
|
|
||||||
|
delete sessionContext.httpRequest
|
||||||
|
delete sessionContext.httpResponse
|
||||||
|
|
||||||
|
|
||||||
recordFault = (currentRequest, fault, provisions, channels) ->
|
recordFault = (sessionContext, fault, provisions, channels) ->
|
||||||
sessionData = currentRequest.sessionData
|
if not provisions
|
||||||
faults = sessionData.faults
|
provisions = sessionContext.provisions
|
||||||
|
channels = sessionContext.channels
|
||||||
|
|
||||||
|
faults = sessionContext.faults
|
||||||
|
|
||||||
for channel of channels
|
for channel of channels
|
||||||
provs = sessionData.faults[channel]?.provisions or []
|
provs = sessionContext.faults[channel]?.provisions or []
|
||||||
faults[channel] = Object.assign({provisions: provs}, fault)
|
faults[channel] = Object.assign({provisions: provs}, fault)
|
||||||
|
|
||||||
if sessionData.retries[channel]?
|
if sessionContext.retries[channel]?
|
||||||
++ sessionData.retries[channel]
|
++ sessionContext.retries[channel]
|
||||||
else
|
else
|
||||||
sessionData.retries[channel] = 0
|
sessionContext.retries[channel] = 0
|
||||||
if Object.keys(channels).length != 1
|
if Object.keys(channels).length != 1
|
||||||
faults[channel].retryNow = true
|
faults[channel].retryNow = true
|
||||||
|
|
||||||
if channels[channel] == 0
|
if channels[channel] == 0
|
||||||
faults[channel].precondition = true
|
faults[channel].precondition = true
|
||||||
|
|
||||||
sessionData.faultsTouched ?= {}
|
sessionContext.faultsTouched ?= {}
|
||||||
sessionData.faultsTouched[channel] = true
|
sessionContext.faultsTouched[channel] = true
|
||||||
|
|
||||||
logger.accessError({
|
logger.accessError({
|
||||||
currentRequest: currentRequest
|
sessionContext: sessionContext
|
||||||
message: 'Channel has faulted'
|
message: 'Channel has faulted'
|
||||||
fault: fault
|
fault: fault
|
||||||
channel: channel
|
channel: channel
|
||||||
retries: sessionData.retries[channel]
|
retries: sessionContext.retries[channel]
|
||||||
})
|
})
|
||||||
|
|
||||||
for provision, i in provisions
|
for provision, i in provisions
|
||||||
@@ -132,51 +138,51 @@ recordFault = (currentRequest, fault, provisions, channels) ->
|
|||||||
faults[channel].provisions = []
|
faults[channel].provisions = []
|
||||||
appendProvisions(faults[channel].provisions, provs)
|
appendProvisions(faults[channel].provisions, provs)
|
||||||
|
|
||||||
session.clearProvisions(sessionData)
|
session.clearProvisions(sessionContext)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
inform = (currentRequest, cwmpRequest) ->
|
inform = (sessionContext, rpc) ->
|
||||||
session.inform(currentRequest.sessionData, cwmpRequest.methodRequest, (err, rpcResponse) ->
|
session.inform(sessionContext, rpc.cpeRequest, (err, acsResponse) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
res = soap.response({
|
res = soap.response({
|
||||||
id : cwmpRequest.id,
|
id: rpc.id,
|
||||||
methodResponse : rpcResponse,
|
acsResponse: acsResponse,
|
||||||
cwmpVersion : currentRequest.sessionData.cwmpVersion
|
cwmpVersion : sessionContext.cwmpVersion
|
||||||
})
|
})
|
||||||
|
|
||||||
if !!cookiesPath = config.get('COOKIES_PATH', currentRequest.sessionData.deviceId)
|
if !!cookiesPath = config.get('COOKIES_PATH', sessionContext.deviceId)
|
||||||
res.headers['Set-Cookie'] = "session=#{currentRequest.sessionData.sessionId}; Path=#{cookiesPath}"
|
res.headers['Set-Cookie'] = "session=#{sessionContext.sessionId}; Path=#{cookiesPath}"
|
||||||
else
|
else
|
||||||
res.headers['Set-Cookie'] = "session=#{currentRequest.sessionData.sessionId}"
|
res.headers['Set-Cookie'] = "session=#{sessionContext.sessionId}"
|
||||||
|
|
||||||
writeResponse(currentRequest, res)
|
writeResponse(sessionContext, res)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
transferComplete = (currentRequest, cwmpRequest) ->
|
transferComplete = (sessionContext, rpc) ->
|
||||||
session.transferComplete(currentRequest.sessionData, cwmpRequest.methodRequest, (err, rpcResponse, operation, fault) ->
|
session.transferComplete(sessionContext, rpc.cpeRequest, (err, acsResponse, operation, fault) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
|
|
||||||
if not operation?
|
if not operation?
|
||||||
logger.accessWarn({
|
logger.accessWarn({
|
||||||
currentRequest: currentRequest
|
sessionContext: sessionContext
|
||||||
message: 'Unrecognized command key'
|
message: 'Unrecognized command key'
|
||||||
cpeRequest: cwmpRequest.methodRequest
|
rpc: rpc
|
||||||
})
|
})
|
||||||
|
|
||||||
if fault
|
if fault
|
||||||
for k, v of operation.retries
|
for k, v of operation.retries
|
||||||
currentRequest.sessionData.retries[k] = v
|
sessionContext.retries[k] = v
|
||||||
|
|
||||||
recordFault(currentRequest, fault, operation.provisions, operation.channels)
|
recordFault(sessionContext, fault)
|
||||||
|
|
||||||
res = soap.response({
|
res = soap.response({
|
||||||
id : cwmpRequest.id,
|
id : rpc.id,
|
||||||
methodResponse : rpcResponse,
|
acsResponse : acsResponse,
|
||||||
cwmpVersion : currentRequest.sessionData.cwmpVersion
|
cwmpVersion : sessionContext.cwmpVersion
|
||||||
})
|
})
|
||||||
writeResponse(currentRequest, res)
|
writeResponse(sessionContext, res)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -227,38 +233,34 @@ appendProvisions = (original, toAppend) ->
|
|||||||
return modified
|
return modified
|
||||||
|
|
||||||
|
|
||||||
applyPresets = (currentRequest) ->
|
applyPresets = (sessionContext) ->
|
||||||
sessionData = currentRequest.sessionData
|
|
||||||
|
|
||||||
cache.getPresets((err, presetsHash, presets) ->
|
cache.getPresets((err, presetsHash, presets) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
|
|
||||||
# Filter presets based on existing faults
|
# Filter presets based on existing faults
|
||||||
blackList = {}
|
blackList = {}
|
||||||
whiteList = null
|
whiteList = null
|
||||||
whiteListProvisions = null
|
whiteListProvisions = null
|
||||||
|
|
||||||
RETRY_DELAY = config.get('RETRY_DELAY', sessionData.deviceId)
|
RETRY_DELAY = config.get('RETRY_DELAY', sessionContext.deviceId)
|
||||||
|
|
||||||
if sessionData.faults?
|
if sessionContext.faults?
|
||||||
for channel, fault of sessionData.faults
|
for channel, fault of sessionContext.faults
|
||||||
if fault.retryNow
|
if fault.retryNow
|
||||||
retryTimestamp = 0
|
retryTimestamp = 0
|
||||||
else
|
else
|
||||||
retryTimestamp = fault.timestamp + (RETRY_DELAY * Math.pow(2, sessionData.retries[channel])) * 1000
|
retryTimestamp = fault.timestamp + (RETRY_DELAY * Math.pow(2, sessionContext.retries[channel])) * 1000
|
||||||
|
|
||||||
if retryTimestamp <= sessionData.timestamp
|
if retryTimestamp <= sessionContext.timestamp
|
||||||
whiteList = channel
|
whiteList = channel
|
||||||
whiteListProvisions = fault.provisions
|
whiteListProvisions = fault.provisions
|
||||||
break
|
break
|
||||||
|
|
||||||
blackList[channel] = if fault.precondition then 1 else 2
|
blackList[channel] = if fault.precondition then 1 else 2
|
||||||
|
|
||||||
sessionData.presetsHash = presetsHash
|
|
||||||
|
|
||||||
deviceEvents = {}
|
deviceEvents = {}
|
||||||
for p in sessionData.deviceData.paths.find(['Events', '*'], false, true)
|
for p in sessionContext.deviceData.paths.find(['Events', '*'], false, true)
|
||||||
if sessionData.timestamp <= sessionData.deviceData.attributes.get(p)?.value?[1][0]
|
if sessionContext.timestamp <= sessionContext.deviceData.attributes.get(p)?.value?[1][0]
|
||||||
deviceEvents[p[1]] = true
|
deviceEvents[p[1]] = true
|
||||||
|
|
||||||
parameters = {}
|
parameters = {}
|
||||||
@@ -277,11 +279,11 @@ applyPresets = (currentRequest) ->
|
|||||||
break
|
break
|
||||||
|
|
||||||
continue if (not eventsMatch) or (preset.schedule and not
|
continue if (not eventsMatch) or (preset.schedule and not
|
||||||
(preset.schedule.schedule and testSchedule(preset.schedule, sessionData.timestamp)[0]))
|
(preset.schedule.schedule and testSchedule(preset.schedule, sessionContext.timestamp)[0]))
|
||||||
|
|
||||||
filteredPresets.push(preset)
|
filteredPresets.push(preset)
|
||||||
for k of preset.precondition
|
for k of preset.precondition
|
||||||
sessionData.channels[preset.channel] = 0
|
sessionContext.channels[preset.channel] = 0
|
||||||
p = k.split(/([^a-zA-Z0-9\-\_\.].*)/, 1)[0]
|
p = k.split(/([^a-zA-Z0-9\-\_\.].*)/, 1)[0]
|
||||||
parameters[p] = common.parsePath(p)
|
parameters[p] = common.parsePath(p)
|
||||||
|
|
||||||
@@ -289,28 +291,28 @@ applyPresets = (currentRequest) ->
|
|||||||
for k, v of parameters
|
for k, v of parameters
|
||||||
declarations.push([v, 1, {value: 1}])
|
declarations.push([v, 1, {value: 1}])
|
||||||
|
|
||||||
session.rpcRequest(sessionData, declarations, (err, fault, id, rpcRequest) ->
|
session.rpcRequest(sessionContext, declarations, (err, fault, id, acsRequest) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
|
|
||||||
if fault
|
if fault
|
||||||
recordFault(currentRequest, fault, sessionData.provisions, sessionData.channels)
|
recordFault(sessionContext, fault)
|
||||||
session.clearProvisions(sessionData)
|
session.clearProvisions(sessionContext)
|
||||||
return applyPresets(currentRequest)
|
return applyPresets(sessionContext)
|
||||||
|
|
||||||
if rpcRequest?
|
if acsRequest
|
||||||
return sendRpcRequest(currentRequest, id, rpcRequest)
|
return sendAcsRequest(sessionContext, id, acsRequest)
|
||||||
|
|
||||||
session.clearProvisions(sessionData)
|
session.clearProvisions(sessionContext)
|
||||||
|
|
||||||
for k, v of parameters
|
for k, v of parameters
|
||||||
unpacked = device.unpack(sessionData.deviceData, v)
|
unpacked = device.unpack(sessionContext.deviceData, v)
|
||||||
if unpacked[0] and (vv = sessionData.deviceData.attributes.get(unpacked[0]).value?[1])?
|
if unpacked[0] and (vv = sessionContext.deviceData.attributes.get(unpacked[0]).value?[1])?
|
||||||
parameters[k] = vv[0]
|
parameters[k] = vv[0]
|
||||||
else
|
else
|
||||||
delete parameters[k]
|
delete parameters[k]
|
||||||
|
|
||||||
if whiteList?
|
if whiteList?
|
||||||
session.addProvisions(sessionData, whiteList, whiteListProvisions)
|
session.addProvisions(sessionContext, whiteList, whiteListProvisions)
|
||||||
|
|
||||||
appendProvisionsToFaults = {}
|
appendProvisionsToFaults = {}
|
||||||
for p in filteredPresets
|
for p in filteredPresets
|
||||||
@@ -319,221 +321,222 @@ applyPresets = (currentRequest) ->
|
|||||||
appendProvisionsToFaults[p.channel] =
|
appendProvisionsToFaults[p.channel] =
|
||||||
(appendProvisionsToFaults[p.channel] or []).concat(p.provisions)
|
(appendProvisionsToFaults[p.channel] or []).concat(p.provisions)
|
||||||
else
|
else
|
||||||
session.addProvisions(sessionData, p.channel, p.provisions)
|
session.addProvisions(sessionContext, p.channel, p.provisions)
|
||||||
|
|
||||||
for channel, provisions of appendProvisionsToFaults
|
for channel, provisions of appendProvisionsToFaults
|
||||||
if appendProvisions(sessionData.faults[channel].provisions, provisions)
|
if appendProvisions(sessionContext.faults[channel].provisions, provisions)
|
||||||
sessionData.faultsTouched ?= {}
|
sessionContext.faultsTouched ?= {}
|
||||||
sessionData.faultsTouched[channel] = true
|
sessionContext.faultsTouched[channel] = true
|
||||||
|
|
||||||
sessionData.presetCycles = (sessionData.presetCycles or 0) + 1
|
sessionContext.presetCycles = (sessionContext.presetCycles or 0) + 1
|
||||||
|
|
||||||
if sessionData.presetCycles > MAX_CYCLES
|
if sessionContext.presetCycles > MAX_CYCLES
|
||||||
fault = {
|
fault = {
|
||||||
code: 'endless_cycle'
|
code: 'endless_cycle'
|
||||||
message: 'The provision seems to be repeating indefinitely'
|
message: 'The provision seems to be repeating indefinitely'
|
||||||
timestamp: sessionData.timestamp
|
timestamp: sessionContext.timestamp
|
||||||
}
|
}
|
||||||
recordFault(currentRequest, fault, sessionData.provisions, sessionData.channels)
|
recordFault(sessionContext, fault)
|
||||||
session.clearProvisions(sessionData)
|
session.clearProvisions(sessionContext)
|
||||||
return sendRpcRequest(currentRequest)
|
return sendAcsRequest(sessionContext)
|
||||||
|
|
||||||
session.rpcRequest(sessionData, null, (err, fault, id, rpcRequest) ->
|
session.rpcRequest(sessionContext, null, (err, fault, id, acsRequest) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
|
|
||||||
if fault
|
if fault
|
||||||
recordFault(currentRequest, fault, sessionData.provisions, sessionData.channels)
|
recordFault(sessionContext, fault)
|
||||||
session.clearProvisions(sessionData)
|
session.clearProvisions(sessionContext)
|
||||||
return applyPresets(currentRequest)
|
return applyPresets(sessionContext)
|
||||||
|
|
||||||
if not rpcRequest?
|
if not acsRequest
|
||||||
for channel, flags of sessionData.channels
|
for channel, flags of sessionContext.channels
|
||||||
if channel of sessionData.faults
|
if channel of sessionContext.faults
|
||||||
delete sessionData.faults[channel]
|
delete sessionContext.faults[channel]
|
||||||
sessionData.faultsTouched ?= {}
|
sessionContext.faultsTouched ?= {}
|
||||||
sessionData.faultsTouched[channel] = true
|
sessionContext.faultsTouched[channel] = true
|
||||||
|
|
||||||
if whiteList?
|
if whiteList?
|
||||||
return applyPresets(currentRequest)
|
return applyPresets(sessionContext)
|
||||||
|
|
||||||
sendRpcRequest(currentRequest, id, rpcRequest)
|
sendAcsRequest(sessionContext, id, acsRequest)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
nextRpc = (currentRequest) ->
|
nextRpc = (sessionContext) ->
|
||||||
session.rpcRequest(currentRequest.sessionData, null, (err, fault, id, rpcRequest) ->
|
session.rpcRequest(sessionContext, null, (err, fault, id, acsRequest) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
|
|
||||||
if fault
|
if fault
|
||||||
recordFault(currentRequest, fault, currentRequest.sessionData.provisions, currentRequest.sessionData.channels)
|
recordFault(sessionContext, fault)
|
||||||
session.clearProvisions(currentRequest.sessionData)
|
session.clearProvisions(sessionContext)
|
||||||
return nextRpc(currentRequest)
|
return nextRpc(sessionContext)
|
||||||
|
|
||||||
if rpcRequest?
|
if acsRequest
|
||||||
return sendRpcRequest(currentRequest, id, rpcRequest)
|
return sendAcsRequest(sessionContext, id, acsRequest)
|
||||||
|
|
||||||
for channel, flags of currentRequest.sessionData.channels
|
for channel, flags of sessionContext.channels
|
||||||
if flags and channel of currentRequest.sessionData.faults
|
if flags and channel of sessionContext.faults
|
||||||
delete currentRequest.sessionData.faults[channel]
|
delete sessionContext.faults[channel]
|
||||||
currentRequest.sessionData.faultsTouched ?= {}
|
sessionContext.faultsTouched ?= {}
|
||||||
currentRequest.sessionData.faultsTouched[channel] = true
|
sessionContext.faultsTouched[channel] = true
|
||||||
|
|
||||||
if channel.startsWith('task_')
|
if channel.startsWith('task_')
|
||||||
taskId = channel.slice(5)
|
taskId = channel.slice(5)
|
||||||
currentRequest.sessionData.doneTasks ?= []
|
sessionContext.doneTasks ?= []
|
||||||
currentRequest.sessionData.doneTasks.push(taskId)
|
sessionContext.doneTasks.push(taskId)
|
||||||
for t, j in currentRequest.sessionData.tasks
|
for t, j in sessionContext.tasks
|
||||||
if t._id == taskId
|
if t._id == taskId
|
||||||
currentRequest.sessionData.tasks.splice(j, 1)
|
sessionContext.tasks.splice(j, 1)
|
||||||
break
|
break
|
||||||
|
|
||||||
session.clearProvisions(currentRequest.sessionData)
|
session.clearProvisions(sessionContext)
|
||||||
|
|
||||||
for task in currentRequest.sessionData.tasks
|
for task in sessionContext.tasks
|
||||||
channel = "task_#{task._id}"
|
channel = "task_#{task._id}"
|
||||||
|
|
||||||
# Delete if expired
|
# Delete if expired
|
||||||
if task.expiry <= currentRequest.sessionData.timestamp
|
if task.expiry <= sessionContext.timestamp
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
currentRequest: currentRequest
|
sessionContext: sessionContext
|
||||||
message: 'Task expired'
|
message: 'Task expired'
|
||||||
task: task
|
task: task
|
||||||
})
|
})
|
||||||
currentRequest.sessionData.doneTasks ?= []
|
sessionContext.doneTasks ?= []
|
||||||
currentRequest.sessionData.doneTasks.push(String(task._id))
|
sessionContext.doneTasks.push(String(task._id))
|
||||||
if channel of currentRequest.sessionData.faults
|
if channel of sessionContext.faults
|
||||||
delete currentRequest.sessionData.faults[channel]
|
delete sessionContext.faults[channel]
|
||||||
currentRequest.sessionData.faultsTouched ?= {}
|
sessionContext.faultsTouched ?= {}
|
||||||
currentRequest.sessionData.faultsTouched[channel] = true
|
sessionContext.faultsTouched[channel] = true
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if currentRequest.sessionData.faults[channel]
|
if sessionContext.faults[channel]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
switch task.name
|
switch task.name
|
||||||
when 'getParameterValues'
|
when 'getParameterValues'
|
||||||
# Set channel in case params array is empty
|
# Set channel in case params array is empty
|
||||||
currentRequest.sessionData.channels["task_#{task._id}"] = 0
|
sessionContext.channels["task_#{task._id}"] = 0
|
||||||
for p in task.parameterNames
|
for p in task.parameterNames
|
||||||
session.addProvisions(currentRequest.sessionData, "task_#{task._id}",
|
session.addProvisions(sessionContext, "task_#{task._id}",
|
||||||
[['refresh', p]])
|
[['refresh', p]])
|
||||||
when 'setParameterValues'
|
when 'setParameterValues'
|
||||||
# Set channel in case params array is empty
|
# Set channel in case params array is empty
|
||||||
currentRequest.sessionData.channels["task_#{task._id}"] = 0
|
sessionContext.channels["task_#{task._id}"] = 0
|
||||||
for p in task.parameterValues
|
for p in task.parameterValues
|
||||||
session.addProvisions(currentRequest.sessionData, "task_#{task._id}",
|
session.addProvisions(sessionContext, "task_#{task._id}",
|
||||||
[['value', p[0], p[1]]])
|
[['value', p[0], p[1]]])
|
||||||
when 'refreshObject'
|
when 'refreshObject'
|
||||||
session.addProvisions(currentRequest.sessionData, "task_#{task._id}",
|
session.addProvisions(sessionContext, "task_#{task._id}",
|
||||||
[['refresh', task.objectName]])
|
[['refresh', task.objectName]])
|
||||||
when 'reboot'
|
when 'reboot'
|
||||||
session.addProvisions(currentRequest.sessionData, "task_#{task._id}",
|
session.addProvisions(sessionContext, "task_#{task._id}",
|
||||||
[['reboot']])
|
[['reboot']])
|
||||||
when 'factoryReset'
|
when 'factoryReset'
|
||||||
session.addProvisions(currentRequest.sessionData, "task_#{task._id}",
|
session.addProvisions(sessionContext, "task_#{task._id}",
|
||||||
[['reset']])
|
[['reset']])
|
||||||
when 'download'
|
when 'download'
|
||||||
session.addProvisions(currentRequest.sessionData, "task_#{task._id}",
|
session.addProvisions(sessionContext, "task_#{task._id}",
|
||||||
[['download', task.fileType, task.fileName, task.targetFileName]])
|
[['download', task.fileType, task.fileName, task.targetFileName]])
|
||||||
when 'addObject'
|
when 'addObject'
|
||||||
alias = ("#{p[0]}:#{JSON.stringify(p[1])}" for p in task.parameterValues or []).join(',')
|
alias = ("#{p[0]}:#{JSON.stringify(p[1])}" for p in task.parameterValues or []).join(',')
|
||||||
session.addProvisions(currentRequest.sessionData, "task_#{task._id}",
|
session.addProvisions(sessionContext, "task_#{task._id}",
|
||||||
[['instances', "#{task.objectName}.[#{alias}]", "+1"]]);
|
[['instances', "#{task.objectName}.[#{alias}]", "+1"]]);
|
||||||
when 'deleteObject'
|
when 'deleteObject'
|
||||||
session.addProvisions(currentRequest.sessionData, "task_#{task._id}",
|
session.addProvisions(sessionContext, "task_#{task._id}",
|
||||||
[['instances', task.objectName, 0]]);
|
[['instances', task.objectName, 0]]);
|
||||||
else
|
else
|
||||||
return throwError(new Error('Task name not recognized'), currentRequest.httpResponse) if err
|
return throwError(new Error('Task name not recognized'), sessionContext.httpResponse) if err
|
||||||
|
|
||||||
return nextRpc(currentRequest)
|
return nextRpc(sessionContext)
|
||||||
|
|
||||||
return applyPresets(currentRequest)
|
return applyPresets(sessionContext)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
sendRpcRequest = (currentRequest, id, rpcRequest) ->
|
sendAcsRequest = (sessionContext, id, acsRequest) ->
|
||||||
if not rpcRequest?
|
if not acsRequest?
|
||||||
session.end(currentRequest.sessionData, (err, isNew) ->
|
session.end(sessionContext, (err, isNew) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
|
|
||||||
delete currentSessions.get(currentRequest.httpRequest.connection)[currentRequest.sessionData.sessionId]
|
delete currentSessions.get(sessionContext.httpRequest.connection)[sessionContext.sessionId]
|
||||||
if isNew
|
if isNew
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
currentRequest: currentRequest
|
sessionContext: sessionContext
|
||||||
message: 'New device registered'
|
message: 'New device registered'
|
||||||
})
|
})
|
||||||
|
|
||||||
db.clearTasks(currentRequest.sessionData.deviceId, currentRequest.sessionData.doneTasks, (err) ->
|
db.clearTasks(sessionContext.deviceId, sessionContext.doneTasks, (err) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
|
|
||||||
counter = 3
|
counter = 3
|
||||||
|
|
||||||
for k of currentRequest.sessionData.faultsTouched
|
for k of sessionContext.faultsTouched
|
||||||
counter += 2
|
counter += 2
|
||||||
if currentRequest.sessionData.faults[k]
|
if sessionContext.faults[k]
|
||||||
currentRequest.sessionData.faults[k].retries = currentRequest.sessionData.retries[k]
|
sessionContext.faults[k].retries = sessionContext.retries[k]
|
||||||
db.saveFault(currentRequest.sessionData.deviceId, k, currentRequest.sessionData.faults[k], (err) ->
|
db.saveFault(sessionContext.deviceId, k, sessionContext.faults[k], (err) ->
|
||||||
if err
|
if err
|
||||||
throwError(err, currentRequest.httpResponse) if counter & 1
|
throwError(err, sessionContext.httpResponse) if counter & 1
|
||||||
return counter = 0
|
return counter = 0
|
||||||
|
|
||||||
if (counter -= 2) == 1
|
if (counter -= 2) == 1
|
||||||
return writeResponse(currentRequest, soap.response(null))
|
return writeResponse(sessionContext, soap.response(null))
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
db.deleteFault(currentRequest.sessionData.deviceId, k, (err) ->
|
db.deleteFault(sessionContext.deviceId, k, (err) ->
|
||||||
if err
|
if err
|
||||||
throwError(err, currentRequest.httpResponse) if counter & 1
|
throwError(err, sessionContext.httpResponse) if counter & 1
|
||||||
return counter = 0
|
return counter = 0
|
||||||
|
|
||||||
if (counter -= 2) == 1
|
if (counter -= 2) == 1
|
||||||
return writeResponse(currentRequest, soap.response(null))
|
return writeResponse(sessionContext, soap.response(null))
|
||||||
)
|
)
|
||||||
|
|
||||||
if (counter -= 2) == 1
|
if (counter -= 2) == 1
|
||||||
return writeResponse(currentRequest, soap.response(null))
|
return writeResponse(sessionContext, soap.response(null))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if rpcRequest.type is 'Download'
|
if acsRequest.name is 'Download'
|
||||||
if not rpcRequest.url?
|
if not acsRequest.url?
|
||||||
FS_PORT = config.get('FS_PORT')
|
FS_PORT = config.get('FS_PORT')
|
||||||
FS_IP = config.get('FS_IP')
|
FS_IP = config.get('FS_IP')
|
||||||
FS_SSL = config.get('FS_SSL')
|
FS_SSL = config.get('FS_SSL')
|
||||||
rpcRequest.url = if FS_SSL then 'https://' else 'http://'
|
acsRequest.url = if FS_SSL then 'https://' else 'http://'
|
||||||
rpcRequest.url += FS_IP
|
acsRequest.url += FS_IP
|
||||||
rpcRequest.url += ":#{FS_PORT}" if FS_PORT != 80
|
acsRequest.url += ":#{FS_PORT}" if FS_PORT != 80
|
||||||
rpcRequest.url += "/#{encodeURIComponent(rpcRequest.fileName)}"
|
acsRequest.url += "/#{encodeURIComponent(acsRequest.fileName)}"
|
||||||
|
|
||||||
if not rpcRequest.fileSize?
|
if not acsRequest.fileSize?
|
||||||
return cache.getFiles((err, hash, files) ->
|
return cache.getFiles((err, hash, files) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
|
|
||||||
if rpcRequest.fileName of files
|
if acsRequest.fileName of files
|
||||||
rpcRequest.fileSize = files[rpcRequest.fileName].length
|
acsRequest.fileSize = files[acsRequest.fileName].length
|
||||||
else
|
else
|
||||||
rpcRequest.fileSize = 0
|
acsRequest.fileSize = 0
|
||||||
|
|
||||||
return sendRpcRequest(currentRequest, id, rpcRequest)
|
return sendAcsRequest(sessionContext, id, acsRequest)
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.accessInfo({
|
rpc = {
|
||||||
currentRequest: currentRequest
|
|
||||||
message: 'ACS request'
|
|
||||||
acsRequest: rpcRequest
|
|
||||||
acsRequestId: id
|
|
||||||
})
|
|
||||||
|
|
||||||
res = soap.response({
|
|
||||||
id : id,
|
id : id,
|
||||||
methodRequest : rpcRequest,
|
acsRequest : acsRequest,
|
||||||
cwmpVersion : currentRequest.sessionData.cwmpVersion
|
cwmpVersion : sessionContext.cwmpVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.accessInfo({
|
||||||
|
sessionContext: sessionContext
|
||||||
|
message: 'ACS request'
|
||||||
|
rpc: rpc
|
||||||
})
|
})
|
||||||
|
|
||||||
writeResponse(currentRequest, res)
|
res = soap.response(rpc)
|
||||||
|
|
||||||
|
writeResponse(sessionContext, res)
|
||||||
|
|
||||||
|
|
||||||
getSession = (httpRequest, callback) ->
|
getSession = (httpRequest, callback) ->
|
||||||
@@ -544,18 +547,18 @@ getSession = (httpRequest, callback) ->
|
|||||||
|
|
||||||
return callback() if not sessionId?
|
return callback() if not sessionId?
|
||||||
|
|
||||||
sessionData = currentSessions.get(httpRequest.connection)?[sessionId]
|
sessionContext = currentSessions.get(httpRequest.connection)?[sessionId]
|
||||||
|
|
||||||
if sessionData?
|
if sessionContext?
|
||||||
return callback(null, sessionData)
|
return callback(null, sessionContext)
|
||||||
|
|
||||||
setTimeout(() ->
|
setTimeout(() ->
|
||||||
db.redisClient.eval('local v=redis.call("get",KEYS[1]);redis.call("del",KEYS[1]);return v;', 1, "session_#{sessionId}", (err, sessionDataString) ->
|
db.redisClient.eval('local v=redis.call("get",KEYS[1]);redis.call("del",KEYS[1]);return v;', 1, "session_#{sessionId}", (err, sessionContextString) ->
|
||||||
return callback(err) if err or not sessionDataString
|
return callback(err) if err or not sessionContextString
|
||||||
session.deserialize(sessionDataString, (err, sessionData) ->
|
session.deserialize(sessionContextString, (err, sessionContext) ->
|
||||||
return callback(err) if err
|
return callback(err) if err
|
||||||
currentSessions.get(httpRequest.connection)[sessionId] = sessionData
|
currentSessions.get(httpRequest.connection)[sessionId] = sessionContext
|
||||||
callback(null, sessionData)
|
callback(null, sessionContext)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
, 100
|
, 100
|
||||||
@@ -569,11 +572,13 @@ onConnection = (socket) ->
|
|||||||
currentSessions.set(socket, {})
|
currentSessions.set(socket, {})
|
||||||
socket.on('close', () ->
|
socket.on('close', () ->
|
||||||
sessions = currentSessions.get(socket)
|
sessions = currentSessions.get(socket)
|
||||||
for sessionId, sessionData of sessions
|
for sessionId, sessionContext of sessions
|
||||||
session.serialize(sessionData, (err, sessionDataString) ->
|
delete sessionContext.httpRequest
|
||||||
|
delete sessionContext.httpResponse
|
||||||
|
session.serialize(sessionContext, (err, sessionContextString) ->
|
||||||
return throwError(err) if err
|
return throwError(err) if err
|
||||||
# TODO don't set if process is shutting down
|
# TODO don't set if process is shutting down
|
||||||
db.redisClient.setex("session_#{sessionId}", sessionData.timeout, sessionDataString, (err) ->
|
db.redisClient.setex("session_#{sessionId}", sessionContext.timeout, sessionContextString, (err) ->
|
||||||
return throwError(err) if err
|
return throwError(err) if err
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -618,121 +623,114 @@ listener = (httpRequest, httpResponse) ->
|
|||||||
return body
|
return body
|
||||||
|
|
||||||
stream.on('end', () ->
|
stream.on('end', () ->
|
||||||
cwmpRequest = null
|
rpc = null
|
||||||
getSession(httpRequest, f = (err, sessionData) ->
|
getSession(httpRequest, f = (err, sessionContext) ->
|
||||||
return throwError(err, httpResponse) if err
|
return throwError(err, httpResponse) if err
|
||||||
cwmpRequest ?= soap.request(httpRequest, sessionData?.cwmpVersion)
|
rpc ?= soap.request(httpRequest, sessionContext?.cwmpVersion)
|
||||||
if not sessionData?
|
if not sessionContext?
|
||||||
if cwmpRequest.methodRequest?.type isnt 'Inform'
|
if rpc.cpeRequest?.name isnt 'Inform'
|
||||||
httpResponse.writeHead(400)
|
httpResponse.writeHead(400)
|
||||||
httpResponse.end('Session is expired')
|
httpResponse.end('Session is expired')
|
||||||
return
|
return
|
||||||
|
|
||||||
deviceId = common.generateDeviceId(cwmpRequest.methodRequest.deviceId)
|
deviceId = common.generateDeviceId(rpc.cpeRequest.deviceId)
|
||||||
return session.init(deviceId, cwmpRequest.cwmpVersion, cwmpRequest.sessionTimeout ? config.get('SESSION_TIMEOUT', deviceId), (err, sessionData) ->
|
return session.init(deviceId, rpc.cwmpVersion, rpc.sessionTimeout ? config.get('SESSION_TIMEOUT', deviceId), (err, sessionContext) ->
|
||||||
return throwError(err, httpResponse) if err
|
return throwError(err, httpResponse) if err
|
||||||
|
|
||||||
sessionData.sessionId = crypto.randomBytes(8).toString('hex')
|
sessionContext.sessionId = crypto.randomBytes(8).toString('hex')
|
||||||
|
|
||||||
currentSessions.get(httpRequest.connection)[sessionData.sessionId] = sessionData
|
currentSessions.get(httpRequest.connection)[sessionContext.sessionId] = sessionContext
|
||||||
|
|
||||||
httpRequest.connection.setTimeout(sessionData.timeout * 1000)
|
httpRequest.connection.setTimeout(sessionContext.timeout * 1000)
|
||||||
|
|
||||||
db.getDueTasksAndFaultsAndOperations(deviceId, sessionData.timestamp, (err, dueTasks, faults, operations) ->
|
db.getDueTasksAndFaultsAndOperations(deviceId, sessionContext.timestamp, (err, dueTasks, faults, operations) ->
|
||||||
return throwError(err, httpResponse) if err
|
return throwError(err, httpResponse) if err
|
||||||
sessionData.tasks = dueTasks
|
sessionContext.tasks = dueTasks
|
||||||
sessionData.faults = faults
|
sessionContext.faults = faults
|
||||||
sessionData.retries = {}
|
sessionContext.retries = {}
|
||||||
for k, v of faults
|
for k, v of faults
|
||||||
sessionData.retries[k] = v.retries
|
sessionContext.retries[k] = v.retries
|
||||||
sessionData.operations = operations
|
sessionContext.operations = operations
|
||||||
|
|
||||||
# Delete expired faults
|
# Delete expired faults
|
||||||
for k, v of sessionData.faults
|
for k, v of sessionContext.faults
|
||||||
if v.expiry >= sessionData.timestamp
|
if v.expiry >= sessionContext.timestamp
|
||||||
delete sessionData.faults[k]
|
delete sessionContext.faults[k]
|
||||||
sessionData.faultsTouched ?= {}
|
sessionContext.faultsTouched ?= {}
|
||||||
sessionData.faultsTouched[k] = true
|
sessionContext.faultsTouched[k] = true
|
||||||
|
|
||||||
f(null, sessionData)
|
f(null, sessionContext)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
currentRequest = {
|
sessionContext.httpRequest = httpRequest
|
||||||
httpRequest : httpRequest,
|
sessionContext.httpResponse = httpResponse
|
||||||
httpResponse : httpResponse,
|
|
||||||
sessionData : sessionData
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.get('DEBUG', currentRequest.sessionData.deviceId)
|
if config.get('DEBUG', sessionContext.deviceId)
|
||||||
dump = "# REQUEST #{new Date(Date.now())}\n" + JSON.stringify(httpRequest.headers) + "\n#{httpRequest.getBody()}\n\n"
|
dump = "# REQUEST #{new Date(Date.now())}\n" + JSON.stringify(httpRequest.headers) + "\n#{httpRequest.getBody()}\n\n"
|
||||||
require('fs').appendFile("./debug/#{currentRequest.sessionData.deviceId}.dump", dump, (err) ->
|
require('fs').appendFile("./debug/#{sessionContext.deviceId}.dump", dump, (err) ->
|
||||||
return throwError(err) if err
|
return throwError(err) if err
|
||||||
)
|
)
|
||||||
|
|
||||||
if cwmpRequest.methodRequest?
|
if rpc.cpeRequest?
|
||||||
if cwmpRequest.methodRequest.type is 'Inform'
|
if rpc.cpeRequest.name is 'Inform'
|
||||||
logger.accessInfo({
|
logger.accessInfo({
|
||||||
currentRequest: currentRequest
|
sessionContext: sessionContext
|
||||||
message: 'Inform'
|
message: 'Inform'
|
||||||
cpeRequest: cwmpRequest.methodRequest
|
rpc: rpc
|
||||||
cpeRequestId: cwmpRequest.id
|
|
||||||
});
|
|
||||||
inform(currentRequest, cwmpRequest)
|
|
||||||
else if cwmpRequest.methodRequest.type is 'TransferComplete'
|
|
||||||
logger.accessInfo({
|
|
||||||
currentRequest: currentRequest
|
|
||||||
message: 'CPE request'
|
|
||||||
cpeRequest: cwmpRequest.methodRequest
|
|
||||||
cpeRequestId: cwmpRequest.id
|
|
||||||
});
|
|
||||||
transferComplete(currentRequest, cwmpRequest)
|
|
||||||
else if cwmpRequest.methodRequest.type is 'GetRPCMethods'
|
|
||||||
logger.accessInfo({
|
|
||||||
currentRequest: currentRequest
|
|
||||||
message: 'CPE request'
|
|
||||||
cpeRequest: cwmpRequest.methodRequest
|
|
||||||
cpeRequestId: cwmpRequest.id
|
|
||||||
});
|
|
||||||
res = soap.response({
|
|
||||||
id : cwmpRequest.id,
|
|
||||||
methodResponse : {type : 'GetRPCMethodsResponse', methodList : ['Inform', 'GetRPCMethods', 'TransferComplete', 'RequestDownload']},
|
|
||||||
cwmpVersion : currentRequest.sessionData.cwmpVersion
|
|
||||||
})
|
})
|
||||||
writeResponse(currentRequest, res)
|
inform(sessionContext, rpc)
|
||||||
|
else if rpc.cpeRequest.name is 'TransferComplete'
|
||||||
|
logger.accessInfo({
|
||||||
|
sessionContext: sessionContext
|
||||||
|
message: 'CPE request'
|
||||||
|
rpc: rpc
|
||||||
|
})
|
||||||
|
transferComplete(sessionContext, rpc)
|
||||||
|
else if rpc.cpeRequest.name is 'GetRPCMethods'
|
||||||
|
logger.accessInfo({
|
||||||
|
sessionContext: sessionContext
|
||||||
|
message: 'CPE request'
|
||||||
|
rpc: rpc
|
||||||
|
})
|
||||||
|
res = soap.response({
|
||||||
|
id : rpc.id
|
||||||
|
acsResponse : {name: 'GetRPCMethodsResponse', methodList: ['Inform', 'GetRPCMethods', 'TransferComplete', 'RequestDownload']}
|
||||||
|
cwmpVersion : sessionContext.cwmpVersion
|
||||||
|
})
|
||||||
|
writeResponse(sessionContext, res)
|
||||||
else
|
else
|
||||||
return throwError(new Error('ACS method not supported'), currentRequest.httpResponse)
|
return throwError(new Error('ACS method not supported'), sessionContext.httpResponse)
|
||||||
else if cwmpRequest.methodResponse?
|
else if rpc.cpeResponse
|
||||||
session.rpcResponse(currentRequest.sessionData, cwmpRequest.id, cwmpRequest.methodResponse, (err) ->
|
session.rpcResponse(sessionContext, rpc.id, rpc.cpeResponse, (err) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
nextRpc(currentRequest)
|
nextRpc(sessionContext)
|
||||||
)
|
)
|
||||||
else if cwmpRequest.fault?
|
else if rpc.cpeFault
|
||||||
logger.accessWarn({
|
logger.accessWarn({
|
||||||
currentRequest: currentRequest
|
sessionContext: sessionContext
|
||||||
message: 'CPE fault'
|
message: 'CPE fault'
|
||||||
cpeFault: cwmpRequest.fault.detail
|
rpc:rpc
|
||||||
cpeRequestId: cwmpRequest.id
|
})
|
||||||
});
|
session.rpcFault(sessionContext, rpc.id, rpc.cpeFault, (err, fault) ->
|
||||||
session.rpcFault(currentRequest.sessionData, cwmpRequest.id, cwmpRequest.fault, (err, fault) ->
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
|
||||||
|
|
||||||
if fault
|
if fault
|
||||||
recordFault(currentRequest, fault, currentRequest.sessionData.provisions, currentRequest.sessionData.channels)
|
recordFault(sessionContext, fault)
|
||||||
session.clearProvisions(currentRequest.sessionData)
|
session.clearProvisions(sessionContext)
|
||||||
nextRpc(currentRequest)
|
nextRpc(sessionContext)
|
||||||
)
|
)
|
||||||
else # CPE sent empty response
|
else # CPE sent empty response
|
||||||
session.timeoutOperations(currentRequest.sessionData, (err, faults, operations) ->
|
session.timeoutOperations(sessionContext, (err, faults, operations) ->
|
||||||
return throwError(err, currentRequest.httpResponse) if err
|
return throwError(err, sessionContext.httpResponse) if err
|
||||||
|
|
||||||
for fault, i in faults
|
for fault, i in faults
|
||||||
for k, v of operations[i].retries
|
for k, v of operations[i].retries
|
||||||
sessionData.retries[k] = v
|
sessionContext.retries[k] = v
|
||||||
|
|
||||||
recordFault(currentRequest, fault, operations[i].provisions, operations[i].channels)
|
recordFault(sessionContext, fault, operations[i].provisions, operations[i].channels)
|
||||||
|
|
||||||
nextRpc(currentRequest)
|
nextRpc(sessionContext)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
+13
-13
@@ -23,14 +23,14 @@ const device = require("./device");
|
|||||||
const MAX_DEPTH = config.get("MAX_DEPTH");
|
const MAX_DEPTH = config.get("MAX_DEPTH");
|
||||||
|
|
||||||
|
|
||||||
function refresh(sessionData, provision, declarations, startRevision, endRevision) {
|
function refresh(sessionContext, provision, declarations, startRevision, endRevision) {
|
||||||
let path = common.parsePath(provision[1]).slice();
|
let path = common.parsePath(provision[1]).slice();
|
||||||
let l = path.length;
|
let l = path.length;
|
||||||
path.length = MAX_DEPTH;
|
path.length = MAX_DEPTH;
|
||||||
path.fill("*", l);
|
path.fill("*", l);
|
||||||
let every = 1000 * (provision[2] || 1);
|
let every = 1000 * (provision[2] || 1);
|
||||||
let offset = Math.abs(common.djb2(sessionData.deviceId)) % 86400000;
|
let offset = Math.abs(common.djb2(sessionContext.deviceId)) % 86400000;
|
||||||
let t = Math.trunc((sessionData.timestamp + offset) / every) * every - offset;
|
let t = Math.trunc((sessionContext.timestamp + offset) / every) * every - offset;
|
||||||
|
|
||||||
for (let i = l; i < path.length; ++ i)
|
for (let i = l; i < path.length; ++ i)
|
||||||
declarations.push([path.slice(0, i), t, {object: 1, writable: 1, value: t}]);
|
declarations.push([path.slice(0, i), t, {object: 1, writable: 1, value: t}]);
|
||||||
@@ -39,7 +39,7 @@ function refresh(sessionData, provision, declarations, startRevision, endRevisio
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function value(sessionData, provision, declarations, startRevision, endRevision) {
|
function value(sessionContext, provision, declarations, startRevision, endRevision) {
|
||||||
let path = common.parsePath(provision[1]);
|
let path = common.parsePath(provision[1]);
|
||||||
declarations.push([path, 1, {value: 1}, null, {value: [provision[2]]}]);
|
declarations.push([path, 1, {value: 1}, null, {value: [provision[2]]}]);
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ function value(sessionData, provision, declarations, startRevision, endRevision)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function tag(sessionData, provision, declarations, startRevision, endRevision) {
|
function tag(sessionContext, provision, declarations, startRevision, endRevision) {
|
||||||
let path = ["Tags", provision[1]];
|
let path = ["Tags", provision[1]];
|
||||||
declarations.push([path, 1, {value: 1}, null, {value: [provision[2]]}]);
|
declarations.push([path, 1, {value: 1}, null, {value: [provision[2]]}]);
|
||||||
|
|
||||||
@@ -55,33 +55,33 @@ function tag(sessionData, provision, declarations, startRevision, endRevision) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function reboot(sessionData, provision, declarations, startRevision, endRevision) {
|
function reboot(sessionContext, provision, declarations, startRevision, endRevision) {
|
||||||
declarations.push([["Reboot"], 1, {value: 1}, null, {value: [sessionData.timestamp]}]);
|
declarations.push([["Reboot"], 1, {value: 1}, null, {value: [sessionContext.timestamp]}]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function reset(sessionData, provision, declarations, startRevision, endRevision) {
|
function reset(sessionContext, provision, declarations, startRevision, endRevision) {
|
||||||
declarations.push([["FactoryReset"], 1, {value: 1}, null, {value: [sessionData.timestamp]}]);
|
declarations.push([["FactoryReset"], 1, {value: 1}, null, {value: [sessionContext.timestamp]}]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function download(sessionData, provision, declarations, startRevision, endRevision) {
|
function download(sessionContext, provision, declarations, startRevision, endRevision) {
|
||||||
let alias = ["FileType", provision[1] || "", "FileName", provision[2] || "",
|
let alias = ["FileType", provision[1] || "", "FileName", provision[2] || "",
|
||||||
"TargetFileName", provision[3] || ""];
|
"TargetFileName", provision[3] || ""];
|
||||||
|
|
||||||
declarations.push([["Downloads", alias], 1, {}, 1]);
|
declarations.push([["Downloads", alias], 1, {}, 1]);
|
||||||
declarations.push([["Downloads", alias, "Download"], 1, {value: 1}, null,
|
declarations.push([["Downloads", alias, "Download"], 1, {value: 1}, null,
|
||||||
{value: [sessionData.timestamp]}]);
|
{value: [sessionContext.timestamp]}]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function instances(sessionData, provision, declarations, startRevision, endRevision) {
|
function instances(sessionContext, provision, declarations, startRevision, endRevision) {
|
||||||
let count = Number(provision[2]);
|
let count = Number(provision[2]);
|
||||||
|
|
||||||
if (Number.isNaN(count))
|
if (Number.isNaN(count))
|
||||||
@@ -95,7 +95,7 @@ function instances(sessionData, provision, declarations, startRevision, endRevis
|
|||||||
if (endRevision === startRevision)
|
if (endRevision === startRevision)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
let unpacked = device.unpack(sessionData.deviceData, path, startRevision + 1);
|
let unpacked = device.unpack(sessionContext.deviceData, path, startRevision + 1);
|
||||||
count = Math.max(0, unpacked.length + count);
|
count = Math.max(0, unpacked.length + count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-23
@@ -131,10 +131,10 @@ function close() {
|
|||||||
|
|
||||||
|
|
||||||
function flatten(details) {
|
function flatten(details) {
|
||||||
if (details.currentRequest) {
|
if (details.sessionContext) {
|
||||||
details.deviceId = details.currentRequest.sessionData.deviceId;
|
details.deviceId = details.sessionContext.deviceId;
|
||||||
details.remoteAddress = details.currentRequest.httpRequest.connection.remoteAddress;
|
details.remoteAddress = details.sessionContext.httpRequest.connection.remoteAddress;
|
||||||
delete details.currentRequest;
|
delete details.sessionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.exception) {
|
if (details.exception) {
|
||||||
@@ -149,30 +149,31 @@ function flatten(details) {
|
|||||||
delete details.task;
|
delete details.task;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.acsRequest) {
|
if (details.rpc) {
|
||||||
details.acsRequestName = details.acsRequest.type;
|
if (details.rpc.acsRequest) {
|
||||||
if (details.acsRequest.commandKey)
|
details.acsRequestId = details.rpc.id;
|
||||||
details.acsRequestCommandKey = details.acsRequest.commandKey;
|
details.acsRequestName = details.rpc.acsRequest.name;
|
||||||
delete details.acsRequest;
|
if (details.rpc.acsRequest.commandKey)
|
||||||
|
details.acsRequestCommandKey = details.rpc.acsRequest.commandKey;
|
||||||
}
|
}
|
||||||
|
else if (details.rpc.cpeRequest) {
|
||||||
if (details.cpeRequest) {
|
details.cpeRequestId = details.rpc.id;
|
||||||
if (details.cpeRequest.type === "Inform") {
|
if (details.rpc.cpeRequest.name === "Inform") {
|
||||||
details.informEvent = details.cpeRequest.event.join(",");
|
details.informEvent = details.rpc.cpeRequest.event.join(",");
|
||||||
details.informRetryCount = details.cpeRequest.retryCount;
|
details.informRetryCount = details.rpc.cpeRequest.retryCount;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
details.cpeRequestName = details.cpeRequest.type;
|
details.cpeRequestName = details.rpc.cpeRequest.type;
|
||||||
if (details.cpeRequest.commandKey)
|
if (details.rpc.cpeRequest.commandKey)
|
||||||
details.cpeRequestCommandKey = details.cpeRequest.commandKey;
|
details.cpeRequestCommandKey = details.rpc.cpeRequest.commandKey;
|
||||||
}
|
}
|
||||||
delete details.cpeRequest;
|
|
||||||
}
|
}
|
||||||
|
else if (details.rpc.cpeFault) {
|
||||||
if (details.cpeFault) {
|
details.acsRequestId = details.rpc.id;
|
||||||
details.cpeFaultCode = details.cpeFault.faultCode;
|
details.cpeFaultCode = details.rpc.cpeFault.faultCode;
|
||||||
details.cpeFaultString = details.cpeFault.faultString;
|
details.cpeFaultString = details.rpc.cpeFault.faultString;
|
||||||
delete details.cpeFault;
|
}
|
||||||
|
delete details.rpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.fault) {
|
if (details.fault) {
|
||||||
@@ -180,6 +181,7 @@ function flatten(details) {
|
|||||||
details.faultMessage = details.fault.message;
|
details.faultMessage = details.fault.message;
|
||||||
delete details.fault;
|
delete details.fault;
|
||||||
}
|
}
|
||||||
|
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+426
-425
File diff suppressed because it is too large.
Load diff
-464
@@ -1,464 +0,0 @@
|
|||||||
###
|
|
||||||
# Copyright 2013, 2014 Zaid Abdulla
|
|
||||||
#
|
|
||||||
# GenieACS is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# GenieACS is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# This file incorporates work covered by the following copyright and
|
|
||||||
# permission notice:
|
|
||||||
#
|
|
||||||
# Copyright 2013 Fanoos Telecom
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
|
||||||
# in the Software without restriction, including without limitation the rights
|
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
|
||||||
# furnished to do so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be included in
|
|
||||||
# all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
# THE SOFTWARE.
|
|
||||||
###
|
|
||||||
|
|
||||||
config = require './config'
|
|
||||||
libxmljs = require 'libxmljs'
|
|
||||||
|
|
||||||
SERVER_NAME = "GenieACS/#{require('../package.json').version}"
|
|
||||||
|
|
||||||
NAMESPACES = {
|
|
||||||
'1.0' : {
|
|
||||||
'soap-enc' : 'http://schemas.xmlsoap.org/soap/encoding/',
|
|
||||||
'soap-env' : 'http://schemas.xmlsoap.org/soap/envelope/',
|
|
||||||
'xsd' : 'http://www.w3.org/2001/XMLSchema',
|
|
||||||
'xsi' : 'http://www.w3.org/2001/XMLSchema-instance',
|
|
||||||
'cwmp' : 'urn:dslforum-org:cwmp-1-0'
|
|
||||||
},
|
|
||||||
'1.1' : {
|
|
||||||
'soap-enc' : 'http://schemas.xmlsoap.org/soap/encoding/',
|
|
||||||
'soap-env' : 'http://schemas.xmlsoap.org/soap/envelope/',
|
|
||||||
'xsd' : 'http://www.w3.org/2001/XMLSchema',
|
|
||||||
'xsi' : 'http://www.w3.org/2001/XMLSchema-instance',
|
|
||||||
'cwmp' : 'urn:dslforum-org:cwmp-1-1'
|
|
||||||
},
|
|
||||||
'1.2' : {
|
|
||||||
'soap-enc' : 'http://schemas.xmlsoap.org/soap/encoding/',
|
|
||||||
'soap-env' : 'http://schemas.xmlsoap.org/soap/envelope/',
|
|
||||||
'xsd' : 'http://www.w3.org/2001/XMLSchema',
|
|
||||||
'xsi' : 'http://www.w3.org/2001/XMLSchema-instance',
|
|
||||||
'cwmp' : 'urn:dslforum-org:cwmp-1-2'
|
|
||||||
},
|
|
||||||
'1.3' : {
|
|
||||||
'soap-enc' : 'http://schemas.xmlsoap.org/soap/encoding/',
|
|
||||||
'soap-env' : 'http://schemas.xmlsoap.org/soap/envelope/',
|
|
||||||
'xsd' : 'http://www.w3.org/2001/XMLSchema',
|
|
||||||
'xsi' : 'http://www.w3.org/2001/XMLSchema-instance',
|
|
||||||
'cwmp' : 'urn:dslforum-org:cwmp-1-2'
|
|
||||||
},
|
|
||||||
'1.4' : {
|
|
||||||
'soap-enc' : 'http://schemas.xmlsoap.org/soap/encoding/',
|
|
||||||
'soap-env' : 'http://schemas.xmlsoap.org/soap/envelope/',
|
|
||||||
'xsd' : 'http://www.w3.org/2001/XMLSchema',
|
|
||||||
'xsi' : 'http://www.w3.org/2001/XMLSchema-instance',
|
|
||||||
'cwmp' : 'urn:dslforum-org:cwmp-1-3'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LIBXMLJS_PARSE_OPTIONS = {
|
|
||||||
'nocdata' : true,
|
|
||||||
'noent' : true,
|
|
||||||
'recover' : config.get('XML_RECOVER'),
|
|
||||||
'ignore_enc' : config.get('XML_IGNORE_ENC')
|
|
||||||
}
|
|
||||||
|
|
||||||
LIBXMLJS_SAVE_OPTIONS = {
|
|
||||||
'format' : config.get('XML_FORMAT'),
|
|
||||||
'declaration' : not config.get('XML_NO_DECL'),
|
|
||||||
'selfCloseEmpty' : not config.get('XML_NO_EMPTY')
|
|
||||||
}
|
|
||||||
|
|
||||||
XML_IGNORE_NAMESPACE = config.get('XML_IGNORE_NAMESPACE')
|
|
||||||
|
|
||||||
|
|
||||||
# Workaround for devices that don't use correct namespace declarations or prefixes
|
|
||||||
if XML_IGNORE_NAMESPACE
|
|
||||||
libxmljs.Element.prototype.__find = libxmljs.Element.prototype.find
|
|
||||||
libxmljs.Element.prototype.find = (xpath, namespaces) ->
|
|
||||||
# Modify xpath queries to work regardless of element's namespace
|
|
||||||
p = xpath.replace(/([^\/:]+:)?([^\/]+)(\/|$)/g, '$2$3') \
|
|
||||||
.replace(/([a-zA-Z0-9_-]+)([^\/]*)(\/|$)/g, "*[local-name()='$1']$2$3")
|
|
||||||
libxmljs.Element.prototype.__find.call(this, p, namespaces)
|
|
||||||
|
|
||||||
|
|
||||||
event = (xml) ->
|
|
||||||
e.text().trim() for e in xml.find('EventStruct/EventCode')
|
|
||||||
|
|
||||||
|
|
||||||
parameterInfoList = (xml) ->
|
|
||||||
infoList = []
|
|
||||||
for e in xml.find('ParameterInfoStruct')
|
|
||||||
name = e.get('Name').text().trim()
|
|
||||||
writable = Boolean(JSON.parse(e.get('Writable').text()))
|
|
||||||
infoList.push([name, writable])
|
|
||||||
return infoList
|
|
||||||
|
|
||||||
|
|
||||||
parameterValueList = (xml) ->
|
|
||||||
valueList = []
|
|
||||||
for e in xml.find('ParameterValueStruct')
|
|
||||||
valueType = e.get('Value').attr('type').value().trim()
|
|
||||||
name = e.get('Name').text().trim()
|
|
||||||
value = e.get('Value').text().trim()
|
|
||||||
try
|
|
||||||
switch valueType
|
|
||||||
when 'xsd:boolean'
|
|
||||||
value = Boolean(JSON.parse(value))
|
|
||||||
when 'xsd:unsignedInt'
|
|
||||||
value = JSON.parse(value)
|
|
||||||
when 'xsd:int'
|
|
||||||
value = JSON.parse(value)
|
|
||||||
catch err
|
|
||||||
value = null
|
|
||||||
valueList.push([name, value, valueType])
|
|
||||||
return valueList
|
|
||||||
|
|
||||||
|
|
||||||
cpeGetParameterNames = (xml, methodRequest) ->
|
|
||||||
el = xml.node('cwmp:GetParameterNames')
|
|
||||||
el.node('ParameterPath').text(methodRequest.parameterPath)
|
|
||||||
el.node('NextLevel').text(+methodRequest.nextLevel)
|
|
||||||
|
|
||||||
|
|
||||||
cpeGetParameterNamesResponse = (xml) ->
|
|
||||||
{parameterList : parameterInfoList(xml.get('ParameterList'))}
|
|
||||||
|
|
||||||
|
|
||||||
cpeGetParameterValues = (xml, methodRequest) ->
|
|
||||||
el = xml.node('cwmp:GetParameterValues').node('ParameterNames')
|
|
||||||
el.attr({'soap-enc:arrayType' : "xsd:string[#{methodRequest.parameterNames.length}]"})
|
|
||||||
for p in methodRequest.parameterNames
|
|
||||||
el.node('string').text(p)
|
|
||||||
|
|
||||||
|
|
||||||
cpeGetParameterValuesResponse = (xml) ->
|
|
||||||
{parameterList : parameterValueList(xml.get('ParameterList'))}
|
|
||||||
|
|
||||||
|
|
||||||
cpeSetParameterValues = (xml, methodRequest) ->
|
|
||||||
el = xml.node('cwmp:SetParameterValues')
|
|
||||||
paramList = el.node('ParameterList')
|
|
||||||
paramList.attr({'soap-enc:arrayType' : "cwmp:ParameterValueStruct[#{methodRequest.parameterList.length}]"})
|
|
||||||
for i in methodRequest.parameterList
|
|
||||||
pvs = paramList.node('ParameterValueStruct')
|
|
||||||
pvs.node('Name').text(i[0])
|
|
||||||
v = pvs.node('Value')
|
|
||||||
v.text(i[1])
|
|
||||||
v.attr({'xsi:type' : i[2]}) if i[2]?
|
|
||||||
el.node('ParameterKey').text(methodRequest.parameterKey ? '')
|
|
||||||
|
|
||||||
|
|
||||||
cpeSetParameterValuesResponse = (xml) ->
|
|
||||||
{status : JSON.parse(xml.get('Status').text())}
|
|
||||||
|
|
||||||
|
|
||||||
cpeAddObject = (xml, methodRequest) ->
|
|
||||||
el = xml.node('cwmp:AddObject')
|
|
||||||
el.node('ObjectName').text(methodRequest.objectName)
|
|
||||||
el.node('ParameterKey').text(methodRequest.parameterKey ? '')
|
|
||||||
|
|
||||||
|
|
||||||
cpeAddObjectResponse = (xml) ->
|
|
||||||
{
|
|
||||||
instanceNumber : parseInt(xml.get('InstanceNumber').text()),
|
|
||||||
status : parseInt(xml.get('Status').text())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
cpeDeleteObject = (xml, methodRequest) ->
|
|
||||||
el = xml.node('cwmp:DeleteObject')
|
|
||||||
el.node('ObjectName').text(methodRequest.objectName)
|
|
||||||
el.node('ParameterKey').text(methodRequest.parameterKey ? '')
|
|
||||||
|
|
||||||
|
|
||||||
cpeDeleteObjectResponse = (xml) ->
|
|
||||||
{status : parseInt(xml.get('Status').text())}
|
|
||||||
|
|
||||||
|
|
||||||
cpeReboot = (xml, methodRequest) ->
|
|
||||||
el = xml.node('cwmp:Reboot')
|
|
||||||
el.node('CommandKey').text(methodRequest.commandKey ? '')
|
|
||||||
|
|
||||||
|
|
||||||
cpeRebootResponse = (xml) ->
|
|
||||||
{}
|
|
||||||
|
|
||||||
|
|
||||||
cpeFactoryReset = (xml, methodRequest) ->
|
|
||||||
el = xml.node('cwmp:FactoryReset')
|
|
||||||
|
|
||||||
|
|
||||||
cpeFactoryResetResponse = (xml, methodRequest) ->
|
|
||||||
{}
|
|
||||||
|
|
||||||
|
|
||||||
cpeDownload = (xml, methodRequest) ->
|
|
||||||
el = xml.node('cwmp:Download')
|
|
||||||
el.node('CommandKey').text(methodRequest.commandKey ? '')
|
|
||||||
el.node('FileType').text(methodRequest.fileType)
|
|
||||||
el.node('URL').text(methodRequest.url)
|
|
||||||
el.node('Username').text(methodRequest.username ? '')
|
|
||||||
el.node('Password').text(methodRequest.password ? '')
|
|
||||||
el.node('FileSize').text(methodRequest.fileSize ? '0')
|
|
||||||
el.node('TargetFileName').text(methodRequest.targetFileName ? '')
|
|
||||||
el.node('DelaySeconds').text(methodRequest.delaySeconds ? '0')
|
|
||||||
el.node('SuccessURL').text(methodRequest.successUrl ? '')
|
|
||||||
el.node('FailureURL').text(methodRequest.failureUrl ? '')
|
|
||||||
|
|
||||||
|
|
||||||
cpeDownloadResponse = (xml) ->
|
|
||||||
res = {
|
|
||||||
status : parseInt(xml.get('Status').text()),
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.status == 0
|
|
||||||
res.startTime = Date.parse(xml.get('StartTime').text())
|
|
||||||
res.completeTime = Date.parse(xml.get('CompleteTime').text())
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
acsInform = (xml) ->
|
|
||||||
{
|
|
||||||
parameterList : parameterValueList(xml.get('ParameterList')),
|
|
||||||
deviceId : {
|
|
||||||
'Manufacturer' : xml.get('DeviceId/Manufacturer').text(),
|
|
||||||
'OUI' : xml.get('DeviceId/OUI').text(),
|
|
||||||
'ProductClass' : xml.get('DeviceId/ProductClass').text(),
|
|
||||||
'SerialNumber' : xml.get('DeviceId/SerialNumber').text()
|
|
||||||
}
|
|
||||||
event : event(xml.get('Event')),
|
|
||||||
retryCount : parseInt(xml.get('RetryCount').text())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
acsInformResponse = (xml, methodResponse) ->
|
|
||||||
xml.node('cwmp:InformResponse').node('MaxEnvelopes').text(1)
|
|
||||||
|
|
||||||
|
|
||||||
acsGetRPCMethods = (xml) ->
|
|
||||||
{}
|
|
||||||
|
|
||||||
|
|
||||||
acsGetRPCMethodsResponse = (xml, methodResponse) ->
|
|
||||||
el = xml.node('cwmp:GetRPCMethodsResponse').node('MethodList')
|
|
||||||
el.attr({'soap-enc:arrayType' : "xsd:string[#{methodResponse.methodList.length}]"})
|
|
||||||
for m in methodResponse.methodList
|
|
||||||
el.node('string').text(m)
|
|
||||||
|
|
||||||
|
|
||||||
acsTransferComplete = (xml) ->
|
|
||||||
{
|
|
||||||
commandKey : xml.get('CommandKey').text(),
|
|
||||||
faultStruct : faultStruct(xml.get('FaultStruct')),
|
|
||||||
startTime : Date.parse(xml.get('StartTime').text()),
|
|
||||||
completeTime : Date.parse(xml.get('CompleteTime').text())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
acsTransferCompleteResponse = (xml, methodResponse) ->
|
|
||||||
xml.node('cwmp:TransferCompleteResponse').text('')
|
|
||||||
|
|
||||||
|
|
||||||
acsRequestDownload = (xml) ->
|
|
||||||
# TODO FileTypeArg
|
|
||||||
{
|
|
||||||
fileType : xml.get('FileType').text()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
acsRequestDownloadResponse = (xml, methodResponse) ->
|
|
||||||
xml.node('cwmp:RequestDownloadResponse').text('')
|
|
||||||
|
|
||||||
|
|
||||||
faultStruct = (xml) ->
|
|
||||||
f = {
|
|
||||||
faultCode: xml.get('FaultCode').text(),
|
|
||||||
faultString: xml.get('FaultString').text()
|
|
||||||
}
|
|
||||||
|
|
||||||
for e in xml.find('SetParameterValuesFault')
|
|
||||||
f.setParameterValuesFault ?= []
|
|
||||||
f.setParameterValuesFault.push({
|
|
||||||
parameterName: e.get('ParameterName').text(),
|
|
||||||
faultCode: e.get('FaultCode').text(),
|
|
||||||
faultString: e.get('FaultString').text()
|
|
||||||
})
|
|
||||||
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
fault = (xml, cwmpVersion) ->
|
|
||||||
{
|
|
||||||
faultCode: xml.get('faultcode').text(),
|
|
||||||
faultString: xml.get('faultstring').text(),
|
|
||||||
detail: faultStruct(xml.get('detail/cwmp:Fault', NAMESPACES[cwmpVersion]))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
exports.request = (httpRequest, cwmpVersion) ->
|
|
||||||
cwmpRequest = {cwmpVersion : cwmpVersion}
|
|
||||||
|
|
||||||
data = httpRequest.getBody()
|
|
||||||
|
|
||||||
if data.length > 0
|
|
||||||
xml = libxmljs.parseXml(data, LIBXMLJS_PARSE_OPTIONS)
|
|
||||||
|
|
||||||
if not cwmpRequest.cwmpVersion?
|
|
||||||
# cwmpVersion not passed, thus it's an inform request
|
|
||||||
methodElement = xml.get('/soap-env:Envelope/soap-env:Body/*', NAMESPACES['1.0'])
|
|
||||||
switch methodElement.namespace().href()
|
|
||||||
when 'urn:dslforum-org:cwmp-1-0'
|
|
||||||
cwmpRequest.cwmpVersion = '1.0'
|
|
||||||
when 'urn:dslforum-org:cwmp-1-1'
|
|
||||||
cwmpRequest.cwmpVersion = '1.1'
|
|
||||||
when 'urn:dslforum-org:cwmp-1-2'
|
|
||||||
cwmpRequest.sessionTimeout = try xml.get('/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout', NAMESPACES['1.2']).text() catch then null
|
|
||||||
if cwmpRequest.sessionTimeout?
|
|
||||||
cwmpRequest.cwmpVersion = '1.3'
|
|
||||||
else
|
|
||||||
cwmpRequest.cwmpVersion = '1.2'
|
|
||||||
when 'urn:dslforum-org:cwmp-1-3'
|
|
||||||
cwmpRequest.cwmpVersion = '1.4'
|
|
||||||
else
|
|
||||||
throw new Error('Unrecognized CWMP version')
|
|
||||||
|
|
||||||
cwmpRequest.sessionTimeout ?= try xml.get('/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout', NAMESPACES[cwmpRequest.cwmpVersion]).text() catch then null
|
|
||||||
else
|
|
||||||
methodElement = xml.get('/soap-env:Envelope/soap-env:Body/cwmp:*', NAMESPACES[cwmpRequest.cwmpVersion])
|
|
||||||
|
|
||||||
cwmpRequest.id = try xml.get('/soap-env:Envelope/soap-env:Header/cwmp:ID', NAMESPACES[cwmpRequest.cwmpVersion]).text() catch then null
|
|
||||||
|
|
||||||
if methodElement? and not (XML_IGNORE_NAMESPACE and methodElement.name() is 'Fault')
|
|
||||||
switch methodElement.name()
|
|
||||||
when 'Inform'
|
|
||||||
cwmpRequest.methodRequest = acsInform(methodElement)
|
|
||||||
cwmpRequest.methodRequest.type = 'Inform'
|
|
||||||
when 'GetRPCMethods'
|
|
||||||
cwmpRequest.methodRequest = acsGetRPCMethods(methodElement)
|
|
||||||
cwmpRequest.methodRequest.type = 'GetRPCMethods'
|
|
||||||
when 'TransferComplete'
|
|
||||||
cwmpRequest.methodRequest = acsTransferComplete(methodElement)
|
|
||||||
cwmpRequest.methodRequest.type = 'TransferComplete'
|
|
||||||
when 'RequestDownload'
|
|
||||||
cwmpRequest.methodRequest = acsRequestDownload(methodElement)
|
|
||||||
cwmpRequest.methodRequest.type = 'RequestDownload'
|
|
||||||
when 'GetParameterNamesResponse'
|
|
||||||
cwmpRequest.methodResponse = cpeGetParameterNamesResponse(methodElement)
|
|
||||||
cwmpRequest.methodResponse.type = 'GetParameterNamesResponse'
|
|
||||||
when 'GetParameterValuesResponse'
|
|
||||||
cwmpRequest.methodResponse = cpeGetParameterValuesResponse(methodElement)
|
|
||||||
cwmpRequest.methodResponse.type = 'GetParameterValuesResponse'
|
|
||||||
when 'SetParameterValuesResponse'
|
|
||||||
cwmpRequest.methodResponse = cpeSetParameterValuesResponse(methodElement)
|
|
||||||
cwmpRequest.methodResponse.type = 'SetParameterValuesResponse'
|
|
||||||
when 'AddObjectResponse'
|
|
||||||
cwmpRequest.methodResponse = cpeAddObjectResponse(methodElement)
|
|
||||||
cwmpRequest.methodResponse.type = 'AddObjectResponse'
|
|
||||||
when 'DeleteObjectResponse'
|
|
||||||
cwmpRequest.methodResponse = cpeDeleteObjectResponse(methodElement)
|
|
||||||
cwmpRequest.methodResponse.type = 'DeleteObjectResponse'
|
|
||||||
when 'RebootResponse'
|
|
||||||
cwmpRequest.methodResponse = cpeRebootResponse(methodElement)
|
|
||||||
cwmpRequest.methodResponse.type = 'RebootResponse'
|
|
||||||
when 'FactoryResetResponse'
|
|
||||||
cwmpRequest.methodResponse = cpeFactoryResetResponse(methodElement)
|
|
||||||
cwmpRequest.methodResponse.type = 'FactoryResetResponse'
|
|
||||||
when 'DownloadResponse'
|
|
||||||
cwmpRequest.methodResponse = cpeDownloadResponse(methodElement)
|
|
||||||
cwmpRequest.methodResponse.type = 'DownloadResponse'
|
|
||||||
else
|
|
||||||
throw Error('8000 Method not supported ' + methodElement.name())
|
|
||||||
else
|
|
||||||
faultElement = xml.get('/soap-env:Envelope/soap-env:Body/soap-env:Fault', NAMESPACES[cwmpRequest.cwmpVersion])
|
|
||||||
cwmpRequest.fault = fault(faultElement, cwmpRequest.cwmpVersion)
|
|
||||||
|
|
||||||
return cwmpRequest
|
|
||||||
|
|
||||||
|
|
||||||
createSoapEnv = (cwmpVersion) ->
|
|
||||||
xml = libxmljs.Document()
|
|
||||||
env = xml.node('soap-env:Envelope')
|
|
||||||
for prefix, url of NAMESPACES[cwmpVersion]
|
|
||||||
env.defineNamespace prefix, url
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
exports.response = (cwmpResponse) ->
|
|
||||||
headers = {
|
|
||||||
'Server' : SERVER_NAME,
|
|
||||||
'SOAPServer' : SERVER_NAME,
|
|
||||||
}
|
|
||||||
|
|
||||||
if cwmpResponse?.methodResponse?
|
|
||||||
env = createSoapEnv(cwmpResponse.cwmpVersion)
|
|
||||||
header = env.node('soap-env:Header')
|
|
||||||
header.node('cwmp:ID').attr({'soap-env:mustUnderstand' : 1}).text(cwmpResponse.id)
|
|
||||||
body = env.node('soap-env:Body')
|
|
||||||
switch cwmpResponse.methodResponse.type
|
|
||||||
when 'InformResponse'
|
|
||||||
acsInformResponse(body, cwmpResponse.methodResponse)
|
|
||||||
when 'GetRPCMethodsResponse'
|
|
||||||
acsGetRPCMethodsResponse(body, cwmpResponse.methodResponse)
|
|
||||||
when 'TransferCompleteResponse'
|
|
||||||
acsTransferCompleteResponse(body, cwmpResponse.methodResponse)
|
|
||||||
when 'RequestDownloadResponse'
|
|
||||||
acsRequestDownloadResponse(body, cwmpResponse.methodResponse)
|
|
||||||
else
|
|
||||||
throw Error("Unknown method response type #{cwmpResponse.methodResponse.type}")
|
|
||||||
else if cwmpResponse?.methodRequest?
|
|
||||||
env = createSoapEnv(cwmpResponse.cwmpVersion)
|
|
||||||
header = env.node('soap-env:Header')
|
|
||||||
header.node('cwmp:ID').attr({'soap-env:mustUnderstand' : 1}).text(cwmpResponse.id)
|
|
||||||
body = env.node('soap-env:Body')
|
|
||||||
switch cwmpResponse.methodRequest.type
|
|
||||||
when 'GetParameterNames'
|
|
||||||
cpeGetParameterNames(body, cwmpResponse.methodRequest)
|
|
||||||
when 'GetParameterValues'
|
|
||||||
cpeGetParameterValues(body, cwmpResponse.methodRequest)
|
|
||||||
when 'SetParameterValues'
|
|
||||||
cpeSetParameterValues(body, cwmpResponse.methodRequest)
|
|
||||||
when 'AddObject'
|
|
||||||
cpeAddObject(body, cwmpResponse.methodRequest)
|
|
||||||
when 'DeleteObject'
|
|
||||||
cpeDeleteObject(body, cwmpResponse.methodRequest)
|
|
||||||
when 'Reboot'
|
|
||||||
cpeReboot(body, cwmpResponse.methodRequest)
|
|
||||||
when 'FactoryReset'
|
|
||||||
cpeFactoryReset(body, cwmpResponse.methodRequest)
|
|
||||||
when 'Download'
|
|
||||||
cpeDownload(body, cwmpResponse.methodRequest)
|
|
||||||
else
|
|
||||||
throw Error("Unknown method request #{cwmpResponse.methodRequest.type}")
|
|
||||||
|
|
||||||
if env?
|
|
||||||
headers['Content-Type'] = 'text/xml; charset="utf-8"'
|
|
||||||
return {code: 200, headers: headers, data: new Buffer(env.doc().toString(LIBXMLJS_SAVE_OPTIONS))}
|
|
||||||
else
|
|
||||||
return {code: 204, headers: headers, data: new Buffer(0)}
|
|
||||||
+559
@@ -0,0 +1,559 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2013-2017 Zaid Abdulla
|
||||||
|
*
|
||||||
|
* GenieACS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* GenieACS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const libxmljs = require("libxmljs");
|
||||||
|
|
||||||
|
const config = require("./config");
|
||||||
|
|
||||||
|
const SERVER_NAME = `GenieACS/${require("../package.json").version}`;
|
||||||
|
|
||||||
|
const NAMESPACES = {
|
||||||
|
"1.0": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
"xsd": "http://www.w3.org/2001/XMLSchema",
|
||||||
|
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
"cwmp": "urn:dslforum-org:cwmp-1-0"
|
||||||
|
},
|
||||||
|
"1.1": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
"xsd": "http://www.w3.org/2001/XMLSchema",
|
||||||
|
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
"cwmp": "urn:dslforum-org:cwmp-1-1"
|
||||||
|
},
|
||||||
|
"1.2": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
"xsd": "http://www.w3.org/2001/XMLSchema",
|
||||||
|
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
"cwmp": "urn:dslforum-org:cwmp-1-2"
|
||||||
|
},
|
||||||
|
"1.3": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
"xsd": "http://www.w3.org/2001/XMLSchema",
|
||||||
|
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
"cwmp": "urn:dslforum-org:cwmp-1-2"
|
||||||
|
},
|
||||||
|
"1.4": {
|
||||||
|
"soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||||
|
"soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||||
|
"xsd": "http://www.w3.org/2001/XMLSchema",
|
||||||
|
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
"cwmp": "urn:dslforum-org:cwmp-1-3"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const LIBXMLJS_PARSE_OPTIONS = {
|
||||||
|
"nocdata": true,
|
||||||
|
"noent": true,
|
||||||
|
"recover": config.get("XML_RECOVER"),
|
||||||
|
"ignore_enc": config.get("XML_IGNORE_ENC")
|
||||||
|
};
|
||||||
|
|
||||||
|
const LIBXMLJS_SAVE_OPTIONS = {
|
||||||
|
"format": config.get("XML_FORMAT"),
|
||||||
|
"declaration": !config.get("XML_NO_DECL"),
|
||||||
|
"selfCloseEmpty": !config.get("XML_NO_EMPTY")
|
||||||
|
};
|
||||||
|
|
||||||
|
const XML_IGNORE_NAMESPACE = config.get("XML_IGNORE_NAMESPACE");
|
||||||
|
|
||||||
|
if (XML_IGNORE_NAMESPACE) {
|
||||||
|
libxmljs.Element.prototype.__find = libxmljs.Element.prototype.find;
|
||||||
|
libxmljs.Element.prototype.find = function(xpath, namespaces) {
|
||||||
|
const p = xpath.replace(/([^\/:]+:)?([^\/]+)(\/|$)/g, "$2$3").replace(/([a-zA-Z0-9_-]+)([^\/]*)(\/|$)/g, "*[local-name()=\"$1\"]$2$3");
|
||||||
|
return libxmljs.Element.prototype.__find.call(this, p, namespaces);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function parseBool(v) {
|
||||||
|
v = "" + v;
|
||||||
|
if (v === "true" || v === "TRUE" || v === "True" || v === "1")
|
||||||
|
return true;
|
||||||
|
else if (v === "false" || v === "FALSE" || v === "False" || v === "0")
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
throw new Error("Invalid boolean value");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function event(xml) {
|
||||||
|
return xml.find("EventStruct/EventCode").map(e => e.text().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function parameterInfoList(xml) {
|
||||||
|
return xml.find("ParameterInfoStruct").map(
|
||||||
|
e => [e.get("Name").text(), parseBool(e.get("Writable").text())]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function parameterValueList(xml) {
|
||||||
|
return xml.find("ParameterValueStruct").map(function(e) {
|
||||||
|
const valueElement = e.get("Value");
|
||||||
|
const valueType = valueElement.attr("type").value();
|
||||||
|
let value = valueElement.text();
|
||||||
|
if (valueType === "xsd:boolean")
|
||||||
|
value = parseBool(value);
|
||||||
|
else if (valueType === "xsd:int" || valueType === "xsd:unsignedInt")
|
||||||
|
value = parseInt(value);
|
||||||
|
else if (valueType === "xsd:dateTime")
|
||||||
|
value = Date.parse(value);
|
||||||
|
|
||||||
|
return [e.get("Name").text(), value, valueType];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetParameterNames(xml, methodRequest) {
|
||||||
|
const el = xml.node("cwmp:GetParameterNames");
|
||||||
|
el.node("ParameterPath").text(methodRequest.parameterPath);
|
||||||
|
el.node("NextLevel").text(+methodRequest.nextLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetParameterNamesResponse(xml) {
|
||||||
|
return {
|
||||||
|
name: "GetParameterNamesResponse",
|
||||||
|
parameterList: parameterInfoList(xml.get("ParameterList"))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetParameterValues(xml, methodRequest) {
|
||||||
|
const el = xml.node("cwmp:GetParameterValues").node("ParameterNames");
|
||||||
|
el.attr({
|
||||||
|
"soap-enc:arrayType": `xsd:string[${methodRequest.parameterNames.length}]`
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const p of methodRequest.parameterNames)
|
||||||
|
el.node("string").text(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetParameterValuesResponse(xml) {
|
||||||
|
return {
|
||||||
|
name: "GetParameterValuesResponse",
|
||||||
|
parameterList: parameterValueList(xml.get("ParameterList"))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SetParameterValues(xml, methodRequest) {
|
||||||
|
const el = xml.node("cwmp:SetParameterValues");
|
||||||
|
const paramList = el.node("ParameterList");
|
||||||
|
paramList.attr({
|
||||||
|
"soap-enc:arrayType": `cwmp:ParameterValueStruct[${methodRequest.parameterList.length}]`
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const p of methodRequest.parameterList) {
|
||||||
|
const pvs = paramList.node("ParameterValueStruct");
|
||||||
|
pvs.node("Name").text(p[0]);
|
||||||
|
pvs.node("Value").attr({"xsi:type": p[2]}).text(p[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SetParameterValuesResponse(xml) {
|
||||||
|
return {
|
||||||
|
name: "SetParameterValuesResponse",
|
||||||
|
status: parseInt(xml.get("Status").text())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function AddObject(xml, methodRequest) {
|
||||||
|
const el = xml.node("cwmp:AddObject");
|
||||||
|
el.node("ObjectName").text(methodRequest.objectName);
|
||||||
|
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function AddObjectResponse(xml) {
|
||||||
|
return {
|
||||||
|
name: "AddObjectResponse",
|
||||||
|
instanceNumber: parseInt(xml.get("InstanceNumber").text()),
|
||||||
|
status: parseInt(xml.get("Status").text())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function DeleteObject(xml, methodRequest) {
|
||||||
|
const el = xml.node("cwmp:DeleteObject");
|
||||||
|
el.node("ObjectName").text(methodRequest.objectName);
|
||||||
|
el.node("ParameterKey").text(methodRequest.parameterKey || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function DeleteObjectResponse(xml) {
|
||||||
|
return {
|
||||||
|
name: "DeleteObjectResponse",
|
||||||
|
status: parseInt(xml.get("Status").text())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Reboot(xml, methodRequest) {
|
||||||
|
const el = xml.node("cwmp:Reboot");
|
||||||
|
el.node("CommandKey").text(methodRequest.commandKey || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RebootResponse(xml) {
|
||||||
|
return {
|
||||||
|
name: "RebootResponse"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FactoryReset(xml, methodRequest) {
|
||||||
|
xml.node("cwmp:FactoryReset");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FactoryResetResponse(xml, methodRequest) {
|
||||||
|
return {
|
||||||
|
name: "FactoryResetResponse"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Download(xml, methodRequest) {
|
||||||
|
const el = xml.node("cwmp:Download");
|
||||||
|
el.node("CommandKey").text(methodRequest.commandKey || "");
|
||||||
|
el.node("FileType").text(methodRequest.fileType);
|
||||||
|
el.node("URL").text(methodRequest.url);
|
||||||
|
el.node("Username").text(methodRequest.username || "");
|
||||||
|
el.node("Password").text(methodRequest.password || "");
|
||||||
|
el.node("FileSize").text(methodRequest.fileSize || "0");
|
||||||
|
el.node("TargetFileName").text(methodRequest.targetFileName || "");
|
||||||
|
el.node("DelaySeconds").text(methodRequest.delaySeconds || "0");
|
||||||
|
el.node("SuccessURL").text(methodRequest.successUrl || "");
|
||||||
|
el.node("FailureURL").text(methodRequest.failureUrl || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function DownloadResponse(xml) {
|
||||||
|
const res = {
|
||||||
|
name: "DownloadResponse",
|
||||||
|
status: parseInt(xml.get("Status").text())
|
||||||
|
};
|
||||||
|
|
||||||
|
if (res.status === 0) {
|
||||||
|
res.startTime = Date.parse(xml.get("StartTime").text());
|
||||||
|
res.completeTime = Date.parse(xml.get("CompleteTime").text());
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Inform(xml) {
|
||||||
|
return {
|
||||||
|
name: "Inform",
|
||||||
|
parameterList: parameterValueList(xml.get("ParameterList")),
|
||||||
|
deviceId: {
|
||||||
|
"Manufacturer": xml.get("DeviceId/Manufacturer").text(),
|
||||||
|
"OUI": xml.get("DeviceId/OUI").text(),
|
||||||
|
"ProductClass": xml.get("DeviceId/ProductClass").text(),
|
||||||
|
"SerialNumber": xml.get("DeviceId/SerialNumber").text()
|
||||||
|
},
|
||||||
|
event: event(xml.get("Event")),
|
||||||
|
retryCount: parseInt(xml.get("RetryCount").text())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function InformResponse(xml, methodResponse) {
|
||||||
|
xml.node("cwmp:InformResponse").node("MaxEnvelopes").text(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetRPCMethods(xml) {
|
||||||
|
return {name: "GetRPCMethods"};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetRPCMethodsResponse(xml, methodResponse) {
|
||||||
|
const el = xml.node("cwmp:GetRPCMethodsResponse").node("MethodList");
|
||||||
|
el.attr({
|
||||||
|
"soap-enc:arrayType": `xsd:string[${methodResponse.methodList.length}]`
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const m of methodResponse.methodList)
|
||||||
|
el.node("string").text(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function TransferComplete(xml) {
|
||||||
|
return {
|
||||||
|
name: "TransferComplete",
|
||||||
|
commandKey: xml.get("CommandKey").text(),
|
||||||
|
faultStruct: faultStruct(xml.get("FaultStruct")),
|
||||||
|
startTime: Date.parse(xml.get("StartTime").text()),
|
||||||
|
completeTime: Date.parse(xml.get("CompleteTime").text())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function TransferCompleteResponse(xml, methodResponse) {
|
||||||
|
xml.node("cwmp:TransferCompleteResponse").text("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RequestDownload(xml) {
|
||||||
|
return {
|
||||||
|
name: "RequestDownload",
|
||||||
|
fileType: xml.get("FileType").text()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RequestDownloadResponse(xml, methodResponse) {
|
||||||
|
xml.node("cwmp:RequestDownloadResponse").text("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function faultStruct(xml) {
|
||||||
|
const f = {
|
||||||
|
faultCode: xml.get("FaultCode").text(),
|
||||||
|
faultString: xml.get("FaultString").text()
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const e of xml.find("SetParameterValuesFault")) {
|
||||||
|
if (!f.setParameterValuesFault)
|
||||||
|
f.setParameterValuesFault = [];
|
||||||
|
|
||||||
|
f.setParameterValuesFault.push({
|
||||||
|
parameterName: e.get("ParameterName").text(),
|
||||||
|
faultCode: e.get("FaultCode").text(),
|
||||||
|
faultString: e.get("FaultString").text()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function fault(xml, cwmpVersion) {
|
||||||
|
return {
|
||||||
|
faultCode: xml.get("faultcode").text(),
|
||||||
|
faultString: xml.get("faultstring").text(),
|
||||||
|
detail: faultStruct(xml.get("detail/cwmp:Fault", NAMESPACES[cwmpVersion]))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function request(httpRequest, cwmpVersion) {
|
||||||
|
const rpc = {
|
||||||
|
cwmpVersion: cwmpVersion
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = httpRequest.getBody();
|
||||||
|
|
||||||
|
if (!data.length)
|
||||||
|
return rpc;
|
||||||
|
|
||||||
|
const xml = libxmljs.parseXml(data, LIBXMLJS_PARSE_OPTIONS);
|
||||||
|
|
||||||
|
let methodElement;
|
||||||
|
|
||||||
|
if (rpc.cwmpVersion) {
|
||||||
|
methodElement = xml.get("/soap-env:Envelope/soap-env:Body/cwmp:*", NAMESPACES[rpc.cwmpVersion]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// cwmpVersion not passed, thus it's an inform request
|
||||||
|
methodElement = xml.get("/soap-env:Envelope/soap-env:Body/*", NAMESPACES["1.0"]);
|
||||||
|
switch (methodElement.namespace().href()) {
|
||||||
|
case "urn:dslforum-org:cwmp-1-0":
|
||||||
|
rpc.cwmpVersion = "1.0";
|
||||||
|
break;
|
||||||
|
case "urn:dslforum-org:cwmp-1-1":
|
||||||
|
rpc.cwmpVersion = "1.1";
|
||||||
|
break;
|
||||||
|
case "urn:dslforum-org:cwmp-1-2":
|
||||||
|
const timeoutElement = xml.get("/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout", NAMESPACES["1.2"]);
|
||||||
|
if (timeoutElement) {
|
||||||
|
rpc.sessionTimeout = parseInt(timeoutElement.text());
|
||||||
|
rpc.cwmpVersion = "1.3";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rpc.cwmpVersion = "1.2";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "urn:dslforum-org:cwmp-1-3":
|
||||||
|
rpc.cwmpVersion = "1.4";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Unrecognized CWMP version");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rpc.sessionTimeout) {
|
||||||
|
const timeoutElement = xml.get("/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout", NAMESPACES[rpc.cwmpVersion]);
|
||||||
|
if (timeoutElement)
|
||||||
|
rpc.sessionTimeout = parseInt(timeoutElement.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc.id = xml.get("/soap-env:Envelope/soap-env:Header/cwmp:ID", NAMESPACES[rpc.cwmpVersion]).text();
|
||||||
|
|
||||||
|
if (methodElement && !(XML_IGNORE_NAMESPACE && methodElement.name() === "Fault")) {
|
||||||
|
switch (methodElement.name()) {
|
||||||
|
case "Inform":
|
||||||
|
rpc.cpeRequest = Inform(methodElement);
|
||||||
|
break;
|
||||||
|
case "GetRPCMethods":
|
||||||
|
rpc.cpeRequest = GetRPCMethods(methodElement);
|
||||||
|
break;
|
||||||
|
case "TransferComplete":
|
||||||
|
rpc.cpeRequest = TransferComplete(methodElement);
|
||||||
|
break;
|
||||||
|
case "RequestDownload":
|
||||||
|
rpc.cpeRequest = RequestDownload(methodElement);
|
||||||
|
break;
|
||||||
|
case "GetParameterNamesResponse":
|
||||||
|
rpc.cpeResponse = GetParameterNamesResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "GetParameterValuesResponse":
|
||||||
|
rpc.cpeResponse = GetParameterValuesResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "SetParameterValuesResponse":
|
||||||
|
rpc.cpeResponse = SetParameterValuesResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "AddObjectResponse":
|
||||||
|
rpc.cpeResponse = AddObjectResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "DeleteObjectResponse":
|
||||||
|
rpc.cpeResponse = DeleteObjectResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "RebootResponse":
|
||||||
|
rpc.cpeResponse = RebootResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "FactoryResetResponse":
|
||||||
|
rpc.cpeResponse = FactoryResetResponse(methodElement);
|
||||||
|
break;
|
||||||
|
case "DownloadResponse":
|
||||||
|
rpc.cpeResponse = DownloadResponse(methodElement);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`8000 Method not supported ${methodElement.name()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const faultElement = xml.get("/soap-env:Envelope/soap-env:Body/soap-env:Fault", NAMESPACES[rpc.cwmpVersion]);
|
||||||
|
rpc.cpeFault = fault(faultElement, rpc.cwmpVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createSoapEnv(cwmpVersion) {
|
||||||
|
const xml = libxmljs.Document();
|
||||||
|
const env = xml.node("soap-env:Envelope");
|
||||||
|
|
||||||
|
for (const prefix of Object.keys(NAMESPACES[cwmpVersion]))
|
||||||
|
env.defineNamespace(prefix, NAMESPACES[cwmpVersion][prefix]);
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function response(rpc) {
|
||||||
|
const headers = {
|
||||||
|
"Server": SERVER_NAME,
|
||||||
|
"SOAPServer": SERVER_NAME
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!rpc)
|
||||||
|
return {code: 204, headers: headers, data: new Buffer(0)};
|
||||||
|
|
||||||
|
const env = createSoapEnv(rpc.cwmpVersion);
|
||||||
|
const header = env.node("soap-env:Header");
|
||||||
|
header.node("cwmp:ID").attr({
|
||||||
|
"soap-env:mustUnderstand": 1
|
||||||
|
}).text(rpc.id);
|
||||||
|
|
||||||
|
const body = env.node("soap-env:Body");
|
||||||
|
|
||||||
|
if (rpc.acsResponse) {
|
||||||
|
switch (rpc.acsResponse.name) {
|
||||||
|
case "InformResponse":
|
||||||
|
InformResponse(body, rpc.acsResponse);
|
||||||
|
break;
|
||||||
|
case "GetRPCMethodsResponse":
|
||||||
|
GetRPCMethodsResponse(body, rpc.acsResponse);
|
||||||
|
break;
|
||||||
|
case "TransferCompleteResponse":
|
||||||
|
TransferCompleteResponse(body, rpc.acsResponse);
|
||||||
|
break;
|
||||||
|
case "RequestDownloadResponse":
|
||||||
|
RequestDownloadResponse(body, rpc.acsResponse);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown method response type ${rpc.acsResponse.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (rpc.acsRequest) {
|
||||||
|
switch (rpc.acsRequest.name) {
|
||||||
|
case "GetParameterNames":
|
||||||
|
GetParameterNames(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "GetParameterValues":
|
||||||
|
GetParameterValues(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "SetParameterValues":
|
||||||
|
SetParameterValues(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "AddObject":
|
||||||
|
AddObject(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "DeleteObject":
|
||||||
|
DeleteObject(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "Reboot":
|
||||||
|
Reboot(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "FactoryReset":
|
||||||
|
FactoryReset(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
case "Download":
|
||||||
|
Download(body, rpc.acsRequest);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown method request ${rpc.acsRequest.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headers["Content-Type"] = "text/xml; charset=\"utf-8\"";
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
headers: headers,
|
||||||
|
data: new Buffer(env.doc().toString(LIBXMLJS_SAVE_OPTIONS))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exports.request = request;
|
||||||
|
exports.response = response;
|
||||||
Reference in new issue
Block a user