domotz-remote-pawn
Version:
Domotz Agent
168 lines (147 loc) • 5.88 kB
JavaScript
/** This file is part of Domotz Agent.
* Copyright (C) 2021 Domotz Ltd
*
* Domotz Agent is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Domotz Agent is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Domotz Agent. If not, see <http://www.gnu.org/licenses/>.
**/
const MDNS_OPTIONS = {
host: '224.0.0.251', // N.B. Equivalent to ip
port: 5353,
type: 'udp4',
interface: null, // Just for udp6
reuseAddr: true, // If another MDNS is on the system
socket: null, // If you want create your own socket
multicast: true,
ttl: 255,
loopback: true, // Set Multicast Loopback
};
function ServiceHandler(resourceLocator) {
var ignoredServices = resourceLocator.lodash.get(resourceLocator, 'configuration.settings.discovery_blacklist.bonjour.ignored_services', []);
var ignoredPaths = resourceLocator.lodash.get(resourceLocator, 'configuration.settings.discovery_blacklist.bonjour.ignored_paths', []);
var piggybackDiscovery = resourceLocator.lodash.get(resourceLocator, 'configuration.settings.discovery.bonjour.piggyback_discovery', false);
var bonjourServicesByMac = {};
var myConsole = resourceLocator.log.decorateLogs();
var ipConflict = resourceLocator.networkTools.ipConflictDetection;
function registerService(serviceData) {
var serviceFilter = resourceLocator.discovery.serviceFilter;
var service = parse(serviceData);
if (service === null) {
myConsole.warn('Cannot parse Bonjour service: ' + JSON.stringify(serviceData));
return;
}
if (ipConflict.checkIfIpInConflict(service.ip)) {
myConsole.warn('Ignore bonjour data for conflicting IP: %s', service.ip);
return;
}
var deviceBinding = resourceLocator.interfacesBindingStorage.indexedByIpFresh()[service.ip];
if (!deviceBinding) {
myConsole.warn('Service declaration for IP %s arrived before device discovery or when device is OFFLINE', service.ip);
return;
}
var deviceMac = deviceBinding.mac;
if (serviceFilter.isServiceIgnored(ignoredServices, service)) {
myConsole.debug('Ignoring service %s for device %s (%s)', service.service, deviceMac, service.ip);
return;
}
var deviceData = bonjourServicesByMac[deviceMac];
if (!deviceData) {
deviceData = { $: { mac: deviceMac, ip: service.ip } };
}
deviceData[service.service] = service;
serviceFilter.ignorePaths(ignoredPaths, deviceData);
serviceFilter.cleanupObject(deviceData);
if (!deviceData) {
return;
}
if (Object.keys(deviceData).length === 1) {
// we must count the $ key
myConsole.debug('No valid service retrieved for device %s', deviceMac);
return;
}
// assures keys are sorted alphabetically
deviceData = resourceLocator.lodash(deviceData).toPairs().sortBy(0).fromPairs().value();
myConsole.info('Stored services for device %s: %s', deviceMac, JSON.stringify(Object.keys(deviceData)));
if (piggybackDiscovery) {
resourceLocator.discovery.accumulator.store(deviceMac, 'bonjour', deviceData);
}
bonjourServicesByMac[deviceMac] = deviceData;
}
return {
registerService: registerService,
reset: function () {
bonjourServicesByMac = {};
},
_services: function () {
return bonjourServicesByMac;
},
};
}
function discovererFactory(resourceLocator) {
var bonjour = resourceLocator.bonjour(MDNS_OPTIONS);
var myConsole = resourceLocator.log.decorateLogs();
var browser = null;
var handler = module.exports._ServiceHandler(resourceLocator, myConsole);
return {
startOrRefresh: function () {
if (!browser) {
myConsole.info('Started service browser');
browser = bonjour.find({}, handler.registerService);
browser.start();
} else {
myConsole.debug('Repeating multicast request');
browser.update();
}
},
reset: function () {
myConsole.info('Resetting devices data');
handler.reset();
},
getServicesByMac: function () {
return handler._services();
},
};
}
module.exports = {
factory: discovererFactory,
_ServiceHandler: ServiceHandler, // for testing
_parse: parse, // for testing
};
function extractIpAddress(serviceData) {
var referer = serviceData.referer || {};
if (referer.family === 'IPv4') {
return referer.address;
}
serviceData.addresses = serviceData.addresses || [];
for (var i = 0; i < serviceData.addresses.length; i++) {
var address = serviceData.addresses[i];
if (/^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$/.test(address)) {
return address;
}
}
return null;
}
function parse(serviceData) {
var ipAddress = extractIpAddress(serviceData);
if (ipAddress === null) {
return null;
}
return {
ip: ipAddress,
name: serviceData.name,
txt: serviceData.txt,
host: serviceData.host,
fqdn: serviceData.fqdn,
service: serviceData.type,
port: serviceData.port,
};
}