nua_notifier.c 29.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2006 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 nua_notifier.c
 * @brief SUBSCRIBE server, NOTIFY client and REFER server
 *
28 29
 * Simpler event server. See nua_event_server.c for more complex event
 * server.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @date Created: Wed Mar  8 15:10:08 EET 2006 ppessi
 */

#include "config.h"

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include <assert.h>

#include <sofia-sip/string0.h>
#include <sofia-sip/sip_protos.h>
Pekka Pessi's avatar
Pekka Pessi committed
47
#include <sofia-sip/sip_extra.h>
48 49 50
#include <sofia-sip/sip_status.h>
#include <sofia-sip/sip_util.h>
#include <sofia-sip/su_uniqueid.h>
51 52
#include <sofia-sip/su_md5.h>
#include <sofia-sip/token64.h>
53 54 55 56 57 58 59 60 61

#include "nua_stack.h"

/* ---------------------------------------------------------------------- */
/* Notifier event usage */

struct notifier_usage
{
  enum nua_substate  nu_substate;	/**< Subscription state */
62 63
  sip_time_t         nu_expires; 	/**< Expiration time */
  sip_time_t         nu_requested;      /**< Requested expiration time */
64
#if SU_HAVE_EXPERIMENTAL
65 66 67 68
  char              *nu_tag;	        /**< @ETag in last NOTIFY */
  unsigned           nu_etags:1;	/**< Subscriber supports etags */
  unsigned           nu_appl_etags:1;   /**< Application generates etags */
  unsigned           nu_no_body:1;      /**< Suppress body */
69
#endif
70 71 72 73 74 75 76 77 78
};

static char const *nua_notify_usage_name(nua_dialog_usage_t const *du);
static int nua_notify_usage_add(nua_handle_t *nh, 
				   nua_dialog_state_t *ds,
				   nua_dialog_usage_t *du);
static void nua_notify_usage_remove(nua_handle_t *nh, 
				       nua_dialog_state_t *ds,
				       nua_dialog_usage_t *du);
79
static void nua_notify_usage_refresh(nua_handle_t *nh,
80
				     nua_dialog_state_t *ds,
81 82 83
				     nua_dialog_usage_t *du,
				     sip_time_t now);
static int nua_notify_usage_shutdown(nua_handle_t *nh,
84
				     nua_dialog_state_t *ds,
85
				     nua_dialog_usage_t *du);
86 87 88 89 90 91 92

static nua_usage_class const nua_notify_usage[1] = {
  {
    sizeof (struct notifier_usage), (sizeof nua_notify_usage),
    nua_notify_usage_add,
    nua_notify_usage_remove,
    nua_notify_usage_name,
93 94 95
    NULL,
    nua_notify_usage_refresh,
    nua_notify_usage_shutdown,
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
  }};

static char const *nua_notify_usage_name(nua_dialog_usage_t const *du)
{
  return "notify";
}

static 
int nua_notify_usage_add(nua_handle_t *nh, 
			   nua_dialog_state_t *ds,
			   nua_dialog_usage_t *du)
{
  ds->ds_has_events++;
  ds->ds_has_notifys++;
  return 0;
}

static 
void nua_notify_usage_remove(nua_handle_t *nh, 
			       nua_dialog_state_t *ds,
			       nua_dialog_usage_t *du)
{
  ds->ds_has_events--;	
  ds->ds_has_notifys--;	
}

/* ====================================================================== */
/* SUBSCRIBE server */

