• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

telefonicaid / iotagent-node-lib / 18165933486

01 Oct 2025 02:42PM UTC coverage: 79.318% (-0.001%) from 79.319%
18165933486

Pull #1734

github

web-flow
Merge 04824d46d into fd0c311cb
Pull Request #1734: allow use commands by simple subscriptions/notifications

2038 of 2743 branches covered (74.3%)

Branch coverage included in aggregate %.

89 of 110 new or added lines in 10 files covered. (80.91%)

10 existing lines in 1 file now uncovered.

3914 of 4761 relevant lines covered (82.21%)

281.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.2
/lib/services/devices/registrationUtils.js
1
/*
2
 * Copyright 2015 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, see http://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
 * Modified by: Daniel Calvo - ATOS Research & Innovation
25
 */
26

27
/* eslint-disable consistent-return */
28

29
const errors = require('../../errors');
1✔
30
const logger = require('logops');
1✔
31
const _ = require('underscore');
1✔
32
const intoTrans = require('../common/domain').intoTrans;
1✔
33
const config = require('../../commonConfig');
1✔
34
const context = {
1✔
35
    op: 'IoTAgentNGSI.Registration'
36
};
37
const async = require('async');
1✔
38
const utils = require('../northBound/restUtils');
1✔
39
const subscriptionService = require('../ngsi/subscriptionService');
1✔
40

41
const NGSI_LD_URN = 'urn:ngsi-ld:';
1✔
42

43
/**
44
 * Generates a handler for the registration requests that checks all the possible errors derived from the registration.
45
 * The parameter information is needed in order to fulfill error information.
46
 *
47
 * @param {Boolen} unregister       Indicates whether this registration is an unregistration or register.
48
 * @param {Object} deviceData       Object containing all the deviceData needed to send the registration.
49
 * @return {Function}              The generated handler.
50
 */
51
function createRegistrationHandlerNgsiLD(unregister, deviceData, callback) {
52
    /* eslint-disable-next-line no-unused-vars */
53
    return function handleRegistrationResponse(error, response, body) {
160✔
54
        if (error) {
160!
55
            logger.error(context, 'ORION-002: Connection error sending registrations to the Context Broker: %s', error);
×
56
            callback(error);
×
57
        } else if (response && response.statusCode === 201 && response.headers.location && unregister === false) {
160✔
58
            logger.debug(context, 'Registration success.');
158✔
59
            callback(null, {
158✔
60
                registrationId: response.headers.location.substr(response.headers.location.lastIndexOf('/') + 1)
61
            });
62
        } else if (response && response.statusCode === 204 && unregister === true) {
2!
63
            logger.debug(context, 'Unregistration success.');
×
64
            callback(null, null);
×
65
        } else if (response && response.statusCode && response.statusCode !== 500) {
2✔
66
            logger.error(context, 'Registration error connecting to the Context Broker: %j', response.statusCode);
1✔
67
            callback(new errors.BadRequest(JSON.stringify(response.statusCode)));
1✔
68
        } else {
69
            let errorObj;
70

71
            logger.error(context, 'ORION-003: Protocol error connecting to the Context Broker: %j', errorObj);
1✔
72

73
            if (unregister) {
1!
74
                errorObj = new errors.UnregistrationError(deviceData.id, deviceData.type);
×
75
            } else {
76
                errorObj = new errors.RegistrationError(deviceData.id, deviceData.type);
1✔
77
            }
78

79
            callback(errorObj);
1✔
80
        }
81
    };
82
}
83

84
/**
85
 * Generates a handler for the registration requests that checks all the possible errors derived from the registration.
86
 * The parameter information is needed in order to fulfill error information.
87
 *
88
 * @param {Boolen} unregister       Indicates whether this registration is an unregistration or register.
89
 * @param {Object} deviceData       Object containing all the deviceData needed to send the registration.
90
 * @return {Function}              The generated handler.
91
 */
