sip_feature.c 17.2 KB
Newer Older
Pekka Pessi's avatar
Pekka Pessi committed
1 2 3 4 5 6 7
/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2005 Nokia Corporation.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
8
 * This library is free software; you can redistribute it and/or
Pekka Pessi's avatar
Pekka Pessi committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
 * 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 sip_feature.c 
 *
 * @brief Feature-related SIP header handling
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>.
 *
 * @date Created: Tue Jun 13 02:57:51 2000 ppessi
 */

#include "config.h"

36 37 38 39 40 41
/* Avoid casting sip_t to msg_pub_t and sip_header_t to msg_header_t */
#define MSG_PUB_T       struct sip_s
#define MSG_HDR_T       union sip_header_u

#include "sofia-sip/sip_parser.h"

Pekka Pessi's avatar
Pekka Pessi committed
42 43 44 45 46 47 48 49 50 51
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

/* ====================================================================== */

/**@SIP_HEADER sip_allow Allow Header
 *
 * The Allow header lists the set of methods supported by the user agent
52
 * generating the message.  Its syntax is defined in @RFC3261 as
Pekka Pessi's avatar
Pekka Pessi committed
53 54 55 56 57 58
 * follows:
 * 
 * @code
 *    Allow  =  "Allow" HCOLON [Method *(COMMA Method)]
 * @endcode
 *
59
 * The parsed Allow header is stored in #sip_allow_t structure.
60 61 62 63 64 65
 *
 * Note that SIP methods are case-sensitive: "INVITE" method is different from
 * "Invite".
 *
 * @sa msg_header_find_item(), msg_header_replace_item(),
 * msg_header_remove_item()
Pekka Pessi's avatar
Pekka Pessi committed
66 67
 */

68 69 70
/**@ingroup sip_allow
 * @typedef struct msg_list_s sip_allow_t; 
 *
71
 * The structure #sip_allow_t contains representation of an @Allow header.
72
 *
73
 * The #sip_allow_t is defined as follows:
74
 * @code
75
 * typedef struct msg_allow_s
76 77 78 79
 * {
 *   msg_common_t       k_common[1];  // Common fragment info
 *   msg_list_t        *k_next;	      // Link to next header
 *   msg_param_t       *k_items;      // List of items
80
 *   uint32_t           k_bitmap;     // Bitmap of allowed methods
81 82
 * } sip_allow_t;
 * @endcode
83 84
 *
 * @note The field @a k_bitmap was added in @VERSION_1_12_5.
85
 */
Pekka Pessi's avatar
Pekka Pessi committed
86

87 88 89 90
#define sip_allow_dup_xtra msg_list_dup_xtra
#define sip_allow_dup_one  msg_list_dup_one
static msg_update_f sip_allow_update;

Pekka Pessi's avatar
Pekka Pessi committed
91
msg_hclass_t sip_allow_class[] = 
92
SIP_HEADER_CLASS(allow, "Allow", "", k_items, list, allow);
Pekka Pessi's avatar
Pekka Pessi committed
93

94
issize_t sip_allow_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen)
Pekka Pessi's avatar
Pekka Pessi committed
95 96
{
  sip_allow_t *k = (sip_allow_t *)h;
97 98 99
  issize_t retval = msg_commalist_d(home, &s, &k->k_items, msg_token_scan);
  msg_header_update_params(k->k_common, 0);
  return retval;
Pekka Pessi's avatar
Pekka Pessi committed
100 101
}

102
issize_t sip_allow_e(char b[], isize_t bsiz, sip_header_t const *h, int f)
Pekka Pessi's avatar
Pekka Pessi committed
103 104 105 106 107
{
  assert(sip_is_allow(h));
  return msg_list_e(b, bsiz, h, f);
}

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
static int sip_allow_update(msg_common_t *h, 
			  char const *name, isize_t namelen,
			  char const *value)
{
  sip_allow_t *k = (sip_allow_t *)h;

  if (name == NULL) {
    k->k_bitmap = 0;
  }
  else {
    sip_method_t method = sip_method_code(name);

    if (method >= 0 && method < 32) 
      k->k_bitmap |= 1 << method;
  }

  return 0;
}

