├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── example ├── clientBootstrapTest.js └── factoryBootstrapTest.js ├── index.js ├── lib ├── coap-node.js ├── components │ ├── constants.js │ ├── cutils.js │ ├── helper.js │ └── reqHandler.js ├── config.js └── init.js ├── package.json └── test ├── coap-node.functional.test.js ├── coap-node.reqHandler.test.js ├── coap-node.signature.test.js └── cutils.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1.0" 4 | - "6.9.2" 5 | - "8.11.1" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | Peter Yi 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test-all: 2 | @./node_modules/.bin/mocha -u bdd --reporter spec 3 | 4 | .PHONY: test-all -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coap-node 2 | Client node of lightweight M2M (LWM2M). 3 | 4 | [![NPM](https://nodei.co/npm/coap-node.png?downloads=true)](https://nodei.co/npm/coap-node/) 5 | 6 | [![Build Status](https://travis-ci.org/PeterEB/coap-node.svg?branch=develop)](https://travis-ci.org/PeterEB/coap-node) 7 | [![npm](https://img.shields.io/npm/v/coap-node.svg?maxAge=2592000)](https://www.npmjs.com/package/coap-node) 8 | [![npm](https://img.shields.io/npm/l/coap-node.svg?maxAge=2592000)](https://www.npmjs.com/package/coap-node) 9 | 10 |
11 | 12 | ## Documentation 13 | 14 | Please visit the [Wiki](https://github.com/PeterEB/coap-node/wiki). 15 | 16 |
17 | 18 | ## Overview 19 | 20 | [**OMA Lightweight M2M**](http://technical.openmobilealliance.org/Technical/technical-information/release-program/current-releases/oma-lightweightm2m-v1-0) (LWM2M) is a resource constrained device management protocol relies on [**CoAP**](https://tools.ietf.org/html/rfc7252). And **CoAP** is an application layer protocol that allows devices to communicate with each other RESTfully over the Internet. 21 | 22 | **coap-shepherd**, **coap-node** and **lwm2m-bs-server** modules aim to provide a simple way to build and manage a **LWM2M** machine network. 23 | * Server-side library: [**coap-shepherd**](https://github.com/PeterEB/coap-shepherd) 24 | * Client-side library: **coap-node** (this module) 25 | * Bootstrap server library: [**lwm2m-bs-server**](https://github.com/PeterEB/lwm2m-bs-server) 26 | * [**A simple demo webapp**](https://github.com/PeterEB/quick-demo) 27 | 28 | ![coap-shepherd net](https://raw.githubusercontent.com/PeterEB/documents/master/coap-shepherd/media/lwm2m_net.png) 29 | 30 | ### LWM2M Client: coap-node 31 | 32 | * It is an implementation of LWM2M Client managed by a **coap-shepherd** Server. 33 | * It follows most parts of **LWM2M** specification to meet the requirements of a machine network and devices management. 34 | * It works well with [**Leshan**](https://github.com/eclipse/leshan). 35 | * Support mulitple servers, factory bootstrap and client initiated bootstrap. 36 | * It uses [smartobject](https://github.com/PeterEB/smartobject) as its fundamental of resource organizing on devices. **smartobject** can help you create smart objects with IPSO data model, and it also provides a scheme to help you abstract your hardware into smart objects. You may like to use **smartobject** to create many plugins for your own hardware or modules, i.e., temperature sensor, humidity sensor, light control. Here is a [tutorual of how to plan resources](https://github.com/PeterEB/smartobject/blob/master/docs/resource_plan.md) with smartobject. 37 | 38 |
39 | 40 | ## Installation 41 | 42 | > $ npm install coap-node --save 43 | 44 |
45 | 46 | ## Usage 47 | 48 | Client-side example (the following example is how you use `coap-node` on a machine node): 49 | 50 | * Step 1: Resources initialzation. 51 | ```js 52 | var SmartObject = require('smartobject'); 53 | 54 | // initialize Resources that follow IPSO definition 55 | var so = new SmartObject(); 56 | 57 | // initialize your Resources 58 | // oid = 'temperature', iid = 0 59 | so.init('temperature', 0, { 60 | sensorValue: 21, 61 | units: 'C' 62 | }); 63 | 64 | // oid = 'lightCtrl', iid = 0 65 | so.init('lightCtrl', 0, { 66 | onOff: false 67 | }); 68 | ``` 69 | 70 | * Step 2: Client device initialzation. 71 | ```js 72 | var CoapNode = require('coap-node'); 73 | 74 | // Instantiate a machine node with a client name and your smart object 75 | var cnode = new CoapNode('my_first_node', so); 76 | 77 | cnode.on('registered', function () { 78 | // If the registration procedure completes successfully, 'registered' will be fired 79 | 80 | // after registered, start your application 81 | }); 82 | 83 | // register to a Server with its ip and port 84 | cnode.register('192.168.0.77', 5683, function (err, rsp) { 85 | console.log(rsp); // { status: '2.05' } 86 | }); 87 | ``` 88 | 89 | Server-side example (please go to [coap-shepherd](https://github.com/PeterEB/coap-shepherd) document for details): 90 | 91 | ```js 92 | var cnode = cserver.find('my_first_node'); 93 | 94 | cnode.read('/temperature/0/sensorValue', function (err, rsp) { 95 | console.log(rsp); // { status: '2.05', data: 21 } 96 | }); 97 | 98 | cnode.write('/lightCtrl/0/onOff', true, function (err, rsp) { 99 | console.log(rsp); // { status: '2.04' } 100 | }); 101 | ``` 102 | 103 |
104 | 105 | ## License 106 | 107 | Licensed under [MIT](https://github.com/PeterEB/coap-node/blob/master/LICENSE). 108 | -------------------------------------------------------------------------------- /example/clientBootstrapTest.js: -------------------------------------------------------------------------------- 1 | var CoapNode = require('../index.js'), 2 | SmartObject = require('smartobject'); 3 | 4 | var so = new SmartObject(); 5 | 6 | so.init(3303, 0, { 7 | sensorValue: 21, 8 | units: 'C', 9 | 5702: { 10 | read: function (cb) { 11 | var time = new Date(); 12 | cb(null, time.toString()); 13 | } 14 | }, 15 | 5703: { 16 | write: function (val, cb) { 17 | console.log('write ' + val); 18 | cb(null, val); 19 | } 20 | }, 21 | 5704: { 22 | exec: function (val1, val2, cb) { 23 | console.log(val1 + ': Hello ' + val2 + '!'); 24 | cb(null); 25 | } 26 | } 27 | }); 28 | 29 | so.init(3303, 1, { 30 | 5700: 70, 31 | 5701: 'F' 32 | }); 33 | 34 | so.init(3312, 0, { 35 | 5850: false, 36 | }); 37 | 38 | var coapNode = new CoapNode('coap-node-test', so, { lifetime: 300 }); 39 | 40 | coapNode.on('bootstrapped', function () { 41 | console.log('bootstrapped'); 42 | coapNode.registerAllCfg(function (err, rsp) { 43 | console.log(rsp); 44 | }); 45 | }); 46 | 47 | coapNode.on('registered', function () { 48 | console.log('registered'); 49 | }); 50 | 51 | coapNode.on('deregistered', function (msg) { 52 | console.log('deregistered'); 53 | }); 54 | 55 | coapNode.on('offline', function (msg) { 56 | console.log('offline'); 57 | }); 58 | 59 | coapNode.on('reconnect', function (msg) { 60 | console.log('reconnect'); 61 | }); 62 | 63 | coapNode.on('error', function (err) { 64 | console.log(err); 65 | }); 66 | 67 | coapNode.bootstrap('leshan.eclipse.org', 5783, function (err, rsp) { 68 | console.log(rsp); 69 | }); 70 | 71 | // coapNode.bootstrap('127.0.0.1', 5783, function (err, rsp) { 72 | // console.log(rsp); 73 | // }); 74 | 75 | // update test 76 | // setTimeout(function () { 77 | // coapNode.update({ lifetime: 85741 }, function (err, rsp) { 78 | // console.log(rsp); 79 | // }); 80 | // }, 10000); 81 | 82 | // deregister test 83 | // setTimeout(function () { 84 | // coapNode.deregister(function (err, rsp) { 85 | // console.log(rsp); 86 | // }); 87 | // }, 20000); 88 | 89 | // setTimeout(function () { 90 | // coapNode.checkout(10, function (err, rsp) { 91 | // console.log(rsp); 92 | // }); 93 | // }, 5000); 94 | 95 | // setTimeout(function () { 96 | // coapNode.checkin(function (err, rsp) { 97 | // console.log(rsp); 98 | // }); 99 | // }, 15000); 100 | -------------------------------------------------------------------------------- /example/factoryBootstrapTest.js: -------------------------------------------------------------------------------- 1 | var CoapNode = require('../index.js'), 2 | SmartObject = require('smartobject'); 3 | 4 | var so = new SmartObject(); 5 | 6 | so.init(3303, 0, { 7 | sensorValue: 21, 8 | units: 'C', 9 | 5702: { 10 | read: function (cb) { 11 | var time = new Date(); 12 | cb(null, time.toString()); 13 | } 14 | }, 15 | 5703: { 16 | write: function (val, cb) { 17 | console.log('write ' + val); 18 | cb(null, val); 19 | } 20 | }, 21 | 5704: { 22 | exec: function (val1, val2, cb) { 23 | console.log(val1 + ': Hello ' + val2 + '!'); 24 | cb(null); 25 | } 26 | } 27 | }); 28 | 29 | so.init(3303, 1, { 30 | 5700: 70, 31 | 5701: 'F' 32 | }); 33 | 34 | so.init(3312, 0, { 35 | 5850: false, 36 | }); 37 | 38 | var coapNode = new CoapNode('coap-node-test', so, { lifetime: 300 }); 39 | 40 | coapNode.on('registered', function () { 41 | console.log('registered'); 42 | }); 43 | 44 | coapNode.on('deregistered', function (msg) { 45 | console.log('deregistered'); 46 | }); 47 | 48 | coapNode.on('offline', function (msg) { 49 | console.log('offline'); 50 | }); 51 | 52 | coapNode.on('reconnect', function (msg) { 53 | console.log('reconnect'); 54 | }); 55 | 56 | coapNode.on('error', function (err) { 57 | console.log(err); 58 | }); 59 | 60 | coapNode.on('observe', function (msg) { 61 | console.log(msg); 62 | }); 63 | 64 | // coapNode.configure('leshan.eclipse.org', 5683); 65 | coapNode.configure('::1', 5683); 66 | 67 | coapNode.registerAllCfg(function (err, rsp) { 68 | console.log(rsp); 69 | }); 70 | 71 | // setInterval(function () { 72 | // so.read(3303, 0, 5702, function () {}); 73 | // }, 3000); 74 | 75 | // setTimeout(function () { 76 | // coapNode.register('127.0.0.1', 5683, function (err, rsp) { 77 | // console.log(rsp); 78 | // }); 79 | // }, 5000); 80 | 81 | // setTimeout(function () { 82 | // coapNode.register('127.0.0.1', 5683, function (err, rsp) { 83 | // console.log(rsp); 84 | // }); 85 | // }, 10000); 86 | 87 | // update test 88 | // setTimeout(function () { 89 | // coapNode.update({ lifetime: 12000 }, function (err, rsp) { 90 | // console.log(rsp); 91 | // }); 92 | // }, 15000); 93 | 94 | // // deregister test 95 | // setTimeout(function () { 96 | // coapNode.deregister(function (err, rsp) { 97 | // console.log(rsp); 98 | // }); 99 | // }, 20000); 100 | 101 | // setTimeout(function () { 102 | // coapNode.checkout(10, function (err, rsp) { 103 | // console.log(rsp); 104 | // }); 105 | // }, 5000); 106 | 107 | // setTimeout(function () { 108 | // coapNode.checkin(function (err, rsp) { 109 | // console.log(rsp); 110 | // }); 111 | // }, 15000); 112 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/coap-node.js'); 4 | -------------------------------------------------------------------------------- /lib/coap-node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var url = require('url'), 4 | net = require('net'), 5 | util = require('util'), 6 | EventEmitter = require('events').EventEmitter, 7 | SmartObject = require('smartobject'), 8 | _ = require('busyman'), 9 | coap = require('coap'), 10 | debug = require('debug')('coap-node'), 11 | logReq = require('debug')('coap-node:request'), 12 | logRsp = require('debug')('coap-node:response'); 13 | 14 | var reqHandler = require('./components/reqHandler'), 15 | cutils = require('./components/cutils'), 16 | helper = require('./components/helper'), 17 | CNST = require('./components/constants'), 18 | config = require('./config'), 19 | init = require('./init'); 20 | 21 | if (process.env.npm_lifecycle_event === 'test') { 22 | var network = { 23 | get_active_interface: function (cb) { 24 | setTimeout(function () { 25 | if (config.connectionType === 'udp6') { 26 | cb(null, { 27 | ip_address: '::1', 28 | gateway_ip: '::c0a8:0101', 29 | mac_address: '00:00:00:00:00:00' 30 | }); 31 | } else { 32 | cb(null, { 33 | ip_address: '127.0.0.1', 34 | gateway_ip: '192.168.1.1', 35 | mac_address: '00:00:00:00:00:00' 36 | }); 37 | } 38 | }, 100); 39 | } 40 | }; 41 | } else { 42 | var network = require('network'); 43 | } 44 | 45 | /**** Code Enumerations ****/ 46 | var TTYPE = CNST.TTYPE, 47 | TAG = CNST.TAG, 48 | ERR = CNST.ERR, 49 | RSP = CNST.RSP; 50 | 51 | /********************************************************* 52 | * CoapNode * 53 | *********************************************************/ 54 | function CoapNode (clientName, smartObj, devAttrs) { 55 | if (!_.isString(clientName)) 56 | throw new TypeError('clientName should be a string.'); 57 | 58 | if (!_.isObject(smartObj)) 59 | throw new TypeError('smartObj should be a instance of SmartObject Class.'); 60 | 61 | EventEmitter.call(this); 62 | 63 | devAttrs = devAttrs || {}; 64 | 65 | this.servers = {}; // CoAP Server 66 | this.serversIdTable = {}; // LwM2M Servers URI-iid table 67 | this.serversInfo = {}; // LwM2M Servers info 68 | /* 69 | shoutId: { 70 | shortServerId: 10, 71 | ip: '192.168.1.119', 72 | port: 5683, 73 | locationPath: '1a2b', 74 | registered: true, 75 | repAttrs: {}, 76 | reporters: {}, 77 | hbPacemaker = null, 78 | hbStream = { stream: null, port: null, finishCb: null }, 79 | lfsecs = 0 80 | } 81 | */ 82 | 83 | this.clientName = clientName; 84 | this.locationPath = 'unknown'; 85 | this.ip = 'unknown'; 86 | this.mac = 'unknown'; 87 | this.port = 'unknown'; 88 | this.version = devAttrs.version || '1.0'; 89 | this.lifetime = devAttrs.lifetime || 86400; 90 | this.bsServer = {}; 91 | /* 92 | { 93 | ip: '192.168.1.120', 94 | port: 5683, 95 | } 96 | */ 97 | 98 | this.objList = null; 99 | this.so = smartObj; 100 | this.autoReRegister = devAttrs.autoReRegister || true; 101 | 102 | this._bootstrapping = false; 103 | this._sleep = false; 104 | this._updater = null; 105 | this._socketServerChker = null; 106 | 107 | this._config = { 108 | reqTimeout: config.reqTimeout, 109 | heartbeatTime: config.heartbeatTime, 110 | serverChkTime: config.serverChkTime, 111 | connectionType: config.connectionType, 112 | defaultMinPeriod: config.defaultMinPeriod, 113 | defaultMaxPeriod: config.defaultMaxPeriod 114 | }; 115 | 116 | init.setupNode(this, devAttrs); 117 | } 118 | 119 | util.inherits(CoapNode, EventEmitter); 120 | 121 | CoapNode.prototype._updateNetInfo = function (callback) { 122 | var self = this; 123 | 124 | network.get_active_interface(function(err, obj) { 125 | if (err) { 126 | callback(err); 127 | } else { 128 | self.ip = obj.ip_address; 129 | self.mac = obj.mac_address; 130 | self.so.set('connMonitor', 0, 'ip', self.ip); 131 | self.so.set('connMonitor', 0, 'routeIp', obj.gateway_ip || 'unknown'); 132 | callback(null, { ip: obj.ip_address, mac: obj.mac_address, routeIp: obj.gateway_ip }); 133 | } 134 | }); 135 | }; 136 | 137 | /********************************************************* 138 | * resources function * 139 | *********************************************************/ 140 | CoapNode.prototype.getSmartObject = function () { 141 | return this.so; 142 | }; 143 | 144 | CoapNode.prototype._writeInst = function (oid, iid, value, callback) { 145 | var self = this, 146 | exist = this.so.has(oid, iid), 147 | okey = cutils.oidKey(oid), 148 | dump = {}, 149 | chkErr = null, 150 | count = _.keys(value).length; 151 | 152 | if (!exist) { 153 | callback(ERR.notfound, null); 154 | } else { 155 | _.forEach(value, function (rsc, rid) { 156 | if (!self.so.isWritable(oid, iid, rid) && oid != 'lwm2mSecurity' && oid != 'lwm2mServer') 157 | chkErr = chkErr || new Error('Resource is unwritable.'); 158 | }); 159 | 160 | if (chkErr) 161 | return callback(chkErr, TAG.unwritable); 162 | 163 | _.forEach(value, function (rsc, rid) { 164 | self.so.write(oid, iid, rid, rsc, function (err, data) { 165 | count -= 1; 166 | 167 | if (err) { // [TODO] reply to the original data? 168 | chkErr = chkErr || err; 169 | dump = data; 170 | count = 0; 171 | } else { 172 | dump[cutils.ridNumber(oid, rid)] = data; 173 | } 174 | 175 | if (count === 0 && _.isFunction(callback)) 176 | return callback(chkErr, dump); 177 | }); 178 | }); 179 | } 180 | }; 181 | 182 | CoapNode.prototype.execResrc = function (oid, iid, rid, argus, callback) { 183 | return this.so.exec(oid, iid, rid, argus, callback); 184 | }; 185 | 186 | CoapNode.prototype.createInst = function (oid, iid, resrcs) { 187 | return this.so.init(oid, iid, resrcs); 188 | }; 189 | 190 | CoapNode.prototype.deleteInst = function (oid, iid) { 191 | return this.so.remove(oid, iid); 192 | }; 193 | 194 | /********************************************************* 195 | * network function * 196 | *********************************************************/ 197 | CoapNode.prototype.configure = function (ip, port, opts) { // opts: { lifetime, pmax, pmin } 198 | if (!_.isString(ip)) 199 | throw new TypeError('ip should be a string.'); 200 | 201 | if ((!_.isString(port) && !_.isNumber(port)) || _.isNaN(port)) 202 | throw new TypeError('port should be a string or a number.'); 203 | 204 | if (net.isIPv6(ip)) 205 | ip = '[' + ip + ']'; 206 | 207 | var securityIid, 208 | serverIid, 209 | shortServerId, 210 | serversIdInfo = this.serversIdTable['coap://' + ip + ':' + port]; 211 | 212 | if (!opts) 213 | opts = {}; 214 | 215 | if (serversIdInfo) { 216 | securityIid = serversIdInfo.securityIid; 217 | serverIid = serversIdInfo.serverIid; 218 | shortServerId = serversIdInfo.shortServerId; 219 | } else { 220 | securityIid = this._idCounter('securityIid'); 221 | serverIid = this._idCounter('serverIid'); 222 | shortServerId = this._idCounter('shortServerId'); 223 | 224 | this.serversIdTable['coap://' + ip + ':' + port] = { 225 | securityIid: securityIid, 226 | serverIid: serverIid, 227 | shortServerId: shortServerId 228 | }; 229 | } 230 | 231 | this.so.init('lwm2mSecurity', securityIid, { 232 | lwm2mServerURI: 'coap://' + ip + ':' + port, 233 | bootstrapServer: false, 234 | securityMode: 3, 235 | pubKeyId: '', 236 | serverPubKeyId: '', 237 | secretKey: '', 238 | shortServerId: shortServerId 239 | }); 240 | 241 | this.so.init('lwm2mServer', serverIid, { 242 | shortServerId: shortServerId, 243 | lifetime: opts.lifetime || this.lifetime, 244 | defaultMinPeriod: opts.pmax || this._config.defaultMinPeriod, 245 | defaultMaxPeriod: opts.pmin || this._config.defaultMaxPeriod, 246 | notificationStoring: false, 247 | binding: 'U' 248 | }); 249 | 250 | return shortServerId; 251 | }; 252 | 253 | CoapNode.prototype.bootstrap = function (ip, port, callback) { 254 | if (!_.isString(ip)) 255 | throw new TypeError('ip should be a string.'); 256 | 257 | if ((!_.isString(port) && !_.isNumber(port)) || _.isNaN(port)) 258 | throw new TypeError('port should be a string or a number.'); 259 | 260 | var self = this, 261 | reqObj = { 262 | hostname: ip, 263 | port: port, 264 | pathname: '/bs', 265 | method: 'POST' 266 | }, 267 | agent = this._createAgent(), 268 | securityIid = this._idCounter('securityIid'), 269 | shortServerId = this._idCounter('shortServerId'), 270 | resetCount = 0, 271 | rsps = { status: null, data: [] }, 272 | msg; 273 | 274 | function setListenerStart(port, msg) { 275 | if (!agent._sock) { 276 | startListener(self, port, function (err) { 277 | if (err) { 278 | invokeCbNextTick(err, null, callback); 279 | } else { 280 | self._sleep = false; 281 | // [TODO] .on('_register', cb) 282 | invokeCbNextTick(null, msg, callback); 283 | } 284 | }); 285 | } else { 286 | if (resetCount < 10) { 287 | resetCount += 1; 288 | setTimeout(function () { 289 | return setListenerStart(port, msg); 290 | }, 10); 291 | } else { 292 | invokeCbNextTick(new Error('Socket can not be create.'), null, callback); 293 | } 294 | } 295 | } 296 | 297 | reqObj.query = 'ep=' + self.clientName; 298 | 299 | self.request(reqObj, agent, function (err, rsp) { 300 | if (err) { 301 | invokeCbNextTick(err, null, callback); 302 | } else { 303 | msg = { status: rsp.code }; 304 | if (rsp.code === RSP.changed) { 305 | self.bsServer = { 306 | ip: rsp.rsinfo.address, 307 | port: rsp.rsinfo.port, 308 | }; 309 | 310 | self.ip = rsp.outSocket.ip; 311 | self.port = rsp.outSocket.port; 312 | self._bootstrapping = true; 313 | setListenerStart(rsp.outSocket.port, msg); 314 | } else { 315 | invokeCbNextTick(null, msg, callback); 316 | } 317 | } 318 | }); 319 | }; 320 | 321 | CoapNode.prototype.registerAllCfg = function (callback) { 322 | var self = this, 323 | securityObjs = this.so.dumpSync('lwm2mSecurity'), 324 | serverObjs = this.so.dumpSync('lwm2mServer'), 325 | requestCount = 0, 326 | requestInfo = [], 327 | serverInfo, 328 | rsps = { status: null, data: [] }, 329 | chkErr; 330 | 331 | _.forEach(serverObjs, function (serverObj, iid) { 332 | _.forEach(securityObjs, function (securityObj, iid) { 333 | if (!_.isNil(serverObj.shortServerId) && !_.isNil(serverObj.shortServerId) && serverObj.shortServerId === securityObj.shortServerId && !securityObj.bootstrapServer) { 334 | requestInfo.push({ uri: securityObj.lwm2mServerURI, ssid: securityObj.shortServerId }); 335 | requestCount = requestCount + 1; 336 | } 337 | }); 338 | }); 339 | 340 | if (requestCount === 0) { 341 | invokeCbNextTick(new Error('Do not have any allowed configuration.'), null, callback); 342 | } else { 343 | _.forEach(requestInfo, function (info, key) { 344 | serverInfo = getServerUriInfo(info.uri); 345 | self._register(serverInfo.address, serverInfo.port, info.ssid, function (err, msg) { 346 | requestCount -= 1; 347 | if (err) { 348 | chkErr = chkErr || err; 349 | requestCount = 0; 350 | } else { 351 | if (msg.status === RSP.created) 352 | rsps.status = rsps.status || RSP.created; 353 | else 354 | rsps.status = msg.status; 355 | rsps.data.push({ shortServerId: info.ssid, status: msg.status }); 356 | } 357 | 358 | if (requestCount === 0) { 359 | if (chkErr) 360 | invokeCbNextTick(chkErr, null, callback); 361 | else 362 | invokeCbNextTick(null, rsps, callback); 363 | } 364 | }); 365 | }); 366 | } 367 | }; 368 | 369 | CoapNode.prototype.register = function (ip, port, opts, callback) { 370 | if (_.isFunction(opts)) { 371 | callback = opts; 372 | opts = undefined; 373 | } 374 | 375 | var ssid = this.configure(ip, port, opts); 376 | 377 | this._register(ip, port, ssid, callback); 378 | }; 379 | 380 | // [FIXME 'hb'] 381 | CoapNode.prototype._register = function (ip, port, ssid, callback) { 382 | if (!_.isString(ip)) 383 | throw new TypeError('ip should be a string.'); 384 | 385 | if ((!_.isString(port) && !_.isNumber(port)) || _.isNaN(port)) 386 | throw new TypeError('port should be a string or a number.'); 387 | 388 | var self = this, 389 | reqObj = { 390 | hostname: ip, 391 | port: port, 392 | pathname: '/rd', 393 | payload: helper.checkAndBuildObjList(this, false, { ct: 'application/json', hb: true }), 394 | method: 'POST', 395 | options: {'Content-Format': 'application/link-format'} 396 | }, 397 | agent = this._createAgent(), 398 | resetCount = 0, 399 | msg; 400 | 401 | function setListenerStart(port, msg) { 402 | if (!agent._sock) { 403 | startListener(self, port, function (err) { 404 | if (err) { 405 | invokeCbNextTick(err, null, callback); 406 | } else { 407 | self._sleep = false; 408 | invokeCbNextTick(null, msg, callback); 409 | self.emit('registered'); 410 | } 411 | }); 412 | } else { 413 | if (resetCount < 10) { 414 | resetCount += 1; 415 | setTimeout(function () { 416 | return setListenerStart(port, msg); 417 | }, 10); 418 | } else { 419 | invokeCbNextTick(new Error('Socket can not be create.'), null, callback); 420 | } 421 | } 422 | } 423 | 424 | // [TODO] server is exist 425 | this._updateNetInfo(function () { 426 | reqObj.query = 'ep=' + self.clientName + '<=' + self.lifetime + '&lwm2m=' + self.version + '&mac=' + self.mac; 427 | self.request(reqObj, agent, function (err, rsp) { 428 | if (err) { 429 | invokeCbNextTick(err, null, callback); 430 | } else { 431 | msg = { status: rsp.code }; 432 | 433 | if (rsp.code === RSP.created) { 434 | self.serversInfo[ssid] = { 435 | shortServerId: ssid, 436 | ip: rsp.rsinfo.address, 437 | port: rsp.rsinfo.port, 438 | locationPath: '/rd/' + rsp.headers['Location-Path'], 439 | registered: true, 440 | lfsecs: 0, 441 | repAttrs: {}, 442 | reporters: {}, 443 | hbPacemaker: null, 444 | hbStream: { stream: null, port: null, finishCb: null } 445 | }; 446 | 447 | self.ip = rsp.outSocket.ip; 448 | self.port = rsp.outSocket.port; 449 | setListenerStart(rsp.outSocket.port, msg); 450 | } else { 451 | invokeCbNextTick(null, msg, callback); 452 | } 453 | } 454 | }); 455 | }); 456 | }; 457 | 458 | CoapNode.prototype.update = function (attrs, callback) { 459 | if (!_.isPlainObject(attrs)) 460 | throw new TypeError('attrs should be an object.'); 461 | 462 | var self = this, 463 | requestCount = Object.keys(this.serversInfo).length, 464 | rsps = { status: null, data: [] }, 465 | updateObj = {}, 466 | objListInPlain, 467 | chkErr; 468 | 469 | _.forEach(attrs, function (val, key) { 470 | if (key === 'lifetime') { 471 | // self.so.set('lwm2mServer', 0, 'lifetime', attrs.lifetime); // [TODO] need to check / multi server 472 | if (attrs.lifetime !== self.lifetime) { 473 | self.lifetime = updateObj.lifetime = attrs.lifetime; 474 | } else { 475 | chkErr = new Error('The given lifetime is the same as cnode current lifetime.'); 476 | } 477 | } else { 478 | chkErr = new Error('There is an unrecognized attribute within the attrs.'); 479 | } 480 | }); 481 | 482 | objListInPlain = helper.checkAndBuildObjList(self, true); 483 | 484 | if (!_.isNil(objListInPlain)) 485 | updateObj.objList = objListInPlain; 486 | 487 | if (chkErr) { 488 | invokeCbNextTick(chkErr, null, callback); 489 | } else { 490 | _.forEach(this.serversInfo, function (serverInfo, ssid) { 491 | self._update(serverInfo, updateObj, function (err, msg) { 492 | requestCount -= 1; 493 | if (err) { 494 | chkErr = chkErr || err; 495 | requestCount = 0; 496 | } else { 497 | if (msg.status === RSP.changed) 498 | rsps.status = rsps.status || RSP.changed; 499 | else 500 | rsps.status = msg.status; 501 | rsps.data.push({ shortServerId: ssid, status: msg.status }); 502 | } 503 | 504 | if (requestCount === 0) { 505 | if (chkErr) 506 | invokeCbNextTick(chkErr, null, callback); 507 | else 508 | invokeCbNextTick(null, rsps, callback); 509 | } 510 | }); 511 | }); 512 | } 513 | }; 514 | 515 | CoapNode.prototype._update = function (serverInfo, attrs, callback) { 516 | if (!_.isPlainObject(attrs)) 517 | throw new TypeError('attrs should be an object.'); 518 | 519 | var self = this, 520 | reqObj = { 521 | hostname: serverInfo.ip, 522 | port: serverInfo.port, 523 | pathname: serverInfo.locationPath, 524 | query: cutils.buildUpdateQuery(attrs), 525 | payload: attrs.objList, 526 | method: 'POST' 527 | }, 528 | agent = this._createAgent(), 529 | resetCount = 0, 530 | msg; 531 | 532 | function setListenerStart(port, msg) { 533 | if (!agent._sock) { 534 | startListener(self, port, function (err) { 535 | if (err) { 536 | invokeCbNextTick(err, null, callback); 537 | } else { 538 | self._sleep = false; 539 | invokeCbNextTick(null, msg, callback); 540 | } 541 | }); 542 | } else { 543 | if (resetCount < 10) { 544 | resetCount += 1; 545 | setTimeout(function () { 546 | setListenerStart(port, msg); 547 | }, 10); 548 | } else { 549 | invokeCbNextTick(new Error('Socket can not be create.'), null, callback); 550 | } 551 | } 552 | } 553 | 554 | if (attrs.objList) 555 | reqObj.options = {'Content-Format': 'application/link-format'}; 556 | 557 | if (serverInfo.registered) { 558 | this.request(reqObj, agent, function (err, rsp) { 559 | if (err) { 560 | invokeCbNextTick(err, null, callback); 561 | } else { 562 | msg = { status: rsp.code }; 563 | 564 | if (rsp.code === RSP.changed) { 565 | self.ip = rsp.outSocket.address; 566 | self.port = rsp.outSocket.port; 567 | setListenerStart(rsp.outSocket.port, msg); 568 | } else { 569 | invokeCbNextTick(null, msg, callback); 570 | } 571 | } 572 | }); 573 | } else { 574 | invokeCbNextTick( null, { status: RSP.notfound }, callback); 575 | } 576 | }; 577 | 578 | CoapNode.prototype.deregister = function (ssid, callback) { 579 | var self = this, 580 | requestCount = Object.keys(this.serversInfo).length, 581 | rsps = { status: null, data: [] }, 582 | chkErr; 583 | 584 | function deregister(serverInfo, cb) { 585 | var reqObj = { 586 | hostname: serverInfo.ip, 587 | port: serverInfo.port, 588 | pathname: serverInfo.locationPath, 589 | method: 'DELETE' 590 | }; 591 | 592 | if (serverInfo.registered === true) { 593 | self.request(reqObj, function (err, rsp) { 594 | if (err) { 595 | invokeCbNextTick(err, null, cb); 596 | } else { 597 | var msg = { status: rsp.code }; 598 | 599 | if (rsp.code === RSP.deleted) { 600 | self._disableAllReport(serverInfo.shortServerId); 601 | 602 | // [TODO] 603 | // _.forEach(self.servers, function (server, key) { 604 | // server.close(); 605 | // }); 606 | 607 | serverInfo.registered = false; 608 | self.emit('deregistered'); 609 | } 610 | 611 | invokeCbNextTick(null, msg, cb); 612 | } 613 | }); 614 | } else { 615 | invokeCbNextTick(null, { status: RSP.notfound }, cb); 616 | } 617 | } 618 | 619 | if (_.isFunction(ssid)) { 620 | callback = ssid; 621 | ssid = undefined; 622 | } 623 | 624 | if (_.isNil(ssid)) { 625 | _.forEach(this.serversInfo, function (serverInfo, ssid) { 626 | deregister(serverInfo, function (err, msg) { 627 | requestCount -= 1; 628 | if (err) { 629 | chkErr = chkErr || err; 630 | requestCount = 0; 631 | } else { 632 | if (msg.status === RSP.deleted) 633 | rsps.status = rsps.status || RSP.deleted; 634 | else 635 | rsps.status = msg.status; 636 | rsps.data.push({ shortServerId: ssid, status: msg.status }); 637 | } 638 | 639 | if (requestCount === 0) { 640 | if (chkErr) 641 | invokeCbNextTick(chkErr, null, callback); 642 | else 643 | invokeCbNextTick(null, rsps, callback); 644 | } 645 | }); 646 | }); 647 | } else if (this.serversInfo[ssid]) { 648 | deregister(this.serversInfo[ssid], callback); 649 | } else { 650 | invokeCbNextTick(null, { status: RSP.notfound }, callback); 651 | } 652 | }; 653 | 654 | CoapNode.prototype.checkin = function (callback) { 655 | var self = this, 656 | agent = this._createAgent(), 657 | requestCount = Object.keys(this.serversInfo).length, 658 | resetCount = 0, 659 | rsps = { status: null, data: [] }, 660 | chkErr; 661 | 662 | function setListenerStart(port, msg, cb) { 663 | if (!agent._sock) { 664 | startListener(self, port, function (err) { 665 | if (err) { 666 | invokeCbNextTick(err, null, cb); 667 | } else { 668 | invokeCbNextTick(null, msg, cb); 669 | } 670 | }); 671 | } else { 672 | if (resetCount < 10) { 673 | resetCount += 1; 674 | setTimeout(function () { 675 | setListenerStart(port, msg, cb); 676 | }, 10); 677 | } else { 678 | invokeCbNextTick(new Error('Socket can not be create.'), null, cb); 679 | } 680 | } 681 | } 682 | 683 | function checkin(serverInfo, cb) { 684 | var reqObj = { 685 | hostname: serverInfo.ip, 686 | port: serverInfo.port, 687 | pathname: serverInfo.locationPath, 688 | query: 'chk=in', 689 | method: 'PUT' 690 | }; 691 | 692 | if (serverInfo.registered === true) { 693 | self.request(reqObj, agent, function (err, rsp) { 694 | if (err) { 695 | invokeCbNextTick(err, null, cb); 696 | } else { 697 | var msg = { status: rsp.code }; 698 | 699 | if (rsp.code === RSP.changed) { 700 | self.ip = rsp.outSocket.address; 701 | self.port = rsp.outSocket.port; 702 | self._sleep = false; 703 | setListenerStart(rsp.outSocket.port, msg, cb); 704 | } else { 705 | invokeCbNextTick(null, msg, cb); 706 | } 707 | } 708 | }); 709 | } else { 710 | invokeCbNextTick(null, { status: RSP.notfound }, cb); 711 | } 712 | } 713 | 714 | _.forEach(this.serversInfo, function (serverInfo, ssid) { 715 | checkin(serverInfo, function (err, msg) { 716 | requestCount -= 1; 717 | if (err) { 718 | chkErr = chkErr || err; 719 | requestCount = 0; 720 | } else { 721 | if (msg.status === RSP.changed) 722 | rsps.status = rsps.status || RSP.changed; 723 | else 724 | rsps.status = msg.status; 725 | rsps.data.push({ shortServerId: ssid, status: msg.status }); 726 | } 727 | 728 | if (requestCount === 0) { 729 | if (chkErr) 730 | invokeCbNextTick(chkErr, null, callback); 731 | else 732 | invokeCbNextTick(null, rsps, callback); 733 | } 734 | }); 735 | }); 736 | }; 737 | 738 | CoapNode.prototype.checkout = function (duration, callback) { 739 | var self = this, 740 | requestCount = Object.keys(this.serversInfo).length, 741 | rsps = { status: null, data: [] }, 742 | chkErr; 743 | 744 | if (_.isFunction(duration)) { 745 | callback = duration; 746 | duration = undefined; 747 | } 748 | 749 | if (!_.isUndefined(duration) && (!_.isNumber(duration) || _.isNaN(duration))) 750 | throw new TypeError('duration should be a number if given.'); 751 | else if (!_.isUndefined(callback) && !_.isFunction(callback)) 752 | throw new TypeError('callback should be a function if given.'); 753 | 754 | function checkout(serverInfo, cb) { 755 | var reqObj = { 756 | hostname: serverInfo.ip, 757 | port: serverInfo.port, 758 | pathname: serverInfo.locationPath, 759 | query: duration ? 'chk=out&t=' + duration : 'chk=out', 760 | method: 'PUT' 761 | }; 762 | 763 | if (serverInfo.registered === true) { 764 | self.request(reqObj, function (err, rsp) { 765 | if (err) { 766 | invokeCbNextTick(err, null, cb); 767 | } else { 768 | var msg = { status: rsp.code }; 769 | 770 | if (rsp.code === RSP.changed) { 771 | self._disableAllReport(serverInfo.shortServerId); 772 | self._sleep = true; 773 | _.forEach(self.servers, function (server, key) { 774 | server.close(); 775 | }); 776 | } 777 | 778 | invokeCbNextTick(null, msg, cb); 779 | } 780 | }); 781 | } else { 782 | invokeCbNextTick(null, { status: RSP.notfound }, cb); 783 | } 784 | } 785 | 786 | _.forEach(this.serversInfo, function (serverInfo, ssid) { 787 | checkout(serverInfo, function (err, msg) { 788 | requestCount = requestCount - 1; 789 | if (err) { 790 | chkErr = chkErr || err; 791 | requestCount = 0; 792 | } else { 793 | if (msg.status === RSP.changed) 794 | rsps.status = rsps.status || RSP.changed; 795 | else 796 | rsps.status = msg.status; 797 | rsps.data.push({ shortServerId: ssid, status: msg.status }); 798 | } 799 | 800 | if (requestCount === 0) { 801 | if (chkErr) 802 | invokeCbNextTick(chkErr, null, callback); 803 | else 804 | invokeCbNextTick(null, rsps, callback); 805 | } 806 | }); 807 | }); 808 | }; 809 | 810 | CoapNode.prototype.lookup = function (ssid, clientName, callback) { 811 | var serverInfo = this.serversInfo[ssid], 812 | reqObj = { 813 | hostname: serverInfo.ip, 814 | port: serverInfo.port, 815 | pathname: '/rd-lookup/ep', 816 | query: 'ep=' + clientName, 817 | method: 'GET' 818 | }; 819 | 820 | this.request(reqObj, function (err, rsp) { 821 | if (err) { 822 | invokeCbNextTick(err, null, callback); 823 | } else { 824 | var msg = { status: rsp.code }; 825 | 826 | if (rsp.code === RSP.content) { 827 | msg.data = rsp.payload; 828 | } 829 | 830 | invokeCbNextTick(null, msg, callback); 831 | } 832 | }); 833 | }; 834 | 835 | CoapNode.prototype.request = function (reqObj, ownAgent, callback) { 836 | if (!_.isPlainObject(reqObj)) 837 | throw new TypeError('reqObj should be an object.'); 838 | 839 | if (_.isFunction(ownAgent)) { 840 | callback = ownAgent; 841 | ownAgent = undefined; 842 | } 843 | 844 | var self = this, 845 | agent = ownAgent || new coap.Agent({ type: this._config.connectionType }), 846 | req = agent.request(reqObj); 847 | 848 | req.on('response', function(rsp) { 849 | if (!_.isEmpty(rsp.payload)) 850 | rsp.payload = rsp.payload.toString(); 851 | 852 | if (_.isFunction(callback)) 853 | callback(null, rsp); 854 | }); 855 | 856 | req.on('error', function(err) { 857 | if (!_.isFunction(callback)) 858 | self.emit('error', err); 859 | else if (err.retransmitTimeout) 860 | callback(null, { code: RSP.timeout }); 861 | else 862 | callback(err); 863 | }); 864 | 865 | req.end(reqObj.payload); 866 | }; 867 | 868 | /********************************************************* 869 | * protect function * 870 | *********************************************************/ 871 | CoapNode.prototype._createAgent = function () { 872 | return new coap.Agent({ type: this._config.connectionType }); 873 | }; 874 | 875 | CoapNode.prototype._target = function (oid, iid, rid) { 876 | var okey = cutils.oidKey(oid), 877 | trg = { 878 | type: null, 879 | exist: this.so.has(oid, iid, rid), 880 | value: null, 881 | pathKey: null, 882 | oidKey: okey, 883 | ridKey: null, 884 | }, 885 | rkey; 886 | 887 | if (!_.isNil(oid)) { 888 | trg.type = TTYPE.obj; 889 | trg.pathKey = okey; 890 | if (!_.isNil(iid)) { 891 | trg.type = TTYPE.inst; 892 | trg.pathKey = okey + '/' + iid; 893 | if (!_.isNil(rid)) { 894 | trg.type = TTYPE.rsc; 895 | rkey = cutils.ridKey(oid, rid); 896 | trg.ridKey = rkey; 897 | trg.pathKey = okey + '/' + iid + '/' + rkey; 898 | } 899 | } 900 | } 901 | 902 | if (trg.exist) { 903 | if (trg.type === TTYPE.obj) 904 | trg.value = this.so.findObject(oid); 905 | else if (trg.type === TTYPE.inst) 906 | trg.value = this.so.findObjectInstance(oid, iid); 907 | else if (trg.type === TTYPE.rsc) 908 | trg.value = this.so.get(oid, iid, rid); 909 | } 910 | 911 | return trg; 912 | }; 913 | 914 | CoapNode.prototype._setAttrs = function (ssid, oid, iid, rid, attrs) { 915 | if (!_.isPlainObject(attrs)) 916 | throw new TypeError('attrs should be given as an object.'); 917 | 918 | var target = this._target(oid, iid, rid), 919 | rAttrs = this._getAttrs(ssid, oid, iid, rid), 920 | key = target.pathKey; 921 | 922 | rAttrs.pmin = _.isNumber(attrs.pmin) ? attrs.pmin : rAttrs.pmin; 923 | rAttrs.pmax = _.isNumber(attrs.pmax) ? attrs.pmax : rAttrs.pmax; 924 | rAttrs.gt = _.isNumber(attrs.gt) ? attrs.gt : rAttrs.gt; 925 | rAttrs.lt = _.isNumber(attrs.lt) ? attrs.lt : rAttrs.lt; 926 | rAttrs.stp = _.isNumber(attrs.stp) ? attrs.stp : rAttrs.stp; 927 | 928 | return this; 929 | }; 930 | 931 | CoapNode.prototype._getAttrs = function (ssid, oid, iid, rid) { 932 | var serverInfo = this.serversInfo[ssid], 933 | key = this._target(oid, iid, rid).pathKey, 934 | defaultAttrs; 935 | 936 | defaultAttrs = { 937 | pmin: this._config.defaultMinPeriod, // [TODO] need to check 938 | pmax: this._config.defaultMaxPeriod, // [TODO] need to check 939 | mute: true, 940 | enable: false, 941 | lastRpVal: null 942 | }; 943 | 944 | serverInfo.repAttrs[key] = serverInfo.repAttrs[key] || defaultAttrs; 945 | 946 | return serverInfo.repAttrs[key]; 947 | }; 948 | 949 | CoapNode.prototype._enableReport = function (ssid, oid, iid, rid, format, rsp, callback) { 950 | var self = this, 951 | serverInfo = this.serversInfo[ssid], 952 | target = this._target(oid, iid, rid), 953 | key = target.pathKey, 954 | rAttrs = this._getAttrs(ssid, oid, iid, rid), 955 | pmin = rAttrs.pmin * 1000, 956 | pmax = rAttrs.pmax * 1000, 957 | rpt, 958 | dumper; 959 | 960 | if (target.type === TTYPE.inst) { 961 | dumper = function (cb) { 962 | self.so.dump(oid, iid, cb); 963 | }; 964 | } else if (target.type === TTYPE.rsc) { 965 | dumper = function (cb) { 966 | self.so.read(oid, iid, rid, cb); 967 | }; 968 | } 969 | 970 | function reporterMin() { 971 | rAttrs.mute = false; 972 | } 973 | 974 | function reporterMax() { 975 | dumper(function (err, val) { 976 | return err ? self.emit('error', err) : rpt.write(val); 977 | }); 978 | } 979 | 980 | function reporterWrite(val) { 981 | rAttrs.mute = true; 982 | 983 | if (_.isObject(val)) { 984 | _.forEach(val, function (val, rid) { 985 | rAttrs.lastRpVal[rid] = val; 986 | }); 987 | } else { 988 | rAttrs.lastRpVal = val; 989 | } 990 | 991 | if (format === 'text/plain') { 992 | if (_.isBoolean(val)) 993 | val = val ? '1' : '0'; 994 | else 995 | val = val.toString(); 996 | } else if (format === 'application/json') { 997 | val = cutils.encodeJson(key, val); 998 | } else { 999 | val = cutils.encodeTlv(key, val); 1000 | } 1001 | 1002 | try { 1003 | rsp.write(val); 1004 | } catch (e) { 1005 | self.emit('error', e); 1006 | } 1007 | 1008 | if (!_.isNil(rpt.min)) 1009 | clearTimeout(rpt.min); 1010 | 1011 | if (!_.isNil(rpt.max)) 1012 | clearInterval(rpt.max); 1013 | 1014 | rpt.min = setTimeout(reporterMin, pmin); 1015 | rpt.max = setInterval(reporterMax, pmax); 1016 | } 1017 | 1018 | function finishHdlr() { 1019 | removeReporter(self, ssid, oid, iid, rid); 1020 | } 1021 | 1022 | dumper(function (err, data) { 1023 | if (!err && data !== TAG.unreadable && data !== TAG.exec) { 1024 | rAttrs.mute = false; 1025 | rAttrs.enable = true; 1026 | rAttrs.lastRpVal = data; 1027 | 1028 | rsp.once('finish', finishHdlr); 1029 | 1030 | rpt = serverInfo.reporters[key] = { 1031 | min: setTimeout(reporterMin, pmin), 1032 | max: setInterval(reporterMax, pmax), 1033 | write: reporterWrite, 1034 | stream: rsp, 1035 | port: self.port, 1036 | finishHdlr: finishHdlr 1037 | }; 1038 | } 1039 | 1040 | if (_.isFunction(callback)) 1041 | callback(err, data); 1042 | }); 1043 | }; 1044 | 1045 | CoapNode.prototype._disableReport = function (ssid, oid, iid, rid, callback) { 1046 | var serverInfo = this.serversInfo[ssid], 1047 | key = this._target(oid, iid, rid).pathKey, 1048 | rpt, 1049 | chkErr; 1050 | 1051 | if (serverInfo) 1052 | rpt = serverInfo.reporters[key]; 1053 | 1054 | if (rpt) { 1055 | rpt.stream.removeListener('finish', rpt.finishHdlr); 1056 | rpt.stream.end(); 1057 | removeReporter(this, ssid, oid, iid, rid); 1058 | chkErr = ERR.success; 1059 | } else { 1060 | chkErr = ERR.notfound; 1061 | } 1062 | 1063 | if (_.isFunction(callback)) 1064 | callback(chkErr, null); 1065 | }; 1066 | 1067 | CoapNode.prototype._disableAllReport = function (ssid) { 1068 | var self = this; 1069 | 1070 | function disableReport(serverInfo) { 1071 | helper.heartbeat(self, serverInfo.shortServerId, false); 1072 | 1073 | _.forEach(serverInfo.reporters, function (rpt, key) { 1074 | var oid = key.split('/')[0], 1075 | iid = key.split('/')[1], 1076 | rid = key.split('/')[2]; 1077 | 1078 | self._disableReport(serverInfo.shortServerId, oid, iid, rid, function (err, result) { 1079 | if (err) 1080 | self.emit('error', err); 1081 | }); 1082 | }); 1083 | } 1084 | 1085 | if (_.isNil(ssid)) { 1086 | _.forEach(this.serversInfo, function (serverInfo, ssid) { 1087 | disableReport(self, serverInfo, function (err, rsp) { 1088 | // [TODO] 1089 | }); 1090 | }); 1091 | } else if (this.serversInfo[ssid]) { 1092 | disableReport(this.serversInfo[ssid]); 1093 | } 1094 | }; 1095 | 1096 | CoapNode.prototype._idCounter = function (type) { 1097 | var id, 1098 | idUsed; 1099 | 1100 | switch (type) { 1101 | case 'securityIid': 1102 | id = 1; 1103 | while (this.so.has('lwm2mSecurity', id)) { 1104 | id = id + 1; 1105 | } 1106 | break; 1107 | 1108 | case 'serverIid': 1109 | id = 1; 1110 | while (this.so.has('lwm2mServer', id)) { 1111 | id = id + 1; 1112 | } 1113 | break; 1114 | 1115 | case 'shortServerId': 1116 | id = 0; 1117 | do { 1118 | id = id + 1; 1119 | idUsed = false; 1120 | _.forEach(this.so.dumpSync('lwm2mSecurity'), function (iObj, iid) { 1121 | if (iObj.shortServerId === id) 1122 | idUsed = true; 1123 | }); 1124 | } while(idUsed); 1125 | break; 1126 | 1127 | default: 1128 | break; 1129 | } 1130 | 1131 | return id; 1132 | }; 1133 | 1134 | /********************************************************* 1135 | * Private function * 1136 | *********************************************************/ 1137 | function startListener(cn, port, callback) { 1138 | var server; 1139 | 1140 | server = coap.createServer({ 1141 | type: cn._config.connectionType, 1142 | proxy: true 1143 | }); 1144 | 1145 | cn.servers[port] = server; 1146 | 1147 | server.on('request', function (req, rsp) { 1148 | if (!_.isEmpty(req.payload) && req.headers['Content-Format'] === 'application/json') { 1149 | req.payload = JSON.parse(req.payload); 1150 | } else if (!_.isEmpty(req.payload) && req.headers['Content-Format'] === 'application/tlv') { 1151 | req.payload = req.payload; 1152 | } else if (!_.isEmpty(req.payload)) { 1153 | req.payload = req.payload.toString(); 1154 | } 1155 | 1156 | reqHandler(cn, req, rsp); 1157 | }); 1158 | 1159 | try { 1160 | server.listen(port, function (err) { 1161 | if (err) { 1162 | if (_.isFunction(callback)) 1163 | callback(err); 1164 | else 1165 | cn.emit('error', err); 1166 | } else { 1167 | callback(null, server); 1168 | } 1169 | }); 1170 | } catch (e) { 1171 | callback(e); 1172 | } 1173 | } 1174 | 1175 | function removeReporter(cn, ssid, oid, iid, rid) { 1176 | var serverInfo = cn.serversInfo[ssid], 1177 | key = cn._target(oid, iid, rid).pathKey, 1178 | rAttrs = cn._getAttrs(ssid, oid, iid, rid), 1179 | rpt; 1180 | 1181 | if (!serverInfo) 1182 | return; 1183 | 1184 | rpt = serverInfo.reporters[key]; 1185 | 1186 | if (rpt) { 1187 | clearTimeout(rpt.min); 1188 | clearInterval(rpt.max); 1189 | rpt.min = null; 1190 | rpt.max = null; 1191 | rpt.write = null; 1192 | rpt.stream = null; 1193 | rpt.port = null; 1194 | delete serverInfo.reporters[key]; 1195 | } 1196 | 1197 | rAttrs.enable = false; 1198 | rAttrs.mute = true; 1199 | } 1200 | 1201 | function getServerUriInfo(uri) { 1202 | var uriInfo = url.parse(uri), 1203 | info = { 1204 | address: uriInfo.hostname, 1205 | port: uriInfo.port 1206 | }; 1207 | 1208 | return info; 1209 | } 1210 | 1211 | function invokeCbNextTick(err, val, cb) { 1212 | if (_.isFunction(cb)) 1213 | process.nextTick(function () { 1214 | cb(err, val); 1215 | }); 1216 | } 1217 | 1218 | /********************************************************* 1219 | * Module Exports * 1220 | *********************************************************/ 1221 | module.exports = CoapNode; 1222 | -------------------------------------------------------------------------------- /lib/components/constants.js: -------------------------------------------------------------------------------- 1 | var CONSTANTS = { 2 | TTYPE: { 3 | root: 0, 4 | obj: 1, 5 | inst: 2, 6 | rsc: 3 7 | }, 8 | TAG: { 9 | notfound: '_notfound_', 10 | unreadable: '_unreadable_', 11 | exec: '_exec_', 12 | unwritable: '_unwritable_', 13 | unexecutable: '_unexecutable_' 14 | }, 15 | ERR: { 16 | success: 0, 17 | notfound: 1, 18 | unreadable: 2, 19 | unwritable: 3, 20 | unexecutable: 4, 21 | timeout: 5, 22 | badtype: 6 23 | }, 24 | RSP: { 25 | created: '2.01', 26 | deleted: '2.02', 27 | changed: '2.04', 28 | content: '2.05', 29 | badreq: '4.00', 30 | unauth: '4.01', 31 | forbid: '4.03', 32 | notfound: '4.04', 33 | notallowed: '4.05', 34 | timeout: '4.08', 35 | serverError: '5.00' 36 | } 37 | }; 38 | 39 | module.exports = CONSTANTS; 40 | -------------------------------------------------------------------------------- /lib/components/cutils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var urlParser = require('url').parse, 4 | lwm2mId = require('lwm2m-id'), 5 | lwm2mCodec = require('lwm2m-codec'), 6 | _ = require('busyman'); 7 | 8 | var cutils = {}; 9 | 10 | cutils.getTime = function () { 11 | return Math.round(new Date().getTime()/1000); 12 | }; 13 | 14 | /********************************************************* 15 | * lwm2m-id utils * 16 | *********************************************************/ 17 | cutils.oidKey = function (oid) { 18 | var oidItem = lwm2mId.getOid(oid); 19 | return oidItem ? oidItem.key : oid; 20 | }; 21 | 22 | cutils.oidNumber = function (oid) { 23 | var oidItem = lwm2mId.getOid(oid); 24 | 25 | oidItem = oidItem ? oidItem.value : parseInt(oid); 26 | 27 | if (_.isNaN(oidItem)) 28 | oidItem = oid; 29 | 30 | return oidItem; 31 | }; 32 | 33 | cutils.ridKey = function (oid, rid) { 34 | var ridItem = lwm2mId.getRid(oid, rid); 35 | 36 | if (_.isUndefined(rid)) 37 | rid = oid; 38 | 39 | return ridItem ? ridItem.key : rid; 40 | }; 41 | 42 | cutils.ridNumber = function (oid, rid) { 43 | var ridItem = lwm2mId.getRid(oid, rid); 44 | 45 | if (_.isUndefined(rid)) 46 | rid = oid; 47 | 48 | ridItem = ridItem ? ridItem.value : parseInt(rid); 49 | 50 | if (_.isNaN(ridItem)) 51 | ridItem = rid; 52 | 53 | return ridItem; 54 | }; 55 | 56 | /********************************************************* 57 | * path utils * 58 | *********************************************************/ 59 | cutils.buildRptAttr = function (req) { // 'pmin=10&pmax=60' 60 | var allowedAttrs = [ 'pmin', 'pmax', 'gt', 'lt', 'stp' ], 61 | attrs = {}, 62 | query = urlParser(req.url).query, 63 | queryParams = query.split('&'); 64 | 65 | _.forEach(queryParams, function (queryParam, idx) { 66 | queryParams[idx] = queryParam.split('='); // [[ pmin, 10 ], [ pmax, 60 ]] 67 | }); 68 | 69 | _.forEach(queryParams, function(queryParam) { 70 | if (_.includes(allowedAttrs, queryParam[0])) { 71 | attrs[queryParam[0]] = Number(queryParam[1]); 72 | } else { 73 | return false; 74 | } 75 | }); 76 | 77 | return attrs; // { pmin: 10, pmax:60 } 78 | }; 79 | 80 | cutils.buildUpdateQuery = function (attrs) { // { lifetime: 81000, version: 'v0.1.2' } 81 | var query = ''; 82 | 83 | _.forEach(attrs, function (val, key) { 84 | if (key === 'lifetime' || key === 'lt') 85 | query += 'lt=' + val + '&'; 86 | else if (key === 'version' || key === 'lwm2m') 87 | query += 'lwm2m=' + val + '&'; 88 | }); 89 | 90 | if (query[query.length-1] === '&') 91 | query = query.slice(0, query.length-1); 92 | 93 | return query; // 'lt=81000&lwm2m=v0.1.2' 94 | }; 95 | 96 | cutils.getArrayArgus = function (argusInPlain) { // 10,15,'xx' 97 | var argusInArray = [], 98 | notallowed = [' ', '"', "'", '\\'], 99 | isAnyNotallowed = false; 100 | 101 | function chkCharSyntax(string) { 102 | _.forEach(notallowed, function (val) { 103 | if (_.includes(string, val)) 104 | isAnyNotallowed = true; 105 | }); 106 | } 107 | 108 | if (argusInPlain.length === 0) 109 | return []; 110 | 111 | if (Number(argusInPlain)) 112 | argusInPlain = argusInPlain.toString(); 113 | 114 | _.forEach(argusInPlain.split(','), function (argu) { 115 | if (Number(argu)) { 116 | argusInArray.push(Number(argu)); 117 | } else if (_.includes(argu, '=')) { 118 | argusInArray.push(argu.split('=')[1].slice(1, argu.length - 1)); 119 | chkCharSyntax(argusInArray[argusInArray.length - 1]); 120 | } else { 121 | argusInArray.push(argu.slice(1, argu.length - 1)); 122 | chkCharSyntax(argusInArray[argusInArray.length - 1]); 123 | } 124 | }); 125 | 126 | if (isAnyNotallowed) 127 | return false; 128 | else 129 | return argusInArray; // [10, 15, 'xx'] 130 | }; 131 | 132 | /********************************************************* 133 | * path utils * 134 | *********************************************************/ 135 | cutils.chkPathSlash = function (path) { 136 | if (path.charAt(0) === '/') { 137 | return path; 138 | } else { 139 | return '/' + path; 140 | } 141 | }; 142 | 143 | cutils.urlParser = function (url) { 144 | var urlObj = { 145 | pathname: url.split('?')[0], 146 | query: url.split('?')[1] 147 | }; 148 | 149 | return urlObj; 150 | }; 151 | 152 | cutils.getPathArray = function (url) { 153 | var path = urlParser(url).pathname, 154 | pathArray = path.split('/'); // '/x/y/z' 155 | 156 | if (pathArray[0] === '') 157 | pathArray = pathArray.slice(1); 158 | 159 | if (pathArray[pathArray.length-1] === '') 160 | pathArray = pathArray.slice(0, pathArray.length-1); 161 | 162 | return pathArray; // ['x', 'y', 'z'] 163 | }; 164 | 165 | cutils.getPathIdKey = function (url) { 166 | var pathArray = this.getPathArray(url), // '/1/2/3' 167 | pathObj = {}, 168 | oid, 169 | rid; 170 | 171 | if (url) { 172 | if (pathArray[0]) { //oid 173 | oid = this.oidKey(pathArray[0]); 174 | pathObj.oid = oid; 175 | 176 | if (pathArray[1]) { //iid 177 | pathObj.iid = + pathArray[1]; 178 | 179 | if (pathArray[2]) { //rid 180 | rid = this.ridKey(oid, pathArray[2]); 181 | pathObj.rid = rid; 182 | } 183 | } 184 | } 185 | } 186 | 187 | return pathObj; // {oid:'lwm2mServer', iid: '2', rid: 'defaultMaxPeriod'} 188 | }; 189 | 190 | cutils.getPathDateType = function (path) { 191 | var pathArray = this.getPathArray(path), 192 | dateType = [ 'so', 'object', 'instance', 'resource' ][pathArray.length]; 193 | return dateType; 194 | }; 195 | 196 | /********************************************************* 197 | * Link utils * 198 | *********************************************************/ 199 | cutils.encodeLinkFormat = function (path, value, attrs) { 200 | return lwm2mCodec.encode('link', path, value, attrs); 201 | }; 202 | /********************************************************* 203 | * TLV utils * 204 | *********************************************************/ 205 | cutils.encodeTlv = function (path, value) { 206 | return lwm2mCodec.encode('tlv', path, value); 207 | }; 208 | 209 | cutils.decodeTlv = function (path, value) { 210 | return lwm2mCodec.decode('tlv', path, value); 211 | }; 212 | 213 | /********************************************************* 214 | * JSON utils * 215 | *********************************************************/ 216 | cutils.encodeJson = function (path, value) { 217 | return lwm2mCodec.encode('json', path, value); 218 | }; 219 | 220 | cutils.decodeJson = function (path, value) { 221 | return lwm2mCodec.decode('json', path, value); 222 | }; 223 | 224 | module.exports = cutils; 225 | -------------------------------------------------------------------------------- /lib/components/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('busyman'); 4 | 5 | var cutils = require('./cutils'), 6 | CNST = require('./constants'); 7 | 8 | /**** Code Enumerations ****/ 9 | var TTYPE = CNST.TTYPE, 10 | TAG = CNST.TAG, 11 | ERR = CNST.ERR, 12 | RSP = CNST.RSP; 13 | 14 | var helper = {}; 15 | 16 | /********************************************************* 17 | * helper * 18 | *********************************************************/ 19 | helper.lfUpdate = function (cn, enable) { 20 | clearInterval(cn._updater); 21 | cn._updater = null; 22 | 23 | if (enable) { 24 | cn._updater = setInterval(function () { 25 | _.forEach(cn.serversInfo, function (serverInfo, ssid) { 26 | if (serverInfo.registered) { 27 | serverInfo.lfsecs += 1; 28 | if (serverInfo._lfsecs >= (cn.lifetime - 10)) { 29 | cn.update({}, function (err, msg) { 30 | if (err) { 31 | cn.emit('error', err); 32 | } else { 33 | // if (msg.status === RSP.notfound) 34 | // helper.lfUpdate(cn, false); 35 | } 36 | }); 37 | 38 | serverInfo._lfsecs = 0; 39 | } 40 | } 41 | }); 42 | }, 1000); 43 | } 44 | }; 45 | 46 | helper.heartbeat = function (cn, ssid, enable, rsp) { 47 | var serverInfo = cn.serversInfo[ssid]; 48 | 49 | clearInterval(serverInfo.hbPacemaker); 50 | serverInfo.hbPacemaker = null; 51 | 52 | if (serverInfo.hbStream.stream) { 53 | serverInfo.hbStream.stream.removeListener('finish', serverInfo.hbStream.finishCb); 54 | serverInfo.hbStream.stream.end(); 55 | serverInfo.hbStream.stream = null; 56 | cn.emit('logout'); 57 | } 58 | 59 | if (enable) { 60 | serverInfo.hbStream.stream = rsp; 61 | serverInfo.hbStream.finishCb = function () { 62 | clearInterval(serverInfo.hbPacemaker); 63 | cn.emit('offline'); 64 | if (cn.autoReRegister === true) 65 | helper.reRegister(cn, ssid); 66 | }; 67 | 68 | rsp.on('finish', serverInfo.hbStream.finishCb); 69 | 70 | serverInfo.hbPacemaker = setInterval(function () { 71 | try { 72 | serverInfo.hbStream.stream.write('hb'); 73 | } catch (e) { 74 | cn.emit('error', e); 75 | } 76 | }, cn._config.heartbeatTime * 1000); 77 | cn.emit('login'); 78 | } 79 | }; 80 | 81 | helper.reRegister = function (cn, ssid) { 82 | var serverInfo = cn.serversInfo[ssid]; 83 | 84 | cn.emit('reconnect'); 85 | cn._register(serverInfo.ip, serverInfo.port, function (err, msg) { 86 | if (!msg || !(msg.status === RSP.created)) { 87 | setTimeout(function () { 88 | helper.reRegister(cn); 89 | }, 5000); 90 | } 91 | }); 92 | }; 93 | 94 | helper.checkAndBuildObjList = function (cn, check, opts) { 95 | var objList = cn.getSmartObject().objectList(), 96 | objListInPlain = '', 97 | newObjList = {}; 98 | 99 | 100 | _.forEach(objList, function (rec) { 101 | newObjList[rec.oid] = rec.iid; 102 | }); 103 | 104 | if (!_.isEmpty(cn.objList) && _.isEqual(cn.objList, newObjList) && check === true) 105 | return null; // not diff 106 | 107 | cn.objList = newObjList; 108 | 109 | if (opts) { 110 | objListInPlain += ';rt="oma.lwm2m"'; 111 | 112 | _.forEach(opts, function (val, key) { 113 | if (key === 'ct' && val === 'application/json') 114 | objListInPlain += ';ct=11543'; 115 | else if (key === 'hb' && val === true) 116 | objListInPlain += ';hb'; 117 | }); 118 | 119 | objListInPlain += ','; 120 | } 121 | 122 | _.forEach(newObjList, function (iidArray, oidNum) { 123 | var oidNumber = oidNum; 124 | 125 | if (oidNum === 0 || oidNum === '0') 126 | return; 127 | 128 | if (_.isEmpty(iidArray)) { 129 | objListInPlain += ','; 130 | } else { 131 | _.forEach(iidArray, function (iid) { 132 | objListInPlain += ','; 133 | }); 134 | } 135 | }); 136 | 137 | if (objListInPlain[objListInPlain.length-1] === ',') 138 | objListInPlain = objListInPlain.slice(0, objListInPlain.length - 1); 139 | 140 | return objListInPlain; 141 | }; 142 | 143 | 144 | helper.checkAndReportResrc = function (cn, oid, iid, rid, val) { 145 | _.forEach(cn.serversInfo, function (serverInfo, ssid) { 146 | helper._checkAndReportResrc(cn, ssid, oid, iid, rid, val); 147 | }); 148 | }; 149 | 150 | // [FIXME] 151 | helper._checkAndReportResrc = function (cn, ssid, oid, iid, rid, val) { 152 | var serverInfo = cn.serversInfo[ssid], 153 | target = cn._target(oid, iid, rid), 154 | oidKey = target.oidKey, 155 | ridKey = target.ridKey, 156 | rAttrs = cn._getAttrs(ssid, oid, iid, rid), 157 | iAttrs = cn._getAttrs(ssid, oid, iid), 158 | rpt = serverInfo.reporters[target.pathKey], 159 | iRpt = serverInfo.reporters[oidKey + '/' + iid], 160 | iObj = {}, 161 | lastRpVal, 162 | chkRp; 163 | 164 | if (!rAttrs.enable && !iAttrs.enable) 165 | return false; 166 | 167 | if (_.isNil(rAttrs.lastRpVal) && iAttrs.lastRpVal) 168 | lastRpVal = iAttrs.lastRpVal[ridKey]; 169 | else 170 | lastRpVal = rAttrs.lastRpVal; 171 | 172 | chkRp = chackResourceAttrs(val, rAttrs.gt, rAttrs.lt, rAttrs.stp, lastRpVal); 173 | 174 | // chack Resource pmin and report 175 | if (rAttrs.mute && rAttrs.enable) { 176 | setTimeout(function () { 177 | helper._checkAndReportResrc(cn, ssid, oid, iid, rid, val); 178 | }, rAttrs.pmin * 1000); 179 | } else if (!rAttrs.mute && chkRp && rAttrs.enable && _.isFunction(rpt.write)) { 180 | rpt.write(val); 181 | } 182 | 183 | // chack Object Instance pmin and report 184 | if (iAttrs.mute && iAttrs.enable) { 185 | setTimeout(function () { 186 | helper._checkAndReportResrc(cn, ssid, oid, iid, rid, val); 187 | }, iAttrs.pmin * 1000); 188 | } else if (!iAttrs.mute && chkRp && iAttrs.enable && _.isFunction(iRpt.write)) { 189 | iObj[ridKey] = val; 190 | iRpt.write(iObj); 191 | } 192 | }; 193 | 194 | helper.checkAndCloseServer = function (cn, enable) { 195 | clearInterval(cn._socketServerChker); 196 | cn._socketServerChker = null; 197 | 198 | if (enable) { 199 | cn._socketServerChker = setInterval(function () { 200 | _.forEach(cn.servers, function (server, key) { 201 | var using = false; 202 | 203 | _.forEach(cn.serverInfo, function (serverInfo) { 204 | _.forEach(serverInfo.reporters, function (reporter, path) { 205 | if (server._port === reporter.port) 206 | using = true; 207 | }); 208 | }); 209 | 210 | if (using === false && server._port !== cn.port) { 211 | server.close(); 212 | cn.servers[key] = null; 213 | delete cn.servers[key]; 214 | } 215 | }); 216 | }, cn._config.serverChkTime * 1000); 217 | } 218 | }; 219 | 220 | /********************************************************* 221 | * Private function * 222 | *********************************************************/ 223 | function chackResourceAttrs(val, gt, lt, step, lastRpVal) { 224 | var chkRp = false; 225 | 226 | if (_.isObject(val)) { 227 | if (_.isObject(lastRpVal)) { 228 | _.forEach(lastRpVal, function (v, k) { 229 | chkRp = chkRp || (v !== lastRpVal[k]); 230 | }); 231 | } else { 232 | chkRp = true; 233 | } 234 | } else if (!_.isNumber(val)) { 235 | chkRp = (lastRpVal !== val); 236 | } else { 237 | // check Recource notification class attributes 238 | if (_.isNumber(gt) && _.isNumber(lt) && lt > gt) { 239 | chkRp = (lastRpVal !== val) && (val > gt) && (val < lt); 240 | } else if (_.isNumber(gt) && _.isNumber(lt)) { 241 | chkRp = _.isNumber(gt) && (lastRpVal !== val) && (val > gt); 242 | chkRp = chkRp || (_.isNumber(lt) && (lastRpVal !== val) && (val < lt)); 243 | } else { 244 | chkRp = (lastRpVal !== val); 245 | } 246 | 247 | if (_.isNumber(step)) 248 | chkRp = (Math.abs(val - lastRpVal) > step); 249 | } 250 | 251 | return chkRp; 252 | } 253 | 254 | /********************************************************* 255 | * Module Exports * 256 | *********************************************************/ 257 | module.exports = helper; 258 | -------------------------------------------------------------------------------- /lib/components/reqHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('busyman'), 4 | debug = require('debug')('coap-node:reqHdlr'); 5 | 6 | var cutils = require('./cutils'), 7 | helper = require('./helper'), 8 | CNST = require('./constants'); 9 | 10 | /**** Code Enumerations ****/ 11 | var TTYPE = CNST.TTYPE, 12 | TAG = CNST.TAG, 13 | ERR = CNST.ERR, 14 | RSP = CNST.RSP; 15 | 16 | /********************************************************* 17 | * Handler function * 18 | *********************************************************/ 19 | function serverReqHandler (cn, req, rsp) { 20 | var optType = serverReqParser(req), 21 | bootstrapping = cn._bootstrapping && (req.rsinfo.address === cn.bsServer.ip) && (req.rsinfo.port === cn.bsServer.port), 22 | serverInfo = findServer(cn, req.rsinfo), 23 | bsSequence = false, 24 | reqHdlr; 25 | 26 | switch (optType) { 27 | case 'read': 28 | reqHdlr = serverReadHandler; 29 | break; 30 | case 'discover': 31 | reqHdlr = serverDiscoverHandler; 32 | break; 33 | case 'write': 34 | if (bootstrapping) { 35 | bsSequence = true; 36 | reqHdlr = serverBsWriteHandler; 37 | } else { 38 | reqHdlr = serverWriteHandler; 39 | } 40 | break; 41 | case 'writeAttr': 42 | reqHdlr = serverWriteAttrHandler; 43 | break; 44 | case 'execute': 45 | reqHdlr = serverExecuteHandler; 46 | break; 47 | case 'observe': 48 | reqHdlr = serverObserveHandler; 49 | break; 50 | case 'cancelObserve': 51 | reqHdlr = serverCancelObserveHandler; 52 | break; 53 | case 'ping': 54 | reqHdlr = serverPingHandler; 55 | break; 56 | case 'create': 57 | reqHdlr = serverCreateHandler; 58 | break; 59 | case 'delete': 60 | if (bootstrapping) { 61 | bsSequence = true; 62 | reqHdlr = serverBsDeleteHandler; 63 | } else { 64 | reqHdlr = serverDeleteHandler; 65 | } 66 | break; 67 | case 'finish': 68 | if (bootstrapping) { 69 | bsSequence = true; 70 | reqHdlr = serverFinishHandler; 71 | } else { 72 | rsp.reset(); 73 | } 74 | break; 75 | case 'announce': 76 | reqHdlr = serverAnnounceHandler; 77 | break; 78 | case 'empty': 79 | rsp.reset(); 80 | break; 81 | default: 82 | break; 83 | } 84 | 85 | if (!serverInfo || (cn._bootstrapping && !bsSequence)) // [FIXIT] 86 | rsp.reset(); 87 | else if (reqHdlr) 88 | setImmediate(function () { 89 | reqHdlr(cn, req, rsp, serverInfo); 90 | }); 91 | } 92 | 93 | function serverReadHandler (cn, req, rsp) { 94 | var pathObj = cutils.getPathIdKey(req.url), 95 | target = cn._target(pathObj.oid, pathObj.iid, pathObj.rid), 96 | dataAndOpt; 97 | 98 | function readCallback(err, data) { 99 | if (err) { 100 | rsp.code = (data === TAG.unreadable || data === TAG.exec)? RSP.notallowed : RSP.badreq; 101 | rsp.end(data); 102 | } else { 103 | rsp.code = RSP.content; 104 | dataAndOpt = getRspDataAndOption(req, data); 105 | rsp.setOption('Content-Format', dataAndOpt.option['Content-Format']); 106 | rsp.end(dataAndOpt.data); 107 | } 108 | } 109 | 110 | if (pathObj.oid === 0 || pathObj.oid === 'lwm2mSecurity') { 111 | rsp.code = RSP.notallowed; 112 | rsp.end(); 113 | } else if (!target.exist) { 114 | rsp.code = RSP.notfound; 115 | rsp.end(); 116 | } else if (target.type === TTYPE.obj) { 117 | cn.so.dump(pathObj.oid, { restrict: true }, readCallback); 118 | } else if (target.type === TTYPE.inst) { 119 | cn.so.dump(pathObj.oid, pathObj.iid, { restrict: true }, readCallback); 120 | } else if (target.type === TTYPE.rsc) { 121 | cn.so.read(pathObj.oid, pathObj.iid, pathObj.rid, { restrict: true }, readCallback); 122 | } 123 | } 124 | 125 | function serverDiscoverHandler (cn, req, rsp, serverInfo) { 126 | var pathObj = cutils.getPathIdKey(req.url), 127 | target = cn._target(pathObj.oid, pathObj.iid, pathObj.rid), 128 | rspPayload; 129 | 130 | if (!target.exist) { 131 | rsp.code = RSP.notfound; 132 | rsp.end(); 133 | } else { 134 | rspPayload = buildAttrsAndRsc(cn, serverInfo.shortServerId, pathObj.oid, pathObj.iid, pathObj.rid); 135 | rsp.code = RSP.content; 136 | rsp.setOption('Content-Format', 'application/link-format'); 137 | rsp.end(rspPayload); 138 | } 139 | } 140 | 141 | function serverBsWriteHandler (cn, req, rsp) { 142 | var pathObj = cutils.getPathIdKey(req.url), 143 | target = cn._target(pathObj.oid, pathObj.iid, pathObj.rid), 144 | value = getReqData(req, target.pathKey), 145 | obj = {}; 146 | 147 | // if req come from bootstrap server, should create object instance 148 | if (target.type === TTYPE.obj) { 149 | rsp.code = RSP.notallowed; 150 | rsp.end(); 151 | } else if (target.type === TTYPE.inst) { 152 | cn.createInst(pathObj.oid, pathObj.iid, value); 153 | rsp.code = RSP.changed; 154 | rsp.end(); 155 | } else { 156 | obj[pathObj.rid] = value; 157 | cn.createInst(pathObj.oid, pathObj.iid, obj); 158 | rsp.code = RSP.changed; 159 | rsp.end(); 160 | } 161 | } 162 | 163 | function serverWriteHandler (cn, req, rsp) { 164 | var pathObj = cutils.getPathIdKey(req.url), 165 | target = cn._target(pathObj.oid, pathObj.iid, pathObj.rid), 166 | value = getReqData(req, target.pathKey), 167 | obj = {}; 168 | 169 | function writeCallback(err, data) { 170 | if (err) 171 | rsp.code = (data === TAG.unwritable || data === TAG.exec) ? RSP.notallowed : RSP.badreq ; 172 | else 173 | rsp.code = RSP.changed; 174 | 175 | rsp.end(); 176 | } 177 | 178 | if (!target.exist) { 179 | rsp.code = RSP.notfound; 180 | rsp.end(); 181 | } else if (target.type === TTYPE.obj) { 182 | rsp.code = RSP.notallowed; 183 | rsp.end(); 184 | } else if (target.type === TTYPE.inst) { 185 | cn._writeInst(pathObj.oid, pathObj.iid, value, writeCallback); 186 | } else { 187 | cn.so.write(pathObj.oid, pathObj.iid, pathObj.rid, value, { restrict: true }, writeCallback); 188 | } 189 | } 190 | 191 | function serverWriteAttrHandler (cn, req, rsp, serverInfo) { 192 | var pathObj = cutils.getPathIdKey(req.url), 193 | target = cn._target(pathObj.oid, pathObj.iid, pathObj.rid), 194 | attrs = cutils.buildRptAttr(req); 195 | 196 | if (!target.exist) { 197 | rsp.code = RSP.notfound; 198 | rsp.end(); 199 | } else if (attrs === false) { 200 | rsp.code = RSP.badreq; 201 | rsp.end(); 202 | } else { 203 | cn._setAttrs(serverInfo.shortServerId, pathObj.oid, pathObj.iid, pathObj.rid, attrs); 204 | rsp.code = RSP.changed; 205 | rsp.end(); 206 | } 207 | } 208 | 209 | function serverExecuteHandler (cn, req, rsp) { 210 | var pathObj = cutils.getPathIdKey(req.url), 211 | target = cn._target(pathObj.oid, pathObj.iid, pathObj.rid), 212 | argus = cutils.getArrayArgus(req.payload); 213 | 214 | if (!target.exist) { 215 | rsp.code = RSP.notfound; 216 | rsp.end(); 217 | } else if (argus === false) { 218 | rsp.code = RSP.badreq; 219 | rsp.end(); 220 | } else if (target.type === TTYPE.obj || target.type === TTYPE.inst) { 221 | rsp.code = RSP.notallowed; 222 | rsp.end(); 223 | } else { 224 | cn.execResrc(pathObj.oid, pathObj.iid, pathObj.rid, argus, function (err, data) { 225 | if (err) 226 | rsp.code = (data === TAG.unexecutable) ? RSP.notallowed : RSP.badreq; 227 | else 228 | rsp.code = RSP.changed; 229 | 230 | rsp.end(); 231 | }); 232 | } 233 | } 234 | 235 | function serverObserveHandler (cn, req, rsp, serverInfo) { 236 | var pathObj = cutils.getPathIdKey(req.url), 237 | ssid = serverInfo.shortServerId, 238 | target = cn._target(pathObj.oid, pathObj.iid, pathObj.rid), 239 | rAttrs = cn._getAttrs(ssid, pathObj.oid, pathObj.iid, pathObj.rid), 240 | dataAndOpt; 241 | 242 | function enableReport(oid, iid, rid, format, rsp) { 243 | cn._enableReport(ssid, oid, iid, rid, format, rsp, function (err, val) { 244 | if (err) { 245 | rsp.statusCode = (val === TAG.unreadable || val === TAG.exec) ? RSP.notallowed : RSP.notfound; 246 | rsp.end(val); 247 | } else { 248 | rsp.statusCode = RSP.content; 249 | dataAndOpt = getRspDataAndOption(req, val); 250 | rsp.setOption('Content-Format', dataAndOpt.option['Content-Format']); 251 | rsp.write(dataAndOpt.data); 252 | cn.emit('observe', { shortServerId: ssid, path: target.pathKey }); 253 | } 254 | }); 255 | } 256 | 257 | if (pathObj.oid === 'heartbeat') { 258 | helper.heartbeat(cn, ssid, true, rsp); 259 | rsp.statusCode = RSP.content; 260 | rsp.write('hb'); 261 | } else if (!target.exist) { 262 | rsp.statusCode = RSP.notfound; 263 | rsp.end(); 264 | } else if (target.type === TTYPE.obj) { 265 | rsp.statusCode = RSP.notallowed; 266 | rsp.end(); 267 | } else if (serverInfo.reporters[target.pathKey]) { 268 | cn._disableReport(ssid, pathObj.oid, pathObj.iid, pathObj.rid, function (err) { 269 | enableReport(pathObj.oid, pathObj.iid, pathObj.rid, req.headers.Accept, rsp); 270 | }); 271 | } else { 272 | enableReport(pathObj.oid, pathObj.iid, pathObj.rid, req.headers.Accept, rsp); 273 | } 274 | } 275 | 276 | function serverCancelObserveHandler (cn, req, rsp, serverInfo) { 277 | var pathObj = cutils.getPathIdKey(req.url), 278 | target = cn._target(pathObj.oid, pathObj.iid, pathObj.rid); 279 | 280 | if (pathObj.oid === 'heartbeat') { 281 | helper.heartbeat(cn, false); 282 | rsp.code = RSP.content; 283 | rsp.end(); 284 | } else if (!target.exist) { 285 | rsp.code = RSP.notfound; 286 | rsp.end(); 287 | } else if (target.type === TTYPE.obj) { 288 | rsp.statusCode = RSP.notallowed; 289 | rsp.end(); 290 | } else { 291 | cn._disableReport(serverInfo.shortServerId, pathObj.oid, pathObj.iid, pathObj.rid, function (err, val) { 292 | if (err) 293 | rsp.code = RSP.notfound; 294 | else 295 | rsp.code = RSP.content; 296 | 297 | rsp.end(); 298 | }); 299 | } 300 | } 301 | 302 | function serverPingHandler (cn, req, rsp, serverInfo) { 303 | rsp.code = serverInfo.registered ? RSP.content : RSP.notallowed; 304 | rsp.end(); 305 | } 306 | 307 | function serverCreateHandler (cn, req, rsp) { 308 | var pathObj = cutils.getPathIdKey(req.url), 309 | target = cn._target(pathObj.oid, pathObj.iid), 310 | data = getReqData(req, target.pathKey), 311 | value = data[Object.keys(data)], 312 | iid = Object.keys(data)[0]; 313 | 314 | if (!target.exist) { 315 | rsp.code = RSP.badreq; 316 | rsp.end(); 317 | } else { 318 | cn.createInst(pathObj.oid, iid, value, function (err, data) { 319 | if (err) 320 | rsp.code = RSP.badreq; 321 | else 322 | rsp.code = RSP.created; 323 | rsp.end(); 324 | }); 325 | } 326 | } 327 | 328 | function serverBsDeleteHandler (cn, req, rsp) { 329 | var pathObj = cutils.getPathIdKey(req.url), 330 | objList, 331 | oid; 332 | 333 | if (!_.isNil(pathObj.oid) && !_.isNil(pathObj.iid)) { 334 | cn.deleteInst(pathObj.oid, pathObj.iid); 335 | rsp.code = RSP.deleted; 336 | rsp.end(); 337 | } else { 338 | objList = cn.so.objectList(); 339 | _.forEach(objList, function (obj) { 340 | oid = obj.oid; 341 | switch (oid){ 342 | case 0: 343 | case 1: 344 | case 2: 345 | case 4: 346 | case 5: 347 | case 6: 348 | case 7: 349 | _.forEach(obj.iid, function (iid) { 350 | cn.deleteInst(oid, iid); 351 | }); 352 | delete cn.so[cutils.oidKey(oid)]; 353 | break; 354 | 355 | default: 356 | break; 357 | } 358 | }); 359 | 360 | rsp.code = RSP.deleted; 361 | rsp.end(); 362 | } 363 | } 364 | 365 | function serverDeleteHandler (cn, req, rsp) { 366 | var pathObj = cutils.getPathIdKey(req.url); 367 | 368 | if (_.isNil(pathObj.oid) && _.isNil(pathObj.iid)) { 369 | cn.deleteInst(pathObj.oid, pathObj.iid, function(err) { 370 | if (err) 371 | rsp.code = RSP.badreq; 372 | else 373 | rsp.code = RSP.deleted; 374 | rsp.end(); 375 | }); 376 | } else { 377 | rsp.code = RSP.notallowed; 378 | rsp.end(); 379 | } 380 | } 381 | 382 | function serverFinishHandler (cn, req, rsp) { 383 | var securityObjs = cn.so.dumpSync('lwm2mSecurity'), 384 | serverObjs = cn.so.dumpSync('lwm2mServer'), 385 | lwm2mServerURI, 386 | serverInfo; 387 | 388 | rsp.code = RSP.changed; 389 | rsp.end('finish'); 390 | 391 | cn._bootstrapping = false; 392 | cn.emit('bootstrapped'); 393 | } 394 | 395 | function serverAnnounceHandler (cn, req, rsp) { 396 | cn.emit('announce', req.payload); 397 | } 398 | /********************************************************* 399 | * Private function * 400 | *********************************************************/ 401 | function serverReqParser (req) { 402 | var optType; 403 | 404 | if (req.code === '0.00' && req._packet.confirmable && req.payload.length === 0) { 405 | optType = 'empty'; 406 | } else { 407 | switch (req.method) { 408 | case 'GET': 409 | if (req.headers.Observe === 0) 410 | optType = 'observe'; 411 | else if (req.headers.Observe === 1) 412 | optType = 'cancelObserve'; 413 | else if (req.headers.Accept === 'application/link-format') 414 | optType = 'discover'; 415 | else 416 | optType = 'read'; 417 | break; 418 | case 'PUT': 419 | if (req.headers['Content-Format']) 420 | optType = 'write'; 421 | else 422 | optType = 'writeAttr'; 423 | break; 424 | case 'POST': 425 | if (req.url === '/ping') 426 | optType = 'ping'; 427 | else if (req.url === '/bs') 428 | optType = 'finish'; 429 | else if (req.url === '/announce') 430 | optType = 'announce'; 431 | else if (req.headers['Content-Format']) 432 | optType = 'create'; 433 | else 434 | optType = 'execute'; 435 | break; 436 | case 'DELETE': 437 | optType = 'delete'; 438 | break; 439 | default: 440 | optType = 'empty'; 441 | break; 442 | } 443 | } 444 | 445 | return optType; 446 | } 447 | 448 | // [TODO] 449 | function getRspDataAndOption(req, originalData) { 450 | var format, data; 451 | 452 | if (req.headers.Accept === 'text/plain') { 453 | format = 'text/plain'; 454 | if (_.isBoolean(originalData)) 455 | data = originalData ? '1' : '0'; 456 | else 457 | data = originalData.toString(); 458 | } else if (req.headers.Accept === 'application/json') { 459 | format = 'application/json'; 460 | data = cutils.encodeJson(req.url, originalData); 461 | } else { 462 | format = 'application/tlv'; 463 | data = cutils.encodeTlv(req.url, originalData); 464 | } 465 | 466 | return { 467 | data: data, 468 | option: {'Content-Format': format} 469 | }; 470 | } 471 | 472 | // [TODO] 473 | function getReqData(req, path) { 474 | var data; 475 | 476 | if (req.headers['Content-Format'] === 'application/json') { 477 | data = cutils.decodeJson(path, req.payload); 478 | } else if (req.headers['Content-Format'] === 'application/tlv') { 479 | data = cutils.decodeTlv(path, req.payload); 480 | } else { 481 | data = req.payload.toString(); 482 | } 483 | 484 | return data; 485 | } 486 | 487 | function buildAttrsAndRsc(cn, ssid, oid, iid, rid) { 488 | var attrs = cn._getAttrs(ssid, oid, iid, rid), 489 | allowedAttrs = [ 'pmin', 'pmax', 'gt', 'lt', 'stp' ], 490 | target = cn._target(oid, iid, rid), 491 | value, 492 | data; 493 | 494 | if (!_.isNil(iid)) 495 | value = cn.getSmartObject().dumpSync(oid, iid); 496 | else 497 | value = cn.getSmartObject().dumpSync(oid); 498 | 499 | data = cutils.encodeLinkFormat(target.pathKey, value, attrs); 500 | 501 | return data; 502 | } 503 | 504 | function findServer(cn, rsinfo) { 505 | var data; 506 | 507 | _.forEach(cn.serversInfo, function (serverInfo, ssid) { 508 | if (serverInfo.ip === rsinfo.address && serverInfo.port === rsinfo.port) 509 | data = serverInfo; 510 | }); 511 | 512 | if (!data) 513 | if (cn.bsServer.ip === rsinfo.address && cn.bsServer.port === rsinfo.port) 514 | data = cn.bsServer; 515 | 516 | return data; 517 | } 518 | 519 | /********************************************************* 520 | * Module Exports * 521 | *********************************************************/ 522 | module.exports = serverReqHandler; 523 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | // Minimum time in seconds the Client Device should wait between two notifications. 5 | // default is 0 secs. 6 | defaultMinPeriod: 0, 7 | 8 | // Maximum Period. Maximum time in seconds the Client Device should wait between two notifications. 9 | // When maximum time expires after the last notification, a new notification should be sent. 10 | // default is 60 secs. 11 | defaultMaxPeriod: 60, 12 | 13 | // indicates if the server should create IPv4 connections (udp4) or IPv6 connections (udp6). 14 | // default is udp4. 15 | connectionType: 'udp4', 16 | 17 | // request should get response in the time. 18 | // default is 60 secs. 19 | reqTimeout: 60, 20 | 21 | // how often to sent heartbeat. 22 | // default is 30 secs. 23 | heartbeatTime: 30, 24 | 25 | // how often to check the socket is not used. 26 | // default is 60 secs. 27 | serverChkTime: 60 28 | }; 29 | -------------------------------------------------------------------------------- /lib/init.js: -------------------------------------------------------------------------------- 1 | var coap = require('coap'), 2 | _ = require('busyman'); 3 | 4 | var helper = require('./components/helper'); 5 | 6 | var init = {}; 7 | 8 | init.setupNode = function (cn, devResrcs) { 9 | var propWritable = { writable: true, enumerable: false, configurable: false }, 10 | maxLatency = (cn._config.reqTimeout - 47)/ 2, 11 | so = cn.getSmartObject(); 12 | 13 | coap.updateTiming({ 14 | maxLatency: maxLatency 15 | }); 16 | 17 | coap.registerFormat('application/tlv', 11542); // Leshan TLV binary Content-Formats 18 | coap.registerFormat('application/json', 11543); // Leshan JSON Numeric Content-Formats 19 | 20 | so.init('device', 0, { // oid = 3 21 | manuf: devResrcs.manuf || 'sivann', // rid = 0 22 | model: devResrcs.model || 'cnode-01', // rid = 1 23 | serial: devResrcs.serial || 'c-0000', // rid = 2 24 | firmware: devResrcs.firmware || '1.0', // rid = 3 25 | devType: devResrcs.devType || 'generic', // rid = 17 26 | hwVer: devResrcs.hwVer || '1.0', // rid = 18 27 | swVer: devResrcs.swVer || '1.0', // rid = 19 28 | availPwrSrc: devResrcs.availPwrSrc || 0, 29 | pwrSrcVoltage: devResrcs.pwrSrcVoltage || 100 30 | }); 31 | 32 | so.init('connMonitor', 0, { // oid = 4 33 | ip: cn.ip, // rid = 4 34 | routeIp: 'unknown' // rid = 5 35 | }); 36 | 37 | Object.defineProperty(so, '__read', { value: so.read }, propWritable); // __read is the original read 38 | Object.defineProperty(so, 'read', { value: function (oid, iid, rid, opt, callback) { 39 | var dataToCheck; 40 | 41 | if (_.isFunction(opt)) { 42 | callback = opt; 43 | opt = undefined; 44 | } 45 | 46 | return so.__read(oid, iid, rid, opt, function (err, data) { 47 | dataToCheck = data; 48 | setImmediate(function () { 49 | helper.checkAndReportResrc(cn, oid, iid, rid, dataToCheck); 50 | }); 51 | 52 | callback(err, data); 53 | }); 54 | }}, propWritable); 55 | 56 | Object.defineProperty(so, '__write', { value: so.write }, propWritable); // __write is the original write 57 | Object.defineProperty(so, 'write', { value: function (oid, iid, rid, value, opt, callback) { 58 | var dataToCheck; 59 | 60 | if (_.isFunction(opt)) { 61 | callback = opt; 62 | opt = undefined; 63 | } 64 | 65 | return so.__write(oid, iid, rid, value, opt, function (err, data) { 66 | dataToCheck = data || value; 67 | setImmediate(function () { 68 | helper.checkAndReportResrc(cn, oid, iid, rid, dataToCheck); 69 | }); 70 | 71 | callback(err, data); 72 | }); 73 | }}, propWritable); 74 | 75 | helper.lfUpdate(cn, true); 76 | helper.checkAndCloseServer(cn, false); // [TODO] 77 | }; 78 | 79 | module.exports = init; 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coap-node", 3 | "version": "0.2.8", 4 | "description": "Client node of lightweight M2M (LWM2M).", 5 | "main": "index.js", 6 | "dependencies": { 7 | "busyman": "^0.3.0", 8 | "coap": "^0.17.0", 9 | "debug": "^2.2.0", 10 | "lwm2m-codec": "0.1.1", 11 | "lwm2m-id": "^1.6.1", 12 | "network": "^0.2.1", 13 | "smartobject": "^1.4.3" 14 | }, 15 | "devDependencies": { 16 | "chai": "^3.5.0", 17 | "coap-shepherd": "^0.2.9", 18 | "mocha": "^2.5.3" 19 | }, 20 | "scripts": { 21 | "test": "make test-all" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/PeterEB/coap-node.git" 26 | }, 27 | "keywords": [ 28 | "coap" 29 | ], 30 | "author": "", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/PeterEB/coap-node/issues" 34 | }, 35 | "homepage": "https://github.com/PeterEB/coap-node" 36 | } 37 | -------------------------------------------------------------------------------- /test/coap-node.functional.test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | _ = require('busyman'), 4 | expect = require('chai').expect, 5 | SmartObject = require('smartobject'), 6 | shepherd = require('coap-shepherd'); 7 | 8 | var CoapNode = require('../index'); 9 | 10 | var so = new SmartObject(); 11 | 12 | var node = new CoapNode('utNode', so); 13 | 14 | describe('coap-node - Functional Check', function() { 15 | this.timeout(15000); 16 | 17 | before(function (done) { 18 | try { 19 | fs.unlinkSync(path.resolve('./node_modules/coap-shepherd/lib/database/coap.db')); 20 | } catch (e) { 21 | console.log(e); 22 | } 23 | 24 | shepherd.start(function () { 25 | done(); 26 | }); 27 | }); 28 | 29 | describe('#.register()', function() { 30 | it('should register device and return msg with status 2.01', function (done) { 31 | shepherd.permitJoin(300); 32 | 33 | var devRegHdlr = function (msg) { 34 | switch(msg.type) { 35 | case 'devIncoming': 36 | if (msg.cnode.clientName === 'utNode') { 37 | shepherd.removeListener('ind', devRegHdlr); 38 | done(); 39 | } 40 | break; 41 | default: 42 | break; 43 | } 44 | }; 45 | 46 | shepherd.on('ind', devRegHdlr); 47 | 48 | node.register('127.0.0.1', 5683, function (err, msg) { 49 | var cn; 50 | if (msg.status === '2.01') { 51 | cn = shepherd.find('utNode'); 52 | expect(cn._registered).to.be.eql(true); 53 | } 54 | }); 55 | }); 56 | 57 | it('should register device again and return msg with status 2.01', function (done) { 58 | node.register('127.0.0.1', 5683, function (err, msg) { 59 | expect(msg.status).to.be.eql('2.01'); 60 | done(); 61 | }); 62 | }); 63 | }); 64 | 65 | describe('#.update()', function() { 66 | it('should update device attrs and return msg with status 2.04', function (done) { 67 | var devUpdateHdlr = function (msg) { 68 | switch(msg.type) { 69 | case 'devUpdate': 70 | if (msg.cnode.clientName === 'utNode') { 71 | expect(msg.data.lifetime).to.be.eql(60000); 72 | shepherd.removeListener('ind', devUpdateHdlr); 73 | done(); 74 | } 75 | break; 76 | default: 77 | break; 78 | } 79 | }; 80 | 81 | shepherd.on('ind', devUpdateHdlr); 82 | 83 | node.update({ lifetime: 60000 }, function (err, msg) { 84 | if (msg.status === '2.04') { 85 | expect(node.lifetime).to.be.eql(60000); 86 | } 87 | }); 88 | }); 89 | 90 | it('should update device port and return msg with status 2.04', function (done) { 91 | node.update({}, function (err, msg) { 92 | if (msg.status === '2.04') { 93 | done(); 94 | } 95 | }); 96 | }); 97 | 98 | it('should return msg with status 4.00 when the attrs is bad', function (done) { 99 | node.update({ name: 'peter' }, function (err, msg) { 100 | if (err) { 101 | done(); 102 | } 103 | }); 104 | }); 105 | }); 106 | 107 | describe('#.checkout()', function () { 108 | it('should chect out and _sleep will be true', function (done) { 109 | node.checkout(function (err, msg) { 110 | if (msg.status === '2.04') { 111 | expect(node._sleep).to.be.eql(true); 112 | done(); 113 | } 114 | }); 115 | }); 116 | 117 | it('should chect out and _sleep will be true with duration', function (done) { 118 | node.checkout(10, function (err, msg) { 119 | if (msg.status === '2.04') { 120 | expect(node._sleep).to.be.eql(true); 121 | done(); 122 | } 123 | }); 124 | }); 125 | }); 126 | 127 | describe('#.checkin()', function () { 128 | it('should chect in and _sleep will be false', function (done) { 129 | node.checkin(function (err, msg) { 130 | if (msg.status === '2.04') { 131 | expect(node._sleep).to.be.eql(false); 132 | done(); 133 | } 134 | }); 135 | }); 136 | }); 137 | 138 | describe('#.deregister()', function() { 139 | it('should deregister device and return msg with status 2.02', function (done) { 140 | var devDeregHdlr = function (msg) { 141 | var cn; 142 | 143 | switch(msg.type) { 144 | case 'devLeaving': 145 | if (msg.cnode === 'utNode') { 146 | shepherd.removeListener('ind', devDeregHdlr); 147 | cn = shepherd.find('utNode'); 148 | expect(cn).to.be.eql(undefined); 149 | done(); 150 | } 151 | break; 152 | default: 153 | break; 154 | } 155 | }; 156 | 157 | shepherd.on('ind', devDeregHdlr); 158 | 159 | node.deregister(function (err, msg) { 160 | expect(msg.status).to.be.eql('2.02'); 161 | }); 162 | }); 163 | 164 | it('should return msg with status 4.04 when the device is not registered', function (done) { 165 | node.deregister(function (err, msg) { 166 | if (msg.status === '4.04') { 167 | done(); 168 | } 169 | }); 170 | }); 171 | 172 | it('should return msg with status 4.04 when the device is not registered', function (done) { 173 | node.update({ lifetime: 12000 }, function (err, msg) { 174 | if (msg.status === '4.04') { 175 | done(); 176 | } 177 | }); 178 | }); 179 | }); 180 | 181 | after(function (done) { 182 | shepherd.stop(function () { 183 | done(); 184 | }); 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /test/coap-node.reqHandler.test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | _ = require('busyman'), 4 | expect = require('chai').expect, 5 | SmartObject = require('smartobject'), 6 | shepherd = require('coap-shepherd'); 7 | 8 | var CoapNode = require('../index'); 9 | 10 | var so = new SmartObject(); 11 | var iObj = { 12 | sensorValue: 21, 13 | units: 'C', 14 | 5702: { 15 | read: function (cb) { 16 | var time = '2016/03/18'; 17 | cb(null, time); 18 | } 19 | }, 20 | 5703: { 21 | write: function (val, cb) { 22 | console.log('write ' + val); 23 | cb(null, val); 24 | } 25 | }, 26 | 5704: { 27 | exec: function (val1, val2, cb) { 28 | console.log(val1 + ': Hello ' + val2 + '!'); 29 | cb(null); 30 | } 31 | }, 32 | 5705: 21 33 | }; 34 | 35 | so.init('temperature', 0, iObj); 36 | 37 | var node = new CoapNode('utNode', so), 38 | remoteNode; 39 | 40 | node.on('error', function (err) { 41 | console.log(err); 42 | }); 43 | 44 | describe('coap-node - reqHandler Check', function() { 45 | this.timeout(5000); 46 | 47 | before(function (done) { 48 | try { 49 | fs.unlinkSync(path.resolve('./node_modules/coap-shepherd/lib/database/coap.db')); 50 | } catch (e) { 51 | console.log(e); 52 | } 53 | 54 | shepherd.start(function () { 55 | shepherd.permitJoin(300); 56 | 57 | var devRegHdlr = function (msg) { 58 | switch(msg.type) { 59 | case 'devIncoming': 60 | if (msg.cnode.clientName === 'utNode') { 61 | shepherd.removeListener('ind', devRegHdlr); 62 | remoteNode = shepherd.find('utNode'); 63 | done(); 64 | } 65 | break; 66 | default: 67 | break; 68 | } 69 | }; 70 | 71 | shepherd.on('ind', devRegHdlr); 72 | 73 | node.register('127.0.0.1', 5683, function (err, msg) { 74 | var cn; 75 | if (msg.status === '2.01') { 76 | cn = shepherd.find('utNode'); 77 | expect(cn._registered).to.be.eql(true); 78 | } 79 | }); 80 | }); 81 | }); 82 | 83 | describe('#readReq', function() { 84 | it('read - resource', function (done) { 85 | remoteNode.readReq('/temperature/0/sensorValue', function (err, msg) { 86 | if (err) { 87 | console.log(err); 88 | } else if (msg.status === '2.05') { 89 | expect(msg.data).to.be.eql(21); 90 | done(); 91 | } 92 | }); 93 | }); 94 | 95 | it('read - resource is unreadable', function (done) { 96 | remoteNode.readReq('/temperature/0/5703', function (err, msg) { 97 | if (err) { 98 | console.log(err); 99 | } else if (msg.status === '4.05') { 100 | expect(msg.data).to.be.eql('_unreadable_'); 101 | done(); 102 | } 103 | }); 104 | }); 105 | 106 | it('read - resource is exec', function (done) { 107 | remoteNode.readReq('/temperature/0/5704', function (err, msg) { 108 | if (err) { 109 | console.log(err); 110 | } else if (msg.status === '4.05') { 111 | expect(msg.data).to.be.eql('_exec_'); 112 | done(); 113 | } 114 | }); 115 | }); 116 | 117 | it('read - instence', function (done) { 118 | remoteNode.readReq('/temperature/0', function (err, msg) { 119 | var inst = { 120 | sensorValue: 21, 121 | units: 'C', 122 | 5702: '2016/03/18', 123 | 5703: '_unreadable_', 124 | 5704: '_exec_', 125 | 5705: 21 126 | }; 127 | 128 | if (err) { 129 | console.log(err); 130 | } else if (msg.status === '2.05') { 131 | expect(msg.data).to.be.eql(inst); 132 | done(); 133 | } 134 | }); 135 | }); 136 | 137 | it('read - object', function (done) { 138 | remoteNode.readReq('/temperature', function (err, msg) { 139 | var obj = { 140 | 0: { 141 | sensorValue: 21, 142 | units: 'C', 143 | 5702: '2016/03/18', 144 | 5703: '_unreadable_', 145 | 5704: '_exec_', 146 | 5705: 21 147 | } 148 | }; 149 | 150 | if (err) { 151 | console.log(err); 152 | } else if (msg.status === '2.05') { 153 | expect(msg.data).to.be.eql(obj); 154 | done(); 155 | } 156 | }); 157 | }); 158 | }); 159 | 160 | describe('#writeReq', function() { 161 | it('write - resource', function (done) { 162 | remoteNode.writeReq('/temperature/0/5703', 19, function (err, msg) { 163 | if (err) { 164 | console.log(err); 165 | } else if (msg.status === '2.04') done(); 166 | }); 167 | }); 168 | 169 | it('write - resource is unwriteable', function (done) { 170 | remoteNode.writeReq('/temperature/0/5702', 'x', function (err, msg) { 171 | if (err) { 172 | console.log(err); 173 | } else if (msg.status === '4.05') done(); 174 | }); 175 | }); 176 | 177 | it('write - resource is exec', function (done) { 178 | remoteNode.writeReq('/temperature/0/5704', 'x', function (err, msg) { 179 | if (err) { 180 | console.log(err); 181 | } else if (msg.status === '4.05') done(); 182 | }); 183 | }); 184 | 185 | it('write - instence', function (done) { 186 | var inst = { 187 | 5703: 'x', 188 | 5705: 21 189 | }; 190 | 191 | remoteNode.writeReq('/temperature/0', inst, function (err, msg) { 192 | if (err) { 193 | console.log(err); 194 | } else if (msg.status === '2.04') done(); 195 | }); 196 | }); 197 | 198 | it('write - instence with unwriteable', function (done) { 199 | var inst = { 200 | 5700: 21, 201 | 5701: 'C', 202 | 5702: 'x', 203 | 5703: 'x', 204 | 5704: 'x' 205 | }; 206 | 207 | remoteNode.writeReq('/temperature/0', inst, function (err, msg) { 208 | if (err) { 209 | console.log(err); 210 | } else if (msg.status === '4.05') done(); 211 | }); 212 | }); 213 | }); 214 | 215 | describe('#executeReq', function() { 216 | it('execute - resource with argus', function (done) { 217 | remoteNode.executeReq('/temperature/0/5704', ['peter', 'world'], function (err, msg) { 218 | if (err) { 219 | console.log(err); 220 | } else if (msg.status === '2.04') done(); 221 | }); 222 | }); 223 | 224 | // it('execute - resource without argus', function (done) { 225 | // remoteNode.execute('/temperature/0/5704', function (err, msg) { 226 | // if (msg.status === '2.04') done(); 227 | // }); 228 | // }); 229 | 230 | it('execute - not allowed argus', function (done) { 231 | remoteNode.executeReq('/temperature/0/5703', [ ' ' ], function (err, msg) { 232 | if (err) { 233 | console.log(err); 234 | } else if (msg.status === '4.00') done(); 235 | }); 236 | }); 237 | 238 | it('execute - resource is unexecutable', function (done) { 239 | remoteNode.executeReq('/temperature/0/5702', function (err, msg) { 240 | if (err) { 241 | console.log(err); 242 | } else if (msg.status === '4.05') done(); 243 | }); 244 | }); 245 | 246 | it('execute - bad argus', function (done) { 247 | remoteNode.executeReq('/temperature/0/5703', 'x', function (err, msg) { 248 | if (err) done(); 249 | }); 250 | }); 251 | }); 252 | 253 | describe('#discoverReq', function() { 254 | it('discover - resource', function (done) { 255 | var result = { 256 | path: '/3303/0/5700', 257 | attrs: { pmin: 0, pmax: 60 } 258 | }; 259 | 260 | remoteNode.discoverReq('/temperature/0/sensorValue', function (err, msg) { 261 | if (err) { 262 | console.log(err); 263 | } else if (msg.status === '2.05') { 264 | expect(msg.data).to.be.eql(result); 265 | done(); 266 | } 267 | }); 268 | }); 269 | 270 | it('discover - instence', function (done) { 271 | var result = { 272 | path: '/3303/0', 273 | attrs: { pmin: 0, pmax: 60 }, 274 | resrcList: ['/3303/0/5702', '/3303/0/5703', '/3303/0/5704', '/3303/0/5705', '/3303/0/5700', '/3303/0/5701'] 275 | }; 276 | 277 | remoteNode.discoverReq('/temperature/0', function (err, msg) { 278 | if (err) { 279 | console.log(err); 280 | } else if (msg.status === '2.05') { 281 | expect(msg.data).to.be.eql(result); 282 | done(); 283 | } 284 | }); 285 | }); 286 | 287 | it('discover - object', function (done) { 288 | var result = { 289 | path: '/3303', 290 | attrs: { pmin: 0, pmax: 60 }, 291 | resrcList: ['/3303/0/5702', '/3303/0/5703', '/3303/0/5704', '/3303/0/5705', '/3303/0/5700', '/3303/0/5701'] 292 | }; 293 | 294 | remoteNode.discoverReq('/temperature', function (err, msg) { 295 | if (err) { 296 | console.log(err); 297 | } else if (msg.status === '2.05') { 298 | expect(msg.data).to.be.eql(result); 299 | done(); 300 | } 301 | }); 302 | }); 303 | }); 304 | 305 | describe('#writeAttrsReq', function() { 306 | it('writeAttrs - resource', function (done) { 307 | var attrs = { 308 | pmin: 5, 309 | pmax: 90, 310 | gt: 0, 311 | lt: 100, 312 | stp: 0.5 313 | }; 314 | 315 | remoteNode.writeAttrsReq('/temperature/0/5705', attrs, function (err, msg) { 316 | if (err) { 317 | console.log(err); 318 | } else if (msg.status === '2.04') done(); 319 | }); 320 | }); 321 | 322 | it('writeAttrs - instence', function (done) { 323 | var attrs = { 324 | pmin: 10, 325 | pmax: 90, 326 | gt: 0, 327 | lt: 100, 328 | stp: 0.5 329 | }; 330 | 331 | remoteNode.writeAttrsReq('/temperature/0', attrs, function (err, msg) { 332 | if (err) { 333 | console.log(err); 334 | } else if (msg.status === '2.04') done(); 335 | }); 336 | }); 337 | 338 | it('writeAttrs - object', function (done) { 339 | var attrs = { 340 | pmin: 10, 341 | pmax: 90, 342 | gt: 0, 343 | lt: 100, 344 | stp: 0.5 345 | }; 346 | 347 | remoteNode.writeAttrsReq('/temperature', attrs, function (err, msg) { 348 | if (err) { 349 | console.log(err); 350 | } else if (msg.status === '2.04') done(); 351 | }); 352 | }); 353 | 354 | it('writeAttrs - bad attrs', function (done) { 355 | var attrs = { x: 100 }; 356 | 357 | remoteNode.writeAttrsReq('/temperature/0/sensorValue', attrs, function (err, msg) { 358 | if (err) done(); 359 | }); 360 | }); 361 | }); 362 | 363 | describe('#observeReq', function() { 364 | it('observe - resource', function (done) { 365 | var devNotifyHdlr = function (msg) { 366 | switch(msg.type) { 367 | case 'devNotify': 368 | if (msg.cnode.clientName === 'utNode') { 369 | expect(msg.data.path).to.be.eql('/temperature/0/5705'); 370 | // expect(msg.data.value).to.be.eql(19); 371 | shepherd.removeListener('ind', devNotifyHdlr); 372 | done(); 373 | } 374 | break; 375 | default: 376 | break; 377 | } 378 | }; 379 | 380 | shepherd.on('ind', devNotifyHdlr); 381 | 382 | remoteNode.observeReq('/temperature/0/5705', function (err, msg) { 383 | if (err) { 384 | console.log(err); 385 | } else if (msg.status === '2.05') { 386 | // expect(msg.data).to.be.eql(21); 387 | remoteNode.writeReq('/temperature/0/5705', 21.2, function (err, msg) { 388 | remoteNode.writeReq('/temperature/0/5705', 19, function (err, msg) {}); 389 | }); 390 | } 391 | }); 392 | }); 393 | 394 | it('observe - resource is unreadable', function (done) { 395 | remoteNode.observeReq('/temperature/0/5703', function (err, msg) { 396 | if (err) { 397 | console.log(err); 398 | } else if (msg.status === '4.05') { 399 | done(); 400 | } 401 | }); 402 | }); 403 | 404 | it('observe - resource is exec', function (done) { 405 | remoteNode.observeReq('/temperature/0/5704', function (err, msg) { 406 | if (err) { 407 | console.log(err); 408 | } else if (msg.status === '4.05') { 409 | done(); 410 | } 411 | }); 412 | }); 413 | 414 | it('observe - instence', function (done) { 415 | var reqObj = { 416 | sensorValue: 21, 417 | units: 'C', 418 | 5702: '2016/03/18', 419 | 5703: '_unreadable_', 420 | 5704: '_exec_', 421 | 5705: 19 422 | }, 423 | reqObj2 = { 424 | sensorValue: 21, 425 | units: 'C', 426 | 5702: '2016/03/18', 427 | 5703: '_unreadable_', 428 | 5704: '_exec_', 429 | 5705: 22 430 | }; 431 | 432 | var devNotifyHdlr = function (msg) { 433 | switch(msg.type) { 434 | case 'devNotify': 435 | if (msg.data.path === '/temperature/0') { 436 | // expect(msg.data.value).to.be.eql(reqObj2); 437 | shepherd.removeListener('ind', devNotifyHdlr); 438 | done(); 439 | } 440 | break; 441 | default: 442 | break; 443 | } 444 | }; 445 | 446 | shepherd.on('ind', devNotifyHdlr); 447 | 448 | remoteNode.observeReq('/temperature/0', function (err, msg) { 449 | if (err) { 450 | console.log(err); 451 | } else if (msg.status === '2.05') { 452 | // expect(msg.data).to.be.eql(reqObj); 453 | remoteNode.writeReq('/temperature/0/5705', 22, function (err, msg) {}); 454 | } 455 | }); 456 | }); 457 | 458 | it('observe - object', function (done) { 459 | remoteNode.observeReq('/temperature', function (err, msg) { 460 | if (err) { 461 | console.log(err); 462 | } else if (msg.status === '4.05') 463 | done(); 464 | }); 465 | }); 466 | }); 467 | 468 | describe('#cancelObserveReq', function() { 469 | it('cancelObserve - resource', function (done) { 470 | remoteNode.cancelObserveReq('/temperature/0/5705', function (err, msg) { 471 | if (err) { 472 | console.log(err); 473 | } else if (msg.status === '2.05') done(); 474 | }); 475 | }); 476 | 477 | it('cancelObserve - resource is not observed', function (done) { 478 | remoteNode.cancelObserveReq('/temperature/0/5703', function (err, msg) { 479 | if (err) { 480 | console.log(err); 481 | } else if (msg.status === '4.04') done(); 482 | }); 483 | }); 484 | 485 | it('cancelObserve - instence', function (done) { 486 | remoteNode.cancelObserveReq('/temperature/0', function (err, msg) { 487 | if (err) { 488 | console.log(err); 489 | } else if (msg.status === '2.05') done(); 490 | }); 491 | }); 492 | 493 | it('cancelObserve - object', function (done) { 494 | remoteNode.cancelObserveReq('/temperature', function (err, msg) { 495 | if (err) { 496 | console.log(err); 497 | } else if (msg.status === '4.05') done(); 498 | }); 499 | }); 500 | }); 501 | 502 | after(function (done) { 503 | node.deregister(function (err, msg) { 504 | if (err) { 505 | console.log(err); 506 | } else if (msg.status === '2.02') { 507 | shepherd.stop(function () { 508 | done(); 509 | }); 510 | } 511 | }); 512 | }); 513 | }); 514 | -------------------------------------------------------------------------------- /test/coap-node.signature.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('busyman'), 2 | expect = require('chai').expect, 3 | SmartObject = require('smartobject'); 4 | 5 | var CoapNode = require('../index'); 6 | 7 | var so = new SmartObject(), 8 | node = new CoapNode('utNode', so); 9 | 10 | describe('coap-node - Constructor Check', function () { 11 | describe('CoapNode', function () { 12 | it('should throw TypeError if attrs is not correct', function (done) { 13 | var defSo = { 14 | device: { 15 | 0: { // oid = 3 16 | manuf: 'sivann', // rid = 0 17 | model: 'cnode-01', // rid = 1 18 | serial: 'c-0000', // rid = 2 19 | firmware: '1.0', // rid = 3 20 | devType: 'generic', // rid = 17 21 | hwVer: '1.0', // rid = 18 22 | swVer: '1.0', // rid = 19 23 | availPwrSrc: 0, 24 | pwrSrcVoltage: 100 25 | } 26 | }, 27 | connMonitor: { 28 | 0: { // oid = 4 29 | ip: 'unknown', // rid = 4 30 | routeIp: 'unknown' // rid = 5 31 | } 32 | } 33 | }; 34 | 35 | expect(node.clientName).to.be.eql('utNode'); 36 | expect(node.locationPath).to.be.eql('unknown'); 37 | expect(node.lifetime).to.be.eql(86400); 38 | expect(node.version).to.be.eql('1.0'); 39 | expect(node.ip).to.be.eql('unknown'); 40 | expect(node.port).to.be.eql('unknown'); 41 | expect(node.servers).to.be.eql({}); 42 | expect(node.serversInfo).to.be.eql({}); 43 | expect(node.objList).to.be.eql(null); 44 | 45 | node.so.dump(function (err, data) { 46 | if (err) { 47 | console.log(err); 48 | } else { 49 | if (_.isEqual(defSo, data)) 50 | done(); 51 | } 52 | }); 53 | }); 54 | }); 55 | }); 56 | 57 | describe('coap-node - Signature Check', function () { 58 | describe('new CoapNode()', function () { 59 | it('should throw TypeError if clientName is not a string', function () { 60 | expect(function () { return new CoapNode(); }).to.throw(TypeError); 61 | expect(function () { return new CoapNode(undefined); }).to.throw(TypeError); 62 | expect(function () { return new CoapNode(null); }).to.throw(TypeError); 63 | expect(function () { return new CoapNode(NaN); }).to.throw(TypeError); 64 | expect(function () { return new CoapNode(100); }).to.throw(TypeError); 65 | expect(function () { return new CoapNode([]); }).to.throw(TypeError); 66 | expect(function () { return new CoapNode({}); }).to.throw(TypeError); 67 | expect(function () { return new CoapNode(true); }).to.throw(TypeError); 68 | expect(function () { return new CoapNode(new Date()); }).to.throw(TypeError); 69 | expect(function () { return new CoapNode(function () {}); }).to.throw(TypeError); 70 | }); 71 | }); 72 | 73 | describe('#.bootstrap()', function () { 74 | it('should throw TypeError if ip is not a string', function () { 75 | expect(function () { return node.bootstrap(1); }).to.throw(TypeError); 76 | expect(function () { return node.bootstrap(1, undefined, '1'); }).to.throw(TypeError); 77 | expect(function () { return node.bootstrap(1, null, '1'); }).to.throw(TypeError); 78 | expect(function () { return node.bootstrap(1, NaN, '1'); }).to.throw(TypeError); 79 | expect(function () { return node.bootstrap(1, 100, '1'); }).to.throw(TypeError); 80 | expect(function () { return node.bootstrap(1, [], '1'); }).to.throw(TypeError); 81 | expect(function () { return node.bootstrap(1, {}, '1'); }).to.throw(TypeError); 82 | expect(function () { return node.bootstrap(1, true, '1'); }).to.throw(TypeError); 83 | expect(function () { return node.bootstrap(1, new Date(), '1'); }).to.throw(TypeError); 84 | expect(function () { return node.bootstrap(1, function () {}, '1'); }).to.throw(TypeError); 85 | }); 86 | 87 | it('should throw TypeError if port is not a string or a number', function () { 88 | expect(function () { return node.bootstrap(1, '192.168.1.1'); }).to.throw(TypeError); 89 | expect(function () { return node.bootstrap(1, '192.168.1.1', undefined); }).to.throw(TypeError); 90 | expect(function () { return node.bootstrap(1, '192.168.1.1', null); }).to.throw(TypeError); 91 | expect(function () { return node.bootstrap(1, '192.168.1.1', NaN); }).to.throw(TypeError); 92 | expect(function () { return node.bootstrap(1, '192.168.1.1', []); }).to.throw(TypeError); 93 | expect(function () { return node.bootstrap(1, '192.168.1.1', {}); }).to.throw(TypeError); 94 | expect(function () { return node.bootstrap(1, '192.168.1.1', true); }).to.throw(TypeError); 95 | expect(function () { return node.bootstrap(1, '192.168.1.1', new Date()); }).to.throw(TypeError); 96 | expect(function () { return node.bootstrap(1, '192.168.1.1', function () {}); }).to.throw(TypeError); 97 | }); 98 | }); 99 | 100 | describe('#.configure()', function () { 101 | it('should throw TypeError if ip is not a string', function () { 102 | expect(function () { return node.configure(); }).to.throw(TypeError); 103 | expect(function () { return node.configure(undefined, '1'); }).to.throw(TypeError); 104 | expect(function () { return node.configure(null, '1'); }).to.throw(TypeError); 105 | expect(function () { return node.configure(NaN, '1'); }).to.throw(TypeError); 106 | expect(function () { return node.configure(100, '1'); }).to.throw(TypeError); 107 | expect(function () { return node.configure([], '1'); }).to.throw(TypeError); 108 | expect(function () { return node.configure({}, '1'); }).to.throw(TypeError); 109 | expect(function () { return node.configure(true, '1'); }).to.throw(TypeError); 110 | expect(function () { return node.configure(new Date(), '1'); }).to.throw(TypeError); 111 | expect(function () { return node.configure(function () {}, '1'); }).to.throw(TypeError); 112 | }); 113 | 114 | it('should throw TypeError if port is not a string or a number', function () { 115 | expect(function () { return node.configure('192.168.1.1'); }).to.throw(TypeError); 116 | expect(function () { return node.configure('192.168.1.1', undefined); }).to.throw(TypeError); 117 | expect(function () { return node.configure('192.168.1.1', null); }).to.throw(TypeError); 118 | expect(function () { return node.configure('192.168.1.1', NaN); }).to.throw(TypeError); 119 | expect(function () { return node.configure('192.168.1.1', []); }).to.throw(TypeError); 120 | expect(function () { return node.configure('192.168.1.1', {}); }).to.throw(TypeError); 121 | expect(function () { return node.configure('192.168.1.1', true); }).to.throw(TypeError); 122 | expect(function () { return node.configure('192.168.1.1', new Date()); }).to.throw(TypeError); 123 | expect(function () { return node.configure('192.168.1.1', function () {}); }).to.throw(TypeError); 124 | }); 125 | }); 126 | 127 | describe('#.register()', function () { 128 | it('should throw TypeError if ip is not a string', function () { 129 | expect(function () { return node.register(); }).to.throw(TypeError); 130 | expect(function () { return node.register(undefined, '1'); }).to.throw(TypeError); 131 | expect(function () { return node.register(null, '1'); }).to.throw(TypeError); 132 | expect(function () { return node.register(NaN, '1'); }).to.throw(TypeError); 133 | expect(function () { return node.register(100, '1'); }).to.throw(TypeError); 134 | expect(function () { return node.register([], '1'); }).to.throw(TypeError); 135 | expect(function () { return node.register({}, '1'); }).to.throw(TypeError); 136 | expect(function () { return node.register(true, '1'); }).to.throw(TypeError); 137 | expect(function () { return node.register(new Date(), '1'); }).to.throw(TypeError); 138 | expect(function () { return node.register(function () {}, '1'); }).to.throw(TypeError); 139 | }); 140 | 141 | it('should throw TypeError if ip is not a string or a number', function () { 142 | expect(function () { return node.register('192.168.1.1'); }).to.throw(TypeError); 143 | expect(function () { return node.register('192.168.1.1', undefined); }).to.throw(TypeError); 144 | expect(function () { return node.register('192.168.1.1', null); }).to.throw(TypeError); 145 | expect(function () { return node.register('192.168.1.1', NaN); }).to.throw(TypeError); 146 | expect(function () { return node.register('192.168.1.1', []); }).to.throw(TypeError); 147 | expect(function () { return node.register('192.168.1.1', {}); }).to.throw(TypeError); 148 | expect(function () { return node.register('192.168.1.1', true); }).to.throw(TypeError); 149 | expect(function () { return node.register('192.168.1.1', new Date()); }).to.throw(TypeError); 150 | expect(function () { return node.register('192.168.1.1', function () {}); }).to.throw(TypeError); 151 | }); 152 | }); 153 | 154 | describe('#.update()', function () { 155 | it('should throw TypeError if attrs is not a number', function () { 156 | expect(function () { return node.update(); }).to.throw(TypeError); 157 | expect(function () { return node.update(undefined); }).to.throw(TypeError); 158 | expect(function () { return node.update(null); }).to.throw(TypeError); 159 | expect(function () { return node.update(NaN); }).to.throw(TypeError); 160 | expect(function () { return node.update(100); }).to.throw(TypeError); 161 | expect(function () { return node.update('xx'); }).to.throw(TypeError); 162 | expect(function () { return node.update([]); }).to.throw(TypeError); 163 | expect(function () { return node.update(true); }).to.throw(TypeError); 164 | expect(function () { return node.update(new Date()); }).to.throw(TypeError); 165 | expect(function () { return node.update(function () {}); }).to.throw(TypeError); 166 | 167 | expect(function () { return node.update({}); }).not.to.throw(TypeError); 168 | }); 169 | }); 170 | 171 | describe('#.checkout()', function () { 172 | it('should throw TypeError if duration is not a number', function () { 173 | expect(function () { return node.checkout(null); }).to.throw(TypeError); 174 | expect(function () { return node.checkout(NaN); }).to.throw(TypeError); 175 | expect(function () { return node.checkout('xx'); }).to.throw(TypeError); 176 | expect(function () { return node.checkout([]); }).to.throw(TypeError); 177 | expect(function () { return node.checkout({}); }).to.throw(TypeError); 178 | expect(function () { return node.checkout(true); }).to.throw(TypeError); 179 | expect(function () { return node.checkout(new Date()); }).to.throw(TypeError); 180 | 181 | expect(function () { return node.checkout(100); }).not.to.throw(TypeError); 182 | }); 183 | }); 184 | }); -------------------------------------------------------------------------------- /test/cutils.test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect, 2 | cutils = require('../lib/components/cutils.js'); 3 | 4 | describe('cutils', function () { 5 | describe('Signature Check', function () { 6 | 7 | it('#.getTime()', function () { 8 | expect(function () { cutils.getTime(); }).not.to.throw(); 9 | }); 10 | 11 | it('#.oidKey()', function () { 12 | expect(function () { cutils.oidKey({}); }).to.throw(); 13 | expect(function () { cutils.oidKey([]); }).to.throw(); 14 | expect(function () { cutils.oidKey(); }).to.throw(); 15 | 16 | expect(function () { cutils.oidKey('x'); }).not.to.throw(); 17 | expect(function () { cutils.oidKey(5); }).not.to.throw(); 18 | }); 19 | 20 | it('#.oidNumber()', function () { 21 | expect(function () { cutils.oidNumber({}); }).to.throw(); 22 | expect(function () { cutils.oidNumber([]); }).to.throw(); 23 | expect(function () { cutils.oidNumber(); }).to.throw(); 24 | 25 | expect(function () { cutils.oidNumber('x'); }).not.to.throw(); 26 | expect(function () { cutils.oidNumber(5); }).not.to.throw(); 27 | }); 28 | 29 | it('#.ridKey()', function () { 30 | expect(function () { cutils.ridNumber({}, 'x'); }).to.throw(); 31 | expect(function () { cutils.ridNumber([], 'x'); }).to.throw(); 32 | expect(function () { cutils.ridNumber('x', []); }).to.throw(); 33 | expect(function () { cutils.ridNumber('x', {}); }).to.throw(); 34 | expect(function () { cutils.ridNumber(); }).to.throw(); 35 | 36 | expect(function () { cutils.ridNumber('x', 'y'); }).not.to.throw(); 37 | expect(function () { cutils.ridNumber(5, 'y'); }).not.to.throw(); 38 | expect(function () { cutils.ridNumber('x', 5); }).not.to.throw(); 39 | expect(function () { cutils.ridNumber(1, 5); }).not.to.throw(); 40 | }); 41 | 42 | it('#.ridNumber()', function () { 43 | expect(function () { cutils.ridNumber({}, 'x'); }).to.throw(); 44 | expect(function () { cutils.ridNumber([], 'x'); }).to.throw(); 45 | expect(function () { cutils.ridNumber('x', []); }).to.throw(); 46 | expect(function () { cutils.ridNumber('x', {}); }).to.throw(); 47 | expect(function () { cutils.ridNumber(); }).to.throw(); 48 | 49 | expect(function () { cutils.ridNumber('x', 'y'); }).not.to.throw(); 50 | expect(function () { cutils.ridNumber(5, 'y'); }).not.to.throw(); 51 | expect(function () { cutils.ridNumber('x', 5); }).not.to.throw(); 52 | expect(function () { cutils.ridNumber(1, 5); }).not.to.throw(); 53 | }); 54 | 55 | it('#.buildRptAttr()', function () { 56 | 57 | }); 58 | 59 | it('#.buildUpdateQuery()', function () { 60 | 61 | }); 62 | 63 | it('#.getArrayArgus()', function () { 64 | 65 | }); 66 | 67 | it('#.getPathArray()', function () { 68 | expect(function () { cutils.getPathArray(5); }).to.throw(); 69 | expect(function () { cutils.getPathArray({}); }).to.throw(); 70 | expect(function () { cutils.getPathArray([]); }).to.throw(); 71 | expect(function () { cutils.getPathArray(); }).to.throw(); 72 | 73 | expect(function () { cutils.getPathArray('x'); }).not.to.throw(); 74 | }); 75 | 76 | it('#.getPathIdKey()', function () { 77 | expect(function () { cutils.getPathIdKey(5); }).to.throw(); 78 | expect(function () { cutils.getPathIdKey({}); }).to.throw(); 79 | expect(function () { cutils.getPathIdKey([]); }).to.throw(); 80 | expect(function () { cutils.getPathIdKey(); }).to.throw(); 81 | 82 | expect(function () { cutils.getPathIdKey('x'); }).not.to.throw(); 83 | }); 84 | 85 | it('#.encodeJson()', function () { 86 | expect(function () { cutils.encodeJson('x', 'y'); }).to.throw(); 87 | expect(function () { cutils.encodeJson('x/y', 'y'); }).to.throw(); 88 | expect(function () { cutils.encodeJson('x', 5); }).to.throw(); 89 | expect(function () { cutils.encodeJson('x/y', 5); }).to.throw(); 90 | expect(function () { cutils.encodeJson('x', []); }).to.throw(); 91 | expect(function () { cutils.encodeJson(5, 'y'); }).to.throw(); 92 | expect(function () { cutils.encodeJson(1, 5); }).to.throw(); 93 | expect(function () { cutils.encodeJson({}, 'x'); }).to.throw(); 94 | expect(function () { cutils.encodeJson([], 'x'); }).to.throw(); 95 | expect(function () { cutils.encodeJson(); }).to.throw(); 96 | 97 | expect(function () { cutils.encodeJson('x/y/z', 'y'); }).not.to.throw(); 98 | expect(function () { cutils.encodeJson('x/y/z', 5); }).not.to.throw(); 99 | expect(function () { cutils.encodeJson('x', {}); }).not.to.throw(); 100 | }); 101 | 102 | it('#.decodeJson()', function () { 103 | expect(function () { cutils.decodeJson('x', 'y'); }).to.throw(); 104 | expect(function () { cutils.decodeJson('x/y', 'y'); }).to.throw(); 105 | expect(function () { cutils.decodeJson('x', 5); }).to.throw(); 106 | expect(function () { cutils.decodeJson('x/y', 5); }).to.throw(); 107 | expect(function () { cutils.decodeJson('x', []); }).to.throw(); 108 | expect(function () { cutils.decodeJson(5, 'y'); }).to.throw(); 109 | expect(function () { cutils.decodeJson(1, 5); }).to.throw(); 110 | expect(function () { cutils.decodeJson({}, 'x'); }).to.throw(); 111 | expect(function () { cutils.decodeJson([], 'x'); }).to.throw(); 112 | expect(function () { cutils.decodeJson(); }).to.throw(); 113 | 114 | expect(function () { cutils.decodeJson('x/y/z', {e:[]}); }).not.to.throw(); 115 | expect(function () { cutils.decodeJson('x', {e:[]}); }).not.to.throw(); 116 | }); 117 | }); 118 | 119 | describe('Functional Check', function () { 120 | it('#.oidKey()', function () { 121 | expect(cutils.oidKey('x')).to.be.eql('x'); 122 | expect(cutils.oidKey(9999)).to.be.eql(9999); 123 | expect(cutils.oidKey(2051)).to.be.eql('cmdhDefEcValues'); 124 | expect(cutils.oidKey('2051')).to.be.eql('cmdhDefEcValues'); 125 | expect(cutils.oidKey('cmdhDefEcValues')).to.be.eql('cmdhDefEcValues'); 126 | }); 127 | 128 | it('#.oidNumber()', function () { 129 | expect(cutils.oidNumber('x')).to.be.eql('x'); 130 | expect(cutils.oidNumber(9999)).to.be.eql(9999); 131 | expect(cutils.oidNumber(2051)).to.be.eql(2051); 132 | expect(cutils.oidNumber('2051')).to.be.eql(2051); 133 | expect(cutils.oidNumber('cmdhDefEcValues')).to.be.eql(2051); 134 | }); 135 | 136 | it('#.ridKey()', function () { 137 | expect(cutils.ridKey('x', 1)).to.be.eql(1); 138 | expect(cutils.ridKey('x', 1)).to.be.eql(1); 139 | expect(cutils.ridKey(9999)).to.be.eql(9999); 140 | expect(cutils.ridKey(9999, 1)).to.be.eql(1); 141 | expect(cutils.ridKey(1, 9999)).to.be.eql(9999); 142 | expect(cutils.ridKey(1, 'xxx')).to.be.eql('xxx'); 143 | 144 | expect(cutils.ridKey(5602)).to.be.eql('maxMeaValue'); 145 | expect(cutils.ridKey('5602')).to.be.eql('maxMeaValue'); 146 | expect(cutils.ridKey('maxMeaValue')).to.be.eql('maxMeaValue'); 147 | expect(cutils.ridKey('lwm2mServer', 5)).to.be.eql('disableTimeout'); 148 | expect(cutils.ridKey('lwm2mServer', '5')).to.be.eql('disableTimeout'); 149 | expect(cutils.ridKey(1, 5)).to.be.eql('disableTimeout'); 150 | expect(cutils.ridKey(1, '5')).to.be.eql('disableTimeout'); 151 | expect(cutils.ridKey(1, 'disableTimeout')).to.be.eql('disableTimeout'); 152 | expect(cutils.ridKey('1', 'disableTimeout')).to.be.eql('disableTimeout'); 153 | }); 154 | 155 | it('#.ridNumber()', function () { 156 | expect(cutils.ridNumber('x', 1)).to.be.eql(1); 157 | expect(cutils.ridNumber('x', 1)).to.be.eql(1); 158 | expect(cutils.ridNumber(9999)).to.be.eql(9999); 159 | expect(cutils.ridNumber(9999, 1)).to.be.eql(1); 160 | expect(cutils.ridNumber(1, 9999)).to.be.eql(9999); 161 | expect(cutils.ridNumber(1, 'xxx')).to.be.eql('xxx'); 162 | 163 | expect(cutils.ridNumber(5602)).to.be.eql(5602); 164 | expect(cutils.ridNumber('5602')).to.be.eql(5602); 165 | expect(cutils.ridNumber('maxMeaValue')).to.be.eql(5602); 166 | expect(cutils.ridNumber('lwm2mServer', 5)).to.be.eql(5); 167 | expect(cutils.ridNumber('lwm2mServer', '5')).to.be.eql(5); 168 | expect(cutils.ridNumber(1, 5)).to.be.eql(5); 169 | expect(cutils.ridNumber(1, '5')).to.be.eql(5); 170 | expect(cutils.ridNumber(1, 'disableTimeout')).to.be.eql(5); 171 | expect(cutils.ridNumber('1', 'disableTimeout')).to.be.eql(5); 172 | }); 173 | 174 | it('#.buildRptAttr()', function () { 175 | 176 | }); 177 | 178 | it('#.buildUpdateQuery()', function () { 179 | 180 | }); 181 | 182 | it('#.getArrayArgus()', function () { 183 | 184 | }); 185 | 186 | it('#.getPathArray()', function () { 187 | expect(cutils.getPathArray('/x/y/z')).to.be.eql(['x', 'y', 'z']); 188 | expect(cutils.getPathArray('/x/y/z/')).to.be.eql(['x', 'y', 'z']); 189 | expect(cutils.getPathArray('x/y/z/')).to.be.eql(['x', 'y', 'z']); 190 | expect(cutils.getPathArray('x/y/z')).to.be.eql(['x', 'y', 'z']); 191 | }); 192 | 193 | it('#.getPathIdKey()', function () { 194 | expect(cutils.getPathIdKey('/1/2/3')).to.be.eql({ oid: 'lwm2mServer', iid: 2, rid: 'defaultMaxPeriod' }); 195 | expect(cutils.getPathIdKey('/lwm2mServer/2/3')).to.be.eql({ oid: 'lwm2mServer', iid: 2, rid: 'defaultMaxPeriod' }); 196 | expect(cutils.getPathIdKey('/1/2/defaultMaxPeriod')).to.be.eql({ oid: 'lwm2mServer', iid: 2, rid: 'defaultMaxPeriod' }); 197 | expect(cutils.getPathIdKey('/lwm2mServer/2/defaultMaxPeriod')).to.be.eql({ oid: 'lwm2mServer', iid: 2, rid: 'defaultMaxPeriod' }); 198 | }); 199 | }); 200 | }); 201 | --------------------------------------------------------------------------------