125
/** @NUA_EVENT nua_i_subscribe
126
 *
127 128 129 130
 * Incoming @b SUBSCRIBE request.
 *
 * @b SUBSCRIBE request is used to query SIP event state or establish a SIP
 * event subscription.
131
 *
132 133 134 135 136 137 138 139
 * @param status status code of response sent automatically by stack
 * @param phrase response phrase sent automatically by stack
 * @param nh     operation handle associated with the incoming request
 * @param hmagic application context associated with the handle
 *               (NULL when handle is created by the stack)
 * @param sip    SUBSCRIBE request headers
 * @param tags   NUTAG_SUBSTATE()
 *
140 141 142
 * Initial SUBSCRIBE requests are dropped with <i>489 Bad Event</i>
 * response, unless the application has explicitly included the @Event in
 * the list of allowed events with nua_set_params() tag NUTAG_ALLOW_EVENTS()
143 144 145
 * (or SIPTAG_ALLOW_EVENTS() or SIPTAG_ALLOW_EVENTS_STR()).
 *
 * If the event has been allowed the application
146 147
 * can decide whether to accept the SUBSCRIBE request or reject it. The
 * nua_response() call responding to a SUBSCRIBE request must have
148
 * NUTAG_WITH() (or NUTAG_WITH_THIS()/NUTAG_WITH_SAVED()) tag.
149 150 151
 *
 * If the application accepts the SUBSCRIBE request, it must immediately
 * send an initial NOTIFY establishing the dialog. This is because the
152 153
 * response to the SUBSCRIBE request may be lost by an intermediate proxy
 * because it had forked the SUBSCRIBE request.
154 155 156 157
 *
 * SUBSCRIBE requests modifying (usually refreshing or terminating) an
 * existing event subscription are accepted by default and a <i>200 OK</i>
 * response along with a copy of previously sent NOTIFY is sent
158
 * automatically to the subscriber.
159 160 161 162 163
 *
 * By default, only event subscriptions accepted are those created
 * implicitly by REFER request. See #nua_i_refer how the application must
 * handle the REFER requests.
 *
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
 * @par Subscription Lifetime and Terminating Subscriptions
 *
 * Accepting the SUBSCRIBE request creates a dialog with a <i>notifier
 * dialog usage</i> on the handle. The dialog usage is active, until the
 * subscriber terminates the subscription, it times out or the application
 * terminates the usage with nua_notify() call containing the tag
 * NUTAG_SUBSTATE(nua_substate_terminated) or @SubscriptionState header with
 * state "terminated" and/or expiration time 0. 
 *
 * When the subscriber terminates the subscription, the application is
 * notified of an termination by a #nua_i_subscribe event with
 * NUTAG_SUBSTATE(nua_substate_terminated) tag. When the subscription times
 * out, nua automatically initiates a NOTIFY transaction. When it is
 * terminated, the application is sent a #nua_r_notify event with
 * NUTAG_SUBSTATE(nua_substate_terminated) tag. 
179 180 181 182 183
 *
 * @sa @RFC3265, nua_notify(), NUTAG_SUBSTATE(), @SubscriptionState,
 * @Event, nua_subscribe(), #nua_r_subscribe, #nua_i_refer, nua_refer()
 *
 * @END_NUA_EVENT
184 185
 */

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
static int nua_subscribe_server_init(nua_server_request_t *sr);
static int nua_subscribe_server_preprocess(nua_server_request_t *sr);
static int nua_subscribe_server_respond(nua_server_request_t*, tagi_t const *);
static int nua_subscribe_server_report(nua_server_request_t*, tagi_t const *);

nua_server_methods_t const nua_subscribe_server_methods = 
  {
    SIP_METHOD_SUBSCRIBE,
    nua_i_subscribe,		/* Event */
    { 
      1,			/* Create dialog */
      0,			/* Initial request */
      1,			/* Target refresh request  */
      1,			/* Add Contact */
    },
    nua_subscribe_server_init,
    nua_subscribe_server_preprocess,
    nua_base_server_params,
    nua_subscribe_server_respond,
    nua_subscribe_server_report,
  };

int nua_subscribe_server_init(nua_server_request_t *sr)
209
{
210 211 212 213
  nua_handle_t *nh = sr->sr_owner;
  nua_dialog_state_t *ds = nh->nh_ds;
  sip_allow_events_t const *allow_events = NH_PGET(nh, allow_events);
  sip_t const *sip = sr->sr_request.sip;
214
  sip_event_t *o = sip->sip_event;
215 216
  char const *event = o ? o->o_type : NULL;
  
217
  if (sr->sr_initial || !nua_dialog_usage_get(ds, nua_notify_usage, o)) {
218 219
    if (event && str0cmp(event, "refer") == 0)
      /* refer event subscription should be initiated with REFER */
220 221
      return SR_STATUS1(sr, SIP_403_FORBIDDEN);

Pekka Pessi's avatar
Pekka Pessi committed
222
    /* XXX - event is case-sensitive, should use msg_header_find_item() */
223 224
    if (!event || !msg_header_find_param(allow_events->k_common, event))
      return SR_STATUS1(sr, SIP_489_BAD_EVENT);
225
  }
226

227 228
  return 0;
}
229

230 231 232 233 234 235 236 237 238 239 240
int nua_subscribe_server_preprocess(nua_server_request_t *sr)
{
  nua_handle_t *nh = sr->sr_owner;
  nua_dialog_state_t *ds = nh->nh_ds;
  nua_dialog_usage_t *du;
  struct notifier_usage *nu;
  sip_t const *sip = sr->sr_request.sip;
  sip_event_t *o = sip->sip_event;
  char const *event = o ? o->o_type : NULL;
  /* Maximum expiration time */
  unsigned long expires = 3600;
241

242 243 244
  assert(nh && nh->nh_nua->nua_dhandle != nh);
  
  du = nua_dialog_usage_get(ds, nua_notify_usage, o);
245

246 247 248 249 250 251 252 253 254 255
  if (du == NULL) {
    /* Create a new subscription */
    du = nua_dialog_usage_add(nh, ds, nua_notify_usage, o);
    if (du == NULL)
      return SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR);
  }
  else {
    /* Refresh existing subscription */
    if (str0cmp(event, "refer") == 0)
      expires = NH_PGET(nh, refer_expires);
256 257

    SR_STATUS1(sr, SIP_200_OK);
258 259
  }

260
  nu = nua_dialog_usage_private(du);
261

262 263 264
  if (sip->sip_expires && sip->sip_expires->ex_delta < expires)
    expires = sip->sip_expires->ex_delta;
  nu->nu_requested = sip_now() + expires;
265

