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

telefonicaid / iotagent-ul / 6560712423

18 Oct 2023 12:16PM UTC coverage: 79.681% (-0.3%) from 79.947%
6560712423

push

github

web-flow
Merge pull request #647 from telefonicaid/task/remove_placeholder_logs

remove placeholder from logs

355 of 507 branches covered (0.0%)

Branch coverage included in aggregate %.

17 of 17 new or added lines in 4 files covered. (100.0%)

845 of 999 relevant lines covered (84.58%)

69.88 hits per line

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

75.62
/lib/bindings/HTTPBindings.js
1
/*
2
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
3
 *
4
 * This file is part of iotagent-ul
5
 *
6
 * iotagent-ul 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-ul 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-ul.
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::[iot_support@tid.es]
22
 */
23

24
const fs = require('fs');
1✔
25
const http = require('http');
1✔
26
const https = require('https');
1✔
27
const async = require('async');
1✔
28
const apply = async.apply;
1✔
29
const iotAgentLib = require('iotagent-node-lib');
1✔
30
const regenerateTransid = iotAgentLib.regenerateTransid;
1✔
31
const finishSouthBoundTransaction = iotAgentLib.finishSouthBoundTransaction;
1✔
32
const fillService = iotAgentLib.fillService;
1✔
33
const _ = require('underscore');
1✔
34
const intoTrans = iotAgentLib.intoTrans;
1✔
35
const express = require('express');
1✔
36
const utils = require('../iotaUtils');
1✔
37
const bodyParser = require('body-parser');
1✔
38
const constants = require('../constants');
1✔
39
const commonBindings = require('./../commonBindings');
1✔
40
const errors = require('../errors');
1✔
41
const ulParser = require('../ulParser');
1✔
42
let httpBindingServer;
43
const request = iotAgentLib.request;
1✔
44
const config = require('../configService');
1✔
45
let context = {
1✔
46
    op: 'IOTAUL.HTTP.Binding'
47
};
48
const transport = 'HTTP';
1✔
49

50
/* eslint-disable-next-line no-unused-vars */
51
function handleError(error, req, res, next) {
52
    let code = 500;
×
53
    context = fillService(context, { service: 'n/a', subservice: 'n/a' });
×
54
    config.getLogger().debug(context, 'Error %s handing request: %s', error.name, error.message);
×
55

56
    if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
×
57
        code = error.code;
×
58
    }
59

60
    res.status(code).json({
×
61
        name: error.name,
62
        message: error.message
63
    });
64
}
65

66
function parseData(req, res, next) {
67
    let data;
68
    let error;
69
    let payload;
70

71
    if (req.body) {
47✔
72
        payload = req.body;
18✔
73
    } else {
74
        payload = req.query.d;
29✔
75
    }
76
    regenerateTransid(payload);
47✔
77
    config.getLogger().debug(context, 'Parsing payload %s', payload);
47✔
78

79
    try {
47✔
80
        if (payload) {
47✔
81
            data = ulParser.parse(payload);
46✔
82
        }
83
    } catch (e) {
84
        error = e;
×
85
    }
86

87
    if (error) {
47!
88
        next(error);
×
89
    } else {
90
        req.ulPayload = data;
47✔
91

92
        config.getLogger().debug(context, 'Parsed data: %j', data);
47✔
93
        next();
47✔
94
    }
95
}
96

97
function addTimestamp(req, res, next) {
98
    if (req.query.t && req.ulPayload) {
47✔
99
        for (let i = 0; i < req.ulPayload.length; i++) {
3✔
100
            req.ulPayload[i][constants.TIMESTAMP_ATTRIBUTE] = req.query.t;
3✔
101
        }
102
    }
103

104
    next();
47✔
105
}
106