92
function createRegistrationHandlerNgsi2(unregister, deviceData, callback) {
93
    /* eslint-disable-next-line no-unused-vars */
94
    return function handleRegistrationResponse(error, response, body) {
203✔
95
        if (error) {
203✔
96
            logger.error(context, 'ORION-002: Connection error sending registrations to the Context Broker: %s', error);
3✔
97
            callback(error);
3✔
98
        } else if (response && response.statusCode === 201 && response.headers.location && unregister === false) {
200✔
99
            logger.debug(context, 'Registration success.');
175✔
100
            callback(null, {
175✔
101
                registrationId: response.headers.location.substr(response.headers.location.lastIndexOf('/') + 1)
102
            });
103
        } else if (response && response.statusCode === 204 && unregister === true) {
25✔
104
            logger.debug(context, 'Unregistration success.');
20✔
105
            callback(null, null);
20✔
106
        } else if (response && response.statusCode && response.statusCode !== 500) {
5✔
107
            logger.error(context, 'Registration error connecting to the Context Broker: %j', response.statusCode);
1✔
108
            callback(new errors.BadRequest(JSON.stringify(response.statusCode)));
1✔
109
        } else {
110
            let errorObj;
111

112
            logger.error(context, 'ORION-003: Protocol error connecting to the Context Broker: %j', errorObj);
4✔
113

114
            if (unregister) {
4✔
115
                errorObj = new errors.UnregistrationError(deviceData.id, deviceData.type);
3✔
116
            } else {
117
                errorObj = new errors.RegistrationError(deviceData.id, deviceData.type);
1✔
118
            }
119

120
            callback(errorObj);
4✔
121
        }
122
    };
123
}
124

125
/**
126
 * Sends a Context Provider unregistration request to the Context Broker using NGSIv2.
127
 *
128
 * @param {Object} deviceData       Object containing all the deviceData needed to send the registration.
129
 */
130
function sendUnregistrationsNgsi2(deviceData, callback) {
131
    let cbHost = config.getConfig().contextBroker.url;
20✔
132
    if (deviceData.cbHost && deviceData.cbHost.indexOf('://') !== -1) {
20!
133
        cbHost = deviceData.cbHost;
×
134
    } else if (deviceData.cbHost && deviceData.cbHost.indexOf('://') === -1) {
20!
135
        cbHost = 'http://' + deviceData.cbHost;
×
136
    }
137
    const options = {
20✔
138
        url: cbHost + '/v2/registrations/' + deviceData.registrationId,
139
        method: 'DELETE',
140
        headers: {
141
            'fiware-service': deviceData.service,
142
            'fiware-servicepath': deviceData.subservice
143
        }
144
    };
145
    if (deviceData.cbHost && deviceData.cbHost.indexOf('://') !== -1) {
20!
146
        options.url = deviceData.cbHost + '/v2/registrations/' + deviceData.registrationId;
×
147
    } else if (deviceData.cbHost && deviceData.cbHost.indexOf('://') === -1) {
20!
148
        options.url = 'http://' + deviceData.cbHost + '/v2/registrations/' + deviceData.registrationId;
×
149
    }
150
    if (deviceData.registrationId) {
20✔
151
        logger.debug(context, 'Sending v2 device unregistrations to Context Broker at [%s]', options.url);
18✔
152
        logger.debug(context, 'Using the following request:\n\n%s\n\n', JSON.stringify(options, null, 4));
18✔
153

154
        return utils.executeWithSecurity(
18✔
155
            options,
156
            deviceData,
157
            createRegistrationHandlerNgsi2(true, deviceData, callback)
158
        );
159
    }
160

161
    logger.debug(context, 'No Context Provider registrations found for unregister');
2✔
162
    return callback(null, deviceData);
2✔
163
}
164

165
/**
166
 * Sends a Context Provider unregistration request to the Context Broker using NGSI-LD.
167
 *
168
 * @param {Object} deviceData       Object containing all the deviceData needed to send the registration.
169
 */
170
function sendUnregistrationsNgsiLD(deviceData, callback) {
171
    let cbHost = config.getConfig().contextBroker.url;
7✔
172
    if (deviceData.cbHost && deviceData.cbHost.indexOf('://') !== -1) {
7!
173
        cbHost = deviceData.cbHost;
×
174
    } else if (deviceData.cbHost && deviceData.cbHost.indexOf('://') === -1) {
7!
175
        cbHost = 'http://' + deviceData.cbHost;
×
176
    }
177
    const options = {
7✔
178
        url: cbHost + '/ngsi-ld/v1/csourceRegistrations/' + deviceData.registrationId,
179
        method: 'DELETE',
180
        headers: {
181
            'fiware-service': deviceData.service,
182
            'fiware-servicepath': deviceData.subservice,
183
            'NGSILD-Tenant': deviceData.service,
184
            'NGSILD-Path': deviceData.subservice
185
        }
186
    };
187
    if (deviceData.cbHost && deviceData.cbHost.indexOf('://') !== -1) {
7!
188
        options.url = deviceData.cbHost + '/ngsi-ld/v1/csourceRegistrations/' + deviceData.registrationId;
×
189
    } else if (deviceData.cbHost && deviceData.cbHost.indexOf('://') === -1) {
7!
190
        options.url = 'http://' + deviceData.cbHost + '/ngsi-ld/v1/csourceRegistrations/' + deviceData.registrationId;
×
191
    }
192
    if (deviceData.registrationId) {
7✔
193
        logger.debug(context, 'Sending device LD unregistrations to Context Broker at [%s]', options.url);
5✔
194
        logger.debug(context, 'Using the following request:\n\n%s\n\n', JSON.stringify(options, null, 4));
5✔
195

196
        return utils.executeWithSecurity(
5✔
197
            options,
198
            deviceData,
199
            createRegistrationHandlerNgsi2(true, deviceData, callback)
200
        );
201
    }
202

203
    logger.debug(context, 'No Context Provider registrations found for unregister');
2✔
204
    return callback(null, deviceData);
2✔
205
}
206