266
#if SU_HAVE_EXPERIMENTAL
267 268 269 270
  nu->nu_etags = 
    sip_suppress_body_if_match(sip) ||
    sip_suppress_notify_if_match(sip) ||
    sip_has_feature(sr->sr_request.sip->sip_supported, "etags");
271 272
#endif

273
  sr->sr_usage = du;
274

275
  return sr->sr_status <= 100 ? 0 : sr->sr_status;
276 277
}

Pekka Pessi's avatar
Pekka Pessi committed
278
/** @internal Respond to a SUBSCRIBE request.
279 280 281
 *
 */
static
282
int nua_subscribe_server_respond(nua_server_request_t *sr, tagi_t const *tags)
283
{
284
  struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage);
285

286 287
  msg_t *msg = sr->sr_response.msg;
  sip_t *sip = sr->sr_response.sip;
288

289 290
  if (200 <= sr->sr_status && sr->sr_status < 300) {
    sip_expires_t ex[1]; 
291

292
    sip_expires_init(ex);
293

294 295
    if (nu) {
      sip_time_t now = sip_now();
296

297 298 299 300 301 302
      if (nu->nu_requested) {
	if (nu->nu_requested > nu->nu_expires)
	  nu->nu_expires = nu->nu_requested;
	else if (nu->nu_expires <= now || nu->nu_requested <= now)
	  nu->nu_substate = nua_substate_terminated;
      }
303

304 305 306 307 308 309
      if (nu->nu_expires > now)
	ex->ex_delta = nu->nu_expires - now;
    }
    else {
      /* Add header Expires: 0 */
    }
310

311 312
    if (!sip->sip_expires || sip->sip_expires->ex_delta > ex->ex_delta)
      sip_add_dup(msg, sip, (sip_header_t *)ex);
313
  }
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333

  return nua_base_server_respond(sr, tags);
}

static
int nua_subscribe_server_report(nua_server_request_t *sr, tagi_t const *tags)
{
  nua_handle_t *nh = sr->sr_owner;
  nua_dialog_state_t *ds = nh->nh_ds;
  struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage);
  enum nua_substate substate = nua_substate_terminated;
  int notify = 0;
  int retval;

  if (nu && !sr->sr_terminating) {
    substate = nu->nu_substate;
  }

  /* nu_requested is set by SUBSCRIBE and cleared when NOTIFY is sent */
  if (nu && nu->nu_requested && substate != nua_substate_embryonic) {
334
#if SU_HAVE_EXPERIMENTAL
335 336 337 338 339 340 341 342 343 344 345
    sip_t const *sip = sr->sr_request.sip;
    sip_suppress_notify_if_match_t *snim = sip_suppress_notify_if_match(sip);
    sip_suppress_body_if_match_t *sbim = sip_suppress_body_if_match(sip);
    
    if (!nu->nu_tag)
      notify = 1;
    else if (snim && !strcasecmp(snim->snim_tag, nu->nu_tag))
      notify = 0;
    else if (sbim && !strcasecmp(snim->snim_tag, nu->nu_tag))
      notify = 1, nu->nu_no_body = 1;
    else 
346
#endif
347 348 349 350 351 352 353 354 355 356 357
      notify = 1;
  }

  retval = nua_base_server_treport(sr, NUTAG_SUBSTATE(substate), TAG_END());

  if (retval >= 2 || nu == NULL)
    return retval;
  
  if (notify) {
    /* Send NOTIFY (and terminate subscription, when needed) */
    nua_dialog_usage_refresh(nh, ds, sr->sr_usage, sip_now());
358 359
  }
  
360
  return retval;
361
}
362 363

/* ======================================================================== */
364
/* NOTIFY client */
365

366 367 368 369 370 371
/**@fn void nua_notify(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...);
 *
 * Send a SIP NOTIFY request message.
 *
 * This function is used when the application implements itself the
 * notifier. The application must provide valid @SubscriptionState and
372 373 374
 * @Event headers using SIP tags. The subscription state can be modified
 * with NUTAG_SUBSTATE(), however, its effect is overriden by
 * @SubscriptionState header included in the nua_notify() tags.
375 376 377
 *
 * @bug If the @Event is not given by application, stack uses the @Event
 * header from the first subscription usage on handle.
378 379 380 381 382 383 384 385 386 387
 * 
 * If there is no active <i>notifier dialog usage</i> or no notifier dialog
 * usage matches the @Event header given by the application the nua_notify()
 * request is rejected locally by the stack with status code 481. The local
 * rejection can be bypassed if NUTAG_NEWSUB(1) is included in tags.
 *
 * Please note that including NUTAG_NEWSUB(1) in nua_notify() tags if there
 * is a valid subscription may lead to an extra NOTIFY sent to subscriber if
 * the subscription had been terminated by the subscriber or by a timeout
 * before the nua_notify() is processed.
388 389 390 391 392 393 394 395
 *
 * @param nh              Pointer to operation handle
 * @param tag, value, ... List of tagged parameters
 *
 * @return 
 *    nothing
 *
 * @par Related Tags:
396
 *    NUTAG_SUBSTATE() \n
397
 *    NUTAG_NEWSUB() \n
398
 *    Tags of nua_set_hparams() \n
399
 *    Header tags defined in <sofia-sip/sip_tag.h>
400 401 402 403 404 405 406
 *
 * @par Events:
 *    #nua_r_notify
 *
 * @sa @RFC3265, #nua_i_subscribe, #nua_i_refer, NUTAG_ALLOW_EVENTS()
 */

