Logo Search packages:      
Sourcecode: sofia-sip version File versions  Download package

nea.c
/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2005 Nokia Corporation.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

/**@CFILE nea.c  Nokia Event Client API agent implementation.
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @date Created: Wed Feb 14 18:32:58 2001 ppessi
 */

#include "config.h"

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>

#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su_string.h>

#include <sofia-sip/sip.h>
#include <sofia-sip/sip_header.h>
#include <sofia-sip/sip_util.h>
#include <sofia-sip/sip_status.h>

#define SU_TIMER_ARG_T       struct nea_s
#define NTA_LEG_MAGIC_T      struct nea_s
#define NTA_OUTGOING_MAGIC_T struct nea_s

#define NEA_TIMER_DELTA 2 /* time to resubscribe without expiration */
#define EXPIRES_DEFAULT       3600

#include <sofia-sip/su_wait.h>

#include "sofia-sip/nea.h"

00060 struct nea_s {
  su_home_t         nea_home[1];
  su_timer_t       *nea_timer;

  nta_agent_t      *nea_agent;
  nta_leg_t        *nea_leg;
00066   nta_outgoing_t   *nea_oreq;       /**< Outstanding request */
00067   sip_to_t         *nea_to;         /**< The other end of subscription :) */
00068   nea_notify_f      nea_callback;   /**< Notify callback  */
00069   nea_magic_t      *nea_context;    /**< Application context */

00071   sip_contact_t    *nea_contact;    /**< */
00072   sip_expires_t    *nea_expires;    /**< Proposed expiration time */

00074   nea_state_t       nea_state;              /**< State of our subscription */
00075   sip_time_t        nea_deadline;   /**< When our subscription expires */
  tagi_t           *nea_args;

00078   unsigned          nea_dialog : 1;     /**< Dialog has been established */
  unsigned          nea_notify_received : 1;
  unsigned          nea_terminating : 1;
00081   unsigned          nea_strict_3265 : 1;       /**< Strict mode */
};

int details = 0;

static int process_nea_request(nea_t *nea,
                         nta_leg_t *leg,
                         nta_incoming_t *ireq,
                         sip_t const *sip);

static int handle_notify(nta_leg_magic_t *lmagic,
                   nta_leg_t *leg,
                   nta_incoming_t *ireq,
                   sip_t const *sip);

static int response_to_subscribe(nea_t *nea,
                         nta_outgoing_t *req,
                         sip_t const *sip);

static int response_to_unsubscribe(nea_t *nea,
                           nta_outgoing_t *req,
                           sip_t const *sip);

static void nea_expires_renew(su_root_magic_t *magic,
                   su_timer_t *timer,
                   nea_t *nea);

/* ---------------------------------------------------------- */

/** Create a event watcher object.
 *
 */