107
function checkMandatoryParams(queryPayload) {
108
    return function (req, res, next) {
232✔
109
        const notFoundParams = [];
47✔
110
        let error;
111

112
        req.apiKey = req.query.k;
47✔
113
        req.deviceId = req.query.i;
47✔
114

115
        if (!req.apiKey) {
47!
116
            notFoundParams.push('API Key');
×
117
        }
118

119
        if (!req.deviceId) {
47!
120
            notFoundParams.push('Device Id');
×
121
        }
122

123
        // CHeck if retrievingParam
124
        if (queryPayload && !req.query.d && req.query.getCmd !== '1') {
47!
125
            notFoundParams.push('Payload');
×
126
        }
127

128
        if (req.method === 'POST' && !req.is('text/plain')) {
47!
129
            error = new errors.UnsupportedType('text/plain');
×
130
        }
131

132
        if (notFoundParams.length !== 0) {
47!
133
            next(new errors.MandatoryParamsNotFound(notFoundParams));
×
134
        } else {
135
            next(error);
47✔
136
        }
137
    };
138
}
139

140
/**
141
 * This middleware checks whether there is any polling command pending to be sent to the device. If there is some,
142
 * add the command information to the return payload. Otherwise it returns an empty payload.
143
 */
144

145
/* eslint-disable-next-line no-unused-vars */
146
function returnCommands(req, res, next) {
147
    function updateCommandStatus(device, commandList) {
148
        context = fillService(context, device);
6✔
149
        function createCommandUpdate(command) {
150
            return apply(
6✔
151
                iotAgentLib.setCommandResult,
152
                device.name,
153
                device.resource,
154
                req.query.k,
155
                command.name,
156
                ' ',
157
                'DELIVERED',
158
                device
159
            );
160
        }
161

162
        function cleanCommand(command) {
163
            return apply(iotAgentLib.removeCommand, device.service, device.subservice, device.id, command.name);
6✔
164
        }
165

166
        const updates = commandList.map(createCommandUpdate);
6✔
167
        const cleanCommands = commandList.map(cleanCommand);
6✔
168

169
        /* eslint-disable-next-line no-unused-vars */
170
        async.parallel(updates.concat(cleanCommands), function (error, results) {
6✔
171
            if (error) {
6!
172
                // prettier-ignore
173
                config.getLogger().error(
×
174
                    context,
175
                    'Error updating command status after delivering commands for device %s',
176
                    device.id
177
                );
178
            } else {
179
                // prettier-ignore
180
                config.getLogger().debug(
6✔
181
                    context,
182
                    'Command status updated successfully after delivering command list to device %s',
183
                    device.id
184
                );
185
            }
186
        });
187
    }
188

189
    function parseCommand(item) {
190
        return ulParser.createCommandPayload(req.device, item.name, item.value);
6✔
191
    }
192

193
    function concatCommand(previous, current) {
194
        if (previous === '') {
6!
195
            return current;
6✔
196
        }
197
        return previous + '|' + current;
×
198
    }
199

200
    if (req.query && req.query.getCmd === '1') {
47✔
201
        iotAgentLib.commandQueue(req.device.service, req.device.subservice, req.deviceId, function (error, list) {
6✔
202
            if (error || !list || list.count === 0) {
6!
203
                res.set('Content-Type', 'text/plain');
×
204
                res.status(200).send('');
×
205
            } else {
206
                res.set('Content-Type', 'text/plain');
6✔
207
                res.status(200).send(list.commands.map(parseCommand).reduce(concatCommand, ''));
6✔
208
                process.nextTick(updateCommandStatus.bind(null, req.device, list.commands));
6✔
209
            }
210
        });
211
    } else {
212
        res.set('Content-Type', 'text/plain');
41✔
213
        res.status(200).send('');
41✔
214
    }
215
}
216

