UNPKG

domotz-remote-pawn

Version:

Domotz Agent

168 lines (147 loc) 5.88 kB
/** 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, }; }