Pekka Pessi's avatar
Pekka Pessi committed
407 408 409
static int nua_notify_client_init(nua_client_request_t *cr, 
				  msg_t *, sip_t *,
				  tagi_t const *tags);
410 411 412
static int nua_notify_client_init_etag(nua_client_request_t *cr,
				       msg_t *msg, sip_t *sip,
				       tagi_t const *tags);
Pekka Pessi's avatar
Pekka Pessi committed
413 414 415
static int nua_notify_client_request(nua_client_request_t *cr,
				     msg_t *, sip_t *,
				     tagi_t const *tags);
416 417 418 419 420
static int nua_notify_client_report(nua_client_request_t *cr,
				    int status, char const *phrase,
				    sip_t const *sip,
				    nta_outgoing_t *orq,
				    tagi_t const *tags);
Pekka Pessi's avatar
Pekka Pessi committed
421

422
static nua_client_methods_t const nua_notify_client_methods = {
Pekka Pessi's avatar
Pekka Pessi committed
423 424 425 426 427 428 429 430 431 432 433
  SIP_METHOD_NOTIFY,
  0,
  { 
    /* create_dialog */ 1,
    /* in_dialog */ 1,
    /* target refresh */ 1
  },
  /* nua_notify_client_template */ NULL,
  nua_notify_client_init,
  nua_notify_client_request,
  /* nua_notify_client_check_restart */ NULL,
434 435 436
  /* nua_notify_client_response */ NULL,
  /* nua_notify_client_preliminary */ NULL,
  nua_notify_client_report
Pekka Pessi's avatar
Pekka Pessi committed
437 438
};

439 440 441 442 443
/**@internal Send NOTIFY. */
int nua_stack_notify(nua_t *nua,
		     nua_handle_t *nh,
		     nua_event_t e,
		     tagi_t const *tags)
444
{
Pekka Pessi's avatar
Pekka Pessi committed
445
  return nua_client_create(nh, e, &nua_notify_client_methods, tags);
446 447
}

Pekka Pessi's avatar
Pekka Pessi committed
448 449 450
static int nua_notify_client_init(nua_client_request_t *cr,
				  msg_t *msg, sip_t *sip,
				  tagi_t const *tags)
451
{
Pekka Pessi's avatar
Pekka Pessi committed
452 453
  nua_handle_t *nh = cr->cr_owner;
  nua_dialog_usage_t *du;
454
  struct notifier_usage *nu;
Pekka Pessi's avatar
Pekka Pessi committed
455 456 457 458 459
  sip_event_t const *o = sip->sip_event;
  sip_subscription_state_t *ss = sip->sip_subscription_state;
  sip_time_t now = sip_now();
    
  if (o == NULL && nh->nh_ds->ds_has_notifys == 1)
460 461 462
    o = NONE;

  du = nua_dialog_usage_get(nh->nh_ds, nua_notify_usage, o);
Pekka Pessi's avatar
Pekka Pessi committed
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480

  if (!du) {
    tagi_t const *newsub = tl_find_last(tags, nutag_newsub);

    if (!newsub || !newsub->t_value)
      return 0; /* Rejected eventually by nua_notify_client_request() */

    /* Create new notifier */
    du = nua_dialog_usage_add(nh, nh->nh_ds, nua_notify_usage, o);
    if (du == NULL)
      return -1;

    nu = nua_dialog_usage_private(du);
    nu->nu_expires = now;
  }
  else
    nu = nua_dialog_usage_private(du);

481

Pekka Pessi's avatar
Pekka Pessi committed
482 483 484 485
  if (nu->nu_substate == nua_substate_terminated) {
    /*Xyzzy*/;
  }
  else if (ss != NULL) {
486
    /* SIPTAG_SUBSCRIPTION_STATE() overrides NUTAG_SUBSTATE() */
Pekka Pessi's avatar
Pekka Pessi committed
487
    nu->nu_substate = nua_substate_make(ss->ss_substate);
488

Pekka Pessi's avatar
Pekka Pessi committed
489 490
    if (ss->ss_expires) {
      unsigned long expires = strtoul(ss->ss_expires, NULL, 10);
Pekka Pessi's avatar
Pekka Pessi committed
491 492
      if (now + expires < now)
	expires = SIP_TIME_MAX - now - 1;
493

494 495 496 497 498
      /* We can change the lifetime of unsolicited subscription at will */
      if (nu->nu_requested == 0)
	nu->nu_expires = nu->nu_requested = now + expires;
      /* Notifier can only shorten the subscribed time */ 
      else if (nu->nu_requested >= now + expires)
499
	nu->nu_expires = nu->nu_requested = now + expires;
500
    }
501 502 503 504 505
    else {
      if (nu->nu_requested >= nu->nu_expires)
	nu->nu_expires = nu->nu_requested;
    }

506 507
  }
  else {
Pekka Pessi's avatar
Pekka Pessi committed
508
    enum nua_substate substate = nu->nu_substate;
509

510 511 512
    if (nu->nu_requested >= nu->nu_expires)
      nu->nu_expires = nu->nu_requested;

Pekka Pessi's avatar
Pekka Pessi committed
513
    if (nu->nu_expires > now) {
514 515
      tagi_t const *t = tl_find_last(tags, nutag_substate);
      if (t)
Pekka Pessi's avatar
Pekka Pessi committed
516
        substate = (enum nua_substate)t->t_value;
517
    }
Pekka Pessi's avatar
Pekka Pessi committed
518 519
    else
      substate = nua_substate_terminated;
520 521

    switch (substate) {
522 523 524
    case nua_substate_embryonic:
      /*FALLTHROUGH*/
    case nua_substate_pending:
525
      nu->nu_substate = nua_substate_pending;
526 527 528
      break;
    case nua_substate_active:
    default:
529
      nu->nu_substate = nua_substate_active;
530 531
      break;
    case nua_substate_terminated:
532
      nu->nu_substate = nua_substate_terminated;
533 534
      break;
    }
Pekka Pessi's avatar
Pekka Pessi committed
535
  }
536

Pekka Pessi's avatar
Pekka Pessi committed
537 538
  if (nu->nu_substate == nua_substate_terminated)
    cr->cr_terminating = 1;
539

Pekka Pessi's avatar
Pekka Pessi committed
540
  cr->cr_usage = du;
541

542 543 544 545 546 547 548
  return nua_notify_client_init_etag(cr, msg, sip, tags);
}