/** Return true if the method is listed in @Allow header. */
int sip_is_allowed(sip_allow_t const *allow,
		   sip_method_t method,
		   char const *name)
{
  if (method < sip_method_unknown || !allow)
    return 0;

  if (sip_method_unknown < method && method < 32)
    /* Well-known method */
    return (allow->k_bitmap & (1 << method)) != 0;

  if (method == sip_method_unknown &&
      (allow->k_bitmap & (1 << sip_method_unknown)) == 0)
    return 0;

  return msg_header_find_item(allow->k_common, name) != NULL;
}


Pekka Pessi's avatar
Pekka Pessi committed
147 148 149 150 151
/* ====================================================================== */

/**@SIP_HEADER sip_proxy_require Proxy-Require Header
 *
 * The Proxy-Require header is used to indicate proxy-sensitive features
152
 * that @b MUST be supported by the proxy.  Its syntax is defined in @RFC3261
Pekka Pessi's avatar
Pekka Pessi committed
153 154 155 156 157 158
 * as follows:
 * 
 * @code
 *    Proxy-Require  =  "Proxy-Require" HCOLON option-tag *(COMMA option-tag)
 * @endcode
 *
159 160
 *
 * The parsed Proxy-Require header is stored in #sip_proxy_require_t structure.
Pekka Pessi's avatar
Pekka Pessi committed
161 162
 */

163 164 165
/**@ingroup sip_proxy_require
 * @typedef struct msg_list_s sip_proxy_require_t; 
 *
166 167
 * The structure #sip_proxy_require_t contains representation of an 
 * @ProxyRequire header.
168
 *
169
 * The #sip_proxy_require_t is defined as follows:
170 171 172 173
 * @code
 * typedef struct msg_list_s
 * {
 *   msg_common_t       k_common[1];  // Common fragment info
174
 *   msg_list_t        *k_next;	      // Dummy link
175 176 177 178 179
 *   msg_param_t       *k_items;      // List of items
 * } sip_proxy_require_t;
 * @endcode
 */

Pekka Pessi's avatar
Pekka Pessi committed
180 181 182
msg_hclass_t sip_proxy_require_class[] = 
SIP_HEADER_CLASS_LIST(proxy_require, "Proxy-Require", "", list);

183
issize_t sip_proxy_require_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen)
Pekka Pessi's avatar
Pekka Pessi committed
184 185 186 187 188
{
  sip_proxy_require_t *k = (sip_proxy_require_t *)h;
  return msg_commalist_d(home, &s, &k->k_items, msg_token_scan);
}

189
issize_t sip_proxy_require_e(char b[], isize_t bsiz, sip_header_t const *h, int f)
Pekka Pessi's avatar
Pekka Pessi committed
190 191 192 193 194 195 196 197 198 199 200
{
  assert(sip_is_proxy_require(h));
  return msg_list_e(b, bsiz, h, f);
}

/* ====================================================================== */

/**@SIP_HEADER sip_require Require Header
 *
 * The Require header is used by clients to tell user agent servers about
 * options that the client expects the server to support in order to
201
 * properly process the request.  Its syntax is defined in @RFC3261 
Pekka Pessi's avatar
Pekka Pessi committed
202 203 204 205 206 207
 * as follows:
 * 
 * @code
 *    Require       =  "Require" HCOLON option-tag *(COMMA option-tag)
 * @endcode
 *
208
 * The parsed Require header is stored in #sip_require_t structure.
Pekka Pessi's avatar
Pekka Pessi committed
209 210
 */

211 212 213
/**@ingroup sip_require
 * @typedef struct msg_list_s sip_require_t; 
 *
214 215
 * The structure #sip_require_t contains representation of an 
 * @Require header.
216
 *
217
 * The #sip_require_t is defined as follows:
218 219 220 221 222 223 224 225 226 227
 * @code
 * typedef struct msg_list_s
 * {
 *   msg_common_t       k_common[1];  // Common fragment info
 *   msg_list_t        *k_next;	      // Link to next header
 *   msg_param_t       *k_items;      // List of items
 * } sip_require_t;
 * @endcode
 */

