nua_publish.c 15.5 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_publish.c
 * @brief PUBLISH and publications
 *
28 29
 * @sa @RFC3903
 *
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @date Created: Wed Mar  8 17:01:32 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>
#include <sofia-sip/sip_status.h>

#include "nua_stack.h"

/* ====================================================================== */
/* Publish usage */

struct publish_usage {
  sip_etag_t *pu_etag;
55
  int pu_published;
56 57 58
};

static char const *nua_publish_usage_name(nua_dialog_usage_t const *du);
Pekka Pessi's avatar
Pekka Pessi committed
59
static int nua_publish_usage_add(nua_handle_t *nh,
60 61
				  nua_dialog_state_t *ds,
				  nua_dialog_usage_t *du);
Pekka Pessi's avatar
Pekka Pessi committed
62
static void nua_publish_usage_remove(nua_handle_t *nh,
63 64 65 66
				     nua_dialog_state_t *ds,
				     nua_dialog_usage_t *du,
				     nua_client_request_t *cr,
				     nua_server_request_t *sr);
67
static void nua_publish_usage_refresh(nua_handle_t *nh,
68
				      nua_dialog_state_t *ds,
69 70 71
				      nua_dialog_usage_t *du,
				      sip_time_t now);
static int nua_publish_usage_shutdown(nua_handle_t *nh,
72
				      nua_dialog_state_t *ds,
73
				      nua_dialog_usage_t *du);
74 75 76 77 78 79 80 81

static nua_usage_class const nua_publish_usage[1] = {
  {
    sizeof (struct publish_usage),
    sizeof nua_publish_usage,
    nua_publish_usage_add,
    nua_publish_usage_remove,
    nua_publish_usage_name,
82
    nua_base_usage_update_params,
83 84 85
    NULL,
    nua_publish_usage_refresh,
    nua_publish_usage_shutdown,
86 87 88 89 90 91 92 93
  }};

static
char const *nua_publish_usage_name(nua_dialog_usage_t const *du)
{
  return "publish";
}

Pekka Pessi's avatar
Pekka Pessi committed
94 95
static
int nua_publish_usage_add(nua_handle_t *nh,
96 97 98 99 100 101 102 103 104
			   nua_dialog_state_t *ds,
			   nua_dialog_usage_t *du)
{
  if (ds->ds_has_publish)
    return -1;			/* There can be only one */
  ds->ds_has_publish = 1;
  return 0;
}

Pekka Pessi's avatar
Pekka Pessi committed
105 106
static
void nua_publish_usage_remove(nua_handle_t *nh,
107 108 109 110 111
			      nua_dialog_state_t *ds,
			      nua_dialog_usage_t *du,
			      nua_client_request_t *cr,
			      nua_server_request_t *sr
)
112
{
113
  struct publish_usage *pu = NUA_DIALOG_USAGE_PRIVATE(du);
114 115 116 117 118 119 120 121 122

  su_free(nh->nh_home, pu->pu_etag);

  ds->ds_has_publish = 0;	/* There can be only one */
}

/* ======================================================================== */
/* PUBLISH */

123 124 125 126 127 128 129 130 131
/**@fn \
 * void nua_publish(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...);
 *
 * Send PUBLISH request to publication server.
 *
 * Request status will be delivered to the application using #nua_r_publish
 * event. When successful the publication will be updated periodically until
 * nua_unpublish() is called or handle is destroyed. Note that the periodic
 * updates and unpublish do not include the original message body nor the @b
132 133 134
 * Content-Type header. Instead, the periodic update will include the
 * @SIPIfMatch header, which was generated from the latest @SIPETag
 * header received in response to @b PUBLISH request.
135 136 137 138 139 140 141 142 143 144 145
 *
 * The handle used for publication cannot be used for any other purposes.
 *
 * @param nh              Pointer to operation handle
 * @param tag, value, ... List of tagged parameters
 *
 * @return
 *    nothing
 *
 * @par Related Tags:
 *    NUTAG_URL() \n
146
 *    Tags of nua_set_hparams() \n
147
 *    Header tags defined in <sofia-sip/sip_tag.h>
148 149 150
 *
 * @par Events:
 *    #nua_r_publish
151 152 153
 *
 * @sa #nua_r_publish, @RFC3903, @SIPIfMatch,
 * nua_unpublish(), #nua_r_unpublish, #nua_i_publish
154 155
 */

