Virtual parameters

Implemented support for user-defined parameters driven by Javascript
code. Renamed scripts collection to provisions.
This commit is contained in:
Zaid Abdulla committed 2016-04-17 18:54:48 +03:00
1 parent 359ff44819
commit 7b18cb3bbf
6 files changed
+401 -166

No files matched your search

+68 -25
View File
@@ -25,7 +25,8 @@ REFRESH = 3000
nextRefresh = 0 nextRefresh = 0
hash = null hash = null
presets = null presets = null
scripts = null provisions = null
virtualParameters = null
UNLOCK_SCRIPT = 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end' UNLOCK_SCRIPT = 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end'
@@ -55,18 +56,22 @@ lock = (lockName, ttl, callback) ->
computeHash = () -> computeHash = () ->
# MD5 hash for presets and scripts for detecting changes # MD5 hash for presets, provisions, virtual parameters for detecting changes
h = crypto.createHash('md5') h = crypto.createHash('md5')
for p in presets for p in presets
h.update(JSON.stringify(p.name)) h.update(JSON.stringify(p.name))
h.update(JSON.stringify(p.precondition)) h.update(JSON.stringify(p.precondition))
h.update(JSON.stringify(p.provisions)) h.update(JSON.stringify(p.provisions))
for s in Object.keys(scripts).sort() keys = Object.keys(provisions).sort()
h.update(JSON.stringify(s)) h.update(JSON.stringify(keys))
h.update(JSON.stringify(scripts[s].source)) h.update(provisions[k].md5) for k in keys
return h.digest('hex') keys = Object.keys(virtualParameters).sort()
h.update(JSON.stringify(keys))
h.update(virtualParameters[k].md5) for k in keys
hash = h.digest('hex')
refresh = (callback) -> refresh = (callback) ->
@@ -79,7 +84,7 @@ refresh = (callback) ->
return callback() return callback()
lock('presets_hash_lock', 3000, (err, unlockOrExtend) -> lock('presets_hash_lock', 3000, (err, unlockOrExtend) ->
counter = 2 counter = 3
db.presetsCollection.find().toArray((err, res) -> db.presetsCollection.find().toArray((err, res) ->
if err if err
callback(err) if -- counter >= 0 callback(err) if -- counter >= 0
@@ -98,25 +103,25 @@ refresh = (callback) ->
for preset in res for preset in res
precondition = JSON.parse(preset.precondition) precondition = JSON.parse(preset.precondition)
provisions = preset.provisions or [] _provisions = preset.provisions or []
# Generate provisions from the old configuration format # Generate provisions from the old configuration format
for c in preset.configurations for c in preset.configurations
switch c.type switch c.type
when 'age' when 'age'
provisions.push(['refresh', c.name, c.age * -1000]) _provisions.push(['refresh', c.name, c.age * -1000])
when 'value' when 'value'
provisions.push(['value', c.name, c.value]) _provisions.push(['value', c.name, c.value])
when 'add_tag' when 'add_tag'
provisions.push(['tag', c.tag, true]) _provisions.push(['tag', c.tag, true])
when 'delete_tag' when 'delete_tag'
provisions.push(['tag', c.tag, false]) _provisions.push(['tag', c.tag, false])
when 'script' when 'provision'
provisions.push([c.name].concat(c.args or [])) _provisions.push([c.name].concat(c.args or []))
else else
throw new Error("Unknown configuration type #{c.type}") throw new Error("Unknown configuration type #{c.type}")
presets.push({name: preset._id, precondition: precondition, provisions: provisions}) presets.push({name: preset._id, precondition: precondition, provisions: _provisions})
if -- counter == 0 if -- counter == 0
computeHash() computeHash()
@@ -127,18 +132,38 @@ refresh = (callback) ->
) )
) )
db.scriptsCollection.find().toArray((err, res) -> db.provisionsCollection.find().toArray((err, res) ->
if err if err
callback(err) if -- counter >= 0 callback(err) if -- counter >= 0
counter = 0 counter = 0
return return
scripts = {} provisions = {}
for r in res for r in res
scripts[r._id] = {} provisions[r._id] = {}
scripts[r._id].source = r.source provisions[r._id].md5 = crypto.createHash('md5').update(r.script).digest('hex')
scripts[r._id].md5 = r.md5 provisions[r._id].script = vm.Script(r.script, {filename: r._id, timeout: 50})
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()
)
)
db.virtualParametersCollection.find().toArray((err, res) ->
if err
callback(err) if -- counter >= 0
counter = 0
return
virtualParameters = {}
for r in res
virtualParameters[r._id] = {}
virtualParameters[r._id].md5 = crypto.createHash('md5').update(r.script).digest('hex')
virtualParameters[r._id].script = vm.Script(r.script, {filename: r._id, timeout: 50})
if -- counter == 0 if -- counter == 0
computeHash() computeHash()
@@ -162,14 +187,32 @@ getPresets = (callback) ->
) )
getScripts = (callback) -> getProvisions = (callback) ->
if Date.now() < nextRefresh if Date.now() < nextRefresh
return callback(null, hash, scripts) return callback(null, hash, provisions)
refresh((err) -> refresh((err) ->
return callback(err, hash, scripts) return callback(err, hash, provisions)
) )
getVirtualParameters = (callback) ->
if Date.now() < nextRefresh
return callback(null, hash, virtualParameters)
refresh((err) ->
return callback(err, hash, virtualParameters)
)
getProvisionsAndVirtualParameters = (callback) ->
if Date.now() < nextRefresh
return callback(null, hash, provisions, virtualParameters)
refresh((err) ->
return callback(err, hash, provisions, virtualParameters)
)
exports.getPresets = getPresets exports.getPresets = getPresets
exports.getScripts = getScripts exports.getProvisions = getProvisions
exports.getProvisionsAndVirtualParameters = getProvisionsAndVirtualParameters
+12 -4
View File
@@ -26,11 +26,12 @@ tasksCollection = null
devicesCollection = null devicesCollection = null
presetsCollection = null presetsCollection = null
objectsCollection = null objectsCollection = null
scriptsCollection = null provisionsCollection = null
virtualParametersCollection = null
connect = (callback) -> connect = (callback) ->
callbackCounter = 7 callbackCounter = 8
mongodb.MongoClient.connect(config.get('MONGODB_CONNECTION_URL'), {db:{w:1},server:{autoReconnect:true}}, (err, db) -> mongodb.MongoClient.connect(config.get('MONGODB_CONNECTION_URL'), {db:{w:1},server:{autoReconnect:true}}, (err, db) ->
return callback(err) if err return callback(err) if err
exports.mongoDb = db exports.mongoDb = db
@@ -72,8 +73,15 @@ connect = (callback) ->
return callback(err) return callback(err)
) )
db.collection('scripts', (err, collection) -> db.collection('provisions', (err, collection) ->
exports.scriptsCollection = scriptsCollection = collection exports.provisionsCollection = provisionsCollection = collection
if --callbackCounter == 0 or err
callbackCounter = 0
return callback(err)
)
db.collection('virtualParameters', (err, collection) ->
exports.virtualParametersCollection = virtualParametersCollection = collection
if --callbackCounter == 0 or err if --callbackCounter == 0 or err
callbackCounter = 0 callbackCounter = 0
return callback(err) return callback(err)
+44 -2
View File
@@ -98,7 +98,7 @@ init = () ->
declare = (deviceData, path, declaration, now) -> declare = (deviceData, path, declaration, now) ->
wildcard = false wildcard = false
for i in [0...path.length] when not path[i]? for i in [0...path.length] by 1 when not path[i]?
wildcard = true wildcard = true
break break
@@ -137,7 +137,7 @@ declare = (deviceData, path, declaration, now) ->
set = (deviceData, path, revision, properties) -> set = (deviceData, path, revision, properties) ->
++ revision # revisions start at layer 1 ++ revision # revisions start at layer 1
wildcard = false wildcard = false
for i in [0...path.length] when not path[i]? for i in [0...path.length] by 1 when not path[i]?
wildcard = true wildcard = true
break break
@@ -343,6 +343,45 @@ traverse = (deviceData, pattern, revision, callback) ->
return recursive(deviceData, [], [], 0, 1) return recursive(deviceData, [], [], 0, 1)
collapse = (deviceData, revision, wildcard) ->
ref = deviceData
if ref[0]?
for depth, i in ref[0] when depth?.length > revision + 2
if i == 0 and not wildcard
depth[revision + 1] ?= []
for j in [revision + 2...depth.length] by 1 when depth[j]?
for k in [0...depth[j].length] by 2 when depth[j][k]?
depth[revision + 1][k] = depth[j][k]
depth[revision + 1][k + 1] = depth[j][k + 1]
else
for j in [revision + 2...depth.length] by 1 when depth[j]?
depth[revision + 1] = depth[j]
depth.length = revision + 2
for i in [1...ref.length] by 1 when ref[i]?
for k, v of ref[i]
collapse(v, revision, wildcard or i != 1)
clearDeclarations = (deviceData, prefix) ->
ref = deviceData
if prefix?
for p in prefix
ref = ref[1]?[p]
return if not ref?
if ref[0]?
for depth in ref[0] when depth?
depth[0] = null
for i in [1...ref.length] by 1 when ref[i]?
for k, v of ref[i]
clearDeclarations(v)
getPrerequisiteDeclarations = (declarations) -> getPrerequisiteDeclarations = (declarations) ->
dec = [declarations[0], 1] dec = [declarations[0], 1]
for i in [1...declarations.length] by 2 for i in [1...declarations.length] by 2
@@ -368,3 +407,6 @@ exports.getAll = getAll
exports.declare = declare exports.declare = declare
exports.set = set exports.set = set
exports.traverse = traverse exports.traverse = traverse
exports.collapse = collapse
exports.clearDeclarations = clearDeclarations
exports.sanitizeParameterValue = sanitizeParameterValue
+42 -9
View File
@@ -41,7 +41,6 @@
url = require 'url' url = require 'url'
mongodb = require 'mongodb' mongodb = require 'mongodb'
querystring = require 'querystring' querystring = require 'querystring'
crypto = require 'crypto'
config = require './config' config = require './config'
common = require './common' common = require './common'
@@ -59,7 +58,8 @@ FILES_REGEX = /^\/files\/([a-zA-Z0-9\%\!\*\'\(\)\;\:\@\&\=\+\$\,\?\#\[\]\-\_\.\~
PING_REGEX = /^\/ping\/([a-zA-Z0-9\-\_\.]+)\/?$/ PING_REGEX = /^\/ping\/([a-zA-Z0-9\-\_\.]+)\/?$/
QUERY_REGEX = /^\/([a-zA-Z0-9_]+s)\/?$/ QUERY_REGEX = /^\/([a-zA-Z0-9_]+s)\/?$/
DELETE_DEVICE_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/?$/ DELETE_DEVICE_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/?$/
SCRIPTS_REGEX = /^\/scripts\/([a-zA-Z0-9\-\_\%]+)\/?$/ PROVISIONS_REGEX = /^\/provisions\/([a-zA-Z0-9\-\_\%]+)\/?$/
VIRTUAL_PARAMETERS_REGEX = /^\/virtual_parameters\/([a-zA-Z0-9\-\_\%]+)\/?$/
errorToString = (err) -> errorToString = (err) ->
@@ -151,16 +151,15 @@ listener = (request, response) ->
else else
response.writeHead 405, {'Allow': 'PUT, DELETE'} response.writeHead 405, {'Allow': 'PUT, DELETE'}
response.end('405 Method Not Allowed') response.end('405 Method Not Allowed')
else if SCRIPTS_REGEX.test(urlParts.pathname) else if PROVISIONS_REGEX.test(urlParts.pathname)
scriptName = querystring.unescape(SCRIPTS_REGEX.exec(urlParts.pathname)[1]) provisionName = querystring.unescape(PROVISIONS_REGEX.exec(urlParts.pathname)[1])
if request.method == 'PUT' if request.method == 'PUT'
object = { object = {
_id: scriptName _id: provisionName
source: body.toString() script: body.toString()
md5: crypto.createHash('md5').update(body).digest('hex')
} }
db.scriptsCollection.save(object, (err) -> db.provisionsCollection.save(object, (err) ->
db.redisClient.del('presets_hash', (err) -> db.redisClient.del('presets_hash', (err) ->
throw err if err throw err if err
) )
@@ -172,7 +171,41 @@ listener = (request, response) ->
response.end() response.end()
) )
else if request.method == 'DELETE' else if request.method == 'DELETE'
db.scriptsCollection.remove({'_id' : scriptName}, (err) -> db.provisionsCollection.remove({'_id' : provisionName}, (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 VIRTUAL_PARAMETERS_REGEX.test(urlParts.pathname)
virtualParameterName = querystring.unescape(VIRTUAL_PARAMETERS_REGEX.exec(urlParts.pathname)[1])
if request.method == 'PUT'
object = {
_id: virtualParameterName
script: body.toString()
}
db.virtualParametersCollection.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.virtualParametersCollection.remove({'_id' : virtualParameterName}, (err) ->
db.redisClient.del('presets_hash', (err) -> db.redisClient.del('presets_hash', (err) ->
throw err if err throw err if err
) )
+9 -12
View File
@@ -38,9 +38,10 @@ sandbox.context.declare = (decs) ->
if ++ sandbox.revision > sandbox.maxRevision + 1 if ++ sandbox.revision > sandbox.maxRevision + 1
throw new Error('Declare function should not be called from within a try/catch block') throw new Error('Declare function should not be called from within a try/catch block')
sandbox.declarations = decs
if sandbox.revision == sandbox.maxRevision + 1 if sandbox.revision == sandbox.maxRevision + 1
for k, v of decs
sandbox.declarations.push([common.parsePath(k)].concat(toIndexDeclaration(v)))
throw new Declare() throw new Declare()
res = {} res = {}
@@ -98,24 +99,20 @@ toIndexDeclaration = (keyDeclaraiton) ->
return indexDeclaraiton return indexDeclaraiton
run = (script, args, deviceData, revision, declarations) -> run = (script, args, deviceData, startRevision, maxRevision) ->
sandbox.deviceData = deviceData sandbox.deviceData = deviceData
sandbox.args = args sandbox.args = args
sandbox.maxRevision = revision sandbox.revision = startRevision
sandbox.revision = 0 sandbox.maxRevision = maxRevision
sandbox.declarations = [] sandbox.declarations = []
try try
script.runInNewContext(sandbox.context) ret = script.runInNewContext(sandbox.context)
return {done: true, declarations: sandbox.declarations, returnValue: ret}
catch err catch err
throw err if err not instanceof Declare 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 return {done: false, declarations: sandbox.declarations}
declarations.push([common.parsePath(k)].concat(toIndexDeclaration(v)))
return false
exports.run = run exports.run = run
+226 -114
View File
@@ -37,7 +37,9 @@ init = (deviceId, cwmpVersion, timeout) ->
cwmpVersion : cwmpVersion cwmpVersion : cwmpVersion
timeout : timeout timeout : timeout
provisions: [] provisions: []
counter : [0, 0, 0] # rpc, revision, batch revisions: [0]
rpcCount: 0
cycle: 0
} }
return sessionData return sessionData
@@ -67,12 +69,10 @@ loadParameters = (sessionData, toLoad, callback) ->
generateRpcId = (sessionData) -> generateRpcId = (sessionData) ->
if sessionData.counter[0] > 255 or sessionData.counter[1] > 15 or sessionData.counter[2] > 15 if sessionData.rpcCount > 255 or sessionData.cycle > 15 or sessionData.revisions.length > 15
throw new Error('Too many RPCs') throw new Error('Too many RPCs')
return sessionData.counter[2].toString(16).slice(-1) + return sessionData.timestamp.toString(16) + "0#{sessionData.rpcCount.toString(16)}".slice(-2)
sessionData.counter[1].toString(16).slice(-1) +
('0' + sessionData.counter[0].toString(16)).slice(-2)
inform = (sessionData, rpcReq, callback) -> inform = (sessionData, rpcReq, callback) ->
@@ -95,25 +95,24 @@ inform = (sessionData, rpcReq, callback) ->
addProvisions = (sessionData, provisions) -> addProvisions = (sessionData, provisions) ->
sessionData.provisions = sessionData.provisions.concat(provisions) sessionData.provisions = sessionData.provisions.concat(provisions)
if sessionData.counter[1] > 0 if sessionData.revisions.length > 1 or sessionData.revisions[0] > 0
sessionData.counter[2] += 1 sessionData.cycle += 1
sessionData.counter[1] = 0 sessionData.rpcCount = 0
sessionData.counter[0] = 0 sessionData.revisions = [0]
device.collapse(sessionData.deviceData, 1)
clearProvisions = (sessionData) -> clearProvisions = (sessionData) ->
sessionData.provisions = [] sessionData.provisions = []
if sessionData.counter[1] > 0 if sessionData.revisions.length > 1 or sessionData.revisions[0] > 0
sessionData.counter[2] += 1 sessionData.cycle += 1
sessionData.counter[1] = 0 sessionData.rpcCount = 0
sessionData.counter[0] = 0 sessionData.revisions = [0]
device.collapse(sessionData.deviceData, 1)
generateRpcRequest = (sessionData, declarations, callback) -> generateRpcRequest = (sessionData) ->
test = (cb) ->
toLoad = []
res = device.traverse(sessionData.deviceData, null, null, (path, declaration, base, current, descendantTimestamps, children) -> res = device.traverse(sessionData.deviceData, null, null, (path, declaration, base, current, descendantTimestamps, children) ->
if path[0] == 'Tags' if path[0] == 'Tags'
if path.length == 2 and path[1]? and declaration[7]? if path.length == 2 and path[1]? and declaration[7]?
if declaration[7][0] != current[7][0] if declaration[7][0] != current[7][0]
@@ -123,65 +122,42 @@ generateRpcRequest = (sessionData, declarations, callback) ->
r = {} r = {}
gpn = false gpn = false
descendantFlags = [] descendantRefresh = []
for i in [0...descendantTimestamps.length] by 1 for i in [0...descendantTimestamps.length] by 1 when descendantTimestamps[i][4]? or descendantTimestamps[i][2]?
if descendantTimestamps[i][2]? if descendantTimestamps[i][2] > descendantTimestamps[i][4]
if not descendantTimestamps[i][4]? if current[3] == 1
toLoad.push(descendantTimestamps[i][0]) descendantRefresh[i] = true
continue else
else if descendantTimestamps[i][2] > descendantTimestamps[i][4] r.object = true if current[2] < descendantTimestamps[i][2]
descendantFlags[i] = 1
descendantFlags[i] |= 2 if descendantTimestamps[i][0].length > path.length + 1
else if not descendantTimestamps[i][4]?
continue
for j in [0...i] by 1 when descendantTimestamps[j][4]? or descendantFlags[j] for j in [0...i] by 1 when descendantTimestamps[j][4]? or descendantRefresh[j]
overlap = common.pathOverlap(descendantTimestamps[j][0], descendantTimestamps[i][0], path.length) overlap = common.pathOverlap(descendantTimestamps[j][0], descendantTimestamps[i][0], path.length)
if overlap & 1 and descendantTimestamps[j][4]? if overlap & 1 and descendantTimestamps[j][4]?
if descendantFlags[i] & 2 and descendantTimestamps[j][0].length == path.length + 1 if descendantRefresh[i] and descendantTimestamps[j][4] > descendantTimestamps[i][2]
descendantFlags[i] ^= 2 descendantRefresh[i] = false
if descendantFlags[i] & 1 and descendantTimestamps[j][4] > descendantTimestamps[i][2]
descendantFlags[i] ^= 1
if overlap & 2 and descendantTimestamps[i][4]? if overlap & 2 and descendantTimestamps[i][4]?
if descendantFlags[j] & 2 and descendantTimestamps[i][0].length == path.length + 1 if descendantRefresh[j] and descendantTimestamps[i][4] > descendantTimestamps[j][2]
descendantFlags[j] ^= 2 descendantRefresh[j] = false
if descendantFlags[j] & 1 and descendantTimestamps[i][4] > descendantTimestamps[j][2]
descendantFlags[j] ^= 1
for d, j in descendantFlags for d in descendantRefresh when d
if d & 1
gpn = true gpn = true
if d & 2 break
toLoad.push(path.concat(descendantTimestamps[j][0][path.length] ? null))
if declaration.length if declaration.length
if declaration[0]? if declaration[0] > current[0]
if not current[0]?
toLoad.push(path)
else if declaration[0] > current[0]
r.exist = true r.exist = true
if declaration[2]? if declaration[2] > current[2]
if not current[2]?
toLoad.push(path)
else if declaration[2] > current[2]
r.writable = true r.writable = true
r.exist = false r.exist = false
if declaration[4]? if declaration[4] > current[4]
if not current[4]?
toLoad.push(path)
else if declaration[4] > current[4]
r.object = true r.object = true
r.exist = false r.exist = false
if declaration[6]? if declaration[6] > current[6]
if not current[6]?
toLoad.push(path) if not current[3]
else if declaration[6] > current[6]
if current[7]? or current[3] == 0 if current[7]? or current[3] == 0
r.gpv ?= [] r.gpv ?= []
r.gpv.push(path) r.gpv.push(path)
@@ -192,9 +168,7 @@ generateRpcRequest = (sessionData, declarations, callback) ->
r.object = true r.object = true
if declaration[7]? and current[7]? if declaration[7]? and current[7]?
if not current[5]? if current[4] == 0
toLoad.push(path)
else if current[4] == 0
r.writable = true r.writable = true
else if current[5] else if current[5]
if declaration[7][0] != current[7][0] or declaration[7][1] != current[7][1] if declaration[7][0] != current[7][0] or declaration[7][1] != current[7][1]
@@ -228,72 +202,142 @@ generateRpcRequest = (sessionData, declarations, callback) ->
return r return r
) )
if toLoad.length
loadParameters(sessionData, toLoad, (err) ->
return callback(err) if err
test(cb)
)
return
if res.gpn? if res.gpn?
rpcReq = { rpcReq = {
type: 'GetParameterNames' type: 'GetParameterNames'
parameterPath: res.gpn[0].join('.') parameterPath: res.gpn[0].join('.')
nextLevel: true nextLevel: true
} }
return callback(null, rpcReq) return rpcReq
if res.gpv? if res.gpv?
rpcReq = { rpcReq = {
type: 'GetParameterValues' type: 'GetParameterValues'
parameterNames: (p.join('.') for p in res.gpv.slice(0, config.get('TASK_PARAMETERS_BATCH_SIZE', sessionData.deviceId))) parameterNames: (p.join('.') for p in res.gpv.slice(0, config.get('TASK_PARAMETERS_BATCH_SIZE', sessionData.deviceId)))
} }
return callback(null, rpcReq) return rpcReq
if res.spv? if res.spv?
rpcReq = { rpcReq = {
type: 'SetParameterValues' 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))) 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, rpcReq) return rpcReq
return cb() return null
for declaration in declarations
pre = device.getPrerequisiteDeclarations(declaration)
for d in pre
device.declare(sessionData.deviceData, d[0], d.slice(1), sessionData.timestamp)
test(() -> loadDeclarations = (sessionData, virtualParameters, callback) ->
for declaration in declarations toLoad = []
params = device.getAll(sessionData.deviceData, declaration[0], null) device.traverse(sessionData.deviceData, null, null, (path, declaration, base, current, descendantTimestamps, children) ->
if declaration[0][0] == 'Tags' and declaration[0].length == 2 descendantLoad = []
if not declaration[0][1]? for i in [0...descendantTimestamps.length] by 1
continue if declaration[8][0] if descendantTimestamps[i][2]?
else if params.length == 0 if not descendantTimestamps[i][4]?
device.set(sessionData.deviceData, declaration[0], 1, [sessionData.timestamp, 1, null, null, null, null, sessionData.timestamp, [false, 'xsd:boolean']]) toLoad.push(descendantTimestamps[i][0])
device.declare(sessionData.deviceData, declaration[0], declaration.slice(1), sessionData.timestamp) continue
else if descendantTimestamps[i][2] > descendantTimestamps[i][4]
descendantLoad[i] = true if descendantTimestamps[i][0].length > path.length + 1
else if not descendantTimestamps[i][4]?
continue continue
for p in params for j in [0...i] by 1 when descendantTimestamps[j][4]? or descendantFlags[j]
device.declare(sessionData.deviceData, p[0], declaration.slice(1), sessionData.timestamp) overlap = common.pathOverlap(descendantTimestamps[j][0], descendantTimestamps[i][0], path.length)
test(() -> if overlap & 1 and descendantTimestamps[j][4]?
return callback() if descendantLoad[i] and descendantTimestamps[j][0].length == path.length + 1
) descendantLoad[i] = false
if overlap & 2 and descendantTimestamps[i][4]?
if descendantLoad[j] and descendantTimestamps[i][0].length == path.length + 1
descendantLoad[j] = false
for d, j in descendantLoad when d
toLoad.push(path.concat(descendantTimestamps[j][0][path.length] ? null))
for i in [0...declaration.length] by 2
if declaration[i]? and not current[i]?
toLoad.push(path)
break
) )
# Virtual parameters
for p in toLoad
if not p[0]? or p[0] == 'VirtualParameters'
if p.length == 1
device.set(sessionData.deviceData, ['VirtualParameters'], 1, [sessionData.timestamp, 1, sessionData.timestamp, 1, sessionData.timestamp, 0])
else if p.length == 2
if not p[1]?
for k, v of virtualParameters
device.set(sessionData.deviceData, ['VirtualParameters', k], 1, [sessionData.timestamp, 1, sessionData.timestamp, 0])
else if virtualParameters[p[1]]?
device.set(sessionData.deviceData, ['VirtualParameters', p[1]], 1, [sessionData.timestamp, 1, sessionData.timestamp, 0])
rpcRequest = (sessionData, allDeclarations, callback) -> return loadParameters(sessionData, toLoad, callback)
allDeclarations = allDeclarations?.slice() ? []
nextRevision = false
cache.getScripts((err, presetsHash, scripts) -> extractVirtualParameterDeclarations = (sessionData) ->
virtualParameterDeclarations = []
res = device.traverse(sessionData.deviceData, ['VirtualParameters', null], null, (path, declaration, base, current, descendantTimestamps, children) ->
if path.length == 2 and declaration?.length
d = [path]
for i in [0...declaration.length] by 2
if declaration[i]? and not (declaration[i] <= current[i])
d[i + 1] = declaration[i]
if declaration[i + 1]?
if i != 6 or not current[i + 1]?
d[i + 2] = declaration[i + 1]
else if declaration[i + 1][0] != current[i + 1][0] and
(declaration[i + 1][0] != current[i + 1][0] or not declaration[i + 1][1]?)
d[i + 2] = declaration[i + 1]
if d.length > 1
virtualParameterDeclarations.push(d)
)
device.clearDeclarations(sessionData.deviceData, ['VirtualParameters'])
return virtualParameterDeclarations
commitVirtualParameter = (sessionData, parameterDeclaration, revision, update) ->
v = []
if update.writable?
if update.writable[0] <= 0
v[4] = now + update.writable[0]
else
v[4] = Math.min(sessionData.timestamp, update.writable[0])
if parameterDeclaration[5]?
v[4] = Math.max(parameterDeclaration[5], v[4])
v[5] = Boolean(JSON.parse(update.writable[1]))
else if parameterDeclaration[5]? or parameterDeclaration[6]?
throw new Error('Virtual parameter must provide declared attributes')
if update.value?
if update.value[0] <= 0
v[6] = sessionData.timestamp + update.value[0]
else
v[6] = Math.min(now, update.value[0])
if parameterDeclaration[7]?
v[6] = Math.max(parameterDeclaration[7], v[6])
v[7] = device.sanitizeParameterValue(update.value[1])
else if parameterDeclaration[7]? or parameterDeclaration[8]?
throw new Error('Virtual parameter must provide declared attributes')
device.set(sessionData.deviceData, parameterDeclaration[0], revision, v)
rpcRequest = (sessionData, declarations, callback) ->
allDeclarations = declarations?.slice() ? []
cache.getProvisionsAndVirtualParameters((err, presetsHash, provisions, virtualParameters) ->
return callback(err) if err or presetsHash != sessionData.presetsHash return callback(err) if err or presetsHash != sessionData.presetsHash
done = true
for provision in sessionData.provisions for provision in sessionData.provisions
if not scripts[provision[0]]? if not provisions[provision[0]]?
switch provision[0] switch provision[0]
when 'refresh' when 'refresh'
path = common.parsePath(provision[1]) path = common.parsePath(provision[1])
@@ -306,63 +350,131 @@ rpcRequest = (sessionData, allDeclarations, callback) ->
allDeclarations.push([[['Tags', provision[1]], null, null, null, null, null, null, null, [provision[2], 'xsd:boolean']]]) allDeclarations.push([[['Tags', provision[1]], null, null, null, null, null, null, null, [provision[2], 'xsd:boolean']]])
continue continue
nextRevision ||= sandbox.run(scripts[provision[0]].compiled, provision.slice(1), sessionData.deviceData, sessionData.counter[1], allDeclarations) ret = sandbox.run(provisions[provision[0]].script, provision.slice(1), sessionData.deviceData, 0, sessionData.revisions[0] >> 1)
done &&= ret.done
allDeclarations = allDeclarations.concat(ret.declarations)
generateRpcRequest(sessionData, allDeclarations, (err, rpcReq) -> if done and (not allDeclarations?.length or sessionData.revisions[0] > 1)
sessionData.rpcRequest = rpcReq
return callback(err) if err
if not rpcReq?
if not nextRevision
return callback() return callback()
sessionData.counter[0] = 0
sessionData.counter[1] += 1
return rpcRequest(sessionData, null, callback)
callback(err, generateRpcId(sessionData), rpcReq) doVirtualParameters = (iter, virtualParameterDeclarations, cb) ->
if not virtualParameterDeclarations?
virtualParameterDeclarations = extractVirtualParameterDeclarations(sessionData)
if not virtualParameterDeclarations?.length
return cb(false)
sessionData.revisions[iter] ?= 0
decs = []
virtualParameterUpdates = []
for vpd, i in virtualParameterDeclarations
vpName = vpd[0][1]
continue if not virtualParameters[vpName]?
lastRevision = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1))
firstRevision = lastRevision - (sessionData.revisions[sessionData.revisions.length - 1] >> 1)
ret = sandbox.run(virtualParameters[vpName].script, [vpd.slice(1)], sessionData.deviceData, firstRevision, lastRevision)
decs = decs.concat(ret.declarations)
if ret.done
virtualParameterUpdates.push(ret.returnValue)
if virtualParameterUpdates.length == virtualParameterDeclarations.length
rev = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1))
for vpd, i in virtualParameterDeclarations
commitVirtualParameter(sessionData, vpd, rev + 1, virtualParameterUpdates[i])
if sessionData.revisions[iter] == 0
return cb(false)
sessionData.revisions.length = iter
rev = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1))
device.collapse(sessionData.deviceData, rev + 1)
return cb(true)
applyDeclarations(iter, decs, () ->
return doVirtualParameters(iter, virtualParameterDeclarations, cb)
)
applyDeclarations = (iter, decs, cb) ->
if sessionData.revisions[iter] % 2 == 0
for declaration in decs
for d in device.getPrerequisiteDeclarations(declaration)
device.declare(sessionData.deviceData, d[0], d.slice(1), sessionData.timestamp)
else
for declaration in decs
params = device.getAll(sessionData.deviceData, declaration[0], sessionData.revisions[iter] >> 1)
if declaration[0][0] == 'Tags' and declaration[0].length == 2
if not declaration[0][1]?
continue if declaration[8][0]
else if params.length == 0
device.set(sessionData.deviceData, declaration[0], 1, [sessionData.timestamp, 1, null, null, null, null, sessionData.timestamp, [false, 'xsd:boolean']])
device.declare(sessionData.deviceData, declaration[0], declaration.slice(1), sessionData.timestamp)
continue
for p in params
device.declare(sessionData.deviceData, p[0], declaration.slice(1), sessionData.timestamp)
nextRpc = (applied) ->
if not applied
if (rpcReq = generateRpcRequest(sessionData))?
sessionData.rpcRequest = rpcReq
return callback(null, generateRpcId(sessionData), rpcReq)
if ++ sessionData.revisions[iter] % 2 == 0
return cb()
else
return applyDeclarations(iter, decs, cb)
if sessionData.revisions.length > iter + 1
doVirtualParameters(iter + 1, null, nextRpc)
else
loadDeclarations(sessionData, virtualParameters, (err) ->
return callback(err) if err
doVirtualParameters(iter + 1, null, nextRpc)
)
applyDeclarations(0, allDeclarations, () ->
return rpcRequest(sessionData, declarations, callback)
) )
) )
return
rpcResponse = (sessionData, id, rpcRes, callback) -> rpcResponse = (sessionData, id, rpcRes, callback) ->
if id != generateRpcId(sessionData) if id != generateRpcId(sessionData)
return callback(new Error('Request ID not recognized')) return callback(new Error('Request ID not recognized'))
++ sessionData.counter[0] ++ sessionData.rpcCount
rpcReq = sessionData.rpcRequest rpcReq = sessionData.rpcRequest
sessionData.rpcRequest = null sessionData.rpcRequest = null
timestamp = sessionData.timestamp timestamp = sessionData.timestamp
revision = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1)) + 1
switch rpcRes.type switch rpcRes.type
when 'GetParameterValuesResponse' when 'GetParameterValuesResponse'
return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'GetParameterValues' return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'GetParameterValues'
for p in rpcRes.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)]) device.set(sessionData.deviceData, common.parsePath(p[0]), revision, [timestamp, 1, timestamp, 0, null, null, timestamp, p.slice(1)])
when 'GetParameterNamesResponse' when 'GetParameterNamesResponse'
return callback(new Error('Response type does not match request type')) if rpcReq.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(rpcReq.parameterPath).concat(null), 1, timestamp) device.set(sessionData.deviceData, common.parsePath(rpcReq.parameterPath).concat(null), revision, timestamp)
for p in rpcRes.parameterList for p in rpcRes.parameterList
if common.endsWith(p[0], '.') if common.endsWith(p[0], '.')
path = common.parsePath(p[0][0...-1]) path = common.parsePath(p[0][0...-1])
if not rpcReq.nextLevel if not rpcReq.nextLevel
device.set(sessionData.deviceData, path.conact(null), 1, timestamp) device.set(sessionData.deviceData, path.conact(null), revision, timestamp)
device.set(sessionData.deviceData, path, 1, [timestamp, 1, timestamp, 1, timestamp, if p[1] then 1 else 0]) device.set(sessionData.deviceData, path, revision, [timestamp, 1, timestamp, 1, timestamp, if p[1] then 1 else 0])
else else
device.set(sessionData.deviceData, common.parsePath(p[0]), 1, [timestamp, 1, timestamp, 0, timestamp, if p[1] then 1 else 0]) device.set(sessionData.deviceData, common.parsePath(p[0]), revision, [timestamp, 1, timestamp, 0, timestamp, if p[1] then 1 else 0])
when 'SetParameterValuesResponse' when 'SetParameterValuesResponse'
return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'SetParameterValues' return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'SetParameterValues'
for p in rpcReq.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)]) device.set(sessionData.deviceData, common.parsePath(p[0]), revision, [timestamp, 1, timestamp, 0, timestamp, 1, timestamp, p.slice(1)])
else else
return callback(new Error('Response type not recognized')) return callback(new Error('Response type not recognized'))