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

telefonicaid / iotagent-json / 18720939518

22 Oct 2025 03:13PM UTC coverage: 79.55%. First build
18720939518

Pull #897

github

web-flow
Merge 0d87dfe17 into 699b09110
Pull Request #897: fix for cmdMode = notification

529 of 756 branches covered (69.97%)

Branch coverage included in aggregate %.

2 of 3 new or added lines in 1 file covered. (66.67%)

1132 of 1332 relevant lines covered (84.98%)

134.89 hits per line

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

75.0
/lib/bindings/HTTPBinding.js
1
/*
2
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
3
 *
4
 * This file is part of iotagent-json
5
 *
6
 * iotagent-json 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-json 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-json.
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
/* eslint-disable no-unused-vars */
25

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

50
const config = require('../configService');
1✔
51
let httpBindingServer;
52
const transport = 'HTTP';
1✔
53

54
const { promisify } = require('util');
1✔
55
const json = promisify(bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit })); // accept anything JSON.parse accepts.
1✔
56
const text = promisify(bodyParser.text({ limit: config.getConfig().iota.expressLimit }));
1✔
57
const raw = promisify(bodyParser.raw({ limit: config.getConfig().iota.expressLimit }));
1✔
58
const xml2js = require('xml2js');
1✔
59
const xmlStripPrefix = xml2js.processors.stripPrefix;
1✔
60
const xml = promisify(
1✔
61
    bodyParser.xml({
62
        xmlParseOptions: {
63
            // XML namespaces might change from one request to the next.
64
            // It is useful to remove them from the document,
65
            // to be able to refer to tags later in JEXL transformations.
66
            // See https://github.com/Leonidas-from-XIV/node-xml2js/issues/87
67
            tagNameProcessors: [xmlStripPrefix],
68
            attrNameProcessors: [xmlStripPrefix]
69
        }
70
    })
71
);
72

73
function parserBody() {
74
    // generic bodyParser
75
    return function (req, res, next) {
262✔
76
        if (req.is('text/plain')) {
8✔
77
            text(req, res).then(() => next(), next);
2✔
78
        } else if (req.is('application/octet-stream')) {
6✔
79
            raw(req, res).then(() => next(), next);
2✔
80
        } else if (req.is('application/soap+xml')) {
4✔
81
            xml(req, res).then(() => next(), next);
2✔
82
        } else {
83
            // req.is('json')
84
            json(req, res).then(() => next(), next);
2✔
85
        }
86
    };
87
}
88

89
function checkMandatoryParams(queryPayload) {
90
    return function (req, res, next) {
1,310✔
91
        const notFoundParams = [];
156✔
92
        let error;
93

94
        req.apiKey = req.query.k;
156✔
95
        req.deviceId = req.query.i;
156✔
96
        req.attr = req.params ? req.params.attrValue : undefined;
156!
97

98
        if (!req.apiKey) {
156!
99
            notFoundParams.push('API Key');
×
100
        }
101

102
        if (!req.deviceId) {
156!
103
            notFoundParams.push('Device Id');
×
104
        }
105

106
        // Check if retrievingParam
107
        if (queryPayload && !req.query.d && req.query.getCmd !== '1') {
156!
108
            notFoundParams.push('Payload');
×
109
        }
110
        if (
156!
111
            req.method === 'POST' &&
323✔
112
            !req.is('json') &&
113
            !req.is('text/plain') &&
114
            !req.is('application/octet-stream') &&
115
            !req.is('application/soap+xml')
116
        ) {
117
            error = new errors.UnsupportedType(
×
118
                'application/json, text/plain, application/octet-stream, application/soap+xml'
119
            );
120
        }
121

122
        if (notFoundParams.length !== 0) {
156!
123
            next(new errors.MandatoryParamsNotFound(notFoundParams));
×
124
        } else {
125
            next(error);
156✔
126
        }
127
    };
128
}
129