156
/** @NUA_EVENT nua_r_publish
157
 *
158
 * Response to an outgoing PUBLISH.
159
 *
160 161
 * The PUBLISH request may be sent explicitly by nua_publish() or implicitly
 * by NUA state machine.
162
 *
163 164 165 166 167 168 169
 * @param status status code of PUBLISH request
 *               (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 publication
 * @param hmagic application context associated with the handle
170
 * @param sip    response to PUBLISH request or NULL upon an error
171
 *               (status code is in @a status and
172
 *                descriptive message in @a phrase parameters)
173
 * @param tags   empty
174 175 176 177 178
 *
 * @sa nua_publish(), @RFC3903, @SIPETag, @Expires,
 * nua_unpublish(), #nua_r_unpublish, #nua_i_publish
 *
 * @END_NUA_EVENT
179 180 181
 */

/**@fn \
182
void nua_unpublish(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...);
183
 *
184 185 186 187
 * Send un-PUBLISH request to publication server. Un-PUBLISH request is just
 * a PUBLISH request with @Expires set to 0. It is possible to un-publish a
 * publication not associated with the handle by providing correct ETag in
 * SIPTAG_IF_MATCH() or SIPTAG_IF_MATCH_STR() tags.
188
 *
189 190 191 192
 * Response to the un-PUBLISH request will be delivered to the application
 * using #nua_r_unpublish event.
 *
 * The handle used for publication cannot be used for any other purposes.
193 194 195 196 197 198 199 200 201
 *
 * @param nh              Pointer to operation handle
 * @param tag, value, ... List of tagged parameters
 *
 * @return
 *    nothing
 *
 * @par Related Tags:
 *    NUTAG_URL() \n
202 203
 *    SIPTAG_IF_MATCH(), SIPTAG_IF_MATCH_STR() \n
 *    SIPTAG_EVENT(), SIPTAG_EVENT_STR() \n
204
 *    Tags of nua_set_hparams() \n
205
 *    Other header tags defined in <sofia-sip/sip_tag.h> except SIPTAG_EXPIRES() or SIPTAG_EXPIRES_STR()
206 207
 *
 * @par Events:
208
 *    #nua_r_unpublish
209 210
 *
 * @sa #nua_r_unpublish, @RFC3903, @SIPIfMatch,
211
 * #nua_i_publish, nua_publish(), #nua_r_publish
212 213
 */

214
/** @NUA_EVENT nua_r_unpublish
215
 *
216
 * Response to an outgoing un-PUBLISH.
217
 *
218 219 220 221 222 223 224
 * @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 publication
 * @param hmagic application context associated with the handle
225
 * @param sip    response to PUBLISH request or NULL upon an error
226
 *               (status code is in @a status and
227
 *                descriptive message in @a phrase parameters)
228
 * @param tags   empty
229 230 231 232 233
 *
 * @sa nua_unpublish(), @RFC3903, @SIPETag, @Expires,
 * nua_publish(), #nua_r_publish, #nua_i_publish
 *
 * @END_NUA_EVENT
234 235
 */

236 237 238 239 240 241 242 243 244
static int nua_publish_client_template(nua_client_request_t *cr,
				       msg_t **return_msg,
				       tagi_t const *tags);
static int nua_publish_client_init(nua_client_request_t *cr,
				   msg_t *, sip_t *,
				   tagi_t const *tags);
static int nua_publish_client_request(nua_client_request_t *cr,
				      msg_t *, sip_t *,
				      tagi_t const *tags);
245 246 247
static int nua_publish_client_check_restart(nua_client_request_t *cr,
					    int status, char const *phrase,
					    sip_t const *sip);
248 249 250 251
static int nua_publish_client_response(nua_client_request_t *cr,
				       int status, char const *phrase,
				       sip_t const *sip);

252
static nua_client_methods_t const nua_publish_client_methods = {
253 254 255
  SIP_METHOD_PUBLISH,		/* crm_method, crm_method_name */
  0,				/* crm_extra */
  {				/* crm_flags */
256 257 258 259
    /* create_dialog */ 0,
    /* in_dialog */ 0,
    /* target refresh */ 0
  },
260 261 262 263 264 265 266 267
  nua_publish_client_template,	/* crm_template */
  nua_publish_client_init,	/* crm_init */
  nua_publish_client_request,	/* crm_send */
  nua_publish_client_check_restart, /* crm_check_restart */
  nua_publish_client_response,	/* crm_recv */
  NULL,				/* crm_preliminary */
  NULL,				/* crm_report */
  NULL,				/* crm_complete */
268 269 270 271 272 273 274
};

/**@internal Send PUBLISH. */
int nua_stack_publish(nua_t *nua,
		     nua_handle_t *nh,
		     nua_event_t e,
		     tagi_t const *tags)
275
{
276
  return nua_client_create(nh, e, &nua_publish_client_methods, tags);
277 278
}

279 280 281
static int nua_publish_client_template(nua_client_request_t *cr,
				       msg_t **return_msg,
				       tagi_t const *tags)