207
function formatAttributes(originalVector) {
208
    const attributeList = [];
686✔
209
    if (originalVector && originalVector.length) {
686✔
210
        for (let i = 0; i < originalVector.length; i++) {
278✔
211
            attributeList.push(originalVector[i].name);
284✔
212
        }
213
    }
214
    return attributeList;
686✔
215
}
216

217
function mergeWithSameName(old, current) {
218
    if (old.indexOf(current) < 0) {
284!
219
        old.push(current);
284✔
220
    }
221
    return old;
284✔
222
}
223

224
/**
225
 * Sends a Context Provider registration or unregistration request to the Context Broker using NGSIv2.
226
 *
227
 * @param {Boolen} unregister       Indicates whether this registration is an unregistration or register.
228
 * @param {Object} deviceData       Object containing all the deviceData needed to send the registration.
229
 */
230
function sendRegistrationsNgsi2(unregister, deviceData, callback) {
231
    // FIXME: When https://github.com/telefonicaid/fiware-orion/issues/3007 is merged into master branch,
232
    // this function should use the new API. This is just a temporary solution which implies deleting the
233
    // registration and creating a new one.
234
    function updateRegistrationNgsi2(deviceData, callback) {
235
        const functions = [];
9✔
236

237
        function removeRegistrationId(deviceData, unregistrationResult, callback) {
238
            delete deviceData.registrationId;
8✔
239
            return callback(null, deviceData);
8✔
240
        }
241

242
        functions.push(async.apply(sendRegistrationsNgsi2, true, deviceData));
9✔
243
        functions.push(async.apply(removeRegistrationId, deviceData));
9✔
244
        functions.push(async.apply(sendRegistrationsNgsi2, false));
9✔
245
        async.waterfall(functions, callback);
9✔
246
    }
247

248
    if (unregister) {
370✔
249
        return sendUnregistrationsNgsi2(deviceData, callback);
20✔
250
    }
251
    if (deviceData.registrationId) {
350✔
252
        return updateRegistrationNgsi2(deviceData, callback);
9✔
253
    }
254

255
    let cbHost = config.getConfig().contextBroker.url;
341✔
256
    if (deviceData.cbHost && deviceData.cbHost.indexOf('://') !== -1) {
341✔
257
        cbHost = deviceData.cbHost;
17✔
258
    } else if (deviceData.cbHost && deviceData.cbHost.indexOf('://') === -1) {
324!
259
        cbHost = 'http://' + deviceData.cbHost;
×
260
    }
261
    const options = {
341✔
262
        url: cbHost + '/v2/registrations',
263
        method: 'POST',
264
        json: {
265
            dataProvided: {
266
                entities: [
267
                    {
268
                        type: deviceData.type,
269
                        id: String(deviceData.name)
270
                    }
271
                ],
272
                attrs: []
273
            },
274
            provider: {
275
                http: {
276
                    url: config.getConfig().providerUrl
277
                }
278
            }
279
        },
280
        headers: {
281
            'fiware-service': deviceData.service,
282
            'fiware-servicepath': deviceData.subservice
283
        }
284
    };
285

286
    options.json.dataProvided.attrs = options.json.dataProvided.attrs
341✔
287
        .concat(formatAttributes(deviceData.lazy), formatAttributes(deviceData.commands))
288
        .reduce(mergeWithSameName, []);
289

290
    if (options.json.dataProvided.attrs.length === 0) {
341✔
291
        logger.debug(context, 'Registration with Context Provider is not needed. Device without lazy atts or commands');
161✔
292
        return callback(null, deviceData);
161✔
293
    }
294

295
    logger.debug(context, 'Sending v2 device registrations to Context Broker at [%s]', options.url);
180✔
296
    logger.debug(context, 'Using the following request:\n\n%s\n\n', JSON.stringify(options, null, 4));
180✔
297
    utils.executeWithSecurity(options, deviceData, createRegistrationHandlerNgsi2(unregister, deviceData, callback));
180✔
298
}
299