130
function parseData(req, res, next) {
131
    let data;
132
    let error;
133
    let payload;
134
    context['from'] = req.ip || req.connection.remoteAddress;
13!
135
    if (req.body) {
13✔
136
        config.getLogger().debug(context, 'Using body %s', req.body);
12✔
137
        data = req.body;
12✔
138
        regenerateTransid(data);
12✔
139
    } else {
140
        payload = req.query.d;
1✔
141
        regenerateTransid(payload);
1✔
142
        config.getLogger().debug(context, 'Parsing payload %s', payload);
1✔
143

144
        try {
1✔
145
            if (payload) {
1!
146
                data = JSON.parse(payload);
×
147
            }
148
        } catch (e) {
149
            error = e;
×
150
        }
151
    }
152

153
    if (error) {
13!
154
        next(error);
×
155
    } else {
156
        req.jsonPayload = data;
13✔
157
        if (req.body !== undefined) {
13✔
158
            try {
12✔
159
                // This is just for log data
160
                data = data.toString('hex');
12✔
161
            } catch (e) {
162
                // no error should be reported
163
            }
164
        }
165
        config.getLogger().debug(context, 'Parsed data: %j', data);
13✔
166
        next();
13✔
167
    }
168
}
169

170
function parseDataMultipleMeasure(req, res, next) {
171
    let data;
172
    let error;
173
    let payload;
174
    let dataArray;
175
    dataArray = [];
143✔
176
    context['from'] = req.ip || req.connection.remoteAddress;
143!
177
    if (req.body) {
143!
178
        config.getLogger().debug(context, 'Using body %s', req.body);
143✔
179
        if (!Array.isArray(req.body)) {
143✔
180
            dataArray.push(req.body);
127✔
181
        } else {
182
            dataArray = req.body;
16✔
183
        }
184
        regenerateTransid(dataArray);
143✔
185
    } else {
186
        payload = req.query.d;
×
187
        regenerateTransid(payload);
×
188
        config.getLogger().debug(context, 'Parsing payload %s', payload);
×
189
        try {
×
190
            if (payload) {
×
191
                data = JSON.parse(payload);
×
192
                dataArray.push(data);
×
193
            }
194
        } catch (e) {
195
            error = e;
×
196
        }
197
    }
198
    if (error) {
143!
199
        next(error);
×
200
    } else {
201
        req.jsonPayload = dataArray;
143✔
202
        config.getLogger().debug(context, 'Parsed data array: %j', dataArray);
143✔
203
        next();
143✔
204
    }
205
}
206