Pekka Pessi's avatar
Pekka Pessi committed
228 229 230
msg_hclass_t sip_require_class[] = 
SIP_HEADER_CLASS_LIST(require, "Require", "", list);

231
issize_t sip_require_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen)
Pekka Pessi's avatar
Pekka Pessi committed
232 233 234 235 236
{
  sip_require_t *k = (sip_require_t *)h;
  return msg_commalist_d(home, &s, &k->k_items, msg_token_scan);
}

237
issize_t sip_require_e(char b[], isize_t bsiz, sip_header_t const *h, int f)
Pekka Pessi's avatar
Pekka Pessi committed
238 239 240 241 242 243 244 245 246 247
{
  assert(sip_is_require(h));
  return msg_list_e(b, bsiz, h, f);
}

/* ====================================================================== */

/**@SIP_HEADER sip_supported Supported Header
 *
 * The Supported header enumerates all the capabilities of the client or
248
 * server.  Its syntax is defined in @RFC3261 as follows:
Pekka Pessi's avatar
Pekka Pessi committed
249 250 251 252 253 254
 * 
 * @code
 *    Supported  =  ( "Supported" / "k" ) HCOLON
 *                  [option-tag *(COMMA option-tag)]
 * @endcode
 *
255 256
 * The parsed option-tags of Supported header
 * are stored in #sip_supported_t structure.
Pekka Pessi's avatar
Pekka Pessi committed
257 258
 */

259 260 261
/**@ingroup sip_supported
 * @typedef struct msg_list_s sip_supported_t; 
 *
262 263
 * The structure #sip_supported_t contains representation of an 
 * @Supported header.
264
 *
265
 * The #sip_supported_t is defined as follows:
266 267 268 269 270 271 272 273 274 275 276
 * @code
 * typedef struct msg_list_s
 * {
 *   msg_common_t       k_common[1];  // Common fragment info
 *   msg_list_t        *k_next;	      // Link to next header
 *   msg_param_t       *k_items;      // List of items
 * } sip_supported_t;
 * @endcode
 */


Pekka Pessi's avatar
Pekka Pessi committed
277 278 279
msg_hclass_t sip_supported_class[] = 
SIP_HEADER_CLASS_LIST(supported, "Supported", "k", list);

280
issize_t sip_supported_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen)
Pekka Pessi's avatar
Pekka Pessi committed
281 282 283 284 285
{
  sip_supported_t *k = (sip_supported_t *)h;
  return msg_commalist_d(home, &s, &k->k_items, msg_token_scan);
}

286
issize_t sip_supported_e(char b[], isize_t bsiz, sip_header_t const *h, int f)
Pekka Pessi's avatar
Pekka Pessi committed
287 288 289 290 291 292 293 294 295 296
{
  assert(sip_is_supported(h));
  return msg_list_e(b, bsiz, h, f);
}

/* ====================================================================== */

/**@SIP_HEADER sip_unsupported Unsupported Header
 *
 * The Unsupported header lists the features not supported by the server.
297
 * Its syntax is defined in @RFC3261 as follows:
Pekka Pessi's avatar
Pekka Pessi committed
298 299
 * 
 * @code
300
 *    Unsupported  =  "Unsupported" HCOLON [option-tag *(COMMA option-tag)]
Pekka Pessi's avatar
Pekka Pessi committed
301 302
 * @endcode
 *
303 304
 *
 * The parsed Unsupported header is stored in #sip_unsupported_t structure.
Pekka Pessi's avatar
Pekka Pessi committed
305 306
 */

307 308 309
/**@ingroup sip_unsupported
 * @typedef struct msg_list_s sip_unsupported_t; 
 *
310 311
 * The structure #sip_unsupported_t contains representation of an 
 * @Unsupported header.
312
 *
313
 * The #sip_unsupported_t is defined as follows:
314 315 316 317 318 319 320 321 322 323
 * @code
 * typedef struct msg_list_s
 * {
 *   msg_common_t       k_common[1];  // Common fragment info
 *   msg_list_t        *k_next;	      // Link to next header
 *   msg_param_t       *k_items;      // List of items
 * } sip_unsupported_t;
 * @endcode
 */

