/****************************************************************** * * 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 <sassarol@cefriel.it> * - 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 <newell@media-rush.com> * - 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 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; 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"; //////////////////////////////////////////////// // Member //////////////////////////////////////////////// private SSDPNotifySocketList ssdpNotifySocketList; private SSDPSearchResponseSocketList ssdpSearchResponseSocketList; private SSDPNotifySocketList getSSDPNotifySocketList() { return ssdpNotifySocketList; } private 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 udn = USN.getUDN(usn); Device dev = getDevice(udn); if (dev != null) { dev.setSSDPPacket(ssdpPacket); return; } String location = ssdpPacket.getLocation(); try { URL locationUrl = new URL(location); 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", 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(ssdpPacket.toString()); Debug.warning(me); } catch (ParserException pe) { Debug.warning(ssdpPacket.toString()); Debug.warning(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<nRoots; n++) { // AIOOB was thrown from here, maybe would be better to // copy the list before traversal? Node rootNode; try { rootNode = devNodeList.getNode(n); } catch (ArrayIndexOutOfBoundsException aioob) { break; } Device dev = getDevice(rootNode); if (dev == null) continue; devList.add(dev); } return devList; } public Device getDevice(String name) { int nRoots = devNodeList.size(); for (int n=0; n<nRoots; n++) { // AIOOB was thrown from here, maybe would be better to // copy the list before traversal? Node rootNode; try { rootNode = devNodeList.getNode(n); } catch (ArrayIndexOutOfBoundsException aioob) { break; } Device dev = getDevice(rootNode); if (dev == null) continue; if (dev.isDevice(name) == true) return dev; Device cdev = dev.getDevice(name); if (cdev != null) return cdev; } return null; } public boolean hasDevice(String name) { return (getDevice(name) != null) ? true : false; } private void removeDevice(Node rootNode) { // Thanks for Oliver Newell (2004/10/16) // Invoke device removal listener prior to actual removal so Device node // remains valid for the duration of the listener (application may want // to access the node) Device dev = getDevice(rootNode); if( dev != null && dev.isRootDevice() ) performRemoveDeviceListener( dev ); devNodeList.remove(rootNode); } protected void removeDevice(Device dev) { if (dev == null) return; removeDevice(dev.getRootNode()); } protected void removeDevice(String name) { Device dev = getDevice(name); removeDevice(dev); } private void removeDevice(SSDPPacket packet) { if (packet.isByeBye() == false) return; String usn = packet.getUSN(); String udn = USN.getUDN(usn); removeDevice(udn); } //////////////////////////////////////////////// // Expired Device //////////////////////////////////////////////// private Disposer deviceDisposer; private long expiredDeviceMonitoringInterval; public void removeExpiredDevices() { DeviceList devList = getDeviceList(); int devCnt = devList.size(); Device dev[] = new Device[devCnt]; for (int n=0; n<devCnt; n++) dev[n] = devList.getDevice(n); for (int n=0; n<devCnt; n++) { if (dev[n].isExpired() == true) { Debug.message("Expired device = " + dev[n].getFriendlyName()); removeDevice(dev[n]); } } } public void setExpiredDeviceMonitoringInterval(long interval) { expiredDeviceMonitoringInterval = interval; } public long getExpiredDeviceMonitoringInterval() { return expiredDeviceMonitoringInterval; } public void setDeviceDisposer(Disposer disposer) { deviceDisposer = disposer; } public Disposer getDeviceDisposer() { return deviceDisposer; } //////////////////////////////////////////////// // Notify //////////////////////////////////////////////// private ListenerList deviceNotifyListenerList = new ListenerList(); public void addNotifyListener(NotifyListener listener) { deviceNotifyListenerList.add(listener); } public void removeNotifyListener(NotifyListener listener) { deviceNotifyListenerList.remove(listener); } public void performNotifyListener(SSDPPacket ssdpPacket) { int listenerSize = deviceNotifyListenerList.size(); for (int n=0; n<listenerSize; n++) { NotifyListener listener = (NotifyListener)deviceNotifyListenerList.get(n); try{ listener.deviceNotifyReceived(ssdpPacket); }catch(Exception e){ Debug.warning("NotifyListener returned an error:", e); } } } //////////////////////////////////////////////// // SearchResponse //////////////////////////////////////////////// private ListenerList deviceSearchResponseListenerList = new ListenerList(); public void addSearchResponseListener(SearchResponseListener listener) { deviceSearchResponseListenerList.add(listener); } public void removeSearchResponseListener(SearchResponseListener listener) { deviceSearchResponseListenerList.remove(listener); } public void performSearchResponseListener(SSDPPacket ssdpPacket) { int listenerSize = deviceSearchResponseListenerList.size(); for (int n=0; n<listenerSize; n++) { SearchResponseListener listener = (SearchResponseListener)deviceSearchResponseListenerList.get(n); try{ listener.deviceSearchResponseReceived(ssdpPacket); }catch(Exception e){ Debug.warning("SearchResponseListener returned an error:", e); } } } ///////////////////////////////////////////////////////////////////// // Device status changes (device added or removed) // Applications that support the DeviceChangeListener interface are // notified immediately when a device is added to, or removed from, // the control point. ///////////////////////////////////////////////////////////////////// ListenerList deviceChangeListenerList = new ListenerList(); public void addDeviceChangeListener(DeviceChangeListener listener) { deviceChangeListenerList.add(listener); } public void removeDeviceChangeListener(DeviceChangeListener listener) { deviceChangeListenerList.remove(listener); } public void performAddDeviceListener( Device dev ) { int listenerSize = deviceChangeListenerList.size(); for (int n=0; n<listenerSize; n++) { DeviceChangeListener listener = (DeviceChangeListener)deviceChangeListenerList.get(n); listener.deviceAdded( dev ); } } public void performRemoveDeviceListener( Device dev ) { int listenerSize = deviceChangeListenerList.size(); for (int n=0; n<listenerSize; n++) { DeviceChangeListener listener = (DeviceChangeListener)deviceChangeListenerList.get(n); listener.deviceRemoved( dev ); } } //////////////////////////////////////////////// // SSDPPacket //////////////////////////////////////////////// public void notifyReceived(SSDPPacket packet) { if (packet.isRootDevice() == true) { if (packet.isAlive() == true){ addDevice(packet); }else if (packet.isByeBye() == true){ removeDevice(packet); } } performNotifyListener(packet); } public void searchResponseReceived(SSDPPacket packet) { if (packet.isRootDevice() == true) addDevice(packet); performSearchResponseListener(packet); } //////////////////////////////////////////////// // M-SEARCH //////////////////////////////////////////////// private int searchMx = SSDP.DEFAULT_MSEARCH_MX; public int getSearchMx() { return searchMx; } public void setSearchMx(int mx) { searchMx = mx; } public void search(String target, int mx) { SSDPSearchRequest msReq = new SSDPSearchRequest(target, mx); SSDPSearchResponseSocketList ssdpSearchResponseSocketList = getSSDPSearchResponseSocketList(); ssdpSearchResponseSocketList.post(msReq); } public void search(String target) { search(target, SSDP.DEFAULT_MSEARCH_MX); } public void search() { search(ST.ROOT_DEVICE, SSDP.DEFAULT_MSEARCH_MX); } //////////////////////////////////////////////// // EventSub HTTPServer //////////////////////////////////////////////// private HTTPServerList httpServerList = new HTTPServerList(); private HTTPServerList getHTTPServerList() { return httpServerList; } public void httpRequestRecieved(HTTPRequest httpReq) { if (Debug.isOn() == true) httpReq.print(); // Thanks for Giordano Sassaroli <sassarol@cefriel.it> (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<listenerSize; n++) { EventListener listener = (EventListener)eventListenerList.get(n); listener.eventNotifyReceived(uuid, seq, name, value); } } //////////////////////////////////////////////// // Subscription //////////////////////////////////////////////// private String eventSubURI = DEFAULT_EVENTSUB_URI; public String getEventSubURI() { return eventSubURI; } public void setEventSubURI(String url) { eventSubURI = url; } private String getEventSubCallbackURL(String host) { return HostInterface.getHostURL(host, getHTTPPort(), getEventSubURI()); } public boolean subscribe(Service service, long timeout) { if (service.isSubscribed() == true) { String sid = service.getSID(); return subscribe(service, sid, timeout); } Device rootDev = service.getRootDevice(); if (rootDev == null) return false; String ifAddress = rootDev.getInterfaceAddress(); SubscriptionRequest subReq = new SubscriptionRequest(); subReq.setSubscribeRequest(service, getEventSubCallbackURL(ifAddress), timeout); SubscriptionResponse subRes = subReq.post(); if (subRes.isSuccessful() == true) { service.setSID(subRes.getSID()); service.setTimeout(subRes.getTimeout()); return true; } service.clearSID(); return false; } public boolean subscribe(Service service) { return subscribe(service, Subscription.INFINITE_VALUE); } public boolean subscribe(Service service, String uuid, long timeout) { SubscriptionRequest subReq = new SubscriptionRequest(); subReq.setRenewRequest(service, uuid, timeout); if (Debug.isOn() == true) subReq.print(); SubscriptionResponse subRes = subReq.post(); if (Debug.isOn() == true) subRes.print(); if (subRes.isSuccessful() == true) { service.setSID(subRes.getSID()); service.setTimeout(subRes.getTimeout()); return true; } service.clearSID(); return false; } public boolean subscribe(Service service, String uuid) { return subscribe(service, uuid, Subscription.INFINITE_VALUE); } public boolean isSubscribed(Service service) { if (service == null) return false; return service.isSubscribed(); } public boolean unsubscribe(Service service) { SubscriptionRequest subReq = new SubscriptionRequest(); subReq.setUnsubscribeRequest(service); SubscriptionResponse subRes = subReq.post(); if (subRes.isSuccessful() == true) { service.clearSID(); return true; } return false; } public void unsubscribe(Device device) { ServiceList serviceList = device.getServiceList(); int serviceCnt = serviceList.size(); for (int n=0; n<serviceCnt; n++) { Service service = serviceList.getService(n); if (service.hasSID() == true) unsubscribe(service); } DeviceList childDevList = device.getDeviceList(); int childDevCnt = childDevList.size(); for (int n=0; n<childDevCnt; n++) { Device cdev = childDevList.getDevice(n); unsubscribe(cdev); } } public void unsubscribe() { DeviceList devList = getDeviceList(); int devCnt = devList.size(); for (int n=0; n<devCnt; n++) { Device dev = devList.getDevice(n); unsubscribe(dev); } } //////////////////////////////////////////////// // getSubscriberService //////////////////////////////////////////////// public Service getSubscriberService(String uuid) { DeviceList devList = getDeviceList(); int devCnt = devList.size(); for (int n=0; n<devCnt; n++) { Device dev = devList.getDevice(n); Service service = dev.getSubscriberService(uuid); if (service != null) return service; } return null; } //////////////////////////////////////////////// // getSubscriberService //////////////////////////////////////////////// public void renewSubscriberService(Device dev, long timeout) { ServiceList serviceList = dev.getServiceList(); int serviceCnt = serviceList.size(); for (int n=0; n<serviceCnt; n++) { Service service = serviceList.getService(n); if (service.isSubscribed() == false) continue; String sid = service.getSID(); boolean isRenewed = subscribe(service, sid, timeout); if (isRenewed == false) subscribe(service, timeout); } DeviceList cdevList = dev.getDeviceList(); int cdevCnt = cdevList.size(); for (int n=0; n<cdevCnt; n++) { Device cdev = cdevList.getDevice(n); renewSubscriberService(cdev, timeout); } } public void renewSubscriberService(long timeout) { DeviceList devList = getDeviceList(); int devCnt = devList.size(); for (int n=0; n<devCnt; n++) { Device dev = devList.getDevice(n); renewSubscriberService(dev, timeout); } } public void renewSubscriberService() { renewSubscriberService(Subscription.INFINITE_VALUE); } //////////////////////////////////////////////// // Subscriber //////////////////////////////////////////////// private RenewSubscriber renewSubscriber; public void setRenewSubscriber(RenewSubscriber sub) { renewSubscriber = sub; } public RenewSubscriber getRenewSubscriber() { return renewSubscriber; } //////////////////////////////////////////////// // run //////////////////////////////////////////////// public boolean start(String target, int mx) { stop(); //////////////////////////////////////// // HTTP Server //////////////////////////////////////// int retryCnt = 0; int bindPort = getHTTPPort(); HTTPServerList httpServerList = getHTTPServerList(); while (httpServerList.open(bindPort) == false) { retryCnt++; if (UPnP.SERVER_RETRY_COUNT < retryCnt) { Debug.warning("Failed to open HTTP event listener port " + bindPort); // I2P do we really need this, or can we just break ? return false; } // I2P go down not up so we don't run into other I2P things setHTTPPort(bindPort - 1); bindPort = getHTTPPort(); } httpServerList.addRequestListener(this); httpServerList.start(); //////////////////////////////////////// // Notify Socket //////////////////////////////////////// SSDPNotifySocketList ssdpNotifySocketList = getSSDPNotifySocketList(); if (ssdpNotifySocketList.open() == false) { Debug.warning("Failed to open SSDP notify port 1900"); return false; } ssdpNotifySocketList.setControlPoint(this); ssdpNotifySocketList.start(); //////////////////////////////////////// // SeachResponse Socket //////////////////////////////////////// int ssdpPort = getSSDPPort(); retryCnt = 0; SSDPSearchResponseSocketList ssdpSearchResponseSocketList = getSSDPSearchResponseSocketList(); while (ssdpSearchResponseSocketList.open(ssdpPort) == false) { retryCnt++; if (UPnP.SERVER_RETRY_COUNT < retryCnt) { Debug.warning("Failed to open SSDP search response port " + ssdpPort); return false; } // I2P go down not up so we don't run into other I2P things setSSDPPort(ssdpPort - 1); ssdpPort = getSSDPPort(); } ssdpSearchResponseSocketList.setControlPoint(this); ssdpSearchResponseSocketList.start(); //////////////////////////////////////// // search root devices //////////////////////////////////////// search(target, mx); //////////////////////////////////////// // Disposer //////////////////////////////////////// Disposer disposer = new Disposer(this); setDeviceDisposer(disposer); disposer.start(); //////////////////////////////////////// // Subscriber //////////////////////////////////////// if (isNMPRMode() == true) { RenewSubscriber renewSub = new RenewSubscriber(this); setRenewSubscriber(renewSub); renewSub.start(); } return true; } public boolean start(String target) { return start(target, SSDP.DEFAULT_MSEARCH_MX); } public boolean start() { return start(ST.ROOT_DEVICE, SSDP.DEFAULT_MSEARCH_MX); } public boolean stop() { unsubscribe(); SSDPNotifySocketList ssdpNotifySocketList = getSSDPNotifySocketList(); ssdpNotifySocketList.stop(); ssdpNotifySocketList.close(); ssdpNotifySocketList.clear(); SSDPSearchResponseSocketList ssdpSearchResponseSocketList = getSSDPSearchResponseSocketList(); ssdpSearchResponseSocketList.stop(); ssdpSearchResponseSocketList.close(); ssdpSearchResponseSocketList.clear(); HTTPServerList httpServerList = getHTTPServerList(); httpServerList.stop(); httpServerList.close(); httpServerList.clear(); //////////////////////////////////////// // Disposer //////////////////////////////////////// Disposer disposer = getDeviceDisposer(); if (disposer != null) { disposer.stop(); setDeviceDisposer(null); } //////////////////////////////////////// // Subscriber //////////////////////////////////////// RenewSubscriber renewSub = getRenewSubscriber(); if (renewSub != null) { renewSub.stop(); setRenewSubscriber(null); } // I2P so we will re-notify on restart DeviceList dl = getDeviceList(); for (int i = 0; i < dl.size(); i++) { removeDevice(dl.getDevice(i)); } return true; } //////////////////////////////////////////////// // userData //////////////////////////////////////////////// private Object userData = null; public void setUserData(Object data) { userData = data; } public Object getUserData() { return userData; } //////////////////////////////////////////////// // print //////////////////////////////////////////////// public void print() { DeviceList devList = getDeviceList(); int devCnt = devList.size(); Debug.message("Device Num = " + devCnt); for (int n=0; n<devCnt; n++) { Device dev = devList.getDevice(n); Debug.message("[" + n + "] " + dev.getFriendlyName() + ", " + dev.getLeaseTime() + ", " + dev.getElapsedTime()); } } }