/*
* Copyright (C) 2009 Risto Känsäkoski - Sesca ISW Ltd
* Copyright (C) 2005 Luca Veltri - University of Parma - Italy
*
* This file is part of SIP-Applet (www.sesca.com, www.purplescout.com)
* This file is modified from MjSip (http://www.mjsip.org)
*
* MjSip 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.
*
* MjSip 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 MjSip; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* Modified by:
* Daina Interrante (daina.interrante@studenti.unipr.it)
*/
package org.zoolu.sip.dialog;
import org.zoolu.sip.address.*;
import org.zoolu.sip.authentication.DigestAuthentication;
import org.zoolu.sip.transaction.*;
import org.zoolu.sip.message.*;
import org.zoolu.sip.header.*;
import org.zoolu.sip.provider.*;
import org.zoolu.tools.LogLevel;
import org.zoolu.tools.Random;
import com.sesca.misc.Logger;
import java.util.Date;
/** SubscriberDialog.
*/
public class SubscriberDialog extends Dialog implements TransactionClientListener
{
static final int MAX_ATTEMPTS=3;
private int attempts=0;
String qop;
/** String "active" */
protected static final String ACTIVE="active";
/** String "pending" */
protected static final String PENDING="pending";
/** String "terminated" */
protected static final String TERMINATED="terminated";
/** The current subscribe method */
//Message subscribe=null;
/** The subscribe transaction */
TransactionClient subscribe_transaction;
/** The notify transaction */
//TransactionServer notify_transaction=null;
/** The SubscriberDialog listener */
SubscriberDialogListener listener;
/** The event package name */
String event;
/** The subscription id */
String id;
String toTag;
String fromTag;
private String username;
private String password;
private String realm;
/** Internal state D_INIT */
protected static final int D_INIT=0;
/** Internal state D_SUBSCRIBING */
protected static final int D_SUBSCRIBING=1;
/** Internal state D_SUBSCRIBED */
protected static final int D_ACCEPTED=2;
/** Internal state D_PENDING */
protected static final int D_PENDING=3;
/** Internal state D_ACTIVE */
protected static final int D_ACTIVE=4;
/** Internal state D_TERMINATED */
protected static final int D_TERMINATED=9;
/** Gets the dialog state */
protected String getStatus()
{ switch (status)
{ case D_INIT : return "D_INIT";
case D_SUBSCRIBING: return "D_SUBSCRIBING";
case D_ACCEPTED : return "D_ACCEPTED";
case D_PENDING : return "D_PENDING";
case D_ACTIVE : return "D_ACTIVE";
case D_TERMINATED : return "D_TERMINATED";
default : return null;
}
}
// *************************** Public methods **************************
/** Whether the dialog is in "early" state. */
public boolean isEarly()
{ return (status<D_ACCEPTED);
}
/** Whether the dialog is in "confirmed" state. */
public boolean isConfirmed()
{ return (status>=D_ACCEPTED && status<D_TERMINATED);
}
/** Whether the dialog is in "active" state. */
public boolean isTerminated()
{ return (status==D_TERMINATED);
}
/** Whether the subscription is "pending". */
public boolean isSubscriptionPending()
{ return (status>=D_ACCEPTED && status<D_ACTIVE);
}
/** Whether the subscription is "active". */
public boolean isSubscriptionActive()
{ return (status==D_ACTIVE);
}
/** Whether the subscription is "terminated". */
public boolean isSubscriptionTerminated()
{ return (status==D_TERMINATED);
}
/** Gets event type. */
public String getEvent()
{ return event;
}
/** Gets the event "id" parameter. */
public String getId()
{ return id;
}
public String getToTag()
{
return toTag;
}
// **************************** Costructors ****************************
/** Creates a new SubscriberDialog. */
public SubscriberDialog(SipProvider sip_provider, /*String subscriber, String contact, */String event, String id, SubscriberDialogListener listener)
{ super(sip_provider);
this.listener=listener;
this.subscribe_transaction=null;
//this.from_url=new NameAddress(subscriber);
//if (contact!=null) this.contact_url=new NameAddress(contact);
//else this.contact_url=from_url;
this.event=event;
this.id = Random.nextNumString(12) + "@" + sip_provider.getViaAddress();
//this.id=null;
changeStatus(D_INIT);
}
// *************************** Public methods **************************
/** Sends a new SUBSCRIBE request (starts a new subscription).
* It also initializes the dialog state information.
* @param target the target url (and display name)
* @param subscriber the subscriber url (and display name)
* @param contact the contact url OR the contact user-name
*/
public void subscribe(String target, String subscriber, String contact, int expires)
{
printLog("inside subscribe(target=" + target + ",subscriber=" + subscriber + ",contact=" + contact + ",id=" + id + ",expires=" + expires + ")", LogLevel.MEDIUM);
SipURL request_uri = new SipURL(target);
NameAddress to_url = new NameAddress(target);
NameAddress from_url = new NameAddress(subscriber);
NameAddress contact_url;
if(contact != null)
contact_url = new NameAddress(contact);
else
contact_url = from_url;
String content_type = null;
String body = null;
Message req;
MessageFactory msgf = new MessageFactory();
if(expires != 0)
req = msgf.createSubscribeRequest(sip_provider, request_uri, to_url, from_url, contact_url, event, id, content_type, body, fromTag);
else
req = msgf.createSubscribeRequest(sip_provider, request_uri, to_url, from_url, contact_url, event, id, content_type, body, fromTag, toTag);
req.setHeader(new AcceptHeader("application/pidf+xml"));
req.setExpiresHeader(new ExpiresHeader(expires));
subscribe(req);
}
/** Initiates RFC3909 presence subscription
* @param esc Event State Compositor
* @param target the target url
* @param subscriber the subscriber url
* @param contact the contact url OR the contact user-name
* @param realm
* @param passwd
* @param username
*
*/
public void subscribe(String esc, String target, String subscriber, String contact, int expires, String username, String passwd, String realm)
{
this.username=username;
this.password=passwd;
this.realm=realm;
attempts=0;
SipURL request_uri = new SipURL(esc);
NameAddress to_url = new NameAddress(target);
NameAddress from_url = new NameAddress(subscriber);
NameAddress contact_url;
if(contact != null)
contact_url = new NameAddress(contact);
else
contact_url = from_url;
String content_type = null;
String body = null;
Message req;
MessageFactory msgf = new MessageFactory();
if(expires != 0)
req = msgf.createSubscribeRequest(sip_provider, request_uri, to_url, from_url, contact_url, event, id, content_type, body, fromTag);
else
req = msgf.createSubscribeRequest(sip_provider, request_uri, to_url, from_url, contact_url, event, id, content_type, body, fromTag, toTag);
req.setHeader(new AcceptHeader("application/pidf+xml"));
req.setExpiresHeader(new ExpiresHeader(expires));
subscribe(req);
}
/** Sends a new SUBSCRIBE request (starts a new subscription).
* It also initializes the dialog state information.
* @param req the SUBSCRIBE message
*/
public void subscribe(Message req)
{ printLog("inside subscribe(req)",LogLevel.MEDIUM);
if (statusIs(D_TERMINATED))
{ Logger.error("subscription already terminated: request aborted");
return;
}
// else
if(statusIs(D_INIT))
{ changeStatus(D_SUBSCRIBING);
}
update(UAC,req);
// start client transaction
subscribe_transaction=new TransactionClient(sip_provider,req,this);
subscribe_transaction.request();
}
/** Sends a new SUBSCRIBE request (starts a new subscription). */
public void reSubscribe(String target, String subscriber, String contact, int expires)
{ subscribe(target,subscriber,contact,expires);
}
public void reSubscribe(int expireTime)
{
//this.username=username;
// this.password=passwd;
// this.realm=realm;
attempts=0;
// SipURL request_uri = new SipURL(esc);
// NameAddress to_url = new NameAddress(target);
// NameAddress from_url = new NameAddress(subscriber);
//NameAddress contact_url;
// if(contact != null)
// contact_url = new NameAddress(contact);
// else
// contact_url = from_url;
String content_type = null;
String body = null;
Message req;
MessageFactory msgf = new MessageFactory();
req=msgf.createSubscribeRequest(this, event, content_type, body);
req.setHeader(new AcceptHeader("application/pidf+xml"));
req.setExpiresHeader(new ExpiresHeader(expireTime));
subscribe(req);
}
// ************** Inherited from TransactionClientListener **************
/** When the TransactionClient is (or goes) in "Proceeding" state and receives a new 1xx provisional response */
public void onTransProvisionalResponse(TransactionClient tc, Message resp)
{ printLog("onTransProvisionalResponse()",LogLevel.MEDIUM);
// do nothing.
}
/** When the TransactionClient goes into the "Completed" state receiving a 2xx response */
public void onTransSuccessResponse(TransactionClient tc, Message resp)
{ printLog("onTransSuccessResponse()",LogLevel.MEDIUM);
if(!statusIs(D_ACTIVE))
{ changeStatus(D_ACCEPTED);
update(UAC,resp);
StatusLine status_line=resp.getStatusLine();
if (listener!=null) listener.onDlgSubscriptionSuccess(this,status_line.getCode(),status_line.getReason(),resp);
}
else
if(statusIs(D_ACTIVE))
{ StatusLine status_line=resp.getStatusLine();
if (listener!=null) listener.onDlgSubscriptionSuccess(this,status_line.getCode(),status_line.getReason(),resp);
}
}
/** When the TransactionClient goes into the "Completed" state receiving a 300-699 response */
public void onTransFailureResponse(TransactionClient tc, Message resp)
{ Logger.debug("SubscriberDialog.onTransFailureResponse");
StatusLine status=resp.getStatusLine();
int code=status.getCode();
Logger.debug("response="+resp);
Logger.debug("code="+code);
//Logger.debug("authheader?"+resp.hasWwwAuthenticateHeader());
//Logger.debug("authrealm="+resp.getWwwAuthenticateHeader().getRealmParam());
Logger.debug("this.realm="+this.realm);
Logger.debug("attempts="+attempts);
Logger.debug("max attempts="+MAX_ATTEMPTS);
// authenticate
if (code==407 && attempts<MAX_ATTEMPTS && resp.hasProxyAuthenticateHeader() && resp.getProxyAuthenticateHeader().getRealmParam().equalsIgnoreCase(this.realm))
{
Logger.debug("SUBSCRIBE needs to be authenticated");
attempts++;
Message req=tc.getRequestMessage();
req.setCSeqHeader(req.getCSeqHeader().incSequenceNumber());
ProxyAuthenticateHeader wah=resp.getProxyAuthenticateHeader();
String qop_options=wah.getQopOptionsParam();
qop=(qop_options!=null)? (char)34+"auth"+(char)34 : null;
AuthorizationHeader ah=(new DigestAuthentication(req.getRequestLine().getMethod(),req.getRequestLine().getAddress().toString(),wah,null,null,this.username,this.password)).getProxyAuthorizationHeader();
req.setAuthorizationHeader(ah);
subscribe(req);
}
else
{
changeStatus(D_TERMINATED);
StatusLine status_line=resp.getStatusLine();
if (listener!=null) listener.onDlgSubscriptionFailure(this,status_line.getCode(),status_line.getReason(),resp);
}
}
/** When the TransactionClient goes into the "Terminated" state, caused by transaction timeout */
public void onTransTimeout(TransactionClient tc)
{ printLog("onTransTimeout()",LogLevel.MEDIUM);
changeStatus(D_TERMINATED);
if (listener!=null) listener.onDlgSubscribeTimeout(this);
}
// ***************** Inherited from SipProviderListener *****************
/** When a new Message is received by the SipProvider. */
public void onReceivedMessage(SipProvider sip_provider, Message msg)
{
printLog("onReceivedMessage()", LogLevel.MEDIUM);
if(statusIs(D_TERMINATED))
{
printLog("subscription already terminated: message discarded", LogLevel.MEDIUM);
return;
}
// else
if(msg.isRequest() && msg.isNotify())
{
TransactionServer ts = new TransactionServer(sip_provider, msg, null);
ts.respondWith(MessageFactory.createResponse(msg, 200, SipResponses.reasonOf(200), null));
NameAddress to = msg.getToHeader().getNameAddress();
NameAddress from = msg.getFromHeader().getNameAddress();
NameAddress contact = null;
if(msg.hasContactHeader())
contact = msg.getContactHeader().getNameAddress();
String state = null;
if(msg.hasSubscriptionStateHeader())
state = msg.getSubscriptionStateHeader().getState();
String content_type = null;
if(msg.hasContentTypeHeader())
content_type = msg.getContentTypeHeader().getContentType();
String body = null;
if(msg.hasBody())
body = msg.getBody();
if(listener != null)
listener.onDlgNotify(this, to, from, contact, state, content_type, body, msg);
if(state != null)
{
if(state.equalsIgnoreCase(ACTIVE) && !statusIs(D_TERMINATED))
{
changeStatus(D_ACTIVE);
}
else if(state.equalsIgnoreCase(PENDING) && statusIs(D_ACCEPTED))
{
changeStatus(D_PENDING);
}
else if(state.equalsIgnoreCase(TERMINATED) && !statusIs(D_TERMINATED))
{
changeStatus(D_TERMINATED);
if(listener != null)
listener.onDlgSubscriptionTerminated(this);
}
}
}
else
{
printLog("message is not a NOTIFY: message discarded", LogLevel.HIGH);
}
}
/*Sets to tag*/
public void setToTag(Message msg)
{
toTag = msg.getToHeader().getTag();
}
// **************************** Logs ****************************/
/** Adds a new string to the default Log */
protected void printLog(String str, int level)
{ if (log!=null) log.println("SubscriberDialog#"+dialog_sqn+": "+str,level+SipStack.LOG_LEVEL_DIALOG);
}
public void setFromTag(Message msg)
{
fromTag = msg.getFromHeader().getTag();
}
}