Pekka Pessi's avatar
Pekka Pessi committed
324 325 326
msg_hclass_t sip_unsupported_class[] = 
SIP_HEADER_CLASS_LIST(unsupported, "Unsupported", "", list);

327
issize_t sip_unsupported_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen)
Pekka Pessi's avatar
Pekka Pessi committed
328 329 330 331 332
{
  sip_unsupported_t *k = (sip_unsupported_t *)h;
  return msg_commalist_d(home, &s, &k->k_items, msg_token_scan);
}

333
issize_t sip_unsupported_e(char b[], isize_t bsiz, sip_header_t const *h, int f)
Pekka Pessi's avatar
Pekka Pessi committed
334 335 336 337 338
{
  assert(sip_is_unsupported(h));
  return msg_list_e(b, bsiz, h, f);
}

339
/** Check if required feature is not supported.
340 341
 *
 * @retval NULL if all the required features are supported
342
 * @retval pointer to a @Unsupported header or
343
 *         #SIP_NONE if @a home is NULL  
Pekka Pessi's avatar
Pekka Pessi committed
344 345 346 347 348 349 350 351 352 353
 */
sip_unsupported_t *sip_has_unsupported(su_home_t *home,
				       sip_supported_t const *support, 
				       sip_require_t const *require)
{
  return sip_has_unsupported_any(home, support, NULL, NULL, 
				 require, NULL, NULL);
}


354
/** Check if required feature is not supported.
Pekka Pessi's avatar
Pekka Pessi committed
355 356
 *
 * @retval NULL if all the required features are supported
357
 * @retval pointer to a @Unsupported header or
358
 *         #SIP_NONE if @a home is NULL
Pekka Pessi's avatar
Pekka Pessi committed
359 360 361 362 363 364 365 366 367 368 369 370 371 372
 */
sip_unsupported_t *
sip_has_unsupported2(su_home_t *home,
		     sip_supported_t const *support,
		     sip_require_t const *support_by_require,
		     sip_require_t const *require)
{
  return 
    sip_has_unsupported_any(home, 
			    support, support_by_require, NULL, 
			    require, NULL, NULL);
}


373
/** Check if required features are not supported.
Pekka Pessi's avatar
Pekka Pessi committed
374
 *
375 376
 * The supported features can be listed in @Supported, @Require or 
 * @ProxyRequire headers (in @a supported, @a by_require, or @a
377
 * by_proxy_require parameters, respectively)
Pekka Pessi's avatar
Pekka Pessi committed
378
 *
379 380
 * @param home (optional) home pointer for allocating @Unsupported header
 * @param supported @Supported features (may be NULL) [IN]
381
 * @param by_require  supported features listed by 
382
 *                    @Require (may be NULL) [IN] 
383
 * @param by_proxy_require supported features listed 
384
 *                         by @ProxyRequire (may be NULL) [IN]
Pekka Pessi's avatar
Pekka Pessi committed
385 386 387 388 389 390
 *
 * @param require   list of required features (may be NULL) [IN]
 * @param require2  2nd list of required features (may be NULL) [IN]
 * @param require3  3rd list of required features (may be NULL) [IN]
 *
 * @retval NULL if all the required features are supported
391
 * @retval pointer to a @Unsupported header or
Pekka Pessi's avatar
Pekka Pessi committed
392 393 394 395 396 397 398 399 400 401 402
 *         #SIP_NONE if @a home is NULL 
 */