217
function handleIncomingMeasure(req, res, next) {
218
    let updates = [];
47✔
219
    context = fillService(context, { service: 'n/a', subservice: 'n/a' });
47✔
220
    // prettier-ignore
221
    config.getLogger().debug(context, 'Processing multiple HTTP measures for device %s with apiKey %j',
47✔
222
        req.deviceId, req.apiKey);
223

224
    function processHTTPWithDevice(device) {
225
        context = fillService(context, device);
47✔
226
        if (req.ulPayload) {
47✔
227
            updates = req.ulPayload.reduce(commonBindings.processMeasureGroup.bind(null, device, req.apiKey), []);
46✔
228
        }
229

230
        async.series(updates, function (error) {
47✔
231
            if (error) {
47!
232
                next(error);
×
233
                // prettier-ignore
234
                config.getLogger().error(
×
235
                    context,
236
                    "MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
237
                    error
238
                );
239
            } else {
240
                // prettier-ignore
241
                config.getLogger().info(
47✔
242
                    context,
243
                    'Multiple measures for device %s with apiKey %s successfully updated',
244
                    req.deviceId,
245
                    req.apiKey
246
                );
247
                finishSouthBoundTransaction(next);
47✔
248
            }
249
        });
250
    }
251

252
    function processDeviceMeasure(error, device) {
253
        if (error) {
47!
254
            next(error);
×
255
        } else {
256
            const localContext = _.clone(context);
47✔
257

258
            req.device = device;
47✔
259

260
            localContext.service = device.service;
47✔
261
            localContext.subservice = device.subservice;
47✔
262

263
            intoTrans(localContext, processHTTPWithDevice)(device);
47✔
264
        }
265
    }
266

267
    utils.retrieveDevice(req.deviceId, req.apiKey, transport, processDeviceMeasure);
47✔
268
}
269

270
/**
271
 * Generate a function that executes the given command in the device.
272
 *
273
 * @param {String} apiKey           APIKey of the device's service or default APIKey.
274
 * @param {Object} device           Object containing all the information about a device.
275
 * @param {Object} attribute        Attribute in NGSI format.
276
 * @return {Function}               Command execution function ready to be called with async.series.
277
 */
278
function generateCommandExecution(apiKey, device, attribute) {
279
    const cmdName = attribute.name;
12✔
280
    const cmdAttributes = attribute.value;
12✔
281
    const options = {
12✔
282
        url: device.endpoint,
283
        method: 'POST',
284
        body: ulParser.createCommandPayload(device, cmdName, cmdAttributes),
285
        headers: {
286
            'fiware-service': device.service,
287
            'fiware-servicepath': device.subservice,
288
            'content-type': 'text/plain'
289
        },
290
        responseType: 'text'
291
    };
292

293
    if (device.endpoint) {
12!
294
        // device.endpoint or another field like device.endpointExp ?
295
        const parser = iotAgentLib.dataPlugins.expressionTransformation;
12✔
296
        let attrList = iotAgentLib.dataPlugins.utils.getIdTypeServSubServiceFromDevice(device);
12✔
297
        attrList = device.staticAttributes ? attrList.concat(device.staticAttributes) : attrList.concat([]);
12!
298
        const ctxt = parser.extractContext(attrList, device);
12✔
299
        config.getLogger().debug(context, 'attrList %j for device %j', attrList, device);
12✔
300
        // expression result will be the full command payload
301
        let endpointRes = null;
12✔
302
        try {
12✔
303
            endpointRes = parser.applyExpression(device.endpoint, ctxt, device);
12✔
304
        } catch (e) {
305
            // no error should be reported
306
        }
307
        options.url = endpointRes ? endpointRes : device.endpoint;
12!
308
    }
309

310
    if (config.getConfig().http.timeout) {
12!
311
        options.timeout = config.getConfig().http.timeout;
×
312
    }
313

314
    return function sendUlCommandHTTP(callback) {
12✔
315
        let commandObj;
316

317
        request(options, function (error, response, body) {
12✔
318
            if (error) {
12✔
319
                callback(new errors.HTTPCommandResponseError('', error, cmdName));
2✔
320
            } else if (response && response.statusCode !== 200) {
10!
321
                let errorMsg;
322

323
                try {
×
324
                    commandObj = ulParser.result(body);
×
325
                    errorMsg = commandObj.result;
×
326
                } catch (e) {
327
                    errorMsg = body;
×
328
                }
329

330
                callback(new errors.HTTPCommandResponseError(response.statusCode, errorMsg, cmdName));
×
331
            } else if (apiKey) {
10!
332
                commandObj = ulParser.result(body);
10✔
333

334
                process.nextTick(
10✔
335
                    utils.updateCommand.bind(
336
                        null,
337
                        apiKey,
338
                        device,
339
                        commandObj.result,
340
                        commandObj.command,
341
                        constants.COMMAND_STATUS_COMPLETED,
342
                        callback
343
                    )
344
                );
345
            } else {
346
                callback();
×
347
            }
348
        });
349
    };
350
}
351