static int nua_notify_client_init_etag(nua_client_request_t *cr,
				       msg_t *msg, sip_t *sip,
				       tagi_t const *tags)
{
549
#if SU_HAVE_EXPERIMENTAL
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
  nua_handle_t *nh = cr->cr_owner;
  struct notifier_usage *nu = nua_dialog_usage_private(cr->cr_usage);
  nua_server_request_t *sr;

  if (nu->nu_tag)
    su_free(nh->nh_home, nu->nu_tag), nu->nu_tag = NULL;
    nu->nu_no_body = 0;

  if (sip->sip_etag) {
    nu->nu_appl_etags = 1;
    nu->nu_tag = su_strdup(nh->nh_home, sip->sip_etag->g_string);
  }
  else if (!nu->nu_appl_etags && nu->nu_etags) {
    su_md5_t md5[1];
    unsigned char digest[SU_MD5_DIGEST_SIZE];
    sip_payload_t pl[1] = { SIP_PAYLOAD_INIT() };
    char token[2 * 16];

    su_md5_init(md5);

    if (sip->sip_payload) *pl = *sip->sip_payload;

    if (pl->pl_len)
      su_md5_update(md5, pl->pl_data, pl->pl_len);
    su_md5_update(md5, &pl->pl_len, sizeof(pl->pl_len));

    if (sip->sip_content_type)
      su_md5_striupdate(md5, sip->sip_content_type->c_type);

    su_md5_digest(md5, digest);
    token64_e(token, sizeof token, digest, sizeof digest);
    token[(sizeof token) - 1] = '\0';
    nu->nu_tag = su_strdup(nh->nh_home, token);
  }

  if (!nu->nu_requested || !nu->nu_tag)
    return 0;

  /* Check if SUBSCRIBE had matching suppression */
  for (sr = nh->nh_ds->ds_sr; sr; sr = sr->sr_next)
    if (sr->sr_usage == cr->cr_usage && sr->sr_method == sip_method_subscribe)
      break;

  if (sr) {
    sip_t const *sip = sr->sr_request.sip;

    sip_suppress_body_if_match_t *sbim;
    sip_suppress_notify_if_match_t *snim;
    
    if (cr->cr_usage->du_ready) {
      snim = sip_suppress_notify_if_match(sip);

      if (snim && !strcasecmp(snim->snim_tag, nu->nu_tag)) {
	if (nu->nu_requested > nu->nu_expires)
	  nu->nu_expires = nu->nu_requested;
	nu->nu_requested = 0;
	return nua_client_return(cr, 202, "NOTIFY Suppressed", msg);
      }
    }

    sbim = sip_suppress_body_if_match(sip);
    if (sbim && !strcasecmp(sbim->sbim_tag, nu->nu_tag))
      nu->nu_no_body = 1;
  }
614
#endif
615

Pekka Pessi's avatar
Pekka Pessi committed
616 617
  return 0;
}
618