sip_unsupported_t *
sip_has_unsupported_any(su_home_t *home,
			sip_supported_t const *supported,
			sip_require_t const *by_require,
			sip_proxy_require_t const *by_proxy_require,
			sip_require_t const *require,
			sip_require_t const *require2,
			sip_require_t const *require3)
{
403
  size_t i, j;
Pekka Pessi's avatar
Pekka Pessi committed
404
  sip_unsupported_t *unsupported = NULL;
Pekka Pessi's avatar
Pekka Pessi committed
405 406 407 408
  msg_param_t const empty[1] = { NULL };
  msg_param_t const *slist = empty;
  msg_param_t const *rlist = empty;
  msg_param_t const *prlist = empty;
Pekka Pessi's avatar
Pekka Pessi committed
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423

  if (require2 == NULL)
    require2 = require3, require3 = NULL;
  if (require == NULL)
    require = require2, require2 = NULL;

  if (require && require->k_items) {
    if (supported && supported->k_items)
      slist = supported->k_items;
    if (by_require && by_require->k_items)
      rlist = by_require->k_items;
    if (by_proxy_require && by_proxy_require->k_items)
      prlist = by_proxy_require->k_items;

    for (i = 0; require->k_items && require->k_items[i];) {
Pekka Pessi's avatar
Pekka Pessi committed
424
      msg_param_t feature = require->k_items[i++];
Pekka Pessi's avatar
Pekka Pessi committed
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450

      for (j = 0; slist[j]; j++)
	if (strcasecmp(feature, slist[j]) == 0) {
	  feature = NULL;
	  break;
	}

      if (feature)
	for (j = 0; rlist[j]; j++)
	  if (strcasecmp(feature, rlist[j]) == 0) {
	    feature = NULL;
	    break;
	  }

      if (feature)
	for (j = 0; prlist[j]; j++)
	  if (strcasecmp(feature, prlist[j]) == 0) {
	    feature = NULL;
	    break;
	  }

      if (feature) {
	if (home) {
	  if (unsupported == NULL) 
	    unsupported = sip_unsupported_make(home, feature);
	  else
Pekka Pessi's avatar
Pekka Pessi committed
451
	    msg_params_add(home, 
Pekka Pessi's avatar
Pekka Pessi committed
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
			   (msg_param_t **)&unsupported->k_items, 
			   feature);
	}
	else {
	  return (sip_unsupported_t *)SIP_NONE;
	}
      }

      if (require->k_items[i] == NULL && require2 && require2->k_items) {
	i = 0, require = require2, require2 = require3, require3 = NULL;
      }
    }
  }
  
  return unsupported;
}


int sip_has_feature(msg_list_t const *supported, char const *feature)
{
472
  size_t i;
Pekka Pessi's avatar
Pekka Pessi committed
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
  
  if (!feature || !feature[0])
    return 1;			/* Empty feature is always supported */

  for (; supported; supported = supported->k_next)
    if (supported->k_items)
      for (i = 0; supported->k_items[i]; i++)
	if (strcasecmp(feature, supported->k_items[i]) == 0)
	  return 1;

  return 0;
}

/** Check that a feature is supported. */
int sip_has_supported(sip_supported_t const *supported, char const *feature)
{
  return sip_has_feature(supported, feature);
}

/* ====================================================================== */

/**@SIP_HEADER sip_path Path Header
 *
496
 * The Path header field is a SIP extension header field (@RFC3327) with
497
 * syntax very similar to the @RecordRoute header field. It is used in
Pekka Pessi's avatar
Pekka Pessi committed
498 499 500 501 502 503 504 505
 * conjunction with SIP REGISTER requests and with 200 class messages in
 * response to REGISTER (REGISTER responses).
 * 
 * @code
 *    Path        =  "Path" HCOLON path-value *(COMMA path-value)
 *    path-value  =  name-addr *( SEMI rr-param )
 * @endcode
 *
506 507
 *
 * The parsed Path header is stored in #sip_path_t structure.
Pekka Pessi's avatar
Pekka Pessi committed
508 509 510 511 512
 */