207
function executeCommand(apiKey, group, device, cmdName, serializedPayload, extraHeaders, contentType, callback) {
208
    const options = {
12✔
209
        url: device.endpoint || (group && group.endpoint ? group.endpoint : undefined),
24!
210
        method: 'POST',
211
        body: serializedPayload,
212
        headers: {
213
            'fiware-service': device.service,
214
            'fiware-servicepath': device.subservice,
215
            'content-type': contentType
216
        }
217
    };
218
    if (options.url || extraHeaders) {
12!
219
        // endpoint and extraHeaders could be an expression
220
        const parser = iotAgentLib.dataPlugins.expressionTransformation;
12✔
221
        let attrList = iotAgentLib.dataPlugins.utils.getIdTypeServSubServiceFromDevice(device);
12✔
222
        attrList = device.staticAttributes ? attrList.concat(device.staticAttributes) : attrList.concat([]);
12!
223
        const ctxt = parser.extractContext(attrList, device);
12✔
224
        config.getLogger().debug(context, 'attrList %j for device %j', attrList, device);
12✔
225
        if (options.url) {
12!
226
            let endpointRes = null;
12✔
227
            try {
12✔
228
                endpointRes = parser.applyExpression(options.url, ctxt, device);
12✔
229
            } catch (e) {
230
                // no error should be reported
231
            }
232
            options.url = endpointRes ? endpointRes : options.url;
12✔
233
        }
234
        if (extraHeaders) {
12✔
235
            if (
4!
236
                typeof extraHeaders !== 'object' ||
16✔
237
                extraHeaders === null ||
238
                Array.isArray(extraHeaders) ||
239
                Object.getPrototypeOf(extraHeaders) !== Object.prototype
240
            ) {
241
                config
×
242
                    .getLogger()
243
                    .info(
244
                        context,
245
                        'extraHeaders %j in device %j command %j  not plain object',
246
                        extraHeaders,
247
                        device,
248
                        cmdName
249
                    );
250
            } else {
251
                for (const key in extraHeaders) {
4✔
252
                    if (Object.prototype.hasOwnProperty.call(extraHeaders, key)) {
4!
253
                        const headerValue = extraHeaders[key];
4✔
254
                        let headerRes = null;
4✔
255
                        try {
4✔
256
                            headerRes = parser.applyExpression(headerValue, ctxt, device);
4✔
257
                        } catch (e) {
258
                            // no error should be reported
259
                        }
260
                        options.headers[key] = headerRes;
4✔
261
                    }
262
                } // for
263
            }
264
        }
265
    }
266
    if (config.getConfig().http.timeout) {
12!
267
        options.timeout = config.getConfig().http.timeout;
×
268
    }
269
    config
12✔
270
        .getLogger()
271
        .debug(
272
            context,
273
            'Sending command %s with payload %s and with http options %j',
274
            cmdName,
275
            serializedPayload,
276
            options
277
        );
278
    request(options, function (error, response, body) {
12✔
279
        if (error || !response || (response.statusCode !== 200 && response.statusCode !== 201)) {
12!
280
            callback(new errors.HTTPCommandResponseError(response ? response.statusCode : 400, error));
×
281
        } else if (apiKey) {
12!
282
            config
12✔
283
                .getLogger()
284
                .info(
285
                    context,
286
                    'Cmd: %j was sent to the device %s with http options %j',
287
                    serializedPayload,
288
                    cmdName,
289
                    options
290
                );
291
            process.nextTick(commandHandler.updateCommand.bind(null, apiKey, device.id, device, body));
12✔
292
            callback();
12✔
293
        } else {
294
            config
×
295
                .getLogger()
296
                .info(
297
                    context,
298
                    'Cmd: %j was sent to the device %s with http options %j',
299
                    serializedPayload,
300
                    cmdName,
301
                    options
302
                );
303
            callback();
×
304
        }
305
    });
306
}
307

308
function addTimestamp(req, res, next) {
309
    const arr = req.jsonPayload;
153✔
310
    let timeStampData;
311
    for (const i in arr) {
153✔
312
        if (req.query.t && arr[i]) {
382✔
313
            timeStampData = arr[i];
4✔
314
            timeStampData[constants.TIMESTAMP_ATTRIBUTE] = req.query.t;
4✔
315
        }
316
    }
317
    next();
153✔
318
}
319