Pekka Pessi's avatar
Pekka Pessi committed
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
static
int nua_notify_client_request(nua_client_request_t *cr,
			      msg_t *msg, sip_t *sip,
			      tagi_t const *tags)
{
  nua_dialog_usage_t *du = cr->cr_usage; 
  struct notifier_usage *nu = nua_dialog_usage_private(du);
  su_home_t *home = msg_home(msg);
  sip_time_t now = sip_now();
  sip_subscription_state_t *ss = sip->sip_subscription_state;
  char const *expires;

  if (du == NULL)		/* Subscription has been terminated */
    return nua_client_return(cr, SIP_481_NO_TRANSACTION, msg);

  assert(du && nu);

  if (du && nua_client_bind(cr, du) < 0)
    return -1;

639 640 641 642 643
  if (nu->nu_requested)
    nu->nu_expires = nu->nu_requested;
  nu->nu_requested = 0;

  if (nu->nu_expires <= now || du->du_shutdown) {
Pekka Pessi's avatar
Pekka Pessi committed
644 645 646 647 648
    nu->nu_substate = nua_substate_terminated;
    expires = "expires=0";
  }
  else {
    expires = su_sprintf(home, "expires=%lu", nu->nu_expires - now);
649 650
  }

651
  if (ss == NULL || nua_substate_make(ss->ss_substate) != nu->nu_substate) {
Pekka Pessi's avatar
Pekka Pessi committed
652 653 654 655 656 657
    if (nu->nu_substate == nua_substate_terminated)
      expires = nu->nu_expires > now ? "noresource" : "timeout";

    ss = sip_subscription_state_format(home, "%s;%s", 
				       nua_substate_name(nu->nu_substate),
				       expires);
658

Pekka Pessi's avatar
Pekka Pessi committed
659 660
    msg_header_insert(msg, (void *)sip, (void *)ss);
  }
661
  else if (nu->nu_substate != nua_substate_terminated) {
Pekka Pessi's avatar
Pekka Pessi committed
662
    msg_header_replace_param(home, ss->ss_common, expires);
663 664
  }

665
#if SU_HAVE_EXPERIMENTAL
666 667 668 669 670 671 672 673
  if (nu->nu_tag && !sip->sip_etag)
    msg_header_add_make(msg, (void *)sip, sip_etag_class, nu->nu_tag);

  if (nu->nu_no_body) {
    nu->nu_no_body = 0;
    msg_header_remove(msg, (void *)sip, (void *)sip->sip_payload);
    msg_header_remove(msg, (void *)sip, (void *)sip->sip_content_length);
  }
674
#endif
675

Pekka Pessi's avatar
Pekka Pessi committed
676 677
  if (nu->nu_substate == nua_substate_terminated)
    cr->cr_terminating = 1;
678

Pekka Pessi's avatar
Pekka Pessi committed
679 680
  if (du->du_event && !sip->sip_event)
    sip_add_dup(cr->cr_msg, sip, (sip_header_t *)du->du_event);
681

Pekka Pessi's avatar
Pekka Pessi committed
682
  return nua_base_client_request(cr, msg, sip, tags);
683 684
}

685
/** @NUA_EVENT nua_r_notify
686
 *
687
 * Response to an outgoing @b NOTIFY request.
688
 *
689 690
 * The @b NOTIFY may be sent explicitly by nua_notify() or implicitly by NUA
 * state machine. Implicit @b NOTIFY is sent when an established dialog is
691
 * refreshed by client or it is terminated (either by client or because of a
692 693 694 695 696 697
 * timeout).
 *
 * The current subscription state is included in NUTAG_SUBSTATE() tag. The
 * nua_substate_terminated indicates that the subscription is terminated,
 * the notifier usage has been removed and when there was no other usages of
 * the dialog the dialog state is also removed.
698
 *
699 700 701 702 703 704 705 706 707 708 709
 * @param status response status code
 *               (if the request is retried, @a status is 100, the @a
 *               sip->sip_status->st_status contain the real status code
 *               from the response message, e.g., 302, 401, or 407)
 * @param phrase a short textual description of @a status code
 * @param nh     operation handle associated with the subscription
 * @param hmagic application context associated with the handle
 * @param sip    response to @b NOTIFY request or NULL upon an error 
 *               (status code is in @a status and 
 *               descriptive message in @a phrase parameters)
 * @param tags   NUTAG_SUBSTATE() indicating subscription state
Pekka Pessi's avatar
Pekka Pessi committed
710
 *               SIPTAG_EVENT() indicating subscription event
711
 *
712
 * @sa nua_notify(), @RFC3265, #nua_i_subscribe, #nua_i_refer, NUTAG_SUBSTATE()
713 714
 *
 * @END_NUA_EVENT
715 716
 */

717 718 719 720 721
static int nua_notify_client_report(nua_client_request_t *cr,
				    int status, char const *phrase,
				    sip_t const *sip,
				    nta_outgoing_t *orq,
				    tagi_t const *tags)
722
{
723 724
  nua_handle_t *nh = cr->cr_owner;
  nua_dialog_usage_t *du = cr->cr_usage;
Pekka Pessi's avatar
Pekka Pessi committed
725
  struct notifier_usage *nu = nua_dialog_usage_private(du);
726 727
  enum nua_substate substate = nua_substate_terminated;

728
  if (nu && !cr->cr_terminated)
729 730
    substate = nu->nu_substate;

731 732 733 734 735 736 737 738
  nua_stack_tevent(nh->nh_nua, nh, 
		   nta_outgoing_getresponse(orq),
		   cr->cr_event,
		   status, phrase,
		   NUTAG_SUBSTATE(substate),
		   SIPTAG_EVENT(du ? du->du_event : NULL),
		   TAG_NEXT(tags));

739 740 741 742 743 744
  if (du && du->du_cr == cr && !cr->cr_terminated) {
    if (nu->nu_requested) {
      /* Re-SUBSCRIBEd while NOTIFY was in progress, resend NOTIFY */
      nua_client_resend_request(cr, 0);
    }
    else if (nu->nu_expires) {
745
      nua_dialog_usage_set_refresh_at(du, nu->nu_expires);
746 747
    }
  }
748

749
  return 0;
750 751
}