/**@ingroup sip_path
 * @typedef typedef struct sip_route_s sip_path_t;
 *
513
 * The structure #sip_path_t contains representation of SIP @Path header.
Pekka Pessi's avatar
Pekka Pessi committed
514 515 516 517 518
 *
 * The #sip_path_t is defined as follows:
 * @code
 * typedef struct sip_route_s {
 *   sip_common_t        r_common[1];   // Common fragment info
519
 *   sip_path_t         *r_next;        // Link to next @Path
Pekka Pessi's avatar
Pekka Pessi committed
520
 *   char const         *r_display;     // Display name
521
 *   url_t               r_url[1];      // @Path URL
Pekka Pessi's avatar
Pekka Pessi committed
522
 *   msg_param_t const  *r_params;      // List of parameters
Pekka Pessi's avatar
Pekka Pessi committed
523 524 525 526 527 528 529
 * } sip_path_t;
 * @endcode
 */

msg_hclass_t sip_path_class[] =
SIP_HEADER_CLASS(path, "Path", "", r_params, prepend, any_route);

530
issize_t sip_path_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen)
Pekka Pessi's avatar
Pekka Pessi committed
531 532 533 534
{
  return sip_any_route_d(home, h, s, slen);
}

535
issize_t sip_path_e(char b[], isize_t bsiz, sip_header_t const *h, int flags)
Pekka Pessi's avatar
Pekka Pessi committed
536 537 538 539 540 541 542 543 544
{
  assert(sip_is_path(h));
  return sip_any_route_e(b, bsiz, h, flags);
}

/* ====================================================================== */

/**@SIP_HEADER sip_service_route Service-Route Header
 *
545
 * The "Service-Route" is a SIP extension header field (@RFC3608), which can
Pekka Pessi's avatar
Pekka Pessi committed
546 547 548 549 550 551 552 553 554 555 556 557 558
 * contain a route vector that will direct requests through a specific
 * sequence of proxies. A registrar may use a Service-Route header field to
 * inform a UA of a service route that, if used by the UA, will provide
 * services from a proxy or set of proxies associated with that registrar. 
 * The Service-Route header field may be included by a registrar in the
 * response to a REGISTER request. The syntax for the Service-Route header
 * field is:
 *
 * @code
 *    Service-Route = "Service-Route" HCOLON sr-value *(COMMA sr-value)
 *    sr-value  =  name-addr *( SEMI rr-param )
 * @endcode
 *
559 560 561
 * The parsed Service-Route header is stored in #sip_service_route_t structure.
 *
 * @sa @RFC3608, @Path, @Route, @RecordRoute
Pekka Pessi's avatar
Pekka Pessi committed
562 563 564 565 566 567
 */

/**@ingroup sip_service_route
 * @typedef typedef struct sip_route_s sip_service_route_t;
 *
 * The structure #sip_service_route_t contains representation of SIP 
568
 * @ServiceRoute header.
Pekka Pessi's avatar
Pekka Pessi committed
569 570 571 572 573
 *
 * The #sip_service_route_t is defined as follows:
 * @code
 * typedef struct sip_route_s {
 *   sip_common_t        r_common[1];   // Common fragment info
574
 *   sip_service_route_t*r_next;        // Link to next @ServiceRoute
Pekka Pessi's avatar
Pekka Pessi committed
575 576
 *   char const         *r_display;     // Display name
 *   url_t               r_url[1];      // Service-Route URL
Pekka Pessi's avatar
Pekka Pessi committed
577
 *   msg_param_t const  *r_params;      // List of parameters
Pekka Pessi's avatar
Pekka Pessi committed
578 579 580 581 582 583 584 585
 * } sip_service_route_t;
 * @endcode
 */

msg_hclass_t sip_service_route_class[] =
SIP_HEADER_CLASS(service_route, "Service-Route", "", 
		 r_params, append, any_route);

586
issize_t sip_service_route_d(su_home_t *home, sip_header_t *h, char *s, isize_t slen)
Pekka Pessi's avatar
Pekka Pessi committed
587 588 589 590
{
  return sip_any_route_d(home, h, s, slen);
}

591
issize_t sip_service_route_e(char b[], isize_t bsiz, sip_header_t const *h, int flags)
Pekka Pessi's avatar
Pekka Pessi committed
592 593 594 595
{
  assert(sip_is_service_route(h));
  return sip_any_route_e(b, bsiz, h, flags);
}