300
function sendUnsubscriptionsNgsi2(deviceData, callback) {
301
    if (deviceData.subscriptionId) {
2!
302
        logger.debug(
2✔
303
            context,
304
            'Sending v2 device unsubscriptions to Context Broker at [%s]',
305
            config.getConfig().contextBroker.url
306
        );
307
        logger.debug(context, 'Using the following subscriptionId %j', deviceData.subscriptionId);
2✔
308
        subscriptionService.unsubscribe(deviceData, deviceData.subscriptionId, callback);
2✔
309
    } else {
NEW
310
        logger.debug(context, 'No subscription found for unregister');
×
NEW
311
        return callback(null, deviceData);
×
312
    }
313
}
314

315
function sendSubscriptionsNgsi2(unregister, deviceData, callback) {
316
    function updateSubscriptionNgsi2(deviceData, callback) {
317
        const functions = [];
1✔
318

319
        function removeSubscriptionId(deviceData, unregistrationResult, callback) {
320
            delete deviceData.subscriptionId;
1✔
321
            return callback(null, deviceData);
1✔
322
        }
323

324
        functions.push(async.apply(sendSubscriptionsNgsi2, true, deviceData));
1✔
325
        functions.push(async.apply(removeSubscriptionId, deviceData));
1✔
326
        functions.push(async.apply(sendSubscriptionsNgsi2, false));
1✔
327
        async.waterfall(functions, callback);
1✔
328
    }
329

330
    if (unregister) {
7✔
331
        return sendUnsubscriptionsNgsi2(deviceData, callback);
2✔
332
    }
333
    if (deviceData.subscriptionId) {
5✔
334
        return updateSubscriptionNgsi2(deviceData, callback);
1✔
335
    }
336
    const attrs = [].concat(formatAttributes(deviceData.commands)).reduce(mergeWithSameName, []);
4✔
337

338
    if (attrs.length === 0) {
4!
NEW
339
        logger.debug(context, 'Subscription is not needed. Device without commands');
×
NEW
340
        return callback(null, deviceData);
×
341
    }
342
    const trigger = attrs; // one subscription for all commands
4✔
343
    const content = attrs;
4✔
344
    const attrsFormat = 'simplifiedNormalized';
4✔
345

346
    logger.debug(
4✔
347
        context,
348
        'Sending v2 device subscriptions to Context Broker at [%s]',
349
        config.getConfig().contextBroker.url
350
    );
351
    logger.debug(context, 'Using the following trigger %j and content %j', trigger, content);
4✔
352
    subscriptionService.subscribe(deviceData, trigger, content, attrsFormat, callback);
4✔
353
}
354

355
/**
356
 * Sends a Context Provider registration or unregistration request to the Context Broker using NGSI-LD.
357
 *
358
 * @param {Boolen} unregister       Indicates whether this registration is an unregistration or register.
359
 * @param {Object} deviceData       Object containing all the deviceData needed to send the registration.
360
 */
361
function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
362
    if (unregister) {
218✔
363
        return sendUnregistrationsNgsiLD(deviceData, callback);
7✔
364
    }
365

366
    const operations = [];
211✔
367
    const properties = [];
211✔
368
    const lazy = deviceData.lazy || [];
211✔
369
    const commands = deviceData.commands || [];
211✔
370
    const supportMerge = config.getConfig().server.ldSupport.merge;
211✔
371

372
    lazy.forEach((element) => {
211✔
373
        properties.push(element.name);
120✔
374
    });
375
    commands.forEach((element) => {
211✔
376
        properties.push(element.name);
148✔
377
    });
378

379
    if (lazy.length > 0) {
211✔
380
        operations.push('retrieveOps');
119✔
381
    }
382
    if (commands.length > 0) {
211✔
383
        operations.push('updateOps');
110✔
384
    }
385
    if (supportMerge) {
211✔
386
        operations.push('mergeEntity');
1✔
387
    }
388

389
    if (properties.length === 0) {
211✔
390
        logger.debug(context, 'Registration with Context Provider is not needed. Device without lazy atts or commands');
51✔
391
        return callback(null, deviceData);
51✔
392
    }
393

394
    let cbHost = config.getConfig().contextBroker.url;
