/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2005 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 sdp.c Simple SDP interface.
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 * @author Kai Vehmanen <kai.vehmanen@nokia.com>
 *
 * @date Created: Fri Feb 18 10:25:08 2000 ppessi
 */

#include "config.h"

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>

#include <sofia-sip/su_alloc.h>
#include <sofia-sip/su_types.h>

#include "sofia-sip/sdp.h"

struct align { void  *_a; char _b; };

#define ALIGN(v, n) ((n - (intptr_t)(v)) & (n - 1))
#define STRUCT_ALIGN_  (sizeof(struct align) - offsetof(struct align, _b))
#define STRUCT_ALIGN(v) ALIGN(v, sizeof(void *))
#define ASSERT_STRUCT_ALIGN(p) \
  (STRUCT_ALIGN(p) ? (void)assert(!"STRUCT_ALIGNED(" #p ")") : (void)0)

const unsigned sdp_struct_align_ = sizeof(void *) - STRUCT_ALIGN_;

#define STR_XTRA(rv, s)    ((s) ? rv += strlen((s)) + 1 : 0)
#define PTR_XTRA(rv, p, f) \
  ((p) ? (rv += STRUCT_ALIGN(rv) + f(p)) : 0)
#define LST_XTRA(rv, l, f) \
  ((l) ? (rv += STRUCT_ALIGN(rv) + list_xtra_all((xtra_f*)f, l)) : 0)


#define STRUCT_DUP(p, dst, src) \
  ASSERT_STRUCT_ALIGN(p); assert(*(int*)(src) >= (int)sizeof(*src));	\
  ((*(int*)(src) >= (int)sizeof(*src)					\
    ? (dst = memcpy((p), (src), sizeof(*src)))				\
    : (dst = memcpy((p), (src), *(int*)(src))),				\
    memset((p)+*(int*)(src), 0, sizeof(*src) - *(int*)(src))), \
  ((p) += sizeof(*src)))

#define STRUCT_DUP2(p, dst, src) \
  ASSERT_STRUCT_ALIGN(p); assert(*(int*)(src) >= (int)sizeof(*src));	\
  (dst = memcpy((p), (src), *(int*)(src)), ((p) += *(int*)(src)))

#define STR_DUP(p, dst, src, m) \
 ((src->m) ? ((dst->m) = strcpy((p), (src->m)), (p) += strlen((p)) + 1) \
           : ((dst->m) = 0))
#define PTR_DUP(p, dst, src, m, dup) \
 ((dst->m) = (src->m)?((p += STRUCT_ALIGN(p)), ((dup)(&(p), (src->m)))): 0)
#define LST_DUP(p, dst, src, m, dup) \
 ((dst->m) = (src->m)?((p += STRUCT_ALIGN(p)),\
                       list_dup_all((dup_f*)(dup), &(p), src->m)) : 0)
#define MED_XTRA_EX(rv, l, c) \
  ((l) ? (rv += STRUCT_ALIGN(rv) + media_xtra_ex(l, c)) : 0)
#define MED_DUP_EX(p, dst, src, m, dst_c, src_c) \
 ((dst->m) = (src->m)?((p += STRUCT_ALIGN(p)),\
                       media_dup_all(&(p), src->m, dst, dst_c, src_c)) : 0)

#define MED_XTRA_ALL(rv, m) \
  ((m) ? (rv += STRUCT_ALIGN(rv) + media_xtra_all(m)) : 0)
#define MED_DUP_ALL(p, dst, src, m) \
 ((dst->m) = (src->m)?((p += STRUCT_ALIGN(p)),\
                       media_dup_all(&(p), src->m, dst)) : 0)

typedef size_t xtra_f(void const *);
typedef void *dup_f(char **bb, void const *src);

static size_t list_xtra_all(xtra_f *xtra, void const *v);
static void *list_dup_all(dup_f *dup, char **bb, void const *vsrc);

static size_t session_xtra(sdp_session_t const *o);
static sdp_session_t *session_dup(char **pp, sdp_session_t const *o);

static size_t origin_xtra(sdp_origin_t const *o);
static sdp_origin_t *origin_dup(char **pp, sdp_origin_t const *o);

static size_t connection_xtra(sdp_connection_t const *o);
static sdp_connection_t *connection_dup(char **pp, sdp_connection_t const *o);

static size_t bandwidth_xtra(sdp_bandwidth_t const *o);
static sdp_bandwidth_t *bandwidth_dup(char **pp, sdp_bandwidth_t const *o);

static size_t time_xtra(sdp_time_t const *o);
static sdp_time_t *time_dup(char **pp, sdp_time_t const *o);

static size_t repeat_xtra(sdp_repeat_t const *o);
static sdp_repeat_t *repeat_dup(char **pp, sdp_repeat_t const *o);

static size_t zone_xtra(sdp_zone_t const *o);
static sdp_zone_t *zone_dup(char **pp, sdp_zone_t const *o);

static size_t key_xtra(sdp_key_t const *o);
static sdp_key_t *key_dup(char **pp, sdp_key_t const *o);

static size_t attribute_xtra(sdp_attribute_t const *o);
static sdp_attribute_t *attribute_dup(char **pp, sdp_attribute_t const *o);

static size_t list_xtra(sdp_list_t const *o);
static sdp_list_t *list_dup(char **pp, sdp_list_t const *o);

static size_t rtpmap_xtra(sdp_rtpmap_t const *o);
static sdp_rtpmap_t *rtpmap_dup(char **pp, sdp_rtpmap_t const *o);

static size_t media_xtra(sdp_media_t const *o);
static sdp_media_t *media_dup(char **pp,
			      sdp_media_t const *o,
			      sdp_session_t *sdp);
#ifdef nomore
static size_t media_xtra_ex(sdp_media_t const *o,
			  sdp_connection_t const *c);
static sdp_media_t *media_dup_ex(char **pp,
				  sdp_media_t const *o,
				  sdp_session_t *sdp,
				  sdp_connection_t *dst_c,
				  sdp_connection_t const *src_c);
#endif
static size_t media_xtra_all(sdp_media_t const *o);
static sdp_media_t *media_dup_all(char **pp,
				  sdp_media_t const *o,
				  sdp_session_t *sdp);


/** Define a function body duplicating an SDP structure. */
#define SDP_DUP(type, name) \
  sdp_##type##_t *rv; size_t size; char *p, *end; \
  if (!name) return NULL; \
  size = type##_xtra(name); \
  p = su_alloc(h, size); end = p + size; \
  rv = type##_dup(&p, name); \
  assert(p == end); \
  return rv;

/** Define a function body duplicating a list of SDP structures. */
#define SDP_LIST_DUP(type, name) \
  sdp_##type##_t *rv; size_t size; char *p, *end; \
  if (!name) return NULL; \
  size = list_xtra_all((xtra_f*)type##_xtra, name); \
  rv = su_alloc(h, size); p = (char *)rv; end = p + size; \
  list_dup_all((dup_f*)type##_dup, &p, name); \
  assert(p == end); \
  return rv;

/**Duplicate an SDP origin description.
 *
 * The function sdp_origin_dup() duplicates (deeply copies) an SDP origin
 * description @a o allocating memory using memory @a home.
 *
 * @param h     Memory home
 * @param o     SDP origin description to be duplicated
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_origin_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_origin_t *sdp_origin_dup(su_home_t *h, sdp_origin_t const *o)
{
  SDP_DUP(origin, o);
}

/**Duplicate an SDP connection description.
 *
 * The function sdp_connection_dup() duplicates (deeply copies) a list of
 * SDP connection description @a c allocating memory using memory @a home.
 *
 * @param h     Memory home
 * @param c     SDP connection description to be duplicated
 *
 * @note The duplicated list is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_connection_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_connection_t *sdp_connection_dup(su_home_t *h, sdp_connection_t const *c)
{
  SDP_LIST_DUP(connection, c);
}

/**Duplicate an SDP bandwidth description.
 *
 * The function sdp_bandwidth_dup() duplicates (deeply copies) a list of SDP
 * bandwidth descriptions @a b allocating memory using memory @a home.
 *
 * @param h     Memory home
 * @param b     SDP bandwidth description to be duplicated
 *
 * @note The duplicated list is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_bandwidth_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_bandwidth_t *sdp_bandwidth_dup(su_home_t *h, sdp_bandwidth_t const *b)
{
  SDP_LIST_DUP(bandwidth, b);
}

/**Duplicate an SDP time description.
 *
 * The function sdp_time_dup() duplicates (deeply copies) a list of SDP time
 * descriptions @a t allocating memory using memory @a home.
 *
 * @param h  Memory home
 * @param t  SDP time description to be duplicated
 *
 * @note The duplicated list is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_time_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_time_t *sdp_time_dup(su_home_t *h, sdp_time_t const *t)
{
  SDP_LIST_DUP(time, t);
}

/**Duplicate an SDP repeat description.
 *
 * The function sdp_repeat_dup() duplicates (deeply copies) an SDP repeat
 * description @a r allocating memory using memory @a home.
 *
 * @param h  Memory home
 * @param r  SDP repeat description to be duplicated
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_repeat_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_repeat_t *sdp_repeat_dup(su_home_t *h, sdp_repeat_t const *r)
{
  SDP_DUP(repeat, r);
}

/**Duplicate an SDP zone description.
 *
 * The function sdp_zone_dup() duplicates (deeply copies) an SDP zone
 * description @a z allocating memory using memory @a home.
 *
 * @param h  Memory home
 * @param z  SDP zone description to be duplicated
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_zone_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_zone_t *sdp_zone_dup(su_home_t *h, sdp_zone_t const *z)
{
  SDP_DUP(zone, z);
}

/**Duplicate an SDP key description.
 *
 * The function sdp_key_dup() duplicates (deeply copies) an SDP key
 * description @a k allocating memory using memory @a home.
 *
 * @param h  Memory home
 * @param k  SDP key description to be duplicated
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_key_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_key_t *sdp_key_dup(su_home_t *h, sdp_key_t const *k)
{
  SDP_DUP(key, k);
}

/**Duplicate an SDP attribute list.
 *
 * The function sdp_attribute_dup() duplicates (deeply copies) an SDP
 * attribute list @a a allocating memory using memory @a home.
 *
 * @param h  Memory home
 * @param a  SDP attribute description to be duplicated
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_attribute_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_attribute_t *sdp_attribute_dup(su_home_t *h, sdp_attribute_t const *a)
{
  SDP_LIST_DUP(attribute, a);
}

/**Duplicate an SDP list of text.
 *
 * The function sdp_list_dup() duplicates (deeply copies) an SDP text
 * list @a l allocating memory using memory @a home.
 *
 * @param h  Memory home
 * @param l  SDP list description to be duplicated
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_list_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_list_t *sdp_list_dup(su_home_t *h, sdp_list_t const *l)
{
  SDP_LIST_DUP(list, l);
}

/**Duplicate an SDP rtpmap list.
 *
 * The function sdp_rtpmap_dup() duplicates (deeply copies) an SDP rtpmap
 * list @a rm allocating memory using memory @a home.
 *
 * @param h  Memory home
 * @param rm SDP rtpmap description to be duplicated
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_rtpmap_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_rtpmap_t *sdp_rtpmap_dup(su_home_t *h, sdp_rtpmap_t const *rm)
{
  SDP_LIST_DUP(rtpmap, rm);
}

/**Duplicate an SDP media description.
 *
 * The function sdp_media_dup() duplicates (deeply copies) an SDP media
 * description @a m allocating memory using memory @a home.
 *
 * @param h   Memory home
 * @param m   SDP media description to be duplicated
 * @param sdp SDP session description to which the newly allocated
 *            media description is linked
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_media_t structure is
 * returned, otherwise NULL is returned.
 */
sdp_media_t *sdp_media_dup(su_home_t *h, sdp_media_t const *m,
			   sdp_session_t *sdp)
{
  sdp_media_t *rv; size_t size; char *p, *end;
  size = media_xtra(m);
  p = su_alloc(h, size); end = p + size;
  rv = media_dup(&p, m, sdp);
  assert(p == end);
  return rv;
}

/**Duplicate an SDP media description.
 *
 * The function sdp_media_dup_all() duplicates (deeply copies) a list of SDP
 * media descriptions @a m allocating memory using memory @a home.
 *
 * @param h     Memory home
 * @param m     list of SDP media descriptions to be duplicated
 * @param sdp   SDP session description to which the newly allocated
 *              media descriptions are linked
 *
 * @note The duplicated list is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to a newly allocated list of sdp_media_t
 * structures is returned, otherwise NULL is returned.
 */
sdp_media_t *sdp_media_dup_all(su_home_t *h, sdp_media_t const *m,
			       sdp_session_t *sdp)
{
  sdp_media_t *rv; size_t size; char *p, *end;
  size = media_xtra_all(m);
  p = su_alloc(h, size); end = p + size;
  rv = media_dup_all(&p, m, sdp);
  assert(p == end);
  return rv;
}

#ifdef nomore			/* really deprecated */
/**Duplicate media description with common address.
 *
 * This function is provided in order to avoid duplicate @c c= lines.  If
 * the @c c= line in media description equals to @a src_c, it is not
 * duplicated but replaced with @a dst_c instead.
 *
 * @param home  Memory home
 * @param src   SDP media description to be duplicated
 * @param sdp   SDP session description to which the newly allocated
 *              media description is linked
 * @param dst_c Connection description used instead of duplicate of @a src_c.
 * @param src_c Connection description not to be duplicated

 * @return
 * If successful, a pointer to newly allocated sdp_media_t structure is
 * returned, otherwise NULL is returned.
 *
 * @deprecated
 * This function is deprecated. Use sdp_media_dup() instead.
 */
sdp_media_t *sdp_media_dup_ex(su_home_t *home,
			      sdp_media_t const *src,
			      sdp_session_t *sdp,
			      sdp_connection_t *dst_c,
			      sdp_connection_t const *src_c)
{
  sdp_media_t *rv; size_t size; char *p, *end;
  size = media_xtra_all(src, src_c);
  p = su_alloc(home, size); end = p + size;
  rv = media_dup_all(&p, src, sdp, dst_c, src_c);
  assert(p == end);
  return rv;
}
#endif

/* ---------------------------------------------------------------------- */

static size_t origin_xtra(sdp_origin_t const *o)
{
  size_t rv = sizeof(*o);
  STR_XTRA(rv, o->o_username);
  PTR_XTRA(rv, o->o_address, connection_xtra);
  return rv;
}

static
sdp_origin_t *origin_dup(char **pp, sdp_origin_t const *src)
{
  char *p;
  sdp_origin_t *o;

  p = *pp;
  STRUCT_DUP(p, o, src);
  STR_DUP(p, o, src, o_username);
  PTR_DUP(p, o, src, o_address, connection_dup);

  assert((size_t)(p - *pp) == origin_xtra(src));
  *pp = p;
  return o;
}

static size_t connection_xtra(sdp_connection_t const *c)
{
  size_t rv = sizeof(*c);
  STR_XTRA(rv, c->c_address);
  return rv;
}

static
sdp_connection_t *connection_dup(char **pp, sdp_connection_t const *src)
{
  char *p;
  sdp_connection_t *c;

  p = *pp;
  STRUCT_DUP(p, c, src);
  c->c_next = NULL;
  STR_DUP(p, c, src, c_address);

  assert((size_t)(p - *pp) == connection_xtra(src));
  *pp = p;
  return c;
}

static size_t bandwidth_xtra(sdp_bandwidth_t const *b)
{
  size_t rv = sizeof(*b);
  STR_XTRA(rv, b->b_modifier_name);
  return rv;
}

static
sdp_bandwidth_t *bandwidth_dup(char **pp, sdp_bandwidth_t const *src)
{
  char *p;
  sdp_bandwidth_t *b;

  p = *pp;
  STRUCT_DUP(p, b, src);
  b->b_next = NULL;
  STR_DUP(p, b, src, b_modifier_name);

  assert((size_t)(p - *pp) == bandwidth_xtra(src));
  *pp = p;
  return b;
}


static size_t time_xtra(sdp_time_t const *t)
{
  size_t rv = sizeof(*t);
  PTR_XTRA(rv, t->t_repeat, repeat_xtra);
  PTR_XTRA(rv, t->t_zone, zone_xtra);
  return rv;
}

static
sdp_time_t *time_dup(char **pp, sdp_time_t const *src)
{
  char *p;
  sdp_time_t *t;

  p = *pp;
  STRUCT_DUP(p, t, src);
  t->t_next = NULL;
  PTR_DUP(p, t, src, t_repeat, repeat_dup);
  PTR_DUP(p, t, src, t_zone, zone_dup);

  assert((size_t)(p - *pp) == time_xtra(src));
  *pp = p;
  return t;
}


static size_t repeat_xtra(sdp_repeat_t const *r)
{
  return (size_t)r->r_size;
}

static
sdp_repeat_t *repeat_dup(char **pp, sdp_repeat_t const *src)
{
  char *p;
  sdp_repeat_t *r;

  p = *pp;
  STRUCT_DUP2(p, r, src);

  assert((size_t)(p - *pp) == repeat_xtra(src));
  *pp = p;
  return r;
}


static size_t zone_xtra(sdp_zone_t const *z)
{
  return z->z_size;
}

static
sdp_zone_t *zone_dup(char **pp, sdp_zone_t const *src)
{
  char *p;
  sdp_zone_t *z;

  p = *pp;
  STRUCT_DUP2(p, z, src);

  assert((size_t)(p - *pp) == zone_xtra(src));
  *pp = p;
  return z;
}


static size_t key_xtra(sdp_key_t const *k)
{
  size_t rv = sizeof(*k);
  STR_XTRA(rv, k->k_method_name);
  STR_XTRA(rv, k->k_material);
  return rv;
}

static
sdp_key_t *key_dup(char **pp, sdp_key_t const *src)
{
  char *p;
  sdp_key_t *k;

  p = *pp;
  STRUCT_DUP(p, k, src);
  STR_DUP(p, k, src, k_method_name);
  STR_DUP(p, k, src, k_material);

  assert((size_t)(p - *pp) == key_xtra(src));
  *pp = p;
  return k;
}


static size_t attribute_xtra(sdp_attribute_t const *a)
{
  size_t rv = sizeof(*a);
  STR_XTRA(rv, a->a_name);
  STR_XTRA(rv, a->a_value);
  return rv;
}

static
sdp_attribute_t *attribute_dup(char **pp, sdp_attribute_t const *src)
{
  char *p;
  sdp_attribute_t *a;

  p = *pp;
  STRUCT_DUP(p, a, src);
  a->a_next = NULL;
  STR_DUP(p, a, src, a_name);
  STR_DUP(p, a, src, a_value);

  assert((size_t)(p - *pp) == attribute_xtra(src));
  *pp = p;
  return a;
}


static size_t media_xtra(sdp_media_t const *m)
{
  size_t rv = sizeof(*m);

  STR_XTRA(rv, m->m_type_name);
  STR_XTRA(rv, m->m_proto_name);
  LST_XTRA(rv, m->m_format, list_xtra);
  LST_XTRA(rv, m->m_rtpmaps, rtpmap_xtra);
  STR_XTRA(rv, m->m_information);
  LST_XTRA(rv, m->m_connections, connection_xtra);
  LST_XTRA(rv, m->m_bandwidths, bandwidth_xtra);
  PTR_XTRA(rv, m->m_key, key_xtra);
  LST_XTRA(rv, m->m_attributes, attribute_xtra);

  return rv;
}

static
sdp_media_t *media_dup(char **pp,
		       sdp_media_t const *src,
		       sdp_session_t *sdp)
{
  char *p;
  sdp_media_t *m;

  p = *pp;
  STRUCT_DUP(p, m, src);
  m->m_next = NULL;

  STR_DUP(p, m, src, m_type_name);
  STR_DUP(p, m, src, m_proto_name);
  LST_DUP(p, m, src, m_format, list_dup);
  LST_DUP(p, m, src, m_rtpmaps, rtpmap_dup);
  STR_DUP(p, m, src, m_information);
  LST_DUP(p, m, src, m_connections, connection_dup);
  LST_DUP(p, m, src, m_bandwidths, bandwidth_dup);
  PTR_DUP(p, m, src, m_key, key_dup);
  LST_DUP(p, m, src, m_attributes, attribute_dup);

  /* note! we must not implicitly use 'src->m_session' as it
           might point to a temporary session */
  m->m_session = sdp;

  m->m_rejected = src->m_rejected;
  m->m_mode = src->m_mode;

  assert((size_t)(p - *pp) == media_xtra(src));
  *pp = p;
  return m;
}

#ifdef nomore
static
int media_xtra_ex(sdp_media_t const *m, sdp_connection_t const *c)
{
  int rv = 0;

  for (; m; m = m->m_next) {
    rv += STRUCT_ALIGN(rv);
    rv += sizeof(*m);

    STR_XTRA(rv, m->m_type_name);
    STR_XTRA(rv, m->m_proto_name);
    LST_XTRA(rv, m->m_format, list_xtra);
    LST_XTRA(rv, m->m_rtpmaps, rtpmap_xtra);
    STR_XTRA(rv, m->m_information);
    if (c != m->m_connections)
      LST_XTRA(rv, m->m_connections, connection_xtra);
    LST_XTRA(rv, m->m_bandwidths, bandwidth_xtra);
    PTR_XTRA(rv, m->m_key, key_xtra);
    LST_XTRA(rv, m->m_attributes, attribute_xtra);
  }

  return rv;
}

static
sdp_media_t *media_dup_ex(char **pp,
			  sdp_media_t const *src,
			  sdp_session_t *sdp,
			  sdp_connection_t *dst_c,
			  sdp_connection_t const *src_c)
{
  char *p;
  sdp_media_t *retval = NULL, *m, **mm = &retval;
  int xtra = media_xtra_ex(src, src_c);

  p = *pp;

  for (; src; src = src->m_next) {
    p += STRUCT_ALIGN(p);
    STRUCT_DUP(p, m, src);
    m->m_next = NULL;

    STR_DUP(p, m, src, m_type_name);
    STR_DUP(p, m, src, m_proto_name);
    LST_DUP(p, m, src, m_format, list_dup);
    LST_DUP(p, m, src, m_rtpmaps, rtpmap_dup);
    STR_DUP(p, m, src, m_information);
    if (src_c != src->m_connections)
      LST_DUP(p, m, src, m_connections, connection_dup);
    else
      m->m_connections = dst_c;
    LST_DUP(p, m, src, m_bandwidths, bandwidth_dup);
    PTR_DUP(p, m, src, m_key, key_dup);
    LST_DUP(p, m, src, m_attributes, attribute_dup);

    /* note! we must not implicitly use 'src->m_session' as it
       might point to a temporary session */
    m->m_session = sdp;

    m->m_rejected = src->m_rejected;
    m->m_mode = src->m_mode;

    assert(m);
    *mm = m; mm = &m->m_next;
  }

  assert(p - *pp == xtra);


  *pp = p;

  return retval;
}
#endif

static size_t media_xtra_all(sdp_media_t const *m)
{
  size_t rv = 0;

  for (; m; m = m->m_next) {
    rv += STRUCT_ALIGN(rv);
    rv += media_xtra(m);
  }

  return rv;
}

static
sdp_media_t *media_dup_all(char **pp,
			   sdp_media_t const *src,
			   sdp_session_t *sdp)
{
  char *p;
  sdp_media_t *retval = NULL, *m, **mm = &retval;

  p = *pp;

  for (; src; src = src->m_next) {
    p += STRUCT_ALIGN(p);
    m = media_dup(&p, src, sdp);
    assert(m);
    *mm = m; mm = &m->m_next;
  }

  *pp = p;

  return retval;
}

static size_t list_xtra(sdp_list_t const *l)
{
  size_t rv = sizeof(*l);
  rv += strlen(l->l_text) + 1;
  return rv;
}

static
sdp_list_t *list_dup(char **pp, sdp_list_t const *src)
{
  char *p;
  sdp_list_t *l;

  p = *pp;
  STRUCT_DUP(p, l, src);
  l->l_next = NULL;
  STR_DUP(p, l, src, l_text);

  assert((size_t)(p - *pp) == list_xtra(src));
  *pp = p;
  return l;
}


static size_t rtpmap_xtra(sdp_rtpmap_t const *rm)
{
  size_t rv = sizeof(*rm);
  STR_XTRA(rv, rm->rm_encoding);
  STR_XTRA(rv, rm->rm_params);
  STR_XTRA(rv, rm->rm_fmtp);
  return rv;
}

static
sdp_rtpmap_t *rtpmap_dup(char **pp, sdp_rtpmap_t const *src)
{
  char *p;
  sdp_rtpmap_t *rm;

  p = *pp;
  STRUCT_DUP(p, rm, src);
  rm->rm_next = NULL;
  STR_DUP(p, rm, src, rm_encoding);
  STR_DUP(p, rm, src, rm_params);
  STR_DUP(p, rm, src, rm_fmtp);

  assert((size_t)(p - *pp) == rtpmap_xtra(src));
  *pp = p;
  return rm;
}

/** Return total size of a list, including size of all nodes */
static size_t list_xtra_all(xtra_f *xtra, void const *v)
{
  size_t rv = 0;
  sdp_list_t const *l;

  for (l = v; l; l = l->l_next) {
    rv += STRUCT_ALIGN(rv);
    rv += xtra(l);
  }

  return rv;
}

static
void *list_dup_all(dup_f *dup, char **pp, void const *vsrc)
{
  char *p;
  sdp_list_t const *src;
  sdp_list_t *retval = NULL, *l, **ll = &retval;

  p = *pp;

  for (src = vsrc; src; src = src->l_next) {
    p += STRUCT_ALIGN(p);
    l = dup(&p, src);
    assert(l);
    *ll = l; ll = &l->l_next;
  }

  *pp = p;

  return retval;
}

#if 0
static size_t XXX_xtra(sdp_XXX_t const *YYY)
{
  size_t rv = sizeof(*YYY);
  rv += strlen(YYY->YYY_encoding) + 1;
  if (YYY->YYY_params);
    rv += strlen(YYY->YYY_params) + 1;
  return rv;
}

static
sdp_XXX_t *XXX_dup(char **pp, sdp_XXX_t const *src)
{
  char *p;
  sdp_XXX_t *YYY;

  p = *pp; ASSERT_STRUCT_ALIGN(p);
  YYY = memcpy(p, src, src->YYY_size);
  p += src->YYY_size;
  YYY->YYY_next = NULL;
  ZZZ
  *pp = p;
  return YYY;
}

#endif

static size_t session_xtra(sdp_session_t const *sdp)
{
  size_t rv = sizeof(*sdp);

  PTR_XTRA(rv, sdp->sdp_origin, origin_xtra);
  STR_XTRA(rv, sdp->sdp_subject);
  STR_XTRA(rv, sdp->sdp_information);
  STR_XTRA(rv, sdp->sdp_uri);
  LST_XTRA(rv, sdp->sdp_emails, list_xtra);
  LST_XTRA(rv, sdp->sdp_phones, list_xtra);
  LST_XTRA(rv, sdp->sdp_connection, connection_xtra);
  LST_XTRA(rv, sdp->sdp_bandwidths, bandwidth_xtra);
  LST_XTRA(rv, sdp->sdp_time, time_xtra);
  PTR_XTRA(rv, sdp->sdp_key, key_xtra);
  LST_XTRA(rv, sdp->sdp_attributes, attribute_xtra);
  STR_XTRA(rv, sdp->sdp_charset);
  MED_XTRA_ALL(rv, sdp->sdp_media);

  return rv;
}

static
sdp_session_t *session_dup(char **pp, sdp_session_t const *src)
{
  char *p;
  sdp_session_t *sdp;

  p = *pp;
  STRUCT_DUP(p, sdp, src);
  sdp->sdp_next = NULL;

  PTR_DUP(p, sdp, src, sdp_origin, origin_dup);
  STR_DUP(p, sdp, src, sdp_subject);
  STR_DUP(p, sdp, src, sdp_information);
  STR_DUP(p, sdp, src, sdp_uri);
  LST_DUP(p, sdp, src, sdp_emails, list_dup);
  LST_DUP(p, sdp, src, sdp_phones, list_dup);
  LST_DUP(p, sdp, src, sdp_connection, connection_dup);
  LST_DUP(p, sdp, src, sdp_bandwidths, bandwidth_dup);
  LST_DUP(p, sdp, src, sdp_time, time_dup);
  PTR_DUP(p, sdp, src, sdp_key, key_dup);
  LST_DUP(p, sdp, src, sdp_attributes, attribute_dup);
  STR_DUP(p, sdp, src, sdp_charset);
  MED_DUP_ALL(p, sdp, src, sdp_media);

  assert((size_t)(p - *pp) == session_xtra(src));
  *pp = p;
  return sdp;
}

/**Duplicate an SDP session description.
 *
 * The function sdp_session_dup() duplicates (deeply copies) an SDP
 * session description @a sdp allocating memory using memory @a home.
 *
 * @param h   Memory home
 * @param sdp SDP session description to be duplicated
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_session_t structure is
 * returned, otherwise NULL is returned.
 */

sdp_session_t *sdp_session_dup(su_home_t *h, sdp_session_t const *sdp)
{
  SDP_DUP(session, sdp);
}

/* ---------------------------------------------------------------------- */

static size_t session_without_media_xtra(sdp_session_t const *sdp)
{
  size_t rv = sizeof(*sdp);

  PTR_XTRA(rv, sdp->sdp_origin, origin_xtra);
  STR_XTRA(rv, sdp->sdp_subject);
  STR_XTRA(rv, sdp->sdp_information);
  STR_XTRA(rv, sdp->sdp_uri);
  LST_XTRA(rv, sdp->sdp_emails, list_xtra);
  LST_XTRA(rv, sdp->sdp_phones, list_xtra);
  LST_XTRA(rv, sdp->sdp_connection, connection_xtra);
  LST_XTRA(rv, sdp->sdp_bandwidths, bandwidth_xtra);
  LST_XTRA(rv, sdp->sdp_time, time_xtra);
  PTR_XTRA(rv, sdp->sdp_key, key_xtra);
  LST_XTRA(rv, sdp->sdp_attributes, attribute_xtra);
  STR_XTRA(rv, sdp->sdp_charset);

  return rv;
}

static
sdp_session_t *session_without_media_dup(char **pp, sdp_session_t const *src)
{
  char *p;
  sdp_session_t *sdp;

  p = *pp;
  STRUCT_DUP(p, sdp, src);
  sdp->sdp_next = NULL;

  PTR_DUP(p, sdp, src, sdp_origin, origin_dup);
  STR_DUP(p, sdp, src, sdp_subject);
  STR_DUP(p, sdp, src, sdp_information);
  STR_DUP(p, sdp, src, sdp_uri);
  LST_DUP(p, sdp, src, sdp_emails, list_dup);
  LST_DUP(p, sdp, src, sdp_phones, list_dup);
  LST_DUP(p, sdp, src, sdp_connection, connection_dup);
  LST_DUP(p, sdp, src, sdp_bandwidths, bandwidth_dup);
  LST_DUP(p, sdp, src, sdp_time, time_dup);
  PTR_DUP(p, sdp, src, sdp_key, key_dup);
  LST_DUP(p, sdp, src, sdp_attributes, attribute_dup);
  STR_DUP(p, sdp, src, sdp_charset);

  sdp->sdp_media = NULL;

  assert((size_t)(p - *pp) == session_without_media_xtra(src));
  *pp = p;
  return sdp;
}

/* SDP_DUP macro requires this */
typedef sdp_session_t sdp_session_without_media_t;

/**Duplicate an SDP session description without media descriptions.
 *
 * The function sdp_session_dup() duplicates (deeply copies) an SDP session
 * description @a sdp allocating memory using memory @a home. It does not
 * copy the media descriptions, however.
 *
 * @param h     memory h
 * @param sdp   SDP session description to be duplicated
 *
 * @note The duplicated structure is allocated using a single call to
 * su_alloc() and it can be freed with su_free().
 *
 * @return
 * If successful, a pointer to newly allocated sdp_session_t structure is
 * returned, otherwise NULL is returned.
 */

sdp_session_t *sdp_session_dup_without_media(su_home_t *h,
					     sdp_session_t const *sdp)
{
  SDP_DUP(session_without_media, sdp);
}

/* ---------------------------------------------------------------------- */
/* SDP Tag classes */

#include <sofia-sip/su_tag_class.h>

size_t sdptag_session_xtra(tagi_t const *t, size_t offset)
{
  sdp_session_t const *sdp = (sdp_session_t *)t->t_value;

  if (sdp)
    return STRUCT_ALIGN(offset) + session_xtra(sdp);
  else
    return 0;
}

tagi_t *sdptag_session_dup(tagi_t *dst, tagi_t const *src, void **bb)
{
  sdp_session_t *sdp;
  sdp_session_t const *srcsdp;
  char *b;

  assert(src); assert(*bb);

  b = *bb;
  b += STRUCT_ALIGN(b);
  srcsdp = (sdp_session_t *)src->t_value;

  sdp = srcsdp ? session_dup(&b, srcsdp) : NULL;

  dst->t_tag = src->t_tag;
  dst->t_value = (tag_value_t)sdp;

  *bb = b;

  return dst + 1;
}

int sdptag_session_snprintf(tagi_t const *t, char b[], size_t size)
{
  sdp_session_t const *sdp;
  sdp_printer_t *print;
  size_t retval;

  assert(t);

  if (!t || !t->t_value) {
    if (size && b) b[0] = 0;
    return 0;
  }

  sdp = (sdp_session_t const *)t->t_value;

  print = sdp_print(NULL, sdp, b, size, 0);

  retval = sdp_message_size(print);

  sdp_printer_free(print);

  return (int)retval;
}

/** Tag class for SDP tags. @HIDE */
tag_class_t sdptag_session_class[1] =
  {{
    sizeof(sdptag_session_class),
    /* tc_next */     NULL,
    /* tc_len */      NULL,
    /* tc_move */     NULL,
    /* tc_xtra */     sdptag_session_xtra,
    /* tc_dup */      sdptag_session_dup,
    /* tc_free */     NULL,
    /* tc_find */     NULL,
    /* tc_snprintf */ sdptag_session_snprintf,
    /* tc_filter */   NULL /* msgtag_str_filter */,
    /* tc_ref_set */  t_ptr_ref_set,
  }};


/* ---------------------------------------------------------------------- */

/* Compare two string pointers */
su_inline
int str0cmp(char const *a, char const *b)
{
  if (a == NULL) a = "";
  if (b == NULL) b = "";
  return strcmp(a, b);
}

/* Compare two string pointers ignoring case. */
su_inline
int str0casecmp(char const *a, char const *b)
{
  if (a == NULL) a = "";
  if (b == NULL) b = "";
  return strcasecmp(a, b);
}

/** Compare two session descriptions
 */
int sdp_session_cmp(sdp_session_t const *a, sdp_session_t const *b)
{
  int rv;
  sdp_bandwidth_t const *ab, *bb;
  sdp_attribute_t const *aa, *ba;
  sdp_media_t const *am, *bm;

  if ((rv = (a != NULL) - (b != NULL)))
    return rv;
  if (a == b)
    return 0;
  if ((rv = (a->sdp_version[0] - b->sdp_version[0])))
    return rv;
  if ((rv = sdp_origin_cmp(a->sdp_origin, b->sdp_origin)))
    return rv;
  if ((rv = str0cmp(a->sdp_subject, b->sdp_subject)))
    return rv;
  if ((rv = str0cmp(a->sdp_information, b->sdp_information)))
    return rv;
  if ((rv = str0cmp(a->sdp_uri, b->sdp_uri)))
    return rv;
  if ((rv = sdp_list_cmp(a->sdp_emails, b->sdp_emails)))
    return rv;
  if ((rv = sdp_list_cmp(a->sdp_phones, b->sdp_phones)))
    return rv;
  if ((rv = sdp_connection_cmp(a->sdp_connection, b->sdp_connection)))
    return rv;

  for (ab = a->sdp_bandwidths, bb = b->sdp_bandwidths;
       ab || bb;
       ab = ab->b_next, bb = bb->b_next)
    if ((rv = sdp_bandwidth_cmp(a->sdp_bandwidths, b->sdp_bandwidths)))
      return rv;

  if ((rv = sdp_time_cmp(a->sdp_time, b->sdp_time)))
    return rv;
  if ((rv = sdp_key_cmp(a->sdp_key, b->sdp_key)))
    return rv;

  for (aa = a->sdp_attributes, ba = b->sdp_attributes;
       aa || bb;
       aa = aa->a_next, ba = ba->a_next)
    if ((rv = sdp_attribute_cmp(aa, ba)))
      return rv;

  for (am = a->sdp_media, bm = b->sdp_media;
       am || bm;
       am = am->m_next, bm = bm->m_next)
    if ((rv = sdp_media_cmp(am, bm)))
      return rv;

  return 0;
}

/** Compare two origin fields
 */
int sdp_origin_cmp(sdp_origin_t const *a, sdp_origin_t const *b)
{
  int rv;

  if ((rv = (a != NULL) - (b != NULL)))
    return rv;
  if (a == b)
    return 0;
  if (a->o_version != b->o_version)
    return a->o_version < b->o_version ? -1 : 1;
  if (a->o_id != b->o_id)
    return a->o_id < b->o_id ? -1 : 1;
  if ((rv = strcasecmp(a->o_username, b->o_username)))
    return rv;
  if ((rv = strcasecmp(a->o_address->c_address, b->o_address->c_address)))
    return rv;

  return 0;
}

/** Compare two connection fields
 */
int sdp_connection_cmp(sdp_connection_t const *a, sdp_connection_t const *b)
{
  if (a == b)
    return 0;
  if ((a != NULL) != (b != NULL))
    return (a != NULL) < (b != NULL) ? -1 : 1;

  if (a->c_nettype != b->c_nettype)
    return a->c_nettype < b->c_nettype ? -1 : 1;
  if (a->c_addrtype != b->c_addrtype)
    return a->c_addrtype < b->c_addrtype ? -1 : 1;
  if (a->c_ttl != b->c_ttl)
    return a->c_ttl < b->c_ttl ? -1 : 1;
  if (a->c_groups != b->c_groups)
    return a->c_groups < b->c_groups ? -1 : 1;

  return strcmp(a->c_address, b->c_address);
}

/** Compare two bandwidth (b=) fields */
int sdp_bandwidth_cmp(sdp_bandwidth_t const *a, sdp_bandwidth_t const *b)
{
  int rv;

  if (a == b)
    return 0;
  if ((a != NULL) != (b != NULL))
    return (a != NULL) < (b != NULL) ? -1 : 1;

  if (a->b_modifier != b->b_modifier)
    return a->b_modifier < b->b_modifier ? -1 : 1;
  if (a->b_modifier == sdp_bw_x &&
      (rv = strcmp(a->b_modifier_name, b->b_modifier_name)))
    return rv;

  if (a->b_value != b->b_value)
    return a->b_value < b->b_value ? -1 : 1;

  return 0;
}

/** Compare two time fields */
int sdp_time_cmp(sdp_time_t const *a, sdp_time_t const *b)
{
  int rv;

  if ((rv = (a != NULL) - (b != NULL)))
    return rv;
  if (a == b)
    return 0;
  if (a->t_start != b->t_start)
    return a->t_start < b->t_start ? -1 : 1;
  if (a->t_stop != b->t_stop)
    return a->t_stop < b->t_stop ? -1 : 1;
  if ((rv = sdp_zone_cmp(a->t_zone, b->t_zone)))
    return rv;
  if ((rv = sdp_repeat_cmp(a->t_repeat, b->t_repeat)))
    return rv;
  return 0;
}

/** Compare two repeat (r=) fields */
int sdp_repeat_cmp(sdp_repeat_t const *a, sdp_repeat_t const *b)
{
  int i, n;

  if (a == b)
    return 0;
  if ((a != NULL) != (b != NULL))
    return (a != NULL) < (b != NULL) ? -1 : 1;

  if (a->r_interval != b->r_interval)
    return a->r_interval < b->r_interval ? -1 : 1;
  if (a->r_duration != b->r_duration)
    return a->r_duration < b->r_duration ? -1 : 1;
  n = a->r_number_of_offsets < b->r_number_of_offsets
    ? a->r_number_of_offsets : b->r_number_of_offsets;
  for (i = 0; i < n; i++)
    if (a->r_offsets[i] != b->r_offsets[i])
      return a->r_offsets[i] < b->r_offsets[i] ? -1 : 1;

  if (a->r_number_of_offsets != b->r_number_of_offsets)
    return a->r_number_of_offsets < b->r_number_of_offsets ? -1 : 1;

  return 0;
 }

/** Compare two zone (z=) fields */
int sdp_zone_cmp(sdp_zone_t const *a, sdp_zone_t const *b)
{
  int i, n;

  if (a == b)
    return 0;
  if ((a != NULL) != (b != NULL))
    return (a != NULL) < (b != NULL) ? -1 : 1;

  n = a->z_number_of_adjustments < b->z_number_of_adjustments
    ? a->z_number_of_adjustments : b->z_number_of_adjustments;
  for (i = 0; i < n; i++) {
    if (a->z_adjustments[i].z_at != b->z_adjustments[i].z_at)
      return a->z_adjustments[i].z_at < b->z_adjustments[i].z_at ? -1 : 1;
    if (a->z_adjustments[i].z_offset != b->z_adjustments[i].z_offset)
      return a->z_adjustments[i].z_offset < b->z_adjustments[i].z_offset
	? -1 : 1;
  }

  if (a->z_number_of_adjustments != b->z_number_of_adjustments)
    return a->z_number_of_adjustments < b->z_number_of_adjustments ? -1 : 1;

  return 0;
}

/** Compare two key (k=) fields */
int sdp_key_cmp(sdp_key_t const *a, sdp_key_t const *b)
{
  int rv;

  if (a == b)
    return 0;
  if ((a != NULL) != (b != NULL))
    return (a != NULL) < (b != NULL) ? -1 : 1;

  if (a->k_method != b->k_method)
    return a->k_method < b->k_method ? -1 : 1;
  if (a->k_method == sdp_key_x &&
      (rv = str0cmp(a->k_method_name, b->k_method_name)))
    return rv;
  return str0cmp(a->k_material, b->k_material);
}

/** Compare two attribute (a=) fields */
int sdp_attribute_cmp(sdp_attribute_t const *a, sdp_attribute_t const *b)
{
  int rv;

  if (a == b)
    return 0;
  if ((a != NULL) != (b != NULL))
    return (a != NULL) < (b != NULL) ? -1 : 1;

  if ((rv = str0cmp(a->a_name, b->a_name)))
    return rv;
  return str0cmp(a->a_value, b->a_value);
}

/** Compare two rtpmap structures. */
int sdp_rtpmap_cmp(sdp_rtpmap_t const *a, sdp_rtpmap_t const *b)
{
  int rv;

  if (a == b)
    return 0;
  if ((a != NULL) != (b != NULL))
    return (a != NULL) < (b != NULL) ? -1 : 1;

  if (a->rm_pt != b->rm_pt)
    return a->rm_pt < b->rm_pt ? -1 : 1;

  /* Case insensitive encoding */
  if ((rv = str0cmp(a->rm_encoding, b->rm_encoding)))
    return rv;
  /* Rate */
  if (a->rm_rate != b->rm_rate)
    return a->rm_rate < b->rm_rate ? -1 : 1;

  {
    char const *a_param = "1", *b_param = "1";

    if (a->rm_params)
      a_param = a->rm_params;
    if (b->rm_params)
      b_param = b->rm_params;

    rv = strcasecmp(a_param, b_param);

    if (rv)
      return rv;
  }

  return str0casecmp(a->rm_fmtp, b->rm_fmtp);
}

/** Compare two lists. */
int sdp_list_cmp(sdp_list_t const *a, sdp_list_t const *b)
{
  int rv;

  for (;a || b; a = a->l_next, b = b->l_next) {
    if (a == b)
      return 0;
    if ((a != NULL) != (b != NULL))
      return (a != NULL) < (b != NULL) ? -1 : 1;
    if ((rv = str0cmp(a->l_text, b->l_text)))
      return rv;
  }

  return 0;
}

/** Compare two media (m=) fields */
int sdp_media_cmp(sdp_media_t const *a, sdp_media_t const *b)
{
  int rv;

  sdp_connection_t const *ac, *bc;
  sdp_bandwidth_t const *ab, *bb;
  sdp_rtpmap_t const *arm, *brm;
  sdp_attribute_t const *aa, *ba;

  if (a == b)
    return 0;
  if ((rv = (a != NULL) - (b != NULL)))
    return rv;

  if (a->m_type != b->m_type)
    return a->m_type < b->m_type ? -1 : 1;
  if (a->m_type == sdp_media_x)
    if ((rv = str0cmp(a->m_type_name, b->m_type_name)))
      return rv;
  if (a->m_port != b->m_port)
    return a->m_port < b->m_port ? -1 : 1;

  if (a->m_port == 0 /* && b->m_port == 0 */)
    /* Ignore transport protocol and media list if media has been rejected */
    return 0;

  if (a->m_number_of_ports != b->m_number_of_ports)
    return a->m_number_of_ports < b->m_number_of_ports ? -1 : 1;

  if (a->m_proto != b->m_proto)
    return a->m_proto < b->m_proto ? -1 : 1;
  if (a->m_proto == sdp_media_x)
    if ((rv = str0cmp(a->m_proto_name, b->m_proto_name)))
      return rv;

  if (a->m_mode != b->m_mode)
    return a->m_mode < b->m_mode ? -1 : 1;

  for (arm = a->m_rtpmaps, brm = b->m_rtpmaps;
       arm || brm;
       arm = arm->rm_next, brm = brm->rm_next)
    if ((rv = sdp_rtpmap_cmp(arm, brm)))
      return rv;

  if ((rv = sdp_list_cmp(a->m_format, b->m_format)))
    return rv;

  if ((rv = str0cmp(a->m_information, b->m_information)))
    return rv;

  for (ac = a->m_connections, bc = b->m_connections;
       ac || bc;
       ac = ac->c_next, bc = bc->c_next)
  if ((rv = sdp_connection_cmp(ac, bc)))
    return rv;

  for (ab = a->m_bandwidths, bb = b->m_bandwidths;
       ab || bb;
       ab = ab->b_next, bb = bb->b_next)
    if ((rv = sdp_bandwidth_cmp(a->m_bandwidths, b->m_bandwidths)))
      return rv;

  if ((rv = sdp_key_cmp(a->m_key, b->m_key)))
    return rv;

  for (aa = a->m_attributes, ba = b->m_attributes;
       aa || bb;
       aa = aa->a_next, ba = ba->a_next)
    if ((rv = sdp_attribute_cmp(aa, ba)))
      return rv;

  return 0;
}

/* ---------------------------------------------------------------------- */

sdp_connection_t *sdp_media_connections(sdp_media_t const *m)
{
  if (m) {
    if (m->m_connections)
      return m->m_connections;
    if (m->m_session)
      return m->m_session->sdp_connection;
  }
  return NULL;
}

/* ---------------------------------------------------------------------- */

/** Find named attribute from given list. */
sdp_attribute_t *sdp_attribute_find(sdp_attribute_t const *a, char const *name)
{
  for (; a; a = a->a_next) {
    if (strcasecmp(a->a_name, name) == 0)
      break;
  }

  return (sdp_attribute_t *)a;
}

/** Find named attribute from given lists (a or a2). */
sdp_attribute_t *sdp_attribute_find2(sdp_attribute_t const *a,
				     sdp_attribute_t const *a2,
				     char const *name)
{
  for (; a; a = a->a_next) {
    if (strcasecmp(a->a_name, name) == 0)
      break;
  }

  if (a == 0)
    for (a = a2; a; a = a->a_next) {
      if (strcasecmp(a->a_name, name) == 0)
	break;
    }

  return (sdp_attribute_t *)a;
}

/** Get session mode from attribute list. */
sdp_mode_t sdp_attribute_mode(sdp_attribute_t const *a, sdp_mode_t defmode)
{
  for (; a; a = a->a_next) {
    if (strcasecmp(a->a_name, "sendrecv") == 0)
      return sdp_sendrecv;
    if (strcasecmp(a->a_name, "inactive") == 0)
      return sdp_inactive;
    if (strcasecmp(a->a_name, "recvonly") == 0)
      return sdp_recvonly;
    if (strcasecmp(a->a_name, "sendonly") == 0)
      return sdp_sendonly;
  }

  return defmode;
}

/** Convert session mode as #sdp_attribute_t structure. */
sdp_attribute_t *sdp_attribute_by_mode(su_home_t *home, sdp_mode_t mode)
{
  sdp_attribute_t *a;
  char const *name;

  if (mode == sdp_inactive)
    name = "inactive";
  else if (mode == sdp_sendonly)
    name = "sendonly";
  else if (mode == sdp_recvonly)
    name = "recvonly";
  else if (mode == sdp_sendrecv)
    name = "sendrecv";
  else
    return NULL;

  a = su_salloc(home, sizeof(*a));
  if (a)
    a->a_name = name;

  return a;
}

/** Find a mapped attribute.
 *
 * A mapped attribute has form 'a=<name>:<pt> <value>' where pt is a RTP
 * payload type, integer in range 0..127. For example, "a=atmmap" [@RFC3108]
 * is a mapped attribute. Note that common mapped attributes, "a=rtpmap" and
 * "a=fmtp" are already parsed as list of #sdp_rtpmap_t in #sdp_media_t.
 *
 * @param a pointer to first attribute in the list
 * @param name name of the attribute
 * @param pt payload type number (must be 0..127)
 * @param return_result return value parameter for mapped attribute value
 *
 * @return Pointer to a matching attribute structure, or NULL.
 *
 * If a matching attribute is found, @a return_result will point to part of
 * the attribute after the payload type and whitespace.
 */
sdp_attribute_t *sdp_attribute_mapped_find(sdp_attribute_t const *a,
					   char const *name,
					   int pt, char **return_result)
{
  char pt_value[4];
  size_t pt_len;

  if (return_result)
    *return_result = NULL;

  if (0 > pt || pt > 127)
    return NULL;

  snprintf(pt_value, sizeof(pt_value), "%u", (unsigned)pt);
  pt_len = strlen(pt_value);

  for (; (a = sdp_attribute_find(a, name)); a = a->a_next) {
    char const *value = a->a_value;
    size_t wlen;

    if (strncmp(value, pt_value, pt_len))
      continue;

    wlen = strspn(value + pt_len, " \t");

    if (wlen == 0 || value[pt_len + wlen] == '\0')
      continue;

    if (return_result)
      *return_result = (char *)value + pt_len + wlen;

    return (sdp_attribute_t *)a;
  }

  return NULL;
}

/** Append a (list of) attribute(s) to a list of attributes. */
void sdp_attribute_append(sdp_attribute_t **list,
			  sdp_attribute_t const *a)
{
  assert(list);

  if (list == NULL || a == NULL)
    return;

  for (;*list; list = &(*list)->a_next)
    ;

  *list = (sdp_attribute_t *)a;
}

/**Replace or append a attribute within a list of attributes.
 *
 * @retval 1 if replaced existing attribute
 * @retval 0 if attribute was appended
 * @retval -1 upon an error
 */
int sdp_attribute_replace(sdp_attribute_t **list,
			  sdp_attribute_t *a,
			  sdp_attribute_t **return_replaced)
{
  sdp_attribute_t *replaced;

  assert(list);

  if (return_replaced)
    *return_replaced = NULL;

  if (list == NULL || a == NULL)
    return -1;

  assert(a->a_name != NULL); assert(a->a_next == NULL);

  for (; *list; list = &(*list)->a_next) {
    if (strcasecmp((*list)->a_name, a->a_name) == 0)
      break;
  }

  replaced = *list, *list = a;

  if (replaced) {
    a->a_next = replaced->a_next;
    replaced->a_next = NULL;

    if (return_replaced)
      *return_replaced = replaced;

    return 1;
  }

  return 0;
}

/** Remove a named attribute from a list of attributes. */
sdp_attribute_t *sdp_attribute_remove(sdp_attribute_t **list,
				      char const *name)
{
  sdp_attribute_t *a;

  assert(list);

  if (list == NULL)
    return NULL;
  if (name == NULL)
    return NULL;

  for (a = *list; a; list = &a->a_next, a = *list) {
    if (strcasecmp(name, a->a_name) == 0)
      break;
  }

  if (a) {
    *list = a->a_next;
    a->a_next = NULL;
  }

  return a;
}

/* Return 1 if m= line struct matches with given type and name */
unsigned sdp_media_match(sdp_media_t const *m,
			 sdp_media_e type,
			 sdp_text_t *type_name,
			 sdp_proto_e proto,
			 sdp_text_t *proto_name)
{
  if (m == NULL)
    return 0;

  if (type == sdp_media_any || m->m_type == sdp_media_any)
    return 1;

  if (type_name == NULL)
    type_name = "";

  if (type != m->m_type ||
      (type == sdp_media_x && strcasecmp(m->m_type_name, type_name)))
    return 0;

  if (proto == sdp_proto_any || m->m_proto == sdp_proto_any)
    return 1;

  if (proto_name == NULL)
    proto_name = "";

  if (proto != m->m_proto ||
      (proto == sdp_proto_x && strcasecmp(m->m_proto_name, proto_name)))
    return 0;

  return 1;
}

/* Return 1 if media type and protocol of m= line structs matches */
unsigned sdp_media_match_with(sdp_media_t const *a,
			      sdp_media_t const *b)
{
  if (a == NULL || b == NULL)
    return a == b;

  if (a->m_type == sdp_media_any || b->m_type == sdp_media_any)
    return 1;

  if (a->m_type != b->m_type ||
      (a->m_type == sdp_media_x &&
       strcasecmp(b->m_type_name, a->m_type_name)))
    return 0;

  if (a->m_proto == sdp_proto_any || b->m_proto == sdp_proto_any)
    return 1;

  if (a->m_proto != b->m_proto ||
      (a->m_proto == sdp_proto_x &&
       strcasecmp(b->m_proto_name, a->m_proto_name)))
    return 0;

  return 1;
}


/** Count matching media lines in SDP. */
unsigned sdp_media_count(sdp_session_t const *sdp,
			 sdp_media_e type,
			 sdp_text_t *type_name,
			 sdp_proto_e proto,
			 sdp_text_t *proto_name)
{
  unsigned count = 0;
  sdp_media_t const *m;

  if (sdp != NULL)
    for (m = sdp->sdp_media; m; m = m->m_next)
      count += sdp_media_match(m, type, type_name, proto, proto_name);

  return count;
}

/** Count matching media lines in SDP. */
unsigned sdp_media_count_with(sdp_session_t const *sdp,
			      sdp_media_t const *m0)
{
  unsigned count = 0;
  sdp_media_t const *m;

  if (sdp != NULL)
    for (m = sdp->sdp_media; m; m = m->m_next)
      count += sdp_media_match_with(m, m0);

  return count;
}

/** Return true if media uses RTP */
int sdp_media_uses_rtp(sdp_media_t const *m)
{
  return m &&
    (m->m_proto == sdp_proto_rtp ||
     m->m_proto == sdp_proto_srtp ||
     (m->m_proto == sdp_proto_x && m->m_proto_name &&
      strncasecmp(m->m_proto_name, "RTP/", 4) == 0));
}

/** Check if payload type, rtp rate and parameters match in rtpmaps*/
int sdp_rtpmap_match(sdp_rtpmap_t const *a, sdp_rtpmap_t const *b)
{
  char const *aparam, *bparam;

  if (a == b)
    return 1;

  if (a == 0 || b == 0)
    return 0;

  if (a->rm_rate != b->rm_rate)
    return 0;

  if (strcasecmp(a->rm_encoding, b->rm_encoding))
    return 0;

  aparam = a->rm_params; bparam = b->rm_params;

  if (aparam == bparam)
    return 1;

  if (!aparam) aparam = "1"; if (!bparam) bparam = "1";

  if (strcasecmp(aparam, bparam))
    return 0;

  return 1;
}

/** Search for matching rtpmap from list.
 *
 * @note
 * The a=fmtp: for the codecs are not compared.
 */
sdp_rtpmap_t *sdp_rtpmap_find_matching(sdp_rtpmap_t const *list,
				       sdp_rtpmap_t const *rm)
{
  char const *lparam, *rparam;

  if (rm == NULL)
    return NULL;

  for (; list; list = list->rm_next) {
    if (rm->rm_rate != list->rm_rate)
      continue;

    if (strcasecmp(rm->rm_encoding, list->rm_encoding) != 0)
      continue;

    lparam = rm->rm_params; rparam = list->rm_params;

    if (lparam == rparam)
      break;

    if (!lparam) lparam = "1"; if (!rparam) rparam = "1";
    if (strcasecmp(lparam, rparam))
      continue;

    break;
  }

  return (sdp_rtpmap_t *)list;
}
