/*
/ Copyright (C) 2009 Risto Känsäkoski, Antti Alho - Sesca ISW Ltd
/
/ This file is part of SIP-Applet (www.sesca.com, www.purplescout.com)
/
/ 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.sesca.sip.presence;
import local.net.KeepAliveSip;
import local.ua.RegisterAgentListener;
import org.zoolu.net.SocketAddress;
import org.zoolu.sip.address.*;
import org.zoolu.sip.provider.SipStack;
import org.zoolu.sip.provider.SipProvider;
import org.zoolu.sip.header.*;
import org.zoolu.sip.message.*;
import org.zoolu.sip.transaction.TransactionClient;
import org.zoolu.sip.transaction.TransactionClientListener;
import org.zoolu.sip.authentication.DigestAuthentication;
import org.zoolu.sip.dialog.SubscriberDialog;
import org.zoolu.sip.dialog.SubscriberDialogListener;
import org.zoolu.tools.Log;
import org.zoolu.tools.LogLevel;
import com.sesca.misc.Logger;
import com.sesca.sip.presence.pidf.Presentity;
import com.sesca.sip.presence.pidf.SimpleParser;
import com.sesca.sip.presence.pidf.Tuple;
import com.sesca.voip.ua.AppletUANG;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
public class PresenceAgent implements Runnable, SubscriberDialogListener, TransactionClientListener, PublishSchedulerListener
{
PresenceAgentListener listener;
SipProvider sip_provider;
NameAddress target;
String username;
String realm;
String authName;
String passwd;
String next_nonce;
String qop;
NameAddress contact;
int expire_time;
int renew_time;
boolean loop;
boolean running = false;
Log log;
int attempts;
//KeepAliveSip keep_alive;
SubscriberDialog subscriberDialog=null;
String currentPresenceStatus="";
String currentPresenceNote = "";
boolean published = false;
HashMap<String, Presentity> presentities=null;
Hashtable dialogs=null;
int publishExpireTime=0;
long publishedTime=0;
long initialTime=0;
static int hysteresis=30;
PublishScheduler ps;
public PresenceAgent(SipProvider sip_provider, String contact_url, PresenceAgentListener listener)
{ init(sip_provider,contact_url,listener);
}
public PresenceAgent(SipProvider sip_provider, String contact_url, String username, String realm, String passwd, PresenceAgentListener listener)
{ init(sip_provider,contact_url,listener);
// authentication
this.username=username;
this.realm=realm;
this.passwd=passwd;
this.authName=username;
}
public PresenceAgent(SipProvider sip_provider, String contact_url, String username, String authName, String realm, String passwd, PresenceAgentListener listener)
{ init(sip_provider,contact_url,listener);
// authentication
this.username=username;
this.realm=realm;
this.passwd=passwd;
this.authName=authName;
}
private void init(SipProvider sip_provider, String contact_url, PresenceAgentListener listener)
{ this.listener=listener;
this.sip_provider=sip_provider;
this.log=sip_provider.getLog();
this.contact=new NameAddress(contact_url);
this.expire_time=SipStack.default_expires;
this.renew_time=0;
this.running=false;
//this.keep_alive=null;
// authentication
this.username=null;
this.realm=null;
this.passwd=null;
this.next_nonce=null;
this.qop=null;
this.attempts=0;
presentities = new HashMap();
dialogs = new Hashtable();
initialTime=System.currentTimeMillis();
}
/*
public void subscribe(int expire_time)
{
attempts=0;
MessageFactory msgf = new MessageFactory();
if (expire_time>0) this.expire_time=expire_time;
//Message req=msgf.createSubscribeRequest(sip_provider, recipient, to, from, contact, event, id, content_type, body, remoteTag)
Message req=msgf.createSubscribeRequest(sip_provider, new SipURL("recipient"), new NameAddress("to"), new NameAddress("from"), new NameAddress("contact"), "event", "id", "content_type", "body", "remoteTag");
req.setExpiresHeader(new ExpiresHeader(String.valueOf(expire_time)));
if (next_nonce!=null)
{ AuthorizationHeader ah=new AuthorizationHeader("Digest");
SipURL target_url=target.getAddress();
ah.addUsernameParam(username);
ah.addRealmParam(realm);
ah.addNonceParam(next_nonce);
ah.addUriParam(req.getRequestLine().getAddress().toString());
ah.addQopParam(qop);
String response=(new DigestAuthentication(SipMethods.REGISTER,ah,null,passwd)).getResponse();
ah.addResponseParam(response);
req.setAuthorizationHeader(ah);
}
//if (expire_time>0) printLog("Registering contact "+contact+" (it expires in "+expire_time+" secs)",LogLevel.HIGH);
//else printLog("Unregistering contact "+contact,LogLevel.HIGH);
System.out.println(req.toString());
}
*/
public void subscribe(int expireTime, String to)
{
if (to==null || to.length()==0 ) return;
// System.out.println("PresenceAgent.subscribeInDialog()");
boolean newDialog = true;
if(expireTime < 0)
expireTime = SipStack.default_expires;
if (dialogs.containsKey(to))
{
subscriberDialog=(SubscriberDialog)dialogs.get(to);
if(subscriberDialog == null)
subscriberDialog = new SubscriberDialog(sip_provider, "presence", null, this);
else newDialog=false;
}
else
{
if (expireTime==0) return;
else subscriberDialog = new SubscriberDialog(sip_provider, "presence", null, this);
}
if (newDialog)
{
String from = username+"@"+realm;
// System.out.println("to="+to);
// if (target!=null)System.out.println("target="+target.toString());
// if (from!=null)System.out.println("from="+from);
// if (contact!=null)System.out.println("contact="+contact.toString());
subscriberDialog.subscribe(to, to, from , contact.toString(), expireTime, username, passwd, realm);
dialogs.put(to, subscriberDialog);
//subscribe(String target, String subscriber, String contact, int expires)
Presentity prs= new Presentity(to,Presentity.statusPending);
if (presentities.containsKey(to))presentities.remove(to);
presentities.put(to, prs);
Logger.debug("to presentities: "+to);
//printPresentities();
}
else
{
subscriberDialog.reSubscribe(expireTime);
dialogs.put(to, subscriberDialog);
//subscribe(String target, String subscriber, String contact, int expires)
Presentity prs;
if (presentities.containsKey(to)) prs=presentities.get(to);
else prs=new Presentity(to, Presentity.statusPending);
prs.setStatus(Presentity.statusPending);
presentities.put(to, prs);
Logger.debug("to presentities: "+to);
//printPresentities();
}
if (expireTime==0)
{
Presentity prs = null;
if (presentities.containsKey(to)) prs=presentities.get(to);
// System.out.println(presentities.containsKey(to));
// System.out.println(presentities.containsKey(to));
if (prs!= null)
{
prs.setStatus(Presentity.statusCancelled);
presentities.put(to, prs);
}
}
onPresenceUpdate();
}
public void publish(String status, String note, int expireTime)
{
publish(status, note, expireTime, false);
}
public void publish(String status, String note, int expireTime, boolean forced)
{
if (expireTime<hysteresis && expireTime>0)expireTime+=hysteresis;
boolean statusChanged=false;
status=status.toLowerCase().trim();
note=note.trim();
if (!status.equals("open") && !status.equals("closed"))
{
Logger.error("Illegal presence status in pidf");
return;
}
if (!status.equals(currentPresenceStatus) || !note.equals(currentPresenceNote)) statusChanged=true;
if (!statusChanged && !forced)
{
Logger.debug("Presence has not changed");
return;
}
currentPresenceStatus=status;
currentPresenceNote=note;
//if(subscriberDialog == null)
// subscriberDialog = new SubscriberDialog(sip_provider, "presence", null, this);
if(expireTime < 0)
expireTime = SipStack.default_expires;
//subscriberDialog.subscribe(target.toString(), contact.getAddress().getUserName()+"@"+contact.getAddress().getHost(), contact.toString(), expireTime);
String from = username+"@"+realm;
// System.out.println("esc="+esc);
//System.out.println("target="+target.toString());
// System.out.println("from="+from);
//System.out.println("contact="+contact.toString());
MessageFactory msgf = new MessageFactory();
Message req = msgf.createPublishRequest(sip_provider, new NameAddress(from), "presence");
req.setExpiresHeader(new ExpiresHeader (expireTime));
String tupleId=sip_provider.UAIdentity.replace("/", "").replace(":", "").toLowerCase();
String entity="sip:"+username+"@"+realm;
//tupleId="ck38g9";
String xml=
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+
"<presence xmlns=\"urn:ietf:params:xml:ns:pidf\""+
"entity=\""+ entity +"\">"+
"<tuple id=\""+ tupleId +"\">"+
"<status>"+
"<basic>"+status+"</basic>"+
"</status>";
if (note !="" && note.length()>0)
xml+="<note>"+note+"</note>";
xml +=
"</tuple>"+
"</presence>";
req.setBody("application/pidf+xml", xml);
// System.out.println("PUBLISH message:");
// System.out.println(req);
TransactionClient t = new TransactionClient(sip_provider, req, this);
t.request();
publishExpireTime=expireTime;
publishedTime=System.currentTimeMillis();
published=false;
run();
}
/** Run method */
public void run()
{
ps=new PublishScheduler(this);
ps.init(publishedTime, publishExpireTime, hysteresis);
ps.start();
}
@Override
public void onDlgNotify(SubscriberDialog dialog, NameAddress target,
NameAddress notifier, NameAddress contact, String state,
String content_type, String body, Message msg) {
String n=null;
// System.out.println("PresenceAgent.onDlgNotify");
// System.out.println("target="+target);
// System.out.println("notifier="+notifier);
if (notifier!=null)n=(notifier.getAddress().toString());
// System.out.println("contact="+contact);
// System.out.println("state="+state);
// System.out.println("msg="+msg);
// System.out.println("content-type="+content_type);
// System.out.println("body"+body);
boolean fail=true;
if (content_type != null && content_type.toLowerCase().equals("application/pidf+xml")) fail=false;
if (!presentities.containsKey(n)) fail=true;
if (!fail && body != null && state.trim().toLowerCase().equals("active"))
{
Logger.debug("pa.onDlgNotify sucksess");
SimpleParser p = new SimpleParser();
p.parse(body);
Vector<Tuple> tuples=p.getTuples();
Presentity prs=new Presentity(n, Presentity.statusActive);
// System.out.println("tuples vector size="+tuples.size());
for (int i=0;i<tuples.size();i++)
{
Tuple t=tuples.elementAt(i);
// System.out.println("tuple "+i+" "+t.getId());
prs.addTuple(t.getId(), t);
}
presentities.put(n, prs);
//printPresentities();
onPresenceUpdate();
}
else
{
Logger.debug("pa.onDlgNotify fails");
if (body == null && presentities.containsKey(n))
{
Logger.debug("body is empty");
if (state.trim().toLowerCase().equals("active"))
{
Logger.debug("new active subscription");
Presentity prs=new Presentity(n, Presentity.statusActive);
presentities.put(n,prs);
}
else {
Logger.debug("subscription not active");
}
}
else Logger.debug("presentity not in list");
//printPresentities();
onPresenceUpdate();
return;
}
// TODO Auto-generated method stub
}
@Override
public void onDlgSubscribeTimeout(SubscriberDialog dialog) {
// TODO Auto-generated method stub
System.out.println("PresenceAgent.onDlgSubscribeTimeout");
}
@Override
public void onDlgSubscriptionFailure(SubscriberDialog dialog, int code,
String reason, Message msg) {
// TODO Auto-generated method stub
// System.out.println("PresenceAgent.onDlgSubscriptionFailure, code="+code);
// System.out.println(msg.getFromHeader().getNameAddress().toString());
String key=(msg.getToHeader().getNameAddress().toString()).replace("<", "").replace(">","");
if (dialogs.containsValue(dialog))
{
if (dialogs.containsKey(key) && dialogs.get(key)==dialog)
{
dialogs.remove(key);
}
}
if (presentities.containsKey(key)) presentities.remove(key);
onPresenceUpdate();
}
@Override
public void onDlgSubscriptionSuccess(SubscriberDialog dialog, int code,
String reason, Message msg) {
// TODO Auto-generated method stub
// System.out.println("PresenceAgent.onDlgSubscriptionSuccess: "+msg.getToHeader().getNameAddress().toString().replace("<", "").replace(">",""));
String s =msg.getToHeader().getNameAddress().toString().replace("<", "").replace(">","");
// if (presentities.containsKey(s)) System.out.println("LÖYTYY "+s);
// else System.out.println("EI LÖYDY "+s);
//printPresentities();
onPresenceUpdate();
}
@Override
public void onDlgSubscriptionTerminated(SubscriberDialog dialog) {
// TODO Auto-generated method stub
// System.out.println("PresenceAgent.onDlgSubscriptionTerminated");
String to = null;
Iterator it = dialogs.keySet().iterator();
while(it.hasNext())
{
String key = (String)it.next();
if (dialogs.get(key).equals(dialog))
{
// System.out.println("--->LÖYTYI<---");
to=key;
break;
}
}
if (to!=null)
{
//presentities.remove(to);
Presentity prs = presentities.get(to);
if (prs!=null)
{
if (prs.getStatus().equals(Presentity.statusCancelled))
{
presentities.remove(to);
dialogs.remove(to);
dialog=null;
}
else
{
prs.setStatus(Presentity.statusExpired);
presentities.put(to, prs);
dialogs.remove(to);
dialog=null;
subscribe(3600, to);
}
}
}
onPresenceUpdate();
}
// **************** Transaction callback functions *****************
private boolean parse(String xml)
{
/* int index=0;
int pIndex = xml.indexOf("<presence");
if (pIndex==0) return false;
index = pIndex;
int pEnd = xml.indexOf(">", index+1);
*/
int start=xml.indexOf("<basic>");
int end=xml.indexOf("</basic>");
String status=xml.substring(start+7, end);
// System.out.println("PRESENCE="+status);
return true;
}
@Override
public void onTransFailureResponse(TransactionClient tc, Message resp) {
// System.out.println("onTransFailureResponse");
// Autentikointi
String method = tc.getTransactionMethod();
StatusLine status_line = resp.getStatusLine();
int code = status_line.getCode();
// AUTHENTICATION-BEGIN
if((code == 401 && resp.hasWwwAuthenticateHeader() && resp.getWwwAuthenticateHeader().getRealmParam().equalsIgnoreCase(realm)) || (code == 407 && resp.hasProxyAuthenticateHeader() && resp.getProxyAuthenticateHeader().getRealmParam().equalsIgnoreCase(realm)))
{
// req:ssa on cseq:ua kasvatettu
Message req = tc.getRequestMessage();
req.setCSeqHeader(req.getCSeqHeader().incSequenceNumber());
WwwAuthenticateHeader wah;
if(code == 401)
wah = resp.getWwwAuthenticateHeader();
else
wah = resp.getProxyAuthenticateHeader();
String qop_options = wah.getQopOptionsParam();
qop = (qop_options != null) ? "auth" : null;
RequestLine rl = req.getRequestLine();
DigestAuthentication digest = new DigestAuthentication(rl.getMethod(), rl.getAddress().toString(), wah, null, null, authName, passwd);
AuthorizationHeader ah;
if(code == 401)
ah = digest.getAuthorizationHeader();
else
ah = digest.getProxyAuthorizationHeader();
req.setAuthorizationHeader(ah);
tc = new TransactionClient(sip_provider, req, this);
tc.request();
}
// AUTHENTICATION-END
}
@Override
public void onTransProvisionalResponse(TransactionClient tc, Message resp) {
// TODO Auto-generated method stub
// System.out.println("onTransProvisionalResponse");
}
@Override
public void onTransSuccessResponse(TransactionClient tc, Message resp) {
// TODO Auto-generated method stub
// System.out.println("onTransSuccessResponse");
published=true;
}
@Override
public void onTransTimeout(TransactionClient tc) {
// TODO Auto-generated method stub
// System.out.println("onTransTimeoutResponse");
}
private void printPresentities()
{
// System.out.println("printPresentities");
if (false)
{
//Vector<String> rows = new Vector<String>();
Iterator it = presentities.keySet().iterator();
while(it.hasNext())
{
String row;
Object key = it.next();
Presentity val = presentities.get(key);
String con=val.getContact();
String sta=val.getStatus();
//System.out.print("("+key+") "+con+", "+sta);
row=("("+key+") "+con+", "+sta);
HashMap tuples=(HashMap)val.getTuples();
if (tuples!=null && !tuples.isEmpty())
{
//System.out.println("iteroidaan tuplet ("+tuples.size()+") kpl");
Iterator tit = tuples.keySet().iterator();
while(tit.hasNext())
{
Object tkey = tit.next();
//System.out.println(tkey);
Tuple tval = (Tuple)tuples.get(tkey);
//System.out.println("-> "+tval.getId()+", "+tval.getStatus_basic());
row+=("-> "+tval.getId()+", "+tval.getStatus_basic());
}
}
//else System.out.println("");
// System.out.println(row);
}
}
}
void onPresenceUpdate()
{
listener.onPresenceChange(presentities);
}
@Override
public void rePublish() {
if (published) publish(currentPresenceStatus, currentPresenceNote, publishExpireTime, true);
}
}