282 283 284
{
  nua_dialog_usage_t *du;

285 286
  if (cr->cr_event == nua_r_publish)
    return 0;
287

288 289 290 291 292 293
  du = nua_dialog_usage_get(cr->cr_owner->nh_ds, nua_publish_usage, NULL);
  if (du && du->du_cr) {
    if (nua_client_set_target(cr, du->du_cr->cr_target) < 0)
      return -1;
    *return_msg = msg_copy(du->du_cr->cr_msg);
    return 1;
294 295
  }

296 297
  return 0;
}
298

299 300 301 302 303 304 305 306 307
static int nua_publish_client_init(nua_client_request_t *cr,
				   msg_t *msg, sip_t *sip,
				   tagi_t const *tags)
{
  nua_handle_t *nh = cr->cr_owner;
  nua_dialog_usage_t *du;
  struct publish_usage *pu;

  if (cr->cr_event == nua_r_publish) {
308
    du = nua_dialog_usage_add(nh, nh->nh_ds, nua_publish_usage, NULL);
309 310 311 312 313 314 315 316 317 318 319
    if (!du)
      return -1;
    pu = nua_dialog_usage_private(du);
    pu->pu_published = 0;
    if (sip->sip_if_match) {
      pu->pu_etag = sip_etag_dup(nh->nh_home, sip->sip_if_match);
      if (!pu->pu_etag)
	return -1;
      sip_header_remove(msg, sip, (sip_header_t *)sip->sip_if_match);
    }
  }
320 321
  else
    du = nua_dialog_usage_get(nh->nh_ds, nua_publish_usage, NULL);
322

323
  cr->cr_usage = du;
324

325 326
  return 0;
}
327

328 329 330 331 332 333 334 335
static
int nua_publish_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;
  int un, done;
  sip_etag_t const *etag = NULL;
336

337 338 339
  un = cr->cr_terminating ||
    cr->cr_event != nua_r_publish ||
    (du && du->du_shutdown) ||
340
    (sip->sip_expires && sip->sip_expires->ex_delta == 0);
341
  nua_client_set_terminating(cr, un);
342
  done = un;
343

344 345
  if (du) {
    struct publish_usage *pu = nua_dialog_usage_private(du);
346

347 348 349 350 351 352
    if (nua_client_bind(cr, du) < 0)
      return -1;
    if (pu->pu_published)
      done = 1;
    etag = pu->pu_etag;
  }
353

354 355 356 357 358 359
  return nua_base_client_trequest(cr, msg, sip,
				  SIPTAG_IF_MATCH(etag),
				  TAG_IF(done, SIPTAG_PAYLOAD(NONE)),
				  TAG_IF(done, SIPTAG_CONTENT_TYPE(NONE)),
				  TAG_IF(un, SIPTAG_EXPIRES_STR("0")),
				  TAG_NEXT(tags));
360 361
}

362 363 364 365 366 367 368 369 370 371
static int nua_publish_client_check_restart(nua_client_request_t *cr,
					    int status, char const *phrase,
					    sip_t const *sip)
{
  char const *restarting = NULL;

  if (cr->cr_terminating || !cr->cr_usage)
    ;
  else if (status == 412)
    restarting = phrase;
372
  else if (200 <= status && status < 300 &&
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
	   sip->sip_expires && sip->sip_expires->ex_delta == 0)
    restarting = "Immediate re-PUBLISH";

  if (restarting) {
    struct publish_usage *pu = nua_dialog_usage_private(cr->cr_usage);

    if (pu) {
      pu->pu_published = 0;
      su_free(cr->cr_owner->nh_home, pu->pu_etag), pu->pu_etag = NULL;
      if (nua_client_restart(cr, 100, restarting))
	return 0;
    }
  }

  return nua_base_client_check_restart(cr, status, phrase, sip);
}

390 391 392
static int nua_publish_client_response(nua_client_request_t *cr,
				       int status, char const *phrase,
				       sip_t const *sip)
393
{
394
  nua_handle_t *nh = cr->cr_owner;
395 396
  nua_dialog_usage_t *du = cr->cr_usage;

397 398 399
  if (!cr->cr_terminated && du && sip) {
    struct publish_usage *pu = nua_dialog_usage_private(du);
    sip_expires_t const *ex = sip->sip_expires;
400

401 402 403 404
    /* Reset state */
    pu->pu_published = 0;
    if (pu->pu_etag)
      su_free(nh->nh_home, pu->pu_etag), pu->pu_etag = NULL;
405

406
    if (status < 300) {
407
      pu->pu_published = 1;
408
      pu->pu_etag = sip_etag_dup(nh->nh_home, sip->sip_etag);
409 410 411 412 413 414 415

      if (!ex || ex->ex_delta == 0 || !pu->pu_etag) {
	cr->cr_terminated = 1;

	if (!ex || ex->ex_delta == 0)
	  SET_STATUS(900, "Received Invalid Expiration Time");
	else
416
	  SET_STATUS1(NUA_ERROR_AT(__FILE__, __LINE__));
417
      }
418 419
      else
	nua_dialog_usage_set_refresh(du, ex->ex_delta);
420
    }
421 422
  }

423
  return nua_base_client_response(cr, status, phrase, sip, NULL);
424 425
}

