nua_publish.c 15.1 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;
Pekka Pessi's avatar
Pekka Pessi committed
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
				      nua_dialog_state_t *ds,
				      nua_dialog_usage_t *du);
65
static void nua_publish_usage_refresh(nua_handle_t *nh,
66
				      nua_dialog_state_t *ds,
67 68 69
				      nua_dialog_usage_t *du,
				      sip_time_t now);
static int nua_publish_usage_shutdown(nua_handle_t *nh,
70
				      nua_dialog_state_t *ds,
71
				      nua_dialog_usage_t *du);
72 73 74 75 76 77 78 79

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,
80 81 82
    NULL,
    nua_publish_usage_refresh,
    nua_publish_usage_shutdown,
83 84 85 86 87 88 89 90
  }};

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

Pekka Pessi's avatar
Pekka Pessi committed
91 92
static
int nua_publish_usage_add(nua_handle_t *nh,
93 94 95 96 97 98 99 100 101
			   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
102 103
static
void nua_publish_usage_remove(nua_handle_t *nh,
104 105 106 107 108 109 110 111 112 113 114 115 116
			       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 */

117 118 119 120 121 122 123 124 125
/**@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
126 127 128
 * 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.
129 130 131 132 133 134 135 136 137 138 139
 *
 * 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
140
 *    Tags of nua_set_hparams() \n
141
 *    Header tags defined in <sofia-sip/sip_tag.h>
142 143 144
 *
 * @par Events:
 *    #nua_r_publish
145 146 147
 *
 * @sa #nua_r_publish, @RFC3903, @SIPIfMatch,
 * nua_unpublish(), #nua_r_unpublish, #nua_i_publish
148 149
 */

150
/** @NUA_EVENT nua_r_publish
151
 *
152
 * Response to an outgoing PUBLISH.
153
 *
154 155
 * The PUBLISH request may be sent explicitly by nua_publish() or implicitly
 * by NUA state machine.
156
 *
157 158 159 160 161 162 163
 * @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
164
 * @param sip    response to PUBLISH request or NULL upon an error
165 166
 *               (status code is in @a status and 
 *                descriptive message in @a phrase parameters)
167
 * @param tags   empty
168 169 170 171 172
 *
 * @sa nua_publish(), @RFC3903, @SIPETag, @Expires,
 * nua_unpublish(), #nua_r_unpublish, #nua_i_publish
 *
 * @END_NUA_EVENT
173 174 175
 */

/**@fn \
176
void nua_unpublish(nua_handle_t *nh, tag_type_t tag, tag_value_t value, ...);
177
 *
178 179 180 181
 * 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.
182
 *
183 184 185 186
 * 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.
187 188 189 190 191 192 193 194 195
 *
 * @param nh              Pointer to operation handle
 * @param tag, value, ... List of tagged parameters
 *
 * @return
 *    nothing
 *
 * @par Related Tags:
 *    NUTAG_URL() \n
196 197
 *    SIPTAG_IF_MATCH(), SIPTAG_IF_MATCH_STR() \n
 *    SIPTAG_EVENT(), SIPTAG_EVENT_STR() \n
198
 *    Tags of nua_set_hparams() \n
199
 *    Other header tags defined in <sofia-sip/sip_tag.h> except SIPTAG_EXPIRES() or SIPTAG_EXPIRES_STR()
200 201
 *
 * @par Events:
202 203 204 205
 *    #nua_r_unpublish
 * 
 * @sa #nua_r_unpublish, @RFC3903, @SIPIfMatch, 
 * #nua_i_publish, nua_publish(), #nua_r_publish
206 207
 */

208
/** @NUA_EVENT nua_r_unpublish
209
 *
210
 * Response to an outgoing un-PUBLISH.
211
 *
212 213 214 215 216 217 218
 * @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
219
 * @param sip    response to PUBLISH request or NULL upon an error
220 221
 *               (status code is in @a status and 
 *                descriptive message in @a phrase parameters)
222
 * @param tags   empty
223 224 225 226 227
 *
 * @sa nua_unpublish(), @RFC3903, @SIPETag, @Expires,
 * nua_publish(), #nua_r_publish, #nua_i_publish
 *
 * @END_NUA_EVENT
228 229
 */

Pekka Pessi's avatar
Pekka Pessi committed
230 231 232 233 234 235 236 237 238
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);
239 240 241
static int nua_publish_client_check_restart(nua_client_request_t *cr,
					    int status, char const *phrase,
					    sip_t const *sip);
Pekka Pessi's avatar
Pekka Pessi committed
242 243 244 245
static int nua_publish_client_response(nua_client_request_t *cr,
				       int status, char const *phrase,
				       sip_t const *sip);

246
static nua_client_methods_t const nua_publish_client_methods = {
Pekka Pessi's avatar
Pekka Pessi committed
247 248 249 250 251 252 253 254 255 256
  SIP_METHOD_PUBLISH,
  0,
  {
    /* create_dialog */ 0,
    /* in_dialog */ 0,
    /* target refresh */ 0
  },
  nua_publish_client_template,
  nua_publish_client_init,
  nua_publish_client_request,
257
  nua_publish_client_check_restart,
Pekka Pessi's avatar
Pekka Pessi committed
258 259 260 261 262 263 264 265 266
  nua_publish_client_response,
  /* nua_publish_client_preliminary */ NULL
};

/**@internal Send PUBLISH. */
int nua_stack_publish(nua_t *nua,
		     nua_handle_t *nh,
		     nua_event_t e,
		     tagi_t const *tags)
267
{
Pekka Pessi's avatar
Pekka Pessi committed
268
  return nua_client_create(nh, e, &nua_publish_client_methods, tags);
269 270
}

Pekka Pessi's avatar
Pekka Pessi committed
271 272 273
static int nua_publish_client_template(nua_client_request_t *cr,
				       msg_t **return_msg,
				       tagi_t const *tags)
274 275 276
{
  nua_dialog_usage_t *du;

Pekka Pessi's avatar
Pekka Pessi committed
277 278
  if (cr->cr_event == nua_r_publish)
    return 0;
279

Pekka Pessi's avatar
Pekka Pessi committed
280 281 282 283 284 285
  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;
286 287
  }

Pekka Pessi's avatar
Pekka Pessi committed
288 289
  return 0;
}
290

Pekka Pessi's avatar
Pekka Pessi committed
291 292 293 294 295 296 297 298 299
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) {
300
    du = nua_dialog_usage_add(nh, nh->nh_ds, nua_publish_usage, NULL);
Pekka Pessi's avatar
Pekka Pessi committed
301 302 303 304 305 306 307 308 309 310 311
    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);
    }
  }
312 313
  else
    du = nua_dialog_usage_get(nh->nh_ds, nua_publish_usage, NULL);
314

Pekka Pessi's avatar
Pekka Pessi committed
315
  cr->cr_usage = du;
316

Pekka Pessi's avatar
Pekka Pessi committed
317 318
  return 0;
}
319

Pekka Pessi's avatar
Pekka Pessi committed
320 321 322 323 324 325 326 327
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;
328

Pekka Pessi's avatar
Pekka Pessi committed
329 330 331
  un = cr->cr_terminating ||
    cr->cr_event != nua_r_publish ||
    (du && du->du_shutdown) ||
332
    (sip->sip_expires && sip->sip_expires->ex_delta == 0);
Pekka Pessi's avatar
Pekka Pessi committed
333 334
  cr->cr_terminating = un;
  done = un;
335

Pekka Pessi's avatar
Pekka Pessi committed
336 337
  if (du) {
    struct publish_usage *pu = nua_dialog_usage_private(du);
338

Pekka Pessi's avatar
Pekka Pessi committed
339 340 341 342 343 344
    if (nua_client_bind(cr, du) < 0)
      return -1;
    if (pu->pu_published)
      done = 1;
    etag = pu->pu_etag;
  }
345

Pekka Pessi's avatar
Pekka Pessi committed
346 347 348 349 350 351
  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));
352 353
}

354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
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;
  else if (200 <= status && status < 300 && 
	   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);
}

Pekka Pessi's avatar
Pekka Pessi committed
382 383 384
static int nua_publish_client_response(nua_client_request_t *cr,
				       int status, char const *phrase,
				       sip_t const *sip)
385
{
Pekka Pessi's avatar
Pekka Pessi committed
386
  nua_handle_t *nh = cr->cr_owner;
387 388
  nua_dialog_usage_t *du = cr->cr_usage;

Pekka Pessi's avatar
Pekka Pessi committed
389 390 391
  if (!cr->cr_terminated && du && sip) {
    struct publish_usage *pu = nua_dialog_usage_private(du);
    sip_expires_t const *ex = sip->sip_expires;
392

Pekka Pessi's avatar
Pekka Pessi committed
393 394 395 396
    /* Reset state */
    pu->pu_published = 0;
    if (pu->pu_etag)
      su_free(nh->nh_home, pu->pu_etag), pu->pu_etag = NULL;
397

398
    if (status < 300) {
Pekka Pessi's avatar
Pekka Pessi committed
399
      pu->pu_published = 1;
400
      pu->pu_etag = sip_etag_dup(nh->nh_home, sip->sip_etag);
Pekka Pessi's avatar
Pekka Pessi committed
401 402 403 404 405 406 407 408 409

      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
	  SET_STATUS1(NUA_INTERNAL_ERROR);
      }
410 411
      else
	nua_dialog_usage_set_refresh(du, ex->ex_delta);
412
    }
413 414
  }

Pekka Pessi's avatar
Pekka Pessi committed
415
  return nua_base_client_response(cr, status, phrase, sip, NULL);
416 417
}

418
static void nua_publish_usage_refresh(nua_handle_t *nh,
Pekka Pessi's avatar
Pekka Pessi committed
419 420 421
				     nua_dialog_state_t *ds,
				     nua_dialog_usage_t *du,
				     sip_time_t now)
422
{
Pekka Pessi's avatar
Pekka Pessi committed
423 424 425
  nua_client_request_t *cr = du->du_cr;

  if (cr) {
426
    if (nua_client_resend_request(cr, 0) >= 0)
Pekka Pessi's avatar
Pekka Pessi committed
427 428 429 430 431
      return;
  }

  nua_stack_event(nh->nh_nua, nh, NULL,
		  nua_r_publish, NUA_INTERNAL_ERROR,
432
		  NULL);
Pekka Pessi's avatar
Pekka Pessi committed
433 434

  nua_dialog_usage_remove(nh, ds, du);
435
}
436

Pekka Pessi's avatar
Pekka Pessi committed
437
/** @interal Shut down PUBLISH usage.
438 439 440 441 442 443
 *
 * @retval >0  shutdown done
 * @retval 0   shutdown in progress
 * @retval <0  try again later
 */
static int nua_publish_usage_shutdown(nua_handle_t *nh,
Pekka Pessi's avatar
Pekka Pessi committed
444 445
				     nua_dialog_state_t *ds,
				     nua_dialog_usage_t *du)
446
{
Pekka Pessi's avatar
Pekka Pessi committed
447
  nua_client_request_t *cr = du->du_cr;
448

Pekka Pessi's avatar
Pekka Pessi committed
449
  if (cr) {
450
    if (nua_client_resend_request(cr, 1) >= 0)
Pekka Pessi's avatar
Pekka Pessi committed
451 452
      return 0;
  }
453

Pekka Pessi's avatar
Pekka Pessi committed
454 455 456
  /* XXX - report to user */
  nua_dialog_usage_remove(nh, ds, du);
  return 200;
457 458
}

459 460 461
/* ---------------------------------------------------------------------- */
/* Server side */

462
/** @NUA_EVENT nua_i_publish
463 464 465 466 467 468 469 470
 *
 * 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
471
 * NUTAG_WITH() (or NUTAG_WITH_THIS()/NUTAG_WITH_SAVED()) tag. Note that
472 473 474
 * a successful response to PUBLISH @b MUST include @Expires and @SIPETag
 * headers.
 *
475 476 477 478 479 480
 * 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.
 *
481 482 483 484 485
 * @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)
486 487 488 489 490 491 492
 * @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,
493 494
 *
 * @since First used in @VERSION_1_12_4
495 496
 *
 * @END_NUA_EVENT
497
 */
498

499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
int nua_publish_server_init(nua_server_request_t *sr);

nua_server_methods_t const nua_publish_server_methods = 
  {
    SIP_METHOD_PUBLISH,
    nua_i_publish,		/* Event */
    { 
      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)
519
{
520 521
  sip_allow_events_t *allow_events = NH_PGET(sr->sr_owner, allow_events);
  sip_event_t *o = sr->sr_request.sip->sip_event;
522 523 524
  char const *event = o ? o->o_type : NULL;
  
  if (!allow_events)
525
    return SR_STATUS1(sr, SIP_501_NOT_IMPLEMENTED);
526
  else if (!event || !msg_header_find_param(allow_events->k_common, event))
527
    return SR_STATUS1(sr, SIP_489_BAD_EVENT);
528

529
  return 0;
530
}