352
/**
353
 * Handles a command execution request coming from the Context Broker. This handler should:
354
 *  - Identify the device affected by the command.
355
 *  - Send the command to the HTTP endpoint of the device.
356
 *  - Update the command status in the Context Broker while pending.
357
 *  - Update the command status when the result from the device is received.
358
 *
359
 * @param {Object} device           Device data stored in the IOTA.
360
 * @param {String} attributes       Command attributes (in NGSIv1 format).
361
 */
362
function commandHandler(device, attributes, callback) {
363
    utils.getEffectiveApiKey(device.service, device.subservice, device, function (error, apiKey) {
12✔
364
        async.series(attributes.map(generateCommandExecution.bind(null, apiKey, device)), function (error) {
12✔
365
            if (error) {
12✔
366
                // prettier-ignore
367
                config.getLogger().error(context, 
3✔
368
                    'COMMANDS-004: Error handling incoming command for device %s', device.id);
369

370
                utils.updateCommand(
3✔
371
                    apiKey,
372
                    device,
373
                    error.message,
374
                    error.command,
375
                    constants.COMMAND_STATUS_ERROR,
376
                    function (error) {
377
                        if (error) {
3✔
378
                            // prettier-ignore
379
                            config.getLogger().error(
2✔
380
                                context,
381
                                ' COMMANDS-005: Error updating error information for device %s',
382
                                device.id
383
                            );
384
                        }
385
                    }
386
                );
387
            } else {
388
                config.getLogger().debug(context, 'Incoming command for device %s', device.id);
9✔
389
            }
390
        });
391
    });
392

393
    callback();
12✔
394
}
395

396
function addDefaultHeader(req, res, next) {
397
    req.headers['content-type'] = req.headers['content-type'] || 'text/plain';
36✔
398
    next();
36✔
399
}
400

401
/**
402
 * Just fills in the transport protocol in case there is none.
403
 *
404
 * @param {Object} device           Device object containing all the information about the provisioned device.
405
 */
406
function setPollingAndDefaultTransport(device, callback) {
407
    if (!device.transport) {
121✔
408
        device.transport = 'HTTP';
14✔
409
    }
410

411
    if (device.transport === 'HTTP') {
121✔
412
        if (device.endpoint) {
48✔
413
            device.polling = false;
16✔
414
        } else {
415
            device.polling = true;
32✔
416
        }
417
    }
418

419
    callback(null, device);
121✔
420
}
421

422
/**
423
 * Device provisioning handler.
424
 *
425
 * @param {Object} device           Device object containing all the information about the provisioned device.
426
 */
427
function deviceProvisioningHandler(device, callback) {
428
    config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler %j', device);
119✔
429
    setPollingAndDefaultTransport(device, callback);
119✔
430
}
431

432
/**
433
 * Device updating handler. This handler just fills in the transport protocol in case there is none.
434
 *
435
 * @param {Object} device           Device object containing all the information about the updated device.
436
 */