752

753
static void nua_notify_usage_refresh(nua_handle_t *nh,
754
				     nua_dialog_state_t *ds,
755 756 757
				     nua_dialog_usage_t *du,
				     sip_time_t now)
{
758
  struct notifier_usage *nu = nua_dialog_usage_private(du);
Pekka Pessi's avatar
Pekka Pessi committed
759 760
  nua_client_request_t *cr = du->du_cr;
  nua_event_t e = nua_r_notify;
761

Pekka Pessi's avatar
Pekka Pessi committed
762
  if (cr) {
763
    int terminating = 0;
764

765
    if (nu->nu_expires && nu->nu_expires <= now)
766 767 768
      terminating = 1;
    else if (nu->nu_requested && nu->nu_requested <= now)
      terminating = 1;
769

770
    if (nua_client_resend_request(cr, terminating) >= 0)
Pekka Pessi's avatar
Pekka Pessi committed
771
      return;
772 773
  }
  else {
Pekka Pessi's avatar
Pekka Pessi committed
774 775
    if (nua_client_create(nh, e, &nua_notify_client_methods, NULL) >= 0)
      return;
776
  }
Pekka Pessi's avatar
Pekka Pessi committed
777

778 779 780
  nua_stack_tevent(nh->nh_nua, nh, NULL, e, NUA_INTERNAL_ERROR,
		   NUTAG_SUBSTATE(nua_substate_terminated),
		   TAG_END());
Pekka Pessi's avatar
Pekka Pessi committed
781 782

  nua_dialog_usage_remove(nh, ds, du);
783 784 785 786 787 788 789 790 791
}

/** @interal Shut down NOTIFY usage. 
 *
 * @retval >0  shutdown done
 * @retval 0   shutdown in progress
 * @retval <0  try again later
 */
static int nua_notify_usage_shutdown(nua_handle_t *nh,
792
				     nua_dialog_state_t *ds,
793 794
				     nua_dialog_usage_t *du)
{
Pekka Pessi's avatar
Pekka Pessi committed
795 796
  struct notifier_usage *nu = nua_dialog_usage_private(du);
  nua_client_request_t *cr = du->du_cr;
797

Pekka Pessi's avatar
Pekka Pessi committed
798
  nu->nu_substate = nua_substate_terminated;
799

Pekka Pessi's avatar
Pekka Pessi committed
800
  if (cr) {
801
    if (nua_client_resend_request(cr, 1) >= 0)
Pekka Pessi's avatar
Pekka Pessi committed
802 803 804
      return 0;
  }
  else {
805 806 807 808 809
    if (nua_client_tcreate(nh, nua_r_notify, 
			   &nua_notify_client_methods, 
			   SIPTAG_EVENT(du->du_event),
			   NUTAG_SUBSTATE(nua_substate_terminated),
			   TAG_END()) >= 0)
Pekka Pessi's avatar
Pekka Pessi committed
810 811
      return 0;
  }
812

Pekka Pessi's avatar
Pekka Pessi committed
813 814 815
  nua_dialog_usage_remove(nh, ds, du);
  return 200;
}
816 817 818 819

/* ======================================================================== */
/* REFER */
/* RFC 3515 */
820

821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
static int nua_refer_server_init(nua_server_request_t *sr);
static int nua_refer_server_preprocess(nua_server_request_t *sr);
static int nua_refer_server_respond(nua_server_request_t*, tagi_t const *);
static int nua_refer_server_report(nua_server_request_t*, tagi_t const *);

nua_server_methods_t const nua_refer_server_methods = 
  {
    SIP_METHOD_REFER,
    nua_i_refer,		/* Event */
    { 
      1,			/* Create dialog */
      0,			/* Initial request */
      1,			/* Target refresh request  */
      1,			/* Add Contact */
    },
    nua_refer_server_init,
    nua_refer_server_preprocess,
    nua_base_server_params,
    nua_refer_server_respond,
    nua_refer_server_report,
  };

static int nua_refer_server_init(nua_server_request_t *sr)
{
  return 0;
}

