/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.hue.internal.tools; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helper class that is able to discover services in the network by a SSDP * broadcast. * * @author Roman Hartmann * @since 1.2.0 */ public class SsdpDiscovery { static final Logger logger = LoggerFactory.getLogger(SsdpDiscovery.class); private final static String DISCOVER_MESSAGE = "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: 120\r\n" + "ST: ssdp:all\r\n"; /** * Determines the IP address of a service provided in the network. The * service is detected by one or more keywords that have to appear in the * answer message of the service to the SSDP discover broadcast. The * keywords are not case sensitive. The timeout of the discovery is 120 * seconds. * * @param keywords * The keywords that have to be found case insensitive in the * response of the service to be searched. * @return The IP if a service with the keywords has been found, null * otherwise. * @throws IOException */ public String findIpForResponseKeywords(String... keywords) throws IOException { try { logger.debug("Sending SSDP discover."); MulticastSocket socket = sendDiscoveryBroacast(); logger.debug("Waiting for response."); return scanResposesForKeywords(socket, keywords); } catch (SocketTimeoutException e) { logger.debug("Timeout of request..."); } return null; } /** * Broadcasts a SSDP discovery message into the network to find provided * services. * * @return The Socket the answers will arrive at. * @throws UnknownHostException * @throws IOException * @throws SocketException * @throws UnsupportedEncodingException */ private MulticastSocket sendDiscoveryBroacast() throws UnknownHostException, IOException, SocketException, UnsupportedEncodingException { InetAddress multicastAddress = InetAddress.getByName("239.255.255.250"); final int port = 1900; MulticastSocket socket = new MulticastSocket(port); socket.setReuseAddress(true); socket.setSoTimeout(130000); socket.joinGroup(multicastAddress); byte[] requestMessage = DISCOVER_MESSAGE.getBytes("UTF-8"); DatagramPacket datagramPacket = new DatagramPacket(requestMessage, requestMessage.length, multicastAddress, port); socket.send(datagramPacket); return socket; } /** * Scans all messages that arrive on the socket and scans them for the * search keywords. The search is not case sensitive. * * @param socket * The socket where the answers arrive. * @param keywords * The keywords to be searched for. * @return * @throws IOException */ private String scanResposesForKeywords(MulticastSocket socket, String... keywords) throws IOException { // In the worst case a SocketTimeoutException raises socket.setSoTimeout(2000); do { logger.debug("Got an answer message."); byte[] rxbuf = new byte[8192]; DatagramPacket packet = new DatagramPacket(rxbuf, rxbuf.length); socket.receive(packet); String foundIp = analyzePacket(packet, keywords); if (foundIp != null) { return foundIp; } } while (true); } /** * Checks whether a packet does contain all given keywords case insensitive. * If all keywords are contained the IP address of the packet sender will be * returned. * * @param packet * The data packet to be analyzed. * @param keywords * The keywords to be searched for. * @return The IP of the sender if all keywords have been found, null * otherwise. * * @throws IOException */ private String analyzePacket(DatagramPacket packet, String... keywords) throws IOException { logger.debug("Analyzing answer message."); InetAddress addr = packet.getAddress(); ByteArrayInputStream in = new ByteArrayInputStream(packet.getData(), 0, packet.getLength()); String response = IOUtils.toString(in); boolean foundAllKeywords = true; for (String keyword : keywords) { foundAllKeywords &= response.toUpperCase().contains(keyword.toUpperCase()); } if (foundAllKeywords) { logger.debug("Found matching answer."); return addr.getHostAddress(); } logger.debug("Answer did not match."); return null; } }