nea_t *nea_create(nta_agent_t *agent,
              su_root_t *root,
              nea_notify_f no_callback,
              nea_magic_t *context,
              tag_type_t tag, tag_value_t value, ...)
{
  nea_t *nea = NULL;
  ta_list ta;
  int have_from, have_to, have_contact;
  sip_expires_t const *expires = NULL;
  char const *expires_str = NULL;
  sip_method_t method = sip_method_subscribe;
  char const *SUBSCRIBE = "SUBSCRIBE";
  char const *method_name = SUBSCRIBE;

  ta_start(ta, tag, value);

  have_to =
    tl_find(ta_args(ta), siptag_to) || tl_find(ta_args(ta), siptag_to_str);
  have_from =
    tl_find(ta_args(ta), siptag_from) || tl_find(ta_args(ta), siptag_from_str);
  have_contact =
    tl_find(ta_args(ta), siptag_contact) ||
    tl_find(ta_args(ta), siptag_contact_str);

  if (have_to && (nea = su_home_new(sizeof(nea_t)))) {
    su_home_t      *home = nea->nea_home;
    sip_contact_t  *m = nta_agent_contact(agent);
    sip_from_t     *from;
    sip_to_t const *to;
    int strict = 0;

    nea->nea_agent = agent;
    nea->nea_callback = no_callback;
    nea->nea_context = context;

    if (!have_from)
      from = sip_from_create(home, (url_string_t*)m->m_url);
    else
      from = NULL;

    nea->nea_args = tl_tlist(home,
                       TAG_IF(!have_contact, SIPTAG_CONTACT(m)),
                       ta_tags(ta));

    /* Get and remove Expires header from tag list */
    tl_gets(nea->nea_args,
          SIPTAG_EXPIRES_REF(expires),
          SIPTAG_EXPIRES_STR_REF(expires_str),
          SIPTAG_TO_REF(to),
          NEATAG_STRICT_3265_REF(strict),
          NTATAG_METHOD_REF(method_name),
          TAG_END());

    nea->nea_strict_3265 = strict;

    if (to)
      nea->nea_to = sip_to_dup(home, to);

    if (expires)
      nea->nea_expires = sip_expires_dup(home, expires);
    else if (expires_str)
      nea->nea_expires = sip_expires_make(home, expires_str);
    else
      nea->nea_expires = sip_expires_create(home, EXPIRES_DEFAULT);

    tl_tremove(nea->nea_args,
             SIPTAG_EXPIRES(0),
             SIPTAG_EXPIRES_STR(0),
             TAG_END());

    if (method_name != SUBSCRIBE)
      method = sip_method_code(method_name);

    if (method != sip_method_invalid)
      /* Create the timer object */
      nea->nea_timer = su_timer_create(su_root_task(root), 0L);

    if (nea->nea_timer) {
      /* Create leg for NOTIFY requests */
      nea->nea_leg = nta_leg_tcreate(nea->nea_agent,
                             process_nea_request, nea,
                             TAG_IF(!have_from, SIPTAG_FROM(from)),
                             TAG_NEXT(nea->nea_args));

      if (nea->nea_leg) {
      nta_leg_tag(nea->nea_leg, NULL);
      nea->nea_oreq = nta_outgoing_tcreate(nea->nea_leg,
                                   response_to_subscribe, nea,
                                   NULL,
                                   method, method_name,
                                   NULL,
                                   SIPTAG_EXPIRES(nea->nea_expires),
                                   TAG_NEXT(nea->nea_args));
      }
    }

    if (!nea->nea_leg ||
      !nea->nea_oreq ||
      !nea->nea_timer)
      nea_destroy(nea), nea = NULL;
  }

  ta_end(ta);
  return nea;
}


int nea_update(nea_t *nea,
             tag_type_t tag,
             tag_value_t value,
             ...)
{
  ta_list ta;
  sip_expires_t const *expires = NULL;
  sip_payload_t const *pl = NULL;
  sip_content_type_t const *ct = NULL;
  char const *cts = NULL;

  /* char const *expires_str = NULL; */
  su_home_t *home = nea->nea_home;

  /* XXX - hack, previous request still waiting for response */
  if (!nea->nea_leg || nea->nea_oreq)
    return -1;

  ta_start(ta, tag, value);

  tl_gets(ta_args(ta),
        SIPTAG_CONTENT_TYPE_REF(ct),
        SIPTAG_CONTENT_TYPE_STR_REF(cts),
        SIPTAG_PAYLOAD_REF(pl),
        SIPTAG_EXPIRES_REF(expires),
        TAG_NULL());

  if (!pl || (!ct && !cts)) {
    ta_end(ta);
    return -1;
  }

  tl_tremove(nea->nea_args,
           SIPTAG_CONTENT_TYPE(0),
           SIPTAG_CONTENT_TYPE_STR(0),
           SIPTAG_PAYLOAD(0),
           SIPTAG_PAYLOAD_STR(0),
           TAG_END());

  su_free(home, nea->nea_expires);

  if (expires)
    nea->nea_expires = sip_expires_dup(home, expires);
  else
    nea->nea_expires = sip_expires_create(home, EXPIRES_DEFAULT);

  /* nta_leg_tag(nea->nea_leg, NULL); */
  nea->nea_oreq = nta_outgoing_tcreate(nea->nea_leg,
                               response_to_subscribe, nea,
                               NULL,
                               SIP_METHOD_SUBSCRIBE,
                               NULL,
                               SIPTAG_TO(nea->nea_to),
                               SIPTAG_PAYLOAD(pl),
                               TAG_IF(ct, SIPTAG_CONTENT_TYPE(ct)),
                               TAG_IF(cts, SIPTAG_CONTENT_TYPE_STR(cts)),
                               SIPTAG_EXPIRES(nea->nea_expires),
                               TAG_NEXT(nea->nea_args));

  ta_end(ta);

  if (!nea->nea_oreq)
    return -1;

  return 0;
}


/** Unsubscribe the agent. */
void nea_end(nea_t *nea)
{
  if (nea == NULL)
    return;

  nea->nea_terminating = 1;

  su_timer_destroy(nea->nea_timer), nea->nea_timer = NULL;

  if (nea->nea_leg && nea->nea_deadline) {
    nea->nea_oreq =
      nta_outgoing_tcreate(nea->nea_leg,
                     response_to_unsubscribe,
                     nea,
                     NULL,
                     SIP_METHOD_SUBSCRIBE,
                     NULL,
                     SIPTAG_EXPIRES_STR("0"),
                     TAG_NEXT(nea->nea_args));
  }
}