320
function handleIncomingMeasure(req, res, next) {
321
    let values;
322
    context = fillService(context, { service: 'n/a', subservice: 'n/a' });
153✔
323
    config
153✔
324
        .getLogger()
325
        .debug(context, 'Processing multiple HTTP measures for device %s with apiKey %s', req.deviceId, req.apiKey);
326

327
    function updateCommandHandler(error) {
328
        if (error) {
152!
329
            next(error);
×
330
            config.getLogger().error(
×
331
                context,
332
                /*jshint quotmark: double */
333
                "MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
334
                /*jshint quotmark: single */
335
                error
336
            );
337
        } else {
338
            config
152✔
339
                .getLogger()
340
                .info(
341
                    context,
342
                    'Multiple measures for device %s with apiKey %s successfully updated',
343
                    req.deviceId,
344
                    req.apiKey
345
                );
346

347
            finishSouthBoundTransaction(next);
152✔
348
        }
349
    }
350

351
    function processHTTPWithDevice(device) {
352
        let payloadDataArr;
353
        let attributeArr;
354
        let attributeValues;
355
        let originalMeasure = {};
153✔
356
        attributeArr = [];
153✔
357
        payloadDataArr = [];
153✔
358

359
        if (req.attr && req.jsonPayload) {
153✔
360
            config.getLogger().debug(context, 'Parsing attr %s with value %s', req.attr, req.jsonPayload);
8✔
361
            try {
8✔
362
                req.jsonPayload = req.jsonPayload.toString('hex');
8✔
363
            } catch (e) {
364
                // no error should be reported
365
            }
366
            const theAttr = [{ name: req.attr, value: req.jsonPayload, type: 'None' }];
8✔
367
            attributeArr.push(theAttr);
8✔
368
            originalMeasure[req.attr] = req.jsonPayload;
8✔
369
        } else {
370
            if (!Array.isArray(req.jsonPayload)) {
145✔
371
                payloadDataArr.push(req.jsonPayload);
2✔
372
            } else {
373
                payloadDataArr = req.jsonPayload;
143✔
374
            }
375
            originalMeasure = payloadDataArr;
145✔
376
            if (req.jsonPayload) {
145✔
377
                config.getLogger().debug(context, 'Parsing payloadDataArr %j for device %j', payloadDataArr, device);
144✔
378
                for (const i in payloadDataArr) {
144✔
379
                    values = commonBindings.extractAttributes(device, payloadDataArr[i], device.payloadType);
178✔
380
                    if (values && values[0] && values[0][0]) {
178✔
381
                        // Check multimeasure from a ngsiv2/ngsild entities array
382
                        attributeArr = attributeArr.concat(values);
2✔
383
                    } else {
384
                        attributeArr.push(values);
176✔
385
                    }
386
                }
387
            } else {
388
                attributeArr = [];
1✔
389
            }
390
        }
391
        if (attributeArr.length === 0) {
153✔
392
            finishSouthBoundTransaction(next);
1✔
393
        } else {
394
            config
152✔
395
                .getLogger()
396
                .debug(context, 'Processing measure device %s with attributeArr %j', device.name, attributeArr);
397
            if (req.isCommand) {
152✔
398
                const executions = [];
1✔
399
                for (const j in attributeArr) {
1✔
400
                    attributeValues = attributeArr[j];
1✔
401
                    for (const k in attributeValues) {
1✔
402
                        executions.push(
1✔
403
                            iotAgentLib.setCommandResult.bind(
404
                                null,
405
                                device.name,
406
                                config.getConfig().iota.defaultResource,
407
                                req.apiKey,
408
                                attributeValues[k].name,
409
                                attributeValues[k].value,
410
                                constants.COMMAND_STATUS_COMPLETED,
411
                                device
412
                            )
413
                        );
414
                    }
415
                }
416
                async.parallel(executions, updateCommandHandler);
1✔
417
            } else {
418
                device.originalMeasure = originalMeasure;
151✔
419
                iotAgentLib.update(device.name, device.type, '', attributeArr, device, updateCommandHandler);
151✔
420
            }
421
        }
422
    }
423

424
    function processDeviceMeasure(error, device) {
425
        if (error) {
153!
426
            next(error);
×
427
        } else {
428
            const localContext = _.clone(context);
153✔
429
            req.device = device;
153✔
430
            localContext.service = device.service;
153✔
431
            localContext.subservice = device.subservice;
153✔
432
            intoTrans(localContext, processHTTPWithDevice)(device);
153✔
433
        }
434
    }
435

436
    iotaUtils.retrieveDevice(req.deviceId, req.apiKey, processDeviceMeasure);
153✔
437
}
438

