nua_publish.c 15.3 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 55 56 57 58 59 60
 * @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>

#define NTA_LEG_MAGIC_T      struct nua_handle_s
#define NTA_OUTGOING_MAGIC_T struct nua_handle_s

#include "nua_stack.h"

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

struct publish_usage {
  sip_etag_t *pu_etag;
};

static char const *nua_publish_usage_name(nua_dialog_usage_t const *du);
Pekka Pessi's avatar
Pekka Pessi committed
61
static int nua_publish_usage_add(nua_handle_t *nh,
62 63
				  nua_dialog_state_t *ds,
				  nua_dialog_usage_t *du);
Pekka Pessi's avatar
Pekka Pessi committed
64
static void nua_publish_usage_remove(nua_handle_t *nh,
65 66
				      nua_dialog_state_t *ds,
				      nua_dialog_usage_t *du);
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 83 84
    NULL,
    nua_publish_usage_refresh,
    nua_publish_usage_shutdown,
85 86 87 88 89 90 91 92
  }};

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

Pekka Pessi's avatar
Pekka Pessi committed
93 94
static
int nua_publish_usage_add(nua_handle_t *nh,
95 96 97 98 99 100 101 102 103
			   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
104 105
static
void nua_publish_usage_remove(nua_handle_t *nh,
106 107 108 109 110 111 112 113 114 115 116 117 118
			       nua_dialog_state_t *ds,
			       nua_dialog_usage_t *du)
{
  struct publish_usage *pu = nua_dialog_usage_private(du);

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

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

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

Pekka Pessi's avatar
Pekka Pessi committed
119 120
static int nua_stack_publish2(nua_t *nua, nua_handle_t *nh, nua_event_t e,
			      int refresh, tagi_t const *tags);
121

122 123 124 125
static int process_response_to_publish(nua_handle_t *nh,
				       nta_outgoing_t *orq,
				       sip_t const *sip);

126 127 128 129 130 131 132 133 134 135

/**@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
136 137 138
 * 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.
139 140 141 142 143 144 145 146 147 148 149
 *
 * 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
150
 *    Tags of nua_set_hparams() \n
151 152 153 154
 *    Tags in <sip_tag.h>
 *
 * @par Events:
 *    #nua_r_publish
155 156 157
 *
 * @sa #nua_r_publish, @RFC3903, @SIPIfMatch,
 * nua_unpublish(), #nua_r_unpublish, #nua_i_publish
158 159
 */

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

/**@fn \
186
void nua_unpublish(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...);
187
 *
188 189 190 191
 * 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.
192
 *
193 194 195 196
 * 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.
197 198 199 200 201 202 203 204 205
 *
 * @param nh              Pointer to operation handle
 * @param tag, value, ... List of tagged parameters
 *
 * @return
 *    nothing
 *
 * @par Related Tags:
 *    NUTAG_URL() \n
206 207
 *    SIPTAG_IF_MATCH(), SIPTAG_IF_MATCH_STR() \n
 *    SIPTAG_EVENT(), SIPTAG_EVENT_STR() \n
208
 *    Tags of nua_set_hparams() \n
209 210 211
 *    Tags in <sip_tag.h>
 *
 * @par Events:
212 213 214 215
 *    #nua_r_unpublish
 * 
 * @sa #nua_r_unpublish, @RFC3903, @SIPIfMatch, 
 * #nua_i_publish, nua_publish(), #nua_r_publish
216 217
 */

218
/** @NUA_EVENT nua_r_unpublish
219
 *
220
 * Response to an outgoing un-PUBLISH.
221
 *
222 223 224 225 226 227 228
 * @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
229
 * @param sip    response to PUBLISH request or NULL upon an error
230 231
 *               (status code is in @a status and 
 *                descriptive message in @a phrase parameters)
232
 * @param tags   empty
233 234 235 236 237
 *
 * @sa nua_unpublish(), @RFC3903, @SIPETag, @Expires,
 * nua_publish(), #nua_r_publish, #nua_i_publish
 *
 * @END_NUA_EVENT
238 239 240 241
 */

int nua_stack_publish(nua_t *nua, nua_handle_t *nh, nua_event_t e,
		      tagi_t const *tags)
242 243 244 245
{
  return nua_stack_publish2(nua, nh, e, 0, tags);
}

Pekka Pessi's avatar
Pekka Pessi committed
246 247 248 249
static
int nua_stack_publish2(nua_t *nua, nua_handle_t *nh, nua_event_t e,
		       int refresh,
		       tagi_t const *tags)
250 251 252
{
  nua_dialog_usage_t *du;
  struct publish_usage *pu;
253
  nua_client_request_t *cr = nh->nh_ds->ds_cr;
254 255
  msg_t *msg = NULL;
  sip_t *sip;
256
  int remove_body = 0;
257

258
  if (nua_stack_set_handle_special(nh, nh_has_nothing, nua_r_publish) < 0)
259
    return UA_EVENT2(e, 900, "Invalid handle for PUBLISH");
260 261

  if (cr->cr_orq) {
262
    return UA_EVENT2(e, 900, "Request already in progress");
263 264
  }

265
  nua_stack_init_handle(nua, nh, TAG_NEXT(tags));
266

267 268 269 270 271 272 273 274
  if (e == nua_r_unpublish) {
    du = nua_dialog_usage_get(nh->nh_ds, nua_publish_usage, NULL);
    if (du)
      refresh = 1;
    else
      du = nua_dialog_usage_add(nh, nh->nh_ds, nua_publish_usage, NULL);
  }
  else if (!refresh)
275 276 277
    du = nua_dialog_usage_add(nh, nh->nh_ds, nua_publish_usage, NULL);
  else
    du = nua_dialog_usage_get(nh->nh_ds, nua_publish_usage, NULL);
278 279

  if (!du)
280
    return UA_EVENT1(e, NUA_INTERNAL_ERROR);
281

282
  nua_dialog_usage_reset_refresh(du);
283 284 285
  pu = nua_dialog_usage_private(du); assert(pu);

  if (refresh) {
286 287 288 289
    if (cr->cr_msg)
      msg_destroy(cr->cr_msg);
    cr->cr_msg = msg_copy(du->du_msg);
    remove_body = pu->pu_etag != NULL;
290 291 292
  }

  msg = nua_creq_msg(nua, nh, cr, cr->cr_retry_count || refresh,
Pekka Pessi's avatar
Pekka Pessi committed
293 294 295
		     SIP_METHOD_PUBLISH,
		     NUTAG_ADD_CONTACT(0),
		     TAG_NEXT(tags));
296 297
  sip = sip_object(msg);

298 299 300
  if (!msg || !sip) 
    goto error;

Pekka Pessi's avatar
Pekka Pessi committed
301
  du->du_terminating =
302
    e != nua_r_publish ||
303
    (sip->sip_expires && sip->sip_expires->ex_delta == 0);
304

305 306
  if (!du->du_terminating && !refresh) {
    /* Save template */
307 308 309 310 311
    if (du->du_msg)
      msg_destroy(du->du_msg);
    du->du_msg = msg_ref_create(cr->cr_msg);
  }

Pekka Pessi's avatar
Pekka Pessi committed
312
  cr->cr_orq =
313 314 315 316
    nta_outgoing_mcreate(nua->nua_nta,
			 process_response_to_publish, nh, NULL,
			 msg,
			 SIPTAG_IF_MATCH(pu->pu_etag),
317 318
			 TAG_IF(remove_body, SIPTAG_PAYLOAD(NONE)),
			 TAG_IF(remove_body, SIPTAG_CONTENT_TYPE(NONE)),
319 320 321
			 TAG_IF(e != nua_r_publish,
				SIPTAG_EXPIRES_STR("0")),
			 SIPTAG_END(), TAG_NEXT(tags));
322 323
  if (!cr->cr_orq)
    goto error;
324 325 326 327

  cr->cr_usage = du;

  return cr->cr_event = e;
328 329 330

 error:
  msg_destroy(msg);
331
  if (!du->du_ready == 0)
332 333
    nua_dialog_usage_remove(nh, nh->nh_ds, du);
  return UA_EVENT1(e, NUA_INTERNAL_ERROR);
334 335 336
}


Pekka Pessi's avatar
Pekka Pessi committed
337
static void
338 339
restart_publish(nua_handle_t *nh, tagi_t *tags)
{
340
  nua_creq_restart(nh, nh->nh_ds->ds_cr, process_response_to_publish, tags);
341 342 343
}


Pekka Pessi's avatar
Pekka Pessi committed
344
static
345 346 347 348 349
int process_response_to_publish(nua_handle_t *nh,
				nta_outgoing_t *orq,
				sip_t const *sip)
{
  int status = sip->sip_status->st_status;
350
  nua_client_request_t *cr = nh->nh_ds->ds_cr;
351 352
  nua_dialog_usage_t *du = cr->cr_usage;
  struct publish_usage *pu = nua_dialog_usage_private(du);
353
  unsigned saved_retry_count = cr->cr_retry_count + 1;
354 355 356 357

  if (nua_creq_check_restart(nh, cr, orq, sip, restart_publish))
    return 0;

358
  if (status < 200 || pu == NULL)
359
    return nua_stack_process_response(nh, cr, orq, sip, TAG_END());
360 361 362 363 364 365

  if (pu->pu_etag)
    su_free(nh->nh_home, pu->pu_etag), pu->pu_etag = NULL;

  if (!du->du_terminating) {
    int retry = 0, invalid_expiration = 0;
366

367 368 369 370 371 372 373 374
    if (status < 300) {
      if (!sip->sip_expires)
	invalid_expiration = 1;
      else if (sip->sip_expires->ex_delta == 0)
	retry = 1, invalid_expiration = 1;
    }
    else if (status == 412)
      retry = 1;
375

376
    if (status < 300 && !invalid_expiration && !retry) {
377
      pu->pu_etag = sip_etag_dup(nh->nh_home, sip->sip_etag);
378
      du->du_ready = 1;
379
      nua_dialog_usage_set_expires(du, sip->sip_expires->ex_delta);
380 381 382
    }
    else if (retry && saved_retry_count < NH_PGET(nh, retry_count)) {
      msg_t *response = nta_outgoing_getresponse(orq);
Pekka Pessi's avatar
Pekka Pessi committed
383
      nua_stack_event(nh->nh_nua, nh, response, cr->cr_event,
384 385 386
      		100, "Trying re-PUBLISH",
      		TAG_END());
      nua_creq_deinit(cr, orq);
Pekka Pessi's avatar
Pekka Pessi committed
387
      nua_stack_publish2(nh->nh_nua, nh, cr->cr_event, 1, NULL);
388 389
      cr->cr_retry_count = saved_retry_count;
      return 0;
390
    }
391 392
    else if (invalid_expiration) {
      msg_t *response = nta_outgoing_getresponse(orq);
393 394
      nua_stack_event(nh->nh_nua, nh, response, cr->cr_event,
      		900, "Received Invalid Expiration Time",
395 396 397 398 399 400
      		TAG_END());
      nua_dialog_usage_remove(nh, nh->nh_ds, cr->cr_usage);
      nua_creq_deinit(cr, orq);
      cr->cr_usage = NULL;
      return 0;
    }
401 402
  }

403
  return nua_stack_process_response(nh, cr, orq, sip, TAG_END());
404 405 406
}


407
static void nua_publish_usage_refresh(nua_handle_t *nh,
408
				      nua_dialog_state_t *ds,
409 410
				      nua_dialog_usage_t *du,
				      sip_time_t now)
411
{
412
  if (ds->ds_cr->cr_usage == du) /* Already publishing. */
413 414 415
    return;
  nua_stack_publish2(nh->nh_nua, nh, nua_r_publish, 1, NULL);
}
416

417 418 419 420 421 422 423
/** @interal Shut down PUBLISH usage. 
 *
 * @retval >0  shutdown done
 * @retval 0   shutdown in progress
 * @retval <0  try again later
 */
static int nua_publish_usage_shutdown(nua_handle_t *nh,
424
				      nua_dialog_state_t *ds,
425 426
				      nua_dialog_usage_t *du)
{
427 428 429
  nua_client_request_t *cr = ds->ds_cr;

  if (!cr->cr_usage) {
430
    /* Unpublish */
431
    nua_stack_publish2(nh->nh_nua, nh, nua_r_destroy, 1, NULL);
432
    return cr->cr_usage != du;
433 434
  }

435 436
  if (!du->du_ready && !cr->cr_orq)
    return 1;			/* had unauthenticated initial request */
437 438

  return -1;  /* Request in progress */
439 440
}

441 442 443 444 445 446
/* ---------------------------------------------------------------------- */
/* Server side */

static
int respond_to_publish(nua_server_request_t *sr, tagi_t const *tags);

447
/** @NUA_EVENT nua_i_publish
448 449 450 451 452 453 454 455 456 457 458 459
 *
 * 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
 * events with nua_set_params() tag NUTAG_ALLOW_EVENTS(). 
 *
 * The nua_response() call responding to a PUBLISH request must have
 * NUTAG_WITH() (or NUTAG_WITH_CURRENT()/NUTAG_WITH_SAVED()) tag. Note that
 * a successful response to PUBLISH @b MUST include @Expires and @SIPETag
 * headers.
 *
460 461 462 463 464 465
 * 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.
 *
466 467 468 469 470
 * @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)
471 472 473 474 475 476 477
 * @param sip    incoming PUBLISH request
 * @param tags   empty
 *
 * @sa @RFC3903, nua_respond(),
 * @Expires, @SIPETag, @SIPIfMatch, @Event, 
 * nua_subscribe(), #nua_i_subscribe, 
 * nua_notifier(), #nua_i_subscription,
478 479
 *
 * @since First used in @VERSION_1_12_4
480 481
 *
 * @END_NUA_EVENT
482
 */
483 484 485 486 487 488

int nua_stack_process_publish(nua_t *nua,
			      nua_handle_t *nh,
			      nta_incoming_t *irq,
			      sip_t const *sip)
{
489 490 491 492 493 494 495 496 497 498 499 500 501
  nua_server_request_t *sr, sr0[1];
  sip_allow_events_t *allow_events = NUA_PGET(nua, nh, allow_events);
  sip_event_t *o = sip->sip_event;
  char const *event = o ? o->o_type : NULL;
  
  sr = SR_INIT(sr0);
  
  if (!allow_events)
    SR_STATUS1(sr, SIP_501_NOT_IMPLEMENTED);
  else if (!event || !msg_header_find_param(allow_events->k_common, event))
    SR_STATUS1(sr, SIP_489_BAD_EVENT);

  sr = nua_server_request(nua, nh, irq, sip, sr, sizeof *sr,
502
			  respond_to_publish, 0);
503

504
  return nua_stack_server_event(nua, sr, nua_i_publish, TAG_END());
505
}
506

507 508 509 510 511 512 513 514
static
int respond_to_publish(nua_server_request_t *sr, tagi_t const *tags)
{
  nua_handle_t *nh = sr->sr_owner;
  nua_t *nua = nh->nh_nua;
  msg_t *msg;

  msg = nua_server_response(sr, sr->sr_status, sr->sr_phrase, TAG_NEXT(tags));
515

516 517 518 519 520 521 522 523 524 525 526 527
  if (msg) {
    nta_incoming_mreply(sr->sr_irq, msg);
  }
  else {
    SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR);
    nta_incoming_treply(sr->sr_irq, sr->sr_status, sr->sr_phrase, TAG_END());
    nua_stack_event(nua, nh, NULL,
		    nua_i_error, 900, "PUBLISH Response Fails",
		    TAG_END());
  }
  
  return sr->sr_status >= 200 ? sr->sr_status : 0;
528
}