/*
* Created on 14-Jun-2004
* Created by Paul Gardner
* Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package com.aelitis.net.upnp.impl.ssdp;
import java.net.*;
import java.util.*;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.RandomUtils;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.core3.util.TimeFormatter;
import org.gudy.azureus2.plugins.utils.UTTimer;
import org.gudy.azureus2.plugins.utils.UTTimerEvent;
import org.gudy.azureus2.plugins.utils.UTTimerEventPerformer;
import com.aelitis.net.udp.mc.MCGroup;
import com.aelitis.net.udp.mc.MCGroupAdapter;
import com.aelitis.net.udp.mc.MCGroupFactory;
import com.aelitis.net.upnp.*;
/**
* @author parg
*
*/
public class
SSDPCore
implements UPnPSSDP, MCGroupAdapter
{
private static final String HTTP_VERSION = "1.1";
private static final String NL = "\r\n";
private static Map singletons = new HashMap();
private static AEMonitor class_mon = new AEMonitor( "SSDPCore:class" );
public static SSDPCore
getSingleton(
UPnPSSDPAdapter adapter,
String group_address,
int group_port,
int control_port,
String[] selected_interfaces )
throws UPnPException
{
try{
class_mon.enter();
String key = group_address + ":" + group_port + ":" + control_port;
SSDPCore singleton = (SSDPCore)singletons.get( key );
if ( singleton == null ){
singleton = new SSDPCore( adapter, group_address, group_port, control_port, selected_interfaces );
singletons.put( key, singleton );
}
return( singleton );
}finally{
class_mon.exit();
}
}
private MCGroup mc_group;
private UPnPSSDPAdapter adapter;
private String group_address_str;
private int group_port;
private boolean first_response = true;
private List listeners = new ArrayList();
private UTTimer timer;
private List timer_queue = new ArrayList();
private long time_event_next;
protected AEMonitor this_mon = new AEMonitor( "SSDP" );
private Set<String> ignore_mx = new HashSet();
private
SSDPCore(
UPnPSSDPAdapter _adapter,
String _group_address,
int _group_port,
int _control_port,
String[] _selected_interfaces )
throws UPnPException
{
adapter = _adapter;
group_address_str = _group_address;
group_port = _group_port;
try{
mc_group = MCGroupFactory.getSingleton( this, _group_address, group_port, _control_port, _selected_interfaces );
}catch( Throwable e ){
throw( new UPnPException( "Failed to initialise SSDP", e ));
}
}
public int
getControlPort()
{
return( mc_group.getControlPort());
}
public void
trace(
String str )
{
adapter.log( str );
}
public void
log(
Throwable e )
{
adapter.log( e );
}
public void
notify(
String NT,
String NTS,
String UUID,
String url )
{
/*
NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=3600
LOCATION: http://192.168.0.1:49152/gateway.xml
NT: urn:schemas-upnp-org:service:WANIPConnection:1
NTS: ssdp:byebye
SERVER: Linux/2.4.17_mvl21-malta-mips_fp_le, UPnP/1.0, Intel SDK for UPnP devices /1.2
USN: uuid:ab5d9077-0710-4373-a4ea-5192c8781666::urn:schemas-upnp-org:service:WANIPConnection:1
*/
if ( url.startsWith("/")){
url = url.substring(1);
}
String str =
"NOTIFY * HTTP/" + HTTP_VERSION + NL +
"HOST: " + group_address_str + ":" + group_port + NL +
"CACHE-CONTROL: max-age=3600" + NL +
"LOCATION: http://%AZINTERFACE%:" + mc_group.getControlPort() + "/" + url + NL +
"NT: " + NT + NL +
"NTS: " + NTS + NL +
"SERVER: " + getServerName() + NL +
"USN: " + (UUID==null?"":(UUID + "::")) + NT + NL + NL;
try{
mc_group.sendToGroup( str );
}catch( Throwable e ){
}
}
protected String
getServerName()
{
return( System.getProperty( "os.name" ) + "/" + System.getProperty("os.version") + " UPnP/1.0 " +
Constants.AZUREUS_NAME + "/" + Constants.AZUREUS_VERSION );
}
public void
search(
String[] STs )
{
for ( String ST: STs ){
String str =
"M-SEARCH * HTTP/" + HTTP_VERSION + NL +
"ST: " + ST + NL +
"MX: 3" + NL +
"MAN: \"ssdp:discover\"" + NL +
"HOST: " + group_address_str + ":" + group_port + NL + NL;
sendMC( str );
}
}
protected void
sendMC(
String str )
{
byte[] data = str.getBytes();
try{
mc_group.sendToGroup( data );
}catch( Throwable e ){
}
}
public void
interfaceChanged(
NetworkInterface network_interface )
{
for (int i=0;i<listeners.size();i++){
try{
((UPnPSSDPListener)listeners.get(i)).interfaceChanged(network_interface);
}catch( Throwable e ){
adapter.log(e);
}
}
}
public void
received(
NetworkInterface network_interface,
InetAddress local_address,
final InetSocketAddress originator,
byte[] packet_data,
int length )
{
String str = new String( packet_data, 0,length );
if ( first_response ){
first_response = false;
adapter.trace( "UPnP:SSDP: first response:\n" + str );
}
// example notify event
/*
NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=3600
LOCATION: http://192.168.0.1:49152/gateway.xml
NT: urn:schemas-upnp-org:service:WANIPConnection:1
NTS: ssdp:byebye
SERVER: Linux/2.4.17_mvl21-malta-mips_fp_le, UPnP/1.0, Intel SDK for UPnP devices /1.2
USN: uuid:ab5d9077-0710-4373-a4ea-5192c8781666::urn:schemas-upnp-org:service:WANIPConnection:1
*/
//if ( originator.getAddress().getHostAddress().equals( "192.168.0.135" )){
// System.out.println( originator + ":" + str );
//}
List<String> lines = new ArrayList<String>();
int pos = 0;
while(true){
int p1 = str.indexOf( NL, pos );
String line;
if ( p1 == -1 ){
line = str.substring(pos);
}else{
line = str.substring(pos,p1);
pos = p1+1;
}
lines.add( line.trim());
if ( p1 == -1 ){
break;
}
}
if ( lines.size() == 0 ){
adapter.trace( "SSDP::receive packet - 0 line reply" );
return;
}
String header = (String)lines.get(0);
// Gudy's Root: http://192.168.0.1:5678/igd.xml, uuid:upnp-InternetGatewayDevice-1_0-12345678900001::upnp:rootdevice, upnp:rootdevice
// Parg's Root: http://192.168.0.1:49152/gateway.xml, uuid:824ff22b-8c7d-41c5-a131-44f534e12555::upnp:rootdevice, upnp:rootdevice
URL location = null;
String usn = null;
String nt = null;
String nts = null;
String st = null;
String al = null;
String mx = null;
String server = null;
for (int i=1;i<lines.size();i++){
String line = (String)lines.get(i);
int c_pos = line.indexOf(":");
if ( c_pos == -1 ){
continue;
}
String key = line.substring( 0, c_pos ).trim().toUpperCase();
String val = line.substring( c_pos+1 ).trim();
if ( key.equals("LOCATION" )){
try{
// xbox throws us a '*' on bootup
if ( !val.equals( "*" )){
location = new URL( val );
}
}catch( MalformedURLException e ){
adapter.log( e );
}
}else if ( key.equals( "NT" )){
nt = val;
}else if ( key.equals( "USN" )){
usn = val;
}else if ( key.equals( "NTS" )){
nts = val;
}else if ( key.equals( "ST" )){
st = val;
}else if ( key.equals( "AL" )){
al = val;
}else if ( key.equals( "MX" )){
mx = val;
}else if ( key.equals( "SERVER" )){
server = val;
}
}
//if ( location != null && location.getHost().equals( "192.168.0.135")){
// System.out.println( str );
//}
if ( server != null ){
// xbox doesn't play well with us doing MX properly, seems like the delay causes
// it not to pick up the response, grrrrr!
if ( server.toLowerCase().startsWith( "xbox" )){
String host = originator.getAddress().getHostAddress();
synchronized( ignore_mx ){
ignore_mx.add( host );
}
}
}
if ( mx != null ){
String host = originator.getAddress().getHostAddress();
synchronized( ignore_mx ){
if ( ignore_mx.contains( host )){
mx = null;
}
}
}
if ( header.startsWith("M-SEARCH")){
if ( st != null ){
/*
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=600
DATE: Tue, 20 Dec 2005 13:07:31 GMT
EXT:
LOCATION: http://192.168.1.1:2869/gatedesc.xml
SERVER: Linux/2.4.17_mvl21-malta-mips_fp_le UPnP/1.0
ST: upnp:rootdevice
USN: uuid:UUID-InternetGatewayDevice-1234::upnp:rootdevice
*/
String[] response = informSearch( network_interface, local_address, originator.getAddress(), st );
if ( response != null ){
String UUID = response[0];
String url = response[1];
if ( url.startsWith("/")){
url = url.substring(1);
}
// Server MUST be in this alpha-case for Xbox to work (SERVER doesn't)...
String data =
"HTTP/1.1 200 OK" + NL +
"USN: " + UUID + "::" + st + NL +
"ST: " + st + NL +
"EXT:" + NL +
"Location: http://" + local_address.getHostAddress() + ":" + mc_group.getControlPort() + "/" + url + NL +
"Server: Azureus/" + Constants.AZUREUS_VERSION + " UPnP/1.0 Azureus/" + Constants.AZUREUS_VERSION + NL +
"Cache-Control: max-age=3600" + NL +
"Date: " + TimeFormatter.getHTTPDate( SystemTime.getCurrentTime()) + NL +
"Content-Length: 0" + NL + NL;
final byte[] data_bytes = data.getBytes();
if ( timer == null ){
timer = adapter.createTimer( "SSDPCore:MX" );
}
int delay = 0;
if ( mx != null ){
try{
delay = Integer.parseInt( mx ) * 1000;
delay = RandomUtils.nextInt( delay );
}catch( Throwable e ){
}
}
final Runnable task =
new Runnable()
{
public void
run()
{
try{
mc_group.sendToMember( originator, data_bytes );
}catch( Throwable e ){
adapter.log(e);
}
}
};
if ( delay == 0 ){
task.run();
}else{
long target_time = SystemTime.getCurrentTime() + delay;
boolean schedule_event;
synchronized( timer_queue ){
timer_queue.add( task );
schedule_event = time_event_next == 0 || target_time < time_event_next;
if ( schedule_event ){
time_event_next = target_time;
}
}
if ( schedule_event ){
timer.addEvent(
target_time,
new UTTimerEventPerformer()
{
public void
perform(
UTTimerEvent event )
{
// only actually ever run of these at a time as they
// have been seen to back up and flood the timer pool
while( true ){
Runnable t;
synchronized( timer_queue ){
if ( timer_queue.size() > 0 ){
t = (Runnable)timer_queue.remove(0);
}else{
time_event_next = 0;
return;
}
}
try{
t.run();
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
});
}
}
}
}else{
adapter.trace( "SSDP::receive M-SEARCH - bad header:" + header );
}
}else if ( header.startsWith( "NOTIFY" )){
// location is null for byebye
if ( nt != null && nts != null ){
informNotify( network_interface, local_address, originator.getAddress(), usn, location, nt, nts );
}else{
adapter.trace( "SSDP::receive NOTIFY - bad header:" + header );
}
}else if ( header.startsWith( "HTTP") && header.indexOf( "200") != -1 ){
if ( location != null && st != null ){
informResult( network_interface, local_address, originator.getAddress(), usn, location, st, al );
}else{
adapter.trace( "SSDP::receive HTTP - bad header:" + header );
}
}else{
adapter.trace( "SSDP::receive packet - bad header:" + header );
}
}
protected void
informResult(
NetworkInterface network_interface,
InetAddress local_address,
InetAddress originator,
String usn,
URL location,
String st,
String al )
{
for (int i=0;i<listeners.size();i++){
try{
((UPnPSSDPListener)listeners.get(i)).receivedResult(network_interface,local_address,originator,usn,location,st,al);
}catch( Throwable e ){
adapter.log(e);
}
}
}
protected void
informNotify(
NetworkInterface network_interface,
InetAddress local_address,
InetAddress originator,
String usn,
URL location,
String nt,
String nts )
{
for (int i=0;i<listeners.size();i++){
try{
((UPnPSSDPListener)listeners.get(i)).receivedNotify(network_interface,local_address,originator,usn,location,nt,nts);
}catch( Throwable e ){
adapter.log(e);
}
}
}
protected String[]
informSearch(
NetworkInterface network_interface,
InetAddress local_address,
InetAddress originator,
String st )
{
for (int i=0;i<listeners.size();i++){
try{
String[] res = ((UPnPSSDPListener)listeners.get(i)).receivedSearch(network_interface,local_address,originator,st );
if ( res != null ){
return( res );
}
}catch( Throwable e ){
adapter.log(e);
}
}
return( null );
}
public void
addListener(
UPnPSSDPListener l )
{
listeners.add( l );
}
public void
removeListener(
UPnPSSDPListener l )
{
listeners.remove(l);
}
}