439
function isCommand(req, res, next) {
440
    if (
1!
441
        req.path ===
442
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + constants.HTTP_COMMANDS_PATH
1!
443
    ) {
444
        req.isCommand = true;
1✔
445
    }
446

447
    next();
1✔
448
}
449

450
function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) {
451
    function handleDeviceResponse(innerCallback) {
452
        return function (error, response, body) {
3✔
453
            if (error) {
3!
454
                innerCallback(error);
×
455
            } else if (response && response.statusCode !== 200) {
3!
456
                innerCallback(new errors.DeviceEndpointError(response.statusCode, body));
×
457
            } else {
458
                innerCallback();
3✔
459
            }
460
        };
461
    }
462

463
    function sendRequest(device, group, results, innerCallback) {
464
        const resultRequest = {
3✔
465
            url:
466
                (device.endpoint || (group && group.endpoint ? group.endpoint : undefined)) +
3!
467
                constants.HTTP_CONFIGURATION_PATH,
468

469
            method: 'POST',
470
            json: iotaUtils.createConfigurationNotification(results),
471
            headers: {
472
                'fiware-service': device.service,
473
                'fiware-servicepath': device.subservice,
474
                'content-type': 'application/json'
475
            }
476
        };
477

478
        request(resultRequest, handleDeviceResponse(innerCallback));
3✔
479
    }
480
    iotaUtils.retrieveDevice(deviceId, apiKey, function (error, device) {
3✔
481
        if (error) {
3!
482
            callback(error);
×
483
        } else if (!device.endpoint) {
3!
484
            callback(new errors.EndpointNotFound(device.id));
×
485
        } else {
486
            sendRequest(device, group, results, callback);
3✔
487
        }
488
    });
489
}
490

491
function handleConfigurationRequest(req, res, next) {
492
    function replyToDevice(error) {
493
        res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
3✔
494
        if (error) {
3!
495
            res.status(error.code).json(error);
×
496
        } else {
497
            res.status(200).json({});
3✔
498
        }
499
    }
500
    iotaUtils.retrieveDevice(req.deviceId, req.apiKey, function (error, device) {
3✔
501
        if (error) {
3!
502
            next(error);
×
503
        } else {
504
            iotaUtils.manageConfiguration(
3✔
505
                req.apiKey,
506
                req.deviceId,
507
                device,
508
                req.jsonPayload,
509
                sendConfigurationToDevice,
510
                replyToDevice
511
            );
512
        }
513
    });
514
}
515

516
function reqTiming(req, res, next) {
517
    req.startTime = Date.now();
156✔
518
    next();
156✔
519
}
520

521
function handleError(error, req, res, next) {
522
    let code = 500;
×
523

524
    config.getLogger().debug(context, 'Error %s handling request: %s', error.name, error.message);
×
525

526
    if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
×
527
        code = error.code;
×
528
    }
529
    if (error.status && String(error.status).match(/^[2345]\d\d$/)) {
×
530
        code = error.status;
×
531
    }
532

533
    res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
×
534
    res.status(code).json({
×
535
        name: error.name,
536
        message: error.message
537
    });
538
}
539

540
/**
541
 * Just fills in the transport protocol in case there is none and polling if endpoint.
542
 *
543
 * @param {Object} device           Device object containing all the information about the device.
544
 * @param {Object} group            Group object containing all the information about the device.
545
 */
546
function setPollingAndDefaultTransport(device, group, callback) {
547
    config.getLogger().debug(context, 'httpbinding.setPollingAndDefaultTransport device %j group %j', device, group);
163✔
548
    if (!device.transport) {
163✔
549
        device.transport = group && group.transport ? group.transport : config.getConfig().defaultTransport;
89!
550
    }
551

552
    if (device.transport === 'HTTP') {
163✔
553
        if (device.endpoint) {
111✔
554
            device.polling = false;
75✔
555
        } else {
556
            device.polling = !(group && group.endpoint);
36✔
557
        }
558
    }
559
    callback(null, device);
163✔
560
}
561

