/* * ============================================================================ * The Apache Software License, Version 1.1 * ============================================================================ * * Copyright (C) 2002 The Apache Software Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without modifica- * tion, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by SuperBonBon Industries (http://www.sbbi.net/)." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "UPNPLib" and "SuperBonBon Industries" must not be * used to endorse or promote products derived from this software without * prior written permission. For written permission, please contact * info@sbbi.net. * * 5. Products derived from this software may not be called * "SuperBonBon Industries", nor may "SBBI" appear in their name, * without prior written permission of SuperBonBon Industries. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT,INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * on behalf of SuperBonBon Industries. For more information on * SuperBonBon Industries, please see <http://www.sbbi.net/>. */ 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 instances = new HashMap(); private Set handledDevices = new HashSet(); private java.net.MulticastSocket skt; private boolean isRunning = false; private boolean isRunningSSDPDaemon = false; private InetSocketAddress bindAddress; public final static UPNPMBeanDevicesDiscoveryHandler getInstance( InetSocketAddress bindAddress ) { String key = bindAddress.toString(); synchronized( instances ) { UPNPMBeanDevicesDiscoveryHandler handler = (UPNPMBeanDevicesDiscoveryHandler)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 i = handledDevices.iterator(); i.hasNext(); ) { UPNPMBeanDevice registred = (UPNPMBeanDevice)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.size() == 0 ) { 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.size() == 0 && 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 packets = getReplyMessages( dv, true, dv.getSSDPAliveDelay() ); for ( int i = 0; i < packets.size(); i++ ) { String packet = (String)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 packets = getByeByeReplyMessages( dv ); for ( int i = 0; i < packets.size(); i++ ) { String packet = (String)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 getReplyMessages( UPNPMBeanDevice rootDevice, boolean ssdpAliveMsg, int maxAge ) { List rtrVal = new ArrayList(); 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 services = new ArrayList(); services.addAll( rootDevice.getUPNPMBeanServices() ); // 2 messages for each embedded devices for ( Iterator i = rootDevice.getUPNPMBeanChildrens().iterator(); i.hasNext(); ) { UPNPMBeanDevice child = (UPNPMBeanDevice)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 i = services.iterator(); i.hasNext(); ) { UPNPMBeanService srv = (UPNPMBeanService)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 getByeByeReplyMessages( UPNPMBeanDevice rootDevice ) { List rtrVal = new ArrayList(); 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 services = new ArrayList(); services.addAll( rootDevice.getUPNPMBeanServices() ); // 2 messages for each embedded devices for ( Iterator i = rootDevice.getUPNPMBeanChildrens().iterator(); i.hasNext(); ) { UPNPMBeanDevice child = (UPNPMBeanDevice)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 i = services.iterator(); i.hasNext(); ) { UPNPMBeanService srv = (UPNPMBeanService)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" ); //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 i = handledDevices.iterator(); i.hasNext(); ) { UPNPMBeanDevice dv = (UPNPMBeanDevice)i.next(); List packets = getReplyMessages( dv, false, dv.getSSDPAliveDelay() ); for ( int z = 0; z < packets.size(); z++ ) { String pack = (String)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 { } } } } catch ( IOException ex ) { if ( isRunning ) { log.error( "Error during multicast socket IO operations", ex ); } } } } private class SSDPAliveBroadcastMessageSender implements Runnable { private Set devices = new HashSet(); private Map devicesLastBroadCast = new HashMap(); private SSDPAliveBroadcastMessageSender( Set upnpRootDevices ) { this.devices = upnpRootDevices; } public void run() { isRunningSSDPDaemon = true; while ( isRunningSSDPDaemon ) { synchronized( devices ) { for ( Iterator i = devices.iterator(); i.hasNext(); ) { UPNPMBeanDevice dv = (UPNPMBeanDevice)i.next(); String key = dv.getUuid(); long deviceDelay = dv.getSSDPAliveDelay(); Long lastCall = (Long)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 packets = getReplyMessages( dv, true, dv.getSSDPAliveDelay() ); for ( int z = 0; z < packets.size(); z++ ) { String pack = (String)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; } } } } }