diff --git a/lib/cache.coffee b/lib/cache.coffee
new file mode 100644
index 0000000..3d77608
--- /dev/null
+++ b/lib/cache.coffee
@@ -0,0 +1,175 @@
+###
+# Copyright 2013-2016 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 .
+###
+
+vm = require 'vm'
+crypto = require 'crypto'
+
+db = require './db'
+
+REFRESH = 3000
+
+nextRefresh = 0
+hash = null
+presets = null
+scripts = null
+
+
+UNLOCK_SCRIPT = 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end'
+EXTEND_SCRIPT = 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("pexpire", KEYS[1], ARGV[2]) else return 0 end'
+
+lock = (lockName, ttl, callback) ->
+ token = Math.random().toString(36).slice(2)
+ unlockOrExtend = (ttl) ->
+ if not ttl
+ db.redisClient.eval(UNLOCK_SCRIPT, 1, lockName, token, (err, res) ->
+ throw err or new Error('Lock expired') if err or not res
+ )
+ else
+ db.redisClient.eval(EXTEND_SCRIPT, 1, lockName, token, ttl, (err, res) ->
+ throw err or new Error('Lock expired') if err or not res
+ )
+ return
+
+ db.redisClient.set(lockName, token, 'NX', 'PX', ttl, (err, res) ->
+ if err or res
+ return callback(err, unlockOrExtend)
+
+ setTimeout(() ->
+ return lock(lockName, ttl, callback)
+ , 200)
+ )
+
+
+computeHash = () ->
+ # MD5 hash for presets and scripts for detecting changes
+ h = crypto.createHash('md5')
+ for p in presets
+ h.update(JSON.stringify(p.name))
+ h.update(JSON.stringify(p.precondition))
+ h.update(JSON.stringify(p.provisions))
+
+ for s in Object.keys(scripts).sort()
+ h.update(JSON.stringify(s))
+ h.update(JSON.stringify(scripts[s].source))
+
+ return h.digest('hex')
+
+
+refresh = (callback) ->
+ now = Date.now()
+ db.redisClient.get('presets_hash', (err, res) ->
+ return callback(err) if err
+
+ if hash? and res == hash
+ nextRefresh = now + REFRESH
+ return callback()
+
+ lock('presets_hash_lock', 3000, (err, unlockOrExtend) ->
+ counter = 2
+ db.presetsCollection.find().toArray((err, res) ->
+ if err
+ callback(err) if -- counter >= 0
+ counter = 0
+ return
+
+ res.sort((a, b) ->
+ if a.weight == b.weight
+ return a._id > b._id
+ else
+ return a.weight - b.weight
+ )
+
+ presets = []
+
+ for preset in res
+ precondition = JSON.parse(preset.precondition)
+
+ provisions = preset.provisions or []
+
+ # Generate provisions from the old configuration format
+ for c in preset.configurations
+ switch c.type
+ when 'age'
+ provisions.push(['refresh', c.name, c.age * -1000])
+ when 'value'
+ provisions.push(['value', c.name, c.value])
+ when 'add_tag'
+ provisions.push(['tag', c.tag, true])
+ when 'delete_tag'
+ provisions.push(['tag', c.tag, false])
+ when 'script'
+ provisions.push([c.name].concat(c.args or []))
+ else
+ throw new Error("Unknown configuration type #{c.type}")
+
+ presets.push({name: preset._id, precondition: precondition, provisions: provisions})
+
+ if -- counter == 0
+ computeHash()
+ db.redisClient.setex("presets_hash", 300, hash, (err) ->
+ unlockOrExtend(0)
+ nextRefresh = now + REFRESH
+ return callback()
+ )
+ )
+
+ db.scriptsCollection.find().toArray((err, res) ->
+ if err
+ callback(err) if -- counter >= 0
+ counter = 0
+ return
+
+ scripts = {}
+ for r in res
+ scripts[r._id] = {}
+ scripts[r._id].source = r.source
+ scripts[r._id].md5 = r.md5
+ scripts[r._id].compiled = vm.Script(r.source, {filename: r._id, timeout: 50})
+
+ if -- counter == 0
+ computeHash()
+ db.redisClient.setex("presets_hash", 300, hash, (err) ->
+ unlockOrExtend(0)
+ nextRefresh = now + REFRESH
+ return callback()
+ )
+ )
+ )
+ )
+
+
+
+getPresets = (callback) ->
+ if Date.now() < nextRefresh
+ return callback(null, hash, presets)
+
+ refresh((err) ->
+ return callback(err, hash, presets)
+ )
+
+
+getScripts = (callback) ->
+ if Date.now() < nextRefresh
+ return callback(null, hash, scripts)
+
+ refresh((err) ->
+ return callback(err, hash, scripts)
+ )
+
+
+exports.getPresets = getPresets
+exports.getScripts = getScripts
diff --git a/lib/cwmp.coffee b/lib/cwmp.coffee
index 970b62f..c35faf3 100644
--- a/lib/cwmp.coffee
+++ b/lib/cwmp.coffee
@@ -44,10 +44,10 @@ zlib = require 'zlib'
config = require './config'
common = require './common'
soap = require './soap'
-db = require './db'
session = require './session'
query = require './query'
device = require './device'
+cache = require './cache'
writeResponse = (currentRequest, res) ->
@@ -104,7 +104,11 @@ inform = (currentRequest, cwmpRequest) ->
applyPresets = (currentRequest) ->
- db.getPresetsObjectsAliases((presets, objects, aliases) ->
+ cache.getPresets((err, presetsHash, presets) ->
+ throw err if err
+
+ currentRequest.sessionData.presetsHash = presetsHash
+
parameters = {}
for p in presets
for a in Object.keys(query.queryProjection(p.precondition))
@@ -126,10 +130,12 @@ applyPresets = (currentRequest) ->
for p in presets
if query.test(parameters, p.precondition)
- session.addPreset(currentRequest.sessionData, p._id, p)
+ session.addProvisions(currentRequest.sessionData, p.provisions)
session.rpcRequest(currentRequest.sessionData, null, (err, id, rpcRequest) ->
throw err if err
+ if not rpcRequest?
+ session.clearProvisions(currentRequest.sessionData)
sendRpcRequest(currentRequest, id, rpcRequest)
)
)
@@ -140,6 +146,7 @@ nextRpc = (currentRequest) ->
session.rpcRequest(currentRequest.sessionData, null, (err, id, rpcRequest) ->
throw err if err
if not rpcRequest?
+ session.clearProvisions(currentRequest.sessionData)
return applyPresets(currentRequest)
sendRpcRequest(currentRequest, id, rpcRequest)
diff --git a/lib/db.coffee b/lib/db.coffee
index 4e91799..f7adf69 100644
--- a/lib/db.coffee
+++ b/lib/db.coffee
@@ -26,10 +26,11 @@ tasksCollection = null
devicesCollection = null
presetsCollection = null
objectsCollection = null
+scriptsCollection = null
connect = (callback) ->
- callbackCounter = 6
+ callbackCounter = 7
mongodb.MongoClient.connect(config.get('MONGODB_CONNECTION_URL'), {db:{w:1},server:{autoReconnect:true}}, (err, db) ->
return callback(err) if err
exports.mongoDb = db
@@ -71,6 +72,13 @@ connect = (callback) ->
return callback(err)
)
+ db.collection('scripts', (err, collection) ->
+ exports.scriptsCollection = scriptsCollection = collection
+ if --callbackCounter == 0 or err
+ callbackCounter = 0
+ return callback(err)
+ )
+
exports.redisClient = redisClient = redis.createClient(config.get('REDIS_PORT'), config.get('REDIS_HOST'))
redisClient.select(config.get('REDIS_DB'), (err) ->
if --callbackCounter == 0 or err
diff --git a/lib/nbi.coffee b/lib/nbi.coffee
index 6ba49c3..2f19cd5 100644
--- a/lib/nbi.coffee
+++ b/lib/nbi.coffee
@@ -41,6 +41,7 @@
url = require 'url'
mongodb = require 'mongodb'
querystring = require 'querystring'
+crypto = require 'crypto'
config = require './config'
common = require './common'
@@ -58,6 +59,7 @@ FILES_REGEX = /^\/files\/([a-zA-Z0-9\%\!\*\'\(\)\;\:\@\&\=\+\$\,\?\#\[\]\-\_\.\~
PING_REGEX = /^\/ping\/([a-zA-Z0-9\-\_\.]+)\/?$/
QUERY_REGEX = /^\/([a-zA-Z0-9_]+s)\/?$/
DELETE_DEVICE_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/?$/
+SCRIPTS_REGEX = /^\/scripts\/([a-zA-Z0-9\-\_\%]+)\/?$/
errorToString = (err) ->
@@ -149,6 +151,41 @@ listener = (request, response) ->
else
response.writeHead 405, {'Allow': 'PUT, DELETE'}
response.end('405 Method Not Allowed')
+ else if SCRIPTS_REGEX.test(urlParts.pathname)
+ scriptName = querystring.unescape(SCRIPTS_REGEX.exec(urlParts.pathname)[1])
+ if request.method == 'PUT'
+ object = {
+ _id: scriptName
+ source: body.toString()
+ md5: crypto.createHash('md5').update(body).digest('hex')
+ }
+
+ db.scriptsCollection.save(object, (err) ->
+ db.redisClient.del('presets_hash', (err) ->
+ throw err if err
+ )
+ if err
+ response.writeHead(500)
+ response.end(errorToString(err))
+ return
+ response.writeHead(200)
+ response.end()
+ )
+ else if request.method == 'DELETE'
+ db.scriptsCollection.remove({'_id' : scriptName}, (err) ->
+ db.redisClient.del('presets_hash', (err) ->
+ throw err if err
+ )
+ if err
+ response.writeHead(500)
+ response.end(errorToString(err))
+ return
+ response.writeHead(200)
+ response.end()
+ )
+ else
+ response.writeHead 405, {'Allow': 'PUT, DELETE'}
+ response.end('405 Method Not Allowed')
else if TAGS_REGEX.test(urlParts.pathname)
r = TAGS_REGEX.exec(urlParts.pathname)
deviceId = querystring.unescape(r[1])
diff --git a/lib/provisions.coffee b/lib/provisions.coffee
deleted file mode 100644
index c266ba8..0000000
--- a/lib/provisions.coffee
+++ /dev/null
@@ -1,46 +0,0 @@
-###
-# Copyright 2013-2016 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 .
-###
-
-common = require './common'
-
-
-processProvision = (type, args, callback) ->
- switch type
- when 'refresh'
- declarations = []
- path = common.parsePath(args[0])
- declaration = [args[1], null, null, null, null, null, args[1]]
- for i in [path.length...14] by 1
- path = path.slice()
- path.length = i
- declarations.push([path].concat(declaration))
-
- return callback(null, declarations)
-
- when 'value'
- declarations = [[common.parsePath(args[0]), args[1], null, null, null, null, null, args[1]]]
- return callback(null, declarations)
-
- when 'tag'
- declarations = [[['Tags', args[0]], null, null, null, null, null, null, null, [args[1], 'xsd:boolean']]]
- return callback(null, declarations)
-
- else
- return callback(new Error("Unknown provision type '#{type}'"))
-
-
-exports.processProvision = processProvision
diff --git a/lib/sandbox.coffee b/lib/sandbox.coffee
new file mode 100644
index 0000000..cd3b6a9
--- /dev/null
+++ b/lib/sandbox.coffee
@@ -0,0 +1,121 @@
+###
+# Copyright 2013-2016 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 .
+###
+
+vm = require 'vm'
+
+common = require './common'
+device = require './device'
+
+
+# Used for throwing to exit user script
+class Declare;
+
+
+sandbox = {
+ deviceData: null
+ args: null
+ revision: null
+ maxRevision: null
+ declarations: null
+ context: vm.createContext()
+}
+
+sandbox.context.declare = (decs) ->
+ if ++ sandbox.revision > sandbox.maxRevision + 1
+ throw new Error('Declare function should not be called from within a try/catch block')
+
+ sandbox.declarations = decs
+
+ if sandbox.revision == sandbox.maxRevision + 1
+ throw new Declare()
+
+ res = {}
+ for k, v of decs
+ path = common.parsePath(k)
+ all = device.getAll(sandbox.deviceData, path, sandbox.revision)
+ res[k] = {}
+ for a in all
+ r = {}
+ r.exist = a[1] if v.exist?
+ r.type = a.slice(3, 5) if v.type?
+ r.writable = a.slice(5, 7) if v.writable?
+ r.value = a.slice(7, 9) if v.value? and a[8]?
+
+ p = a[0].join('.')
+ if p == k
+ res[k] = r
+ else
+ res[k][p] = r
+
+ return res
+
+Object.freeze(sandbox.context)
+
+
+# Convert from friendly declaration format
+toIndexDeclaration = (keyDeclaraiton) ->
+ indexDeclaraiton = []
+
+ if keyDeclaraiton['exist']?
+ if Array.isArray(keyDeclaraiton['exist'])
+ indexDeclaraiton[0] = keyDeclaraiton['exist'][0]
+ else
+ indexDeclaraiton[0] = keyDeclaraiton['exist']
+
+ if keyDeclaraiton['type']?
+ if Array.isArray(keyDeclaraiton['type'])
+ indexDeclaraiton[2] = keyDeclaraiton['type'][0]
+ else
+ indexDeclaraiton[2] = keyDeclaraiton['type']
+
+ if keyDeclaraiton['writable']?
+ if Array.isArray(keyDeclaraiton['writable'])
+ indexDeclaraiton[4] = keyDeclaraiton['writable'][0]
+ else
+ indexDeclaraiton[4] = keyDeclaraiton['writable']
+
+ if keyDeclaraiton['value']?
+ if Array.isArray(keyDeclaraiton['value'])
+ indexDeclaraiton[6] = keyDeclaraiton['value'][0]
+ indexDeclaraiton[7] = keyDeclaraiton['value'][1]
+ else
+ indexDeclaraiton[6] = keyDeclaraiton['value']
+
+ return indexDeclaraiton
+
+
+run = (script, args, deviceData, revision, declarations) ->
+ sandbox.deviceData = deviceData
+ sandbox.args = args
+ sandbox.maxRevision = revision
+ sandbox.revision = 0
+ sandbox.declarations = []
+
+ try
+ script.runInNewContext(sandbox.context)
+ catch err
+ throw err if err not instanceof Declare
+ for k, v of sandbox.declarations
+ declarations.push([common.parsePath(k)].concat(toIndexDeclaration(v)))
+ return true
+
+ for k, v of sandbox.declarations
+ declarations.push([common.parsePath(k)].concat(toIndexDeclaration(v)))
+ return false
+
+
+exports.run = run
diff --git a/lib/session.coffee b/lib/session.coffee
index 2df0197..303a945 100644
--- a/lib/session.coffee
+++ b/lib/session.coffee
@@ -21,7 +21,8 @@ config = require './config'
common = require './common'
db = require './db'
device = require './device'
-provisions = require './provisions'
+sandbox = require './sandbox'
+cache = require './cache'
init = (deviceId, cwmpVersion, timeout) ->
@@ -35,8 +36,8 @@ init = (deviceId, cwmpVersion, timeout) ->
deviceData : deviceData
cwmpVersion : cwmpVersion
timeout : timeout
- presets : {}
- rpcCount : 0
+ provisions: []
+ counter : [0, 0, 0] # rpc, revision, batch
}
return sessionData
@@ -65,26 +66,47 @@ loadParameters = (sessionData, toLoad, callback) ->
)
-inform = (sessionData, rpcRequest, callback) ->
- timestamp = sessionData.timestamp
- device.set(sessionData.deviceData, ['DeviceID', 'Manufacturer'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcRequest.deviceId.Manufacturer, 'xsd:string']])
- device.set(sessionData.deviceData, ['DeviceID', 'OUI'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcRequest.deviceId.OUI, 'xsd:string']])
- device.set(sessionData.deviceData, ['DeviceID', 'ProductClass'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcRequest.deviceId.ProductClass, 'xsd:string']])
- device.set(sessionData.deviceData, ['DeviceID', 'SerialNumber'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcRequest.deviceId.SerialNumber, 'xsd:string']])
+generateRpcId = (sessionData) ->
+ if sessionData.counter[0] > 255 or sessionData.counter[1] > 15 or sessionData.counter[2] > 15
+ throw new Error('Too many RPCs')
- for p in rpcRequest.parameterList
+ return sessionData.counter[2].toString(16).slice(-1) +
+ sessionData.counter[1].toString(16).slice(-1) +
+ ('0' + sessionData.counter[0].toString(16)).slice(-2)
+
+
+inform = (sessionData, rpcReq, callback) ->
+ timestamp = sessionData.timestamp
+ device.set(sessionData.deviceData, ['DeviceID', 'Manufacturer'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcReq.deviceId.Manufacturer, 'xsd:string']])
+ device.set(sessionData.deviceData, ['DeviceID', 'OUI'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcReq.deviceId.OUI, 'xsd:string']])
+ device.set(sessionData.deviceData, ['DeviceID', 'ProductClass'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcReq.deviceId.ProductClass, 'xsd:string']])
+ device.set(sessionData.deviceData, ['DeviceID', 'SerialNumber'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcReq.deviceId.SerialNumber, 'xsd:string']])
+
+ for p in rpcReq.parameterList
device.set(sessionData.deviceData, common.parsePath(p[0]), 1, [timestamp, 1, timestamp, 0, null, null, timestamp, p.slice(1)])
device.set(sessionData.deviceData, ['Events', 'Inform'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [timestamp, 'xsd:dateTime']])
- for e in rpcRequest.event
+ for e in rpcReq.event
device.set(sessionData.deviceData, ['Events', e.replace(' ', '_')], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [timestamp, 'xsd:dateTime']])
return callback(null, {type : 'InformResponse'})
-addPreset = (sessionData, name, preset) ->
- sessionData.presets[name] = preset
+addProvisions = (sessionData, provisions) ->
+ sessionData.provisions = sessionData.provisions.concat(provisions)
+ if sessionData.counter[1] > 0
+ sessionData.counter[2] += 1
+ sessionData.counter[1] = 0
+ sessionData.counter[0] = 0
+
+
+clearProvisions = (sessionData) ->
+ sessionData.provisions = []
+ if sessionData.counter[1] > 0
+ sessionData.counter[2] += 1
+ sessionData.counter[1] = 0
+ sessionData.counter[0] = 0
generateRpcRequest = (sessionData, declarations, callback) ->
@@ -214,26 +236,26 @@ generateRpcRequest = (sessionData, declarations, callback) ->
return
if res.gpn?
- rpcRequest = {
+ rpcReq = {
type: 'GetParameterNames'
parameterPath: res.gpn[0].join('.')
nextLevel: true
}
- return callback(null, rpcRequest)
+ return callback(null, rpcReq)
if res.gpv?
- rpcRequest = {
+ rpcReq = {
type: 'GetParameterValues'
parameterNames: (p.join('.') for p in res.gpv.slice(0, config.get('TASK_PARAMETERS_BATCH_SIZE', sessionData.deviceId)))
}
- return callback(null, rpcRequest)
+ return callback(null, rpcReq)
if res.spv?
- rpcRequest = {
+ rpcReq = {
type: 'SetParameterValues'
parameterList: ([p[0].join('.'), p[1], p[2]] for p in res.spv.slice(0, config.get('TASK_PARAMETERS_BATCH_SIZE', sessionData.deviceId)))
}
- return callback(null, rpcRequest)
+ return callback(null, rpcReq)
return cb()
@@ -263,74 +285,73 @@ generateRpcRequest = (sessionData, declarations, callback) ->
rpcRequest = (sessionData, allDeclarations, callback) ->
- presetNames = Object.keys(sessionData.presets)
- presetNames.sort((a, b) ->
- if sessionData.presets[a].weight == sessionData.presets[b].weight
- return a > b
- else
- return sessionData.presets[a].weight - sessionData.presets[b].weight
- )
-
allDeclarations = allDeclarations?.slice() ? []
- counter = 1
- for presetName in presetNames
- do (presetName) ->
- for provision in sessionData.presets[presetName].provisions
- ++ counter
- provisions.processProvision(provision[0], provision[1..], (err, declarations) ->
- if err
- callback(err) if -- counter >= 0
- counter = 0
- return
+ nextRevision = false
- allDeclarations = allDeclarations.concat(declarations)
+ cache.getScripts((err, presetsHash, scripts) ->
+ return callback(err) if err or presetsHash != sessionData.presetsHash
- if -- counter == 0
- generateRpcRequest(sessionData, allDeclarations, (err, rpcRequest) ->
- sessionData.rpcRequest = rpcRequest
- if err or not rpcRequest?
- return callback(err)
+ for provision in sessionData.provisions
+ if not scripts[provision[0]]?
+ switch provision[0]
+ when 'refresh'
+ path = common.parsePath(provision[1])
+ for i in [path.length...16] by 1
+ path.length = i
+ allDeclarations.push([path.slice(), provision[2], null, null, null, null, null, provision[2]])
+ when 'value'
+ allDeclarations.push([[common.parsePath(provision[1]), provision[2], null, null, null, null, null, provision[2]]])
+ when 'tag'
+ allDeclarations.push([[['Tags', provision[1]], null, null, null, null, null, null, null, [provision[2], 'xsd:boolean']]])
+ continue
- callback(err, sessionData.rpcCount++, rpcRequest)
- )
- return
- )
+ nextRevision ||= sandbox.run(scripts[provision[0]].compiled, provision.slice(1), sessionData.deviceData, sessionData.counter[1], allDeclarations)
- if -- counter == 0
- generateRpcRequest(sessionData, allDeclarations, (err, rpcRequest) ->
- sessionData.rpcRequest = rpcRequest
- if err or not rpcRequest?
- return callback(err)
+ generateRpcRequest(sessionData, allDeclarations, (err, rpcReq) ->
+ sessionData.rpcRequest = rpcReq
+ return callback(err) if err
- callback(err, sessionData.rpcCount++, rpcRequest)
+ if not rpcReq?
+ if not nextRevision
+ return callback()
+ sessionData.counter[0] = 0
+ sessionData.counter[1] += 1
+ return rpcRequest(sessionData, null, callback)
+
+ callback(err, generateRpcId(sessionData), rpcReq)
)
- return
+ )
+ return
-rpcResponse = (sessionData, id, rpcResponse, callback) ->
- # TODO verify ID
- rpcRequest = sessionData.rpcRequest
+rpcResponse = (sessionData, id, rpcRes, callback) ->
+ if id != generateRpcId(sessionData)
+ return callback(new Error('Request ID not recognized'))
+
+ ++ sessionData.counter[0]
+
+ rpcReq = sessionData.rpcRequest
sessionData.rpcRequest = null
timestamp = sessionData.timestamp
- switch rpcResponse.type
+ switch rpcRes.type
when 'GetParameterValuesResponse'
- return callback(new Error('Response type does not match request type')) if rpcRequest.type isnt 'GetParameterValues'
+ return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'GetParameterValues'
- for p in rpcResponse.parameterList
+ for p in rpcRes.parameterList
device.set(sessionData.deviceData, common.parsePath(p[0]), 1, [timestamp, 1, timestamp, 0, null, null, timestamp, p.slice(1)])
when 'GetParameterNamesResponse'
- return callback(new Error('Response type does not match request type')) if rpcRequest.type isnt 'GetParameterNames'
+ return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'GetParameterNames'
- device.set(sessionData.deviceData, common.parsePath(rpcRequest.parameterPath).concat(null), 1, timestamp)
+ device.set(sessionData.deviceData, common.parsePath(rpcReq.parameterPath).concat(null), 1, timestamp)
- for p in rpcResponse.parameterList
+ for p in rpcRes.parameterList
if common.endsWith(p[0], '.')
path = common.parsePath(p[0][0...-1])
- if not rpcRequest.nextLevel
+ if not rpcReq.nextLevel
device.set(sessionData.deviceData, path.conact(null), 1, timestamp)
device.set(sessionData.deviceData, path, 1, [timestamp, 1, timestamp, 1, timestamp, if p[1] then 1 else 0])
@@ -338,9 +359,9 @@ rpcResponse = (sessionData, id, rpcResponse, callback) ->
device.set(sessionData.deviceData, common.parsePath(p[0]), 1, [timestamp, 1, timestamp, 0, timestamp, if p[1] then 1 else 0])
when 'SetParameterValuesResponse'
- return callback(new Error('Response type does not match request type')) if rpcRequest.type isnt 'SetParameterValues'
+ return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'SetParameterValues'
- for p in rpcRequest.parameterList
+ for p in rpcReq.parameterList
device.set(sessionData.deviceData, common.parsePath(p[0]), 1, [timestamp, 1, timestamp, 0, timestamp, 1, timestamp, p.slice(1)])
else
@@ -410,7 +431,8 @@ end = (sessionData, callback) ->
exports.init = init
exports.inform = inform
-exports.addPreset = addPreset
+exports.addProvisions = addProvisions
+exports.clearProvisions = clearProvisions
exports.rpcRequest = rpcRequest
exports.rpcResponse = rpcResponse
exports.rpcFault = rpcFault