562
/**
563
 * Device provisioning handler.
564
 *
565
 * @param {Object} device           Device object containing all the information about the provisioned device.
566
 */
567
function deviceProvisioningHandler(device, callback) {
568
    config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler device %j', device);
159✔
569
    let group = {};
159✔
570
    iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', device.apikey, function (
159!
571
        error,
572
        foundGroup
573
    ) {
574
        if (!error) {
159!
575
            group = foundGroup;
×
576
        }
577
        config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler group %j', group);
159✔
578
        setPollingAndDefaultTransport(device, group, callback);
159✔
579
    });
580
}
581

582
/**
583
 * Device updating handler. This handler just fills in the transport protocol in case there is none.
584
 *
585
 * @param {Object} device           Device object containing all the information about the updated device.
586
 */
587
function deviceUpdatingHandler(newDevice, oldDevice, callback) {
588
    config
4✔
589
        .getLogger()
590
        .debug(context, 'httpbinding.deviceUpdatingHandler newDevice %j oldDevice %j', newDevice, oldDevice);
591
    let group = {};
4✔
592
    iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', oldDevice.apikey, function (
4!
593
        error,
594
        foundGroup
595
    ) {
596
        if (!error) {
4!
597
            group = foundGroup;
×
598
        }
599
        config.getLogger().debug(context, 'httpbinding.deviceUpdatingHandler group %j', group);
4✔
600
        setPollingAndDefaultTransport(newDevice, group, callback);
4✔
601
    });
602
}
603

604
/**
605
 * This middleware checks whether there is any polling command pending to be sent to the device. If there is some,
606
 * add the command information to the return payload. Otherwise it returns an empty payload.
607
 */
608
function returnCommands(req, res, next) {
609
    function updateCommandStatus(device, commandList) {
610
        context = fillService(context, device);
6✔
611
        function createCommandUpdate(command) {
612
            return apply(
6✔
613
                iotAgentLib.setCommandResult,
614
                device.name,
615
                device.resource,
616
                req.query.k,
617
                command.name,
618
                ' ',
619
                'DELIVERED',
620
                device
621
            );
622
        }
623

624
        function cleanCommand(command) {
625
            return apply(iotAgentLib.removeCommand, device.service, device.subservice, device.id, command.name);
6✔
626
        }
627

628
        const updates = commandList.map(createCommandUpdate);
6✔
629
        const cleanCommands = commandList.map(cleanCommand);
6✔
630
        if (updates) {
6!
631
            async.parallel(updates.concat(cleanCommands), function (error, results) {
6✔
632
                if (error) {
6!
633
                    config
×
634
                        .getLogger()
635
                        .error(
636
                            context,
637
                            'Error updating command status after delivering commands for device %s',
638
                            device.id
639
                        );
640
                } else {
641
                    config
6✔
642
                        .getLogger()
643
                        .debug(
644
                            context,
645
                            'Command status updated successfully after delivering command list to device %s',
646
                            device.id
647
                        );
648
                }
649
            });
650
        }
651
    }
652

653
    function parseCommand(item) {
654
        const result = {};
6✔
655
        const cleanedValue = String(item.value).trim();
6✔
656

657
        if (cleanedValue !== '') {
6!
658
            result[item.name] = item.value;
6✔
659
        }
660
        return result;
6✔
661
    }
662

663
    function concatCommand(previous, current) {
664
        if (previous === {}) {
6!
665
            return current;
×
666
        }
667
        return _.extend(previous, current);
6✔
668
    }
669
    if (req.query && req.query.getCmd === '1') {
153✔
670
        iotAgentLib.commandQueue(req.device.service, req.device.subservice, req.deviceId, function (error, list) {
6✔
671
            res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
6✔
672
            if (error || !list || list.count === 0) {
6!
673
                if (req.accepts('json')) {
×
674
                    res.status(200).send({});
×
675
                } else {
676
                    res.status(200).send('');
×
677
                }
678
            } else {
679
                if (req.accepts('json')) {
6!
680
                    res.status(200).send(list.commands.map(parseCommand).reduce(concatCommand, {}));
6✔
681
                } else {
682
                    res.status(200).send(JSON.stringify(list.commands.map(parseCommand).reduce(concatCommand, {})));
×
683
                }
684
                process.nextTick(updateCommandStatus.bind(null, req.device, list.commands));
6✔
685
            }
686
        });
687
    } else if (req.accepts('json')) {
147!
688
        res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
147✔
689
        res.status(200).send({});
147✔
690
    } else {
691
        res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
×
692
        res.status(200).send('');
×
693
    }
694
}
695

