├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGES_NEXT_RELEASE ├── ContributionPolicy.txt ├── README.md ├── bin ├── iotagent-lwm2m-client.js └── iotagent-lwm2m-server.js ├── config-mongo.js ├── config.js ├── docs ├── contribution.md └── deprecated.md ├── lib ├── commonConfig.js ├── constants.js ├── errors.js ├── lwm2m-client.js ├── lwm2m-node-lib.js ├── lwm2m-server.js └── services │ ├── OMAContentFormats.json │ ├── client │ ├── execute.js │ ├── objectRegistry.js │ ├── read.js │ └── write.js │ ├── coapRouter.js │ ├── coapUtils.js │ ├── model │ ├── Device.js │ └── dbConn.js │ ├── object │ ├── device.js │ ├── index.js │ ├── schema.js │ ├── security.js │ ├── senml.js │ └── server.js │ ├── server │ ├── coapUtils.js │ ├── deviceManagement.js │ ├── inMemoryDeviceRegistry.js │ ├── informationReporting.js │ ├── mongodbDeviceRegistry.js │ ├── registration.js │ ├── unregistration.js │ ├── updateRegistration.js │ └── utils │ │ └── deviceRegistrationUtils.js │ └── slidingWindow.js ├── package.json └── test ├── .jshintrc └── unit ├── client ├── client-device-managment-test.js ├── client-information-management-test.js ├── client-registration-test.js └── client-registry-test.js ├── common ├── object-schema-test.js ├── object-senml-test.js └── slidingWindow-test.js ├── server ├── bootstrap-test.js ├── client-registration-test.js ├── client-unregistration-test.js ├── device-management-test.js ├── device-registration-utils-test.js ├── device-registry-mongo-test.js ├── device-registry-test.js ├── information-reporting-test.js ├── multiple-southbound-interfaces-test.js └── update-registration-test.js └── testUtils.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /.idea/ 3 | /doc/ 4 | /coverage/ 5 | /site 6 | /report/ 7 | /.c9revisions/ 8 | /.settings/ 9 | 10 | .settings 11 | .classpath 12 | .project 13 | .metadata 14 | npm-debug.log 15 | package-lock.json 16 | 17 | *~ 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "noempty": true, 9 | "quotmark": "single", 10 | "undef": true, 11 | "unused": true, 12 | "trailing": true, 13 | "maxparams": 7, 14 | "maxdepth": 4, 15 | "camelcase": true, 16 | "maxlen": 120, 17 | "node": true, 18 | "expr": true, 19 | "unused": "vars", 20 | "globals": { 21 | "describe":true, 22 | "it": true, 23 | "expect": true, 24 | "before": true, 25 | "after": true, 26 | "beforeEach": true, 27 | "afterEach": true, 28 | "mock": true 29 | } 30 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | ghpages 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | - "10" 6 | - "12" 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | services: 13 | - mongodb 14 | 15 | install: 16 | # It seems to be a good idea to clean before install 17 | # see https://travis-ci.community/t/i-needed-an-explicit-cleanup-before-starting-my-tests-to-have-then-working-again-is-that-normal/5512/2 18 | - npm run clean 19 | - npm install 20 | 21 | before_script: 22 | - npm run lint 23 | -------------------------------------------------------------------------------- /CHANGES_NEXT_RELEASE: -------------------------------------------------------------------------------- 1 | - Set Nodejs 12 as minimum version in packages.json (effectively removing Nodev8 and Nodev10 from supported versions) 2 | -------------------------------------------------------------------------------- /ContributionPolicy.txt: -------------------------------------------------------------------------------- 1 | Open Source Contribution Policy between Contributor and Telefónica Soluciones de Informática y Comunicaciones de España, S.A.U. (hereinafter TSOL). 2 | 3 | With the aim of establish a Contribution Policy between the contributors and TSOL, contributors must send this policy signed in order to express the acceptance of all its sections. 4 | Contributor Contact Information 5 | Name (Contributor):__________________________________________________ 6 | E-mail:_____________________________________________________________ 7 | Phone:_____________________________________________________________ 8 | Mailing Address:_____________________________________________________ 9 | 10 | 1.- Contributor declare to own all the rights necessary to contribute with any material intended to be compiled or integrated with Source Code offered by TSOL after the acceptance of this policy by TSOL. 11 | 12 | 2. By the acceptance of this policy, the Contributor assigns to TSOL the ownership of the Contribution in all worldwide common law and statutory rights associated with the copyrights, copyright application, copyright registration and moral rights in the Contribution to the extent allowable under applicable local laws and copyright conventions. 13 | 14 | 3. The Contributor will keep the moral rights and the authorship of the Contribution, 15 | 16 | 4. The contributor agrees that the contribution given by this joint copyright assignment can be used by TSOL to establish and create privative versions of the contribution or to include it in privative products. The contributor will be mandatory mentioned as the author of the contribution, but all the rights and revenues of this privative version, will be owned by TSOL. 17 | 18 | 5. This policy supersedes and replaces all prior copyrights assignments or agreements made by the contributor to TSOL under this project. 19 | 20 | 6. This policy will apply to all contributions of the contributor, until one of the parties cancel or modify it by a written declaration or if a new one is signed. 21 | 22 | 7. Contributor is legally entitled to grant the above policy and agrees not to provide any Contribution that violates any law or breaches any contract. 23 | 24 | Signed: ____________________________________________Date:________________ 25 | 26 | Please send a signed original to iot_support at tid dot es 27 | -------------------------------------------------------------------------------- /bin/iotagent-lwm2m-client.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 5 | * 6 | * This file is part of fiware-iotagent-lib 7 | * 8 | * fiware-iotagent-lib is free software: you can redistribute it and/or 9 | * modify it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the License, 11 | * or (at your option) any later version. 12 | * 13 | * fiware-iotagent-lib is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 | * See the GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public 19 | * License along with fiware-iotagent-lib. 20 | * If not, seehttp://www.gnu.org/licenses/. 21 | * 22 | * For those usages not covered by the GNU Affero General Public License 23 | * please contact with::[contacto@tid.es] 24 | */ 25 | 26 | var config = require('../config'), 27 | clUtils = require('command-node'), 28 | lwm2mClient = require('../').client, 29 | async = require('async'), 30 | globalDeviceInfo, 31 | separator = '\n\n\t'; 32 | 33 | function printObject(result) { 34 | var resourceIds = Object.keys(result.attributes); 35 | console.log('\nObject:\n--------------------------------\nObjectType: %s\nObjectId: %s\nObjectUri: %s', 36 | result.objectType, result.objectId, result.objectUri); 37 | 38 | if (resourceIds.length > 0) { 39 | console.log('\nAttributes:'); 40 | for (var i=0; i < resourceIds.length; i++) { 41 | console.log('\t-> %s: %s', resourceIds[i], result.attributes[resourceIds[i]]); 42 | } 43 | console.log('\n'); 44 | } 45 | } 46 | 47 | function handleObjectFunction(error, result) { 48 | if (error) { 49 | clUtils.handleError(error); 50 | } else { 51 | printObject(result); 52 | } 53 | } 54 | 55 | function create(command) { 56 | lwm2mClient.registry.create(command[0], handleObjectFunction); 57 | } 58 | 59 | function get(command) { 60 | lwm2mClient.registry.get(command[0], handleObjectFunction); 61 | } 62 | 63 | function remove(command) { 64 | lwm2mClient.registry.remove(command[0], handleObjectFunction); 65 | } 66 | 67 | function set(command) { 68 | lwm2mClient.registry.setResource(command[0], command[1], command[2], handleObjectFunction); 69 | } 70 | 71 | function unset(command) { 72 | lwm2mClient.registry.unsetResource(command[0], command[1], handleObjectFunction); 73 | } 74 | 75 | function list() { 76 | lwm2mClient.registry.list(function(error, objList) { 77 | if (error){ 78 | clUtils.handleError(error); 79 | } else { 80 | console.log('\nList:\n--------------------------------\n'); 81 | for (var i=0; i < objList.length; i++) { 82 | console.log('\t-> ObjURI: %s / Obj Type: %s / Obj ID: %s / Resource Num: %d', 83 | objList[i].objectUri, objList[i].objectType, objList[i].objectId, 84 | Object.keys(objList[i].attributes).length); 85 | } 86 | } 87 | }); 88 | } 89 | 90 | function handleWrite(objectType, objectId, resourceId, value, callback) { 91 | console.log('\nValue written:\n--------------------------------\n'); 92 | console.log('-> ObjectType: %s', objectType); 93 | console.log('-> ObjectId: %s', objectId); 94 | console.log('-> ResourceId: %s', resourceId); 95 | console.log('-> Written value: %s', value); 96 | clUtils.prompt(); 97 | 98 | callback(null); 99 | } 100 | 101 | function handleExecute(objectType, objectId, resourceId, value, callback) { 102 | console.log('\nCommand executed:\n--------------------------------\n'); 103 | console.log('-> ObjectType: %s', objectType); 104 | console.log('-> ObjectId: %s', objectId); 105 | console.log('-> ResourceId: %s', resourceId); 106 | console.log('-> Command arguments: %s', value); 107 | clUtils.prompt(); 108 | 109 | callback(null); 110 | } 111 | 112 | function handleRead(objectType, objectId, resourceId, value, callback) { 113 | console.log('\nValue read:\n--------------------------------\n'); 114 | console.log('-> ObjectType: %s', objectType); 115 | console.log('-> ObjectId: %s', objectId); 116 | console.log('-> ResourceId: %s', resourceId); 117 | console.log('-> Read Value: %s', value); 118 | clUtils.prompt(); 119 | 120 | callback(null); 121 | } 122 | 123 | function setHandlers(deviceInfo) { 124 | lwm2mClient.setHandler(deviceInfo.serverInfo, 'write', handleWrite); 125 | lwm2mClient.setHandler(deviceInfo.serverInfo, 'execute', handleExecute); 126 | lwm2mClient.setHandler(deviceInfo.serverInfo, 'read', handleRead); 127 | } 128 | 129 | function connect(command) { 130 | var url; 131 | 132 | console.log('\nConnecting to the server. This may take a while.\n'); 133 | 134 | if (command[2] === '/') { 135 | url = command[2]; 136 | } 137 | 138 | lwm2mClient.register(command[0], command[1], command[3], command[2], function (error, deviceInfo) { 139 | if (error) { 140 | clUtils.handleError(error); 141 | } else { 142 | globalDeviceInfo = deviceInfo; 143 | setHandlers(deviceInfo); 144 | console.log('\nConnected:\n--------------------------------\nDevice location: %s', deviceInfo.location); 145 | clUtils.prompt(); 146 | } 147 | }); 148 | } 149 | 150 | function disconnect(command) { 151 | if (globalDeviceInfo) { 152 | lwm2mClient.unregister(globalDeviceInfo, function(error) { 153 | if (error) { 154 | clUtils.handleError(error); 155 | } else { 156 | console.log('\nDisconnected:\n--------------------------------\n'); 157 | clUtils.prompt(); 158 | } 159 | }); 160 | } else { 161 | console.error('\nCouldn\'t find device information (the connection may have not been completed).'); 162 | } 163 | } 164 | 165 | function updateConnection(command) { 166 | if (globalDeviceInfo) { 167 | lwm2mClient.update(globalDeviceInfo, function(error, deviceInfo) { 168 | if (error) { 169 | clUtils.handleError(error); 170 | } else { 171 | globalDeviceInfo = deviceInfo; 172 | setHandlers(deviceInfo); 173 | console.log('\Information updated:\n--------------------------------\n'); 174 | clUtils.prompt(); 175 | } 176 | }); 177 | } else { 178 | console.error('\nCouldn\'t find device information (the connection may have not been completed).'); 179 | } 180 | } 181 | 182 | function quit(command) { 183 | console.log('\nExiting client\n--------------------------------\n'); 184 | process.exit(); 185 | } 186 | 187 | var commands = { 188 | 'create': { 189 | parameters: ['objectUri'], 190 | description: '\tCreate a new object. The object is specified using the /type/id OMA notation.', 191 | handler: create 192 | }, 193 | 'get': { 194 | parameters: ['objectUri'], 195 | description: '\tGet all the information on the selected object.', 196 | handler: get 197 | }, 198 | 'remove': { 199 | parameters: ['objectUri'], 200 | description: '\tRemove an object. The object is specified using the /type/id OMA notation.', 201 | handler: remove 202 | }, 203 | 'set': { 204 | parameters: ['objectUri', 'resourceId', 'resourceValue'], 205 | description: '\tSet the value for a resource. If the resource does not exist, it is created.', 206 | handler: set 207 | }, 208 | 'unset': { 209 | parameters: ['objectUri', 'resourceId'], 210 | description: '\tRemoves a resource from the selected object.', 211 | handler: unset 212 | }, 213 | 'list': { 214 | parameters: [], 215 | description: '\tList all the available objects along with its resource names and values.', 216 | handler: list 217 | }, 218 | 'connect': { 219 | parameters: ['host', 'port', 'endpointName', 'url'], 220 | description: '\tConnect to the server in the selected host and port, using the selected endpointName.', 221 | handler: connect 222 | }, 223 | 'updateConnection': { 224 | parameters: [], 225 | description: '\tUpdates the current connection to a server.', 226 | handler: updateConnection 227 | }, 228 | 'disconnect': { 229 | parameters: [], 230 | description: '\tDisconnect from the current server.', 231 | handler: disconnect 232 | }, 233 | 'config': { 234 | parameters: [], 235 | description: '\tPrint the current config.', 236 | handler: clUtils.showConfig(config, 'client') 237 | }, 238 | 'quit': { 239 | parameters: [], 240 | description: '\tExit the client.', 241 | handler: quit 242 | } 243 | }; 244 | 245 | lwm2mClient.init(require('../config')); 246 | 247 | clUtils.initialize(commands, 'LWM2M-Client> '); 248 | -------------------------------------------------------------------------------- /config-mongo.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | // Configuration of the LWTM2M Server 4 | //-------------------------------------------------- 5 | config.server = { 6 | port: 5683, // Port where the server will be listening 7 | lifetimeCheckInterval: 1000, // Minimum interval between lifetime checks in ms 8 | udpWindow: 100, 9 | defaultType: 'Device', 10 | logLevel: 'FATAL', 11 | ipProtocol: 'udp4', 12 | serverProtocol: 'udp4', 13 | deviceRegistry: { 14 | type: 'mongodb' 15 | }, 16 | mongodb:{ 17 | host: 'localhost', 18 | port: '27017', 19 | db: 'lwtm2m' 20 | }, 21 | formats: [ 22 | { 23 | name: 'application-vnd-oma-lwm2m/text', 24 | value: 1541 25 | }, 26 | { 27 | name: 'application-vnd-oma-lwm2m/tlv', 28 | value: 1542 29 | }, 30 | { 31 | name: 'application-vnd-oma-lwm2m/json', 32 | value: 1543 33 | }, 34 | { 35 | name: 'application-vnd-oma-lwm2m/opaque', 36 | value: 1544 37 | } 38 | ], 39 | writeFormat: 'application-vnd-oma-lwm2m/text' 40 | }; 41 | 42 | // Configuration of the LWTM2M Client 43 | //-------------------------------------------------- 44 | config.client = { 45 | lifetime: '85671', 46 | version: '1.0', 47 | logLevel: 'FATAL', 48 | observe: { 49 | period: 3000 50 | }, 51 | ipProtocol: 'udp4', 52 | serverProtocol: 'udp4', 53 | formats: [ 54 | { 55 | name: 'lightweightm2m/text', 56 | value: 1541 57 | } 58 | ], 59 | writeFormat: 'lightweightm2m/text' 60 | }; 61 | 62 | module.exports = config; 63 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var config = {}; 2 | 3 | // Configuration of the LWTM2M Server 4 | //-------------------------------------------------- 5 | config.server = { 6 | port: 5684, // Port where the server will be listening 7 | lifetimeCheckInterval: 1000, // Minimum interval between lifetime checks in ms 8 | udpWindow: 100, 9 | defaultType: 'Device', 10 | types: [ 11 | { 12 | name: 'OtherType', 13 | url: '/OtherType' 14 | } 15 | ], 16 | logLevel: 'FATAL', 17 | ipProtocol: 'udp4', 18 | serverProtocol: 'udp4', 19 | formats: [ 20 | { 21 | name: 'application-vnd-oma-lwm2m/text', 22 | value: 1541 23 | }, 24 | { 25 | name: 'application-vnd-oma-lwm2m/tlv', 26 | value: 1542 27 | }, 28 | { 29 | name: 'application-vnd-oma-lwm2m/json', 30 | value: 1543 31 | }, 32 | { 33 | name: 'application-vnd-oma-lwm2m/opaque', 34 | value: 1544 35 | } 36 | ], 37 | writeFormat: 'application-vnd-oma-lwm2m/text', 38 | defaultAcceptFormat: 'text/plain' 39 | }; 40 | 41 | // Configuration of the LWTM2M Client 42 | //-------------------------------------------------- 43 | config.client = { 44 | lifetime: '85671', 45 | version: '1.0', 46 | logLevel: 'DEBUG', 47 | observe: { 48 | period: 3000 49 | }, 50 | ipProtocol: 'udp4', 51 | serverProtocol: 'udp4', 52 | formats: [ 53 | { 54 | name: 'lightweightm2m/text', 55 | value: 1541 56 | } 57 | ], 58 | writeFormat: 'lightweightm2m/text' 59 | }; 60 | 61 | module.exports = config; 62 | -------------------------------------------------------------------------------- /docs/contribution.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Overview 4 | Being an Open Source project, everyone can contribute, provided that it respect the following points: 5 | * Before contributing any code, the author must make sure all the tests work (see below how to launch the tests). 6 | * Developed code must adhere to the syntax guidelines enforced by the linters. 7 | * Code must be developed following the branching model and changelog policies defined below. 8 | * For any new feature added, unit tests must be provided, following the example of the ones already created. 9 | 10 | In order to start contributing: 11 | 1. Fork this repository clicking on the "Fork" button on the upper-right area of the page. 12 | 2. Clone your just forked repository: 13 | ``` 14 | git clone https://github.com/your-github-username/lwm2m-node-lib.git 15 | ``` 16 | 3. Add the main lwm2m-node-lib repository as a remote to your forked repository (use any name for your remote 17 | repository, it does not have to be lwm2m-node-lib, although we will use it in the next steps): 18 | ``` 19 | git remote add lwm2m-node-lib https://github.com/telefonicaid/lwm2m-node-lib.git 20 | ``` 21 | 22 | Before starting contributing, remember to synchronize the `master` branch in your forked repository with the `master` 23 | branch in the main lwm2m-node-lib repository, by following this steps 24 | 25 | 1. Change to your local `master` branch (in case you are not in it already): 26 | ``` 27 | git checkout master 28 | ``` 29 | 2. Fetch the remote changes: 30 | ``` 31 | git fetch lwm2m-node-lib 32 | ``` 33 | 3. Merge them: 34 | ``` 35 | git rebase lwm2m-node-lib/master 36 | ``` 37 | 38 | Contributions following this guidelines will be added to the `master` branch, and released in the next version. The 39 | release process is explaind in the *Releasing* section below. 40 | 41 | 42 | ## Branching model 43 | There is a special branch in the repository: `master`. It contains the last stable development code. New features and bug fixes are always merged to `master`. 44 | 45 | In order to start developing a new feature or refactoring, a new branch should be created with name `task/`. 46 | This branch must be created from the current version of the `master` branch. Once the new functionality has been 47 | completed, a Pull Request will be created from the feature branch to `master`. Remember to check both the linters 48 | and the tests before creating the Pull Request. 49 | 50 | Bug fixes work the same way as other tasks, with the exception of the branch name, that should be called `bug/`. 51 | 52 | In order to contribute to the repository, these same scheme should be replicated in the forked repositories, so the 53 | new features or fixes should all come from the current version of `master` and end up in `master` again. 54 | 55 | All the `task/*` and `bug/*` branches are temporary, and should be removed once they have been merged. 56 | 57 | There is another set of branches called `release/`, one for each version of the product. This branches 58 | point to each of the released versions of the project, they are permanent and they are created with each release. 59 | 60 | ## Changelog 61 | The project contains a version changelog, called CHANGES_NEXT_RELEASE, that can be found in the root of the project. 62 | Whenever a new feature or bug fix is going to be merged with `master`, a new entry should be added to this changelog. 63 | The new entry should contain the reference number of the issue it is solving (if any). 64 | 65 | When a new version is released, the changelog is cleared, and remains fixed in the last commit of that version. The 66 | content of the changelog is also moved to the release description in the Github release. 67 | 68 | ## Releasing 69 | The process of making a release consists of the following steps: 70 | 1. Create a new task branch changing the development version number in the package.json (with a sufix `-next`), to the 71 | new target version (without any sufix), and PR into `master`. 72 | 2. Create a release branch from the last version of `master` named with the version number. 73 | 3. Create a tag from `release/x.y.0` named with the version number and push it to the repository. 74 | 4. Create the release in Github, from the created tag. In the description, add the contents of the Changelog. 75 | 6. Create a new task for preparing the next release, adding the sufix `-next` to the current version number (to signal 76 | this as the development version). 77 | -------------------------------------------------------------------------------- /docs/deprecated.md: -------------------------------------------------------------------------------- 1 | # Deprecated functionality 2 | 3 | Deprecated features are features that lwm2m-node-lib stills support but that are 4 | not maintained or evolved any longer. In particular: 5 | 6 | - Bugs or issues related with deprecated features and not affecting 7 | any other feature are not addressed (they are closed in github.com 8 | as soon as they are spotted). 9 | - Documentation on deprecated features is removed from the repository documentation. 10 | Documentation is still available in the documentation set associated to older versions 11 | (in the repository release branches). 12 | - Deprecated functionality is eventually removed from lwm2m-node-lib. Thus you 13 | are strongly encouraged to change your implementations using lwm2m-node-lib 14 | in order not rely on deprecated functionality. 15 | 16 | A list of deprecated features and the version in which they were deprecated follows: 17 | 18 | * Support to Node.js v4 in lwm2m-node-lib 1.1.0. 19 | 20 | ## Using old lwm2m-node-lib versions 21 | 22 | Although you are encouraged to use always the newest lwm2m-node-lib version, take into account the following 23 | information in the case you want to use old versions: 24 | 25 | * Code corresponding to old releases is 26 | available at the [lwm2m-node-lib github repository](https://github.com/telefonicaid/lwm2m-node-lib). Each release number 27 | (e.g. 0.5.0) has associated the following: 28 | * A tag, e.g. `0.5.0`. It points to the base version. 29 | * A release branch, `release/0.5.0`. The HEAD of this branch usually matches the aforementioned tag. However, if some 30 | hotfixes were developed on the base version, this branch contains such hotfixes. 31 | 32 | The following table provides information about the last lwm2m-node-lib version supporting currently removed features: 33 | 34 | | **Removed feature** | **Last lwm2m-node-lib version supporting feature** | **That version release date** | 35 | |----------------------------------------------------------------------------|-------------------------------------------|---------------------------------| 36 | | Support to Node.js v4 | 1.0.0 | July 12th, 2018 | 37 | | Support to Node.js v6 | Not yet defined but it will be done by May 2019 | Not yet defined -------------------------------------------------------------------------------- /lib/commonConfig.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of fiware-iotagent-lib 5 | * 6 | * fiware-iotagent-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * fiware-iotagent-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with fiware-iotagent-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::daniel.moranjimenez@telefonica.com 22 | * 23 | * Modified by: Federico M. Facca - Martel Innovate 24 | */ 25 | 26 | 'use strict'; 27 | 28 | var config = {}, 29 | logger = require('logops'), 30 | registry, 31 | context = { 32 | op: 'LWM2MLib.CommonConfig' 33 | }; 34 | 35 | function anyIsSet(variableSet) { 36 | for (var i = 0; i < variableSet.length; i++) { 37 | if (process.env[variableSet[i]]) { 38 | return true; 39 | } 40 | } 41 | 42 | return false; 43 | } 44 | 45 | /** 46 | * Looks for environment variables that could override configuration values. 47 | */ 48 | function processEnvironmentVariables() { 49 | var environmentVariables = [ 50 | 'LWM2M_PORT', 51 | 'LWM2M_PROTOCOL', 52 | 'LWM2M_REGISTRY_TYPE', 53 | 'LWM2M_LOG_LEVEL', 54 | 'LWM2M_MONGO_HOST', 55 | 'LWM2M_MONGO_PORT', 56 | 'LWM2M_MONGO_DB', 57 | 'LWM2M_MONGO_REPLICASET', 58 | 'LWM2M_WRITE_FORMAT', 59 | 'LWM2M_DEFAULT_ACCEPT_FORMAT' 60 | ], 61 | mongoVariables = [ 62 | 'LWM2M_MONGO_HOST', 63 | 'LWM2M_MONGO_PORT', 64 | 'LWM2M_MONGO_DB', 65 | 'LWM2M_MONGO_REPLICASET' 66 | ]; 67 | 68 | for (var i = 0; i < environmentVariables.length; i++) { 69 | if (process.env[environmentVariables[i]]) { 70 | logger.info(context, 'Setting %s to environment value: %s', 71 | environmentVariables[i], process.env[environmentVariables[i]]); 72 | } 73 | } 74 | 75 | if (process.env.LWM2M_PORT) { 76 | config.port = process.env.LWM2M_PORT; 77 | } 78 | 79 | if (process.env.LWM2M_PROTOCOL) { 80 | config.serverProtocol = process.env.LWM2M_PROTOCOL; 81 | } 82 | 83 | if (process.env.LWM2M_REGISTRY_TYPE) { 84 | config.deviceRegistry = {}; 85 | config.deviceRegistry.type = process.env.LWM2M_REGISTRY_TYPE; 86 | } 87 | 88 | if (process.env.LWM2M_LOG_LEVEL) { 89 | config.logLevel = process.env.LWM2M_LOG_LEVEL; 90 | logger.setLevel(process.env.LWM2M_LOG_LEVEL); 91 | } 92 | 93 | if (process.env.LWM2M_WRITE_FORMAT) { 94 | config.writeFormat = process.env.LWM2M_WRITE_FORMAT; 95 | } 96 | 97 | if (process.env.LWM2M_DEFAULT_ACCEPT_FORMAT) { 98 | config.defaultAcceptFormat = process.env.LWM2M_DEFAULT_ACCEPT_FORMAT; 99 | } 100 | 101 | if (anyIsSet(mongoVariables)) { 102 | config.mongodb = {}; 103 | } 104 | 105 | if (process.env.LWM2M_MONGO_HOST) { 106 | config.mongodb.host = process.env.LWM2M_MONGO_HOST; 107 | } 108 | 109 | if (process.env.LWM2M_MONGO_PORT) { 110 | config.mongodb.port = process.env.LWM2M_MONGO_PORT; 111 | } 112 | 113 | if (process.env.LWM2M_MONGO_DB) { 114 | config.mongodb.db = process.env.LWM2M_MONGO_DB; 115 | } 116 | 117 | if (process.env.LWM2M_MONGO_REPLICASET) { 118 | config.mongodb.replicaSet = process.env.LWM2M_MONGO_REPLICASET; 119 | } 120 | 121 | if (process.env.LWM2M_MONGO_RETRIES) { 122 | config.mongodb.retries = process.env.LWM2M_MONGO_RETRIES; 123 | } 124 | 125 | } 126 | 127 | function setConfig(newConfig) { 128 | config = newConfig; 129 | 130 | if (config.logLevel) { 131 | logger.setLevel(config.logLevel); 132 | } 133 | 134 | processEnvironmentVariables(); 135 | } 136 | 137 | function getConfig() { 138 | return config; 139 | } 140 | 141 | function setRegistry(newRegistry) { 142 | registry = newRegistry; 143 | } 144 | 145 | function getRegistry() { 146 | return registry; 147 | } 148 | 149 | exports.setConfig = setConfig; 150 | exports.getConfig = getConfig; 151 | exports.setRegistry = setRegistry; 152 | exports.getRegistry = getRegistry; 153 | 154 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of fiware-iotagent-lib 5 | * 6 | * fiware-iotagent-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * fiware-iotagent-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with fiware-iotagent-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::daniel.moranjimenez@telefonica.com 22 | */ 23 | 24 | 'use strict'; 25 | 26 | module.exports = { 27 | 28 | DEFAULT_MONGODB_RETRIES: 5, 29 | DEFAULT_MONGODB_RETRY_TIME: 5, 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | module.exports = { 27 | BadRequestError: function(message) { 28 | this.name = 'BAD_REQUEST_ERROR'; 29 | this.message = 'The request was not build correctly: ' + message; 30 | this.code = '4.00'; 31 | }, 32 | DeviceNotFound: function(id) { 33 | this.name = 'DEVICE_NOT_FOUND'; 34 | this.message = 'The device with id: ' + id + ' was not found'; 35 | this.code = '4.04'; 36 | }, 37 | ClientError: function(code) { 38 | this.name = 'CLIENT_ERROR'; 39 | this.message = 'Error code recieved from the client: ' + code; 40 | this.code = code; 41 | }, 42 | ObjectNotFound: function(id) { 43 | this.name = 'OBJECT_NOT_FOUND'; 44 | this.message = 'The object with id: ' + id + ' was not found in the registry'; 45 | this.code = '4.04'; 46 | }, 47 | UnsupportedAttributes: function (attributes) { 48 | this.name = 'UNSUPPORTED_ATTRIBUTES'; 49 | this.message = 'Unsupported attributes writting to object URI: ' + JSON.stringify(attributes); 50 | this.code = '4.00'; 51 | }, 52 | ServerNotFound: function(url) { 53 | this.name = 'SERVER_NOT_FOUND'; 54 | this.message = 'No server was found on url: ' + url; 55 | this.code = '4.04'; 56 | }, 57 | ResourceNotFound: function(id, type, objectId) { 58 | this.name = 'RESOURCE_NOT_FOUND'; 59 | this.code = '4.04'; 60 | 61 | if (id && type && objectId) { 62 | this.message = 'The resource with id: ' + id + ' for the object with type ' + type + ' id ' + objectId + 63 | ' was not found in the registry'; 64 | } else { 65 | this.message = 'The resource was not found in the registry'; 66 | } 67 | }, 68 | WrongObjectUri: function(uri) { 69 | this.name = 'WRONG_OBJECT_URI'; 70 | this.message = 'Tried to parse wrong object URI: ' + uri; 71 | this.code = '4.00'; 72 | }, 73 | InternalDbError: function(msg) { 74 | this.name = 'INTERNAL_DB_ERROR'; 75 | this.message = 'An internal DB Error happened: ' + msg; 76 | this.code = '5.01'; 77 | }, 78 | TypeNotFound: function(url) { 79 | this.name = 'TYPE_NOT_FOUND'; 80 | this.message = 'No type matching found for URL ' + url; 81 | this.code = '4.04'; 82 | }, 83 | IllegalTypeUrl: function(url) { 84 | this.name = 'ILLEGAL_TYPE_URL'; 85 | this.message = 'Illegal URL for type: ' + url + '. Types begining with "/rd" are not allowed'; 86 | this.code = '4.00'; 87 | }, 88 | RegistrationError: function(msg) { 89 | this.name = 'REGISTRATION_ERROR'; 90 | this.message = 'There was an error connecting to the LWM2M Server for registration: ' + msg; 91 | this.code = '5.01'; 92 | }, 93 | UpdateRegistrationError: function(msg) { 94 | this.name = 'UPDATE_REGISTRATION_ERROR'; 95 | this.message = 'There was an error connecting to the LWM2M Server for update registration: ' + msg; 96 | this.code = '5.01'; 97 | }, 98 | UnregistrationError: function(msg) { 99 | this.name = 'UNREGISTRATION_ERROR'; 100 | this.message = 'There was an error connecting to the LWM2M Server for unregistration: ' + msg; 101 | this.code = '5.01'; 102 | }, 103 | RegistrationFailed: function(code) { 104 | this.name = 'REGISTRATION_FAILED'; 105 | this.message = 'Registration to the Lightweight M2M server failed with code: ' + code; 106 | this.code = code; 107 | }, 108 | IllegalMethodAttributes: function(code) { 109 | this.name = 'ILLEGAL_METHOD_ATTRIBUTES'; 110 | this.message = 'The method was called with wrong number or type of attributes ' + 111 | 'or at least one mandatory attribute is empty'; 112 | this.code = '5.01'; 113 | }, 114 | ClientConnectionError: function(msg) { 115 | this.name = 'CLIENT_CONNECTION_ERROR'; 116 | this.message = 'There was an error sending a request to the client: ' + msg; 117 | this.code = '5.01'; 118 | }, 119 | ClientResponseError: function(msg) { 120 | this.name = 'CLIENT_RESPONSE_ERROR'; 121 | this.message = 'Error received while waiting for a client response: ' + msg; 122 | this.code = '5.01'; 123 | }, 124 | ContentFormatNotFound: function() { 125 | this.name = 'CONTENT_FORMAT_NOT_FOUND'; 126 | this.message = 'No Content Format list found.'; 127 | this.code = '4.00'; 128 | } 129 | }; 130 | 131 | -------------------------------------------------------------------------------- /lib/lwm2m-node-lib.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | exports.server = require('./lwm2m-server'); 27 | exports.client = require('./lwm2m-client'); 28 | 29 | var object = require('./services/object'); 30 | exports.Schema = object.Schema; // constructor 31 | exports.schemas = object.schemas; // OMA LWM2M Objects 32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/lwm2m-server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var coapRouter = require('./services/coapRouter'), 27 | errors = require('./errors'), 28 | db = require('./services/model/dbConn'), 29 | registry, 30 | deviceManagement = require('./services/server/deviceManagement'), 31 | informationReporting = require('./services/server/informationReporting'), 32 | coapUtils = require('./services/server/coapUtils'), 33 | async = require('async'), 34 | logger = require('logops'), 35 | config = require('./commonConfig'), 36 | context = { 37 | op: 'LWM2MLib.Server' 38 | }, 39 | apply = async.apply, 40 | status = 'STOPPED'; 41 | 42 | /** 43 | * Load the internal handlers for each kind of operation. Each handler is implemented in a separated module. This 44 | * module will be, in time, in charge of executing the user handler for that operation with all the data extracted 45 | * from the request (and completed with internal data if needed). 46 | * 47 | * @param {Object} serverInfo Object containing all the information of the current server. 48 | */ 49 | function loadDefaultHandlers(serverInfo) { 50 | logger.info(context, 'Loading default handlers'); 51 | 52 | serverInfo.handlers = { 53 | registration: { 54 | module: require('./services/server/registration'), 55 | user: coapRouter.defaultHandler 56 | }, 57 | unregistration: { 58 | module: require('./services/server/unregistration'), 59 | user: coapRouter.defaultHandler 60 | }, 61 | updateRegistration: { 62 | module: require('./services/server/updateRegistration'), 63 | user: coapRouter.defaultHandler 64 | } 65 | }; 66 | 67 | for (var i in serverInfo.handlers) { 68 | if (serverInfo.handlers.hasOwnProperty(i)) { 69 | serverInfo.handlers[i].module.init(config.getRegistry(), config.getConfig()); 70 | serverInfo.handlers[i].lib = serverInfo.handlers[i].module.handle; 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * Load the tables of available routes. For each route, the method, a regexp for the path and the name of the operation 77 | * is indicated (the name of the operation will be used to select the internal and user handlers to execute for each 78 | * route). 79 | * 80 | * @param {Object} serverInfo Object containing all the information of the current server. 81 | */ 82 | function loadRoutes(serverInfo) { 83 | logger.info(context, 'Loading routes'); 84 | 85 | serverInfo.routes = [ 86 | ['POST', /\/rd$/, 'registration'], 87 | ['DELETE', /\/rd\/.*/, 'unregistration'], 88 | ['POST', /\/rd\/.*/, 'updateRegistration'] 89 | ]; 90 | } 91 | 92 | function validateTypes(serverConfig, callback) { 93 | var error; 94 | 95 | logger.info(context, 'Validating configuration types'); 96 | 97 | if (config.getConfig().types) { 98 | for (var i in config.getConfig().types) { 99 | if (config.getConfig().types[i].url.match(/^\/rd.*/)) { 100 | error = new errors.IllegalTypeUrl(config.getConfig().types[i].url); 101 | } 102 | } 103 | } 104 | 105 | callback(error); 106 | } 107 | 108 | function start(serverConfig, startCallback) { 109 | function loadDefaults(serverInfo, callback) { 110 | loadRoutes(serverInfo); 111 | loadDefaultHandlers(serverInfo); 112 | callback(null, serverInfo); 113 | } 114 | 115 | config.setConfig(serverConfig); 116 | if (config.getConfig().logLevel) { 117 | logger.setLevel(config.getConfig().logLevel); 118 | } 119 | 120 | logger.info(context, 'Starting Lightweight M2M Server'); 121 | 122 | if (config.getConfig().deviceRegistry && config.getConfig().deviceRegistry.type === 'mongodb') { 123 | logger.info(context, 'Mongo DB Device registry selected for Lightweight M2M Library'); 124 | registry = require('./services/server/mongodbDeviceRegistry'); 125 | } else { 126 | logger.info(context, 'Memory Device registry selected for Lightweight M2M Library'); 127 | registry = require('./services/server/inMemoryDeviceRegistry'); 128 | } 129 | 130 | config.setRegistry(registry); 131 | 132 | deviceManagement.init(config.getRegistry(), config.getConfig()); 133 | informationReporting.init(config.getRegistry(), config.getConfig()); 134 | coapUtils.init(config.getConfig()); 135 | 136 | exports.listDevices = registry.list; 137 | exports.getDevice = registry.getByName; 138 | 139 | async.waterfall([ 140 | db.configureDb, 141 | apply(validateTypes,config.getConfig()), 142 | apply(coapRouter.start,config.getConfig()), 143 | loadDefaults 144 | ], function (error, results) { 145 | if (error) { 146 | status = 'ERROR'; 147 | } else { 148 | status = 'RUNNING'; 149 | registry.checkLifetime(config.getConfig().lifetimeCheckInterval); 150 | } 151 | 152 | startCallback(error, results); 153 | }); 154 | } 155 | 156 | function stop(deviceInfo, callback) { 157 | status = 'STOPPED'; 158 | 159 | async.series([ 160 | informationReporting.clean, 161 | apply(coapRouter.stop, deviceInfo) 162 | ], callback); 163 | } 164 | 165 | function getRegistry() { 166 | return config.getRegistry(); 167 | } 168 | 169 | function isRunning() { 170 | return status === 'RUNNING'; 171 | } 172 | 173 | /** 174 | * Sets the handler callback for a given type of operation. 175 | * 176 | * The signature of the handler will depend on the operation being handled. The complete list of operations and the 177 | * signature of its handlers can be found in the online documentation. 178 | * 179 | * @param {Object} serverInfo Object containing all the information of the current server. 180 | * @param {String} type Name of the operation to be handled. 181 | * @param {Function} handler Operation handler. 182 | */ 183 | function setHandler(serverInfo, type, handler) { 184 | coapRouter.setHandler(serverInfo, type, handler); 185 | 186 | if (type === 'unregistration') { 187 | registry.checkLifetime(config.lifetimeCheckInterval, handler); 188 | } 189 | } 190 | 191 | exports.start = start; 192 | exports.setHandler = setHandler; 193 | exports.stop = stop; 194 | exports.read = deviceManagement.read; 195 | exports.write = deviceManagement.write; 196 | exports.execute = deviceManagement.execute; 197 | exports.writeAttributes = deviceManagement.writeAttributes; 198 | exports.discover = deviceManagement.discover; 199 | exports.create = deviceManagement.create; 200 | exports.remove = deviceManagement.remove; 201 | exports.observe = informationReporting.observe; 202 | exports.listObservers = informationReporting.list; 203 | exports.cleanObservers = informationReporting.clean; 204 | exports.cancelObserver = informationReporting.cancel; 205 | exports.buildObserverId = informationReporting.buildId; 206 | exports.parseObserverId = informationReporting.parseId; 207 | exports.getRegistry = getRegistry; 208 | exports.isRunning = isRunning; 209 | 210 | -------------------------------------------------------------------------------- /lib/services/OMAContentFormats.json: -------------------------------------------------------------------------------- 1 | { 2 | "formats": [ 3 | { 4 | "name": "lightweightm2m/text", 5 | "value": 1541 6 | } 7 | ], 8 | "writeFormat": "lightweightm2m/text" 9 | } -------------------------------------------------------------------------------- /lib/services/client/execute.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var async = require('async'), 27 | apply = async.apply, 28 | objectRegistry = require('./objectRegistry'), 29 | coapUtils = require('../coapUtils'); 30 | 31 | /** 32 | * Invoke the user handler for this operation, with all the information from the query parameters as its arguments. 33 | * 34 | * @param {Object} queryParams Object containing all the query parameters. 35 | * @param {Function} handler User handler to be invoked. 36 | */ 37 | function applyHandler(resourceId, attributeValue, handler, storedObject, callback) { 38 | handler(storedObject.objectType, storedObject.objectId, resourceId, attributeValue, callback); 39 | } 40 | 41 | /** 42 | * Generates the end of request handler that will generate the final response to the COAP Client. 43 | * 44 | * @param {Object} req Arriving COAP Request to be handled. 45 | * @param {Object} res Outgoing COAP Response. 46 | * @returns {Function} Request handler, receiving an optional error and the write operation result. 47 | */ 48 | function endExecute(req, res) { 49 | return function (error, result) { 50 | if (error) { 51 | res.code = error.code; 52 | res.end(''); 53 | } else { 54 | res.code = '2.04'; 55 | res.end(''); 56 | } 57 | }; 58 | } 59 | 60 | /** 61 | * Handle the execute operation. 62 | * 63 | * @param {Object} req Arriving COAP Request to be handled. 64 | * @param {Object} res Outgoing COAP Response. 65 | * @param {Function} handler User handler to be executed if everything goes ok. 66 | */ 67 | function handleExecute(req, res, handler) { 68 | coapUtils.extractUriInfo(req, res, function (error, objectUri, resourceId, payload) { 69 | async.waterfall([ 70 | apply(objectRegistry.get, objectUri), 71 | apply(applyHandler, resourceId, payload, handler) 72 | ], endExecute(req, res)); 73 | }); 74 | } 75 | 76 | exports.handle = handleExecute; 77 | -------------------------------------------------------------------------------- /lib/services/client/objectRegistry.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var errors = require('../../errors'), 27 | registryBus = new (require('events')).EventEmitter(), 28 | registry = {}, 29 | attributesRegistry = {}, 30 | OBJECT_URI_REGEX = /^\/\d+\/\d+$/; 31 | 32 | registryBus.setMaxListeners(0); 33 | 34 | /** 35 | * Parse a string containing an Object URI. If the URI is not well-formed or if it contains anything beyond the Object 36 | * ID, an error is raised. 37 | * 38 | * @param {String} objectUri String representation of an OMA LWTM2M Object URI (with object Type and Id) 39 | */ 40 | function parseUri(objectUri, callback) { 41 | if (objectUri && objectUri.match && objectUri.match(OBJECT_URI_REGEX)) { 42 | var parsedUri = objectUri.split('/'), 43 | object = { 44 | objectType: parsedUri[1], 45 | objectId: parsedUri[2], 46 | objectUri: objectUri 47 | }; 48 | 49 | callback(null, object); 50 | } else { 51 | callback(new errors.WrongObjectUri(objectUri)); 52 | } 53 | } 54 | 55 | /** 56 | * Get the object of the registry represented by the given String URI. 57 | * 58 | * @param {String} objectUri String representation of an OMA LWTM2M Object URI (with object Type and Id) 59 | */ 60 | function getObject(objectUri, callback) { 61 | parseUri(objectUri, function(error, parsedObject) { 62 | if (error) { 63 | callback(error); 64 | } else if (registry[objectUri]) { 65 | callback(null, registry[objectUri]); 66 | } else { 67 | callback(new errors.ObjectNotFound(objectUri)); 68 | } 69 | }); 70 | } 71 | 72 | /** 73 | * Create a new object in the registry with the given URI. If there already is an object in that URI, it is overwritten. 74 | * 75 | * @param {String} objectUri String representation of an OMA LWTM2M Object URI (with object Type and Id) 76 | */ 77 | function create(objectUri, callback) { 78 | parseUri(objectUri, function(error, parsedObject) { 79 | if (error) { 80 | callback(error); 81 | } else { 82 | registry[objectUri] = parsedObject; 83 | registry[objectUri].attributes = {}; 84 | 85 | callback(null, parsedObject); 86 | } 87 | }); 88 | } 89 | 90 | /** 91 | * Removes the object identified by the URI from the registry. 92 | * 93 | * @param {String} objectUri String representation of an OMA LWTM2M Object URI (with object Type and Id) 94 | */ 95 | function remove(objectUri, callback) { 96 | parseUri(objectUri, function(error, parsedObject){ 97 | if (error) { 98 | callback(error); 99 | } else if (registry[objectUri]) { 100 | var removedObj = registry[objectUri]; 101 | delete registry[objectUri]; 102 | callback(null, removedObj); 103 | } else { 104 | callback(new errors.ObjectNotFound(objectUri)); 105 | } 106 | }); 107 | } 108 | 109 | /** 110 | * List all the objects in the object registry. 111 | */ 112 | function list(callback) { 113 | var keyList = Object.keys(registry), 114 | result = []; 115 | 116 | for (var i=0; i < keyList.length; i++) { 117 | result.push(registry[keyList[i]]); 118 | } 119 | 120 | callback(null, result); 121 | } 122 | 123 | /** 124 | * Modify the object represented by the given URI, by setting the selected resource to the given value. If the resource 125 | * doesn't exist it is created. If it does exist, its value is overwritten. 126 | * 127 | * @param {String} objectUri String representation of an OMA LWTM2M Object URI 128 | * (with object Type and Id) 129 | * @param {Integer} resourceId Id of the resource to set 130 | * @param {String} resourceValue New value of the resource 131 | */ 132 | function setResource(objectUri, resourceId, resourceValue, callback) { 133 | getObject(objectUri, function(error, retrievedObject) { 134 | if (error) { 135 | callback(error); 136 | } else { 137 | registryBus.emit(objectUri, 'setResource', resourceId, resourceValue); 138 | retrievedObject.attributes[resourceId] = resourceValue; 139 | callback(null, retrievedObject); 140 | } 141 | }); 142 | } 143 | 144 | /** 145 | * Modify the object represented by the given URI, by removing the selected resource. 146 | * 147 | * @param objectUri String representation of an OMA LWTM2M Object URI (with object Type and Id) 148 | * @param {Integer} resourceId Id of the resource to remove 149 | */ 150 | function unsetResource(objectUri, resourceId, callback) { 151 | getObject(objectUri, function(error, retrievedObject) { 152 | if (error) { 153 | callback(error); 154 | } else { 155 | delete retrievedObject.attributes[resourceId]; 156 | callback(null, retrievedObject); 157 | } 158 | }); 159 | } 160 | 161 | /** 162 | * Removes all information an handlers from the registry, setting it back to its original state. 163 | */ 164 | function resetRegistry(callback) { 165 | registryBus.removeAllListeners(); 166 | registry = {}; 167 | 168 | attributesRegistry = {}; 169 | 170 | if (callback) { 171 | callback(); 172 | } else { 173 | return; 174 | } 175 | } 176 | 177 | /** 178 | * Set the attribute set passed as a parameter as the attributes for the object type, instance or resource indicated. 179 | * The set of attributes is completely overwritten by the new one. 180 | * 181 | * @param {String} uri URI of an object type, instance or resource. 182 | * @param {String} attributes Object containing the new set of attributes for the selected entity. 183 | */ 184 | function setAttributes(uri, attributes, callback) { 185 | attributesRegistry[uri] = attributes; 186 | 187 | if (callback) { 188 | callback(); 189 | } else { 190 | return; 191 | } 192 | } 193 | 194 | /** 195 | * Get the current set of attributes for the given entity (object type, instance or resource). 196 | * 197 | * @param {String} uri URI of an object type, instance or resource. 198 | * @returns {Object} An object containing the current attributes or undefined if there are no attributes. 199 | */ 200 | function getAttributes(uri, callback) { 201 | if (callback) { 202 | callback(null, attributesRegistry[uri]); 203 | } else { 204 | return attributesRegistry[uri]; 205 | } 206 | } 207 | 208 | exports.create = create; 209 | exports.remove = remove; 210 | exports.get = getObject; 211 | exports.setResource = setResource; 212 | exports.unsetResource = unsetResource; 213 | exports.setAttributes = setAttributes; 214 | exports.getAttributes = getAttributes; 215 | exports.list = list; 216 | exports.bus = registryBus; 217 | exports.reset = resetRegistry; -------------------------------------------------------------------------------- /lib/services/client/write.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var async = require('async'), 27 | apply = async.apply, 28 | objectRegistry = require('./objectRegistry'), 29 | coapUtils = require('../coapUtils'); 30 | 31 | /** 32 | * Invoke the user handler for this operation, with all the information from the query parameters as its arguments. 33 | * 34 | * @param {Object} queryParams Object containing all the query parameters. 35 | * @param {Function} handler User handler to be invoked. 36 | */ 37 | function applyHandler(resourceId, attributeValue, handler, storedObject, callback) { 38 | handler(storedObject.objectType, storedObject.objectId, resourceId, attributeValue, callback); 39 | } 40 | 41 | /** 42 | * Generates the end of request handler that will generate the final response to the COAP Client. 43 | * 44 | * @param {Object} req Arriving COAP Request to be handled. 45 | * @param {Object} res Outgoing COAP Response. 46 | * @returns {Function} Request handler, receiving an optional error and the write operation result. 47 | */ 48 | function endWrite(req, res) { 49 | return function (error, result) { 50 | if (error) { 51 | res.code = error.code; 52 | res.end(''); 53 | } else { 54 | res.code = '2.04'; 55 | res.end(''); 56 | } 57 | }; 58 | } 59 | 60 | function writeAttributes(objectUri, resourceId, query, callback) { 61 | function split(pair) { 62 | return pair.split('='); 63 | } 64 | 65 | function group(previous, current) { 66 | if (current && current.length === 2) { 67 | previous[current[0]] = current[1]; 68 | } 69 | 70 | return previous; 71 | } 72 | 73 | var attributes = query.split('&').map(split).reduce(group, {}); 74 | 75 | if (resourceId) { 76 | objectRegistry.setAttributes(objectUri + '/' + resourceId, attributes, callback); 77 | } else { 78 | objectRegistry.setAttributes(objectUri, attributes, callback); 79 | } 80 | } 81 | 82 | function write(objectUri, resourceId, payload, handler, callback) { 83 | async.waterfall([ 84 | apply(objectRegistry.setResource, objectUri, resourceId, payload), 85 | apply(applyHandler, resourceId, payload, handler) 86 | ], callback); 87 | } 88 | 89 | /** 90 | * Handle the write operation. 91 | * 92 | * @param {Object} req Arriving COAP Request to be handled. 93 | * @param {Object} res Outgoing COAP Response. 94 | * @param {Function} handler User handler to be executed if everything goes ok. 95 | */ 96 | function handleWrite(req, res, handler) { 97 | coapUtils.extractUriInfo(req, res, function (error, objectUri, resourceId, payload) { 98 | if (error) { 99 | endWrite(req, res)(error); 100 | } else { 101 | if (req.url.indexOf('?') > 0) { 102 | writeAttributes(objectUri, resourceId, req.urlObj.query, endWrite(req, res)); 103 | } else { 104 | write(objectUri, resourceId, payload, handler, endWrite(req, res)); 105 | } 106 | } 107 | }); 108 | } 109 | 110 | exports.handle = handleWrite; -------------------------------------------------------------------------------- /lib/services/coapRouter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var libcoap = require('coap'), 27 | logger = require('logops'), 28 | errors = require('../errors'), 29 | defaultFormats = require('./OMAContentFormats'), 30 | Window = require('./slidingWindow'), 31 | context = { 32 | op: 'LWM2MLib.COAPRouter' 33 | }; 34 | 35 | /** 36 | * Handles the arrival of a request to the LWTM2M Server. To do so, it loops through the routes table, trying to match 37 | * the pathname and method of the request to an existing route. If a route matches, and the route has a handler, 38 | * the handler is invoked with the request, response and user handler for that operation. Otherwise, a 4.04 error is 39 | * returned. 40 | * 41 | * @param {Object} serverInfo Object containing all the information of the current server. 42 | */ 43 | function dataHandler(serverInfo) { 44 | return function(req, res) { 45 | if (req.code === '0.00' && req._packet.confirmable && req.payload.length === 0) { 46 | res.reset(); 47 | } else if (serverInfo.window.contains(req._packet.messageId)) { 48 | logger.debug(context, 'Discarding duplicate package [%s] on url [%s] with messageId [%d]', 49 | req.method, req.url, req._packet.messageId); 50 | } else { 51 | serverInfo.window.push(req._packet.messageId); 52 | 53 | logger.debug(context, 'Handling request with method [%s] on url [%s] with messageId [%d]', 54 | req.method, req.url, req._packet.messageId); 55 | 56 | req.urlObj = require('url').parse(req.url); 57 | 58 | for (var i in serverInfo.routes) { 59 | if (req.method === serverInfo.routes[i][0] && req.urlObj.pathname.match(serverInfo.routes[i][1])) { 60 | serverInfo.handlers[serverInfo.routes[i][2]] 61 | .lib(req, res, serverInfo.handlers[serverInfo.routes[i][2]].user); 62 | return; 63 | } 64 | } 65 | } 66 | }; 67 | } 68 | 69 | function defaultHandler() { 70 | var callback = null; 71 | 72 | for (var i=0; i < arguments.length; i++) { 73 | if (arguments[i] instanceof Function) { 74 | callback = arguments[i]; 75 | } 76 | } 77 | 78 | callback(); 79 | } 80 | 81 | /** 82 | * Load the COAP Content Formats and numbers used in LWM2M. 83 | */ 84 | function loadLightweightM2MFormats(config) { 85 | var formats; 86 | 87 | if (config.formats) { 88 | formats = config.formats; 89 | } else if (defaultFormats && defaultFormats.formats) { 90 | formats = defaultFormats.formats; 91 | config.formats = formats; 92 | } else { 93 | throw new errors.ContentFormatNotFound(); 94 | } 95 | 96 | if (!config.writeFormat && defaultFormats && defaultFormats.writeFormat) { 97 | config.writeFormat = defaultFormats.writeFormat; 98 | } 99 | 100 | for (var i = 0; i < formats.length; i++) { 101 | libcoap.registerFormat(formats[i].name, formats[i].value); 102 | } 103 | } 104 | 105 | /** 106 | * Start the Lightweight M2M Server. This server module is a singleton, no multiple instances can be started (invoking 107 | * start multiple times without invoking stop can have unexpected results). 108 | * 109 | * @param {Object} config Configuration object including all the information needed for starting the server. 110 | */ 111 | function startCoap(config, callback) { 112 | var serverInfo = { 113 | server: null, 114 | routes: [], 115 | handlers: null 116 | }; 117 | 118 | if (config.udpWindow) { 119 | serverInfo.window = new Window(config.udpWindow); 120 | } else { 121 | serverInfo.window = new Window(100); 122 | } 123 | 124 | logger.info(context, 'Starting COAP Server on port [%d]', config.port); 125 | 126 | loadLightweightM2MFormats(config); 127 | 128 | serverInfo.server = libcoap.createServer({ 129 | type: config.serverProtocol, 130 | proxy: true, 131 | piggybackReplyMs: 500 132 | }); 133 | 134 | serverInfo.server.on('request', dataHandler(serverInfo)); 135 | 136 | serverInfo.server.on('error', function(error) { 137 | logger.error(context, 'An error occurred creating COAP listener: %j', error); 138 | callback(error); 139 | }); 140 | 141 | serverInfo.server.listen(config.port, function (error) { 142 | if (error) { 143 | logger.error(context, 'Couldn\'t start COAP server: %s', error); 144 | } else { 145 | logger.info(context, 'COAP Server started successfully'); 146 | } 147 | 148 | callback(error, serverInfo); 149 | }); 150 | } 151 | 152 | /** 153 | * Stops the LWTM2M Server. 154 | * 155 | * @param {Object} serverInfo Object containing all the information of the current server. 156 | */ 157 | function stopCoap(serverInfo, callback) { 158 | logger.info(context, 'Stopping COAP Server'); 159 | 160 | if (serverInfo.server) { 161 | serverInfo.server.close(callback); 162 | } else { 163 | logger.error(context, 'Tried to close an unexistent server'); 164 | callback(); 165 | } 166 | } 167 | 168 | /** 169 | * Sets the handler callback for a given type of operation. If for a given type no handler is provided, a default 170 | * dummy handler will be used. 171 | * 172 | * The signature of the handler will depend on the operation being handled. The complete list of operations and the 173 | * signature of its handlers can be found in the online documentation. 174 | * 175 | * @param {Object} serverInfo Object containing all the information of the current server. 176 | * @param {String} type Name of the operation to be handled. 177 | * @param {Function} handler Operation handler. 178 | */ 179 | function setHandler(serverInfo, type, handler) { 180 | logger.debug(context, 'Setting [%s] handler', type); 181 | serverInfo.handlers[type].user = handler; 182 | } 183 | 184 | exports.start = startCoap; 185 | exports.setHandler = setHandler; 186 | exports.stop = stopCoap; 187 | exports.defaultHandler = defaultHandler; 188 | -------------------------------------------------------------------------------- /lib/services/coapUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var errors = require('../errors'), 27 | logger = require('logops'), 28 | context = { 29 | op: 'LWM2MLib.COAPUtils' 30 | }; 31 | 32 | /** 33 | * Extract Object type, id and payload from the request URI, returning it using the callback. 34 | * 35 | * @param {Object} req Arriving COAP Request to be handled. 36 | * @param {Object} res Outgoing COAP Response. 37 | */ 38 | 39 | function extractUriInfo(req, res, callback) { 40 | var element, 41 | elementList = [], 42 | objectType, 43 | objectInstance, 44 | resourceId, 45 | objectUri, 46 | payload = req.payload.toString('utf8'), 47 | currentPath = req.urlObj.pathname; 48 | 49 | /* jshint -W084 */ 50 | while (element = currentPath.match(/\/\d+/)) { 51 | elementList.push(element[0].substr(1)); 52 | currentPath = currentPath.substr(element[0].length); 53 | } 54 | /* jshint +W084 */ 55 | 56 | objectType = elementList[0]; 57 | objectInstance = elementList[1]; 58 | resourceId = elementList[2]; 59 | 60 | if (objectInstance) { 61 | objectUri = '/' + objectType + '/' + objectInstance; 62 | } else { 63 | objectUri = '/' + objectType; 64 | } 65 | 66 | callback(null, objectUri, resourceId, payload); 67 | } 68 | 69 | /** 70 | * Extract the query parameters from a COAP request, creating a JS Object with them. The function can be executed both 71 | * synchronously (if no callback is provided) or asynchronously. 72 | * 73 | * @param {Object} req COAP Request to process. 74 | * @param {Function} callback Callback function (optional). The second parameter contains the query object. 75 | * 76 | * @returns {Object} Query parameters object. 77 | */ 78 | function extractQueryParams(req, callback) { 79 | var queryParams; 80 | 81 | logger.debug(context, 'Extracting query parameters from request'); 82 | 83 | function extractAsObject(previous, current) { 84 | var fields = current.split('='); 85 | 86 | previous[fields[0]] = fields[1]; 87 | 88 | return previous; 89 | } 90 | 91 | if (!req.urlObj) { 92 | req.urlObj = require('url').parse(req.url); 93 | } 94 | 95 | if (req.urlObj.query) { 96 | logger.debug(context, 'Processing query [%s]', req.urlObj.query); 97 | 98 | queryParams = req.urlObj.query.split('&'); 99 | } else { 100 | queryParams = []; 101 | } 102 | 103 | if (callback) { 104 | callback(null, queryParams.reduce(extractAsObject, {})); 105 | } else { 106 | return queryParams.reduce(extractAsObject, {}); 107 | } 108 | } 109 | 110 | /** 111 | * Checks that all the mandatory query parameters are present in the Query Parameters object. If any parameter is not 112 | * present, the callback is invoked with a BadRequestError, indicating the missing parameters. 113 | * 114 | * @param {Object} queryParams Query Parameters object. 115 | */ 116 | function checkMandatoryQueryParams(mandatoryQueryParams, queryParams, callback) { 117 | var missing = []; 118 | 119 | logger.debug(context, 'Checking for the existence of the following parameters [%j]', mandatoryQueryParams); 120 | 121 | for (var p in mandatoryQueryParams) { 122 | var found = false; 123 | 124 | for (var i in queryParams) { 125 | if (queryParams.hasOwnProperty(i)) { 126 | if (i === mandatoryQueryParams[p]) { 127 | found = true; 128 | } 129 | } 130 | } 131 | 132 | if (!found) { 133 | missing.push(mandatoryQueryParams[p]); 134 | } 135 | } 136 | 137 | if (missing.length !== 0) { 138 | var error = new errors.BadRequestError('Missing query params: '); 139 | error.code = '4.00'; 140 | 141 | logger.debug(context, 'Missing parameters found [%j]', missing); 142 | callback(error); 143 | } else { 144 | callback(); 145 | } 146 | } 147 | 148 | exports.extractQueryParams = extractQueryParams; 149 | exports.checkMandatoryQueryParams = checkMandatoryQueryParams; 150 | exports.extractUriInfo = extractUriInfo; 151 | -------------------------------------------------------------------------------- /lib/services/model/Device.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of fiware-iotagent-lib 5 | * 6 | * fiware-iotagent-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * fiware-iotagent-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with fiware-iotagent-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::daniel.moranjimenez@telefonica.com 22 | * 23 | * Modified by: Daniel Calvo - ATOS Research & Innovation 24 | */ 25 | 'use strict'; 26 | 27 | var mongoose = require('mongoose'), 28 | Schema = mongoose.Schema, 29 | autoIncrement = require('mongoose-plugin-autoinc'); 30 | 31 | var Device = new Schema({ 32 | id: Number, 33 | name: String, 34 | address: String, 35 | path: String, 36 | port: Number, 37 | type: String, 38 | lifetime: String, 39 | creationDate: { type: Date, default: Date.now } 40 | }); 41 | 42 | Device.plugin(autoIncrement.plugin, { model: 'Device', field: 'id'}); 43 | 44 | function load(db) { 45 | module.exports.model = db.model('Device', Device); 46 | } 47 | 48 | module.exports.internalSchema = Device; 49 | module.exports.load = load; 50 | -------------------------------------------------------------------------------- /lib/services/model/dbConn.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of fiware-iotagent-lib 5 | * 6 | * fiware-iotagent-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * fiware-iotagent-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with fiware-iotagent-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::daniel.moranjimenez@telefonica.com 22 | */ 23 | 'use strict'; 24 | 25 | /** 26 | * This module sets up the connection with the mongodb through mongoose. This connection will be used 27 | * in mongoose schemas to persist objects. 28 | */ 29 | 30 | var mongoose = require('mongoose'), 31 | constants = require('../../constants'), 32 | logger = require('logops'), 33 | async = require('async'), 34 | errors = require('../../errors'), 35 | config = require('../../commonConfig'), 36 | defaultDb, 37 | DEFAULT_DB_NAME = 'lwm2m', 38 | context = { 39 | op: 'LWM2MLib.DbConn' 40 | }; 41 | 42 | function loadModels() { 43 | require('./Device').load(defaultDb); 44 | } 45 | 46 | /** 47 | * Creates a new connection to the Mongo DB. 48 | * 49 | * @this Reference to the dbConn module itself. 50 | */ 51 | function init(host, db, port, username, password, options, callback) { 52 | /*jshint camelcase:false, validthis:true */ 53 | var hosts, 54 | url, 55 | credentials = '', 56 | retries = 0, 57 | lastError, 58 | maxRetries = (config.getConfig().mongodb && config.getConfig().mongodb.retries || 59 | constants.DEFAULT_MONGODB_RETRIES); 60 | 61 | if (username && password) { 62 | credentials = username + ':' + password + '@'; 63 | } 64 | 65 | function addPort(item) { 66 | return item + ':' + port; 67 | } 68 | 69 | function commaConcat(previous, current, currentIndex) { 70 | if (currentIndex !== 0) { 71 | previous += ','; 72 | } 73 | 74 | previous += current; 75 | 76 | return previous; 77 | } 78 | 79 | hosts = host.split(',') 80 | .map(addPort) 81 | .reduce(commaConcat, ''); 82 | 83 | url = 'mongodb://' + credentials + hosts + '/' + db; 84 | 85 | if (options.replicaSet) { 86 | url += '?replicaSet=' + options.replicaSet.rs_name; 87 | } 88 | 89 | function createConnectionHandler(error, results) { 90 | if (defaultDb) { 91 | logger.info(context, 'Successfully connected to MongoDB.'); 92 | module.exports.db = defaultDb; 93 | loadModels(); 94 | } else { 95 | logger.error(context, 'MONGODB-002: Error found after [%d] attempts: %s', retries, error || lastError); 96 | } 97 | 98 | callback(error); 99 | } 100 | 101 | function retryCheck() { 102 | return !defaultDb && retries < maxRetries; 103 | } 104 | 105 | function connectionAttempt(url, options, callback) { 106 | var candidateDb; 107 | 108 | logger.info(context, 'Attempting to connect to MongoDB instance. Attempt %d', retries); 109 | 110 | options.useNewUrlParser = true; 111 | options.useFindAndModify = false; 112 | mongoose.set('useCreateIndex', true); 113 | candidateDb = mongoose.createConnection(url, options, function(error, result) { 114 | if (error) { 115 | logger.error(context, 'MONGODB-001: Error trying to connect to MongoDB: %s', error); 116 | lastError = error; 117 | } else { 118 | defaultDb = candidateDb; 119 | 120 | defaultDb.on('error', function(error) { 121 | logger.error(context, 'Mongo Driver error: %j', error); 122 | }); 123 | defaultDb.on('connecting', function(error) { 124 | logger.debug(context, 'Mongo Driver connecting'); 125 | }); 126 | defaultDb.on('connected', function() { 127 | logger.debug(context, 'Mongo Driver connected'); 128 | }); 129 | defaultDb.on('reconnected', function() { 130 | logger.debug(context, 'Mongo Driver reconnected'); 131 | }); 132 | defaultDb.on('disconnected', function() { 133 | logger.debug(context, 'Mongo Driver disconnected'); 134 | }); 135 | defaultDb.on('disconnecting', function() { 136 | logger.debug(context, 'Mongo Driver disconnecting'); 137 | }); 138 | defaultDb.on('open', function() { 139 | logger.debug(context, 'Mongo Driver open'); 140 | }); 141 | defaultDb.on('close', function() { 142 | logger.debug(context, 'Mongo Driver close'); 143 | }); 144 | } 145 | 146 | callback(); 147 | }); 148 | } 149 | 150 | function tryCreateConnection(callback) { 151 | var attempt = async.apply(connectionAttempt, url, options, callback), 152 | seconds = (config.getConfig().mongodb && config.getConfig().mongodb.retryTime || 153 | constants.DEFAULT_MONGODB_RETRY_TIME); 154 | 155 | retries++; 156 | 157 | if (retries === 1) { 158 | logger.info(context, 'First connection attempt'); 159 | attempt(); 160 | } else { 161 | logger.info(context, 'Waiting %d seconds before attempting again.', seconds); 162 | setTimeout(attempt, seconds * 1000); 163 | } 164 | } 165 | 166 | defaultDb = null; 167 | async.whilst(retryCheck, tryCreateConnection, createConnectionHandler); 168 | } 169 | 170 | function configureDb(callback) { 171 | /*jshint camelcase:false, validthis:true */ 172 | var currentConfig = config.getConfig(); 173 | 174 | if (currentConfig.deviceRegistry && currentConfig.deviceRegistry.type === 'mongodb') { 175 | if (!currentConfig.mongodb || !currentConfig.mongodb.host) { 176 | logger.fatal(context, 'MONGODB-003: No host found for MongoDB driver.'); 177 | callback(new errors.BadConfiguration('No host found for MongoDB driver')); 178 | } else { 179 | var dbName = currentConfig.mongodb.db, 180 | port = currentConfig.mongodb.port || 27017, 181 | options = {}; 182 | 183 | if (!currentConfig.mongodb.db) { 184 | dbName = DEFAULT_DB_NAME; 185 | } 186 | 187 | if (currentConfig.mongodb.replicaSet) { 188 | options.replset = { rs_name: currentConfig.mongodb.replicaSet }; 189 | } 190 | 191 | init(config.getConfig().mongodb.host, dbName, port, 192 | currentConfig.username, currentConfig.password, options, callback); 193 | } 194 | } else { 195 | callback(); 196 | } 197 | } 198 | 199 | exports.configureDb = configureDb; 200 | exports.db = defaultDb; 201 | exports.DEFAULT_DB_NAME = DEFAULT_DB_NAME; 202 | -------------------------------------------------------------------------------- /lib/services/object/device.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | * 23 | * Author: Alexandre Moreno 24 | */ 25 | 26 | 'use strict'; 27 | 28 | var Schema = require('./schema'); 29 | 30 | // add remaining optional resources for completeness 31 | module.exports = new Schema('Device', { 32 | manufacturer: { type: String, id: 0 }, 33 | deviceType: { type: String, id: 17 }, 34 | modelNumber: { type: String, id: 1 }, 35 | serialNumber: { type: String, id: 2 }, 36 | hardwareVer: { type: String, id: 18 }, 37 | firmwareVer: { type: String, id: 3 }, 38 | softwareVer: { type: String, id: 19 }, 39 | powerSrcs: [{ type: Number, id: 6 }], 40 | srcVoltage: [{ type: Number, id: 7 }], 41 | srcCurrent: [{ type: Number, id: 8 }], 42 | batteryLevel: { type: Number, id: 9 }, 43 | memoryFree: { type: Number, id: 10 }, 44 | errorCode: [{ type: Number, id: 11 }], 45 | currentTime: { type: Number, id: 13 }, 46 | utcOffset: { type: String, id: 14 }, 47 | timeZone: { type: String, id: 15 } 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /lib/services/object/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | * 23 | * Author: Alexandre Moreno 24 | */ 25 | 26 | exports.Schema = require('./schema'); 27 | exports.schemas = { 28 | device: require('./device'), 29 | security: require('./security'), 30 | server: require('./server') 31 | }; 32 | -------------------------------------------------------------------------------- /lib/services/object/schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | * 23 | * Author: Alexandre Moreno 24 | */ 25 | 26 | 'use strict'; 27 | 28 | function matches(val, type) { 29 | function typeOf(val) { 30 | return Object.prototype.toString.call(val).slice(8, -1); 31 | } 32 | 33 | if (type === 'Buffer') { 34 | return Buffer.isBuffer(val); 35 | } 36 | 37 | if (!Array.isArray(type)) { 38 | return type === typeOf(val); 39 | } 40 | 41 | if (Array.isArray(val)) { 42 | return matches(val[0], type[0]); 43 | } 44 | 45 | return false; 46 | } 47 | 48 | function isObject(o) { 49 | return Object.prototype.toString.call(o) === '[object Object]'; 50 | } 51 | 52 | function validResourceType(type) { 53 | return ['String', 'Number', 'Boolean', 'Buffer'].indexOf(type) > -1; 54 | } 55 | 56 | function validateProps(type, id) { 57 | return validResourceType(type) && typeof id === 'number'; 58 | } 59 | 60 | 61 | 62 | function Schema(name, resources) { 63 | this.name = name; 64 | this.resources = {}; 65 | this.resourceNames = {}; 66 | 67 | for (var key in resources) { 68 | if (resources.hasOwnProperty(key)) { 69 | 70 | var ok = false, 71 | res = resources[key]; 72 | 73 | if (isObject(res)) { 74 | ok = validateProps(res.type.name, res.id); 75 | 76 | this.resources[key] = { 77 | type: res.type.name, 78 | id: res.id, 79 | required: res.required || false 80 | }; 81 | 82 | this.resourceNames[res.id] = key; 83 | } 84 | 85 | if (Array.isArray(res)) { 86 | var type = res[0].type, 87 | id = res[0].id, 88 | required = res[0].required || false; 89 | 90 | ok = validateProps(type.name, id); 91 | 92 | this.resources[key] = { 93 | type: [type.name], 94 | id: id, 95 | required: required || false 96 | }; 97 | 98 | this.resourceNames[id] = key; 99 | } 100 | 101 | if (!ok) { 102 | throw new TypeError('Invalid resource `' + key + '`'); 103 | } 104 | } 105 | } 106 | } 107 | 108 | Schema.prototype.resourceNameById = function(id) { 109 | return this.resourceNames[id]; 110 | }; 111 | 112 | Schema.prototype.validateResource = function(key, val) { 113 | var name = key; 114 | 115 | if (!this.resources[key]) { 116 | name = this.resourceNameById(key); 117 | } 118 | 119 | if (!name) { 120 | throw new TypeError('Invalid resource `' + key + '`'); 121 | } 122 | 123 | var type = this.resources[name].type; 124 | var ok = matches(val, type); 125 | 126 | if (!ok) { 127 | throw new TypeError('Invalid type for `' + key + '`'); 128 | } 129 | }; 130 | 131 | Schema.prototype.validate = function(obj) { 132 | var keys = Object.keys(this.resources); 133 | 134 | for (var i = 0; i < keys.length; ++i) { 135 | var key = keys[i], 136 | type = this.resources[key].type, 137 | id = this.resources[key].id, 138 | required = this.resources[key].required, 139 | val, ok; 140 | 141 | val = obj[key] === undefined ? obj[id] : obj[key]; 142 | ok = matches(val, type); 143 | 144 | if (val !== undefined && !ok) { 145 | throw new TypeError('Invalid resource `' + key + '`'); 146 | } 147 | 148 | if (required && !ok) { 149 | throw new TypeError('Missing resource `' + key + '`'); 150 | } 151 | } 152 | }; 153 | 154 | module.exports = Schema; 155 | -------------------------------------------------------------------------------- /lib/services/object/security.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | * 23 | * Author: Alexandre Moreno 24 | */ 25 | 26 | 'use strict'; 27 | 28 | var Schema = require('./schema'); 29 | 30 | // note: these are only the mandatory resources 31 | // add optional resources for completeness 32 | module.exports = new Schema('LWM2M Security', { 33 | uri: { type: String, id: 0, required: true }, 34 | bootstrap: { type: Boolean, id: 1, required: true }, 35 | mode: { type: Number, id: 2, required: true }, 36 | clientCert: { type: Buffer, id: 3, required: true }, 37 | serverId: { type: Number, id: 10, required: true }, 38 | serverCert: { type: Buffer, id: 4, required: true }, 39 | secret: { type: Buffer, id: 5, required: true }, 40 | }); 41 | -------------------------------------------------------------------------------- /lib/services/object/senml.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | * 23 | * Author: Alexandre Moreno 24 | */ 25 | 26 | 'use strict'; 27 | 28 | var logger = require('logops'), 29 | context = { 30 | op: 'LWM2MLib.BootstrapServer' 31 | }; 32 | 33 | function serializeToString(obj, schema) { 34 | var result = { e: [] }, 35 | keys = Object.keys(obj), 36 | res = schema.resources; 37 | 38 | function append(arr, key, value) { 39 | var types = { 40 | String: function() { 41 | arr.push({ n: key, sv: value }); 42 | }, 43 | Number: function() { 44 | arr.push({ n: key, v: value }); 45 | }, 46 | Boolean: function() { 47 | arr.push({ n: key, bv: value }); 48 | }, 49 | Buffer: function() { 50 | arr.push({ 51 | n: key, 52 | sv: value.toString('base64') 53 | }); 54 | }, 55 | Array: function() { 56 | for (var i = 0; i < value.length; i++) { 57 | append(arr, key + '/' + i, value[i]); 58 | } 59 | } 60 | }; 61 | 62 | function skip () { 63 | logger.warn(context, 'Skipping resource with invalid type %s', typeof value); 64 | } 65 | 66 | var type = Object.prototype.toString.call(value).slice(8, -1); 67 | 68 | if (Buffer.isBuffer(value)) { 69 | type = Buffer.name; 70 | } 71 | 72 | (types[type] || skip)(); 73 | 74 | return arr; 75 | } 76 | 77 | 78 | if (keys.length === 1) { // single resource 79 | schema.validateResource(keys[0], obj[keys[0]]); 80 | } else { // whole object 81 | schema.validate(obj); 82 | } 83 | 84 | for (var i = 0; i < keys.length; ++i) { 85 | var key = keys[i], 86 | id = res[key] ? res[key].id : key; 87 | 88 | append(result.e, String(id), obj[key]); 89 | } 90 | 91 | return JSON.stringify(result); 92 | } 93 | 94 | function parse(payload, schema) { 95 | var result = {}, 96 | obj = {}; 97 | 98 | function append(obj, key, type, value) { 99 | var types = { 100 | String: function() { 101 | obj[key] = value.sv; 102 | }, 103 | Number: function() { 104 | obj[key] = value.v; 105 | }, 106 | Boolean: function() { 107 | obj[key] = value.bv; 108 | }, 109 | Buffer: function() { 110 | obj[key] = Buffer.from(value.sv, 'base64'); 111 | }, 112 | }; 113 | 114 | 115 | if (Array.isArray(type)) { 116 | var id = value.n.split('/')[1]; 117 | 118 | if (!obj[key]) { 119 | obj[key] = []; 120 | } 121 | 122 | append(obj[key], id, type[0], value); 123 | } else { 124 | (types[type])(); 125 | } 126 | 127 | if (obj[key] === undefined) { 128 | throw new Error('JSON payload does not match ' + 129 | schema.name + ' definition'); 130 | } 131 | 132 | return obj; 133 | } 134 | 135 | obj = JSON.parse(payload); 136 | 137 | if (!Array.isArray(obj.e)) { 138 | throw new Error('Invalid JSON payload'); 139 | } 140 | 141 | for (var i = 0; i < obj.e.length; ++i) { 142 | var key, type; 143 | 144 | key = schema.resourceNameById(obj.e[i].n.split('/')[0]); 145 | 146 | // skip resources not defined in schema. 147 | if (!key) { 148 | continue; 149 | } 150 | 151 | type = schema.resources[key].type; 152 | 153 | append(result, key, type, obj.e[i]); 154 | } 155 | 156 | return result; 157 | } 158 | 159 | exports.serialize = serializeToString; 160 | exports.parse = parse; 161 | -------------------------------------------------------------------------------- /lib/services/object/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | * 23 | * Author: Alexandre Moreno 24 | */ 25 | 26 | 'use strict'; 27 | 28 | var Schema = require('./schema'); 29 | 30 | module.exports = new Schema('LWM2M Server', { 31 | serverId: { type: Number, id: 0, required: true }, 32 | lifetime: { type: Number, id: 1, required: true }, 33 | notifStoring: { type: Boolean, id: 6, required: true}, 34 | binding: { type: String, id:7, required: true } 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /lib/services/server/coapUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var coap = require('coap'), 27 | Readable = require('stream').Readable, 28 | errors = require('../../errors'), 29 | config; 30 | 31 | function isObserveAction(res) { 32 | var observeFlag = false; 33 | 34 | for (var i = 0; i < res.options.length; i++) { 35 | if (res.options[i].name === 'Observe') { 36 | observeFlag = true; 37 | } 38 | } 39 | return observeFlag; 40 | } 41 | 42 | function readResponse(res, callback) { 43 | var data = ''; 44 | 45 | res.on('data', function (chunk) { 46 | data += chunk; 47 | }); 48 | 49 | res.on('error', function(error) { 50 | callback(new errors.ClientResponseError(error)); 51 | }); 52 | 53 | res.on('end', function(chunk) { 54 | if (chunk) { 55 | data += chunk; 56 | } 57 | callback(null, res); 58 | }); 59 | } 60 | 61 | /** 62 | * Send the COAP Request passed as a parameter. If the request contains a parameter "payload", the parameter is sent 63 | * as the payload of the request; otherwise, the request is sent without any payload. 64 | * 65 | * @param {Object} request Object containing all the request information (in the Node COAP format). 66 | */ 67 | function sendRequest(request, callback) { 68 | var agent = new coap.Agent({type: config.serverProtocol}), 69 | req = agent.request(request), 70 | rs = new Readable(); 71 | 72 | req.on('response', function(res) { 73 | if (isObserveAction(res)) { 74 | callback(null, res); 75 | } else { 76 | readResponse(res, callback); 77 | } 78 | }); 79 | 80 | req.on('error', function(error) { 81 | callback(new errors.ClientConnectionError(error)); 82 | }); 83 | 84 | if (request.payload) { 85 | rs.push(request.payload); 86 | rs.push(null); 87 | rs.pipe(req); 88 | } else { 89 | req.end(); 90 | } 91 | } 92 | 93 | /** 94 | * Generates a generic response processing callback for all the resource based operations. 95 | * 96 | * @param {String} objectType ID of the type of object. 97 | * @param {String} objectId ID of the instance where the operation was performed. 98 | * @param code Return code if the callback is successful. 99 | * @returns {processResponse} The generated handler. 100 | */ 101 | function generateProcessResponse(objectType, objectId, resourceId, code) { 102 | return function processResponse(res, callback) { 103 | if (res.code === code) { 104 | callback(null, res.payload.toString('utf8')); 105 | } else if (res.code === '4.04') { 106 | callback(new errors.ResourceNotFound()); 107 | } else { 108 | callback(new errors.ClientError(res.code)); 109 | } 110 | }; 111 | } 112 | 113 | function init(newConfig) { 114 | config = newConfig; 115 | } 116 | 117 | exports.generateProcessResponse = generateProcessResponse; 118 | exports.sendRequest = sendRequest; 119 | exports.init = init; -------------------------------------------------------------------------------- /lib/services/server/inMemoryDeviceRegistry.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var registry = {}, 27 | idCounter = 1, 28 | errors = require('../../errors'), 29 | _ = require('underscore'), 30 | logger = require('logops'), 31 | context = { 32 | op: 'LWM2MLib.MemoryDeviceRegistry' 33 | }, 34 | checkLifetimeInterval; 35 | 36 | /** 37 | * Gets the device that has the device name passed as a parameter (should be unique) or return a DeviceNotFound error 38 | * in case none exist. 39 | * 40 | * @param {String} deviceName Name of the device to retrieve. 41 | */ 42 | function getByName(deviceName, callback) { 43 | var result; 44 | 45 | for(var key in registry) { 46 | if (registry[key] && registry[key].name === deviceName) { 47 | result = registry[key]; 48 | } 49 | } 50 | 51 | if (result) { 52 | callback(null, result); 53 | } else { 54 | callback(new errors.DeviceNotFound(deviceName)); 55 | } 56 | } 57 | 58 | /** 59 | * Removes the object identified by this id from the registry. The removed object is passed as the first callback 60 | * parameter. 61 | * 62 | * @param {Integer} id Identifier of the object to be removed. 63 | */ 64 | function unregister(id, callback) { 65 | var obj = registry[id]; 66 | 67 | if (obj) { 68 | delete registry[id]; 69 | 70 | callback(null, obj); 71 | } else { 72 | callback(new errors.DeviceNotFound(id)); 73 | } 74 | } 75 | 76 | /** 77 | * Inserts the given object in the registry and removes the old registration. 78 | * The generated ID is returned through the callback. 79 | * 80 | * @param {Object} object Object to insert into the registry. 81 | */ 82 | function register(object, callback) { 83 | 84 | function save(cb) { 85 | var id = idCounter++; 86 | registry[id] = object; 87 | registry[id].id = id; 88 | return cb(null, id); 89 | } 90 | 91 | getByName(object.name, function(error, result){ 92 | if (!error && result) { 93 | unregister(result.id, function(err){ 94 | return save(callback); 95 | }); 96 | } 97 | else { 98 | return save(callback); 99 | } 100 | }); 101 | } 102 | 103 | /** 104 | * Remove all the objects from the registry. 105 | */ 106 | function clean(callback) { 107 | registry = {}; 108 | 109 | callback(); 110 | } 111 | 112 | /** 113 | * Retrieves from the registry the object identified by the given id. 114 | * 115 | * @param {String} id Id of the object to be retrieved. 116 | */ 117 | function getObject(id, callback) { 118 | if (registry[id]) { 119 | callback(null, _.clone(registry[id])); 120 | } else { 121 | callback(new errors.DeviceNotFound(id)); 122 | } 123 | } 124 | 125 | /** 126 | * Update the object identified with the given id with the object value passed as a parameter. 127 | * 128 | * @param {String} id Id of the object to update. 129 | * @param {Object} obj New object value to insert in the registry. 130 | */ 131 | function update(id, obj, callback) { 132 | if (registry[id]) { 133 | registry[id] = obj; 134 | callback(null, registry[id]); 135 | } else { 136 | callback(new errors.DeviceNotFound(id)); 137 | } 138 | } 139 | 140 | /** 141 | * Returns an array of all the devices as the parameter of the callback. 142 | */ 143 | function list(callback) { 144 | var result = []; 145 | 146 | for(var key in registry) { 147 | if (registry.hasOwnProperty(key)) { 148 | result.push(registry[key]); 149 | } 150 | } 151 | 152 | callback(null, result); 153 | } 154 | 155 | 156 | /** 157 | * Stops checking for device lifetime. 158 | */ 159 | function stopLifetimeCheck() { 160 | clearInterval(checkLifetimeInterval); 161 | checkLifetimeInterval = null; 162 | } 163 | 164 | /** 165 | * If Lifetime Resource exists, then the registration SHOULD be removed by the Server if a new registration or update 166 | * is not received within this lifetime. 167 | * 168 | * @param {Object} lifetimeCheckInterval Minimum interval between lifetime checks in ms 169 | * @param {Function} unregistrationHandler Unregistration device handler 170 | */ 171 | function checkLifetime(lifetimeCheckInterval, unregistrationHandler) { 172 | stopLifetimeCheck(); 173 | 174 | checkLifetimeInterval = setInterval(function(){ 175 | list(function(error, deviceList){ 176 | if (!error && deviceList) { 177 | deviceList.forEach(function(device){ 178 | if (device.lifetime && 179 | new Date() - new Date(device.creationDate) > Number(device.lifetime) * 1000) { 180 | unregister(device.id, function(err, obj){ 181 | if (err) { 182 | logger.debug(context, 183 | 'Lifetime unregistration for device [%s] ended up in error [%s] with code [%s]', 184 | obj.name, err.name, err.code); 185 | } 186 | else { 187 | logger.debug(context, 188 | 'Lifetime unregistration for device [%s] ended successfully', 189 | obj.name); 190 | 191 | if (unregistrationHandler) { 192 | unregistrationHandler(obj, function(){ 193 | 194 | }); 195 | } 196 | } 197 | }); 198 | } 199 | }); 200 | } 201 | }); 202 | }, lifetimeCheckInterval); 203 | } 204 | 205 | /** 206 | * Initializes the device registry based on the parameter found in the configuration. For this in memory registry this 207 | * function doesn't do anything. 208 | * 209 | * @param {Object} config Configuration object. 210 | */ 211 | function init(config, callback) { 212 | if (config.logLevel) { 213 | logger.setLevel(config.logLevel); 214 | } 215 | callback(null); 216 | } 217 | 218 | exports.register = register; 219 | exports.unregister = unregister; 220 | exports.get = getObject; 221 | exports.update = update; 222 | exports.clean = clean; 223 | exports.list = list; 224 | exports.checkLifetime = checkLifetime; 225 | exports.stopLifetimeCheck = stopLifetimeCheck; 226 | exports.getByName = getByName; 227 | exports.init = init; 228 | -------------------------------------------------------------------------------- /lib/services/server/informationReporting.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var async = require('async'), 27 | apply = async.apply, 28 | coapUtils = require('./coapUtils'), 29 | registry, 30 | logger = require('logops'), 31 | config, 32 | context = { 33 | op: 'LWM2MLib.InformationReporting' 34 | }, 35 | subscriptions = {}, 36 | _ = require('underscore'); 37 | 38 | /** 39 | * Generates an observer ID from its parts. The ID has the following pattern: 40 | * 41 | * :/// 42 | * 43 | * @param {Number} deviceId ID of the device, provided by the LWM2M server. 44 | * @param {Number} objectType ID of the object type. 45 | * @param {Number} objectId ID of the object instance. 46 | * @param {Number} resourceId Observed resource of the object. 47 | * @returns {string} The generated ID. 48 | */ 49 | function buildId(deviceId, objectType, objectId, resourceId) { 50 | return deviceId + ':/' + objectType + '/' + objectId + '/' + resourceId; 51 | } 52 | 53 | /** 54 | * Parse an observer ID, returning its parts encapsulated in an object. 55 | * 56 | * @param {String} idString Observer ID. 57 | * @returns {{deviceId: string, objectType: string, objectId: string, resourceId: string}} 58 | */ 59 | function parseId(idString) { 60 | var fieldList = idString.substring(idString.indexOf(':') + 2).split('/'); 61 | 62 | return { 63 | deviceId: idString.substring(0, idString.indexOf(':')), 64 | objectType: fieldList[0], 65 | objectId: fieldList[1], 66 | resourceId: fieldList[2] 67 | }; 68 | } 69 | 70 | /** 71 | * Constructs an object representing the subscription for the changes in the value of a particular resource of a 72 | * device. 73 | * 74 | * @param {Number} deviceId Internal ID of the device of the subscription (not the endpoint ID). 75 | * @param {Number} objectType Number identifying the object type. 76 | * @param {Number} objectId Number identifying the object instance. 77 | * @param {Number} resourceId Resource of the object to be subscribed to. 78 | * @param {Object} observeStream Stream object for writing into. 79 | * @param {Function} handler Handler to be called each time a new piece of data arrives. 80 | * @returns {{id: string, resource: string, deviceId: *, stream: *, dataHandler: Function, finishHandler: Function}} 81 | * @constructor 82 | */ 83 | function ResourceListener(deviceId, objectType, objectId, resourceId, observeStream, handler) { 84 | return { 85 | id: buildId(deviceId, objectType, objectId, resourceId), 86 | resource: '/' + objectType + '/' + objectId + '/' + resourceId, 87 | deviceId: deviceId, 88 | stream: observeStream, 89 | 90 | dataHandler: function (chunk) { 91 | logger.debug(context, 'New data on resource /%s/%s/%s in device [%d]', 92 | objectType, objectId, resourceId, deviceId); 93 | 94 | handler(chunk.toString('utf8'), objectType, objectId, resourceId, deviceId); 95 | }, 96 | 97 | finishHandler: function(chunk) { 98 | logger.debug(context, 'Finished observing value of resource /%s/%s/%s in device [%d]', 99 | objectType, objectId, resourceId, deviceId); 100 | 101 | delete subscriptions[this.id]; 102 | } 103 | }; 104 | } 105 | 106 | /** 107 | * Creates the subscription object and establish the handlers for the incoming data. The first piece of data has a 108 | * special treatment, as it has to be returned with the callback. 109 | * 110 | * @param {Number} deviceId Internal ID of the device of the subscription (not the endpoint ID). 111 | * @param {Number} objectType Number identifying the object type. 112 | * @param {Number} objectId Number identifying the object instance. 113 | * @param {Number} resourceId Resource of the object to be subscribed to. 114 | * @param {Function} handler Handler to be called each time a new piece of data arrives. 115 | * @param {Object} observeStream Stream object for writing into. 116 | */ 117 | function processStream(deviceId, objectType, objectId, resourceId, handler, observeStream, callback) { 118 | var subscriptionId = buildId(deviceId, objectType, objectId, resourceId); 119 | 120 | observeStream.pause(); 121 | 122 | subscriptions[subscriptionId] = 123 | new ResourceListener(deviceId, objectType, objectId, resourceId, observeStream, handler); 124 | 125 | observeStream.on('data', function (chunk) { 126 | logger.debug(context, 'Got first piece of data on resource /%s/%s/%s in device [%d]', 127 | objectType, objectId, resourceId, deviceId); 128 | 129 | observeStream.removeAllListeners('data'); 130 | observeStream.on('data', subscriptions[subscriptionId].dataHandler); 131 | callback(null, chunk.toString('utf8')); 132 | }); 133 | 134 | observeStream.on('finish', subscriptions[subscriptionId].finishHandler); 135 | observeStream.resume(); 136 | } 137 | 138 | /** 139 | * Creates a subscription to a resource in a device, executing the given handler each time new data arrives to the 140 | * server. As part of the the subscription process, the first piece of data will be automatically resolved in the 141 | * same way as a Read action (returning the current resource data in the callback). Subsquent data will be processed 142 | * by the handler. 143 | * 144 | * @param {Number} deviceId Internal ID of the device of the subscription (not the endpoint ID). 145 | * @param {Number} objectType Number identifying the object type. 146 | * @param {Number} objectId Number identifying the object instance. 147 | * @param {Number} resourceId Resource of the object to be subscribed to. 148 | * @param {Function} handler Handler to be called each time a new piece of data arrives. 149 | */ 150 | function observe(deviceId, objectType, objectId, resourceId, handler, callback) { 151 | function createObserveRequest(obj, callback) { 152 | var request = { 153 | host: (config.ipProtocol === 'udp6') ? '::1' : '127.0.0.1', 154 | port: config.port, 155 | method: 'GET', 156 | proxyUri: 'coap://' + (config.ipProtocol === 'udp6' ? '[' + obj.address + ']' : obj.address) + 157 | ':' + obj.port, 158 | pathname: '/' + objectType + '/' + objectId + '/' + resourceId, 159 | observe: true 160 | }; 161 | 162 | if (config.defaultAcceptFormat) { 163 | request.options = { 164 | 'Accept': config.defaultAcceptFormat 165 | }; 166 | } 167 | 168 | callback(null, request); 169 | } 170 | 171 | logger.debug(context, 'Observing value from resource /%s/%s/%s in device [%d]', 172 | objectType, objectId, resourceId, deviceId); 173 | 174 | async.waterfall([ 175 | apply(registry.get, deviceId), 176 | createObserveRequest, 177 | coapUtils.sendRequest, 178 | apply(processStream, deviceId, objectType, objectId, resourceId, handler) 179 | ], callback); 180 | } 181 | 182 | /** 183 | * Gets a list of all the subscriptions to client resources actually in use. 184 | * 185 | */ 186 | function list(callback) { 187 | callback(null, _.values(subscriptions)); 188 | } 189 | 190 | /** 191 | * Remove all the observations the server is currently performing. 192 | * 193 | * TODO: completely close the streams instead of simply removing them. 194 | */ 195 | function clean(callback) { 196 | var subscriptionKeys = _.keys(subscriptions); 197 | 198 | for (var i in subscriptionKeys) { 199 | subscriptions[subscriptionKeys[i]].stream.close(); 200 | delete subscriptions[i]; 201 | } 202 | 203 | subscriptions = {}; 204 | 205 | if (registry){ 206 | registry.stopLifetimeCheck(); 207 | } 208 | 209 | callback(); 210 | } 211 | 212 | /** 213 | * Cancel an observation for the specified resource in a particular device. 214 | * 215 | * @param {Number} deviceId 216 | * @param {Number} objectType 217 | * @param {Number} objectId 218 | * @param {Number} resourceId 219 | */ 220 | function cancel(deviceId, objectType, objectId, resourceId, callback) { 221 | var subscriptionId = buildId(deviceId, objectType, objectId, resourceId); 222 | 223 | subscriptions[subscriptionId].stream.close(); 224 | delete subscriptions[subscriptionId]; 225 | 226 | callback(); 227 | } 228 | 229 | /** 230 | * Initializes the Information Reporting interface, specifiying the current device registry to use. 231 | * 232 | * @param {Object} deviceRegistry Device registry in use by the application. 233 | */ 234 | function init(deviceRegistry, newConfig) { 235 | registry = deviceRegistry; 236 | config = newConfig; 237 | } 238 | 239 | exports.buildId = buildId; 240 | exports.parseId = parseId; 241 | exports.observe = observe; 242 | exports.list = list; 243 | exports.clean = clean; 244 | exports.cancel = cancel; 245 | exports.init = init; 246 | -------------------------------------------------------------------------------- /lib/services/server/mongodbDeviceRegistry.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var errors = require('../../errors'), 27 | Device = require('../model/Device'), 28 | logger = require('logops'), 29 | context = { 30 | op: 'LWM2MLib.MomgodbDeviceRegistry' 31 | }, 32 | checkLifetimeInterval; 33 | 34 | /** 35 | * Generic function to retrieve a device based on a parameter value. This is an auxiliary function meant to abstract 36 | * all the getBySomething functions. 37 | * 38 | * @param {String} parameterName Name of the parameter that is used to identify the device. 39 | * @param {String} parameterValue Value of the parameter to check. 40 | */ 41 | function getByParameter(parameterName, parameterValue, callback) { 42 | var query, 43 | filter = {}; 44 | 45 | filter[parameterName] = parameterValue; 46 | 47 | query = Device.model.findOne(filter); 48 | query.select({__v: 0}); 49 | 50 | query.exec(function handleGet(error, data) { 51 | if (error) { 52 | callback(errors.InternalDbError(error)); 53 | } else if (data) { 54 | callback(null, data); 55 | } else { 56 | callback(new errors.DeviceNotFound(parameterValue)); 57 | } 58 | }); 59 | } 60 | 61 | /** 62 | * Auxiliary function to transform a Mongoose DAO to a plain JS Object as part of the callback process. It returns a 63 | * function that will invoke the callback passed as a parameter transforming the received model object before. 64 | */ 65 | function toObject(callback) { 66 | return function (error, result) { 67 | if (error) { 68 | callback(error); 69 | } else { 70 | callback(null, result.toObject()); 71 | } 72 | }; 73 | } 74 | 75 | /** 76 | * Retrieves from the registry the object identified by the given id. 77 | * 78 | * @param {String} id Id of the object to be retrieved. 79 | */ 80 | function getObject(id, callback) { 81 | getByParameter('id', id, toObject(callback)); 82 | } 83 | 84 | /** 85 | * Gets the device that has the device name passed as a parameter (should be unique) or return a DeviceNotFound error 86 | * in case none exist. 87 | * 88 | * @param {String} deviceName Name of the device to retrieve. 89 | */ 90 | function getByName(deviceName, callback) { 91 | getByParameter('name', deviceName, toObject(callback)); 92 | } 93 | 94 | /** 95 | * Removes the object identified by this id from the registry. The removed object is passed as the first callback 96 | * parameter. 97 | * 98 | * @param {Integer} id Identifier of the object to be removed. 99 | */ 100 | function unregister(id, callback) { 101 | Device.model.findOneAndRemove({ id: id }, function(error, device) { 102 | if (error) { 103 | callback(errors.InternalDbError(error)); 104 | } else if (device) { 105 | callback(null, device.toObject()); 106 | } else { 107 | callback(new errors.DeviceNotFound(id)); 108 | } 109 | }); 110 | } 111 | 112 | /** 113 | * Inserts the given object in the registry and removes the old registration. 114 | * The generated ID is returned through the callback. 115 | * 116 | * @param {Object} object Object to insert into the registry. 117 | */ 118 | function register(object, callback) { 119 | function saveDeviceHandler(error, deviceDAO) { 120 | if (error) { 121 | callback(errors.InternalDbError(error)); 122 | } else { 123 | callback(null, deviceDAO.id); 124 | } 125 | } 126 | 127 | function mongoStore(innerCb) { 128 | var deviceObj = new Device.model(); 129 | 130 | deviceObj.address = object.address; 131 | deviceObj.port = object.port; 132 | deviceObj.path = object.path; 133 | deviceObj.lifetime = object.lifetime; 134 | deviceObj.name = object.name; 135 | deviceObj.type = object.type; 136 | deviceObj.creationDate = object.creationDate; 137 | 138 | deviceObj.save(innerCb); 139 | } 140 | 141 | getByName(object.name, function(error, result){ 142 | if (!error && result) { 143 | unregister(result.id, function(err){ 144 | return mongoStore(saveDeviceHandler); 145 | }); 146 | } 147 | else { 148 | return mongoStore(saveDeviceHandler); 149 | } 150 | }); 151 | } 152 | 153 | /** 154 | * Remove all the objects from the registry. 155 | */ 156 | function clean(callback) { 157 | Device.model.deleteOne({}, function(error, number) { 158 | if (error) { 159 | callback(errors.InternalDbError(error)); 160 | } else { 161 | callback(null); 162 | } 163 | }); 164 | } 165 | 166 | /** 167 | * Update the object identified with the given id with the object value passed as a parameter. 168 | * 169 | * @param {String} id Id of the object to update. 170 | * @param {Object} obj New object value to insert in the registry. 171 | */ 172 | function update(id, obj, callback) { 173 | getByParameter('id', id, function(error, objDAO) { 174 | if (error) { 175 | callback(error); 176 | } else { 177 | objDAO.id = obj.id; 178 | objDAO.address = obj.address; 179 | objDAO.port = obj.port; 180 | objDAO.path = obj.path; 181 | objDAO.lifetime = obj.lifetime; 182 | objDAO.name = obj.name; 183 | objDAO.type = obj.type; 184 | objDAO.creationDate = obj.creationDate; 185 | objDAO.save(toObject(callback)); 186 | } 187 | }); 188 | } 189 | 190 | /** 191 | * Returns an array of all the devices as the parameter of the callback. 192 | */ 193 | function list(callback) { 194 | var condition = {}, 195 | query; 196 | 197 | query = Device.model.find(condition).sort(); 198 | 199 | query.exec(callback); 200 | } 201 | 202 | 203 | /** 204 | * Stops checking for device lifetime. 205 | */ 206 | function stopLifetimeCheck() { 207 | clearInterval(checkLifetimeInterval); 208 | checkLifetimeInterval = null; 209 | } 210 | 211 | /** 212 | * If Lifetime Resource exists, then the registration SHOULD be removed by the Server if a new registration or update 213 | * is not received within this lifetime. 214 | * 215 | * @param {Object} lifetimeCheckInterval Minimum interval between lifetime checks in ms 216 | * @param {Function} unregistrationHandler Unregistration device handler 217 | */ 218 | function checkLifetime(lifetimeCheckInterval, unregistrationHandler) { 219 | stopLifetimeCheck(); 220 | 221 | checkLifetimeInterval = setInterval(function(){ 222 | list(function(error, deviceList){ 223 | if (!error && deviceList) { 224 | deviceList.forEach(function(device){ 225 | if (device.lifetime && 226 | new Date() - new Date(device.creationDate) > Number(device.lifetime) * 1000) { 227 | unregister(device.id, function(err, obj){ 228 | if (err) { 229 | logger.debug(context, 230 | 'Lifetime unregistration for device [%s] ended up in error [%s] with code [%s]', 231 | obj.name, err.name, err.code); 232 | } 233 | else { 234 | logger.debug(context, 235 | 'Lifetime unregistration for device [%s] ended successfully', 236 | obj.name); 237 | 238 | if (unregistrationHandler) { 239 | unregistrationHandler(obj, function(){ 240 | 241 | }); 242 | } 243 | } 244 | }); 245 | } 246 | }); 247 | } 248 | }); 249 | }, lifetimeCheckInterval); 250 | } 251 | 252 | exports.register = register; 253 | exports.unregister = unregister; 254 | exports.get = getObject; 255 | exports.update = update; 256 | exports.clean = clean; 257 | exports.list = list; 258 | exports.checkLifetime = checkLifetime; 259 | exports.stopLifetimeCheck = stopLifetimeCheck; 260 | exports.getByName = getByName; 261 | -------------------------------------------------------------------------------- /lib/services/server/registration.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var async = require('async'), 27 | errors = require('../../errors'), 28 | logger = require('logops'), 29 | registrationUtils = require('./utils/deviceRegistrationUtils'), 30 | config, 31 | registry, 32 | coapUtils = require('./../coapUtils'), 33 | context = { 34 | op: 'LWM2MLib.Registration' 35 | }; 36 | 37 | /** 38 | * Generates the end of request handler that will generate the final response to the COAP Client. 39 | */ 40 | function endRegistration(req, res) { 41 | return function (error, result) { 42 | if (error) { 43 | logger.debug(context, 'Registration request ended up in error [%s] with code [%s]', error.name, error.code); 44 | 45 | res.code = error.code; 46 | res.end(error.name); 47 | } else { 48 | var root = (config.baseRoot) ? config.baseRoot + '/' : ''; 49 | 50 | logger.debug(context, 'Registration request ended successfully'); 51 | res.code = '2.01'; 52 | res.setOption('Location-Path', root + 'rd/' + result[1]); 53 | res.end(''); 54 | } 55 | }; 56 | } 57 | 58 | /** 59 | * Invoke the user handler for this operation, with all the information from the query parameters as its arguments. 60 | * 61 | * @param {Object} queryParams Object containing all the query parameters. 62 | * @param {Function} handler User handler to be invoked. 63 | */ 64 | function applyHandler(queryParams, payload, handler, callback) { 65 | logger.debug(context, 'Calling user handler for registration actions for device [%s]', queryParams.ep); 66 | handler(queryParams.ep, queryParams.lt, queryParams.lwm2m, queryParams.b, payload, callback); 67 | } 68 | 69 | /** 70 | * Creates the device object to be stored in the registry and stores it. 71 | * 72 | * @param {Object} queryParams Object containing all the query parameters. 73 | * @param {Object} req Arriving COAP Request. 74 | */ 75 | function storeDevice(queryParams, req, callback) { 76 | var device = { 77 | name: queryParams.ep, 78 | lifetime: queryParams.lt, 79 | address: req.rsinfo.address, 80 | port: req.rsinfo.port, 81 | creationDate: new Date() 82 | }; 83 | 84 | logger.debug(context, 'Storing the following device in the db:\n%s', JSON.stringify(device, null, 4)); 85 | 86 | device.path = req.urlObj.pathname; 87 | device.type = registrationUtils.getDeviceTypeFromUrlRequest(req.urlObj, config); 88 | 89 | if (device.type) { 90 | logger.debug(context, 'Registered device [%s] with type [%s]', device.name, device.type); 91 | registry.register(device, callback); 92 | } else { 93 | logger.debug(context, 'No type found for device [%s]', device.name); 94 | 95 | callback(new errors.TypeNotFound(req.url)); 96 | } 97 | } 98 | 99 | /** 100 | * Handle the registration operation. 101 | * 102 | * @param {Object} req Arriving COAP Request to be handled. 103 | * @param {Object} res Outgoing COAP Response. 104 | * @param {Function} handler User handler to be executed if everything goes ok. 105 | */ 106 | function handleRegistration(req, res, handler) { 107 | var queryParams = coapUtils.extractQueryParams(req); 108 | 109 | logger.debug(context, 'Handling registration request'); 110 | 111 | async.series([ 112 | async.apply(coapUtils.checkMandatoryQueryParams, ['ep'], queryParams), 113 | async.apply(storeDevice, queryParams, req), 114 | async.apply(applyHandler, queryParams, req.payload.toString(), handler) 115 | ], endRegistration(req, res)); 116 | } 117 | 118 | function init(newRegistry, newConfig) { 119 | registry = newRegistry; 120 | config = newConfig; 121 | } 122 | 123 | exports.init = init; 124 | exports.handle = handleRegistration; 125 | -------------------------------------------------------------------------------- /lib/services/server/unregistration.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var async = require('async'), 27 | logger = require('logops'), 28 | context = { 29 | op: 'LWM2MLib.Unregistration' 30 | }, 31 | registry; 32 | 33 | /** 34 | * Generates the end of request handler that will generate the final response to the COAP Client. 35 | */ 36 | function endUnregistration(req, res) { 37 | return function (error, result) { 38 | if (error) { 39 | logger.debug(context, 'Registration request ended up in error [%s] with code [%s]', 40 | error.message, error.code); 41 | 42 | res.code = error.code; 43 | res.end(error.message); 44 | } else { 45 | logger.debug(context, 'Unregistration request ended successfully'); 46 | 47 | res.code = '2.02'; 48 | res.end(''); 49 | } 50 | }; 51 | } 52 | 53 | /** 54 | * Parse the pathname of the request to extract the device id and return it through the callback. 55 | * 56 | * @param {Object} req Arriving COAP Request to be processed. 57 | */ 58 | function parsePath(req, callback) { 59 | var pathElements = req.urlObj.pathname.split('/'); 60 | 61 | callback(null, pathElements[2]); 62 | } 63 | 64 | /** 65 | * Invoke the user handler for this operation with the unregistered object as its only argument. 66 | * 67 | * @param {Function} handler User handler for the unregistration. 68 | * @param {Object} removedObj The removed Device object. 69 | */ 70 | function applyHandler(handler, removedObj, callback) { 71 | logger.debug(context, 'Calling user handler for unregistration actions for device [%s]', removedObj.name); 72 | 73 | handler(removedObj, callback); 74 | } 75 | 76 | /** 77 | * Handle the registration operation. 78 | * 79 | * @param {Object} req Arriving COAP Request to be handled. 80 | * @param {Object} res Outgoing COAP Response. 81 | * @param {Function} handler User handler to be executed if everything goes ok. 82 | */ 83 | function handleUnregistration(req, res, handler) { 84 | logger.debug(context, 'Handling unregistration request'); 85 | 86 | async.waterfall([ 87 | async.apply(parsePath, req), 88 | registry.unregister, 89 | async.apply(applyHandler, handler) 90 | ], endUnregistration(req, res)); 91 | } 92 | 93 | function setRegistry(newRegistry) { 94 | registry = newRegistry; 95 | } 96 | 97 | exports.init = setRegistry; 98 | exports.handle = handleUnregistration; -------------------------------------------------------------------------------- /lib/services/server/updateRegistration.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var async = require('async'), 27 | registry, 28 | logger = require('logops'), 29 | context = { 30 | op: 'LWM2MLib.UpdateRegistration' 31 | }, 32 | coapUtils = require('./../coapUtils'), 33 | apply = async.apply; 34 | 35 | /** 36 | * Generates the end of request handler that will generate the final response to the COAP Client. 37 | */ 38 | function endUpdate(req, res) { 39 | return function (error, result) { 40 | if (error) { 41 | logger.debug(context, 'Registration request ended up in error [%s] with code [%s]', error.name, error.code); 42 | 43 | res.code = error.code; 44 | res.end(error.message); 45 | } else { 46 | logger.debug(context, 'Update registration request ended successfully'); 47 | 48 | res.code = '2.04'; 49 | res.end(''); 50 | } 51 | }; 52 | } 53 | 54 | /** 55 | * Updates the lifetime and address of the Device. 56 | * 57 | * @param {Object} req Arriving COAP Request to be processed. 58 | * @param {Object} queryParams Object containing all the query parameters. 59 | * @param {Object} obj Old version of the Device retrieved from the registry. 60 | */ 61 | function updateRegister(req, queryParams, obj, callback) { 62 | logger.debug(context, 'Updating device register with lifetime [%s] and address [%s].', 63 | queryParams.lt, req.rsinfo.address); 64 | obj.lifetime = queryParams.lt || obj.lifetime; 65 | obj.address = req.rsinfo.address; 66 | obj.port = req.rsinfo.port; 67 | obj.creationDate = new Date(); 68 | callback(null, obj); 69 | } 70 | 71 | /** 72 | * Parse the pathname of the request to extract the device id and return it through the callback. 73 | * 74 | * @param {Object} req Arriving COAP Request to be processed. 75 | */ 76 | function parsePath(req, callback) { 77 | var pathElements = req.urlObj.pathname.split('/'); 78 | 79 | callback(null, pathElements[2]); 80 | } 81 | 82 | /** 83 | * Invoke the user handler for this operation with the updated object as its only argument. 84 | * 85 | * @param {Function} handler User handler for the update registration. 86 | * @param {String} payload String representation of the update payload. 87 | * @param {Object} updatedObj The updated Device object. 88 | */ 89 | function applyHandler(handler, payload, updatedObj, callback) { 90 | handler(updatedObj, payload.toString(), callback); 91 | } 92 | 93 | /** 94 | * Handle the registration operation. 95 | * 96 | * @param {Object} req Arriving COAP Request to be handled. 97 | * @param {Object} res Outgoing COAP Response. 98 | * @param {Function} handler User handler to be executed if everything goes ok. 99 | */ 100 | function handleUpdateRegistration(req, res, handler) { 101 | logger.debug(context, 'Handling update registration request'); 102 | 103 | async.series([ 104 | apply(coapUtils.extractQueryParams, req), 105 | apply(parsePath, req) 106 | ], function (error, extractedData) { 107 | if (error) { 108 | endUpdate(req, res)(error); 109 | } else { 110 | async.waterfall([ 111 | apply(registry.get, extractedData[1]), 112 | apply(updateRegister, req, extractedData[0]), 113 | apply(registry.update, extractedData[1]), 114 | apply(applyHandler, handler, req.payload) 115 | ], endUpdate(req, res)); 116 | } 117 | }); 118 | } 119 | 120 | function setRegistry(newRegistry) { 121 | registry = newRegistry; 122 | } 123 | 124 | exports.init = setRegistry; 125 | exports.handle = handleUpdateRegistration; -------------------------------------------------------------------------------- /lib/services/server/utils/deviceRegistrationUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 25 | 'use strict'; 26 | 27 | 28 | function getDeviceTypeFromUrlRequest(urlObj, config) { 29 | var type; 30 | 31 | if (urlObj.pathname.startsWith('/rd')) { 32 | 33 | if (urlObj.query.includes('&type=')) { 34 | 35 | 36 | urlObj.query.split('&').forEach( 37 | function (queryParam) { 38 | if (queryParam.includes('type=')) { 39 | type = queryParam.split('=')[1]; 40 | } 41 | } 42 | ); 43 | 44 | for (var i in config.types) { 45 | if (type === config.types[i].name) { 46 | return type; 47 | } 48 | } 49 | 50 | } 51 | else { 52 | return config.defaultType; 53 | } 54 | 55 | } 56 | 57 | else if (config.types) { 58 | 59 | type = urlObj.pathname.substr(0, urlObj.pathname.length - 3); 60 | 61 | for (var j in config.types) { 62 | if (type === config.types[j].url) { 63 | return config.types[j].name; 64 | } 65 | } 66 | 67 | } 68 | 69 | } 70 | 71 | exports.getDeviceTypeFromUrlRequest = getDeviceTypeFromUrlRequest; 72 | -------------------------------------------------------------------------------- /lib/services/slidingWindow.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | function createSlidingWindow(size) { 27 | var window = { 28 | data: new Array(size), 29 | header: 0 30 | }; 31 | 32 | function containNumber(number) { 33 | if (window.data.indexOf(number) < 0) { 34 | return false; 35 | } else { 36 | return true; 37 | } 38 | } 39 | 40 | function pushNumber(number) { 41 | window.data[window.header] = number; 42 | window.header = (window.header++)%window.data.length; 43 | } 44 | 45 | return { 46 | contains: containNumber, 47 | push: pushNumber 48 | }; 49 | } 50 | 51 | module.exports = createSlidingWindow; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lwm2m-node-lib", 3 | "description": "Library for developing servers and client of OMA Lightweight M2M", 4 | "version": "1.4.0-next", 5 | "homepage": "https://github.com/telefonicaid/lwm2m-node-lib", 6 | "author": { 7 | "name": "Daniel Moran", 8 | "email": "daniel.moranjimenez@telefonica.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/telefonicaid/lwm2m-node-lib" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/telefonicaid/lwm2m-node-lib/issues" 16 | }, 17 | "main": "lib/lwm2m-node-lib", 18 | "engines": { 19 | "node": ">=12" 20 | }, 21 | "scripts": { 22 | "clean": "rm -rf package-lock.json && rm -rf node_modules && rm -rf coverage", 23 | "test": "mocha --recursive 'test/**/*.js' --reporter spec --timeout 3000 --ui bdd --exit", 24 | "test:watch": "npm run test -- -w ./lib", 25 | "lint": "jshint lib/ --config .jshintrc && jshint test/ --config test/.jshintrc", 26 | "test:coverage": "istanbul cover _mocha -- --recursive 'test/**/*.js' --reporter spec --exit", 27 | "watch": "watch 'npm test && npm run lint' ./lib ./test" 28 | }, 29 | "bin": { 30 | "iotagent-lwm2m-client": "bin/iotagent-lwm2m-client.js", 31 | "iotagent-lwm2m-server": "bin/iotagent-lwm2m-server.js" 32 | }, 33 | "dependencies": { 34 | "async": "2.6.1", 35 | "coap": "0.22.0", 36 | "coap-cli": "0.5.1", 37 | "command-node": "0.1.1", 38 | "logops": "2.1.2", 39 | "mongodb": "3.6.1", 40 | "mongoose": "5.7.7", 41 | "mongoose-plugin-autoinc": "1.1.9", 42 | "underscore": "1.12.1" 43 | }, 44 | "devDependencies": { 45 | "istanbul": "~0.4.5", 46 | "jshint": "~2.9.6", 47 | "mocha": "5.2.0", 48 | "proxyquire": "2.1.0", 49 | "should": "13.2.3", 50 | "watch": "~1.0.2" 51 | }, 52 | "keywords": [] 53 | } 54 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "noempty": true, 9 | "quotmark": "single", 10 | "undef": true, 11 | "unused": true, 12 | "trailing": true, 13 | "maxparams": 6, 14 | "maxdepth": 4, 15 | "camelcase": true, 16 | "maxlen": 120, 17 | "node": true, 18 | "expr": true, 19 | "unused": "vars", 20 | "sub": true, 21 | "globals": { 22 | "describe":true, 23 | "it": true, 24 | "expect": true, 25 | "before": true, 26 | "after": true, 27 | "beforeEach": true, 28 | "afterEach": true, 29 | "mock": true 30 | }, 31 | "predef": 32 | [ 33 | "describe", "beforeEach", "afterEach", "it" 34 | ] 35 | } -------------------------------------------------------------------------------- /test/unit/client/client-registration-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var should = require('should'), 27 | async = require('async'), 28 | lwm2mServer = require('../../../').server, 29 | lwm2mClient = require('../../../').client, 30 | memoryRegistry = require('../../../lib/services/server/inMemoryDeviceRegistry'), 31 | config = require('../../../config'), 32 | localhost, 33 | testInfo = {}; 34 | 35 | 36 | describe('Client-initiated registration', function() { 37 | beforeEach(function(done) { 38 | if (config.server.ipProtocol === 'udp6') { 39 | localhost = '::1'; 40 | } else { 41 | localhost = '127.0.0.1'; 42 | } 43 | 44 | lwm2mClient.init(config); 45 | 46 | lwm2mClient.registry.reset(function() { 47 | memoryRegistry.clean(function () { 48 | lwm2mServer.start(config.server, function (error, srvInfo) { 49 | testInfo.serverInfo = srvInfo; 50 | done(); 51 | }); 52 | }); 53 | }); 54 | }); 55 | afterEach(function(done) { 56 | memoryRegistry.clean(function() { 57 | async.series([ 58 | async.apply(lwm2mServer.stop, testInfo.serverInfo), 59 | lwm2mClient.registry.reset, 60 | lwm2mClient.cancellAllObservers 61 | ], function() { 62 | lwm2mClient.registry.bus.removeAllListeners(); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | 68 | describe('When the client tries to register in an existent LWTM2M server', function() { 69 | var deviceInformation; 70 | 71 | beforeEach(function (done) { 72 | async.series([ 73 | async.apply(lwm2mClient.registry.create, '/0/1'), 74 | async.apply(lwm2mClient.registry.create, '/3/14'), 75 | async.apply(lwm2mClient.registry.create, '/2/9') 76 | ], done); 77 | }); 78 | 79 | afterEach(function (done) { 80 | async.series([ 81 | async.apply(lwm2mClient.registry.remove, '/0/1'), 82 | async.apply(lwm2mClient.registry.remove, '/3/14'), 83 | async.apply(lwm2mClient.registry.remove, '/2/9') 84 | ], function (error) { 85 | if (deviceInformation) { 86 | lwm2mClient.unregister(deviceInformation, done); 87 | } else { 88 | done(); 89 | } 90 | }); 91 | }); 92 | it('should send a COAP POST Message with the required parameters', function(done) { 93 | var handlerCalled = false; 94 | 95 | lwm2mServer.setHandler(testInfo.serverInfo, 'registration', 96 | function (endpoint, lifetime, version, binding, payload, callback) { 97 | should.exist(endpoint); 98 | should.exist(lifetime); 99 | should.exist(binding); 100 | should.exist(version); 101 | endpoint.should.equal('testEndpoint'); 102 | lifetime.should.equal(config.client.lifetime); 103 | binding.should.equal('U'); 104 | handlerCalled = true; 105 | callback(null); 106 | }); 107 | 108 | lwm2mClient.register(localhost, config.server.port, null, 'testEndpoint', 109 | function (error, info) { 110 | handlerCalled.should.equal(true); 111 | deviceInformation = info; 112 | done(); 113 | }); 114 | }); 115 | it('should pass the returned location to the callback if there is no error'); 116 | it('should send the complete set of supported objects', function(done) { 117 | var handlerCalled = false; 118 | 119 | lwm2mServer.setHandler(testInfo.serverInfo, 'registration', 120 | function (endpoint, lifetime, version, binding, payload, callback) { 121 | handlerCalled = true; 122 | payload.should.equal(',,'); 123 | callback(null); 124 | }); 125 | 126 | lwm2mClient.register(localhost, config.server.port, null, 'testEndpoint', 127 | function (error, info) { 128 | handlerCalled.should.equal(true); 129 | deviceInformation = info; 130 | done(); 131 | }); 132 | }); 133 | }); 134 | describe('When the client tries to register in an unexistent server', function() { 135 | var deviceInformation; 136 | 137 | afterEach(function (done) { 138 | if (deviceInformation) { 139 | lwm2mClient.unregister(deviceInformation, done); 140 | } else { 141 | done(); 142 | } 143 | }); 144 | it('should raise a SERVER_NOT_FOUND error', function(done) { 145 | var handlerCalled = false; 146 | 147 | lwm2mServer.setHandler(testInfo.serverInfo, 'registration', 148 | function (endpoint, lifetime, version, binding, payload, callback) { 149 | handlerCalled = true; 150 | callback(null); 151 | }); 152 | 153 | lwm2mClient.register('http://unexistent.com', 12345, null, 'testEndpoint', 154 | function (error, info) { 155 | should.exist(error); 156 | error.name.should.equal('SERVER_NOT_FOUND'); 157 | handlerCalled.should.equal(false); 158 | done(); 159 | }); 160 | }); 161 | }); 162 | describe('When the client update method is executed', function() { 163 | var deviceInformation; 164 | 165 | beforeEach(function(done) { 166 | lwm2mServer.setHandler(testInfo.serverInfo, 'registration', 167 | function (endpoint, lifetime, version, binding, payload, callback) { 168 | callback(null); 169 | }); 170 | 171 | lwm2mServer.setHandler(testInfo.serverInfo, 'unregistration', function (device, callback) { 172 | callback(null); 173 | }); 174 | 175 | lwm2mClient.registry.create('/3/14', function (error) { 176 | lwm2mClient.register(localhost, config.server.port, null, 'testEndpoint', function (error, info) { 177 | deviceInformation = info; 178 | lwm2mClient.registry.create('/7/5', done); 179 | }); 180 | }); 181 | }); 182 | 183 | afterEach(function (done) { 184 | async.series([ 185 | async.apply(lwm2mClient.registry.remove, '/3/14'), 186 | async.apply(lwm2mClient.registry.remove, '/7/5') 187 | ], function (error) { 188 | if (deviceInformation) { 189 | lwm2mClient.unregister(deviceInformation, done); 190 | } else { 191 | done(); 192 | } 193 | }); 194 | }); 195 | 196 | it('should send a COAP UPDATE Message with the required parameters', function(done) { 197 | var handlerCalled = false; 198 | 199 | lwm2mServer.setHandler(testInfo.serverInfo, 'updateRegistration', function (device, payload, callback) { 200 | should.exist(device); 201 | handlerCalled = true; 202 | callback(null); 203 | }); 204 | 205 | lwm2mClient.update(deviceInformation, function (error) { 206 | handlerCalled.should.equal(true); 207 | done(); 208 | }); 209 | }); 210 | 211 | it('should update the set of supported objects', function(done) { 212 | var handlerCalled = false; 213 | 214 | lwm2mServer.setHandler(testInfo.serverInfo, 'updateRegistration', function (device, payload, callback) { 215 | should.exist(device); 216 | should.exist(payload); 217 | payload.should.equal(','); 218 | handlerCalled = true; 219 | callback(null); 220 | }); 221 | 222 | lwm2mClient.update(deviceInformation, function (error) { 223 | handlerCalled.should.equal(true); 224 | done(); 225 | }); 226 | 227 | }); 228 | }); 229 | describe('When the client unregistration method is executed', function() { 230 | var deviceInformation; 231 | 232 | beforeEach(function(done) { 233 | lwm2mServer.setHandler(testInfo.serverInfo, 'registration', 234 | function (endpoint, lifetime, version, binding, payload, callback) { 235 | callback(null); 236 | }); 237 | 238 | lwm2mClient.register(localhost, config.server.port, null, 'testEndpoint', function (error, info) { 239 | deviceInformation = info; 240 | done(); 241 | }); 242 | }); 243 | 244 | it('should send a COAP DELETE Message to the provided location', function(done) { 245 | var handlerCalled = false; 246 | 247 | lwm2mServer.setHandler(testInfo.serverInfo, 'unregistration', function (device, callback) { 248 | should.exist(device); 249 | handlerCalled = true; 250 | callback(null); 251 | }); 252 | 253 | lwm2mClient.unregister(deviceInformation, function (error) { 254 | handlerCalled.should.equal(true); 255 | done(); 256 | }); 257 | 258 | }); 259 | }); 260 | describe('When the client registration method is rejected with an error', function() { 261 | it('should invoke the callback with the appropriate error'); 262 | }); 263 | describe('When the client update registration method is rejected with an error', function() { 264 | it('should invoke the callback with the same error'); 265 | }); 266 | describe('When the client unregistration method is rejected with an error', function() { 267 | it('should invoke the callback with the same error'); 268 | }); 269 | }); -------------------------------------------------------------------------------- /test/unit/client/client-registry-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var objectRegistry = require('../../..').client.registry, 27 | async = require('async'), 28 | apply = async.apply, 29 | should = require('should'); 30 | 31 | describe('Client registry', function() { 32 | describe('When a new object with URI "/1/3" is created in the registry', function() { 33 | beforeEach(function(done) { 34 | objectRegistry.create('/1/3', done); 35 | }); 36 | 37 | afterEach(function(done) { 38 | objectRegistry.remove('/1/3', done); 39 | }); 40 | 41 | it('should appear in subsequent listings', function(done) { 42 | objectRegistry.list(function(error, objectList) { 43 | var found = false; 44 | 45 | should.not.exist(error); 46 | should.exist(objectList); 47 | 48 | for (var i = 0; i < objectList.length; i++) { 49 | if (objectList[i].objectUri === '/1/3') { 50 | found = true; 51 | } 52 | } 53 | 54 | found.should.equal(true); 55 | done(); 56 | }); 57 | }); 58 | it('should be stored with its ObjectType and ObjectId', function(done) { 59 | objectRegistry.get('/1/3', function (error, result) { 60 | should.not.exist(error); 61 | should.exist(result); 62 | result.objectType = '1'; 63 | result.objectId = '3'; 64 | result.objectUri = '/1/3'; 65 | done(); 66 | }); 67 | }); 68 | }); 69 | describe('When a new object with an invalid URI "1/2" is created', function () { 70 | it('should raise a WRONG_OBJECT_URI error', function(done) { 71 | objectRegistry.create('1/2', function(error, result) { 72 | should.exist(error); 73 | should.not.exist(result); 74 | error.name.should.equal('WRONG_OBJECT_URI'); 75 | done(); 76 | }); 77 | }); 78 | }); 79 | describe('When a non existent object is retrieved from the registry', function() { 80 | it('should raise a NOT_FOUND error', function(done) { 81 | objectRegistry.get('/5/8', function(error, result) { 82 | should.exist(error); 83 | should.not.exist(result); 84 | error.name.should.equal('OBJECT_NOT_FOUND'); 85 | done(); 86 | }); 87 | }); 88 | }); 89 | describe('When an attribute is set in an object', function() { 90 | beforeEach(function(done) { 91 | async.series([ 92 | apply(objectRegistry.create, '/1/3'), 93 | apply(objectRegistry.setResource, '/1/3', '5', '123') 94 | ], done); 95 | }); 96 | afterEach(function(done) { 97 | objectRegistry.remove('/1/3', done); 98 | }); 99 | 100 | it('should appear in the object once retrieved', function(done) { 101 | objectRegistry.get('/1/3', function(error, result) { 102 | should.exist(result.attributes); 103 | should.exist(result.attributes['5']); 104 | result.attributes['5'].should.equal('123'); 105 | done(); 106 | }); 107 | }); 108 | }); 109 | describe('When an attribute is removed from an object', function() { 110 | beforeEach(function(done) { 111 | async.series([ 112 | apply(objectRegistry.create, '/1/3'), 113 | apply(objectRegistry.setResource, '/1/3', '5', '123'), 114 | apply(objectRegistry.unsetResource, '/1/3', '5') 115 | ], done); 116 | }); 117 | afterEach(function(done) { 118 | objectRegistry.remove('/1/3', done); 119 | }); 120 | 121 | it('shouldn\'t appear in the retrieved object', function(done) { 122 | objectRegistry.get('/1/3', function(error, result) { 123 | should.exist(result.attributes); 124 | should.not.exist(result.attributes['5']); 125 | done(); 126 | }); 127 | }); 128 | }); 129 | describe('When multiple objects are created', function() { 130 | beforeEach(function(done) { 131 | async.series([ 132 | apply(objectRegistry.create, '/1/3'), 133 | apply(objectRegistry.create, '/4/8'), 134 | apply(objectRegistry.create, '/9/13') 135 | ], done); 136 | }); 137 | afterEach(function(done) { 138 | async.series([ 139 | apply(objectRegistry.remove, '/1/3'), 140 | apply(objectRegistry.remove, '/4/8'), 141 | apply(objectRegistry.remove, '/9/13') 142 | ], done); 143 | }); 144 | it('all the objects should appear in subsequent listings', function(done) { 145 | objectRegistry.list(function(error, objList) { 146 | should.not.exist(error); 147 | should.exist(objList); 148 | objList.length.should.equal(3); 149 | done(); 150 | }); 151 | }); 152 | }); 153 | describe('When an object is removed from the registry', function() { 154 | beforeEach(function(done) { 155 | async.series([ 156 | apply(objectRegistry.create, '/1/3'), 157 | apply(objectRegistry.remove, '/1/3') 158 | ], done); 159 | }); 160 | afterEach(function(done) { 161 | objectRegistry.remove('/1/3', function (error) { 162 | done(); 163 | }); 164 | }); 165 | 166 | it('should not appear in the listings any more', function(done) { 167 | objectRegistry.list(function(error, objectList) { 168 | var found = false; 169 | 170 | should.not.exist(error); 171 | should.exist(objectList); 172 | 173 | for (var i = 0; i < objectList.length; i++) { 174 | if (objectList[i].objectUri === '/1/3') { 175 | found = true; 176 | } 177 | } 178 | 179 | found.should.equal(false); 180 | done(); 181 | }); 182 | }); 183 | it('should raise an OBJECT_NOT_FOUND error when tried to be retrieved', function(done) { 184 | objectRegistry.get('/1/3', function(error, result) { 185 | should.exist(error); 186 | should.not.exist(result); 187 | error.name.should.equal('OBJECT_NOT_FOUND'); 188 | done(); 189 | }); 190 | }); 191 | }); 192 | }); -------------------------------------------------------------------------------- /test/unit/common/object-schema-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | * 23 | * Author: Alexandre Moreno 24 | */ 25 | 26 | 'use strict'; 27 | 28 | var should = require('should'), // jshint ignore:line 29 | Schema = require('../../../').Schema; 30 | 31 | describe('LWM2M Object Schema', function() { 32 | 33 | describe('constructor', function() { 34 | it('should throw when a resource definition is not correct', function() { 35 | var def = { 36 | a: { type: String, id: 0 }, 37 | b: { bad: 'value' } 38 | }; 39 | 40 | function schema() { 41 | new Schema('test', def); 42 | } 43 | 44 | schema.should.throw(TypeError); 45 | }); 46 | 47 | it('should throw when presented with invalid resource type', function() { 48 | var def = { 49 | a: { type: String, id: 0 }, 50 | b: { type: RegExp, id: 1 } 51 | }; 52 | 53 | function schema() { 54 | new Schema('test', def); 55 | } 56 | 57 | schema.should.throw(TypeError); 58 | }); 59 | 60 | }); 61 | 62 | describe('validate', function() { 63 | 64 | it('should be ok when an object matches an schema', function() { 65 | var def = { 66 | a: { type: String, id: 0 }, 67 | b: { type: Number, id: 1 } 68 | }; 69 | 70 | var schema = new Schema('test', def); 71 | 72 | function validate() { 73 | return schema.validate({ a: 'foo', b: 3 }); 74 | } 75 | 76 | validate.should.not.throw(TypeError); 77 | }); 78 | 79 | it('should be ok when an object uses Object IDs as keys', function() { 80 | var def = { 81 | a: { type: String, id: 0 }, 82 | b: { type: Number, id: 1 } 83 | }; 84 | 85 | var schema = new Schema('test', def); 86 | 87 | function validate() { 88 | return schema.validate({ 0: 'foo', 1: 3 }); 89 | } 90 | 91 | validate.should.not.throw(TypeError); 92 | }); 93 | 94 | it('should throw when an object does not match schema', function() { 95 | var def = { 96 | a: { type: String, id: 0 }, 97 | b: { type: Number, id: 1 }, 98 | c: [{ type: Boolean, id: 2 }] 99 | }; 100 | 101 | var schema = new Schema('test', def); 102 | 103 | function validate() { 104 | return schema.validate({ a: 'foo', b: false }); 105 | } 106 | 107 | validate.should.throw(TypeError); 108 | 109 | function validate2() { 110 | return schema.validate({ a: 'foo', c: [1,2,3] }); 111 | } 112 | 113 | validate2.should.throw(TypeError); 114 | }); 115 | 116 | it('should throw when missing a required resource', function() { 117 | var def = { 118 | a: { type: String, id: 0 }, 119 | b: { type: Number, id: 1, required: true } 120 | }; 121 | 122 | var schema = new Schema('test', def); 123 | 124 | function validate() { 125 | return schema.validate({ a: 'foo' }); 126 | } 127 | 128 | validate.should.throw(TypeError); 129 | }); 130 | }); 131 | }); 132 | 133 | -------------------------------------------------------------------------------- /test/unit/common/object-senml-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | * 23 | * Author: Alexandre Moreno 24 | */ 25 | 26 | 'use strict'; 27 | 28 | var should = require('should'), // jshint ignore:line 29 | senml = require('../../../lib/services/object/senml'), 30 | deviceSchema = require('../../../lib/services/object/device'); 31 | 32 | var object = { 33 | manufacturer: 'Open Mobile Alliance', 34 | modelNumber: 'Lightweight M2M Client', 35 | serialNumber: '345000123', 36 | firmwareVer: '1.0', 37 | powerSrcs: [ 1, 5 ], 38 | srcVoltage: [ 3800, 5000 ], 39 | srcCurrent: [ 125, 900 ], 40 | batteryLevel: 100, 41 | memoryFree: 15, 42 | errorCode: [ 0 ], 43 | currentTime: 1367491215, 44 | utcOffset: '+02:00', 45 | timeZone: 'U' 46 | }; 47 | 48 | var payload = '{"e":[' + 49 | '{"n":"0","sv":"Open Mobile Alliance"},' + 50 | '{"n":"1","sv":"Lightweight M2M Client"},' + 51 | '{"n":"2","sv":"345000123"},' + 52 | '{"n":"3","sv":"1.0"},' + 53 | '{"n":"6/0","v":1},' + 54 | '{"n":"6/1","v":5},' + 55 | '{"n":"7/0","v":3800},' + 56 | '{"n":"7/1","v":5000},' + 57 | '{"n":"8/0","v":125},' + 58 | '{"n":"8/1","v":900},' + 59 | '{"n":"9","v":100},' + 60 | '{"n":"10","v":15},' + 61 | '{"n":"11/0","v":0},' + 62 | '{"n":"13","v":1367491215},' + 63 | '{"n":"14","sv":"+02:00"},' + 64 | '{"n":"15","sv":"U"}]}'; 65 | 66 | describe('De/serializing LWM2M Objects from/into JSON', function() { 67 | 68 | describe('serialize', function() { 69 | it('should return a valid payload', function() { 70 | var dev = senml.serialize(object, deviceSchema); 71 | 72 | dev.should.equal(payload); 73 | }); 74 | }); 75 | 76 | describe('parse', function() { 77 | it('should return an object', function() { 78 | var dev = senml.parse(payload, deviceSchema); 79 | 80 | dev.should.be.an.Object().and.not.empty(); 81 | }); 82 | 83 | it('should strictly return matching resources from schema', function() { 84 | var dev = senml.parse(payload, deviceSchema), 85 | keys = Object.keys(deviceSchema.resources); 86 | 87 | Object.keys(dev).should.matchEach(function(it) { 88 | return it.should.be.oneOf(keys); 89 | }); 90 | }); 91 | }); 92 | 93 | }); 94 | 95 | -------------------------------------------------------------------------------- /test/unit/common/slidingWindow-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var Window = require('../../../lib/services/slidingWindow'), 27 | assert = require('assert'); 28 | 29 | describe('Sliding windows test', function() { 30 | var window; 31 | 32 | beforeEach(function() { 33 | window = new Window(5); 34 | for (var i = 0; i < 5; i++) { 35 | window.push(i); 36 | } 37 | }); 38 | 39 | describe('When a new value is inserted in the sliding window', function() { 40 | beforeEach(function() { 41 | window.push(6); 42 | }); 43 | 44 | it('should be contained in the window', function() { 45 | assert(window.contains(6)); 46 | }); 47 | it('should remove one of the older values from the window', function() { 48 | assert(!window.contains(1)); 49 | }); 50 | }); 51 | }); -------------------------------------------------------------------------------- /test/unit/server/bootstrap-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | describe('Client initiated boostrap interface tests', function() { 27 | describe('When a bootstrapping request comes to the server initiated by the client', function () { 28 | it('should return a 4.00 Bad Request if the request doesn\'t contains the endpoint name'); 29 | it('should return a 2.04 Changed return code if the request is correct'); 30 | it('should write a server object with the short server ID and a lifetime for the registration'); 31 | it('should write a server object with the server binding'); 32 | it('should write a server object with Registration Update Trigger'); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/unit/server/client-registration-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var libLwm2m2 = require('../../../').server, 27 | coap = require('coap'), 28 | Readable = require('stream').Readable, 29 | config = require('../../../config'), 30 | utils = require('./../testUtils'), 31 | should = require('should'), 32 | localhost, 33 | testInfo = {}; 34 | 35 | describe('Client registration interface', function() { 36 | beforeEach(function (done) { 37 | if (config.server.ipProtocol === 'udp6') { 38 | localhost = '::1'; 39 | } else { 40 | localhost = '127.0.0.1'; 41 | } 42 | 43 | config.server.type = 'mongodb'; 44 | libLwm2m2.start(config.server, function (error, srvInfo) { 45 | testInfo.serverInfo = srvInfo; 46 | done(); 47 | }); 48 | }); 49 | 50 | afterEach(function(done) { 51 | delete config.server.type; 52 | libLwm2m2.stop(testInfo.serverInfo, done); 53 | }); 54 | 55 | describe('When a client registration request doesn\'t indicate a endpoint name arrives', function() { 56 | var requestUrl = { 57 | host: 'localhost', 58 | port: config.server.port, 59 | method: 'POST', 60 | pathname: '/rd', 61 | query: 'lt=86400&lwm2m=1.0&b=U' 62 | }, 63 | payload = ', , , , '; 64 | 65 | it('should fail with a 4.00 Bad Request', utils.checkCode(testInfo, requestUrl, payload, '4.00')); 66 | }); 67 | describe('When a client registration requests doesn\'t indicate a lifetime arrives', function () { 68 | var requestUrl = { 69 | host: 'localhost', 70 | port: config.server.port, 71 | method: 'POST', 72 | pathname: '/rd', 73 | query: 'ep=ROOM001&lwm2m=1.0&b=U' 74 | }, 75 | payload = ', , , , '; 76 | 77 | 78 | it('should return a 2.01 OK (as it is optional)', utils.checkCode(testInfo, requestUrl, payload, '2.01')); 79 | }); 80 | describe('When a client registration requests doesn\'t indicate a binding arrives', function () { 81 | var requestUrl = { 82 | host: 'localhost', 83 | port: config.server.port, 84 | method: 'POST', 85 | pathname: '/rd', 86 | query: 'ep=ROOM001<=86400&lwm2m=1.0' 87 | }, 88 | payload = ', , , , '; 89 | 90 | 91 | it('should return a 2.01 OK (as it is optional)', utils.checkCode(testInfo, requestUrl, payload, '2.01')); 92 | }); 93 | describe('When a correct client registration requests arrives', function () { 94 | var requestUrl = { 95 | host: localhost, 96 | port: config.server.port, 97 | method: 'POST', 98 | pathname: '/rd', 99 | query: 'ep=ROOM001<=86400&lwm2m=1.0&b=U' 100 | }, 101 | payload = ', , , , '; 102 | 103 | it('should return a 2.01 Created code', utils.checkCode(testInfo, requestUrl, payload, '2.01')); 104 | 105 | it('should invoke the "registration" handler with the parameters', function (done) { 106 | var req = coap.request(requestUrl), 107 | rs = new Readable(), 108 | handlerCalled = false; 109 | 110 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 111 | function(endpoint, lifetime, version, binding, payload, callback) { 112 | should.exist(endpoint); 113 | should.exist(lifetime); 114 | should.exist(version); 115 | should.exist(binding); 116 | should.exist(payload); 117 | endpoint.should.equal('ROOM001'); 118 | lifetime.should.equal('86400'); 119 | version.should.equal('1.0'); 120 | binding.should.equal('U'); 121 | payload.should.equal(', , , , '); 122 | handlerCalled = true; 123 | 124 | callback(); 125 | }); 126 | 127 | rs.push(payload); 128 | rs.push(null); 129 | rs.pipe(req); 130 | 131 | req.on('response', function(res) { 132 | handlerCalled.should.equal(true); 133 | done(); 134 | }); 135 | }); 136 | it('should include Location-Path Options in the response', function (done) { 137 | var req = coap.request(requestUrl), 138 | rs = new Readable(); 139 | 140 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 141 | function(endpoint, lifetime, version, binding, payload, callback) { 142 | callback(); 143 | }); 144 | 145 | rs.push(payload); 146 | rs.push(null); 147 | rs.pipe(req); 148 | 149 | req.on('response', function(res) { 150 | res.options.length.should.equal(1); 151 | res.options[0].name.should.equal('Location-Path'); 152 | res.options[0].value.should.match(/rd\/.*/); 153 | done(); 154 | }); 155 | }); 156 | }); 157 | describe('When a client registration expires (due to its lifetime)', function(done) { 158 | it('should reject every subsequent operation with that device ID'); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /test/unit/server/client-unregistration-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var libLwm2m2 = require('../../../').server, 27 | coap = require('coap'), 28 | utils = require('./../testUtils'), 29 | config = require('../../../config'), 30 | async = require('async'), 31 | should = require('should'), 32 | testInfo = {}; 33 | 34 | describe('Client unregistration tests', function() { 35 | var deviceLocation; 36 | 37 | function registerHandlers(callback) { 38 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 39 | function(endpoint, lifetime, version, binding, payload, innerCb) { 40 | innerCb(); 41 | }); 42 | 43 | callback(); 44 | } 45 | 46 | beforeEach(function (done) { 47 | libLwm2m2.start(config.server, function (error, srvInfo){ 48 | testInfo.serverInfo = srvInfo; 49 | 50 | async.series([ 51 | libLwm2m2.getRegistry().clean, 52 | registerHandlers, 53 | async.apply(utils.registerClient, 'ROOM001') 54 | ], function (error, results) { 55 | deviceLocation = results[2][0]; 56 | done(); 57 | }); 58 | }); 59 | }); 60 | 61 | afterEach(function(done) { 62 | libLwm2m2.stop(testInfo.serverInfo, done); 63 | }); 64 | 65 | describe('When a unregistration for a not registered device arrives', function () { 66 | var removeRequest = { 67 | host: 'localhost', 68 | port: config.server.port, 69 | method: 'DELETE', 70 | pathname: '/rd/136' 71 | }; 72 | 73 | beforeEach(function () { 74 | libLwm2m2.setHandler(testInfo.serverInfo, 'unregistration', function(device, callback) { 75 | callback(); 76 | }); 77 | }); 78 | 79 | it('should return a 4.04 Not found code', utils.checkCode(testInfo, removeRequest, '', '4.04')); 80 | }); 81 | describe('When a correct client unregistration request arrives', function() { 82 | var removeRequest = { 83 | host: 'localhost', 84 | port: config.server.port, 85 | method: 'DELETE' 86 | }; 87 | 88 | beforeEach(function (done) { 89 | removeRequest.pathname = deviceLocation; 90 | done(); 91 | }); 92 | 93 | it('should remove the device registration', function(done) { 94 | var req = coap.request(removeRequest), 95 | handlerCalled = false; 96 | 97 | libLwm2m2.setHandler(testInfo.serverInfo, 'unregistration', function(device, callback) { 98 | should.exist(device); 99 | should.exist(device.name); 100 | device.name.should.equal('ROOM001'); 101 | handlerCalled = true; 102 | callback(); 103 | }); 104 | 105 | req.on('response', function(res) { 106 | handlerCalled.should.equal(true); 107 | done(); 108 | }); 109 | 110 | req.end(); 111 | }); 112 | it('should return a 4.04 Not found code', utils.checkCode(testInfo, removeRequest, '', '2.02')); 113 | }); 114 | }); -------------------------------------------------------------------------------- /test/unit/server/device-registration-utils-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of lwm2m-node-lib 5 | * 6 | * lwm2m-node-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * lwm2m-node-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with lwm2m-node-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 25 | 'use strict'; 26 | 27 | var registrationUtils = require('../../../lib/services/server/utils/deviceRegistrationUtils'), 28 | assert = require('assert'); 29 | 30 | describe('Device registration utils ', function () { 31 | 32 | describe('When device registers with /rd and no type in query params', function () { 33 | 34 | var config = { 35 | defaultType: 'defaultType' 36 | }; 37 | 38 | var urlObj = { 39 | pathname: '/rd', 40 | query: 'ep=testEndpoint<=85671&lwm2m=1.0&b=U' 41 | }; 42 | 43 | var actual = registrationUtils.getDeviceTypeFromUrlRequest(urlObj, config); 44 | 45 | it('should return default type', function () { 46 | assert.equal(actual, config.defaultType); 47 | }); 48 | }); 49 | 50 | describe('When device registers with /service/rd', function () { 51 | 52 | var urlObj = { 53 | pathname: '/service/rd', 54 | query: 'ep=testEndpoint<=85671&lwm2m=1.0&b=U' 55 | }; 56 | 57 | 58 | it('if config.types include service should return service type', function () { 59 | var config = { 60 | defaultType: 'defaultType', 61 | types: [{ 62 | url: '/service', 63 | name: 'service' 64 | }] 65 | }; 66 | var actual = registrationUtils.getDeviceTypeFromUrlRequest(urlObj, config); 67 | 68 | assert.equal(actual, 'service'); 69 | }); 70 | 71 | it('if config.types include service should return service type', function () { 72 | var config = { 73 | defaultType: 'defaultType', 74 | types: [] 75 | }; 76 | var actual = registrationUtils.getDeviceTypeFromUrlRequest(urlObj, config); 77 | 78 | assert.equal(actual, undefined); 79 | }); 80 | 81 | }); 82 | 83 | describe('When device registers with /rd and type in query params', function () { 84 | 85 | var urlObj = { 86 | pathname: '/rd', 87 | query: 'ep=testEndpoint<=85671&lwm2m=1.0&b=U&type=service' 88 | }; 89 | 90 | 91 | it('if config.types include service should return service type', function () { 92 | var config = { 93 | defaultType: 'defaultType', 94 | types: [{ 95 | url: '/service', 96 | name: 'service' 97 | }] 98 | }; 99 | var actual = registrationUtils.getDeviceTypeFromUrlRequest(urlObj, config); 100 | 101 | assert.equal(actual, 'service'); 102 | }); 103 | 104 | it('if config.types include service should return service type', function () { 105 | var config = { 106 | defaultType: 'defaultType', 107 | types: [] 108 | }; 109 | var actual = registrationUtils.getDeviceTypeFromUrlRequest(urlObj, config); 110 | 111 | assert.equal(actual, undefined); 112 | }); 113 | 114 | }); 115 | 116 | describe('When device registers with type /rd/rd', function () { 117 | 118 | var urlObj = { 119 | pathname: '/rd/rd', 120 | query: 'ep=testEndpoint<=85671&lwm2m=1.0&b=U' 121 | }; 122 | 123 | it('should return default type', function () { 124 | var config = { 125 | defaultType: 'defaultType', 126 | types: [ 127 | { 128 | url: '/service', 129 | name: 'service' 130 | }, 131 | { 132 | url: '/rd', 133 | name: 'rd' 134 | } 135 | ] 136 | }; 137 | var actual = registrationUtils.getDeviceTypeFromUrlRequest(urlObj, config); 138 | 139 | assert.equal(actual, 'defaultType'); 140 | }); 141 | 142 | }); 143 | 144 | describe('When device registers with type /lightConfig/rd', function () { 145 | 146 | var urlObj = { 147 | pathname: '/lightConfig/rd', 148 | query: 'ep=testEndpoint<=85671&lwm2m=1.0&b=U' 149 | }; 150 | 151 | it('should return LightConfig type', function () { 152 | var config = { 153 | defaultType: 'defaultType', 154 | types: [ 155 | { 156 | url: '/light', 157 | name: 'Light' 158 | }, 159 | { 160 | url: '/lightConfig', 161 | name: 'LightConfig' 162 | } 163 | ] 164 | }; 165 | var actual = registrationUtils.getDeviceTypeFromUrlRequest(urlObj, config); 166 | 167 | assert.equal(actual, 'LightConfig'); 168 | }); 169 | 170 | }); 171 | 172 | 173 | }); 174 | -------------------------------------------------------------------------------- /test/unit/server/device-registry-mongo-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var libLwm2m2 = require('../../../').server, 27 | utils = require('./../testUtils'), 28 | config = require('../../../config'), 29 | mongo = require('mongodb').MongoClient, 30 | should = require('should'), 31 | async = require('async'), 32 | testInfo = {}, 33 | iotAgentDb; 34 | 35 | describe('MongoDB Device registry', function() { 36 | var deviceLocation; 37 | 38 | function registerHandlers(callback) { 39 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 40 | function(endpoint, lifetime, version, binding, payload, innerCb) { 41 | innerCb(); 42 | }); 43 | 44 | callback(); 45 | } 46 | 47 | beforeEach(function (done) { 48 | config.server.deviceRegistry = { 49 | type: 'mongodb' 50 | }; 51 | config.server.mongodb = { 52 | host: 'localhost', 53 | port: '27017', 54 | db: 'lwtm2m' 55 | }; 56 | libLwm2m2.start(config.server, function (error, srvInfo){ 57 | testInfo.serverInfo = srvInfo; 58 | 59 | async.series([ 60 | libLwm2m2.getRegistry().clean, 61 | registerHandlers 62 | ], function (error, results) { 63 | deviceLocation = results[2]; 64 | mongo.connect('mongodb://localhost:27017/lwtm2m', { useNewUrlParser: true }, function(err, db) { 65 | iotAgentDb = db; 66 | done(); 67 | }); 68 | }); 69 | }); 70 | }); 71 | 72 | afterEach(function(done) { 73 | delete config.server.deviceRegistry; 74 | delete config.server.mongodb; 75 | 76 | iotAgentDb.db().collection('devices').deleteOne(function(error) { 77 | iotAgentDb.close(function(error) { 78 | libLwm2m2.stop(testInfo.serverInfo, done); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('When a registration request arrives to the server', function() { 84 | it('should insert a new device in the "devices" collection', function(done) { 85 | utils.registerClient('ROOM001', function (error) { 86 | iotAgentDb.db().collection('devices').find({}).toArray(function(err, docs) { 87 | should.not.exist(err); 88 | should.exist(docs); 89 | should.exist(docs.length); 90 | docs.length.should.equal(1); 91 | should.exist(docs[0]); 92 | should.exist(docs[0].path); 93 | should.exist(docs[0].lifetime); 94 | should.exist(docs[0].type); 95 | should.exist(docs[0].id); 96 | done(); 97 | }); 98 | }); 99 | }); 100 | it('should store the device with all its attributes', function(done) { 101 | utils.registerClient('ROOM001', function (error) { 102 | iotAgentDb.db().collection('devices').find({}).toArray(function(err, docs) { 103 | should.exist(docs[0]); 104 | should.exist(docs[0].path); 105 | should.exist(docs[0].lifetime); 106 | should.exist(docs[0].type); 107 | should.exist(docs[0].id); 108 | docs[0].path.should.equal('/rd'); 109 | docs[0].lifetime.should.equal('86400'); 110 | docs[0].type.should.equal('Device'); 111 | done(); 112 | }); 113 | }); 114 | }); 115 | }); 116 | 117 | describe('When a user executes the List operation of the library on a registry with two records', function () { 118 | beforeEach(function (done) { 119 | async.series([ 120 | async.apply(utils.registerClient, 'ROOM001'), 121 | async.apply(utils.registerClient, 'ROOM002') 122 | ], done); 123 | }); 124 | 125 | it('both records should appear in the listing returned to the caller', function (done) { 126 | libLwm2m2.listDevices(function(error, deviceList) { 127 | should.not.exist(error); 128 | should.exist(deviceList); 129 | deviceList.length.should.equal(2); 130 | done(); 131 | }); 132 | }); 133 | }); 134 | describe('When a user looks for an existing device in the registry by name', function () { 135 | beforeEach(function (done) { 136 | async.series([ 137 | async.apply(utils.registerClient, 'ROOM001'), 138 | async.apply(utils.registerClient, 'ROOM002') 139 | ], done); 140 | }); 141 | 142 | it('should return the selected device to the caller', function (done) { 143 | libLwm2m2.getDevice('ROOM002', function(error, device) { 144 | should.not.exist(error); 145 | should.exist(device); 146 | device.name.should.equal('ROOM002'); 147 | done(); 148 | }); 149 | }); 150 | }); 151 | describe('When a user looks for a non-existing device in the registry by name', function () { 152 | beforeEach(function (done) { 153 | async.series([ 154 | async.apply(utils.registerClient, 'ROOM001'), 155 | async.apply(utils.registerClient, 'ROOM002') 156 | ], done); 157 | }); 158 | 159 | it('should return a DeviceNotFound error to the caller', function (done) { 160 | libLwm2m2.getDevice('ROOM009', function(error, device) { 161 | should.exist(error); 162 | should.not.exist(device); 163 | error.name.should.equal('DEVICE_NOT_FOUND'); 164 | done(); 165 | }); 166 | }); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /test/unit/server/device-registry-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var libLwm2m2 = require('../../../').server, 27 | utils = require('./../testUtils'), 28 | config = require('../../../config'), 29 | should = require('should'), 30 | async = require('async'), 31 | testInfo = {}; 32 | 33 | describe('Device registry', function() { 34 | var deviceLocation; 35 | 36 | function registerHandlers(callback) { 37 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 38 | function(endpoint, lifetime, version, binding, payload, innerCb) { 39 | innerCb(); 40 | }); 41 | 42 | callback(); 43 | } 44 | 45 | beforeEach(function (done) { 46 | libLwm2m2.start(config.server, function (error, srvInfo){ 47 | testInfo.serverInfo = srvInfo; 48 | 49 | async.series([ 50 | libLwm2m2.getRegistry().clean, 51 | registerHandlers, 52 | async.apply(utils.registerClient, 'ROOM001'), 53 | async.apply(utils.registerClient, 'ROOM002') 54 | ], function (error, results) { 55 | deviceLocation = results[2]; 56 | done(); 57 | }); 58 | }); 59 | }); 60 | 61 | afterEach(function(done) { 62 | libLwm2m2.stop(testInfo.serverInfo, done); 63 | }); 64 | 65 | describe('When a user executes the List operation of the library on a registry with two records', function () { 66 | it('both records should appear in the listing returned to the caller', function (done) { 67 | libLwm2m2.listDevices(function(error, deviceList) { 68 | should.not.exist(error); 69 | should.exist(deviceList); 70 | deviceList.length.should.equal(2); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | describe('When a user looks for an existing device in the registry by name', function () { 76 | it('should return the selected device to the caller', function (done) { 77 | libLwm2m2.getDevice('ROOM002', function(error, device) { 78 | should.not.exist(error); 79 | should.exist(device); 80 | device.name.should.equal('ROOM002'); 81 | done(); 82 | }); 83 | }); 84 | }); 85 | describe('When a user looks for a non-existing device in the registry by name', function () { 86 | it('should return a DeviceNotFound error to the caller', function (done) { 87 | libLwm2m2.getDevice('ROOM009', function(error, device) { 88 | should.exist(error); 89 | should.not.exist(device); 90 | error.name.should.equal('DEVICE_NOT_FOUND'); 91 | done(); 92 | }); 93 | }); 94 | }); 95 | }); -------------------------------------------------------------------------------- /test/unit/server/information-reporting-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var libLwm2m2 = require('../../../').server, 27 | utils = require('./../testUtils'), 28 | config = require('../../../config'), 29 | memoryRegistry = require('../../../lib/services/server/inMemoryDeviceRegistry'), 30 | libcoap = require('coap'), 31 | should = require('should'), 32 | server = libcoap.createServer({type: config.server.ipProtocol}), 33 | async = require('async'), 34 | testInfo = {}; 35 | 36 | describe('Information reporting interface', function() { 37 | var deviceLocation; 38 | 39 | function registerHandlers(callback) { 40 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 41 | function(endpoint, lifetime, version, binding, payload, innerCb) { 42 | innerCb(); 43 | }); 44 | 45 | libLwm2m2.setHandler(testInfo.serverInfo, 'updateRegistration', function(object, innerCb) { 46 | innerCb(); 47 | }); 48 | 49 | callback(); 50 | } 51 | 52 | function emptyHandler(data) { 53 | 54 | } 55 | 56 | beforeEach(function (done) { 57 | memoryRegistry.clean(function () { 58 | libLwm2m2.start(config.server, function (error, srvInfo){ 59 | testInfo.serverInfo = srvInfo; 60 | 61 | async.series([ 62 | registerHandlers, 63 | async.apply(utils.registerClient, 'ROOM001') 64 | ], function (error, results) { 65 | deviceLocation = results[1][0]; 66 | 67 | server.listen(results[1][1], function (error) { 68 | done(); 69 | }); 70 | }); 71 | }); 72 | }); 73 | }); 74 | 75 | afterEach(function(done) { 76 | async.series([ 77 | libLwm2m2.cleanObservers, 78 | async.apply(libLwm2m2.stop, testInfo.serverInfo) 79 | ], function (error) { 80 | server.removeAllListeners('request'); 81 | server.close(done); 82 | }); 83 | }); 84 | 85 | describe('When the user invokes the Observe operation on a resource', function() { 86 | it('should send a COAP GET Request with a generated Observe Option for the resource', 87 | function (done) { 88 | server.on('request', function (req, res) { 89 | req.method.should.equal('GET'); 90 | req.options.should.containDeep([{name: 'Observe'}]); 91 | res.code = '2.05'; 92 | res.setOption('Observe', 1); 93 | res.end('The Read content'); 94 | }); 95 | 96 | libLwm2m2.observe(deviceLocation.split('/')[1], '6', '2', '5', emptyHandler, function (error, result) { 97 | should.not.exist(error); 98 | should.exist(result); 99 | result.should.equal('The Read content'); 100 | done(); 101 | }); 102 | }); 103 | it('should store the subscription to the value', function (done) { 104 | server.on('request', function (req, res) { 105 | res.code = '2.05'; 106 | res.setOption('Observe', 1); 107 | res.end('The Read content'); 108 | }); 109 | 110 | libLwm2m2.observe(deviceLocation.split('/')[1], '6', '2', '5', emptyHandler, function (error, result) { 111 | should.not.exist(error); 112 | 113 | libLwm2m2.listObservers(function (error, result) { 114 | should.not.exist(error); 115 | result.length.should.equal(1); 116 | result[0].resource.should.equal('/6/2/5'); 117 | result[0].deviceId.should.equal(deviceLocation.split('/')[1]); 118 | done(); 119 | }); 120 | }); 121 | }); 122 | }); 123 | describe('When a Notify message arrives with an Observe Option', function() { 124 | beforeEach(function () { 125 | server.on('request', function (req, res) { 126 | res.code = '2.05'; 127 | res.setOption('Observe', 1); 128 | res.write('The First content'); 129 | res.write('The Second content'); 130 | res.write('The Third content'); 131 | res.end('The final content'); 132 | }); 133 | }); 134 | 135 | it('should invoke the user notification handler once per notify message', function (done) { 136 | var handlerInvokedTimes = 0; 137 | 138 | function userHandler(data) { 139 | handlerInvokedTimes++; 140 | 141 | if (data === 'The final content') { 142 | handlerInvokedTimes.should.equal(3); 143 | done(); 144 | } 145 | } 146 | 147 | libLwm2m2.observe(deviceLocation.split('/')[1], '6', '2', '5', userHandler, function (error, result) { 148 | should.not.exist(error); 149 | }); 150 | }); 151 | }); 152 | describe.skip('When the user invokes the Cancel operation on a resource', function() { 153 | beforeEach(function () { 154 | server.on('request', function (req, res) { 155 | function notify(msg) { 156 | try { 157 | res.write(msg); 158 | } catch(e) { 159 | // Expected to throw an error if the server closes the connection first. 160 | } 161 | } 162 | res.code = '2.05'; 163 | res.setOption('Observe', 1); 164 | 165 | res.write('The First content'); 166 | setTimeout(notify.bind(null, 'The Second content'), 100); 167 | setTimeout(notify.bind(null, 'The Third content'), 200); 168 | setTimeout(notify.bind(null, 'The Fourth content'), 300); 169 | setTimeout(notify.bind(null, 'The Fifth content'), 400); 170 | }); 171 | }); 172 | 173 | it('should stop sending updates for the resource value', function (done) { 174 | var handlerInvokedTimes = 0, 175 | maxTestDuration = 1500; 176 | 177 | function userHandler(data) { 178 | handlerInvokedTimes++; 179 | 180 | if (handlerInvokedTimes === 1) { 181 | libLwm2m2.cancelObserver(deviceLocation.split('/')[1], '6', '2', '5', function () { 182 | setTimeout(function () { 183 | handlerInvokedTimes.should.equal(1); 184 | done(); 185 | }, maxTestDuration); 186 | }); 187 | } 188 | } 189 | 190 | libLwm2m2.observe(deviceLocation.split('/')[1], '6', '2', '5', userHandler, function (error, result) { 191 | should.not.exist(error); 192 | }); 193 | }); 194 | 195 | it('should remove the listener from the observers list', function (done) { 196 | var handlerInvokedTimes = 0; 197 | 198 | function userHandler(data) { 199 | handlerInvokedTimes++; 200 | } 201 | 202 | libLwm2m2.observe(deviceLocation.split('/')[1], '6', '2', '5', userHandler, function (error, result) { 203 | should.not.exist(error); 204 | 205 | libLwm2m2.cancelObserver(deviceLocation.split('/')[1], '6', '2', '5', function () { 206 | libLwm2m2.listObservers(function (error, result) { 207 | should.not.exist(error); 208 | result.length.should.equal(0); 209 | done(); 210 | }); 211 | }); 212 | }); 213 | }); 214 | }); 215 | }); -------------------------------------------------------------------------------- /test/unit/server/multiple-southbound-interfaces-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var libLwm2m2 = require('../../../').server, 27 | coap = require('coap'), 28 | Readable = require('stream').Readable, 29 | config = require('../../../config'), 30 | should = require('should'), 31 | testInfo = {}; 32 | 33 | describe('Multiple southbound interfaces', function() { 34 | function initializeServer(done) { 35 | config.server.baseRoot = '/theBaseUrl'; 36 | config.server.defaultType = 'Device'; 37 | config.server.types = [ 38 | { 39 | name: 'Light', 40 | url: '/light' 41 | } 42 | ]; 43 | libLwm2m2.start(config.server, function (error, srvInfo) { 44 | testInfo.serverInfo = srvInfo; 45 | done(); 46 | }); 47 | } 48 | 49 | beforeEach(initializeServer); 50 | 51 | afterEach(function(done) { 52 | delete config.server.types; 53 | delete config.server.baseRoot; 54 | libLwm2m2.stop(testInfo.serverInfo, done); 55 | }); 56 | 57 | describe('When a client registration request arrives to a server with a customized Base URL', function() { 58 | var requestUrl = { 59 | host: 'localhost', 60 | port: config.server.port, 61 | method: 'POST', 62 | pathname: '/rd', 63 | query: 'ep=ROOM001<=86400&lwm2m=1.0&b=U' 64 | }, 65 | payload = ', , , , '; 66 | 67 | it('should return the Location-info URL with the prefixed Base URL', function (done) { 68 | var req = coap.request(requestUrl), 69 | rs = new Readable(), 70 | found = false; 71 | 72 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 73 | function(endpoint, lifetime, version, binding, payload, callback) { 74 | callback(); 75 | }); 76 | 77 | rs.push(payload); 78 | rs.push(null); 79 | rs.pipe(req); 80 | 81 | req.on('response', function(res) { 82 | for (var i = 0; i < res.options.length; i++) { 83 | if (res.options[i].name === 'Location-Path') { 84 | res.options[i].value.should.match(/\/theBaseUrl.*/); 85 | found = true; 86 | } 87 | } 88 | 89 | found.should.equal(true); 90 | done(); 91 | }); 92 | }); 93 | }); 94 | describe('When a registration targets a type URL', function() { 95 | var requestUrl = { 96 | host: 'localhost', 97 | port: config.server.port, 98 | method: 'POST', 99 | pathname: '/light/rd', 100 | query: 'ep=ROOM001<=86400&lwm2m=1.0&b=U' 101 | }, 102 | payload = ', , , , '; 103 | 104 | it('should return the Location-info URL with the prefixed type URL', function (done) { 105 | var req = coap.request(requestUrl), 106 | rs = new Readable(); 107 | 108 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 109 | function(endpoint, lifetime, version, binding, payload, callback) { 110 | callback(); 111 | }); 112 | 113 | rs.push(payload); 114 | rs.push(null); 115 | rs.pipe(req); 116 | 117 | req.on('response', function(res) { 118 | res.code.should.equal('2.01'); 119 | 120 | libLwm2m2.getRegistry().getByName('ROOM001', function(error, device) { 121 | should.not.exist(error); 122 | should.exist(device.type); 123 | device.type.should.equal('Light'); 124 | done(); 125 | }); 126 | }); 127 | }); 128 | }); 129 | describe('When a registration targets an unexistent type', function() { 130 | var requestUrl = { 131 | host: 'localhost', 132 | port: config.server.port, 133 | method: 'POST', 134 | pathname: '/pressure/rd', 135 | query: 'ep=ROOM001<=86400&lwm2m=1.0&b=U' 136 | }, 137 | payload = ', , , , '; 138 | 139 | it('should return the Location-info URL with the prefixed type URL', function (done) { 140 | var req = coap.request(requestUrl), 141 | rs = new Readable(); 142 | 143 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 144 | function(endpoint, lifetime, version, binding, payload, callback) { 145 | callback(); 146 | }); 147 | 148 | rs.push(payload); 149 | rs.push(null); 150 | rs.pipe(req); 151 | 152 | req.on('response', function(res) { 153 | res.code.should.equal('4.04'); 154 | should.exist(res.payload); 155 | res.payload.toString('utf8').should.equal('TYPE_NOT_FOUND'); 156 | 157 | done(); 158 | }); 159 | }); 160 | }); 161 | describe('When a registration targets a type and there is not a type array configured', function() { 162 | it('should return an TYPE_NOT_FOUND error'); 163 | }); 164 | describe('When a registration targets the default URL and there is not a default type configured', function() { 165 | var requestUrl = { 166 | host: 'localhost', 167 | port: config.server.port, 168 | method: 'POST', 169 | pathname: '/rd', 170 | query: 'ep=ROOM001<=86400&lwm2m=1.0&b=U' 171 | }, 172 | payload = ', , , , '; 173 | 174 | beforeEach(function() { 175 | delete config.server.defaultType; 176 | }); 177 | 178 | afterEach(function() { 179 | config.server.defaultType = 'Device'; 180 | }); 181 | 182 | it('should return a TYPE_NOT_FOUND error', function (done) { 183 | var req = coap.request(requestUrl), 184 | rs = new Readable(); 185 | 186 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 187 | function(endpoint, lifetime, version, binding, payload, callback) { 188 | callback(); 189 | }); 190 | 191 | rs.push(payload); 192 | rs.push(null); 193 | rs.pipe(req); 194 | 195 | req.on('response', function(res) { 196 | res.code.should.equal('4.04'); 197 | should.exist(res.payload); 198 | res.payload.toString('utf8').should.equal('TYPE_NOT_FOUND'); 199 | 200 | done(); 201 | }); 202 | }); 203 | }); 204 | describe('When a server is started with a type url of "/rd"', function() { 205 | beforeEach(function(done) { 206 | config.server.types = [ 207 | { 208 | name: 'Reductioner', 209 | url: '/rd' 210 | } 211 | ]; 212 | libLwm2m2.stop(testInfo.serverInfo, done); 213 | }); 214 | 215 | afterEach(initializeServer); 216 | 217 | it('should raise an ILLEGAL_TYPE_URL exception', function(done) { 218 | libLwm2m2.start(config.server, function (error, srvInfo) { 219 | should.exist(error); 220 | error.name.should.equal('ILLEGAL_TYPE_URL'); 221 | done(); 222 | }); 223 | }); 224 | }); 225 | }); 226 | -------------------------------------------------------------------------------- /test/unit/server/update-registration-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | var libLwm2m2 = require('../../../').server, 27 | utils = require('./../testUtils'), 28 | config = require('../../../config'), 29 | async = require('async'), 30 | testInfo = {}; 31 | 32 | describe('Client update registration interface', function() { 33 | var deviceLocation; 34 | 35 | function registerHandlers(callback) { 36 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 37 | function(endpoint, lifetime, version, binding, payload, innerCb) { 38 | innerCb(); 39 | }); 40 | 41 | libLwm2m2.setHandler(testInfo.serverInfo, 'updateRegistration', function(object, payload, innerCb) { 42 | innerCb(); 43 | }); 44 | 45 | callback(); 46 | } 47 | 48 | beforeEach(function (done) { 49 | libLwm2m2.start(config.server, function (error, srvInfo){ 50 | testInfo.serverInfo = srvInfo; 51 | 52 | async.series([ 53 | registerHandlers, 54 | async.apply(utils.registerClient, 'ROOM001') 55 | ], function (error, results) { 56 | deviceLocation = results[1][0]; 57 | 58 | done(); 59 | }); 60 | }); 61 | }); 62 | 63 | afterEach(function(done) { 64 | libLwm2m2.stop(testInfo.serverInfo, done); 65 | }); 66 | 67 | describe('When a correct client registration update request arrives', function() { 68 | var updateRequest = { 69 | host: 'localhost', 70 | port: config.server.port, 71 | method: 'POST', 72 | query: 'lt=86400&b=U' 73 | }; 74 | 75 | beforeEach(function() { 76 | updateRequest.pathname = deviceLocation; 77 | }); 78 | 79 | it('should return a 2.04 Changed code', utils.checkCode(testInfo, updateRequest, '', '2.04')); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/unit/testUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U 3 | * 4 | * This file is part of iotagent-lwm2m-lib 5 | * 6 | * iotagent-lwm2m-lib is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the License, 9 | * or (at your option) any later version. 10 | * 11 | * iotagent-lwm2m-lib is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * See the GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public 17 | * License along with iotagent-lwm2m-lib. 18 | * If not, seehttp://www.gnu.org/licenses/. 19 | * 20 | * For those usages not covered by the GNU Affero General Public License 21 | * please contact with::[contacto@tid.es] 22 | */ 23 | 24 | 'use strict'; 25 | 26 | /*jshint unused:false */ 27 | 28 | var libLwm2m2 = require('../..').server, 29 | coap = require('coap'), 30 | Readable = require('stream').Readable, 31 | config = require('../../config'), 32 | should = require('should'), 33 | localhost; 34 | 35 | function checkCode(testInfo, requestUrl, payload, code) { 36 | return function (done) { 37 | var agent = new coap.Agent({type: config.server.ipProtocol}), 38 | req = agent.request(requestUrl), 39 | rs = new Readable(); 40 | 41 | if (config.server.ipProtocol === 'udp6') { 42 | localhost = '::1'; 43 | } else { 44 | localhost = '127.0.0.1'; 45 | } 46 | 47 | libLwm2m2.setHandler(testInfo.serverInfo, 'registration', 48 | function(endpoint, lifetime, version, binding, payload, callback) { 49 | callback(); 50 | }); 51 | 52 | rs.push(payload); 53 | rs.push(null); 54 | rs.pipe(req); 55 | 56 | req.on('response', function(res) { 57 | res.code.should.equal(code); 58 | done(); 59 | }); 60 | }; 61 | } 62 | 63 | function registerClient(deviceName, callback) { 64 | var rs = new Readable(), 65 | creationRequest = { 66 | host: localhost, 67 | port: config.server.port, 68 | method: 'POST', 69 | pathname: '/rd', 70 | query: 'ep=' + deviceName + '<=86400&lwm2m=1.0&b=U' 71 | }, 72 | payload = ', , , , ', 73 | agent = new coap.Agent({type: config.server.ipProtocol}), 74 | req = agent.request(creationRequest), 75 | deviceLocation; 76 | 77 | rs.push(payload); 78 | rs.push(null); 79 | rs.pipe(req); 80 | 81 | req.on('response', function(res) { 82 | for (var i = 0; i < res.options.length; i++) { 83 | if (res.options[i].name === 'Location-Path') { 84 | deviceLocation = res.options[i].value; 85 | } 86 | } 87 | 88 | callback(null, deviceLocation, res.outSocket.port); 89 | }); 90 | } 91 | 92 | exports.checkCode = checkCode; 93 | exports.registerClient = registerClient; --------------------------------------------------------------------------------