437
function deviceUpdatingHandler(device, callback) {
438
    config.getLogger().debug(context, 'httpbinding.deviceUpdatingHandler %j', device);
2✔
439
    setPollingAndDefaultTransport(device, callback);
2✔
440
}
441

442
function start(callback) {
443
    const baseRoot = '/';
116✔
444

445
    httpBindingServer = {
116✔
446
        server: null,
447
        app: express(),
448
        router: express.Router()
449
    };
450

451
    httpBindingServer.app.set('port', config.getConfig().http.port);
116✔
452
    httpBindingServer.app.set('host', config.getConfig().http.host || '0.0.0.0');
116!
453

454
    httpBindingServer.router.get(
116✔
455
        config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH,
116!
456
        checkMandatoryParams(true),
457
        parseData,
458
        addTimestamp,
459
        handleIncomingMeasure,
460
        returnCommands
461
    );
462

463
    httpBindingServer.router.post(
116✔
464
        config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH,
116!
465
        addDefaultHeader,
466
        bodyParser.text(),
467
        checkMandatoryParams(false),
468
        parseData,
469
        addTimestamp,
470
        handleIncomingMeasure,
471
        returnCommands
472
    );
473

474
    httpBindingServer.app.use(baseRoot, httpBindingServer.router);
116✔
475
    httpBindingServer.app.use(handleError);
116✔
476

477
    if (config.getConfig().http && config.getConfig().http.privateKey && config.getConfig().http.certificate) {
116!
478
        const privateKey = fs.readFileSync(config.getConfig().http.key, 'utf8');
×
479
        const certificate = fs.readFileSync(config.getConfig().http.cert, 'utf8');
×
480
        const credentials = { key: privateKey, cert: certificate };
×
481

482
        config.getLogger().info(context, 'HTTPS Binding listening on port %s', config.getConfig().http.port);
×
483
        httpBindingServer.server = https.createServer(credentials, httpBindingServer.app);
×
484
    } else {
485
        config.getLogger().info(context, 'HTTP Binding listening on port %s', config.getConfig().http.port);
116✔
486
        httpBindingServer.server = http.createServer(httpBindingServer.app);
116✔
487
    }
488
    httpBindingServer.server.listen(httpBindingServer.app.get('port'), httpBindingServer.app.get('host'), callback);
116✔
489
}
490

491
function stop(callback) {
492
    config.getLogger().info(context, 'Stopping Ultralight HTTP Binding: ');
116✔
493

494
    if (httpBindingServer) {
116!
495
        httpBindingServer.server.close(function () {
116✔
496
            config.getLogger().info(context, 'HTTP Binding Stopped');
116✔
497
            callback();
116✔
498
        });
499
    } else {
500
        callback();
×
501
    }
502
}
503

504
function sendPushNotifications(device, values, callback) {
505
    async.series(values.map(generateCommandExecution.bind(null, null, device)), function (error) {
×
506
        callback(error);
×
507
    });
508
}
509

510
function storePollNotifications(device, values, callback) {
511
    function addPollNotification(item, innerCallback) {
512
        iotAgentLib.addCommand(device.service, device.subservice, device.id, item, innerCallback);
×
513
    }
514

515
    async.map(values, addPollNotification, callback);
×
516
}
517

518
function notificationHandler(device, values, callback) {
519
    if (device.endpoint) {
×
520
        sendPushNotifications(device, values, callback);
×
521
    } else {
522
        storePollNotifications(device, values, callback);
×
523
    }
524
}
525

526
exports.start = start;
1✔
527
exports.stop = stop;
1✔
528
exports.deviceProvisioningHandler = deviceProvisioningHandler;
1✔
529
exports.deviceUpdatingHandler = deviceUpdatingHandler;
1✔
530
exports.notificationHandler = notificationHandler;
1✔
531
exports.commandHandler = commandHandler;
1✔
532
exports.protocol = 'HTTP';
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

© 2026 Coveralls, Inc