696
function start(callback) {
697
    const baseRoot = '/';
262✔
698

699
    httpBindingServer = {
262✔
700
        server: null,
701
        app: express(),
702
        router: express.Router()
703
    };
704

705
    if (!config.getConfig().http) {
262!
706
        config
×
707
            .getLogger()
708
            .fatal(context, 'GLOBAL-002: Configuration error. Configuration object [config.http] is missing');
709
        callback(new errors.ConfigurationError('config.http'));
×
710
        return;
×
711
    }
712

713
    httpBindingServer.app.set('port', config.getConfig().http.port);
262✔
714
    httpBindingServer.app.set('host', config.getConfig().http.host || '0.0.0.0');
262!
715
    httpBindingServer.app.use(reqTiming);
262✔
716
    httpBindingServer.app.set('trust proxy', true); // populate req.ip
262✔
717
    httpBindingServer.router.get(
262✔
718
        config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH,
262!
719
        checkMandatoryParams(true),
720
        parseData,
721
        addTimestamp,
722
        handleIncomingMeasure,
723
        returnCommands
724
    );
725

726
    httpBindingServer.router.post(
262✔
727
        config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH,
262!
728
        bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit }), // accept anything JSON.parse accepts
729
        checkMandatoryParams(false),
730
        parseDataMultipleMeasure,
731
        addTimestamp,
732
        handleIncomingMeasure,
733
        returnCommands
734
    );
735

736
    httpBindingServer.router.post(
262✔
737
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) +
262!
738
            '/' +
739
            constants.MEASURES_SUFIX +
740
            '/:attrValue',
741
        parserBody(),
742
        checkMandatoryParams(false),
743
        parseData, // non multiple measures are expected in this route
744
        addTimestamp,
745
        handleIncomingMeasure,
746
        returnCommands
747
    );
748

749
    httpBindingServer.router.post(
262✔
750
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + constants.HTTP_COMMANDS_PATH,
262!
751
        bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit }), // accept anything JSON.parse accepts.
752
        checkMandatoryParams(false),
753
        parseData,
754
        addTimestamp,
755
        isCommand,
756
        handleIncomingMeasure,
757
        returnCommands
758
    );
759

760
    httpBindingServer.router.post(
262✔
761
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + constants.HTTP_CONFIGURATION_PATH,
262!
762
        bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit }), // accept anything JSON.parse accepts.
763
        checkMandatoryParams(false),
764
        parseData,
765
        handleConfigurationRequest
766
    );
767

768
    httpBindingServer.app.use(baseRoot, httpBindingServer.router);
262✔
769
    httpBindingServer.app.use(handleError);
262✔
770

771
    if (config.getConfig().http && config.getConfig().http.key && config.getConfig().http.cert) {
262!
772
        const privateKey = fs.readFileSync(config.getConfig().http.key, 'utf8');
×
773
        const certificate = fs.readFileSync(config.getConfig().http.cert, 'utf8');
×
774
        const credentials = { key: privateKey, cert: certificate };
×
775

776
        config.getLogger().info(context, 'HTTPS Binding listening on port %s', config.getConfig().http.port);
×
777
        httpBindingServer.server = https.createServer(credentials, httpBindingServer.app);
×
778
    } else {
779
        config.getLogger().info(context, 'HTTP Binding listening on port %s', config.getConfig().http.port);
262✔
780
        httpBindingServer.server = http.createServer(httpBindingServer.app);
262✔
781
    }