void nea_destroy(nea_t *nea)
{
  if (nea == NULL)
    return;

  if (nea->nea_oreq)
    nta_outgoing_destroy(nea->nea_oreq), nea->nea_oreq = NULL;

  if (nea->nea_leg)
    nta_leg_destroy(nea->nea_leg), nea->nea_leg = NULL;

  if (nea->nea_timer) {
    su_timer_reset(nea->nea_timer);
    su_timer_destroy(nea->nea_timer), nea->nea_timer = NULL;
  }

  su_free(NULL, nea);
}


/* Function called by NTA to handle incoming requests belonging to the leg */
int process_nea_request(nea_t *nea,
                  nta_leg_t *leg,
                  nta_incoming_t *ireq,
                  sip_t const *sip)
{

  switch (sip->sip_request->rq_method) {
  case sip_method_notify:
    return handle_notify(nea, leg, ireq, sip);
  case sip_method_ack:
    return 400;
  default:
    nta_incoming_treply(ireq, SIP_405_METHOD_NOT_ALLOWED,
                  SIPTAG_ALLOW_STR("NOTIFY"), TAG_END());
    return 405;
  }
}


/* Callback function to handle subscription requests */
int response_to_subscribe(nea_t *nea,
                    nta_outgoing_t *oreq,
                    sip_t const *sip)
{
  int status = sip->sip_status->st_status;
  int error = status >= 300;

  if (status >= 200 && oreq == nea->nea_oreq)
    nea->nea_oreq = NULL;

  nea->nea_callback(nea, nea->nea_context, sip);

  if (status < 200)
    return 0;

  nea->nea_oreq = NULL;

  if (status < 300) {
    sip_time_t now = sip_now();
    if (!nea->nea_notify_received) {
      nea->nea_deadline = now +
      sip_contact_expires(NULL, sip->sip_expires, sip->sip_date,
                      EXPIRES_DEFAULT, now);
      if (sip->sip_to->a_tag && !nea->nea_dialog) {
      nea->nea_dialog = 1;
      nta_leg_rtag(nea->nea_leg, sip->sip_to->a_tag);
      nta_leg_client_route(nea->nea_leg,
                       sip->sip_record_route, sip->sip_contact);
      }
    }
  }
  else {
    nea->nea_deadline = 0;
    nea->nea_state = nea_terminated;
    if (status == 301 || status == 302 || status == 305) {
      sip_contact_t *m;

      for (m = sip->sip_contact; m; m = m->m_next) {
      if (m->m_url->url_type == url_sip ||
          m->m_url->url_type == url_sips)
        break;
      }

      if (m) {
      url_string_t const *proxy, *url;
      if (status == 305)
        url = NULL, proxy = (url_string_t *)m->m_url;
      else
        url = (url_string_t *)m->m_url, proxy = NULL;

      nea->nea_oreq =
        nta_outgoing_tcreate(nea->nea_leg,
                         response_to_subscribe,
                         nea,
                         proxy,
                         SIP_METHOD_SUBSCRIBE,
                         url,
                         SIPTAG_EXPIRES(nea->nea_expires),
                         TAG_NEXT(nea->nea_args));
      }
    } else if (status == 423 && sip->sip_min_expires) {
      unsigned value = sip->sip_min_expires->me_delta;
      su_free(nea->nea_home, nea->nea_expires);
      nea->nea_expires = sip_expires_format(nea->nea_home, "%u", value);

      nea->nea_oreq =
      nta_outgoing_tcreate(nea->nea_leg,
                       response_to_subscribe,
                       nea,
                       NULL,
                       SIP_METHOD_SUBSCRIBE,
                       NULL,
                       SIPTAG_EXPIRES(nea->nea_expires),
                       TAG_NEXT(nea->nea_args));
    }
  }

  if (status >= 200)
    nta_outgoing_destroy(oreq);

  if (nea->nea_oreq || !error) {
    su_time_t now = su_now();
    now.tv_sec = nea->nea_deadline;
    su_timer_set_at(nea->nea_timer,
                nea_expires_renew,
                nea,
                now);
  }
  else
    nea->nea_callback(nea, nea->nea_context, NULL);

  return 0;
}


