/****************************************************************** * * CyberUPnP for Java * * Copyright (C) Satoshi Konno 2002-2004 * * File: ControlPoint.java * * Revision: * * 11/18/02 * - first revision. * 05/13/03 * - Changed to create socket threads each local interfaces. * (HTTP, SSDPNotiry, SSDPSerachResponse) * 05/28/03 * - Changed to send m-serach packets from SSDPSearchResponseSocket. * The socket doesn't bind interface address. * - SSDPSearchResponsSocketList that binds a port and a interface can't * send m-serch packets of IPv6 on J2SE v 1.4.1_02 and Redhat 9. * 07/23/03 * - Suzan Foster (suislief) * - Fixed a bug. HOST field was missing. * 07/29/03 * - Synchronized when a device is added by the ssdp message. * 09/08/03 * - Giordano Sassaroli * - Problem : when an event notification message is received and the message * contains updates on more than one variable, only the first variable update * is notified. * - Error : the other xml nodes of the message are ignored * - Fix : add two methods to the NotifyRequest for extracting the property array * and modify the httpRequestRecieved method in ControlPoint * 12/12/03 * - Added a static() to initialize UPnP class. * 01/06/04 * - Added the following methods to remove expired devices automatically * removeExpiredDevices() * setExpiredDeviceMonitoringInterval()/getExpiredDeviceMonitoringInterval() * setDeviceDisposer()/getDeviceDisposer() * 04/20/04 * - Added the following methods. * start(String target, int mx) and start(String target). * 06/23/04 * - Added setNMPRMode() and isNMPRMode(). * 07/08/04 * - Added renewSubscriberService(). * - Changed start() to create renew subscriber thread when the NMPR mode is true. * 08/17/04 * - Fixed removeExpiredDevices() to remove using the device array. * 10/16/04 * - Oliver Newell * - Added this class to allow ControlPoint applications to be notified when * the ControlPoint base class adds/removes a UPnP device * 03/30/05 * - Changed addDevice() to use Parser::parse(URL). * 04/12/06 * - Added setUserData() and getUserData() to set a user original data object. * *******************************************************************/ package org.cybergarage.upnp; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.util.Locale; import org.cybergarage.http.HTTPRequest; import org.cybergarage.http.HTTPRequestListener; import org.cybergarage.http.HTTPServerList; import org.cybergarage.net.HostInterface; import org.cybergarage.upnp.control.RenewSubscriber; import org.cybergarage.upnp.device.DeviceChangeListener; import org.cybergarage.upnp.device.Disposer; import org.cybergarage.upnp.device.NotifyListener; import org.cybergarage.upnp.device.ST; import org.cybergarage.upnp.device.SearchResponseListener; import org.cybergarage.upnp.device.USN; import org.cybergarage.upnp.event.EventListener; import org.cybergarage.upnp.event.NotifyRequest; import org.cybergarage.upnp.event.Property; import org.cybergarage.upnp.event.PropertyList; import org.cybergarage.upnp.event.Subscription; import org.cybergarage.upnp.event.SubscriptionRequest; import org.cybergarage.upnp.event.SubscriptionResponse; import org.cybergarage.upnp.ssdp.SSDP; import org.cybergarage.upnp.ssdp.SSDPNotifySocketList; import org.cybergarage.upnp.ssdp.SSDPPacket; import org.cybergarage.upnp.ssdp.SSDPSearchRequest; import org.cybergarage.upnp.ssdp.SSDPSearchResponseSocketList; import org.cybergarage.util.Debug; import org.cybergarage.util.ListenerList; import org.cybergarage.util.Mutex; import org.cybergarage.xml.Node; import org.cybergarage.xml.NodeList; import org.cybergarage.xml.Parser; import org.cybergarage.xml.ParserException; import net.i2p.util.Addresses; import net.i2p.router.transport.TransportUtil; public class ControlPoint implements HTTPRequestListener { private final static int DEFAULT_EVENTSUB_PORT = 8058; private final static int DEFAULT_SSDP_PORT = 8008; private final static int DEFAULT_EXPIRED_DEVICE_MONITORING_INTERVAL = 60; private final static String DEFAULT_EVENTSUB_URI = "/evetSub"; // I2P private static final boolean ALLOW_IPV6_LOCATION = true; //////////////////////////////////////////////// // Member //////////////////////////////////////////////// private SSDPNotifySocketList ssdpNotifySocketList; private SSDPSearchResponseSocketList ssdpSearchResponseSocketList; /** I2P was private */ protected SSDPNotifySocketList getSSDPNotifySocketList() { return ssdpNotifySocketList; } /** I2P was private */ protected SSDPSearchResponseSocketList getSSDPSearchResponseSocketList() { return ssdpSearchResponseSocketList; } //////////////////////////////////////////////// // Initialize //////////////////////////////////////////////// static { UPnP.initialize(); } //////////////////////////////////////////////// // Constructor //////////////////////////////////////////////// public ControlPoint(int ssdpPort, int httpPort,InetAddress[] binds){ ssdpNotifySocketList = new SSDPNotifySocketList(binds); ssdpSearchResponseSocketList = new SSDPSearchResponseSocketList(binds); setSSDPPort(ssdpPort); setHTTPPort(httpPort); setDeviceDisposer(null); setExpiredDeviceMonitoringInterval(DEFAULT_EXPIRED_DEVICE_MONITORING_INTERVAL); setRenewSubscriber(null); setNMPRMode(false); setRenewSubscriber(null); } public ControlPoint(int ssdpPort, int httpPort){ this(ssdpPort,httpPort,null); } public ControlPoint() { this(DEFAULT_SSDP_PORT, DEFAULT_EVENTSUB_PORT); } public void finalize() { stop(); } //////////////////////////////////////////////// // Mutex //////////////////////////////////////////////// private Mutex mutex = new Mutex(); public void lock() { mutex.lock(); } public void unlock() { mutex.unlock(); } //////////////////////////////////////////////// // Port (SSDP) //////////////////////////////////////////////// private int ssdpPort = 0; public int getSSDPPort() { return ssdpPort; } public void setSSDPPort(int port) { ssdpPort = port; } //////////////////////////////////////////////// // Port (EventSub) //////////////////////////////////////////////// private int httpPort = 0; public int getHTTPPort() { return httpPort; } public void setHTTPPort(int port) { httpPort = port; } //////////////////////////////////////////////// // NMPR //////////////////////////////////////////////// private boolean nmprMode; public void setNMPRMode(boolean flag) { nmprMode = flag; } public boolean isNMPRMode() { return nmprMode; } //////////////////////////////////////////////// // Device List //////////////////////////////////////////////// private NodeList devNodeList = new NodeList(); private void addDevice(Node rootNode) { devNodeList.add(rootNode); } private synchronized void addDevice(SSDPPacket ssdpPacket) { if (ssdpPacket.isRootDevice() == false) return; String usn = ssdpPacket.getUSN(); String location = ssdpPacket.getLocation(); try { URL locationUrl = new URL(location); // I2P // Roku fake json port, the real UPnP port is 8060 if (locationUrl.getPort() == 9080) { String lcusn = usn.toLowerCase(Locale.US); if (lcusn.contains("rku") || lcusn.contains("roku")) { Debug.warning("Ignoring Roku at " + location); return; } } // I2P // We duplicate all the checks in Parser.parse() because they // are bypassed for a known device. // Devices may send two SSDP responses, one with an IPv4 location // and one with an IPv6 location. // Do these check BEFORE we call dev.setSSDPPacket() so we don't // overwrite the SSDPPacket in DeviceData. // TODO handle multiple locations in DeviceData. String host = locationUrl.getHost(); if (host == null) { Debug.warning("Ignoring device with bad URL at " + location); return; } if (host.startsWith("127.")) { Debug.warning("Ignoring localhost device at " + location); return; } if (host.startsWith("[") && host.endsWith("]")) { if (!ALLOW_IPV6_LOCATION) { Debug.warning("Ignoring IPv6 device at " + location); return; } // fixup for valid checks below host = host.substring(1, host.length() - 1); } if (!"http".equals(locationUrl.getProtocol())) { Debug.warning("Ignoring non-http device at " + location); return; } if (!Addresses.isIPv4Address(host) && (!ALLOW_IPV6_LOCATION || !Addresses.isIPv6Address(host))) { Debug.warning("Ignoring non-IPv4 address at " + location); return; } byte[] ip = Addresses.getIP(host); if (ip == null) { Debug.warning("Ignoring bad IP at " + location); return; } if (TransportUtil.isPubliclyRoutable(ip, ALLOW_IPV6_LOCATION)) { Debug.warning("Ignoring public address at " + location); return; } String udn = USN.getUDN(usn); Device dev = getDevice(udn); if (dev != null) { Debug.message("Additional SSDP for " + udn + " at " + location); dev.setSSDPPacket(ssdpPacket); return; } Parser parser = UPnP.getXMLParser(); Node rootNode = parser.parse(locationUrl); Device rootDev = getDevice(rootNode); if (rootDev == null) return; rootDev.setSSDPPacket(ssdpPacket); Debug.warning("Add root device at " + location, new Exception("received on " + ssdpPacket.getLocalAddress())); addDevice(rootNode); // Thanks for Oliver Newell (2004/10/16) // After node is added, invoke the AddDeviceListener to notify high-level // control point application that a new device has been added. (The // control point application must implement the DeviceChangeListener interface // to receive the notifications) performAddDeviceListener( rootDev ); } catch (MalformedURLException me) { Debug.warning("Bad location: " + location, me); } catch (ParserException pe) { Debug.warning("Error parsing data at location: " + location, pe); } } private Device getDevice(Node rootNode) { if (rootNode == null) return null; Node devNode = rootNode.getNode(Device.ELEM_NAME); if (devNode == null) return null; return new Device(rootNode, devNode); } public DeviceList getDeviceList() { DeviceList devList = new DeviceList(); int nRoots = devNodeList.size(); for (int n=0; n (09/08/03) if (httpReq.isNotifyRequest() == true) { NotifyRequest notifyReq = new NotifyRequest(httpReq); String uuid = notifyReq.getSID(); long seq = notifyReq.getSEQ(); PropertyList props = notifyReq.getPropertyList(); int propCnt = props.size(); for (int n = 0; n < propCnt; n++) { Property prop = props.getProperty(n); String varName = prop.getName(); String varValue = prop.getValue(); performEventListener(uuid, seq, varName, varValue); } httpReq.returnOK(); return; } httpReq.returnBadRequest(); } //////////////////////////////////////////////// // Event Listener //////////////////////////////////////////////// private ListenerList eventListenerList = new ListenerList(); public void addEventListener(EventListener listener) { eventListenerList.add(listener); } public void removeEventListener(EventListener listener) { eventListenerList.remove(listener); } public void performEventListener(String uuid, long seq, String name, String value) { int listenerSize = eventListenerList.size(); for (int n=0; n