nua_publish.c 14.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_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
				      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 142 143 144
 *    Tags in <sip_tag.h>
 *
 * @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 200 201
 *    Tags in <sip_tag.h>
 *
 * @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
 */

230 231 232 233 234 235 236 237 238 239 240 241 242
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);
static int nua_publish_client_response(nua_client_request_t *cr,
				       int status, char const *phrase,
				       sip_t const *sip);

243
static nua_client_methods_t const nua_publish_client_methods = {
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
  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,
  /* nua_publish_client_check_restart */ NULL,
  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)
264
{
265
  return nua_client_create(nh, e, &nua_publish_client_methods, tags);
266 267
}

268 269 270
static int nua_publish_client_template(nua_client_request_t *cr,
				       msg_t **return_msg,
				       tagi_t const *tags)
271 272 273
{
  nua_dialog_usage_t *du;

274 275
  if (cr->cr_event == nua_r_publish)
    return 0;
276

277 278 279 280 281 282
  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;
283 284
  }

285 286
  return 0;
}
287

288 289 290 291 292 293 294 295 296
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) {
297
    du = nua_dialog_usage_add(nh, nh->nh_ds, nua_publish_usage, NULL);
298 299 300 301 302 303 304 305 306 307 308
    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);
    }
  }
309 310
  else
    du = nua_dialog_usage_get(nh->nh_ds, nua_publish_usage, NULL);
311

312
  cr->cr_usage = du;
313

314 315
  return 0;
}
316

317 318 319 320 321 322 323 324
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;
325

326 327 328
  un = cr->cr_terminating ||
    cr->cr_event != nua_r_publish ||
    (du && du->du_shutdown) ||
329
    (sip->sip_expires && sip->sip_expires->ex_delta == 0);
330 331
  cr->cr_terminating = un;
  done = un;
332

333 334
  if (du) {
    struct publish_usage *pu = nua_dialog_usage_private(du);
335

336 337 338 339 340 341
    if (nua_client_bind(cr, du) < 0)
      return -1;
    if (pu->pu_published)
      done = 1;
    etag = pu->pu_etag;
  }
342

343 344 345 346 347 348
  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));
349 350
}

351 352 353
static int nua_publish_client_response(nua_client_request_t *cr,
				       int status, char const *phrase,
				       sip_t const *sip)
354
{
355
  nua_handle_t *nh = cr->cr_owner;
356 357
  nua_dialog_usage_t *du = cr->cr_usage;

358 359 360
  if (!cr->cr_terminated && du && sip) {
    struct publish_usage *pu = nua_dialog_usage_private(du);
    sip_expires_t const *ex = sip->sip_expires;
361

362 363 364 365
    /* Reset state */
    pu->pu_published = 0;
    if (pu->pu_etag)
      su_free(nh->nh_home, pu->pu_etag), pu->pu_etag = NULL;
366

367 368 369
    if (status == 412) {
      if (nua_client_restart(cr, 100, phrase))
	return 0;
370
    }
371 372 373 374
    else if (status < 300) {
      if (ex && ex->ex_delta == 0 &&
	  nua_client_restart(cr, 100, "Trying re-PUBLISH"))
	return 0;
375

376
      pu->pu_published = 1;
377
      pu->pu_etag = sip_etag_dup(nh->nh_home, sip->sip_etag);
378 379 380 381 382 383 384 385 386

      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);
      }
387
    }
388 389
  }

390
  return nua_base_client_response(cr, status, phrase, sip, NULL);
391 392
}

393
static void nua_publish_usage_refresh(nua_handle_t *nh,
394 395 396
				     nua_dialog_state_t *ds,
				     nua_dialog_usage_t *du,
				     sip_time_t now)
397
{
398 399 400 401 402 403 404 405 406 407
  nua_client_request_t *cr = du->du_cr;

  if (cr) {
    if (nua_client_is_queued(cr) /* Already publishing. */
	|| nua_client_resend_request(cr, 0, NULL) >= 0)
      return;
  }

  nua_stack_event(nh->nh_nua, nh, NULL,
		  nua_r_publish, NUA_INTERNAL_ERROR,
408
		  NULL);
409 410

  nua_dialog_usage_remove(nh, ds, du);
411
}
412

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

425 426 427
  if (cr) {
    if (nua_client_is_queued(cr)) /* Already publishing. */
      return -1;
428

429 430 431
    if (nua_client_resend_request(cr, 1, NULL) >= 0)
      return 0;
  }
432

433 434 435
  /* XXX - report to user */
  nua_dialog_usage_remove(nh, ds, du);
  return 200;
436 437
}

438 439 440
/* ---------------------------------------------------------------------- */
/* Server side */

441
/** @NUA_EVENT nua_i_publish
442 443 444 445 446 447 448 449 450 451 452 453
 *
 * 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.
 *
454 455 456 457 458 459
 * 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.
 *
460 461 462 463 464
 * @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)
465 466 467 468 469 470 471
 * @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,
472 473
 *
 * @since First used in @VERSION_1_12_4
474 475
 *
 * @END_NUA_EVENT
476
 */
477

478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
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)
498
{
499 500
  sip_allow_events_t *allow_events = NH_PGET(sr->sr_owner, allow_events);
  sip_event_t *o = sr->sr_request.sip->sip_event;
501 502 503
  char const *event = o ? o->o_type : NULL;
  
  if (!allow_events)
504
    return SR_STATUS1(sr, SIP_501_NOT_IMPLEMENTED);
505
  else if (!event || !msg_header_find_param(allow_events->k_common, event))
506
    return SR_STATUS1(sr, SIP_489_BAD_EVENT);
507

508
  return 0;
509
}