org.eclipse.smarthome.binding.mqtt.internal.discovery.NetworkDiscoveryService.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.smarthome.binding.mqtt.internal.discovery.NetworkDiscoveryService.java

Source

/**
 * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package org.eclipse.smarthome.binding.mqtt.internal.discovery;

import java.net.Inet4Address;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.net.util.SubnetUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.binding.mqtt.MqttBindingConstants;
import org.eclipse.smarthome.binding.mqtt.internal.MqttThingID;
import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService;
import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder;
import org.eclipse.smarthome.config.discovery.DiscoveryService;
import org.eclipse.smarthome.core.net.NetUtil;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection;
import org.eclipse.smarthome.io.transport.mqtt.MqttException;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The {@link NetworkDiscoveryService} is responsible for discovering brokers on
 * the current Network. It uses every Network Interface available.
 *
 * @author David Graeff - Initial contribution
 */
@Component(immediate = true, service = DiscoveryService.class, configurationPid = "discovery.networkmqttbroker")
@NonNullByDefault
public class NetworkDiscoveryService extends AbstractDiscoveryService {
    private static final int MAX_IPS_PER_INTERFACE = 255;
    private static final int TIMEOUT_IN_MS = 700;

    private final Logger logger = LoggerFactory.getLogger(NetworkDiscoveryService.class);

    private int scannedIPcount = 0;

    public NetworkDiscoveryService() {
        super(Collections.singleton(MqttBindingConstants.BRIDGE_TYPE_BROKER), 0);
    }

    @Override
    @Activate
    protected void activate(@Nullable Map<String, @Nullable Object> config) {
        super.activate(config);
    };

    @Override
    @Deactivate
    protected void deactivate() {
        super.deactivate();
    }

    /**
     * Explicitly override getScanTimeout() instead of using the constructor.
     * This allows to mock the timeout for tests.
     */
    @Override
    public int getScanTimeout() {
        return TIMEOUT_IN_MS;
    }

    /**
     * Creates all required {@link MqttBrokerConnection} for performing connection attempts
     * on the given URLs.
     *
     * Package internal, for testing.
     *
     * @param networkIPs The network IP addresses.
     * @return Returns an array of broker connections with the names "test".
     * @throws ConfigurationException If the URL is invalid, this exception is thrown
     */
    protected MqttBrokerConnection[] createTestConnections(List<String> networkIPs) throws ConfigurationException {

        MqttBrokerConnection o[] = new MqttBrokerConnection[networkIPs.size() * 2];
        for (int i = 0; i < networkIPs.size(); ++i) {
            o[i * 2] = new MqttBrokerConnection("ssl://" + networkIPs.get(i), 8883, false, "testssl");
            o[i * 2].setTimeoutExecutor(scheduler, 100);
            o[i * 2 + 1] = new MqttBrokerConnection("tcp://" + networkIPs.get(i), 1883, false, "test");
            o[i * 2 + 1].setTimeoutExecutor(scheduler, 100);
        }
        return o;
    }

    /**
     * Scans the target host/IP by trying to connect as an Mqtt client. Calls
     * newDevice() if a broker is found. Increments scannedIPcount by one. If
     * the total subnet size is equal scannedIPcount, stopScan() will be called,
     * to signal the scan has finished.
     *
     * @param testConnection The destination to test
     * @throws ConfigurationException If the IP cannot be used by the {@link MqttBrokerConnection}, this exception will
     *             be thrown.
     * @throws MqttException Throws if the connection to the target is not a valid Mqtt connection.
     * @throws InterruptedException Throws if interrupted while waiting for the Mqtt connection to establish.
     */
    protected void scanTarget(MqttBrokerConnection testConnection) {
        testConnection.start().thenAccept(v -> {
            if (v == false) {
                return;
            }
            logger.trace("Found service device at {}:{}", testConnection.getHost(),
                    String.valueOf(testConnection.getPort()));

            Map<String, Object> properties = new HashMap<>();
            properties.put("host", testConnection.getHost());
            properties.put("port", testConnection.getPort());

            ThingUID thingUID = MqttThingID.getThingUID(testConnection.getHost(), testConnection.getPort());
            thingDiscovered(DiscoveryResultBuilder.create(thingUID).withTTL(120).withProperties(properties)
                    .withRepresentationProperty("host").withLabel("MQTT Broker").build());
            testConnection.stop();
        });
    }

    /**
     * Increase a counter and calls stopScan() if counter equals total.
     *
     * @param total The total size
     */
    synchronized protected void stopScanIfAllScanned(int total) {
        scannedIPcount += 1;
        if (scannedIPcount == total) {
            logger.trace("Scan of {} IPs successful", scannedIPcount);
            stopScan();
        }
    }

    /**
     * Return a list of IP v4 addresses to be scanned for the discovery
     */
    protected List<String> getScannableIPs() {
        return NetUtil.getAllInterfaceAddresses().stream().filter(i -> i.getAddress() instanceof Inet4Address)
                .map(interfaceIP -> Arrays.asList(
                        new SubnetUtils(interfaceIP.getAddress().getHostAddress()).getInfo().getAllAddresses())
                        .subList(0, MAX_IPS_PER_INTERFACE))
                .flatMap(List::stream).collect(Collectors.toList());
    }

    /**
     * Starts the DiscoveryThread for each IP on each interface on the network
     */
    @Override
    protected void startScan() {
        removeOlderResults(getTimestampOfLastScan(), null);
        logger.trace("Starting Discovery");

        List<String> networkIPs = getScannableIPs();

        // We perform two scans per IP (plain/secure)
        scannedIPcount = 0;

        // Check localhost first
        networkIPs.add(0, "127.0.0.1");
        final int totalScans = networkIPs.size() * 2;

        // Create necessary objects to test
        MqttBrokerConnection o[];
        try {
            o = createTestConnections(networkIPs);
        } catch (ConfigurationException e) {
            logger.debug("Could not create MqttBrokerConnection object", e);
            stopScan();
            return;
        }

        for (MqttBrokerConnection c : o) {
            scanTarget(c);
            stopScanIfAllScanned(totalScans);
        }
    }
}