782

783
    httpBindingServer.server.listen(httpBindingServer.app.get('port'), httpBindingServer.app.get('host'), callback);
262✔
784
}
785

786
function stop(callback) {
787
    config.getLogger().info(context, 'Stopping JSON HTTP Binding: ');
262✔
788

789
    if (httpBindingServer) {
262!
790
        httpBindingServer.server.close(function () {
262✔
791
            config.getLogger().info(context, 'HTTP Binding Stopped');
262✔
792
            callback();
262✔
793
        });
794
    } else {
795
        callback();
×
796
    }
797
}
798

799
function sendPushNotifications(device, group, values, callback) {
800
    const executions = _.flatten(
1✔
801
        values.map(commandHandler.generateCommandExecution.bind(null, group.apikey, device, group))
802
    );
803

804
    async.series(executions, function (error) {
1✔
805
        callback(error);
1✔
806
    });
807
}
808

809
function storePollNotifications(device, group, values, callback) {
810
    function addPollNotification(item, innerCallback) {
811
        iotAgentLib.addCommand(device.service, device.subservice, device.id, item, innerCallback);
×
812
    }
813
    async.map(values, addPollNotification, callback);
×
814
}
815

816
function notificationHandler(device, values, callback) {
817
    config.getLogger().debug(context, 'values for command %j and device %j', values, device);
1✔
818

819
    function invokeWithConfiguration(apiKey, callback) {
820
        let group = {};
1✔
821
        iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function (
1!
822
            error,
823
            foundGroup
824
        ) {
825
            if (!error) {
1!
826
                group = foundGroup;
1✔
827
            }
828

829
            var cmdValue = { type: 'command' };
1✔
830
            for (let val of values) {
1✔
831
                // Notified by a cmdMode = notification
832
                if (val.type === 'command') {
13!
NEW
833
                    cmdValue = val;
×
834
                } else if (val.name === 'cmd') {
13✔
835
                    // Notified by a cmdMode = advancedNotification
836
                    cmdValue.name = val.value;
1✔
837
                } else if (val.name === 'params') {
12✔
838
                    cmdValue.value = val.value;
1✔
839
                } else {
840
                    // other fields like status, info, onDelivered, OnError
841
                    cmdValue[val.name] = val.value;
11✔
842
                }
843
            }
844
            var cmdValues = [cmdValue];
1✔
845
            config.getLogger().debug(context, 'cmdValues %j', cmdValues);
1✔
846
            iotAgentLib.executeUpdateSideEffects(
1✔
847
                device,
848
                device.id,
849
                device.type,
850
                device.service,
851
                device.subservice,
852
                cmdValues,
853
                function () {
854
                    if (device.endpoint || group.endpoint) {
1!
855
                        sendPushNotifications(device, group, cmdValues, callback);
1✔
856
                    } else {
857
                        storePollNotifications(device, group, cmdValues, callback);
×
858
                    }
859
                }
860
            );
861
        });
862
    }
863

864
    async.waterfall(
1✔
865
        [apply(iotaUtils.getEffectiveApiKey, device.service, device.subservice, device), invokeWithConfiguration],
866
        callback
867
    );
868
}
869

870
exports.start = start;
1✔
871
exports.stop = stop;
1✔
872
exports.sendConfigurationToDevice = sendConfigurationToDevice;
1✔
873
exports.deviceProvisioningHandler = deviceProvisioningHandler;
1✔
874
exports.deviceUpdatingHandler = deviceUpdatingHandler;
1✔
875
exports.notificationHandler = notificationHandler;
1✔
876
exports.executeCommand = executeCommand;
1✔
877
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