160✔
395

396
    if (deviceData.cbHost && deviceData.cbHost.indexOf('://') !== -1) {
160✔
397
        cbHost = deviceData.cbHost;
1✔
398
    } else if (deviceData.cbHost && deviceData.cbHost.indexOf('://') === -1) {
159!
399
        cbHost = 'http://' + deviceData.cbHost;
×
400
    }
401

402
    let id = String(deviceData.name);
160✔
403
    if (!id.startsWith(NGSI_LD_URN)) {
160✔
404
        id = NGSI_LD_URN + deviceData.type + ':' + id;
98✔
405
        logger.debug(context, 'Amending id to a valid URN: %s', id);
98✔
406
    }
407

408
    const options = {
160✔
409
        url: cbHost + '/ngsi-ld/v1/csourceRegistrations/',
410
        method: 'POST',
411
        json: {
412
            type: 'ContextSourceRegistration',
413
            information: [
414
                {
415
                    entities: [{ type: deviceData.type, id }],
416
                    propertyNames: properties
417
                }
418
            ],
419
            mode: 'exclusive',
420
            operations,
421
            endpoint: config.getConfig().providerUrl,
422
            contextSourceInfo: [
423
                {
424
                    key: 'jsonldContext',
425
                    value: config.getConfig().contextBroker.jsonLdContext
426
                }
427
            ],
428
            '@context': config.getConfig().contextBroker.jsonLdContext
429
        },
430
        headers: {
431
            'fiware-service': deviceData.service,
432
            'fiware-servicepath': deviceData.subservice,
433
            'NGSILD-Tenant': deviceData.service,
434
            'NGSILD-Path': deviceData.subservice,
435
            'Content-Type': 'application/ld+json'
436
        }
437
    };
438

439
    logger.debug(context, 'Sending LD device registrations to Context Broker at [%s]', options.url);
160✔
440
    logger.debug(context, 'Using the following request:\n\n%s\n\n', JSON.stringify(options, null, 4));
160✔
441

442
    utils.executeWithSecurity(options, deviceData, createRegistrationHandlerNgsiLD(unregister, deviceData, callback));
160✔
443
}
444

445
/**
446
 * Sends a Context Provider registration or unregistration request to the Context Broker. As registrations cannot be
447
 * removed, an unregistration consists of an update of the existent registration to make reduce its duration to
448
 * 1 second.
449
 *
450
 * The entity type, entity name and lazy attributes list are needed in order to send the registration:
451
 *
452
 * @param {Boolen} unregister       Indicates whether this registration is an unregistration or register.
453
 * @param {Object} deviceData       Object containing all the deviceData needed to send the registration.
454
 */
455
function sendRegistrations(unregister, deviceData, callback) {
456
    switch (config.ngsiVersion()) {
576✔
457
        case 'ld':
458
            sendRegistrationsNgsiLD(unregister, deviceData, callback);
218✔
459
            break;
218✔
460
        default:
461
            if (
358✔
462
                (!deviceData.cmdMode && (!config.getConfig().cmdMode || config.getConfig().cmdMode === 'legacy')) ||
730!
463
                (deviceData && deviceData.cmdMode === 'legacy')
464
            ) {
465
                sendRegistrationsNgsi2(unregister, deviceData, callback);
353✔
466
            } else {
467
                sendSubscriptionsNgsi2(unregister, deviceData, callback);
5✔
468
            }
469
            break;
358✔
470
    }
471
}
472

473
/**
474
 * Process the response from a Register Context request for a device, extracting the 'registrationId' and creating the
475
 * device object that will be stored in the registry.
476
 *
477
 * @param {Object} deviceData       Object containing all the deviceData needed to send the registration.
478
 *
479
 */
480
function processContextRegistration(deviceData, body, callback) {
481
    const newDevice = _.clone(deviceData);
549✔
482

483
    if (body) {
549!
484
        if (
549✔
485
            (!deviceData.cmdMode && (!config.getConfig().cmdMode || config.getConfig().cmdMode === 'legacy')) ||
1,111!
486
            (deviceData && deviceData.cmdMode === 'legacy')
487
        ) {
488
            newDevice.registrationId = body.registrationId;
545✔
489
        } else {
490
            newDevice.subscriptionId = body.subscriptionId;
4✔
491
        }
492
    }
493

494
    callback(null, newDevice);
549✔
495
}
496

497
exports.sendRegistrations = intoTrans(context, sendRegistrations);
1✔
498
exports.processContextRegistration = processContextRegistration;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc