diff --git a/lib/common.coffee b/lib/common.coffee
index 358a9b4..5bc21f7 100644
--- a/lib/common.coffee
+++ b/lib/common.coffee
@@ -247,7 +247,7 @@ parsePath = (pattern, start, res) ->
while i < pattern.length and pattern[i] != ':' and pattern[i] != '.'
++ i
n = pattern.slice(j, i)
- path.push(if n == '*' then null else n)
+ path.push(n)
if i >= pattern.length or pattern[i] == ':'
break
@@ -263,6 +263,15 @@ parsePath = (pattern, start, res) ->
return i
+hammingWeight = (flags) ->
+ flags -= ((flags >> 1) & 0x55555555)
+ flags = (((flags >> 2) & 0x33333333) + (flags & 0x33333333))
+ flags = (((flags >> 4) + flags) & 0x0f0f0f0f)
+ flags += (flags >> 8)
+ flags += (flags >> 16)
+ return flags & 0x0000003f
+
+
exports.UNDEFINED_TYPE = UNDEFINED_TYPE
exports.NULL_TYPE = NULL_TYPE
exports.NUMBER_TYPE = NUMBER_TYPE
@@ -274,3 +283,4 @@ exports.REGEXP_TYPE = REGEXP_TYPE
exports.typeOf = typeOf
exports.pathOverlap = pathOverlap
exports.parsePath = parsePath
+exports.hammingWeight = hammingWeight
diff --git a/lib/cwmp.coffee b/lib/cwmp.coffee
index 3c99f93..13aa1cf 100644
--- a/lib/cwmp.coffee
+++ b/lib/cwmp.coffee
@@ -118,7 +118,7 @@ applyPresets = (currentRequest) ->
declarations = []
for k, v of parameters
- declarations.push([v, 1, null, null, null, null, null, 1])
+ declarations.push([v, {exist: 1, value: 1}])
session.rpcRequest(currentRequest.sessionData, declarations, (err, id, rpcRequest) ->
throw err if err
diff --git a/lib/db.coffee b/lib/db.coffee
index 9d0f887..1d72cec 100644
--- a/lib/db.coffee
+++ b/lib/db.coffee
@@ -248,24 +248,28 @@ optimizeProjection = (obj) ->
fetchDevice = (id, timestamp, patterns, callback) ->
res = []
+ loaded = []
# Build projection
projection = {_id: 1}
for pattern in patterns
+ loaded.push([pattern, 1])
if pattern.length == 0
projection['_timestamp'] = 1
else if not pattern[0]?
projection[''] = 1
else if pattern[0] == 'Events'
if pattern.length == 1
- res.push([['Events'], timestamp, 1, timestamp, 1, timestamp, 0])
+ res.push([['Events'],
+ {exist: timestamp, object: timestamp, writable: timestamp},
+ {exist: 1, object: 1, writable: 0}])
else if pattern.length == 2
if not pattern[1]?
projection['_registered'] = 1
projection['_lastInform'] = 1
projection['_lastBootstrap'] = 1
projection['_lastBoot'] = 1
- res.push([['Events', null], timestamp])
+ res.push([['Events', '*'], {exist: timestamp}])
else if pattern[1] == 'Registered'
projection['_registered'] = 1
else if pattern[1] == 'Inform'
@@ -276,7 +280,10 @@ fetchDevice = (id, timestamp, patterns, callback) ->
projection['_lastBoot'] = 1
else if pattern[0] == 'DeviceID'
if pattern.length == 1
- res.push([['DeviceID'], timestamp, 1, timestamp, 1, timestamp, 0])
+ res.push([['DeviceID'],
+ {exist: timestamp, object: timestamp, writable: timestamp},
+ {exist: 1, object: 1, writable: 0}])
+
else if pattern.length == 2
if not pattern[1]?
projection['_id'] = 1
@@ -284,7 +291,8 @@ fetchDevice = (id, timestamp, patterns, callback) ->
projection['_deviceId._ProductClass'] = 1
projection['_deviceId._SerialNumber'] = 1
projection['_deviceId._OUI'] = 1
- res.push([['DeviceID', null], timestamp])
+ res.push([['DeviceID', '*'], {exist: timestamp}])
+
else if pattern[1] == 'ID'
projection['_id'] = 1
else if pattern[1] == 'Manufacturer'
@@ -297,15 +305,17 @@ fetchDevice = (id, timestamp, patterns, callback) ->
projection['_deviceId._SerialNumber'] = 1
else if pattern[0] == 'Tags'
if pattern.length == 1
- res.push([['Tags'], timestamp, 1, timestamp, 1, timestamp, 0])
+ res.push([['Tags'],
+ {exist: timestamp, object: timestamp, writable: timestamp},
+ {exist: 1, object: 1, writable: 0}])
+
else if pattern.length == 2
- res.push([['Tags', null], timestamp])
+ res.push([['Tags', '*'], {exist: timestamp}])
projection['_tags'] = 1
else
- for i in [0...pattern.length] by 1
- break if not pattern[i]
+ wildcardIndex = pattern.indexOf('*')
- if i == pattern.length
+ if wildcardIndex == -1
s = pattern.join('.')
projection["#{s}._value"] = 1
projection["#{s}._timestamp"] = 1
@@ -320,8 +330,11 @@ fetchDevice = (id, timestamp, patterns, callback) ->
else
projection["#{pattern.slice(0, -1).join('.')}._timestamp"] = 1
else
- s = pattern.slice(0, i).join('.')
+ p = pattern.slice(0, wildcardIndex)
+ loaded.push([p, 99])
+ s = p.join('.')
projection[s] = 1
+ projection[pattern.slice(0, wildcardIndex - 1).concat('_timestamp').join('.')] = 1
if projection['']
projection = {}
@@ -339,25 +352,32 @@ fetchDevice = (id, timestamp, patterns, callback) ->
if obj['_timestamp']?
obj['_timestamp'] = +obj['_timestamp']
- v = [timestamp, 1]
+ t = {}
+ v = {}
if obj['_value']?
- v[6] = obj['_timestamp'] ? timestamp
- v[7] = [obj['_value'], obj['_type']]
+ t.value = obj['_timestamp'] ? timestamp
+ v.value = [obj['_value'], obj['_type']]
obj['_object'] = false
+ t.exist = Math.max(t.exist || 0, t.value)
+ v.exist = 1
if obj['_writable']?
- v[4] = timestamp
- v[5] = if obj['_writable'] then 1 else 0
+ t.writable = timestamp
+ v.writable = if obj['_writable'] then 1 else 0
+ t.exist = Math.max(t.exist || 0, t.writable)
+ v.exist = 1
if obj['_object']?
- v[2] = timestamp
- v[3] = if obj['_object'] then 1 else 0
+ t.object = obj['_timestamp'] ? timestamp
+ v.object = if obj['_object'] then 1 else 0
+ t.exist = Math.max(t.exist || 0, t.object)
+ v.exist = 1
- if v.length
- v[0] ?= timestamp ? 0
- v[1] ?= 1
- res.push([path].concat(v))
+ if not obj['_object']
+ loaded.push([path, 99])
+
+ res.push([path, t, v])
for k, v of obj
if k[0] != '_'
@@ -365,112 +385,146 @@ fetchDevice = (id, timestamp, patterns, callback) ->
storeParams(v, path.concat(k), obj['_timestamp'] ? 0, descendantsFetched)
if obj['_object'] and descendantsFetched
- res.push([path.concat(null), obj['_timestamp'] ? 0])
+ res.push([path.concat('*'), {exist: obj['_timestamp'] ? 0}])
for k, v of device
if k == '_timestamp'
- res.push([[null], v]) if not Object.keys(projection).length
+ res.push([['*'], {exist: v}]) if not Object.keys(projection).length
else if k == '_lastInform'
- res.push([['Events', 'Inform'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']])
+ res.push([['Events', 'Inform'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [+v, 'xsd:dateTime']}])
else if k == '_lastBoot'
- res.push([['Events', '1_BOOT'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']])
+ res.push([['Events', '1_BOOT'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [+v, 'xsd:dateTime']}])
else if k == '_lastBootstrap'
- res.push([['Events', '0_BOOTSTRAP'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']])
+ res.push([['Events', '0_BOOTSTRAP'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [+v, 'xsd:dateTime']}])
else if k == '_registered'
- res.push([['Events', 'Registered'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']])
+ res.push([['Events', 'Registered'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [+v, 'xsd:dateTime']}])
else if k == '_id'
- res.push([['DeviceID', 'ID'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:string']])
+ res.push([['DeviceID', 'ID'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [v, 'xsd:string']}])
else if k == '_tags'
for t in v
- res.push([['Tags', t], timestamp, 1, timestamp, 0, timestamp, 1, timestamp, [true, 'xsd:boolean']])
+ res.push([['Tags', t],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 1, value: [true, 'xsd:boolean']}])
else if k == '_deviceId'
for kk, vv of v
if kk == '_Manufacturer'
- res.push([['DeviceID', 'Manufacturer'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']])
+ res.push([['DeviceID', 'Manufacturer'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [vv, 'xsd:boolean']}])
else if kk == '_OUI'
- res.push([['DeviceID', 'OUI'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']])
+ res.push([['DeviceID', 'OUI'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [vv, 'xsd:boolean']}])
if kk == '_ProductClass'
- res.push([['DeviceID', 'ProductClass'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']])
+ res.push([['DeviceID', 'ProductClass'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [vv, 'xsd:boolean']}])
if kk == '_SerialNumber'
- res.push([['DeviceID', 'SerialNumber'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']])
+ res.push([['DeviceID', 'SerialNumber'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [vv, 'xsd:boolean']}])
else if common.typeOf(v) is common.OBJECT_TYPE
- storeParams(v, [k], device['_timestamp'] ? 0, projection == null)
+ storeParams(v, [k], +(device['_timestamp'] ? 0), projection == null)
- return callback(null, res)
+ return callback(null, res, loaded)
)
-saveDevice = (deviceId, diff, isNew, callback) ->
+saveDevice = (deviceId, deviceData, isNew, callback) ->
update = {'$set' : {}, '$unset' : {}, '$addToSet' : {}, '$pull' : {}}
- for p in diff
- base = p[2]
- current = p[3]
- if p[0][0] == 'Events'
- if p[0].length == 2
- if p[0][1] == 'Inform'
- update['$set']['_lastInform'] = new Date(current[7][0])
- else if p[0][1] == '0_BOOTSTRAP'
- update['$set']['_lastBootstrap'] = new Date(current[7][0])
- else if p[0][1] == '1_BOOT'
- update['$set']['_lastBoot'] = new Date(current[7][0])
- else if p[0][1] == 'Registered'
- update['$set']['_registered'] = new Date(current[7][0])
- else if p[0][0] == 'Tags'
- if p[0].length == 2 and p[0][1]?
- if current[7][0] and not base[7]?[0]
- update['$addToSet']['_tags'] ?= {'$each' : []}
- update['$addToSet']['_tags']['$each'].push(p[0][1])
- else if not current[7]?[0] and base[7]?[0]
- update['$pull']['_tags'] ?= {'$in' : []}
- update['$pull']['_tags']['$in'].push(p[0][1])
- else if p[0][0] == 'DeviceID'
- if p[0].length == 2 and current[7]?
- if p[0][1] == 'ID'
- update['$set']['_id'] = current[7][0]
- else if p[0][1] == 'Manufacturer'
- update['$set']['_deviceId._Manufacturer'] = current[7][0]
- else if p[0][1] == 'OUI'
- update['$set']['_deviceId._OUI'] = current[7][0]
- else if p[0][1] == 'ProductClass'
- update['$set']['_deviceId._ProductClass'] = current[7][0]
- else if p[0][1] == 'SerialNumber'
- update['$set']['_deviceId._SerialNumber'] = current[7][0]
- else if p[1] == p[0].length
- param = p[0].join('.')
- if current[2]?
- if current[3] != base[3]
- if current[3]
- update['$set']["#{param}._object"] = true
- else if base[3]?
- update['$unset']["#{param}._object"] = 1
- else if base[2]?
- update['$unset']["#{param}._object"] = 1
- if current[4]?
- if current[5] != base[5]
- update['$set']["#{param}._writable"] = !!current[5]
- else if base[4]?
- update['$unset']["#{param}._writable"] = 1
+ # Delete parameters that no longer exist
+ iter = deviceData.values.exist.diff()
+ while not (diff = iter.next()).done
+ if diff.value[1] and not diff.value[2]
+ update['$unset'][diff.value[0].join('.')] = 1
- if current[6]? and current[7]?
- if current[6] != base[6]
- update['$set']["#{param}._timestamp"] = new Date(current[6])
+ # Set object's children timestamps
+ iter = deviceData.timestamps.exist.diff()
+ while not (diff = iter.next()).done
+ continue if diff.value[0].wildcards != (1 << (diff.value[0].length - 1))
+ continue if diff.value[0][0] in ['Events', 'DeviceID', 'Tags']
- if current[7][0] != base[7]?[0] or current[7][1] != base[7]?[1]
- if current[7][1] == 'xsd:dateTime'
- update['$set']["#{param}._value"] = new Date(current[7][0])
- else
- update['$set']["#{param}._value"] = current[7][0]
- update['$set']["#{param}._type"] = current[7][1]
- else if base[7]?
- update['$unset']["#{param}._value"] = 1
- update['$unset']["#{param}._type"] = 1
- else if p[1] == p[0].length - 1 and not p[0][p[1]]?
- param = p[0].slice(0, p[1]).concat('_timestamp').join('.')
- if current?
- update['$set'][param] = new Date(current)
- else if base?
- update['$unset'][param] = 1
+ if not diff.value[2]? and diff.value[1]?
+ update['$unset'][diff.value[0].slice(0, -1).concat('_timestamp').join('.')] = 1
+ else if diff.value[2] != diff.value[1]
+ update['$set'][diff.value[0].slice(0, -1).concat('_timestamp').join('.')] = new Date(diff.value[2])
+
+ # Set writable
+ iter = deviceData.values.writable.diff()
+ while not (diff = iter.next()).done
+ continue if diff.value[0][0] in ['Events', 'DeviceID', 'Tags']
+ if not diff.value[2]? and diff.value[1]?
+ update['$unset'][diff.value[0].concat('_writable').join('.')] = 1
+ else if diff.value[2] != diff.value[1]
+ update['$set'][diff.value[0].concat('_writable').join('.')] = Boolean(diff.value[2])
+
+ # Set object
+ iter = deviceData.values.object.diff()
+ while not (diff = iter.next()).done
+ continue if diff.value[0][0] in ['Events', 'DeviceID', 'Tags']
+ if not diff.value[2] and diff.value[1]
+ update['$unset'][diff.value[0].concat('_object').join('.')] = 1
+ else if diff.value[2] and diff.value[2] != diff.value[1]
+ update['$set'][diff.value[0].concat('_object').join('.')] = true
+
+ # Set value
+ iter = deviceData.values.value.diff()
+ while not (diff = iter.next()).done
+ if diff.value[0][0] == 'Events'
+ continue if diff.value[1]?[0] == diff.value[2][0]
+ if diff.value[0][1] == 'Inform'
+ update['$set']['_lastInform'] = new Date(diff.value[2][0])
+ else if diff.value[0][1] == '1_BOOT'
+ update['$set']['_lastBoot'] = new Date(diff.value[2][0])
+ else if diff.value[0][1] == '0_BOOTSTRAP'
+ update['$set']['_lastBootstrap'] = new Date(diff.value[2][0])
+ else if diff.value[0][1] == 'Registered'
+ update['$set']['_registered'] = new Date(diff.value[2][0])
+ else if diff.value[0][0] == 'Tags'
+ continue if diff.value[1]?[0] == diff.value[2]?[0]
+ if diff.value[2][0]
+ update['$addToSet']['_tags'] ?= {'$each' : []}
+ update['$addToSet']['_tags']['$each'].push(p[0][1])
+ else
+ update['$pull']['_tags'] ?= {'$in' : []}
+ update['$pull']['_tags']['$in'].push(p[0][1])
+ else if diff.value[0][0] == 'DeviceID'
+ continue if diff.value[1]?[0] == diff.value[2][0]
+ if diff.value[0][1] == 'ID'
+ update['$set']['_id'] = diff.value[2][0]
+ else if diff.value[0][1] == 'Manufacturer'
+ update['$set']['_deviceId._Manufacturer'] = diff.value[2][0]
+ else if diff.value[0][1] == 'OUI'
+ update['$set']['_deviceId._OUI'] = diff.value[2][0]
+ else if diff.value[0][1] == 'ProductClass'
+ update['$set']['_deviceId._ProductClass'] = diff.value[2][0]
+ else if diff.value[0][1] == 'SerialNumber'
+ update['$set']['_deviceId._SerialNumber'] = diff.value[2][0]
+ else
+ tdiff = deviceData.timestamps.value.getDiff(diff.value[0])
+ if diff.value[2]?
+ if tdiff[2] != tdiff[1]
+ update['$set'][diff.value[0].concat('_timestamp').join('.')] = new Date(tdiff[2])
+ delete update['$unset'][diff.value[0].concat('_timestamp').join('.')]
+ if diff.value[2][0] != diff.value[1]?[0]
+ update['$set'][diff.value[0].concat('_value').join('.')] = diff.value[2][0]
+ if diff.value[2][1] != diff.value[1]?[1]
+ update['$set'][diff.value[0].concat('_type').join('.')] = diff.value[2][1]
+
+ else if diff.value[1]?
+ update['$unset'][diff.value[0].concat('_value').join('.')] = 1
+ update['$unset'][diff.value[0].concat('_value').join('.')] = 1
# Remove empty keys
for k of update
diff --git a/lib/device.coffee b/lib/device.coffee
deleted file mode 100644
index b832321..0000000
--- a/lib/device.coffee
+++ /dev/null
@@ -1,412 +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'
-
-
-sanitizeDeclaration = (declaration, wildcard, now) ->
- if typeof declaration is 'number'
- declaration = [declaration]
-
- if declaration[7]?
- declaration[7] = sanitizeParameterValue(declaration[7])
-
- for i in [0...declaration.length] by 2 when declaration[i]?
- if declaration[i] <= 0
- declaration[i] = now + declaration[i]
- else
- declaration[i] = Math.min(now, declaration[i])
-
- return declaration
-
-
-sanitizeProperties = (properties, wildcard) ->
- if not wildcard
- if typeof properties is 'number'
- properties = [properties]
- else if properties[1]
- properties[1] = 1
-
- for i in [2...properties.length] by 2 \
- when properties[i]? and properties[i + 1]?
- properties[0] = Math.max(properties[0] ? 0, properties[i])
-
- if properties[6]? and properties[7]? and not (properties[6] < properties[2])
- properties[2] = properties[6]
- properties[3] = 0
- else if properties[2]? and properties[3] and not (properties[2] < properties[6])
- properties[6] = properties[2]
- properties[7] = null
-
- else if typeof properties isnt 'number'
- properties = properties[0]
-
- return properties
-
-
-sanitizeParameterValue = (parameterValue) ->
- if parameterValue[0]?
- switch parameterValue[1]
- when 'xsd:boolean'
- if typeof parameterValue[0] isnt 'boolean'
- parameterValue = parameterValue.slice()
- parameterValue[0] = !!JSON.parse(parameterValue[0])
- when 'xsd:string'
- if typeof parameterValue[0] isnt 'string'
- parameterValue = parameterValue.slice()
- parameterValue[0] = '' + parameterValue[0]
- when 'xsd:int', 'xsd:unsignedInt'
- if typeof parameterValue[0] isnt 'number'
- parameterValue = parameterValue.slice()
- parameterValue[0] = +parameterValue[0]
- when 'xsd:dateTime'
- if typeof parameterValue[0] isnt 'number'
- parameterValue = parameterValue.slice()
- if parameterValue[0].getTime?
- parameterValue[0] = parameterValue[0].getTime()
- else if isNaN(parameterValue[0])
- parameterValue[0] = Date.parse(parameterValue[0])
- else
- parameterValue[0] = +parameterValue[0]
- else
- if parameterValue[1]?
- throw new Error("Parameter value type \"#{parameterValue[1]}\" not recognized.")
-
- # Ensure consistency after serialization
- parameterValue[0] = JSON.parse(JSON.stringify(parameterValue[0]))
-
- return parameterValue
-
-
-init = () ->
- return [[[null, [0, 1]]]]
-
-
-declare = (deviceData, path, declaration, now) ->
- wildcard = false
- for i in [0...path.length] by 1 when not path[i]?
- wildcard = true
- break
-
- declaration = sanitizeDeclaration(declaration, wildcard, now)
-
- ref = deviceData
- depth = 0
- for i in [0...path.length] by 1
- if not (p = path[i])?
- ++ depth
- continue
-
- ref[1 + depth] ?= {}
- ref[1 + depth][p] ?= []
- ref = ref[1 + depth][p]
- depth = 0
-
- ref[0] ?= []
- ref[0][depth] ?= []
-
- if wildcard
- ref[0][depth][0] = Math.max(ref[0][depth][0] ? 0, declaration[0])
- else if not ref[0][depth][0]?
- ref[0][depth][0] = declaration
- else
- for i in [0...declaration.length] by 2
- if declaration[i]?
- ref[0][depth][0][i] = Math.max(ref[0][depth][0][i] ? 0, declaration[i])
-
- if declaration[i + 1]?
- ref[0][depth][0][i + 1] = declaration[i + 1]
-
- return
-
-
-set = (deviceData, path, revision, properties) ->
- ++ revision # revisions start at layer 1
- wildcard = false
- for i in [0...path.length] by 1 when not path[i]?
- wildcard = true
- break
-
- properties = sanitizeProperties(properties, wildcard)
-
- ref = deviceData
- depth = 0
- for i in [0...path.length] by 1
- if not (p = path[i])?
- ++ depth
- continue
-
- if not wildcard and properties[1]
- # Update existing timestamp for ancestors
- ref[0] ?= []
- ref[0][0] ?= []
- ref[0][0][revision] ?= []
- if not (ref[0][0][revision][0] >= properties[0])
- ref[0][0][revision][0] = properties[0]
- ref[0][0][revision][1] = 1
-
- if not (ref[0][0][revision][2] >= properties[0])
- ref[0][0][revision][2] = properties[0]
- ref[0][0][revision][3] = 1
-
- ref[1 + depth] ?= {}
- ref[1 + depth][p] ?= []
- ref = ref[1 + depth][p]
- depth = 0
-
- ref[0] ?= []
- ref[0][depth] ?= []
-
- if wildcard
- ref[0][depth][revision] = Math.max(ref[0][depth][revision] ? 0, properties)
- else if not ref[0][0][revision]?
- ref[0][0][revision] = properties
- else
- for i in [0...properties.length] by 2 \
- when properties[i]?# and properties[i + 1]?
- if not (properties[i] < ref[0][0][revision][i])
- ref[0][0][revision][i] = properties[i]
- ref[0][0][revision][i + 1] = properties[i + 1]
-
- if properties[7]?
- ref[0][0][revision][7] = sanitizeParameterValue(ref[0][0][revision][7])
-
- return
-
-
-traverse = (deviceData, pattern, revision, callback) ->
- if not pattern?
- pattern = []
- pattern.length = 99
-
- if not revision?
- revision = 99
-
- ++ revision # First layer is for declarations
-
- collectTimestamps = (ref, path, root, timestamps, wildcard) ->
- # Skip properties of parameter we're currently at
- skip = if root == path.length then 1 else 0
-
- if ref[0]?
- for i in [skip...ref[0].length] by 1
- if ref[0][i]? and i <= pattern.length - path.length
- p = path.slice()
- p.length += i
- if wildcard or i > 0
- revisions = ref[0][i]
- else
- # Extract only timestamp from properties array
- revisions = (j?[0] for j in ref[0][i])
-
- current = -1
- for j in [Math.min(revision, revisions.length - 1)..1] by -1 when revisions[j]?
- current = revisions[j]; break
-
- timestamps.push([p, root, revisions, current, current])
-
- for i in [skip...ref.length - 1] by 1
- if ref[1 + i]? and i < pattern.length - path.length
- p = path.slice()
- p.length += i
- for k, v of ref[1+i]
- if not pattern[path.length + i]? or pattern[path.length + i] == k
- collectTimestamps(v, p.concat(k), root, timestamps, wildcard or i > 0)
-
- return
-
- processOverlaps = (timestamps, root, skip) ->
- skip = 1 if not skip
- for i in [skip...timestamps.length] by 1
- for j in [0...i] by 1
- if timestamps[i][0].length > timestamps[j][0].length
- t = timestamps[j]
- tt = timestamps[i]
- else
- t = timestamps[i]
- tt = timestamps[j]
-
- overlap = common.pathOverlap(t[0], tt[0], root)
-
- if overlap & 1
- if t[0].length == tt[0].length
- tt[3 + (root - tt[1]) * 2] = Math.max(tt[3 + (root - tt[1]) * 2], t[3 + (root - t[1]) * 2])
- else
- tt[4 + (root - tt[1]) * 2] = Math.max(tt[4 + (root - tt[1]) * 2], t[4 + (root - t[1]) * 2])
-
- if overlap & 2
- t[3 + (root - t[1]) * 2] = Math.max(t[3 + (root - t[1]) * 2], tt[3 + (root - t[1]) * 2])
-
- return
-
- recursive = (ref, path, timestamps, timestampThreshold, revisionThreshold) ->
- declaration = ref[0]?[0]?[0]?.slice() or []
- base = ref[0]?[0]?[1]?.slice() or []
- current = []
-
- # Find timestamp and revision in which this parameter was most recently
- # created in order to ignore older properties
- for t in timestamps
- for j in [Math.min(revision, t[2].length - 1)..1] by -1 when t[2][j]?
- if not (t[2][j] <= ref[0]?[0]?[j]?[0]) and t[2][j] >= timestampThreshold
- timestampThreshold = Math.max(timestampThreshold, t[2][j])
- revisionThreshold = Math.max(revisionThreshold, j)
- current[0] = timestampThreshold
- break
-
- # Collect current properties
- if ref[0]?[0]?
- for i in [revisionThreshold...Math.min(revision + 1, ref[0][0].length)] by 1 when (rev = ref[0][0][i])?
- for j in [0...rev.length] by 2 when rev[j]?
- if rev[j] >= timestampThreshold
- current[j] = rev[j]
- current[j + 1] = rev[j + 1]
- else
- current[j] ?= 0
-
- # Return if parameter no longer exists
- if not (declaration[1] or base[1] or current[1])
- l = timestamps.length
- collectTimestamps(ref, path, path.length - 1, timestamps)
- processOverlaps(timestamps, path.length - 1, l)
- return
-
- tentativeTimestamp = -1
- if current[2]? and not current[3]
- tentativeTimestamp = current[2]
-
- filteredTimestamps = timestamps.filter((t) ->
- if t[0][path.length - 1]?
- if t[0][path.length - 1]? and t[0][path.length - 1] == path[path.length - 1]
- t[2 + (path.length - t[1]) * 2] = Math.max(t[2 + (path.length - t[1]) * 2], current[0])
- else
- return false
-
- if t[0].length > path.length
- t[3 + (path.length - t[1]) * 2] = t[1 + (path.length - t[1]) * 2]
- t[4 + (path.length - t[1]) * 2] = tentativeTimestamp
- return true
-
- if not declaration[0] or t[2][0] > declaration[0]
- declaration[0] = t[2][0]
-
- return false
- )
-
- collectTimestamps(ref, path, path.length, filteredTimestamps)
-
- processOverlaps(filteredTimestamps, path.length, 0)
-
- if path.length < pattern.length
- children = {}
- for k, v of ref[1]
- if not pattern[path.length]? or k == pattern[path.length]
- r = recursive(v, path.concat(k), filteredTimestamps, timestampThreshold, revisionThreshold)
- children[k] = r if r?
-
- descendantTimestamps = filteredTimestamps.map((t) ->
- res = [t[0], t[1]]
- res[2] = t[2][0] if t[2][0]?
- res[3] = t[2][1] if t[2][1]?
-
- cur = Math.max(t[3 + (path.length - t[1]) * 2], t[4 + (path.length - t[1]) * 2])
-
- if path.length > t[1]
- t[2 + (path.length - t[1]) * 2] = Math.min(t[2 + (path.length - t[1]) * 2], cur)
-
- if cur >= 0
- res[4] = cur
-
- return res
- ).filter((t) -> t.length > 1)
-
- # Match value type of current if no type is specified
- if declaration[7]? and not declaration[7][1]?
- declaration[7] = sanitizeParameterValue([declaration[7][0], current[7]?[1]])
-
- return callback(path, declaration, base, current, descendantTimestamps, children)
-
- 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) ->
- dec = [declarations[0], 1]
- for i in [1...declarations.length] by 2
- if declarations[i]?
- dec[i] = declarations[i]
- else if declarations[i + 1]?
- dec[i] = 1
- return [dec]
-
-
-getAll = (deviceData, pattern, revision) ->
- allParameters = []
- traverse(deviceData, pattern, revision, (path, declaration, base, current, descendantTimestamps, children) ->
- if path.length == pattern.length and current[1]
- allParameters.push([path].concat(current))
- )
- return allParameters
-
-
-exports.getPrerequisiteDeclarations = getPrerequisiteDeclarations
-exports.init = init
-exports.getAll = getAll
-exports.declare = declare
-exports.set = set
-exports.traverse = traverse
-exports.collapse = collapse
-exports.clearDeclarations = clearDeclarations
-exports.sanitizeParameterValue = sanitizeParameterValue
diff --git a/lib/device.js b/lib/device.js
new file mode 100644
index 0000000..7a9508e
--- /dev/null
+++ b/lib/device.js
@@ -0,0 +1,277 @@
+/**
+ * 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 .
+ */
+"use strict";
+
+
+function sanitizeParameterValue(parameterValue) {
+ if (parameterValue[0] != null) {
+ switch (parameterValue[1]) {
+ case 'xsd:boolean':
+ if (typeof parameterValue[0] !== 'boolean') {
+ parameterValue = parameterValue.slice();
+ parameterValue[0] = !!JSON.parse(parameterValue[0]);
+ }
+ break;
+ case 'xsd:string':
+ if (typeof parameterValue[0] !== 'string') {
+ parameterValue = parameterValue.slice();
+ parameterValue[0] = '' + parameterValue[0];
+ }
+ break;
+ case 'xsd:int':
+ case 'xsd:unsignedInt':
+ if (typeof parameterValue[0] !== 'number') {
+ parameterValue = parameterValue.slice();
+ parameterValue[0] = +parameterValue[0];
+ }
+ break;
+ case 'xsd:dateTime':
+ if (typeof parameterValue[0] !== 'number') {
+ parameterValue = parameterValue.slice();
+ if (parameterValue[0].getTime() != null)
+ parameterValue[0] = parameterValue[0].getTime();
+ else if (isNaN(parameterValue[0]))
+ parameterValue[0] = Date.parse(parameterValue[0]);
+ else
+ parameterValue[0] = +parameterValue[0];
+ }
+ break;
+ default:
+ if (parameterValue[1] != null)
+ throw new Error(`Parameter value type "${parameterValue[1]}" not recognized.`);
+
+ parameterValue[0] = JSON.parse(JSON.stringify(parameterValue[0]));
+ }
+ }
+ return parameterValue;
+}
+
+
+function getAliasDeclarations(path) {
+ // TODO Return declarations necessary for unpacking aliases
+ return [];
+}
+
+
+function inferTimestamps(deviceData, path) {
+ var res = {};
+
+ for (let sup of deviceData.paths.superset(path)) {
+ for (let attrName in deviceData.timestamps) {
+ let t = deviceData.timestamps[attrName].get(sup);
+ if (t && !(t < res[sup]))
+ res[attrName] = t;
+ }
+ }
+
+ for (let i = path.length - 1; i > 0; -- i) {
+ if (path.wildcards & ((2 << i) - 1) === 0)
+ break;
+
+ let param = null;
+ let ancestor = path.slice(0, i);
+ let ancestorTimestamp = 0;
+ for (let sup of deviceData.paths.superset(ancestor)) {
+ if (!sup.wildcards)
+ param = sup;
+
+ let t = deviceData.timestamps.exist.get(sup);
+ if (t > ancestorTimestamp)
+ ancestorTimestamp = t;
+ }
+
+ if (param != null && deviceData.values.exist.get(param)) {
+ if (deviceData.values.object.get(param) == 0) {
+ let t = deviceData.timestamps.object.get(param);
+ for (let attrName in deviceData.timestamps)
+ if (!(t <= res[attrName]))
+ res[attrName] = t;
+ }
+ }
+ else if (ancestorTimestamp) {
+ let newt = {};
+ for (let attrName in deviceData.timestamps)
+ newt[attrName] = ancestorTimestamp;
+
+ for (let sub of deviceData.paths.subset(ancestor)) {
+ if (sub.wildcards)
+ continue;
+ let subt = inferTimestamps(deviceData, sub.concat(path.slice(sub.length, path.length)));
+ for (let attrName in newt) {
+ if (!(attrName in subt))
+ delete newt[attrName];
+ else if (subt[attrName] < newt[attrName])
+ newt[attrName] = subt[attrName];
+ }
+ }
+ for (let attrName in newt)
+ if (!(newt[attrName] <= res[attrName]))
+ res[attrName] = newt[attrName];
+ }
+ }
+
+ return res;
+}
+
+
+function unpack(deviceData, path, revision) {
+ // TODO implement alias unpacking
+ return [path];
+}
+
+
+function invalidateSuperset(deviceData, path, attributes) {
+ throw new Error('Not implemented');
+}
+
+
+function normalizeAttributes(timestamps, values) {
+ // TODO Consider moving implemention within set function
+ if (!values)
+ return;
+
+ if ('value' in values && timestamps.value >= (timestamps.object || 0)) {
+ values.object = 0;
+ timestamps.object = timestamps.value;
+ }
+
+ if (values.object && timestamps.object > (timestamps.value || 0)) {
+ delete values.value;
+ timestamps.value = timestamps.object;
+ }
+
+ if ('value' in values && timestamps.value >= (timestamps.exist || 0)) {
+ values.exist = 1;
+ timestamps.exist = timestamps.value;
+ }
+
+ if ('object' in values && timestamps.object >= (timestamps.exist || 0)) {
+ values.exist = 1;
+ timestamps.exist = timestamps.object;
+ }
+
+ if ('writable' in values && timestamps.writable >= (timestamps.exist || 0)) {
+ values.exist = 1;
+ timestamps.exist = timestamps.writable;
+ }
+}
+
+
+function set(deviceData, path, timestamps, values) {
+ // TODO Optimize
+ // TODO Allow overwriting attribute value with same timestamp
+ path = deviceData.paths.add(path);
+
+ if (!path.wildcards) {
+ normalizeAttributes(timestamps, values);
+ if (values.exist && path.length > 1)
+ set(deviceData, path.slice(0, path.length - 1),
+ {exist: timestamps.exist, object: timestamps.exist},
+ {exist: 1, object: 1});
+ }
+
+ var current = inferTimestamps(deviceData, path);
+
+ for (let c in current)
+ if (current[c] > timestamps[c] || (current[c] == timestamps[c] && path.wildcards > 0))//!deviceData.values[c].has(path)))
+ delete timestamps[c];
+
+ if (timestamps.exist != null) {
+ deviceData.timestamps.exist.set(path, timestamps.exist);
+ if (values != null && values.exist != null)
+ deviceData.values.exist.set(path, values.exist);
+ }
+
+ current = inferTimestamps(deviceData, path);
+
+ for (let c in current)
+ if (current[c] > timestamps[c] || (current[c] == timestamps[c] && path.wildcards > 0))//!deviceData.values[c].has(path)))
+ delete timestamps[c];
+
+ let toInvalidate = new Map();
+
+ for (let sub of deviceData.paths.subset(path, 0)) {
+ if (path.wildcards == sub.wildcards)
+ continue;
+
+ for (let attrName in timestamps) {
+ if (deviceData.timestamps[attrName].get(sub) <= timestamps[attrName]) {
+
+ if (sub.wildcards || !deviceData.values[attrName].has(sub))
+ deviceData.timestamps[attrName].delete(sub);
+ else if (deviceData.timestamps[attrName].get(sub) < timestamps[attrName]) {
+ deviceData.timestamps[attrName].delete(sub);
+ deviceData.values[attrName].delete(sub);
+ let inv = toInvalidate.get(sub) || {};
+ if (!timestamps[attrName] <= inv[attrName]) {
+ inv[attrName] = timestamps[attrName];
+ toInvalidate.set(sub, inv);
+ }
+ }
+ }
+ }
+ }
+
+ for (let attrName in timestamps) {
+ let existing = deviceData.timestamps[attrName].get(path);
+
+ if (existing == null || timestamps[attrName] >= existing) {
+ deviceData.timestamps[attrName].set(path, timestamps[attrName]);
+ let invalidate = false;
+ if (values[attrName] != null) {
+ if (!deviceData.values[attrName].has(path))
+ invalidate = true;
+ deviceData.values[attrName].set(path, values[attrName]);
+ }
+ else {
+ if (deviceData.values[attrName].has(path))
+ invalidate = true;
+ deviceData.values[attrName].delete(path)
+ }
+
+ if (invalidate) {
+ let inv = toInvalidate.get(path) || {};
+ if (!timestamps[attrName] <= inv[attrName]) {
+ inv[attrName] = timestamps[attrName];
+ toInvalidate.set(sub, inv);
+ }
+ }
+ }
+ }
+
+ toInvalidate.forEach(function(p, a) {
+ invalidateSuperset(deviceData, p, a);
+ });
+
+ // TODO recalculate descendant superset
+ // var depth = 0;
+ // if (timestamps.exist != null)
+ // depth = 99;
+ //
+ // for (let sup of deviceData.paths.superset(path, depth)) {
+ // if ((sup.wildcards && (3 << path.length) - 1) != path.wildcards) {
+ // throw new Error('Not implemented');
+ // }
+ // }
+}
+
+
+exports.sanitizeParameterValue = sanitizeParameterValue;
+exports.getAliasDeclarations = getAliasDeclarations;
+exports.inferTimestamps = inferTimestamps;
+exports.unpack = unpack;
+exports.set = set;
diff --git a/lib/sandbox.coffee b/lib/sandbox.coffee
index a7122be..ec61e12 100644
--- a/lib/sandbox.coffee
+++ b/lib/sandbox.coffee
@@ -26,6 +26,7 @@ class Exit;
sandbox = {
+ timestamp: null
deviceData: null
args: null
revision: null
@@ -36,6 +37,16 @@ sandbox = {
context: vm.createContext()
}
+sandbox.context.Date = class
+ constructor: (arg) ->
+ if arguments.length
+ return new (Function.prototype.bind.apply(Date, arguments))
+
+ return new Date(sandbox.timestamp)
+
+ now: () ->
+ return sandbox.timestamp
+
sandbox.context.ext = () ->
if sandbox.extensions?.length
@@ -56,66 +67,47 @@ sandbox.context.declare = (decs) ->
if sandbox.revision == sandbox.maxRevision + 1
for k, v of decs
- sandbox.declarations.push([common.parsePath(k)].concat(toIndexDeclaration(v)))
+ decT = {}
+ decV = {}
+ for attrName, dec of v
+ if Array.isArray(dec)
+ decT[attrName] = dec[0]
+ decV[attrName] = dec[1] if 1 in dec
+ else
+ decT[attrName] = dec
+
+ sandbox.declarations.push([common.parsePath(k), decT, decV])
throw new Exit()
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
+ res[k] = {}
+ unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision)
+ for path in unpacked
+ iter = sandbox.deviceData.paths.subset(path)
+ while (param = iter.next().value)?
+ if param.wildcards == 0 and sandbox.deviceData.values.exist.get(param, sandbox.revision)
+ r = {}
+ for attrName, dec of v
+ if (attrValue = sandbox.deviceData.values[attrName].get(param, sandbox.revision))?
+ r[attrName] = [sandbox.deviceData.timestamps[attrName].get(param, sandbox.revision), attrValue]
+
+ p = param.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, extensionsCache, startRevision, maxRevision) ->
+run = (script, args, timestamp, deviceData, extensionsCache, startRevision, maxRevision) ->
+ sandbox.timestamp = timestamp
sandbox.deviceData = deviceData
sandbox.extensionsCache = extensionsCache
sandbox.args = args
@@ -125,7 +117,7 @@ run = (script, args, deviceData, extensionsCache, startRevision, maxRevision) ->
sandbox.extensions = null
try
- ret = script.runInNewContext(sandbox.context)
+ ret = script.runInNewContext(sandbox.context, {displayErrors: false})
return {done: true, declarations: sandbox.declarations, returnValue: ret}
catch err
throw err if err not instanceof Exit
diff --git a/lib/session.coffee b/lib/session.coffee
index 96cba26..b751876 100644
--- a/lib/session.coffee
+++ b/lib/session.coffee
@@ -24,17 +24,30 @@ device = require './device'
sandbox = require './sandbox'
cache = require './cache'
extensions = require './extensions'
+PathSet = require './path-set'
+VersionedMap = require './versioned-map.js'
+
+
+initDeviceData = () ->
+ return {
+ paths : new PathSet()
+ timestamps : {exist: new VersionedMap(), object: new VersionedMap(), writable: new VersionedMap(), value: new VersionedMap()}
+ values : {exist: new VersionedMap(), object: new VersionedMap(), writable: new VersionedMap(), value: new VersionedMap()}
+ declarationTimestamps : new Map()
+ declarationValues : new Map()
+ virtualParameterTimestamps : new Map()
+ virtualParameterValues : new Map()
+ loaded : new Map()
+ }
init = (deviceId, cwmpVersion, timeout) ->
timestamp = Date.now()
- deviceData = device.init()
- device.set(deviceData, [], 0, [timestamp, 1, timestamp, 1, timestamp, 0, timestamp, null])
sessionData = {
timestamp : timestamp
deviceId : deviceId
- deviceData : deviceData
+ deviceData : initDeviceData()
cwmpVersion : cwmpVersion
timeout : timeout
provisions: []
@@ -47,24 +60,62 @@ init = (deviceId, cwmpVersion, timeout) ->
return sessionData
-loadParameters = (sessionData, toLoad, callback) ->
- # TODO optimize by removing overlaps
- for p, i in toLoad
- device.set(sessionData.deviceData, p, 0, [0, null, 0, null, 0, null, 0, null])
-
- if sessionData.new
+loadParameters = (sessionData, toLoad, virtualParameters, callback) ->
+ if not toLoad.length
return callback()
- db.fetchDevice(sessionData.deviceId, sessionData.timestamp, toLoad, (err, parameters) ->
+ db.fetchDevice(sessionData.deviceId, sessionData.timestamp, toLoad, (err, parameters, loaded) ->
return callback(err) if err
if not parameters?
# Device not available in database, mark as new
sessionData.new = true
+ path = sessionData.deviceData.paths.add([])
+ sessionData.deviceData.loaded.set(path, 99)
return callback()
+ for p in loaded
+ path = sessionData.deviceData.paths.add(p[0])
+
+ if p[1]
+ l = sessionData.deviceData.loaded.get(path) || 0
+ sessionData.deviceData.loaded.set(path, Math.max(l, p[1]))
+
for p in parameters
- device.set(sessionData.deviceData, p[0], 0, p.slice(1))
+ path = sessionData.deviceData.paths.add(p[0])
+
+ for k, v of p[1]
+ sessionData.deviceData.timestamps[k].set(path, v, 0)
+
+ for k, v of p[2]
+ sessionData.deviceData.values[k].set(path, v, 0)
+
+ # Virtual parameters
+ for p in toLoad
+ p = sessionData.deviceData.paths.add(p)
+ if p[0] == '*' or p[0] == 'VirtualParameters'
+ if p.length == 1
+ sessionData.deviceData.timestamps.exist.set(p, sessionData.timestamp, 0)
+ sessionData.deviceData.timestamps.object.set(p, sessionData.timestamp, 0)
+ sessionData.deviceData.timestamps.writable.set(p, sessionData.timestamp, 0)
+ sessionData.deviceData.values.exist.set(p, 1, 0)
+ sessionData.deviceData.values.object.set(p, 1, 0)
+ sessionData.deviceData.values.writable.set(p, 0, 0)
+ else if p.length == 2
+ if not p[1]?
+ sessionData.deviceData.timestamps.exist.set(p, sessionData.timestamp, 0)
+
+ for k, v of virtualParameters
+ p = sessionData.deviceData.paths.add(p)
+ sessionData.deviceData.timestamps.exist.set(p, sessionData.timestamp, 0)
+ sessionData.deviceData.timestamps.object.set(p, sessionData.timestamp, 0)
+ sessionData.deviceData.values.exist.set(p, 1, 0)
+ sessionData.deviceData.values.object.set(p, 0, 0)
+ else if virtualParameters[p[1]]?
+ sessionData.deviceData.timestamps.exist.set(p, sessionData.timestamp, 0)
+ sessionData.deviceData.timestamps.object.set(p, sessionData.timestamp, 0)
+ sessionData.deviceData.values.exist.set(p, 1, 0)
+ sessionData.deviceData.values.object.set(p, 0, 0)
return callback()
)
@@ -79,263 +130,478 @@ generateRpcId = (sessionData) ->
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']])
+
+ params = []
+ params.push([['DeviceID', 'Manufacturer'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [rpcReq.deviceId.Manufacturer, 'xsd:string']}])
+
+ params.push([['DeviceID', 'OUI'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [rpcReq.deviceId.OUI, 'xsd:string']}])
+
+ params.push([['DeviceID', 'ProductClass'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [rpcReq.deviceId.ProductClass, 'xsd:string']}])
+
+ params.push([['DeviceID', 'SerialNumber'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [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)])
+ path = common.parsePath(p[0])
+ params.push([path,
+ {exist: timestamp, object: timestamp, value: timestamp},
+ {exist: 1, object: 0, value: p.slice(1)}])
- device.set(sessionData.deviceData, ['Events', 'Inform'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [timestamp, 'xsd:dateTime']])
+ params.push([['Events', 'Inform'],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [timestamp, 'xsd:dateTime']}])
for e in rpcReq.event
- device.set(sessionData.deviceData, ['Events', e.replace(' ', '_')], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [timestamp, 'xsd:dateTime']])
+ params.push([['Events', e.replace(' ', '_')],
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 0, value: [timestamp, 'xsd:dateTime']}])
- return callback(null, {type : 'InformResponse'})
+ toLoad = []
+ for p in params
+ tl = []
+ for i in [p[0].length...0] by -1
+ path = p[0].slice(0, i)
+ loaded = 0
+ iter = sessionData.deviceData.paths.superset(path)
+ while sup = iter.next().value
+ if (l = sessionData.deviceData.loaded.get(sup)) > loaded
+ loaded = l
+
+ if loaded
+ tl = tl.slice(0, tl.length - (loaded - 1))
+ break
+
+ tl.push(path)
+
+ toLoad = toLoad.concat(tl)
+
+ loadParameters(sessionData, toLoad, null, (err) ->
+ return callback(err) if err
+
+ v.revision = 1 for k, v of sessionData.deviceData.timestamps
+ v.revision = 1 for k, v of sessionData.deviceData.values
+
+ for p in params
+ device.set(sessionData.deviceData, p[0], p[1], p[2])
+
+ return callback(null, {type : 'InformResponse'})
+ )
addProvisions = (sessionData, provisions) ->
sessionData.provisions = sessionData.provisions.concat(provisions)
if sessionData.revisions.length > 1 or sessionData.revisions[0] > 0
+ v.collapse(1) for k, v of sessionData.timestamps
+ v.collapse(1) for k, v of sessionData.values
sessionData.cycle += 1
sessionData.rpcCount = 0
sessionData.revisions = [0]
- device.collapse(sessionData.deviceData, 1)
sessionData.extensionsCache.length = 0
clearProvisions = (sessionData) ->
sessionData.provisions = []
if sessionData.revisions.length > 1 or sessionData.revisions[0] > 0
+ v.collapse(1) for k, v of sessionData.deviceData.timestamps
+ v.collapse(1) for k, v of sessionData.deviceData.values
sessionData.cycle += 1
sessionData.rpcCount = 0
sessionData.revisions = [0]
- device.collapse(sessionData.deviceData, 1)
sessionData.extensionsCache.length = 0
+
generateRpcRequest = (sessionData) ->
- res = device.traverse(sessionData.deviceData, null, null, (path, declaration, base, current, descendantTimestamps, children) ->
- if path[0] == 'Tags'
- if path.length == 2 and path[1]? and declaration[7]?
- if declaration[7][0] != current[7][0]
- device.set(sessionData.deviceData, path, 1, [sessionData.timestamp, 1, null, null, null, null, sessionData.timestamp, declaration[7]])
+ if sessionData.deviceData.declarationTimestamps.size == 0 and
+ sessionData.deviceData.declarationValues.size == 0
+ return null
+
+ refreshPaths = new PathSet()
+ refreshAttributes = {
+ exist: new Set()
+ object: new Set()
+ writable: new Set()
+ value: new Set()
+ }
+ setValues = new Map()
+ refreshGpn = new Map()
+
+ revision = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1
+
+ test = (deviceData, path, timestamps, values) ->
+ iter = deviceData.paths.superset(path)
+ param = null
+ existTimestamp = 0
+ while sup = iter.next().value
+ param = sup if not sup.wildcards
+ for k, v of timestamps
+ continue if not (t = deviceData.timestamps[k].get(sup, revision))?
+ existTimestamp = Math.max(existTimestamp, t) if k is 'exist'
+ delete timestamps[k] if t >= v
+
+ if not Object.keys(timestamps).length and
+ not (values? and Object.keys(values).length)
return
- r = {}
- gpn = false
+ if param?
+ if deviceData.values.exist.get(param, revision)
+ param = refreshPaths.add(param)
+ isObject = deviceData.values.object.get(param, revision)
+ isWritable = deviceData.values.writable.get(param, revision)
+ for k of timestamps
+ if k isnt 'value' or isObject == 0
+ refreshAttributes[k].add(param)
+ else if not isObject?
+ refreshAttributes.object.add(param)
- descendantRefresh = []
- for i in [0...descendantTimestamps.length] by 1 when descendantTimestamps[i][4]? or descendantTimestamps[i][2]?
- if descendantTimestamps[i][2] > descendantTimestamps[i][4]
- if current[3] == 1
- descendantRefresh[i] = true
- else
- r.object = true if current[2] < descendantTimestamps[i][2]
+ if values?.value?
+ if isObject == 0 and isWritable == 1
+ curVal = deviceData.values.value.get(param, revision)
+ cur = setValues.get(param)
+ if not cur?
+ cur = [deviceData.values.value.get(param, revision), values.value]
+ setValues.set(param, cur)
+ else
+ cur[1] = values.value
+ else
+ if not isObject?
+ refreshAttributes.object.add(param)
+ if not isWritable?
+ refreshAttributes.writable.add(param)
+ return
+ else if existTimestamp >= (timestamps.exist or 1)
+ return
+ else if path.indexOf('*') >= 0
+ subIter = deviceData.paths.subset(path)
+ while sub = subIter.next().value
+ continue if not deviceData.values.exist.get(sub, revision)
- 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)
+ isObject = deviceData.values.object.get(sub, revision)
+ isWritable = deviceData.values.writable.get(sub, revision)
- if overlap & 1 and descendantTimestamps[j][4]?
- if descendantRefresh[i] and descendantTimestamps[j][4] >= descendantTimestamps[i][2]
- descendantRefresh[i] = false
+ for k, v of timestamps
+ if not (v <= deviceData.timestamps[k].get(sub, revision))
+ if k isnt 'value' or isObject == 0
+ sub = refreshPaths.add(sub)
+ refreshAttributes[k].add(sub)
+ else if not isObject?
+ sub = refreshPaths.add(sub)
+ refreshAttributes.object.add(sub)
- if overlap & 2 and descendantTimestamps[i][4]?
- if descendantRefresh[j] and descendantTimestamps[i][4] >= descendantTimestamps[j][2]
- descendantRefresh[j] = false
+ if values?.value?
+ if isObject == 0 and isWritable == 1
+ curVal = deviceData.values.value.get(sub, revision)
+ cur = setValues.get(sub)
+ if not cur?
+ cur = [deviceData.values.value.get(sub, revision), values.value]
+ setValues.set(sub, cur)
+ else
+ cur[1] = values.value
+ else
+ if not isObject?
+ refreshAttributes.object.add(sub)
+ if not isWritable?
+ refreshAttributes.writable.add(sub)
- for d in descendantRefresh when d
- gpn = true
- break
+ if existTimestamp >= (timestamps.exist or 1)
+ return
- if declaration.length
- if declaration[0] > current[0]
- r.exist = true
+ childrenTimestamp = existTimestamp
- if declaration[2] > current[2]
- r.object = true
- r.exist = false
+ for i in [path.length - 1...0] by -1
+ existTimestamp = 0
+ p = path.slice(0, i)
+ iter = deviceData.paths.superset(p)
+ param = null
+ while sup = iter.next().value
+ param = sup if not sup.wildcards
+ if (t = deviceData.timestamps.exist.get(sup, revision))?
+ existTimestamp = Math.max(existTimestamp, t)
- if declaration[4] > current[4]
- r.writable = true
- r.exist = false
+ if param?
+ if deviceData.values.exist.get(param, revision)
+ isObject = deviceData.values.object.get(param, revision)
+ objectTimestamp = deviceData.timestamps.object.get(param, revision)
+ if isObject
+ if childrenTimestamp == 0
+ param = refreshPaths.add(param)
+ refreshGpn.set(param, refreshGpn.get(param) | ((1 << path.length - i) - 1))
+ else if childrenTimestamp < (timestamps.exist or 1)
+ param = refreshPaths.add(param)
+ refreshGpn.set(param, refreshGpn.get(param) or 1)
+ if not (objectTimestamp >= (timestamps.exist or 1))
+ param = refreshPaths.add(param)
+ refreshAttributes.object.add(param)
+ return
+ else if existTimestamp >= (timestamps.exist or 1)
+ return
+ else if p.indexOf('*') >= 0
+ subIter = deviceData.paths.subset(p)
+ ret = true
+ while sub = subIter.next().value
+ continue if not deviceData.values.exist.get(sub, revision)
+ # TODO check if not object
+ pp = sub.concat(path.slice(sub.length))
+ ts = {}
+ ts[k] = v for k, v of timestamps
+ ret = ret and test(deviceData, pp, ts)
- if declaration[6] > current[6]
- if current[7]? or current[3] == 0
- r.gpv ?= []
- r.gpv.push(path)
- r.object = false
- r.exist = false
- gpn = false
- else if not current[2] > 0
- r.object = true
+ if existTimestamp >= (timestamps.exist or 1)
+ return
- if declaration[7]? and current[7]?
- if current[4] == 0
- r.writable = true
- else if current[5]
- if declaration[7][0] != current[7][0] or declaration[7][1] != current[7][1]
- r.spv = [[path, declaration[7][0], declaration[7][1]]]
- r.exist = false
- gpn = false
+ childrenTimestamp = existTimestamp
- for k, v of children
- if v.writable or v.object or v.exist
- gpn = true
+ p = refreshPaths.add([])
+ refreshGpn.set(p, refreshGpn.get(p) or 1)
+ return false
- if v.gpn
- r.gpn = (r.gpn ? []).concat(v.gpn)
- r.exist = false
+ iter = sessionData.deviceData.declarationTimestamps.entries()
+ while (pair = iter.next().value)
+ timestamps = {}
+ timestamps[k] = v for k, v of pair[1]
+ test(sessionData.deviceData, pair[0], timestamps, sessionData.deviceData.declarationValues.get(pair[0]))
- if v.gpv
- r.gpv = (r.gpv ? []).concat(v.gpv)
- r.exist = false
- if v.spv
- r.spv = (r.spv ? []).concat(v.spv)
- r.exist = false
+ iter = refreshAttributes.exist.values()
+ while (path = iter.next().value)
+ descendantIter = refreshPaths.subset(path, 99)
+ found = false
+ while (p = descendantIter.next().value)?
+ if refreshAttributes.value.has(p) or
+ refreshAttributes.object.has(p) or
+ refreshAttributes.writable.has(p) or
+ refreshGpn.has(p)
+ found = true
+ break
+ if not found
+ path = refreshPaths.add(path.slice(0, -1))
+ refreshGpn.set(path, refreshGpn.get(path) or 1)
- if gpn
- r.exist = false
- if r.gpn?
- r.gpn = [path].concat(r.gpn)
- else
- r.gpn = [path]
- return r
- )
+ iter = refreshAttributes.object.values()
+ while (path = iter.next().value)
+ descendantIter = refreshPaths.subset(path, 99)
+ found = false
+ while (p = descendantIter.next().value)?
+ if refreshAttributes.value.has(p) or
+ (p.length > path.length and
+ (refreshAttributes.object.has(p) or
+ refreshAttributes.writable.has(p)))
+ found = true
+ break
+ if not found
+ path = refreshPaths.add(path.slice(0, -1))
+ refreshGpn.set(path, refreshGpn.get(path) or 1)
+
+ iter = refreshAttributes.writable.values()
+ while (path = iter.next().value)
+ path = refreshPaths.add(path.slice(0, -1))
+ refreshGpn.set(path, refreshGpn.get(path) or 1)
+
+ iter = refreshGpn.keys()
+ while (path = iter.next().value)
+ descendantIter = refreshPaths.subset(path, 99)
+ found = false
+ while (p = descendantIter.next().value)?
+ continue if p == path
+ if (flags = refreshGpn.get(p))
+ flags <<= p.length - path.length
+ refreshGpn.set(path, refreshGpn.get(path) | flags)
+ refreshGpn.delete(p)
+
+ rpcReq = null
+ completed = true
+
+ if refreshGpn.size
+ GET_PARAMETER_NAMES_DEPTH_THRESHOLD =
+ config.get('GET_PARAMETER_NAMES_DEPTH_THRESHOLD', sessionData.deviceId)
+
+ pair = refreshGpn.entries().next().value
+
+ nextLevel = true
+ if pair[0].length >= GET_PARAMETER_NAMES_DEPTH_THRESHOLD
+ nextLevel = false
+ else if common.hammingWeight(pair[1] >> 3) >= 3
+ nextLevel = false
+
+ if refreshGpn.size > 1 or (pair[1] > 1 and nextLevel)
+ completed = false
- if res.gpn?
rpcReq = {
type: 'GetParameterNames'
- parameterPath: res.gpn[0].join('.')
- nextLevel: true
+ parameterPath: pair[0].join('.')
+ nextLevel: nextLevel
}
- return rpcReq
- if res.gpv?
- rpcReq = {
- type: 'GetParameterValues'
- parameterNames: (p.join('.') for p in res.gpv.slice(0, config.get('TASK_PARAMETERS_BATCH_SIZE', sessionData.deviceId)))
- }
- return rpcReq
+ if refreshAttributes.value.size
+ if rpcReq?
+ completed = false
+ else
+ TASK_PARAMETERS_BATCH_SIZE =
+ config.get('TASK_PARAMETERS_BATCH_SIZE', sessionData.deviceId)
- if res.spv?
- 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 rpcReq
+ parameterNames = []
+ iter = refreshAttributes.value.values()
+ while (path = iter.next().value) and
+ parameterNames.length < TASK_PARAMETERS_BATCH_SIZE
+ parameterNames.push(path)
- return null
+ if refreshAttributes.value.size > parameterNames.length
+ completed = false
+ rpcReq = {
+ type: 'GetParameterValues'
+ parameterNames: (p.join('.') for p in parameterNames)
+ }
-loadDeclarations = (sessionData, virtualParameters, callback) ->
- toLoad = []
- device.traverse(sessionData.deviceData, null, null, (path, declaration, base, current, descendantTimestamps, children) ->
- descendantLoad = []
- for i in [0...descendantTimestamps.length] by 1
- if descendantTimestamps[i][2]?
- if not descendantTimestamps[i][4]?
- toLoad.push(descendantTimestamps[i][0])
- 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
+ TASK_PARAMETERS_BATCH_SIZE =
+ config.get('TASK_PARAMETERS_BATCH_SIZE', sessionData.deviceId)
- for j in [0...i] by 1 when descendantTimestamps[j][4]? or descendantFlags[j]
- overlap = common.pathOverlap(descendantTimestamps[j][0], descendantTimestamps[i][0], path.length)
+ parameterValues = []
+ setValues.forEach((v, k) ->
+ return if completed == false
- if overlap & 1 and descendantTimestamps[j][4]?
- 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))
-
- if declaration[7]? and not current[4]?
- # Need writable attribute when declaring a value
- toLoad.push(path)
+ if rpcReq?
+ completed = false
return
- for i in [0...declaration.length] by 2
- if declaration[i]? and not current[i]?
- toLoad.push(path)
+ val = v[1].slice()
+ if not val[1]?
+ val[1] = v[0][1]
+ device.sanitizeParameterValue(val)
+ if val[0] != v[0][0] or val[1] != v[0][1]
+ if parameterValues.length >= TASK_PARAMETERS_BATCH_SIZE
+ completed = false
+ return
+ parameterValues.push([k, val[0], val[1]])
+ )
+
+ if parameterValues.length and not rpcReq?
+ rpcReq = {
+ type: 'SetParameterValues'
+ parameterList: ([p[0].join('.'), p[1], p[2]] for p in parameterValues)
+ }
+
+ if completed
+ sessionData.deviceData.declarationTimestamps.clear()
+ sessionData.deviceData.declarationValues.clear()
+
+ return rpcReq
+
+
+getParametersToLoad = (sessionData) ->
+ toLoad = []
+
+ test = (timestamps, path) ->
+ tl = []
+ for i in [path.length...0] by -1
+ p = path.slice(0, i)
+ loaded = 0
+ iter = sessionData.deviceData.paths.superset(p, 1)
+
+ while sup = iter.next().value
+ if (l = sessionData.deviceData.loaded.get(sup)) > loaded
+ loaded = l
+
+ if loaded
+ tl = tl.slice(0, tl.length - (loaded - 1))
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])
+ tl.push(p)
- return loadParameters(sessionData, toLoad, callback)
+ toLoad = toLoad.concat(tl)
+
+ sessionData.deviceData.declarationTimestamps.forEach(test)
+ sessionData.deviceData.virtualParameterTimestamps.forEach(test)
+
+ return toLoad
-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)
- )
+extractVirtualParameterDeclarations = (deviceData, revision, virtualParameters) ->
+ if deviceData.virtualParameterTimestamps.size == 0 and
+ deviceData.virtualParameterValues.size == 0
+ return {}
+
+ iter = deviceData.paths.subset(['VirtualParameters', '*'])
+ virtualParameterDeclarations = {}
+ while path = iter.next().value
+ continue if path.wildcards or not virtualParameters[path[1]]?
+
+ if declarationTimestamps = deviceData.virtualParameterTimestamps.get(path)
+ for k, v of declarationTimestamps
+ ct = deviceData.timestamps[k].get(path, revision)
+ if not (v <= ct)
+ virtualParameterDeclarations[path[1]] ?= {timestamps: {}, values: {}}
+ virtualParameterDeclarations[path[1]].timestamps[k] =
+ Math.max(v, virtualParameterDeclarations[path[1]].timestamps[k] ? 0)
+
+ if declarationValues = deviceData.virtualParameterValues.get(path)
+ for k, v of declarationValues
+ virtualParameterDeclarations[path[1]] ?= {timestamps: {}, values: {}}
+ cv = deviceData.values[k].get(path, revision)
+ if not virtualParameterDeclarations[path[1]].values[k]?
+ virtualParameterDeclarations[path[1]].values[k] = [cv, v]
+ else
+ virtualParameterDeclarations[path[1]].values[k][1] = v
+
+ for k, v of virtualParameterDeclarations
+ for attrName, attrValue of v.values
+ val = attrValue[1].slice()
+ val[1] = attrValue[0][1] if not val[1]?
+ device.sanitizeParameterValue(val)
+ if val[0] != attrValue[0][0] or val[1] != attrValue[0][1]
+ v.values[attrName] = attrValue[1]
+ else
+ delete v.values[attrName]
+ if Object.keys(v.values).length == 1
+ if Object.keys(v.timestamps).length == 0
+ delete virtualParameterDeclarations[k]
+ else
+ delete v.values[attrName]
+
+ deviceData.virtualParameterTimestamps.clear()
+ deviceData.virtualParameterValues.clear()
- device.clearDeclarations(sessionData.deviceData, ['VirtualParameters'])
return virtualParameterDeclarations
-commitVirtualParameter = (sessionData, parameterDeclaration, revision, update) ->
- v = []
+commitVirtualParameter = (sessionData, name, declaration, update) ->
+ t = {}
+ v = {}
if update.writable?
if update.writable[0] <= 0
- v[4] = now + update.writable[0]
+ t.writable = sessionData.timestamp + update.writable[0]
else
- v[4] = Math.min(sessionData.timestamp, update.writable[0])
+ t.writable = Math.min(sessionData.timestamp, update.writable[0])
- if parameterDeclaration[5]?
- v[4] = Math.max(parameterDeclaration[5], v[4])
+ if declaration.timestamps.writable?
+ t.writable = Math.max(declaration.timestamps.writable, t.writable)
- v[5] = Boolean(JSON.parse(update.writable[1]))
- else if parameterDeclaration[5]? or parameterDeclaration[6]?
+ v.writable = +update.writable[1]
+ else if declaration.timestamps.writable?
throw new Error('Virtual parameter must provide declared attributes')
if update.value?
if update.value[0] <= 0
- v[6] = sessionData.timestamp + update.value[0]
+ t.value = sessionData.timestamp + update.value[0]
else
- v[6] = Math.min(now, update.value[0])
+ t.value = Math.min(sessionData.timestamp, update.value[0])
- if parameterDeclaration[7]?
- v[6] = Math.max(parameterDeclaration[7], v[6])
+ if declaration.timestamps.value?
+ t.value = Math.max(declaration.timestamps.value, t.value)
- v[7] = device.sanitizeParameterValue(update.value[1])
- else if parameterDeclaration[7]? or parameterDeclaration[8]?
+ v.value = device.sanitizeParameterValue(update.value[1])
+ else if declaration.timestmaps.value?
throw new Error('Virtual parameter must provide declared attributes')
- device.set(sessionData.deviceData, parameterDeclaration[0], revision, v)
+ device.set(sessionData.deviceData, ['VirtualParameters', name], t, v)
runExtensions = (sessionData, revision, _extensions, callback) ->
@@ -361,10 +627,15 @@ runExtensions = (sessionData, revision, _extensions, callback) ->
rpcRequest = (sessionData, declarations, callback) ->
+ if not declarations?.length
+ if (rpcReq = generateRpcRequest(sessionData))?
+ sessionData.rpcRequest = rpcReq
+ return callback(null, generateRpcId(sessionData), rpcReq)
+
allDeclarations = declarations?.slice() ? []
cache.getProvisionsAndVirtualParameters((err, presetsHash, provisions, virtualParameters) ->
- return callback(err) if err or (sessionData.presetsHash? and sessionData.presetsHash != presetsHash)
+ return callback(err) if err or (sessionData.presetsHash? and sessionData.presetsHashpresetsHash)
done = true
_extensions = []
@@ -373,31 +644,38 @@ rpcRequest = (sessionData, declarations, callback) ->
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, 1, null, 1, null, provision[2]])
+ l = path.length
+ path.length = 16
+ path.fill('*', l)
+ t = provision[2]
+ t += sessionData.timestamp if t <= 0
+
+ for i in [l...path.length] by 1
+ allDeclarations.push([path.slice(0, i), {exist: t, object: 1, writable: 1, value: t}])
when 'value'
- allDeclarations.push([common.parsePath(provision[1]), 1, null, null, null, null, null, 1, [provision[2]]])
+ allDeclarations.push([common.parsePath(provision[1]), {exist: 1, value: 1}, {value: [provision[2]]}])
when 'tag'
- allDeclarations.push([[['Tags', provision[1]], null, null, null, null, null, null, null, [provision[2], 'xsd:boolean']]])
+ allDeclarations.push([['Tags', provision[1]], {exist: 1, value: 1}, {exist: 1, value: [provision[2], 'xsd:boolean']}])
when '_task'
# A special provision for tasks compatibility
switch provision[2]
when 'getParameterValues'
for i in [3...provision.length] by 1
- allDeclarations.push([common.parsePath(provision[i]), 1, null, null, null, null, null, sessionData.timestamp])
+ allDeclarations.push([common.parsePath(provision[i]), {exist: 1, value: sessionData.timestamp}])
when 'setParameterValues'
for i in [3...provision.length] by 3
v = if provision[i + 2] then [provision[i + 1], provision[i + 2]] else [provision[i + 1]]
- allDeclarations.push([common.parsePath(provision[i]), 1, null, null, null, null, null, 1, v])
+ allDeclarations.push([common.parsePath(provision[i]), {exist: 1, value: 1}, {value: v}])
when 'refreshObject'
path = common.parsePath(provision[3])
- for i in [path.length...16] by 1
- path.length = i
- allDeclarations.push([path.slice(), sessionData.timestamp, null, 1, null, 1, null, sessionData.timestamp])
+ l = path.length
+ path.length = 16
+ path.fill('*', l)
+ for i in [l...16] by 1
+ allDeclarations.push([path.slice(0, i), {exist: sessionData.timestamp, object: 1, writable: 1, value: sessionData.timestamp}])
continue
- ret = sandbox.run(provisions[provision[0]].script, provision.slice(1), sessionData.deviceData, sessionData.extensionsCache, 0, sessionData.revisions[0] >> 1)
+ ret = sandbox.run(provisions[provision[0]].script, provision.slice(1), sessionData.timestamp, sessionData.deviceData, sessionData.extensionsCache, 0, sessionData.revisions[0] >> 1)
if ret.extensions?.length
_extensions = _extensions.concat(ret.extensions)
done &&= ret.done
@@ -413,91 +691,129 @@ rpcRequest = (sessionData, declarations, callback) ->
)
return
- doVirtualParameters = (iter, virtualParameterDeclarations, cb) ->
+ doVirtualParameters = (inception, virtualParameterDeclarations, cb) ->
+ revision = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1
if not virtualParameterDeclarations?
- virtualParameterDeclarations = extractVirtualParameterDeclarations(sessionData)
- if not virtualParameterDeclarations?.length
+ virtualParameterDeclarations =
+ extractVirtualParameterDeclarations(sessionData.deviceData, revision, virtualParameters)
+ if not Object.keys(virtualParameterDeclarations).length
return cb(false)
- sessionData.revisions[iter] ?= 0
+ sessionData.revisions[inception] ?= 0
decs = []
- virtualParameterUpdates = []
+ virtualParameterUpdates = {}
_extensions = []
- lastRevision = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1))
- firstRevision = lastRevision - (sessionData.revisions[sessionData.revisions.length - 1] >> 1)
- for vpd, i in virtualParameterDeclarations
- vpName = vpd[0][1]
- continue if not virtualParameters[vpName]?
- ret = sandbox.run(virtualParameters[vpName].script, [vpd.slice(1)], sessionData.deviceData, sessionData.extensionsCache, firstRevision, lastRevision)
+
+ firstRevision = revision - (sessionData.revisions[sessionData.revisions.length - 1] >> 1)
+ for vpName, vpDeclarations of virtualParameterDeclarations
+ ret = sandbox.run(virtualParameters[vpName].script, [vpDeclarations.timestamps, vpDeclarations.values], sessionData.timestamp, sessionData.deviceData, sessionData.extensionsCache, firstRevision, revision)
+
if ret.extensions?.length
_extensions = _extensions.concat(ret.extensions)
decs = decs.concat(ret.declarations)
if ret.done
- virtualParameterUpdates.push(ret.returnValue)
+ virtualParameterUpdates[vpName] = ret.returnValue
if _extensions.length
- runExtensions(sessionData, lastRevision, _extensions, (err) ->
+ runExtensions(sessionData, revision, _extensions, (err) ->
return callback(err) if err
- return doVirtualParameters(iter, virtualParameterDeclarations, cb)
+ return doVirtualParameters(inception, virtualParameterDeclarations, cb)
)
return
- 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 Object.keys(virtualParameterUpdates).length == Object.keys(virtualParameterDeclarations).length
+ rev = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1
+ v.revision = rev for k, v of sessionData.deviceData.timestamps
+ v.revision = rev for k, v of sessionData.deviceData.values
+ for vpName, vpDeclarations of virtualParameterDeclarations
+ commitVirtualParameter(sessionData, vpName, vpDeclarations, virtualParameterUpdates[vpName])
- if sessionData.revisions[iter] == 0
+ if sessionData.revisions[inception] == 0
return cb(false)
- sessionData.revisions.length = iter
- rev = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1))
- device.collapse(sessionData.deviceData, rev + 1)
- if sessionData.extensionsCache.length > rev + 1
- sessionData.extensionsCache.length = rev + 1
+ sessionData.revisions.length = inception
+ rev = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1
+ m.collapse(rev) for k, m of sessionData.deviceData.timestamps
+ m.collapse(rev) for k, m of sessionData.deviceData.values
+ if sessionData.extensionsCache.length > rev
+ sessionData.extensionsCache.length = rev
return cb(true)
- applyDeclarations(iter, decs, () ->
- return doVirtualParameters(iter, virtualParameterDeclarations, cb)
+ applyDeclarations(inception, decs, () ->
+ return doVirtualParameters(inception, virtualParameterDeclarations, cb)
)
- applyDeclarations = (iter, decs, cb) ->
- if sessionData.revisions[iter] % 2 == 0
+ applyDeclarations = (inception, decs, cb) ->
+ declare = (path, timestamps, values, allTimestamps, allValues) ->
+ dt = null
+ for k, v of timestamps
+ if not dt?
+ dt = allTimestamps.get(path)
+ if not dt?
+ dt = {}
+ allTimestamps.set(path, dt)
+ v += sessionData.timestamp if v <= 0
+ dt[k] = v if v > (dt[k] || 0)
+
+ dv = null
+ for k, v of values
+ if not dv?
+ dv = allValues.get(path)
+ if not dv?
+ dv = {}
+ allValues.set(path, dv)
+ dv[k] = v
+
+ if sessionData.revisions[inception] % 2 == 0
for declaration in decs
- for d in device.getPrerequisiteDeclarations(declaration)
- device.declare(sessionData.deviceData, d[0], d.slice(1), sessionData.timestamp)
+ aliasDeclarations = device.getAliasDeclarations(declaration[0], declaration[1][0])
+ if aliasDeclarations?
+ for d in aliasDeclarations
+ path = sessionData.deviceData.paths.add(d[0])
+ if path[0] == 'VirtualParameters' or path[0] == '*'
+ declare(path, d[1], d[2],
+ sessionData.deviceData.virtualParameterTimestamps,
+ sessionData.deviceData.virtualParameterValues)
+ continue if path[0] != '*'
+
+ declare(path, d[1], d[2],
+ sessionData.deviceData.declarationTimestmaps,
+ sessionData.deviceData.declarationValues)
else
for declaration in decs
- params = device.getAll(sessionData.deviceData, declaration[0], (sessionData.revisions[iter] >> 1) + 1)
- # TODO move setting tags elsewhere
- 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)
+ unpacked = device.unpack(sessionData.deviceData, declaration[0])
+
+ for path in unpacked
+ path = sessionData.deviceData.paths.add(path)
+ if path[0] == 'VirtualParameters' or path[0] == '*'
+ declare(path, declaration[1], declaration[2],
+ sessionData.deviceData.virtualParameterTimestamps,
+ sessionData.deviceData.virtualParameterValues)
+ continue if path[0] != '*'
+
+ declare(path, declaration[1], declaration[2],
+ sessionData.deviceData.declarationTimestamps,
+ sessionData.deviceData.declarationValues)
nextRpc = (applied) ->
+ ++ sessionData.revisions[inception]
if not applied
if (rpcReq = generateRpcRequest(sessionData))?
sessionData.rpcRequest = rpcReq
return callback(null, generateRpcId(sessionData), rpcReq)
- if ++ sessionData.revisions[iter] % 2 == 0
+ if sessionData.revisions[inception] % 2 == 0
return cb()
else
- return applyDeclarations(iter, decs, cb)
+ return applyDeclarations(inception, decs, cb)
- if sessionData.revisions.length > iter + 1
- doVirtualParameters(iter + 1, null, nextRpc)
+ if sessionData.revisions.length > inception + 1
+ doVirtualParameters(inception + 1, null, nextRpc)
else
- loadDeclarations(sessionData, virtualParameters, (err) ->
+ loadParameters(sessionData, getParametersToLoad(sessionData), virtualParameters, (err) ->
return callback(err) if err
- doVirtualParameters(iter + 1, null, nextRpc)
+ doVirtualParameters(inception + 1, null, nextRpc)
)
applyDeclarations(0, allDeclarations, () ->
@@ -516,35 +832,48 @@ rpcResponse = (sessionData, id, rpcRes, callback) ->
sessionData.rpcRequest = null
timestamp = sessionData.timestamp
- revision = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1)) + 1
+ revision = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1
+
+ v.revision = revision for k, v of sessionData.deviceData.timestamps
+ v.revision = revision for k, v of sessionData.deviceData.values
switch rpcRes.type
when 'GetParameterValuesResponse'
return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'GetParameterValues'
for p in rpcRes.parameterList
- device.set(sessionData.deviceData, common.parsePath(p[0]), revision, [timestamp, 1, timestamp, 0, null, null, timestamp, p.slice(1)])
+ device.set(sessionData.deviceData, common.parsePath(p[0]),
+ {exist: timestamp, object: timestamp, value: timestamp},
+ {exist: 1, object: 0, value: p.slice(1)})
when 'GetParameterNamesResponse'
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), revision, timestamp)
+ device.set(sessionData.deviceData, common.parsePath(rpcReq.parameterPath).concat('*'),
+ {exist: timestamp})
for p in rpcRes.parameterList
if common.endsWith(p[0], '.')
path = common.parsePath(p[0][0...-1])
if not rpcReq.nextLevel
- device.set(sessionData.deviceData, path.conact(null), revision, timestamp)
+ device.set(sessionData.deviceData, path.concat('*'),
+ {exist: timestamp})
- device.set(sessionData.deviceData, path, revision, [timestamp, 1, timestamp, 1, timestamp, if p[1] then 1 else 0])
+ device.set(sessionData.deviceData, path,
+ {exist: timestamp, object: timestamp, writable: timestamp},
+ {exist: 1, object: 1, writable: if p[1] then 1 else 0})
else
- device.set(sessionData.deviceData, common.parsePath(p[0]), revision, [timestamp, 1, timestamp, 0, timestamp, if p[1] then 1 else 0])
+ device.set(sessionData.deviceData, common.parsePath(p[0]),
+ {exist: timestamp, object: timestamp, writable: timestamp},
+ {exist: 1, object: 0, writable: if p[1] then 1 else 0})
when 'SetParameterValuesResponse'
return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'SetParameterValues'
for p in rpcReq.parameterList
- device.set(sessionData.deviceData, common.parsePath(p[0]), revision, [timestamp, 1, timestamp, 0, timestamp, 1, timestamp, p.slice(1)])
+ device.set(sessionData.deviceData, common.parsePath(p[0]),
+ {exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
+ {exist: 1, object: 0, writable: 1, value: p.slice(1)})
else
return callback(new Error('Response type not recognized'))
@@ -557,56 +886,76 @@ rpcFault = (sessionData, id, faultResponse, callback) ->
load = (id, callback) ->
- db.redisClient.get("session_#{id}", (err, sessionData) ->
- return callback(err) if err
- return callback(null, JSON.parse(sessionData))
+ db.redisClient.get("session_#{id}", (err, sessionDataString) ->
+ return callback(err) if err or not sessionDataString?
+ sessionData = JSON.parse(sessionDataString)
+ deviceData = initDeviceData()
+ keys = ['exist', 'object', 'writable', 'value']
+ for r in sessionData.deviceData
+ path = deviceData.paths.add(r[0])
+
+ if r[1]
+ deviceData.loaded.set(path, r[1])
+
+ for k, i in keys
+ if r[2 + i]?
+ deviceData.timestamps[k].setRevisions(path, r[2 + i])
+
+ if r[2 + keys.length + i]?
+ deviceData.values[k].setRevisions(path, r[2 + keys.length + i])
+
+ if r[2 + (keys.length * 2) + i]?
+ deviceData.declarationTimestamps.set(path, r[2 + (keys.length * 2) + i])
+
+ if r[2 + (keys.length * 3) + i]?
+ deviceData.declarationValues.set(path, r[2 + (keys.length * 3) + i])
+
+ sessionData.deviceData = deviceData
+
+ return callback(null, sessionData)
)
save = (sessionData, callback) ->
sessionData.id ?= crypto.randomBytes(8).toString('hex')
+ deviceData = []
+ keys = ['exist', 'object', 'writable', 'value']
+ iter = sessionData.deviceData.paths.find([], 99)
+ while not (p = iter.next()).done
+ path = p.value
+ e = [path]
+ e[1] = sessionData.deviceData.loaded.get(path) || 0
+ for k, i in keys
+ if r = sessionData.deviceData.timestamps[k].getRevisions(path)
+ e[2 + i] = r
- db.redisClient.setex("session_#{sessionData.id}", sessionData.timeout, JSON.stringify(sessionData), (err) ->
+ if r = sessionData.deviceData.values[k].getRevisions(path)
+ e[2 + keys.length + i] = r
+
+ if r = sessionData.deviceData.declarationTimestamps.get(path)
+ e[2 + (keys.length * 2) + i] = r
+
+ if r = sessionData.deviceData.declarationValues.get(path)
+ e[2 + (keys.length * 3) + i] = r
+
+ deviceData.push(e)
+
+ oldDeviceData = sessionData.deviceData.deviceData
+ sessionData.deviceData = deviceData
+ sessionDataString = JSON.stringify(sessionData)
+ sessionData.deviceData = oldDeviceData
+
+ db.redisClient.setex("session_#{sessionData.id}", sessionData.timeout, sessionDataString, (err) ->
return callback(err, sessionData.id)
)
return
end = (sessionData, callback) ->
- getDiff = (cb) ->
- diff = []
- toLoad = []
- res = device.traverse(sessionData.deviceData, null, null, (path, declaration, base, current, descendantTimestamps, children) ->
- for dt in descendantTimestamps
- if dt[1] >= path.length and dt[3] != dt[4]
- toLoad.push(dt[0]) if not dt[3]?
- diff.push([dt[0], dt[1], dt[3], dt[4]])
-
- for i in [0...current.length] by 2
- if current[i] != base[i] or current[i + 1] != base[i + 1]
- if not base[i]?
- toLoad.push(path)
- break
- else
- diff.push([path, path.length, base, current])
- break
- )
-
- if toLoad.length
- loadParameters(sessionData, toLoad, (err) ->
- return callback(err) if err
- getDiff(cb)
- )
- return
-
- return cb(diff)
-
- getDiff((diff) ->
- db.saveDevice(sessionData.deviceId, diff, sessionData.new, (err) ->
- return callback(err) if err
- db.redisClient.del("session_#{sessionData.id}", (err) ->
- callback(err, sessionData.new)
- )
+ db.saveDevice(sessionData.deviceId, sessionData.deviceData, sessionData.new, (err) ->
+ return callback(err) if err
+ db.redisClient.del("session_#{sessionData.id}", (err) ->
+ callback(err, sessionData.new)
)
)