org.cybergarage.upnp.ControlPoint Maven / Gradle / Ivy
/******************************************************************
*
* 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