int response_to_unsubscribe(nea_t *nea,
                      nta_outgoing_t *orq,
                      sip_t const *sip)
{
  int status = sip->sip_status->st_status;

  nea->nea_callback(nea, nea->nea_context, sip);

  if (status >= 200)
    nta_outgoing_destroy(orq), nea->nea_oreq = NULL;
  if (status >= 300)
    nea->nea_callback(nea, nea->nea_context, NULL);

  return 0;
}

/** handle notifications */
int handle_notify(nea_t *nea,
              nta_leg_t *leg,
              nta_incoming_t *irq,
              sip_t const *sip)
{
  sip_subscription_state_t *ss = sip->sip_subscription_state;
  sip_subscription_state_t ss0[1];
  char expires[32];

  if (nea->nea_strict_3265) {
    char const *phrase = NULL;

    if (ss == NULL)
      phrase = "NOTIFY Has No Subscription-State Header";
    else if (sip->sip_event == NULL)
      phrase = "Event Header Missing";

    if (phrase) {
      nta_incoming_treply(irq, 400, phrase, TAG_END());
      nta_incoming_destroy(irq);
      nta_leg_destroy(nea->nea_leg), nea->nea_leg = NULL;
      nea->nea_state = nea_terminated;
      nea->nea_callback(nea, nea->nea_context, NULL);
      return 0;
    }
  }

  if (ss == NULL) {
    /* Do some compatibility stuff here */
    unsigned long delta = 3600;

    sip_subscription_state_init(ss = ss0);

    if (sip->sip_expires)
      delta = sip->sip_expires->ex_delta;

    if (delta == 0)
      ss->ss_substate = "terminated";
    else
      ss->ss_substate = "active";

    if (delta > 0) {
      snprintf(expires, sizeof expires, "%lu", delta);
      ss->ss_expires = expires;
    }
  }

  if (!nea->nea_dialog) {
    nea->nea_dialog = 1;
    nta_leg_rtag(nea->nea_leg, sip->sip_from->a_tag);
    nta_leg_server_route(nea->nea_leg,
                   sip->sip_record_route, sip->sip_contact);
  }

  nea->nea_notify_received = 1;
  nea->nea_callback(nea, nea->nea_context, sip);

  if (su_casematch(ss->ss_substate, "terminated")) {
    nta_leg_destroy(nea->nea_leg), nea->nea_leg = NULL;
    nea->nea_state = nea_terminated;

    if (su_casematch(ss->ss_reason, "deactivated")) {
      nea->nea_state = nea_embryonic;
      nea->nea_deadline = sip_now();
    } else if (su_casematch(ss->ss_reason, "probation")) {
      sip_time_t retry = sip_now() + NEA_TIMER_DELTA;

      if (ss->ss_retry_after)
      retry += strtoul(ss->ss_retry_after, NULL, 10);
      else
      retry += NEA_TIMER_DELTA;

      nea->nea_state = nea_embryonic;
      nea->nea_deadline = retry;
    } else {
      nea->nea_deadline = 0;
      nea->nea_callback(nea, nea->nea_context, NULL);
      return 200;
    }
  }
  else if (su_casematch(ss->ss_substate, "pending"))
    nea->nea_state = nea_pending;
  else if (su_casematch(ss->ss_substate, "active"))
    nea->nea_state = nea_active;
  else
    nea->nea_state = nea_extended;

  if (nea->nea_state != nea_embryonic && ss->ss_expires) {
    unsigned retry = strtoul(ss->ss_expires, NULL, 10);
    if (retry > 60) retry -= 30; else retry /= 2;
    nea->nea_deadline = sip_now() + retry;
  }

  {
    su_time_t now = su_now();
    now.tv_sec = nea->nea_deadline;
    su_timer_set_at(nea->nea_timer,
                nea_expires_renew,
                nea,
                now);
  }

  return 200;
}

void nea_expires_renew(su_root_magic_t *magic,
                   su_timer_t *timer,
                   nea_t *nea)
{
  sip_time_t now = sip_now();

  /* re-subscribe if expires soon */
  if (nea->nea_state == nea_terminated ||
      nea->nea_deadline == 0 ||
      nea->nea_deadline > now + NEA_TIMER_DELTA)
    return;

  if (!nea->nea_notify_received)    /* Hmph. */
    return;

  nea->nea_notify_received = 0;

  nea->nea_oreq =
    nta_outgoing_tcreate(nea->nea_leg,
                   response_to_subscribe,
                   nea,
                   NULL,
                   SIP_METHOD_SUBSCRIBE,
                   NULL,
                   SIPTAG_EXPIRES(nea->nea_expires),
                   TAG_NEXT(nea->nea_args));

  return;
}

Generated by  Doxygen 1.6.0   Back to index