426
static void nua_publish_usage_refresh(nua_handle_t *nh,
427 428 429
				     nua_dialog_state_t *ds,
				     nua_dialog_usage_t *du,
				     sip_time_t now)
430
{
431 432 433
  nua_client_request_t *cr = du->du_cr;

  if (cr) {
434
    if (nua_client_resend_request(cr, 0) >= 0)
435 436 437 438
      return;
  }

  nua_stack_event(nh->nh_nua, nh, NULL,
439
		  nua_r_publish, NUA_ERROR_AT(__FILE__, __LINE__),
440
		  NULL);
441

442
  nua_dialog_usage_remove(nh, ds, du, NULL, NULL);
443
}
444

445
/** @interal Shut down PUBLISH usage.
446 447 448 449 450 451
 *
 * @retval >0  shutdown done
 * @retval 0   shutdown in progress
 * @retval <0  try again later
 */
static int nua_publish_usage_shutdown(nua_handle_t *nh,
452 453
				     nua_dialog_state_t *ds,
				     nua_dialog_usage_t *du)
454
{
455
  nua_client_request_t *cr = du->du_cr;
456

457
  if (cr) {
458
    if (nua_client_resend_request(cr, 1) >= 0)
459 460
      return 0;
  }
461

462
  /* XXX - report to user */
463
  nua_dialog_usage_remove(nh, ds, du, NULL, NULL);
464
  return 200;
465 466
}

467 468 469
/* ---------------------------------------------------------------------- */
/* Server side */

470
/** @NUA_EVENT nua_i_publish
471 472 473 474 475
 *
 * Incoming PUBLISH request.
 *
 * In order to receive #nua_i_publish events, the application must enable
 * both the PUBLISH method with NUTAG_ALLOW() tag and the acceptable SIP
476
 * events with nua_set_params() tag NUTAG_ALLOW_EVENTS().
477 478
 *
 * The nua_response() call responding to a PUBLISH request must have
479
 * NUTAG_WITH() (or NUTAG_WITH_THIS()/NUTAG_WITH_SAVED()) tag. Note that
480 481 482
 * a successful response to PUBLISH @b MUST include @Expires and @SIPETag
 * headers.
 *
483 484 485 486 487 488
 * The PUBLISH request does not create a dialog. Currently the processing
 * of incoming PUBLISH creates a new handle for each incoming request which
 * is not assiciated with an existing dialog. If the handle @a nh is not
 * bound, you should probably destroy it after responding to the PUBLISH
 * request.
 *
489 490 491 492 493
 * @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 call
 *               (usually NULL)
494 495 496 497
 * @param sip    incoming PUBLISH request
 * @param tags   empty
 *
 * @sa @RFC3903, nua_respond(),
498 499
 * @Expires, @SIPETag, @SIPIfMatch, @Event,
 * nua_subscribe(), #nua_i_subscribe,
500
 * nua_notifier(), #nua_i_subscription,
501 502
 *
 * @since First used in @VERSION_1_12_4
503 504
 *
 * @END_NUA_EVENT
505
 */
506

507 508
int nua_publish_server_init(nua_server_request_t *sr);

509
nua_server_methods_t const nua_publish_server_methods =
510 511 512
  {
    SIP_METHOD_PUBLISH,
    nua_i_publish,		/* Event */
513
    {
514 515 516 517 518 519 520 521 522 523 524 525 526
      0,			/* Do not create dialog */
      0,			/* Initial request */
      0,			/* Not a target refresh request  */
      1,			/* Add Contact */
    },
    nua_publish_server_init,
    nua_base_server_preprocess,
    nua_base_server_params,
    nua_base_server_respond,
    nua_base_server_report,
  };

int nua_publish_server_init(nua_server_request_t *sr)
527
{
528 529
  sip_allow_events_t *allow_events = NH_PGET(sr->sr_owner, allow_events);
  sip_event_t *o = sr->sr_request.sip->sip_event;
530
  char const *event = o ? o->o_type : NULL;
531

532
  if (!allow_events)
533
    return SR_STATUS1(sr, SIP_501_NOT_IMPLEMENTED);
534
  else if (!event || !msg_header_find_param(allow_events->k_common, event))
535
    return SR_STATUS1(sr, SIP_489_BAD_EVENT);
536

537
  return 0;
538
}