text: /* $Id$ */
  /* 
   * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
   * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
   *
   * 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 
   */
  #include <pjsip-simple/evsub.h>
  #include <pjsip-simple/evsub_msg.h>
  #include <pjsip-simple/errno.h>
  #include <pjsip/sip_errno.h>
  #include <pjsip/sip_module.h>
  #include <pjsip/sip_endpoint.h>
  #include <pjsip/sip_dialog.h>
  #include <pjsip/sip_auth.h>
  #include <pjsip/sip_transaction.h>
  #include <pjsip/sip_event.h>
  #include <pj/assert.h>
  #include <pj/guid.h>
  #include <pj/log.h>
  #include <pj/os.h>
  #include <pj/pool.h>
  #include <pj/rand.h>
  #include <pj/string.h>
  
  
  #define THIS_FILE	"evsub.c"
  
  /*
   * Global constant
   */
  
  /* Let's define this enum, so that it'll trigger compilation error
   * when somebody define the same enum in sip_msg.h
   */
  enum
  {
      PJSIP_SUBSCRIBE_METHOD = PJSIP_OTHER_METHOD,
      PJSIP_NOTIFY_METHOD = PJSIP_OTHER_METHOD
  };
  
  PJ_DEF_DATA(const pjsip_method) pjsip_subscribe_method = 
  {
      (pjsip_method_e) PJSIP_SUBSCRIBE_METHOD,
      { "SUBSCRIBE", 9 }
  };
  
  PJ_DEF_DATA(const pjsip_method) pjsip_notify_method = 
  {
      (pjsip_method_e) PJSIP_NOTIFY_METHOD,
      { "NOTIFY", 6 }
  };
  
  /**
   * SUBSCRIBE method constant.
   */
  PJ_DEF(const pjsip_method*) pjsip_get_subscribe_method()
  {
      return &pjsip_subscribe_method;
  }
  
  /**
   * NOTIFY method constant.
   */
  PJ_DEF(const pjsip_method*) pjsip_get_notify_method()
  {
      return &pjsip_notify_method;
  }
  
  
  /*
   * Static prototypes.
   */
  static void	   mod_evsub_on_tsx_state(pjsip_transaction*, pjsip_event*);
  static pj_status_t mod_evsub_unload(void);
  
  
  /*
   * State names.
   */
  static pj_str_t evsub_state_names[] = 
  {
      { "NULL",	    4},
      { "SENT",	    4},
      { "ACCEPTED",   8},
      { "PENDING",    7},
      { "ACTIVE",	    6},
      { "TERMINATED", 10},
      { "UNKNOWN",    7}
  };
  
  /*
   * Timer constants.
   */
  
  /* Number of seconds to send SUBSCRIBE before the actual expiration */
  #define TIME_UAC_REFRESH	PJSIP_EVSUB_TIME_UAC_REFRESH
  
  /* Time to wait for the final NOTIFY after sending unsubscription */
  #define TIME_UAC_TERMINATE	PJSIP_EVSUB_TIME_UAC_TERMINATE
  
  /* If client responds NOTIFY with non-2xx final response (such as 401),
   * wait for this seconds for further NOTIFY, otherwise client will
   * unsubscribe
   */
  #define TIME_UAC_WAIT_NOTIFY	PJSIP_EVSUB_TIME_UAC_WAIT_NOTIFY
  
  
  /*
   * Timer id
   */
  enum timer_id
  {
      /* No timer. */
      TIMER_TYPE_NONE,
  
      /* Time to refresh client subscription. 
       * The action is to call on_client_refresh() callback.
       */
      TIMER_TYPE_UAC_REFRESH,
  
      /* UAS timeout after to subscription refresh. 
       * The action is to call on_server_timeout() callback.
       */
      TIMER_TYPE_UAS_TIMEOUT,
  
      /* UAC waiting for final NOTIFY after unsubscribing 
       * The action is to terminate.
       */
      TIMER_TYPE_UAC_TERMINATE,
  
      /* UAC waiting for further NOTIFY after sending non-2xx response to 
       * NOTIFY. The action is to unsubscribe.
       */
      TIMER_TYPE_UAC_WAIT_NOTIFY,
  
      /* Max nb of timer types. */
      TIMER_TYPE_MAX
  };
  
  static const char *timer_names[] = 
  {
      "None",
      "UAC_REFRESH",
      "UAS_TIMEOUT",
      "UAC_TERMINATE",
      "UAC_WAIT_NOTIFY",
      "INVALID_TIMER"
  };
  
  /*
   * Definition of event package.
   */
  struct evpkg
  {
      PJ_DECL_LIST_MEMBER(struct evpkg);
  
      pj_str_t		 pkg_name;
      pjsip_module	*pkg_mod;
      unsigned		 pkg_expires;
      pjsip_accept_hdr	*pkg_accept;
  };
  
  
  /*
   * Event subscription module (mod-evsub).
   */
  static struct mod_evsub
  {
      pjsip_module	     mod;
      pj_pool_t		    *pool;
      pjsip_endpoint	    *endpt;
      struct evpkg	     pkg_list;
      pjsip_allow_events_hdr  *allow_events_hdr;
  
  } mod_evsub = 
  {
      {
  	NULL, NULL,			    /* prev, next.		*/
  	{ "mod-evsub", 9 },		    /* Name.			*/
  	-1,				    /* Id			*/
  	PJSIP_MOD_PRIORITY_DIALOG_USAGE,    /* Priority			*/
  	NULL,				    /* load()			*/
  	NULL,				    /* start()			*/
  	NULL,				    /* stop()			*/
  	&mod_evsub_unload,		    /* unload()			*/
  	NULL,				    /* on_rx_request()		*/
  	NULL,				    /* on_rx_response()		*/
  	NULL,				    /* on_tx_request.		*/
  	NULL,				    /* on_tx_response()		*/
  	&mod_evsub_on_tsx_state,	    /* on_tsx_state()		*/
      }
  };
  
  
  /*
   * Event subscription session.
   */
  struct pjsip_evsub
  {
      char		  obj_name[PJ_MAX_OBJ_NAME]; /**< Name.		    */
      pj_pool_t		 *pool;		/**< Pool.			    */
      pjsip_endpoint	 *endpt;	/**< Endpoint instance.		    */
      pjsip_dialog	 *dlg;		/**< Underlying dialog.		    */
      struct evpkg	 *pkg;		/**< The event package.		    */
      unsigned		  option;	/**< Options.			    */
      pjsip_evsub_user	  user;		/**< Callback.			    */
      pj_bool_t		  call_cb;	/**< Notify callback?		    */
      pjsip_role_e	  role;		/**< UAC=subscriber, UAS=notifier   */
      pjsip_evsub_state	  state;	/**< Subscription state.	    */
      pj_str_t		  state_str;	/**< String describing the state.   */
      pjsip_evsub_state	  dst_state;	/**< Pending state to be set.	    */
      pj_str_t		  dst_state_str;/**< Pending state to be set.	    */
      pj_str_t		  term_reason;	/**< Termination reason.	    */
      pjsip_method	  method;	/**< Method that established subscr.*/
      pjsip_event_hdr	 *event;	/**< Event description.		    */
      pjsip_expires_hdr	 *expires;	/**< Expires header		    */
      pjsip_accept_hdr	 *accept;	/**< Local Accept header.	    */
      pjsip_hdr             sub_hdr_list; /**< User-defined header.           */
  
      pj_time_val		  refresh_time;	/**< Time to refresh.		    */
      pj_timer_entry	  timer;	/**< Internal timer.		    */
      int			  pending_tsx;	/**< Number of pending transactions.*/
      pjsip_transaction	 *pending_sub;	/**< Pending UAC SUBSCRIBE tsx.	    */
      pj_timer_entry	 *pending_sub_timer; /**< Stop pending sub timer.   */
      pj_grp_lock_t	 *grp_lock;	/* Session group lock	    */
  
      void		 *mod_data[PJSIP_MAX_MODULE];	/**< Module data.   */
  };
  
  
  /*
   * This is the structure that will be "attached" to dialog.
   * The purpose is to allow multiple subscriptions inside a dialog.
   */
  struct dlgsub
  {
      PJ_DECL_LIST_MEMBER(struct dlgsub);
      pjsip_evsub *sub;
  };
  
  
  /* Static vars. */
  static const pj_str_t STR_EVENT	       = { "Event", 5 };
  static const pj_str_t STR_EVENT_S      = { "o", 1 };
  static const pj_str_t STR_SUB_STATE    = { "Subscription-State", 18 };
  static const pj_str_t STR_TERMINATED   = { "terminated", 10 };
  static const pj_str_t STR_ACTIVE       = { "active", 6 };
  static const pj_str_t STR_PENDING      = { "pending", 7 };
  static const pj_str_t STR_TIMEOUT      = { "timeout", 7};
  static const pj_str_t STR_RETRY_AFTER  = { "Retry-After", 11 };
  
  
  /*
   * On unload module.
   */
  static pj_status_t mod_evsub_unload(void)
  {
      pjsip_endpt_release_pool(mod_evsub.endpt, mod_evsub.pool);
      mod_evsub.pool = NULL;
  
      return PJ_SUCCESS;
  }
  
  /* Proto for pjsipsimple_strerror().
   * Defined in errno.c
   */
  PJ_DECL(pj_str_t) pjsipsimple_strerror( pj_status_t statcode, 
  				        char *buf, pj_size_t bufsize );
  
  /*
   * Init and register module.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt)
  {
      pj_status_t status;
      pj_str_t method_tags[] = {
  	{ "SUBSCRIBE", 9},
  	{ "NOTIFY", 6}
      };
  
      status = pj_register_strerror(PJSIP_SIMPLE_ERRNO_START,
  				  PJ_ERRNO_SPACE_SIZE,
  				  &pjsipsimple_strerror);
      pj_assert(status == PJ_SUCCESS);
  
      PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
      PJ_ASSERT_RETURN(mod_evsub.mod.id == -1, PJ_EINVALIDOP);
  
      /* Keep endpoint for future reference: */
      mod_evsub.endpt = endpt;
  
      /* Init event package list: */
      pj_list_init(&mod_evsub.pkg_list);
  
      /* Create pool: */
      mod_evsub.pool = pjsip_endpt_create_pool(endpt, "evsub", 512, 512);
      if (!mod_evsub.pool)
  	return PJ_ENOMEM;
  
      /* Register module: */
      status = pjsip_endpt_register_module(endpt, &mod_evsub.mod);
      if (status  != PJ_SUCCESS)
  	goto on_error;
   
      /* Create Allow-Events header: */
      mod_evsub.allow_events_hdr = pjsip_allow_events_hdr_create(mod_evsub.pool);
  
      /* Register SIP-event specific headers parser: */
      pjsip_evsub_init_parser();
  
      /* Register new methods SUBSCRIBE and NOTIFY in Allow-ed header */
      pjsip_endpt_add_capability(endpt, &mod_evsub.mod, PJSIP_H_ALLOW, NULL,
  			       2, method_tags);
  
      /* Done. */
      return PJ_SUCCESS;
  
  on_error:
      if (mod_evsub.pool) {
  	pjsip_endpt_release_pool(endpt, mod_evsub.pool);
  	mod_evsub.pool = NULL;
      }
      mod_evsub.endpt = NULL;
      return status;
  }
  
  
  /*
   * Get the instance of the module.
   */
  PJ_DEF(pjsip_module*) pjsip_evsub_instance(void)
  {
      PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, NULL);
  
      return &mod_evsub.mod;
  }
  
  
  /*
   * Get the event subscription instance in the transaction.
   */
  PJ_DEF(pjsip_evsub*) pjsip_tsx_get_evsub(pjsip_transaction *tsx)
  {
      return (pjsip_evsub*) tsx->mod_data[mod_evsub.mod.id];
  }
  
  
  /*
   * Set event subscription's module data.
   */
  PJ_DEF(void) pjsip_evsub_set_mod_data( pjsip_evsub *sub, unsigned mod_id,
  				       void *data )
  {
      PJ_ASSERT_ON_FAIL(mod_id < PJSIP_MAX_MODULE, return);
      sub->mod_data[mod_id] = data;
  }
  
  
  /*
   * Get event subscription's module data.
   */
  PJ_DEF(void*) pjsip_evsub_get_mod_data( pjsip_evsub *sub, unsigned mod_id )
  {
      PJ_ASSERT_RETURN(mod_id < PJSIP_MAX_MODULE, NULL);
      return sub->mod_data[mod_id];
  }
  
  
  /*
   * Find registered event package with matching name.
   */
  static struct evpkg* find_pkg(const pj_str_t *event_name)
  {
      struct evpkg *pkg;
  
      pkg = mod_evsub.pkg_list.next;
      while (pkg != &mod_evsub.pkg_list) {
  
  	if (pj_stricmp(&pkg->pkg_name, event_name) == 0) {
  	    return pkg;
  	}
  
  	pkg = pkg->next;
      }
  
      return NULL;
  }
  
  /*
   * Register an event package
   */
  PJ_DEF(pj_status_t) pjsip_evsub_register_pkg( pjsip_module *pkg_mod,
  					      const pj_str_t *event_name,
  					      unsigned expires,
  					      unsigned accept_cnt,
  					      const pj_str_t accept[])
  {
      struct evpkg *pkg;
      unsigned i;
  
      PJ_ASSERT_RETURN(pkg_mod && event_name, PJ_EINVAL);
      
      /* Make sure accept_cnt < PJ_ARRAY_SIZE(pkg->pkg_accept->values) */
      PJ_ASSERT_RETURN(accept_cnt <= PJSIP_GENERIC_ARRAY_MAX_COUNT, 
  		     PJ_ETOOMANY);
  
      /* Make sure evsub module has been initialized */
      PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, PJ_EINVALIDOP);
  
      /* Make sure no module with the specified name already registered: */
  
      PJ_ASSERT_RETURN(find_pkg(event_name) == NULL, PJSIP_SIMPLE_EPKGEXISTS);
  
  
      /* Create new event package: */
  
      pkg = PJ_POOL_ALLOC_T(mod_evsub.pool, struct evpkg);
      pkg->pkg_mod = pkg_mod;
      pkg->pkg_expires = expires;
      pj_strdup(mod_evsub.pool, &pkg->pkg_name, event_name);
  
      pkg->pkg_accept = pjsip_accept_hdr_create(mod_evsub.pool);
      pkg->pkg_accept->count = accept_cnt;
      for (i=0; i<accept_cnt; ++i) {
  	pj_strdup(mod_evsub.pool, &pkg->pkg_accept->values[i], &accept[i]);
      }
  
      /* Add to package list: */
  
      pj_list_push_back(&mod_evsub.pkg_list, pkg);
  
      /* Add to Allow-Events header: */
  
      if (mod_evsub.allow_events_hdr->count !=
  	PJ_ARRAY_SIZE(mod_evsub.allow_events_hdr->values))
      {
  	mod_evsub.allow_events_hdr->values[mod_evsub.allow_events_hdr->count] =
  	    pkg->pkg_name;
  	++mod_evsub.allow_events_hdr->count;
      }
      
      /* Add to endpoint's Accept header */
      pjsip_endpt_add_capability(mod_evsub.endpt, &mod_evsub.mod,
  			       PJSIP_H_ACCEPT, NULL,
  			       pkg->pkg_accept->count,
  			       pkg->pkg_accept->values);
  
  
      /* Done */
  
      PJ_LOG(5,(THIS_FILE, "Event pkg \"%.*s\" registered by %.*s",
  	      (int)event_name->slen, event_name->ptr,
  	      (int)pkg_mod->name.slen, pkg_mod->name.ptr));
  
      return PJ_SUCCESS;
  }
  
  
  /*
   * Retrieve Allow-Events header
   */
  PJ_DEF(const pjsip_hdr*) pjsip_evsub_get_allow_events_hdr(pjsip_module *m)
  {
      struct mod_evsub *mod;
  
      if (m == NULL)
  	m = pjsip_evsub_instance();
  
      mod = (struct mod_evsub*)m;
  
      return (pjsip_hdr*) mod->allow_events_hdr;
  }
  
  
  /*
   * Update expiration time.
   */
  static void update_expires( pjsip_evsub *sub, pj_uint32_t interval )
  {
      pj_gettimeofday(&sub->refresh_time);
      sub->refresh_time.sec += interval;
  }
  
  
  /* 
   * Schedule timer.
   */
  static void set_timer( pjsip_evsub *sub, int timer_id,
  		       pj_int32_t seconds)
  {
      if (sub->timer.id != TIMER_TYPE_NONE) {
  	PJ_LOG(5,(sub->obj_name, "%s %s timer", 
  		  (timer_id==sub->timer.id ? "Updating" : "Cancelling"),
  		  timer_names[sub->timer.id]));
  	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
  	sub->timer.id = TIMER_TYPE_NONE;
      }
  
      if (timer_id != TIMER_TYPE_NONE && seconds > 0) {
  	pj_time_val timeout;
  
  	PJ_ASSERT_ON_FAIL(timer_id>TIMER_TYPE_NONE && timer_id<TIMER_TYPE_MAX,
  			  return);
  
  	timeout.sec = seconds;
  	timeout.msec = 0;
  
  	pj_timer_heap_schedule_w_grp_lock(
  			    pjsip_endpt_get_timer_heap(sub->endpt),
  			    &sub->timer, &timeout, timer_id, sub->grp_lock);
  
  	PJ_LOG(5,(sub->obj_name, "Timer %s scheduled in %d seconds", 
  		  timer_names[sub->timer.id], timeout.sec));
      }
  }
  
  
  /*
   * Set event subscription UAS timout.
   */
  PJ_DEF(void) pjsip_evsub_uas_set_timeout(pjsip_evsub *sub, pj_uint32_t seconds)
  {
      set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, (pj_int32_t)seconds);
  }
  
  
  /*
   * Destructor.
   */
  static void evsub_on_destroy(void *obj)
  {
      pjsip_evsub *sub = (pjsip_evsub*)obj;
  
      /* Decrement dialog's session */
      pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod);
  }
  
  
  /*
   * Destroy session.
   */
  static void evsub_destroy( pjsip_evsub *sub )
  {
      struct dlgsub *dlgsub_head, *dlgsub;
  
      PJ_LOG(4,(sub->obj_name, "Subscription destroyed"));
  
      /* Kill timer */
      set_timer(sub, TIMER_TYPE_NONE, 0);
  
      /* Kill timer for stopping pending sub (see ticket #1807) */
      if (sub->pending_sub_timer && sub->pending_sub_timer->id == 1) {
  	pjsip_endpt_cancel_timer(sub->endpt, sub->pending_sub_timer);
  	sub->pending_sub_timer->id = 0;
  	sub->pending_sub_timer = NULL;
      }
  
      /* Remove this session from dialog's list of subscription */
      dlgsub_head = (struct dlgsub *) sub->dlg->mod_data[mod_evsub.mod.id];
      dlgsub = dlgsub_head->next;
      while (dlgsub != dlgsub_head) {
  	
  	if (dlgsub->sub == sub) {
  	    pj_list_erase(dlgsub);
  	    break;
  	}
  
  	dlgsub = dlgsub->next;
      }
  
      pj_grp_lock_dec_ref(sub->grp_lock);
  }
  
  /*
   * Set subscription session state.
   */
  static void set_state( pjsip_evsub *sub, pjsip_evsub_state state,
  		       const pj_str_t *state_str, pjsip_event *event,
  		       const pj_str_t *reason)
  {
      pjsip_evsub_state prev_state = sub->state;
      pj_str_t old_state_str = sub->state_str;
      pjsip_event dummy_event;
  
      sub->state = state;
  
      if (state_str && state_str->slen)
  	pj_strdup_with_null(sub->pool, &sub->state_str, state_str);
      else
  	sub->state_str = evsub_state_names[state];
  
      if (reason && sub->term_reason.slen==0)
  	pj_strdup(sub->pool, &sub->term_reason, reason);
  
      PJ_LOG(4,(sub->obj_name, 
  	      "Subscription state changed %.*s --> %.*s",
  	      (int)old_state_str.slen,
  	      old_state_str.ptr,
  	      (int)sub->state_str.slen,
  	      sub->state_str.ptr));
      pj_log_push_indent();
  
      /* don't call the callback with NULL event, it may crash the app! */
      if (!event) {
  	PJSIP_EVENT_INIT_USER(dummy_event, 0, 0, 0, 0);
  	event = &dummy_event;
      }
  
      if (sub->user.on_evsub_state && sub->call_cb)
  	(*sub->user.on_evsub_state)(sub, event);
  
      if (state == PJSIP_EVSUB_STATE_TERMINATED &&
  	prev_state != PJSIP_EVSUB_STATE_TERMINATED) 
      {
  	/* Kill any timer. */
  	set_timer(sub, TIMER_TYPE_NONE, 0);
  
  	if (sub->pending_tsx == 0) {
  	    evsub_destroy(sub);
  	}
      }
  
      pj_log_pop_indent();
  }
  
  
  /*
   * Timer callback.
   */
  static void on_timer( pj_timer_heap_t *timer_heap,
  		      struct pj_timer_entry *entry)
  {
      pjsip_evsub *sub;
      int timer_id;
  
      PJ_UNUSED_ARG(timer_heap);
  
      sub = (pjsip_evsub*) entry->user_data;
  
      pjsip_dlg_inc_lock(sub->dlg);
  
      /* If this timer entry has just been rescheduled or cancelled
       * while waiting for dialog mutex, just return (see #1885 scenario 1).
       */
      if (pj_timer_entry_running(entry) || entry->id == TIMER_TYPE_NONE) {
  	pjsip_dlg_dec_lock(sub->dlg);
  	return;
      }
  
      timer_id = entry->id;
      entry->id = TIMER_TYPE_NONE;
  
      switch (timer_id) {
  
      case TIMER_TYPE_UAC_REFRESH:
  	/* Time for UAC to refresh subscription */
  	if (sub->user.on_client_refresh && sub->call_cb) {
  	    (*sub->user.on_client_refresh)(sub);
  	} else {
  	    pjsip_tx_data *tdata;
  	    pj_status_t status;
  
  	    PJ_LOG(5,(sub->obj_name, "Refreshing subscription."));
  	    pj_log_push_indent();
  	    status = pjsip_evsub_initiate(sub, NULL, 
  					  sub->expires->ivalue,
  					  &tdata);
  	    if (status == PJ_SUCCESS)
  		pjsip_evsub_send_request(sub, tdata);
  
  	    pj_log_pop_indent();
  	}
  	break;
  
      case TIMER_TYPE_UAS_TIMEOUT:
  	/* Refresh from UAC has not been received */
  	if (sub->user.on_server_timeout && sub->call_cb) {
  	    (*sub->user.on_server_timeout)(sub);
  	} else {
  	    pjsip_tx_data *tdata;
  	    pj_status_t status;
  
  	    PJ_LOG(5,(sub->obj_name, "Timeout waiting for refresh. "
  				     "Sending NOTIFY to terminate."));
  	    pj_log_push_indent();
  	    status = pjsip_evsub_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, 
  					 NULL, &STR_TIMEOUT, &tdata);
  	    if (status == PJ_SUCCESS)
  		pjsip_evsub_send_request(sub, tdata);
  
  	    pj_log_pop_indent();
  	}
  	break;
  
      case TIMER_TYPE_UAC_TERMINATE:
  	{
  	    pj_str_t timeout = {"timeout", 7};
  
  	    PJ_LOG(5,(sub->obj_name, "Timeout waiting for final NOTIFY. "
  				     "Terminating.."));
  	    pj_log_push_indent();
  	    set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, 
  		      &timeout);
  	    pj_log_pop_indent();
  	}
  	break;
  
      case TIMER_TYPE_UAC_WAIT_NOTIFY:
  	{
  	    pjsip_tx_data *tdata;
  	    pj_status_t status;
  
  	    PJ_LOG(5,(sub->obj_name, 
  		     "Timeout waiting for subsequent NOTIFY (we did "
  		     "send non-2xx response for previous NOTIFY). "
  		     "Unsubscribing.."));
  	    pj_log_push_indent();
  	    status = pjsip_evsub_initiate( sub, NULL, 0, &tdata);
  	    if (status == PJ_SUCCESS)
  		pjsip_evsub_send_request(sub, tdata);
  
  	    pj_log_pop_indent();
  	}
  	break;
  
      default:
  	pj_assert(!"Invalid timer id");
      }
  
      pjsip_dlg_dec_lock(sub->dlg);
  }
  
  
  /*
   * Create subscription session, used for both client and notifier.
   */
  static pj_status_t evsub_create( pjsip_dialog *dlg,
  				 pjsip_role_e role,
  				 const pjsip_evsub_user *user_cb,
  				 const pj_str_t *event,
  				 unsigned option,
  				 pjsip_evsub **p_evsub )
  {
      pjsip_evsub *sub;
      struct evpkg *pkg;
      struct dlgsub *dlgsub_head, *dlgsub;
      pj_status_t status;
  
      /* Make sure there's package register for the event name: */
  
      pkg = find_pkg(event);
      if (pkg == NULL)
  	return PJSIP_SIMPLE_ENOPKG;
  
  
      /* Must lock dialog before using pool etc. */
      pjsip_dlg_inc_lock(dlg);
  
      /* Init attributes: */
  
      sub = PJ_POOL_ZALLOC_T(dlg->pool, struct pjsip_evsub);
      sub->pool = dlg->pool;
      sub->endpt = dlg->endpt;
      sub->dlg = dlg;
      sub->pkg = pkg;
      sub->role = role;
      sub->call_cb = PJ_TRUE;
      sub->option = option;
      sub->state = PJSIP_EVSUB_STATE_NULL;
      sub->state_str = evsub_state_names[sub->state];
      sub->expires = pjsip_expires_hdr_create(sub->pool, pkg->pkg_expires);
      sub->accept = (pjsip_accept_hdr*) 
      		  pjsip_hdr_clone(sub->pool, pkg->pkg_accept);
      pj_list_init(&sub->sub_hdr_list);
  
      sub->timer.user_data = sub;
      sub->timer.cb = &on_timer;
  
      /* Set name. */
      pj_ansi_snprintf(sub->obj_name, PJ_ARRAY_SIZE(sub->obj_name),
  		     "evsub%p", sub);
  
  
      /* Copy callback, if any: */
      if (user_cb)
  	pj_memcpy(&sub->user, user_cb, sizeof(pjsip_evsub_user));
  
  
      /* Create Event header: */
      sub->event = pjsip_event_hdr_create(sub->pool);
      pj_strdup(sub->pool, &sub->event->event_type, event);
  
  
      /* Check if another subscription has been registered to the dialog. In
       * that case, just add ourselves to the subscription list, otherwise
       * create and register a new subscription list.
       */
      if (pjsip_dlg_has_usage(dlg, &mod_evsub.mod)) {
  	dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id];
  	dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
  	dlgsub->sub = sub;
  	pj_list_push_back(dlgsub_head, dlgsub);
      } else {
  	dlgsub_head = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
  	dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
  	dlgsub->sub = sub;
  
  	pj_list_init(dlgsub_head);
  	pj_list_push_back(dlgsub_head, dlgsub);
  
  
  	/* Register as dialog usage: */
  
  	status = pjsip_dlg_add_usage(dlg, &mod_evsub.mod, dlgsub_head);
  	if (status != PJ_SUCCESS) {
  	    pjsip_dlg_dec_lock(dlg);
  	    return status;
  	}
      }
  
      PJ_LOG(5,(sub->obj_name, "%s subscription created, using dialog %s",
  	      (role==PJSIP_ROLE_UAC ? "UAC" : "UAS"),
  	      dlg->obj_name));
  
      *p_evsub = sub;
      pjsip_dlg_dec_lock(dlg);
  
      return PJ_SUCCESS;
  }
  
  /*
   * Increment the event subscription's group lock.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_add_ref(pjsip_evsub *sub)
  {
      return pj_grp_lock_add_ref(sub->grp_lock);
  }
  
  /*
   * Decrement the event subscription's group lock.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_dec_ref(pjsip_evsub *sub)
  {
      return pj_grp_lock_dec_ref(sub->grp_lock);
  }
  
  /*
   * Create client subscription session.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
  					    const pjsip_evsub_user *user_cb,
  					    const pj_str_t *event,
  					    unsigned option,
  					    pjsip_evsub **p_evsub)
  {
      pjsip_evsub *sub;
      pj_status_t status;
  
      PJ_ASSERT_RETURN(dlg && event && p_evsub, PJ_EINVAL);
  
      pjsip_dlg_inc_lock(dlg);
      status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, option, &sub);
      if (status != PJ_SUCCESS)
  	goto on_return;
  
      /* Add unique Id to Event header, only when PJSIP_EVSUB_NO_EVENT_ID
       * is not specified.
       */
      if ((option & PJSIP_EVSUB_NO_EVENT_ID) == 0) {
  	pj_create_unique_string(sub->pool, &sub->event->id_param);
      }
  
      /* Increment dlg session. */
      pjsip_dlg_inc_session(sub->dlg, &mod_evsub.mod);
  
      /* Init group lock */
      status = pj_grp_lock_create(dlg->pool, NULL, &sub->grp_lock);
      if (status != PJ_SUCCESS) {
  	pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod);
  	goto on_return;
      }
  
      pj_grp_lock_add_ref(sub->grp_lock);
      pj_grp_lock_add_handler(sub->grp_lock, dlg->pool, sub, &evsub_on_destroy);
  
      /* Done */
      *p_evsub = sub;
  
  on_return:
      pjsip_dlg_dec_lock(dlg);
      return status;
  }
  
  
  /*
   * Create server subscription session from incoming request.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg,
  					    const pjsip_evsub_user *user_cb,
  					    pjsip_rx_data *rdata,
  					    unsigned option,
  					    pjsip_evsub **p_evsub)
  {
      pjsip_evsub *sub;
      pjsip_transaction *tsx;
      pjsip_accept_hdr *accept_hdr;
      pjsip_event_hdr *event_hdr;
      pjsip_expires_hdr *expires_hdr;
      pj_status_t status;
  
      /* Check arguments: */
      PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
  
      /* MUST be request message: */
      PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
  		     PJSIP_ENOTREQUESTMSG);
  
      /* Transaction MUST have been created (in the dialog) */
      tsx = pjsip_rdata_get_tsx(rdata);
      PJ_ASSERT_RETURN(tsx != NULL, PJSIP_ENOTSX);
  
      /* No subscription must have been attached to transaction */
      PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] == NULL, 
  		     PJSIP_ETYPEEXISTS);
  
      /* Package MUST implement on_rx_refresh */
      PJ_ASSERT_RETURN(user_cb->on_rx_refresh, PJ_EINVALIDOP);
  
      /* Request MUST have "Event" header. We need the Event header to get 
       * the package name (don't want to add more arguments in the function).
       */
      event_hdr = (pjsip_event_hdr*) 
  		 pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &STR_EVENT,
  					     &STR_EVENT_S, NULL);
      if (event_hdr == NULL) {
  	return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
      }
  
      /* Start locking the mutex: */
  
      pjsip_dlg_inc_lock(dlg);
  
      /* Create the session: */
  
      status = evsub_create(dlg, PJSIP_UAS_ROLE, user_cb, 
  			  &event_hdr->event_type, option, &sub);
      if (status != PJ_SUCCESS)
  	goto on_return;
  
      /* Just duplicate Event header from the request */
      sub->event = (pjsip_event_hdr*) pjsip_hdr_clone(sub->pool, event_hdr);
  
      /* Set the method: */
      pjsip_method_copy(sub->pool, &sub->method, 
  		      &rdata->msg_info.msg->line.req.method);
  
      /* Update expiration time according to client request: */
  
      expires_hdr = (pjsip_expires_hdr*)
  	pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
      if (expires_hdr) {
  	sub->expires->ivalue = expires_hdr->ivalue;
      }
  
      /* Update time. */
      update_expires(sub, sub->expires->ivalue);
  
      /* Update Accept header: */
  
      accept_hdr = (pjsip_accept_hdr*)
  	pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
      if (accept_hdr)
  	sub->accept = (pjsip_accept_hdr*)pjsip_hdr_clone(sub->pool,accept_hdr);
  
      /* Increment dlg session. */
      pjsip_dlg_inc_session(dlg, &mod_evsub.mod);
  
      /* Init group lock */
      status = pj_grp_lock_create(dlg->pool, NULL, &sub->grp_lock);
      if (status != PJ_SUCCESS) {
  	pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod);
  	goto on_return;
      }
  
      pj_grp_lock_add_ref(sub->grp_lock);
      pj_grp_lock_add_handler(sub->grp_lock, dlg->pool, sub, &evsub_on_destroy);
  
      /* We can start the session: */
  
      sub->pending_tsx++;
      tsx->mod_data[mod_evsub.mod.id] = sub;
  
      /* Done. */
      *p_evsub = sub;
  
  
  on_return:
      pjsip_dlg_dec_lock(dlg);
      return status;
  }
  
  
  /*
   * Forcefully destroy subscription.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_terminate( pjsip_evsub *sub,
  					   pj_bool_t notify )
  {
      PJ_ASSERT_RETURN(sub, PJ_EINVAL);
  
      pjsip_dlg_inc_lock(sub->dlg);
  
      /* I think it's pretty safe to disable this check.
       
      if (sub->pending_tsx) {
  	pj_assert(!"Unable to terminate when there's pending tsx");
  	pjsip_dlg_dec_lock(sub->dlg);
  	return PJ_EINVALIDOP;
      }
      */
  
      sub->call_cb = notify;
      set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, NULL);
  
      pjsip_dlg_dec_lock(sub->dlg);
      return PJ_SUCCESS;
  }
  
  /*
   * Get subscription state.
   */
  PJ_DEF(pjsip_evsub_state) pjsip_evsub_get_state(pjsip_evsub *sub)
  {
      return sub->state;
  }
  
  /*
   * Get state name.
   */
  PJ_DEF(const char*) pjsip_evsub_get_state_name(pjsip_evsub *sub)
  {
      return sub->state_str.ptr;
  }
  
  /*
   * Get termination reason.
   */
  PJ_DEF(const pj_str_t*) pjsip_evsub_get_termination_reason(pjsip_evsub *sub)
  {
      return &sub->term_reason;
  }
  
  /*
   * Initiate client subscription
   */
  PJ_DEF(pj_status_t) pjsip_evsub_initiate( pjsip_evsub *sub,
  					  const pjsip_method *method,
  					  pj_int32_t expires,
  					  pjsip_tx_data **p_tdata)
  {
      pjsip_tx_data *tdata;
      pj_status_t status;
  
      PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL);
  
      /* Use SUBSCRIBE if method is not specified */
      if (method == NULL)
  	method = &pjsip_subscribe_method;
  
      pjsip_dlg_inc_lock(sub->dlg);
  
      /* Update method: */
      if (sub->state == PJSIP_EVSUB_STATE_NULL)
  	pjsip_method_copy(sub->pool, &sub->method, method);
  
      status = pjsip_dlg_create_request( sub->dlg, method, -1, &tdata);
      if (status != PJ_SUCCESS)
  	goto on_return;
  
  
      /* Add Event header: */
      pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
  		       pjsip_hdr_shallow_clone(tdata->pool, sub->event));
  
      /* Update and add expires header: */
      if (expires >= 0)
  	sub->expires->ivalue = expires;
      pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
  		       pjsip_hdr_shallow_clone(tdata->pool, sub->expires));
  
      /* Add Supported header (it's optional in RFC 3265, but some event package
       * RFC may bring this requirement to SHOULD strength - e.g. RFC 5373)
       */
      {
         const pjsip_hdr *hdr = pjsip_endpt_get_capability(sub->endpt,
  						         PJSIP_H_SUPPORTED,
  						         NULL);
         if (hdr) {
  	   pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
  			     pjsip_hdr_shallow_clone(tdata->pool, hdr));
         }
      }
  
      /* Add Accept header: */
      pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
  		       pjsip_hdr_shallow_clone(tdata->pool, sub->accept));
      
  
      /* Add Allow-Events header: */
      pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
  		       pjsip_hdr_shallow_clone(tdata->pool, 
  					       mod_evsub.allow_events_hdr));
  
  
      /* Add custom headers */
      {
  	const pjsip_hdr *hdr = sub->sub_hdr_list.next;
  	while (hdr != &sub->sub_hdr_list) {
  	    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
  			       pjsip_hdr_shallow_clone(tdata->pool, hdr));
  	    hdr = hdr->next;
  	}
      }
  
  
      *p_tdata = tdata;
  
  
  on_return:
  
      pjsip_dlg_dec_lock(sub->dlg);
      return status;
  }
  
  
  /*
   * Add custom headers.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_add_header( pjsip_evsub *sub,
  					    const pjsip_hdr *hdr_list )
  {
      const pjsip_hdr *hdr;
  
      PJ_ASSERT_RETURN(sub && hdr_list, PJ_EINVAL);
  
      hdr = hdr_list->next;
      while (hdr != hdr_list) {
          pj_list_push_back(&sub->sub_hdr_list, (pjsip_hdr*)
  		          pjsip_hdr_clone(sub->pool, hdr));
  	hdr = hdr->next;
      }
  
      return PJ_SUCCESS;
  }
  
  
  /*
   * Accept incoming subscription request.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_accept( pjsip_evsub *sub,
  					pjsip_rx_data *rdata,
  				        int st_code,
  					const pjsip_hdr *hdr_list )
  {
      pjsip_tx_data *tdata;
      pjsip_transaction *tsx;
      pj_status_t status;
  
      /* Check arguments */
      PJ_ASSERT_RETURN(sub && rdata, PJ_EINVAL);
  
      /* Can only be for server subscription: */
      PJ_ASSERT_RETURN(sub->role == PJSIP_ROLE_UAS, PJ_EINVALIDOP);
  
      /* Only expect 2xx status code (for now) */
      PJ_ASSERT_RETURN(st_code/100 == 2, PJ_EINVALIDOP);
  
      /* Subscription MUST have been attached to the transaction.
       * Initial subscription request will be attached on evsub_create_uas(),
       * while subsequent requests will be attached in tsx_state()
       */
      tsx = pjsip_rdata_get_tsx(rdata);
      PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] != NULL,
  		     PJ_EINVALIDOP);
  
      /* Lock dialog */
      pjsip_dlg_inc_lock(sub->dlg);
  
      /* Create response: */
      status = pjsip_dlg_create_response( sub->dlg, rdata, st_code, NULL, 
  					&tdata);
      if (status != PJ_SUCCESS)
  	goto on_return;
  
  
      /* Add expires header: */
      pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
  		       pjsip_hdr_shallow_clone(tdata->pool, sub->expires));
  
      /* Add additional header, if any. */
      if (hdr_list) {
  	const pjsip_hdr *hdr = hdr_list->next;
  	while (hdr != hdr_list) {
  	    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
  			       pjsip_hdr_clone(tdata->pool, hdr));
  	    hdr = hdr->next;
  	}
      }
  
      /* Send the response: */
      status = pjsip_dlg_send_response( sub->dlg, tsx, tdata );
      if (status != PJ_SUCCESS)
  	goto on_return;
  
      /* Set UAS timeout timer, when status code is 2xx and state is not
       * terminated.
       */
      if (st_code/100 == 2 && sub->state != PJSIP_EVSUB_STATE_TERMINATED) {
  	PJ_LOG(5,(sub->obj_name, "UAS timeout in %d seconds",
  		  sub->expires->ivalue));
  	set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, sub->expires->ivalue);
      }
  
  on_return:
  
      pjsip_dlg_dec_lock(sub->dlg);
      return status;
  }
  
  
  /*
   * Create Subscription-State header based on current server subscription
   * state.
   */
  static pjsip_sub_state_hdr* sub_state_create( pj_pool_t *pool,
  					      pjsip_evsub *sub,
  					      pjsip_evsub_state state,
  					      const pj_str_t *state_str,
  					      const pj_str_t *reason )
  {
      pjsip_sub_state_hdr *sub_state;
      pj_time_val now, delay;
  
      /* Get the remaining time before refresh is required */
      pj_gettimeofday(&now);
      delay = sub->refresh_time;
      PJ_TIME_VAL_SUB(delay, now);
  
      /* Create the Subscription-State header */
      sub_state = pjsip_sub_state_hdr_create(pool);
  
      /* Fill up the header */
      switch (state) {
      case PJSIP_EVSUB_STATE_NULL:
      case PJSIP_EVSUB_STATE_SENT:
  	pj_assert(!"Invalid state!");
  	/* Treat as pending */
  
      case PJSIP_EVSUB_STATE_ACCEPTED:
      case PJSIP_EVSUB_STATE_PENDING:
  	sub_state->sub_state = STR_PENDING;
  	sub_state->expires_param = delay.sec;
  	break;
  
      case PJSIP_EVSUB_STATE_ACTIVE:
  	sub_state->sub_state = STR_ACTIVE;
  	sub_state->expires_param = delay.sec;
  	break;
  
      case PJSIP_EVSUB_STATE_TERMINATED:
  	sub_state->sub_state = STR_TERMINATED;
  	if (reason != NULL)
  	    pj_strdup(pool, &sub_state->reason_param, reason);
  	break;
  
      case PJSIP_EVSUB_STATE_UNKNOWN:
  	pj_assert(state_str != NULL);
  	pj_strdup(pool, &sub_state->sub_state, state_str);
  	break;
      }
      
      return sub_state;
  }
  
  /*
   * Create and send NOTIFY request.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_notify( pjsip_evsub *sub,
  					pjsip_evsub_state state,
  					const pj_str_t *state_str,
  					const pj_str_t *reason,
  					pjsip_tx_data **p_tdata)
  {
      pjsip_tx_data *tdata;
      pjsip_sub_state_hdr *sub_state;
      pj_status_t status;
  
      /* Check arguments. */
      PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL);
  
      /* Lock dialog. */
      pjsip_dlg_inc_lock(sub->dlg);
  
      /* Create NOTIFY request */
      status = pjsip_dlg_create_request( sub->dlg, pjsip_get_notify_method(), 
  				       -1, &tdata);
      if (status != PJ_SUCCESS)
  	goto on_return;
  
      /* Add Event header */
      pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
  		      pjsip_hdr_shallow_clone(tdata->pool, sub->event));
  
      /* Add Subscription-State header */
      sub_state = sub_state_create(tdata->pool, sub, state, state_str,
  				 reason);
      pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sub_state);
  
      /* Add Allow-Events header */
      pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
  		      pjsip_hdr_shallow_clone(tdata->pool, mod_evsub.allow_events_hdr));
  
      /* Add Authentication headers. */
      pjsip_auth_clt_init_req( &sub->dlg->auth_sess, tdata );
  
      /* Update reason */
      if (reason)
  	pj_strdup(sub->dlg->pool, &sub->term_reason, reason);
  
      /* Save destination state. */
      sub->dst_state = state;
      if (state_str)
  	pj_strdup(sub->pool, &sub->dst_state_str, state_str);
      else
  	sub->dst_state_str.slen = 0;
  
  
      *p_tdata = tdata;
  
  on_return:
      /* Unlock dialog */
      pjsip_dlg_dec_lock(sub->dlg);
      return status;
  }
  
  
  /*
   * Create NOTIFY to reflect current status.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_current_notify( pjsip_evsub *sub,
  						pjsip_tx_data **p_tdata )
  {
      return pjsip_evsub_notify( sub, sub->state, &sub->state_str, 
  			       NULL, p_tdata );
  }
  
  
  /*
   * Send request.
   */
  PJ_DEF(pj_status_t) pjsip_evsub_send_request( pjsip_evsub *sub,
  					      pjsip_tx_data *tdata)
  {
      pj_status_t status;
  
      /* Must be request message. */
      PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
  		     PJSIP_ENOTREQUESTMSG);
  
      /* Lock */
      pjsip_dlg_inc_lock(sub->dlg);
  
      /* Send the request. */
      status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL);
      if (status != PJ_SUCCESS)
  	goto on_return;
  
  
      /* Special case for NOTIFY:
       * The new state was set in pjsip_evsub_notify(), but we apply the
       * new state now, when the request was actually sent.
       */
      if (pjsip_method_cmp(&tdata->msg->line.req.method, 
  			 &pjsip_notify_method)==0) 
      {
  	PJ_ASSERT_ON_FAIL(  sub->dst_state!=PJSIP_EVSUB_STATE_NULL,
  			    {goto on_return;});
  
  	set_state(sub, sub->dst_state, 
  		  (sub->dst_state_str.slen ? &sub->dst_state_str : NULL), 
  		  NULL, NULL);
  
  	sub->dst_state = PJSIP_EVSUB_STATE_NULL;
  	sub->dst_state_str.slen = 0;
  
      }
  
  
  on_return:
      pjsip_dlg_dec_lock(sub->dlg);
      return status;
  }
  
  
  /* Callback to be called to terminate transaction. */
  static void terminate_timer_cb(pj_timer_heap_t *timer_heap,
  			       struct pj_timer_entry *entry)
  {
      pj_str_t *key;
      pjsip_transaction *tsx;
  
      PJ_UNUSED_ARG(timer_heap);
  
      /* Clear timer ID */
      entry->id = 0;
  
      key = (pj_str_t*)entry->user_data;
      tsx = pjsip_tsx_layer_find_tsx(key, PJ_FALSE);
      /* Chance of race condition here */
      if (tsx) {
  	pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_UPDATED);
      }
  }
  
  
  /*
   * Attach subscription session to newly created transaction, if appropriate.
   */
  static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx,
  				        pjsip_event *event)
  {
      /*
       * Newly created transaction will not have subscription session
       * attached to it. Find the subscription session from the dialog,
       * by matching the Event header.
       */
      pjsip_dialog *dlg;
      pjsip_event_hdr *event_hdr;
      pjsip_msg *msg;
      struct dlgsub *dlgsub_head, *dlgsub;
      pjsip_evsub *sub;
      
      dlg = pjsip_tsx_get_dlg(tsx);
      if (!dlg) {
  	pj_assert(!"Transaction should have a dialog instance!");
  	return NULL;
      }
  
  
      switch (event->body.tsx_state.type) {
      case PJSIP_EVENT_RX_MSG:
  	msg = event->body.tsx_state.src.rdata->msg_info.msg;
  	break;
      case PJSIP_EVENT_TX_MSG:
  	msg = event->body.tsx_state.src.tdata->msg;
  	break;
      default:
  	if (tsx->role == PJSIP_ROLE_UAC)
  	    msg = tsx->last_tx->msg;
  	else
  	    msg = NULL;
  	break;
      }
      
      if (!msg) {
  	//Note:
  	// this transaction can be other transaction in the dialog.
  	// The assertion below probably only valid for dialog that
  	// only has one event subscription usage.
  	//pj_assert(!"First transaction event is not TX or RX!");
  	return NULL;
      }
  
      event_hdr = (pjsip_event_hdr*)
      		pjsip_msg_find_hdr_by_names(msg, &STR_EVENT, 
  					    &STR_EVENT_S, NULL);
      if (!event_hdr) {
  	/* Not subscription related message */
  	return NULL;
      }
  
      /* Find the subscription in the dialog, based on the content
       * of Event header: 
       */
  
      dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id];
      if (dlgsub_head == NULL) {
  	dlgsub_head = PJ_POOL_ALLOC_T(dlg->pool, struct dlgsub);
  	pj_list_init(dlgsub_head);
  	dlg->mod_data[mod_evsub.mod.id] = dlgsub_head;
      }
      dlgsub = dlgsub_head->next;
  
      while (dlgsub != dlgsub_head) {
  
  	if (pj_stricmp(&dlgsub->sub->event->event_type, 
  		       &event_hdr->event_type)==0)
  	{
  	    /* Event type matched. 
  	     * Check if event ID matched too.
  	     */
  	    if (pj_strcmp(&dlgsub->sub->event->id_param, 
  			  &event_hdr->id_param)==0)
  	    {
  		/* Skip this subscription if it has no event ID and has been
  		 * terminated (see ticket #1647).
  		 */
  		if ((dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID) &&
  		    (pjsip_evsub_get_state(dlgsub->sub)==
  					PJSIP_EVSUB_STATE_TERMINATED))
  		{
  		    dlgsub = dlgsub->next;
      		    continue;
  		} else {
  		    break;
  		}
  
  	    }
  	    /*
  	     * Otherwise if it is an UAC subscription, AND
  	     * PJSIP_EVSUB_NO_EVENT_ID flag is set, AND
  	     * the session's event id is NULL, AND
  	     * the incoming request is NOTIFY with event ID, then
  	     * we consider it as a match, and update the
  	     * session's event id.
  	     */
  	    else if (dlgsub->sub->role == PJSIP_ROLE_UAC &&
  		     (dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID)!=0 &&
  		     dlgsub->sub->event->id_param.slen==0 &&
  		     !pjsip_method_cmp(&tsx->method, &pjsip_notify_method))
  	    {
  		/* Update session's event id. */
  		pj_strdup(dlgsub->sub->pool, 
  			  &dlgsub->sub->event->id_param,
  			  &event_hdr->id_param);
  
  		break;
  	    }
  	}
  	
  
  
  	dlgsub = dlgsub->next;
      }
  
      /* Note: 
       *  the second condition is for http://trac.pjsip.org/repos/ticket/911 
       */
      if (dlgsub == dlgsub_head ||
  	(dlgsub->sub && 
  	    pjsip_evsub_get_state(dlgsub->sub)==PJSIP_EVSUB_STATE_TERMINATED))
      {
  	const char *reason_msg = 
  	    (dlgsub == dlgsub_head ? "Subscription Does Not Exist" :
  	     "Subscription already terminated");
  
  	/* This could be incoming request to create new subscription */
  	PJ_LOG(4,(THIS_FILE, 
  		  "%s for %.*s, event=%.*s;id=%.*s",
  		  reason_msg,
  		  (int)tsx->method.name.slen,
  		  tsx->method.name.ptr,
  		  (int)event_hdr->event_type.slen,
  		  event_hdr->event_type.ptr,
  		  (int)event_hdr->id_param.slen,
  		  event_hdr->id_param.ptr));
  
  	/* If this is an incoming NOTIFY, reject with 481 */
  	if (tsx->state == PJSIP_TSX_STATE_TRYING &&
  	    pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0)
  	{
  	    pj_str_t reason;
  	    pjsip_tx_data *tdata;
  	    pj_status_t status;
  
  	    pj_cstr(&reason, reason_msg);
  	    status = pjsip_dlg_create_response(dlg, 
  					       event->body.tsx_state.src.rdata, 
  					       481, &reason, 
  					       &tdata);
  	    if (status == PJ_SUCCESS) {
  		status = pjsip_dlg_send_response(dlg, tsx, tdata);
  	    }
  	}
  	return NULL;
      }
  
      /* Found! */
      sub = dlgsub->sub;
  
      /* Attach session to the transaction */
      tsx->mod_data[mod_evsub.mod.id] = sub;
      sub->pending_tsx++;
  
      /* Special case for outgoing/UAC SUBSCRIBE/REFER transaction. 
       * We can only have one pending UAC SUBSCRIBE/REFER, so if another 
       * transaction is started while previous one still alive, terminate
       * the older one.
       *
       * Sample scenario:
       *	- subscribe sent to destination that doesn't exist, transaction
       *	  is still retransmitting request, then unsubscribe is sent.
       */
      if (tsx->role == PJSIP_ROLE_UAC &&
  	tsx->state == PJSIP_TSX_STATE_CALLING &&
  	(pjsip_method_cmp(&tsx->method, &sub->method) == 0  ||
  	 pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0))
      {
  
  	if (sub->pending_sub && 
  	    sub->pending_sub->state < PJSIP_TSX_STATE_COMPLETED) 
  	{
  	    pj_timer_entry *timer;
  	    pj_str_t *key;
  	    pj_time_val timeout = {0, 0};
  
  	    PJ_LOG(4,(sub->obj_name, 
  		      "Cancelling pending subscription request"));
  
  	    /* By convention, we use 490 (Request Updated) status code.
  	     * When transaction handler (below) see this status code, it
  	     * will ignore the transaction.
  	     */
  	    /* This unfortunately may cause deadlock, because at the moment
  	     * we are holding dialog's mutex. If a response to this
  	     * transaction is in progress in another thread, that thread
  	     * will deadlock when trying to acquire dialog mutex, because
  	     * it is holding the transaction mutex.
  	     *
  	     * So the solution is to register timer to kill this transaction.
  	     */
  	    //pjsip_tsx_terminate(sub->pending_sub, PJSIP_SC_REQUEST_UPDATED);
  	    timer = PJ_POOL_ZALLOC_T(dlg->pool, pj_timer_entry);
  	    key = PJ_POOL_ALLOC_T(dlg->pool, pj_str_t);
  	    pj_strdup(dlg->pool, key, &sub->pending_sub->transaction_key);
  	    timer->cb = &terminate_timer_cb;
  	    timer->user_data = key;
  	    timer->id = 1;
  	    sub->pending_sub_timer = timer;
  
  	    pjsip_endpt_schedule_timer(dlg->endpt, timer, &timeout);
  	}
  
  	sub->pending_sub = tsx;
  
      }
  
      return sub;
  }
  
  
  /*
   * Create response, adding custome headers and msg body.
   */
  static pj_status_t create_response( pjsip_evsub *sub,
  				    pjsip_rx_data *rdata,
  				    int st_code,
  				    const pj_str_t *st_text,
  				    const pjsip_hdr *res_hdr,
  				    const pjsip_msg_body *body,
  				    pjsip_tx_data **p_tdata)
  {
      pjsip_tx_data *tdata;
      pjsip_hdr *hdr;
      pj_status_t status;
  
      status = pjsip_dlg_create_response(sub->dlg, rdata,
  				       st_code, st_text, &tdata);
      if (status != PJ_SUCCESS)
  	return status;
  
      *p_tdata = tdata;
  
      /* Add response headers. */
      hdr = res_hdr->next;
      while (hdr != res_hdr) {
  	pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
  			   pjsip_hdr_clone(tdata->pool, hdr));
  	hdr = hdr->next;
      }
  
      /* Add msg body, if any */
      if (body) {
  	tdata->msg->body = pjsip_msg_body_clone(tdata->pool, body);
  	if (tdata->msg->body == NULL) {
  
  	    PJ_LOG(4,(THIS_FILE, "Error: unable to clone msg body"));
  
  	    /* Ignore */
  	    return PJ_SUCCESS;
  	}
      }
  
      return PJ_SUCCESS;
  }
  
  /*
   * Get subscription state from the value of Subscription-State header.
   */
  static void get_hdr_state( pjsip_sub_state_hdr *sub_state,
  			   pjsip_evsub_state *state,
  			   pj_str_t **state_str )
  {
      if (pj_stricmp(&sub_state->sub_state, &STR_TERMINATED)==0) {
  
  	*state = PJSIP_EVSUB_STATE_TERMINATED;
  	*state_str = NULL;
  
      } else if (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0) {
  
  	*state = PJSIP_EVSUB_STATE_ACTIVE;
  	*state_str = NULL;
  
      } else if (pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0) {
  
  	*state = PJSIP_EVSUB_STATE_PENDING;
  	*state_str = NULL;
  
      } else {
  
  	*state = PJSIP_EVSUB_STATE_UNKNOWN;
  	*state_str = &sub_state->sub_state;
  
      }
  }
  
  /*
   * Transaction event processing by UAC, after subscription is sent.
   */
  static void on_tsx_state_uac( pjsip_evsub *sub, pjsip_transaction *tsx,
  			      pjsip_event *event )
  {
  
      if (pjsip_method_cmp(&tsx->method, &sub->method)==0 || 
  	pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method)==0) 
      {
  
  	/* Received response to outgoing request that establishes/refresh
  	 * subscription. 
  	 */
  
  	/* First time initial request is sent. */
  	if (sub->state == PJSIP_EVSUB_STATE_NULL &&
  	    tsx->state == PJSIP_TSX_STATE_CALLING)
  	{
  	    set_state(sub, PJSIP_EVSUB_STATE_SENT, NULL, event, NULL);
  	    return;
  	}
  
  	/* Only interested in final response */
  	if (tsx->state != PJSIP_TSX_STATE_COMPLETED &&
  	    tsx->state != PJSIP_TSX_STATE_TERMINATED)
  	{
  	    return;
  	}
  
  	/* Clear pending subscription */
  	if (tsx == sub->pending_sub) {
  	    sub->pending_sub = NULL;
  	} else if (sub->pending_sub != NULL) {
  	    /* This SUBSCRIBE transaction has been "renewed" with another
  	     * SUBSCRIBE, so we can just ignore this. For example, user
  	     * sent SUBSCRIBE followed immediately with UN-SUBSCRIBE.
  	     */
  	    return;
  	}
  
  	/* Handle authentication. */
  	if (tsx->status_code==401 || tsx->status_code==407) {
  	    pjsip_tx_data *tdata;
  	    pj_status_t status;
  
  	    if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
  		/* Previously failed transaction has terminated */
  		return;
  	    }
  
  	    status = pjsip_auth_clt_reinit_req(&sub->dlg->auth_sess,
  					       event->body.tsx_state.src.rdata,
  					       tsx->last_tx, &tdata);
  	    if (status == PJ_SUCCESS) 
  		status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL);
  	    
  	    if (status != PJ_SUCCESS) {
  		/* Authentication failed! */
  		set_state(sub, PJSIP_EVSUB_STATE_TERMINATED,
  			  NULL, event, &tsx->status_text);
  		return;
  	    }
  
  	    return;
  	}
  
  	if (tsx->status_code/100 == 2) {
  
  	    /* Successfull SUBSCRIBE request! 
  	     * This could be:
  	     *	- response to initial SUBSCRIBE request
  	     *	- response to subsequent refresh
  	     *	- response to unsubscription
  	     */
  
  	    if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
  		/* Ignore; this transaction has been processed before */
  		return;
  	    }
  
  	    /* Update UAC refresh time, if response contains Expires header,
  	     * only when we're not unsubscribing. 
  	     */
  	    if (sub->expires->ivalue != 0) {
  		pjsip_msg *msg;
  		pjsip_expires_hdr *expires;
  
  		msg = event->body.tsx_state.src.rdata->msg_info.msg;
  		expires = (pjsip_expires_hdr*)
  			  pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
  		if (expires) {
  		    sub->expires->ivalue = expires->ivalue;
  		}
  	    }
  
  	    /* Update time */
  	    update_expires(sub, sub->expires->ivalue);
  
  	    /* Start UAC refresh timer, only when we're not unsubscribing */
  	    if (sub->expires->ivalue != 0) {
  		unsigned timeout = (sub->expires->ivalue > TIME_UAC_REFRESH) ?
  		    sub->expires->ivalue - TIME_UAC_REFRESH : sub->expires->ivalue;
  
  		/* Reduce timeout by about 1 - 10 secs (randomized) */
  		if (timeout > 10)
  		    timeout += -10 + (pj_rand() % 10);
  
  		PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", 
  			  timeout));
  		set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout);
  
  	    } else {
  		/* Otherwise set timer to terminate client subscription when
  		 * NOTIFY to end subscription is not received.
  		 */
  		set_timer(sub, TIMER_TYPE_UAC_TERMINATE, TIME_UAC_TERMINATE);
  	    }
  	    
  	    /* Set state, if necessary */
  	    pj_assert(sub->state != PJSIP_EVSUB_STATE_NULL);
  	    if (sub->state == PJSIP_EVSUB_STATE_SENT) {
  		set_state(sub, PJSIP_EVSUB_STATE_ACCEPTED, NULL, event, NULL);
  	    }
  
  	} else {
  
  	    /* Failed SUBSCRIBE request! 
  	     *
  	     * The RFC 3265 says that if outgoing SUBSCRIBE fails with status
  	     * other than 481, the subscription is still considered valid for
  	     * the duration of the last Expires.
  	     *
  	     * Since we send refresh about 5 seconds (TIME_UAC_REFRESH) before 
  	     * expiration, theoritically the expiration is still valid for the 
  	     * next 5 seconds even when we receive non-481 failed response.
  	     *
  	     * Ah, what the heck!
  	     *
  	     * Just terminate now!
  	     *
  	     */
  
  	    if (sub->state == PJSIP_EVSUB_STATE_TERMINATED) {
  		/* Ignore, has been handled before */
  		return;
  	    }
  
  	    /* Ignore 490 (Request Updated) status. 
  	     * This happens when application sends SUBSCRIBE/REFER while 
  	     * another one is still in progress.
  	     */
  	    if (tsx->status_code == PJSIP_SC_REQUEST_UPDATED) {
  		return;
  	    }
  
  	    /* Set state to TERMINATED */
  	    set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, 
  		      NULL, event, &tsx->status_text);
  
  	}
  
      } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) {
  
  	/* Incoming NOTIFY. 
  	 * This can be the result of:
  	 *  - Initial subscription response
  	 *  - UAS updating the resource info.
  	 *  - Unsubscription response.
  	 */
  	int st_code = 200;
  	pj_str_t *st_text = NULL;
  	pjsip_hdr res_hdr;
  	pjsip_msg_body *body = NULL;
  
  	pjsip_rx_data *rdata;
  	pjsip_msg *msg;
  	pjsip_sub_state_hdr *sub_state;
  
  	pjsip_evsub_state new_state;
  	pj_str_t *new_state_str;
  
  	pjsip_tx_data *tdata;
  	pj_status_t status;
  
  	/* Only want to handle initial NOTIFY receive event. */
  	if (tsx->state != PJSIP_TSX_STATE_TRYING)
  	    return;
  
  
  	rdata = event->body.tsx_state.src.rdata;
  	msg = rdata->msg_info.msg;
  
  	pj_list_init(&res_hdr);
  
  	/* Get subscription state header. */
  	sub_state = (pjsip_sub_state_hdr*)
  		    pjsip_msg_find_hdr_by_name(msg, &STR_SUB_STATE, NULL);
  	if (sub_state == NULL) {
  
  	    pjsip_warning_hdr *warn_hdr;
  	    pj_str_t warn_text = { "Missing Subscription-State header", 33};
  
  	    /* Bad request! Add warning header. */
  	    st_code = PJSIP_SC_BAD_REQUEST;
  	    warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399,
  					        pjsip_endpt_name(sub->endpt),
  						&warn_text);
  	    pj_list_push_back(&res_hdr, warn_hdr);
  	}
  
  	/* Call application registered callback to handle incoming NOTIFY,
  	 * if any.
  	 */
  	if (st_code==200 && sub->user.on_rx_notify && sub->call_cb) {
  	    (*sub->user.on_rx_notify)(sub, rdata, &st_code, &st_text, 
  				      &res_hdr, &body);
  
  	    /* Application MUST specify final response! */
  	    PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });
  
  	    /* Must be a valid status code */
  	    PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; });
  	}
  
  
  	/* If non-2xx should be returned, then send the response.
  	 * No need to update server subscription state.
  	 */
  	if (st_code >= 300) {
  	    status = create_response(sub, rdata, st_code, st_text, &res_hdr,
  				     body, &tdata);
  	    if (status == PJ_SUCCESS) {
  		status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
  	    }
  
  	    /* Start timer to terminate subscription, just in case server
  	     * is not able to generate NOTIFY to our response.
  	     */
  	    if (status == PJ_SUCCESS) {
  		unsigned timeout = TIME_UAC_WAIT_NOTIFY;
  		set_timer(sub, TIMER_TYPE_UAC_WAIT_NOTIFY, timeout);
  	    } else {
  		char errmsg[PJ_ERR_MSG_SIZE];
  		pj_str_t reason;
  
  		reason = pj_strerror(status, errmsg, sizeof(errmsg));
  		set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, 
  			  &reason);
  	    }
  
  	    return;
  	}
  
  	/* Update expiration from the value of expires param in
  	 * Subscription-State header, but ONLY when subscription state 
  	 * is "active" or "pending", AND the header contains expires param.
  	 */
  	if (sub->expires->ivalue != 0 &&
  	    sub_state->expires_param >= 0 &&
  	    (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0 ||
  	     pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0))
  	{
  	    int next_refresh = sub_state->expires_param;
  	    unsigned timeout;
  
  	    update_expires(sub, next_refresh);
  
  	    /* Start UAC refresh timer, only when we're not unsubscribing */
  	    timeout = (next_refresh > TIME_UAC_REFRESH) ?
  			next_refresh - TIME_UAC_REFRESH : next_refresh;
  
  	    PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", timeout));
  	    set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout);
  	}
  	
  	/* Find out the state */
  	get_hdr_state(sub_state, &new_state, &new_state_str);
  
  	/* Send response. */
  	status = create_response(sub, rdata, st_code, st_text, &res_hdr,
  				 body, &tdata);
  	if (status == PJ_SUCCESS)
  	    status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
  
  	/* Set the state */
  	if (status == PJ_SUCCESS) {
  	    set_state(sub, new_state, new_state_str, event, 
  		      &sub_state->reason_param);
  	} else {
  	    char errmsg[PJ_ERR_MSG_SIZE];
  	    pj_str_t reason;
  
  	    reason = pj_strerror(status, errmsg, sizeof(errmsg));
  	    set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event, 
  		      &reason);
  	}
  
  
      } else {
  
  	/*
  	 * Unexpected method!
  	 */
  	PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s",
  		 (int)tsx->method.name.slen, tsx->method.name.ptr));
      }
  }
  
  
  /*
   * Transaction event processing by UAS, after subscription is accepted.
   */
  static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx,
  			      pjsip_event *event)
  {
  
      if (pjsip_method_cmp(&tsx->method, &sub->method) == 0 ||
  	pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0) 
      {
  	
  	/*
  	 * Incoming request (e.g. SUBSCRIBE or REFER) to refresh subsciption.
  	 *
  	 */
  	pjsip_rx_data *rdata;
  	pjsip_event_hdr *event_hdr;
  	pjsip_expires_hdr *expires;
  	pjsip_msg *msg;
  	pjsip_tx_data *tdata;
  	int st_code = 200;
  	pj_str_t *st_text = NULL;
  	pjsip_hdr res_hdr;
  	pjsip_msg_body *body = NULL;
  	pjsip_evsub_state old_state;
  	pj_str_t old_state_str;
  	pj_str_t reason = { NULL, 0 };
  	pj_status_t status;
  
  
  	/* Only wants to handle the first event when the request is 
  	 * received.
  	 */
  	if (tsx->state != PJSIP_TSX_STATE_TRYING)
  	    return;
  
  	rdata = event->body.tsx_state.src.rdata;
  	msg = rdata->msg_info.msg;
  
  	/* Set expiration time based on client request (in Expires header),
  	 * or package default expiration time.
  	 */
  	event_hdr = (pjsip_event_hdr*)
  		    pjsip_msg_find_hdr_by_names(msg, &STR_EVENT, 
  					        &STR_EVENT, NULL);
  	expires = (pjsip_expires_hdr*)
  		  pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
  	if (event_hdr && expires) {
  	    struct evpkg *evpkg;
  
  	    evpkg = find_pkg(&event_hdr->event_type);
  	    if (evpkg) {
  		if (expires->ivalue < (pj_int32_t)evpkg->pkg_expires)
  		    sub->expires->ivalue = expires->ivalue;
  		else
  		    sub->expires->ivalue = evpkg->pkg_expires;
  	    }
  	}
  	
  	/* Update time (before calling on_rx_refresh, since application
  	 * will send NOTIFY.
  	 */
  	update_expires(sub, sub->expires->ivalue);
  
  
  	/* Save old state.
  	 * If application respond with non-2xx, revert to old state.
  	 */
  	old_state = sub->state;
  	old_state_str = sub->state_str;
  
  	if (sub->expires->ivalue == 0) {
  	    sub->state = PJSIP_EVSUB_STATE_TERMINATED;
  	    sub->state_str = evsub_state_names[sub->state];
  	} else  if (sub->state == PJSIP_EVSUB_STATE_NULL) {
  	    sub->state = PJSIP_EVSUB_STATE_ACCEPTED;
  	    sub->state_str = evsub_state_names[sub->state];
  	}
  
  	/* Call application's on_rx_refresh, just in case it wants to send
  	 * response other than 200 (OK)
  	 */
  	pj_list_init(&res_hdr);
  
  	if (sub->user.on_rx_refresh && sub->call_cb) {
  	    (*sub->user.on_rx_refresh)(sub, rdata, &st_code, &st_text, 
  				       &res_hdr, &body);
  	}
  
  	/* Application MUST specify final response! */
  	PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });
  
  	/* Must be a valid status code */
  	PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; });
  
  
  	/* Create and send response */
  	status = create_response(sub, rdata, st_code, st_text, &res_hdr,
  				 body, &tdata);
  	if (status == PJ_SUCCESS) {
  	    /* Add expires header: */
  	    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
  			       pjsip_hdr_shallow_clone(tdata->pool, 
  						       sub->expires));
  
  	    /* Send */
  	    status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
  	}
  
  	/* Update state or revert state */
  	if (st_code/100==2) {
  	    
  	    if (sub->expires->ivalue == 0) {
  		set_state(sub, sub->state, NULL, event, &reason);
  	    } else  if (sub->state == PJSIP_EVSUB_STATE_NULL) {
  		set_state(sub, sub->state, NULL, event, &reason);
  	    }
  
  	    /* Set UAS timeout timer, when state is not terminated. */
  	    if (sub->state != PJSIP_EVSUB_STATE_TERMINATED) {
  		PJ_LOG(5,(sub->obj_name, "UAS timeout in %d seconds",
  			  sub->expires->ivalue));
  		set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, 
  			  sub->expires->ivalue);
  	    }
  
  	}  else {
  	    sub->state = old_state;
  	    sub->state_str = old_state_str;
  	}
  
  
      } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0) {
  
  	/* Handle authentication */
  	if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
  	    (tsx->status_code==401 || tsx->status_code==407))
  	{	    
  	    pjsip_tx_data *tdata;
  	    pj_status_t status;
  	    pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
  
  	    status = pjsip_auth_clt_reinit_req(&sub->dlg->auth_sess, rdata,
  					       tsx->last_tx, &tdata);
  	    if (status == PJ_SUCCESS)
  		status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL);
  
  	    if (status != PJ_SUCCESS) {
  		/* Can't authenticate. Terminate session (?) */
  		set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL,
  			  &tsx->status_text);
  	    }
  	    return;
  
  	}
  
  	if (sub->state == PJSIP_EVSUB_STATE_TERMINATED)
  	    return;
  
  	/* NOTIFY failure check */
  	if (tsx->status_code/100 != 2) {
  	    pj_bool_t should_terminate_sub = PJ_FALSE;
  
  	    if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
  		if (tsx->status_code == 481) {
  		    should_terminate_sub = PJ_TRUE;
  		} else {
  		    pjsip_retry_after_hdr *retry_after;
  		    pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
  		    pjsip_msg *msg = rdata->msg_info.msg;		    
  
  		    retry_after = (pjsip_retry_after_hdr*)
  		       pjsip_msg_find_hdr_by_name(msg, &STR_RETRY_AFTER, NULL);
  
  		    if (!retry_after) {
  			should_terminate_sub = PJ_TRUE;
  		    }
  		}
  	    } else if (event->body.tsx_state.type == PJSIP_EVENT_TIMER) {
  		if (tsx->status_code == 408) {
  		    should_terminate_sub = PJ_TRUE;
  		}
  	    }
  
  	    /*
  	     * Terminate event usage if we receive non 2xx without retry_after
  	     * parameter, 481, 408 responses.
  	     */
  	    if (should_terminate_sub) {
  		set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event,
  			  &tsx->status_text);
  		return;
  	    }
  	}
      } else {
  
  	/*
  	 * Unexpected method!
  	 */
  	PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s",
  		 (int)tsx->method.name.slen, tsx->method.name.ptr));
      
      }
  }
  
  
  /*
   * Notification when transaction state has changed!
   */
  static void mod_evsub_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
  {
      pjsip_evsub *sub = pjsip_tsx_get_evsub(tsx);
  
      if (sub == NULL) {
  	sub = on_new_transaction(tsx, event);
  	if (sub == NULL)
  	    return;
      }
  
  
      /* Call on_tsx_state callback, if any. */
      if (sub->user.on_tsx_state && sub->call_cb)
  	(*sub->user.on_tsx_state)(sub, tsx, event);
  
  
      /* Process the event: */
  
      if (sub->role == PJSIP_ROLE_UAC) {
  	on_tsx_state_uac(sub, tsx, event);
      } else {
  	on_tsx_state_uas(sub, tsx, event);
      }
  
  
      /* Check transaction TERMINATE event */
      if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
  
  	--sub->pending_tsx;
  
  	if (sub->state == PJSIP_EVSUB_STATE_TERMINATED &&
  	    sub->pending_tsx == 0)
  	{
  	    evsub_destroy(sub);
  	}
  
      }
  }
  
  
  