Unify currentRequest and sessionData structures

This commit is contained in:
Zaid Abdulla committed 2017-02-25 16:24:53 -08:00
1 parent bdcb856655
commit 59d412155b
6 files changed
+1285 -1189

No files matched your search

+259 -261
View File
@@ -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
View File
@@ -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);
} }
+28 -26
View File
@@ -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;
if (details.cpeRequest) {
if (details.cpeRequest.type === "Inform") {
details.informEvent = details.cpeRequest.event.join(",");
details.informRetryCount = details.cpeRequest.retryCount;
} }
else { else if (details.rpc.cpeRequest) {
details.cpeRequestName = details.cpeRequest.type; details.cpeRequestId = details.rpc.id;
if (details.cpeRequest.commandKey) if (details.rpc.cpeRequest.name === "Inform") {
details.cpeRequestCommandKey = details.cpeRequest.commandKey; details.informEvent = details.rpc.cpeRequest.event.join(",");
details.informRetryCount = details.rpc.cpeRequest.retryCount;
}
else {
details.cpeRequestName = details.rpc.cpeRequest.type;
if (details.rpc.cpeRequest.commandKey)
details.cpeRequestCommandKey = details.rpc.cpeRequest.commandKey;
}
} }
delete details.cpeRequest; else if (details.rpc.cpeFault) {
} details.acsRequestId = details.rpc.id;
details.cpeFaultCode = details.rpc.cpeFault.faultCode;
if (details.cpeFault) { details.cpeFaultString = details.rpc.cpeFault.faultString;
details.cpeFaultCode = details.cpeFault.faultCode; }
details.cpeFaultString = details.cpeFault.faultString; delete details.rpc;
delete details.cpeFault;
} }
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
View File
File diff suppressed because it is too large. Load diff
-464
View File
@@ -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
View File
@@ -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;