/* * This software copyright by various authors including the RPTools.net * development team, and licensed under the LGPL Version 3 or, at your * option, any later version. * * Portions of this software were originally covered under the Apache * Software License, Version 1.1 or Version 2.0. * * See the file LICENSE elsewhere in this distribution for license details. */ package net.sbbi.upnp.jmx; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import net.sbbi.upnp.Discovery; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Class to handle UPNP discovery mechanism on UPNPMBeanDevice * * @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a> * @version 1.0 */ public class UPNPMBeanDevicesDiscoveryHandler implements Runnable { private final static Log log = LogFactory.getLog(UPNPMBeanDevicesDiscoveryHandler.class); private final static Map<String, UPNPMBeanDevicesDiscoveryHandler> instances = new HashMap<String, UPNPMBeanDevicesDiscoveryHandler>(); private final Set<UPNPMBeanDevice> handledDevices = new HashSet<UPNPMBeanDevice>(); private java.net.MulticastSocket skt; private boolean isRunning = false; private boolean isRunningSSDPDaemon = false; private final InetSocketAddress bindAddress; public final static UPNPMBeanDevicesDiscoveryHandler getInstance(InetSocketAddress bindAddress) { String key = bindAddress.toString(); synchronized (instances) { UPNPMBeanDevicesDiscoveryHandler handler = instances.get(key); if (handler == null) { handler = new UPNPMBeanDevicesDiscoveryHandler(bindAddress); instances.put(key, handler); } return handler; } } private UPNPMBeanDevicesDiscoveryHandler(InetSocketAddress bindAddress) { this.bindAddress = bindAddress; } protected void addUPNPMBeanDevice(UPNPMBeanDevice rootDevice) throws IOException { if (!rootDevice.isRootDevice()) return; synchronized (handledDevices) { for (Iterator<UPNPMBeanDevice> i = handledDevices.iterator(); i.hasNext();) { UPNPMBeanDevice registred = i.next(); if (registred.getDeviceType().equals(rootDevice.getDeviceType()) && registred.getUuid().equals(rootDevice.getUuid())) { // API Use error throw new RuntimeException("An UPNPMBeanDevice object of type " + rootDevice.getDeviceType() + " with uuid " + rootDevice.getUuid() + " is already registred within this class, use a different UPNPMBeanDevice internalId"); } } if (handledDevices.isEmpty()) { Thread runner = new Thread(this, "UPNPMBeanDeviceDiscoveryHandler " + bindAddress.toString()); runner.setDaemon(true); runner.start(); SSDPAliveBroadcastMessageSender sender = new SSDPAliveBroadcastMessageSender(handledDevices); Thread runner2 = new Thread(sender, "SSDPAliveBroadcastMessageSender " + bindAddress.toString()); runner2.setDaemon(true); runner2.start(); } sendHello(rootDevice); handledDevices.add(rootDevice); } } protected void removeUPNPMBeanDevice(UPNPMBeanDevice rootDevice) throws IOException { if (!rootDevice.isRootDevice()) return; synchronized (handledDevices) { if (handledDevices.contains(rootDevice)) { handledDevices.remove(rootDevice); sendByeBye(rootDevice); if (handledDevices.isEmpty() && isRunning) { isRunning = false; isRunningSSDPDaemon = false; skt.close(); } } } } private void sendHello(UPNPMBeanDevice dv) throws IOException { InetAddress group = InetAddress.getByName("239.255.255.250"); java.net.MulticastSocket multi = new java.net.MulticastSocket(bindAddress.getPort()); multi.setInterface(bindAddress.getAddress()); multi.setTimeToLive(dv.getSSDPTTL()); List<String> packets = getReplyMessages(dv, true, dv.getSSDPAliveDelay()); for (int i = 0; i < packets.size(); i++) { String packet = packets.get(i); if (log.isDebugEnabled()) log.debug("Sending ssdp alive message on 239.255.255.250:1900 multicast address:\n" + packet.toString()); byte[] pk = packet.getBytes(); multi.send(new DatagramPacket(pk, pk.length, group, 1900)); } multi.close(); } private void sendByeBye(UPNPMBeanDevice dv) throws IOException { InetAddress group = InetAddress.getByName("239.255.255.250"); java.net.MulticastSocket multi = new java.net.MulticastSocket(bindAddress.getPort()); multi.setInterface(bindAddress.getAddress()); multi.setTimeToLive(dv.getSSDPTTL()); List<String> packets = getByeByeReplyMessages(dv); for (int i = 0; i < packets.size(); i++) { String packet = packets.get(i); if (log.isDebugEnabled()) log.debug("Sending ssdp:byebye message on 239.255.255.250:1900 multicast address:\n" + packet.toString()); byte[] pk = packet.getBytes(); multi.send(new DatagramPacket(pk, pk.length, group, 1900)); } multi.close(); } private List<String> getReplyMessages(UPNPMBeanDevice rootDevice, boolean ssdpAliveMsg, int maxAge) { // TODO handle custom NT and ST // TODO create a thread to dispatch ssdp:alive messages List<String> rtrVal = new ArrayList<String>(); StringBuffer basePacket = new StringBuffer(); StringBuffer packet = null; if (ssdpAliveMsg) { basePacket.append("NOTIFY * HTTP/1.1\r\n"); basePacket.append("HOST: 239.255.255.250:1900\r\n"); } else { basePacket.append("HTTP/1.1 200 OK\r\n"); } basePacket.append("CACHE-CONTROL: max-age = ").append(maxAge).append("\r\n"); basePacket.append("LOCATION: ").append(rootDevice.getLocation()).append("\r\n"); basePacket.append("SERVER: ").append(UPNPMBeanDevice.IMPL_NAME).append("\r\n"); // 3 messages for the root device packet = new StringBuffer(basePacket.toString()); if (ssdpAliveMsg) { packet.append("NT: uuid:").append(rootDevice.getUuid()).append("\r\n"); packet.append("NTS: ssdp:alive\r\n"); } else { packet.append("ST: uuid:").append(rootDevice.getUuid()).append("\r\n"); packet.append("EXT:\r\n"); } packet.append("USN: uuid:").append(rootDevice.getUuid()).append("\r\n\r\n"); rtrVal.add(packet.toString()); packet = new StringBuffer(basePacket.toString()); if (ssdpAliveMsg) { packet.append("NT: ").append(rootDevice.getDeviceType()).append("\r\n"); packet.append("NTS: ssdp:alive\r\n"); } else { packet.append("ST: ").append(rootDevice.getDeviceType()).append("\r\n"); packet.append("EXT:\r\n"); } packet.append("USN: uuid:").append(rootDevice.getUuid()).append("::").append(rootDevice.getDeviceType()).append("\r\n\r\n"); rtrVal.add(packet.toString()); packet = new StringBuffer(basePacket.toString()); if (ssdpAliveMsg) { packet.append("NT: upnp:rootdevice\r\n"); packet.append("NTS: ssdp:alive\r\n"); } else { packet.append("ST: upnp:rootdevice\r\n"); packet.append("EXT:\r\n"); } packet.append("USN: uuid:").append(rootDevice.getUuid()).append("::upnp:rootdevice\r\n\r\n"); rtrVal.add(packet.toString()); packet = new StringBuffer(basePacket.toString()); List<UPNPMBeanService> services = new ArrayList<UPNPMBeanService>(); services.addAll(rootDevice.getUPNPMBeanServices()); // 2 messages for each embedded devices for (Iterator<UPNPMBeanDevice> i = rootDevice.getUPNPMBeanChildrens().iterator(); i.hasNext();) { UPNPMBeanDevice child = i.next(); services.addAll(child.getUPNPMBeanServices()); packet = new StringBuffer(basePacket.toString()); if (ssdpAliveMsg) { packet.append("NT: uuid:").append(child.getUuid()).append("\r\n"); packet.append("NTS: ssdp:alive\r\n"); } else { packet.append("ST: uuid:").append(child.getUuid()).append("\r\n"); packet.append("EXT:\r\n"); } packet.append("USN: uuid:").append(child.getUuid()).append("\r\n\r\n"); rtrVal.add(packet.toString()); packet = new StringBuffer(basePacket.toString()); if (ssdpAliveMsg) { packet.append("NT: ").append(child.getDeviceType()).append("\r\n"); packet.append("NTS: ssdp:alive\r\n"); } else { packet.append("ST: ").append(child.getDeviceType()).append("\r\n"); packet.append("EXT:\r\n"); } packet.append("USN: uuid:").append(child.getUuid()).append("::").append(child.getDeviceType()).append("\r\n\r\n"); rtrVal.add(packet.toString()); } for (Iterator<UPNPMBeanService> i = services.iterator(); i.hasNext();) { UPNPMBeanService srv = i.next(); // 1 message for each service embedded service if (ssdpAliveMsg) { packet.append("NT: ").append(srv.getServiceType()).append("\r\n"); packet.append("NTS: ssdp:alive\r\n"); } else { packet.append("ST: ").append(srv.getServiceType()).append("\r\n"); packet.append("EXT:\r\n"); } packet.append("USN: uuid:").append(srv.getDeviceUUID()).append("::").append(srv.getServiceType()).append("\r\n\r\n"); rtrVal.add(packet.toString()); } return rtrVal; } private List<String> getByeByeReplyMessages(UPNPMBeanDevice rootDevice) { List<String> rtrVal = new ArrayList<String>(); StringBuffer basePacket = new StringBuffer(); StringBuffer packet = null; basePacket.append("NOTIFY * HTTP/1.1\r\n"); basePacket.append("HOST: 239.255.255.250:1900\r\n"); // 3 messages for the root device packet = new StringBuffer(basePacket.toString()); packet.append("NT: uuid:").append(rootDevice.getUuid()).append("\r\n"); packet.append("NTS: ssdp:byebye\r\n"); packet.append("USN: uuid:").append(rootDevice.getUuid()).append("\r\n\r\n"); rtrVal.add(packet.toString()); packet = new StringBuffer(basePacket.toString()); packet.append("NT: ").append(rootDevice.getDeviceType()).append("\r\n"); packet.append("NTS: ssdp:byebye\r\n"); packet.append("USN: uuid:").append(rootDevice.getUuid()).append("::").append(rootDevice.getDeviceType()).append("\r\n\r\n"); rtrVal.add(packet.toString()); packet = new StringBuffer(basePacket.toString()); packet.append("NT: upnp:rootdevice\r\n"); packet.append("NTS: ssdp:byebye\r\n"); packet.append("USN: uuid:").append(rootDevice.getUuid()).append("::upnp:rootdevice\r\n\r\n"); rtrVal.add(packet.toString()); List<UPNPMBeanService> services = new ArrayList<UPNPMBeanService>(); services.addAll(rootDevice.getUPNPMBeanServices()); // 2 messages for each embedded devices for (Iterator<UPNPMBeanDevice> i = rootDevice.getUPNPMBeanChildrens().iterator(); i.hasNext();) { UPNPMBeanDevice child = i.next(); services.addAll(child.getUPNPMBeanServices()); packet = new StringBuffer(basePacket.toString()); packet.append("NT: uuid:").append(child.getUuid()).append("\r\n"); packet.append("NTS: ssdp:byebye\r\n"); packet.append("USN: uuid:").append(child.getUuid()).append("\r\n\r\n"); rtrVal.add(packet.toString()); packet = new StringBuffer(basePacket.toString()); packet.append("NT: ").append(child.getDeviceType()).append("\r\n"); packet.append("NTS: ssdp:byebye\r\n"); packet.append("USN: uuid:").append(child.getUuid()).append("::").append(child.getDeviceType()).append("\r\n\r\n"); rtrVal.add(packet.toString()); } // 1 messages for each service for (Iterator<UPNPMBeanService> i = services.iterator(); i.hasNext();) { UPNPMBeanService srv = i.next(); packet = new StringBuffer(basePacket.toString()); packet.append("NT: urn:").append(srv.getServiceType()).append("\r\n"); packet.append("NTS: ssdp:byebye\r\n"); packet.append("USN: uuid:").append(srv.getDeviceUUID()).append("::").append(srv.getServiceType()).append("\r\n\r\n"); rtrVal.add(packet.toString()); } return rtrVal; } public void run() { InetAddress group = null; try { group = InetAddress.getByName("239.255.255.250"); skt = new java.net.MulticastSocket(1900); skt.setInterface(bindAddress.getAddress()); skt.joinGroup(group); } catch (IOException ex) { log.error("Error during multicast socket creation, thread cannot start", ex); return; } isRunning = true; while (isRunning) { try { byte[] buffer = new byte[4096]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length, group, bindAddress.getPort()); skt.receive(packet); String received = new String(packet.getData(), 0, packet.getLength()); if (log.isDebugEnabled()) log.debug("Received message:\n" + received); HttpRequest req = new HttpRequest(received); if (req.getHttpCommand().equals("M-SEARCH")) { String man = req.getHTTPHeaderField("MAN"); if (man.equals("\"ssdp:discover\"")) { String searchTarget = req.getHTTPHeaderField("ST"); // TODO check ALL devices search target //if ( searchTarget.equals( Discovery.ALL_DEVICES ) ) { if (searchTarget.equals(Discovery.ROOT_DEVICES)) { java.net.MulticastSocket multi = new java.net.MulticastSocket(); multi.setInterface(bindAddress.getAddress()); for (Iterator<UPNPMBeanDevice> i = handledDevices.iterator(); i.hasNext();) { UPNPMBeanDevice dv = i.next(); List<String> packets = getReplyMessages(dv, false, dv.getSSDPAliveDelay()); for (int z = 0; z < packets.size(); z++) { String pack = packets.get(z); if (log.isDebugEnabled()) log.debug("Sending http reply message on " + packet.getAddress() + ":" + packet.getPort() + " multicast address:\n" + pack.toString()); byte[] pk = pack.getBytes(); multi.setTimeToLive(dv.getSSDPTTL()); multi.send(new DatagramPacket(pk, pk.length, packet.getAddress(), packet.getPort())); } } multi.close(); } else { // TODO check a specific search target } } } } catch (IOException ex) { if (isRunning) { log.error("Error during multicast socket IO operations", ex); } } } } private class SSDPAliveBroadcastMessageSender implements Runnable { private Set<UPNPMBeanDevice> devices = new HashSet<UPNPMBeanDevice>(); private final Map<String, Long> devicesLastBroadCast = new HashMap<String, Long>(); private SSDPAliveBroadcastMessageSender(Set<UPNPMBeanDevice> upnpRootDevices) { this.devices = upnpRootDevices; } public void run() { isRunningSSDPDaemon = true; while (isRunningSSDPDaemon) { synchronized (devices) { for (Iterator<UPNPMBeanDevice> i = devices.iterator(); i.hasNext();) { UPNPMBeanDevice dv = i.next(); String key = dv.getUuid(); long deviceDelay = dv.getSSDPAliveDelay(); Long lastCall = devicesLastBroadCast.get(key); if (lastCall == null) { lastCall = new Long(System.currentTimeMillis() + (deviceDelay * 60) + 1000); devicesLastBroadCast.put(key, lastCall); } if (lastCall.longValue() + (deviceDelay * 60) < System.currentTimeMillis()) { try { InetAddress group = InetAddress.getByName("239.255.255.250"); java.net.MulticastSocket multi = new java.net.MulticastSocket(bindAddress.getPort()); multi.setInterface(bindAddress.getAddress()); multi.setTimeToLive(dv.getSSDPTTL()); multi.joinGroup(group); List<String> packets = getReplyMessages(dv, true, dv.getSSDPAliveDelay()); for (int z = 0; z < packets.size(); z++) { String pack = packets.get(z); if (log.isDebugEnabled()) log.debug("Sending http message on " + group.getAddress() + ":1900 multicast address:\n" + pack.toString()); byte[] pk = pack.getBytes(); multi.send(new DatagramPacket(pk, pk.length, group, 1900)); } multi.leaveGroup(group); multi.close(); devicesLastBroadCast.put(key, new Long(System.currentTimeMillis())); } catch (IOException ex) { log.error("Error occured during SSDP alive broadcast message sending", ex); } } } } try { Thread.sleep(1000); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); return; } } } } }