static int nua_refer_server_preprocess(nua_server_request_t *sr)
{
  nua_handle_t *nh = sr->sr_owner;
  sip_t const *sip = sr->sr_request.sip;
  struct notifier_usage *nu;
  sip_event_t *o;

  if (nh->nh_ds->ds_got_referrals || NH_PGET(nh, refer_with_id))
    o = sip_event_format(nh->nh_home, "refer;id=%u", sip->sip_cseq->cs_seq);
  else
    o = sip_event_make(nh->nh_home, "refer");

  if (o) {
    sr->sr_usage = nua_dialog_usage_add(nh, nh->nh_ds, nua_notify_usage, o);
    msg_header_free(nh->nh_home, (msg_header_t *)o);
  }

  if (!sr->sr_usage)
    return SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR);

  nu = nua_dialog_usage_private(sr->sr_usage);
  nu->nu_requested = sip_now() + NH_PGET(nh, refer_expires);

  return 0;
}

static
int nua_refer_server_respond(nua_server_request_t *sr, tagi_t const *tags)
{
  nua_handle_t *nh = sr->sr_owner;
  struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage);
  sip_refer_sub_t const *rs = sip_refer_sub(sr->sr_response.sip);

  if (sr->sr_status < 200 || nu == NULL) {
  }
  else if (sr->sr_status < 300 && 
	   /* Application included Refer-Sub: false in response */
	   (rs == NULL || str0casecmp("false", rs->rs_value))) {
    sr->sr_usage->du_ready = 1;

    nu->nu_expires = sip_now() + NH_PGET(nh, refer_expires);

    if (sr->sr_application)	/* Application responded to REFER */
      nu->nu_substate = nua_substate_active;
  }
  else {
    /* Destroy the implicit subscription usage */
    sr->sr_terminating = 1;
  }

  return nua_base_server_respond(sr, tags);
}

Pekka Pessi's avatar
Pekka Pessi committed
901

902
/** @NUA_EVENT nua_i_refer
903
 *
904 905 906 907 908 909 910 911 912 913 914 915
 * Incoming @b REFER request used to transfer calls. The tag list will
 * contain tag NUTAG_REFER_EVENT() with the @Event header constructed from
 * the REFER request. It will also contain the SIPTAG_REFERRED_BY() tag with
 * the @ReferredBy header containing the identity of the party sending the
 * REFER. The @ReferredBy structure contained in the tag is constructed from
 * the @From header if the @ReferredBy header was not present in the REFER
 * request.
 * 
 * The application can let the nua to send NOTIFYs from the call it
 * initiates with nua_invite() if it includes in the nua_invite() arguments
 * both the NUTAG_NOTIFY_REFER() with the handle with which nua_i_refer was
 * received and the NUTAG_REFER_EVENT() from #nua_i_refer event tags.
916
 *
917 918 919 920 921
 * @param status status code of response sent automatically by stack
 * @param phrase a short textual description of @a status code
 * @param nh     operation handle associated with the incoming request
 * @param hmagic application context associated with the handle
 *               (NULL if outside of an already established session)
922 923 924
 * @param sip    incoming REFER request
 * @param tags   NUTAG_REFER_EVENT() \n
 *               SIPTAG_REFERRED_BY()
925 926 927
 *  
 * @sa nua_refer(), #nua_r_refer, @ReferTo, NUTAG_REFER_EVENT(), 
 * SIPTAG_REFERRED_BY(), @ReferredBy, NUTAG_NOTIFY_REFER(),
928
 * NUTAG_REFER_WITH_ID(), @RFC3515.
929 930
 *
 * @END_NUA_EVENT
931 932
 */

933 934
static
int nua_refer_server_report(nua_server_request_t *sr, tagi_t const *tags)
935
{
936 937 938 939 940 941 942
  nua_handle_t *nh = sr->sr_owner;
  struct notifier_usage *nu = nua_dialog_usage_private(sr->sr_usage);
  sip_t const *sip = sr->sr_request.sip;
  sip_referred_by_t *by = sip->sip_referred_by, default_by[1];
  sip_event_t const *o = sr->sr_usage->du_event;
  enum nua_substate substate = nua_substate_terminated;
  int initial = sr->sr_initial, retval;
Pekka Pessi's avatar
Pekka Pessi committed
943

944 945 946
  if (nu) {
    if (!sr->sr_terminating)
      substate = nu->nu_substate;
Pekka Pessi's avatar
Pekka Pessi committed
947
  }
948

949 950
  if (by == NULL) {
     by = sip_referred_by_init(default_by);
951

952 953
    by->b_display = sip->sip_from->a_display;
    *by->b_url = *sip->sip_from->a_url;
954 955
  }

956 957 958 959 960
  retval = nua_base_server_treport(sr,
				   NUTAG_SUBSTATE(substate),
				   NUTAG_REFER_EVENT(o),
				   TAG_IF(by, SIPTAG_REFERRED_BY(by)),
				   TAG_END());
Pekka Pessi's avatar
Pekka Pessi committed
961

962 963
  if (retval >= 2 || nu == NULL)
    return retval;
964

965 966 967 968 969 970 971
  if (initial)
    nua_stack_post_signal(nh,
			  nua_r_notify,
			  SIPTAG_EVENT(o),
			  SIPTAG_CONTENT_TYPE_STR("message/sipfrag"),
			  SIPTAG_PAYLOAD_STR("SIP/2.0 100 Trying\r\n"),
			  TAG_END());
Pekka Pessi's avatar
Pekka Pessi committed
972

973
  return retval;
974
}