/*
 * 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 soa.c
 * @brief Sofia SDP Offer/Answer Engine interface
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @date Created: Wed Aug  3 20:27:15 EEST 2005
 */

#include "config.h"

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <assert.h>

#include <sofia-sip/su_tag_class.h>
#include <sofia-sip/su_wait.h>

#include "sofia-sip/soa.h"
#include "sofia-sip/sdp.h"
#include "sofia-sip/soa_session.h"
#include "sofia-sip/soa_add.h"

#include <sofia-sip/hostdomain.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su_localinfo.h>
#include <sofia-sip/su_uniqueid.h>

#include <sofia-sip/string0.h>
#include <sofia-sip/su_errno.h>

#define NONE ((void *)-1)
#define XXX assert(!"implemented")

typedef unsigned longlong ull;

#if HAVE_FUNC
#elif HAVE_FUNCTION
#define __func__ __FUNCTION__
#else
static char const __func__[] = "soa";
#endif

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

/* Internal prototypes */
void soa_set_activity(soa_session_t *ss,
		      sdp_media_t const *,
		      int remote);

su_inline int soa_media_is_ready(soa_session_t const *ss);

enum soa_sdp_kind { 
  soa_capability_sdp_kind,
  soa_user_sdp_kind,
  soa_remote_sdp_kind
};

static int soa_set_sdp(soa_session_t *ss, 
		       enum soa_sdp_kind what,
		       sdp_session_t const *sdp0,
		       char const *sdp_str, issize_t str_len);

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

#define SOA_VALID_ACTIONS(a)					\
  ((a)->sizeof_soa_session_actions >= (int)sizeof (*actions) && \
   (a)->sizeof_soa_session >= (int)sizeof(soa_session_t) &&     \
   (a)->soa_name != NULL &&					\
   (a)->soa_init != NULL &&					\
   (a)->soa_deinit != NULL &&					\
   (a)->soa_set_params != NULL &&				\
   (a)->soa_get_params != NULL &&				\
   (a)->soa_get_paramlist != NULL &&				\
   (a)->soa_media_features != NULL &&				\
   (a)->soa_sip_require != NULL &&				\
   (a)->soa_sip_supported != NULL &&				\
   (a)->soa_remote_sip_features != NULL &&			\
   (a)->soa_set_capability_sdp != NULL &&			\
   (a)->soa_set_remote_sdp != NULL &&				\
   (a)->soa_set_user_sdp != NULL &&				\
   (a)->soa_generate_offer != NULL &&				\
   (a)->soa_generate_answer != NULL &&				\
   (a)->soa_process_answer != NULL &&				\
   (a)->soa_process_reject != NULL &&				\
   (a)->soa_activate_session != NULL &&				\
   (a)->soa_deactivate_session != NULL &&			\
   (a)->soa_terminate_session != NULL)

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

/**@var SOA_DEBUG
 *
 * Environment variable determining the default debug log level.
 *
 * The SOA_DEBUG environment variable is used to determine the default
 * debug logging level. The normal level is 3.
 *
 * @sa <sofia-sip/su_debug.h>, su_log_global, SOFIA_DEBUG
 */
extern char const SOA_DEBUG[];

#ifndef SU_DEBUG
#define SU_DEBUG 3
#endif

/**Debug log for @b soa module.
 *
 * The soa_log is the log object used by @b soa module. The level of
 * #soa_log is set using #SOA_DEBUG environment variable.
 */
su_log_t soa_log[] = { SU_LOG_INIT("soa", "SOA_DEBUG", SU_DEBUG) };

/* Add " around string */
#define NICE(s) s ? "\"" : "", s ? s : "(nil)", s ? "\"" : ""

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

/* API Functions */

struct soa_namenode
{
  struct soa_namenode const *next;
  char const *basename;
  struct soa_session_actions const *actions;
};

#define SOA_NAMELISTLEN (16)

static struct soa_namenode const soa_default_node =
  {
    NULL, "default", &soa_default_actions
  };

static struct soa_namenode const *soa_namelist = &soa_default_node;

/** Add a named soa backend */
int soa_add(char const *name,
	    struct soa_session_actions const *actions)
{
  struct soa_namenode const *n;
  struct soa_namenode *e;

  SU_DEBUG_9(("soa_add(%s%s%s, %p) called\n", NICE(name), (void *)actions));

  if (name == NULL || actions == NULL)
    return su_seterrno(EFAULT);

  if (!SOA_VALID_ACTIONS(actions))
    return su_seterrno(EINVAL);

  for (n = soa_namelist; n; n = n->next) {
    if (strcasecmp(name, n->basename) == 0)
      return 0;
  }

  e = malloc(sizeof *e); if (!e) return -1;

  e->next = soa_namelist;
  e->basename = name;
  e->actions = actions;

  soa_namelist = e;

  return 0;
}

/** Search for a named backend */
struct soa_session_actions const *soa_find(char const *name)
{
  SU_DEBUG_9(("soa_find(%s%s%s) called\n", NICE(name)));

  if (name) {
    struct soa_namenode const *n;
    size_t baselen = strcspn(name, ":/");

    for (n = soa_namelist; n; n = n->next) {
      if (strncasecmp(name, n->basename, baselen) == 0)
	break;
    }

    if (n == NULL)
      return (void)su_seterrno(ENOENT), NULL;

    return n->actions;
  }

  return NULL;
}

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

/** Create a soa session. */
soa_session_t *soa_create(char const *name,
			  su_root_t *root,
			  soa_magic_t *magic)
{
  struct soa_session_actions const *actions = &soa_default_actions;

  soa_session_t *ss;
  size_t namelen;

  SU_DEBUG_9(("soa_create(\"%s\", %p, %p) called\n",
	      name ? name : "default", (void *)root, (void *)magic));

  if (name && name[0]) {
    struct soa_namenode const *n;
    size_t baselen = strcspn(name, ":/");

    for (n = soa_namelist; n; n = n->next) {
      if (strncasecmp(name, n->basename, baselen) == 0)
	break;
    }
    if (n == NULL)
      return (void)su_seterrno(ENOENT), NULL;

    actions = n->actions; assert(actions);
  }
  else
    name = "default";

  assert(SOA_VALID_ACTIONS(actions));

  if (root == NULL)
    return (void)su_seterrno(EFAULT), NULL;

  namelen = strlen(name) + 1;
  
  ss = su_home_new(actions->sizeof_soa_session + namelen);
  if (ss) {
    ss->ss_root = root;
    ss->ss_magic = magic;
    ss->ss_actions = actions;
    ss->ss_name = strcpy((char *)ss + actions->sizeof_soa_session, name);

    if (ss->ss_actions->soa_init(name, ss, NULL) < 0)
      ss->ss_actions->soa_deinit(ss), ss = NULL;
  }

  return ss;
}

/** Create a copy of a @soa session object. */
soa_session_t *soa_clone(soa_session_t *parent_ss,
			 su_root_t *root,
			 soa_magic_t *magic)
{
  soa_session_t *ss;
  size_t namelen;

  SU_DEBUG_9(("soa_clone(%s::%p, %p, %p) called\n",
	      parent_ss ? parent_ss->ss_actions->soa_name : "",
	      (void *)parent_ss, (void *)root, (void *)magic));

  if (parent_ss == NULL || root == NULL)
    return (void)su_seterrno(EFAULT), NULL;

  namelen = strlen(parent_ss->ss_name) + 1;

  ss = su_home_new(parent_ss->ss_actions->sizeof_soa_session + namelen);
  if (ss) {
    ss->ss_root = root;
    ss->ss_magic = magic;
    ss->ss_actions = parent_ss->ss_actions;
    ss->ss_name = strcpy((char *)ss + ss->ss_actions->sizeof_soa_session,
			 parent_ss->ss_name);

    if (ss->ss_actions->soa_init(NULL, ss, parent_ss) < 0)
      ss->ss_actions->soa_deinit(ss), ss = NULL;
  }

  return ss;
}

/** Increase reference count */
soa_session_t *soa_session_ref(soa_session_t *ss)
{
  SU_DEBUG_9(("soa_session_ref(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));
  return su_home_ref(ss->ss_home);
}

/** Decrease reference count */
void soa_session_unref(soa_session_t *ss)
{
  SU_DEBUG_9(("soa_session_unref(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));
  su_home_unref(ss->ss_home);
}

/* Initialize session */
int soa_base_init(char const *name,
		     soa_session_t *ss,
		     soa_session_t *parent)
{
  if (parent) {
#define DUP(d, dup, s) if ((s) && !((d) = dup(ss->ss_home, (s)))) return -1
    su_home_t *home = ss->ss_home;

    if (soa_description_dup(home, ss->ss_caps, parent->ss_caps) < 0)
      return -1;
    if (soa_description_dup(home, ss->ss_user, parent->ss_user) < 0)
      return -1;
    if (soa_description_dup(home, ss->ss_local, parent->ss_local) < 0)
      return -1;
    if (soa_description_dup(home, ss->ss_remote, parent->ss_remote) < 0)
      return -1;

    DUP(ss->ss_address, su_strdup, parent->ss_address);
    ss->ss_af = parent->ss_af;
    DUP(ss->ss_hold, su_strdup, parent->ss_hold);

    DUP(ss->ss_cname, su_strdup, parent->ss_cname);

    ss->ss_srtp_enable = parent->ss_srtp_enable;
    ss->ss_srtp_confidentiality = parent->ss_srtp_confidentiality;
    ss->ss_srtp_integrity = parent->ss_srtp_integrity;
  }

  return 0;
}

/** Destroy a session. */
void soa_destroy(soa_session_t *ss)
{
  SU_DEBUG_9(("soa_destroy(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (ss) {
    ss->ss_active = 0;
    ss->ss_terminated++;
    ss->ss_actions->soa_deinit(ss);
    su_home_unref(ss->ss_home);
  }
}

void soa_base_deinit(soa_session_t *ss)
{
  (void)ss;
}

/** Set parameters.
 *
 * @param ss soa session object
 * @param tag, value, ... tagged parameter list
 *
 * @return Number of parameters set, or -1 upon an error.
 *
 * @TAGS
 * SOATAG_CAPS_SDP(),
 * SOATAG_CAPS_SDP_STR(),
 * SOATAG_USER_SDP(),
 * SOATAG_USER_SDP_STR(),
 * SOATAG_REMOTE_SDP(),
 * SOATAG_REMOTE_SDP_STR(),
 * SOATAG_AF(),
 * SOATAG_ADDRESS(),
 * SOATAG_AUDIO_AUX() (currently for "default" only),
 * SOATAG_HOLD(),
 * SOATAG_RTP_SELECT(),
 * SOATAG_RTP_SORT(),
 * SOATAG_RTP_MISMATCH(),
 * SOATAG_SRTP_ENABLE(),
 * SOATAG_SRTP_CONFIDENTIALITY(), and
 * SOATAG_SRTP_INTEGRITY().
 */
int soa_set_params(soa_session_t *ss, tag_type_t tag, tag_value_t value, ...)
{
  ta_list ta;
  int n;

  SU_DEBUG_9(("soa_set_params(%s::%p, ...) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (ss == NULL)
    return su_seterrno(EFAULT), -1;

  ta_start(ta, tag, value);

  n = ss->ss_actions->soa_set_params(ss, ta_args(ta));

  ta_end(ta);

  return n;
}

/**Base method for setting parameters.
 *
 * @param ss   soa session object
 * @param tags  tag item list
 *
 * @return Number of parameters set, or -1 upon an error.
 *
 * @TAGS
 * SOATAG_CAPS_SDP(),
 * SOATAG_CAPS_SDP_STR(),
 * SOATAG_USER_SDP(),
 * SOATAG_USER_SDP_STR(),
 * SOATAG_REMOTE_SDP(),
 * SOATAG_REMOTE_SDP_STR(),
 * SOATAG_AF(),
 * SOATAG_ADDRESS(),
 * SOATAG_HOLD(),
 * SOATAG_RTP_SELECT(),
 * SOATAG_RTP_SORT(),
 * SOATAG_RTP_MISMATCH(),
 * SOATAG_SRTP_ENABLE(),
 * SOATAG_SRTP_CONFIDENTIALITY(), and
 * SOATAG_SRTP_INTEGRITY().
 */
int soa_base_set_params(soa_session_t *ss, tagi_t const *tags)
{
  int n, change_session = 0;

  sdp_session_t const *caps_sdp, *user_sdp;
  char const *caps_sdp_str, *user_sdp_str;

  int af;
  char const *media_address, *hold;
  int rtp_select, rtp_sort;
  int rtp_mismatch;
  int srtp_enable, srtp_confidentiality, srtp_integrity;

  af = ss->ss_af;

  hold = ss->ss_hold;
  media_address = ss->ss_address;

  rtp_select = (int)ss->ss_rtp_select;
  rtp_sort = (int)ss->ss_rtp_sort;
  rtp_mismatch = ss->ss_rtp_mismatch;

  srtp_enable = ss->ss_srtp_enable;
  srtp_confidentiality = ss->ss_srtp_confidentiality;
  srtp_integrity = ss->ss_srtp_integrity;

  caps_sdp = user_sdp = NONE;
  caps_sdp_str = user_sdp_str = NONE;

  n = tl_gets(tags,

	      SOATAG_CAPS_SDP_REF(caps_sdp),
	      SOATAG_CAPS_SDP_STR_REF(caps_sdp_str),

	      SOATAG_USER_SDP_REF(user_sdp),
	      SOATAG_USER_SDP_STR_REF(user_sdp_str),

	      SOATAG_AF_REF(af),
	      SOATAG_ADDRESS_REF(media_address),
	      SOATAG_HOLD_REF(hold),

	      SOATAG_RTP_SELECT_REF(rtp_select),
	      SOATAG_RTP_SORT_REF(rtp_sort),
	      SOATAG_RTP_MISMATCH_REF(rtp_mismatch),

	      SOATAG_SRTP_ENABLE_REF(srtp_enable),
	      SOATAG_SRTP_CONFIDENTIALITY_REF(srtp_confidentiality),
	      SOATAG_SRTP_INTEGRITY_REF(srtp_integrity),

	      TAG_END());

  if (n <= 0)
    return n;

  if (caps_sdp != NONE || caps_sdp_str != NONE) {
    if (caps_sdp == NONE) caps_sdp = NULL;
    if (caps_sdp_str == NONE) caps_sdp_str = NULL;

    if (caps_sdp || caps_sdp_str) {
      if (soa_set_capability_sdp(ss, caps_sdp, caps_sdp_str, -1) < 0) {
	return -1;
      }
    }
    else {
      soa_description_free(ss, ss->ss_caps);
    }
  }

  if (user_sdp != NONE || user_sdp_str != NONE) {
    if (user_sdp == NONE) user_sdp = NULL;
    if (user_sdp_str == NONE) user_sdp_str = NULL;

    if (user_sdp || user_sdp_str) {
      if (soa_set_user_sdp(ss, user_sdp, user_sdp_str, -1) < 0) {
	return -1;
      }
      if (ss->ss_caps->ssd_str == NULL)
	soa_set_capability_sdp(ss, user_sdp, user_sdp_str, -1);
    }
    else {
      soa_description_free(ss, ss->ss_user);
    }
  }

  if (af < SOA_AF_ANY || af > SOA_AF_IP6_IP4) 
    af = ss->ss_af;

  if (rtp_select < SOA_RTP_SELECT_SINGLE || rtp_select > SOA_RTP_SELECT_ALL)
    rtp_select = (int)ss->ss_rtp_select;
  if (rtp_sort < SOA_RTP_SORT_DEFAULT || rtp_sort > SOA_RTP_SORT_REMOTE)
    rtp_sort = (int)ss->ss_rtp_sort;
  rtp_mismatch = rtp_mismatch != 0;

  srtp_enable = srtp_enable != 0;
  srtp_confidentiality = srtp_confidentiality != 0;
  srtp_integrity = srtp_integrity != 0;

  change_session 
    =  af != (int)ss->ss_af
    || rtp_select != (int)ss->ss_rtp_select
    || rtp_sort != (int)ss->ss_rtp_sort
    || rtp_mismatch != (int)ss->ss_rtp_mismatch
    || srtp_enable != (int)ss->ss_srtp_enable
    || srtp_confidentiality != (int)ss->ss_srtp_confidentiality
    || srtp_integrity != (int)ss->ss_srtp_integrity
    ;

  ss->ss_af = af;

  ss->ss_rtp_select = rtp_select;
  ss->ss_rtp_sort = rtp_sort;
  ss->ss_rtp_mismatch = rtp_mismatch;

  ss->ss_srtp_enable = srtp_enable;
  ss->ss_srtp_confidentiality = srtp_confidentiality;
  ss->ss_srtp_integrity = srtp_integrity;

  if (str0casecmp(media_address, ss->ss_address)) {
    su_free(ss->ss_home, (void *)ss->ss_address);
    ss->ss_address = su_strdup(ss->ss_home, media_address);
    change_session = 1;
  }

  if (hold == (char const *)1)
    hold = "*";

  if (str0casecmp(hold, ss->ss_hold)) {
    su_free(ss->ss_home, (void *)ss->ss_hold);
    ss->ss_hold = su_strdup(ss->ss_home, hold);
    change_session = 1;
  }

  if (change_session)
    ss->ss_user_version++;

  return n;
}

/** Get tagged parameters.
 *
 * @param ss soa session object
 * @param tag, value, ... tagged parameter list
 *
 * @return Number of parameters get, or -1 upon an error.
 *
 * @TAGS
 * SOATAG_CAPS_SDP(),
 * SOATAG_CAPS_SDP_STR(),
 * SOATAG_USER_SDP(),
 * SOATAG_USER_SDP_STR(),
 * SOATAG_LOCAL_SDP(),
 * SOATAG_LOCAL_SDP_STR(),
 * SOATAG_REMOTE_SDP(),
 * SOATAG_REMOTE_SDP_STR(),
 * SOATAG_AF(),
 * SOATAG_ADDRESS(),
 * SOATAG_AUDIO_AUX() (currently for "default" only),
 * SOATAG_HOLD(),
 * SOATAG_RTP_SELECT(),
 * SOATAG_RTP_SORT(),
 * SOATAG_RTP_MISMATCH(),
 * SOATAG_SRTP_ENABLE(),
 * SOATAG_SRTP_CONFIDENTIALITY(), and
 * SOATAG_SRTP_INTEGRITY().
 */
int soa_get_params(soa_session_t const *ss,
		   tag_type_t tag, tag_value_t value, ...)
{
  ta_list ta;
  int n;

  SU_DEBUG_9(("soa_get_params(%s::%p, ...) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (ss == NULL)
    return su_seterrno(EFAULT), -1;

  ta_start(ta, tag, value);

  n = ss->ss_actions->soa_get_params(ss, ta_args(ta));

  ta_end(ta);

  return n;
}

/**Base method for getting tagged parameters.
 *
 * @param ss soa session object
 * @param tags   tag item list
 *
 * @return Number of parameters get, or -1 upon an error.
 *
 * @TAGS
 * SOATAG_CAPS_SDP(),
 * SOATAG_CAPS_SDP_STR(),
 * SOATAG_USER_SDP(),
 * SOATAG_USER_SDP_STR(),
 * SOATAG_LOCAL_SDP(),
 * SOATAG_LOCAL_SDP_STR(),
 * SOATAG_REMOTE_SDP(),
 * SOATAG_REMOTE_SDP_STR(),
 * SOATAG_AF(),
 * SOATAG_ADDRESS(),
 * SOATAG_HOLD(),
 * SOATAG_RTP_SELECT(),
 * SOATAG_RTP_SORT(),
 * SOATAG_RTP_MISMATCH(),
 * SOATAG_SRTP_ENABLE(),
 * SOATAG_SRTP_CONFIDENTIALITY(), and
 * SOATAG_SRTP_INTEGRITY().
 */
int soa_base_get_params(soa_session_t const *ss, tagi_t *tags)
{
  int n;

  n = tl_tgets(tags,
	       SOATAG_CAPS_SDP(ss->ss_caps->ssd_sdp),
	       SOATAG_CAPS_SDP_STR(ss->ss_caps->ssd_str),

	       SOATAG_USER_SDP(ss->ss_user->ssd_sdp),
	       SOATAG_USER_SDP_STR(ss->ss_user->ssd_str),

	       SOATAG_LOCAL_SDP(ss->ss_local->ssd_sdp),
	       SOATAG_LOCAL_SDP_STR(ss->ss_local->ssd_str),

	       SOATAG_REMOTE_SDP(ss->ss_remote->ssd_sdp),
	       SOATAG_REMOTE_SDP_STR(ss->ss_remote->ssd_unparsed),

	       SOATAG_AF(ss->ss_af),
	       SOATAG_ADDRESS(ss->ss_address),
	       SOATAG_HOLD(ss->ss_hold),

	       SOATAG_RTP_SELECT((int)ss->ss_rtp_select),
	       SOATAG_RTP_SORT((int)ss->ss_rtp_sort),
	       SOATAG_RTP_MISMATCH(ss->ss_rtp_mismatch),

	       SOATAG_SRTP_ENABLE(ss->ss_srtp_enable),
	       SOATAG_SRTP_CONFIDENTIALITY(ss->ss_srtp_confidentiality),
	       SOATAG_SRTP_INTEGRITY(ss->ss_srtp_integrity),

	       TAG_END());

  return n;
}

/** Return a list of parameters. */
tagi_t *soa_get_paramlist(soa_session_t const *ss,
			  tag_type_t tag, tag_value_t value, ...)
{
  ta_list ta;
  tagi_t *params = NULL;

  SU_DEBUG_9(("soa_get_paramlist(%s::%p, ...) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (ss) {
    ta_start(ta, tag, value);
    params = ss->ss_actions->soa_get_paramlist(ss, ta_tags(ta));
    ta_end(ta);
  }

  return params;
}


/** Base bethod for getting list of parameters. */
tagi_t *soa_base_get_paramlist(soa_session_t const *ss, 
			       tag_type_t tag, tag_value_t value, 
			       ...)
{
  ta_list ta;
  tagi_t *params;

  if (ss == NULL)
    return NULL;
  
  ta_start(ta, tag, value);

  params = tl_llist(
		   TAG_IF(ss->ss_caps->ssd_sdp,
			  SOATAG_CAPS_SDP(ss->ss_caps->ssd_sdp)),
		   TAG_IF(ss->ss_caps->ssd_str,
			  SOATAG_CAPS_SDP_STR(ss->ss_caps->ssd_str)),

		   TAG_IF(ss->ss_user->ssd_sdp,
			  SOATAG_USER_SDP(ss->ss_user->ssd_sdp)),
		   TAG_IF(ss->ss_user->ssd_str,
			  SOATAG_USER_SDP_STR(ss->ss_user->ssd_str)),

		   TAG_IF(ss->ss_local->ssd_sdp,
			  SOATAG_LOCAL_SDP(ss->ss_local->ssd_sdp)),
		   TAG_IF(ss->ss_user->ssd_str,
			  SOATAG_LOCAL_SDP_STR(ss->ss_local->ssd_str)),

		   TAG_IF(ss->ss_remote->ssd_sdp,
			  SOATAG_REMOTE_SDP(ss->ss_remote->ssd_sdp)),
		   TAG_IF(ss->ss_remote->ssd_str,
			  SOATAG_REMOTE_SDP_STR(ss->ss_remote->ssd_unparsed)),

		   SOATAG_AF(ss->ss_af),
		   TAG_IF(ss->ss_address, 
			  SOATAG_ADDRESS(ss->ss_address)),

		   SOATAG_SRTP_ENABLE(ss->ss_srtp_enable),
		   SOATAG_SRTP_CONFIDENTIALITY(ss->ss_srtp_confidentiality),
		   SOATAG_SRTP_INTEGRITY(ss->ss_srtp_integrity),

		   ta_tags(ta));

  ta_end(ta);

  return params;
}

#include <sofia-sip/sip_status.h>

/** Convert @soa error to a SIP response code and phrase. */ 
int soa_error_as_sip_response(soa_session_t *ss,
			      char const **return_phrase)
{
  SU_DEBUG_9(("soa_error_as_sip_response(%s::%p, ...) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (ss == NULL || ss->ss_status < 400 || ss->ss_status >= 700) {
    if (return_phrase)
      *return_phrase = sip_500_Internal_server_error;
    return 500;
  }

  if (return_phrase)
    *return_phrase = ss->ss_phrase;
  return ss->ss_status;
}

/** Convert @soa error to a SIP @Reason header. */ 
char const *soa_error_as_sip_reason(soa_session_t *ss)
{
  char const *phrase;
  int status;
  char *reason;

  SU_DEBUG_9(("soa_error_as_sip_reason(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (ss == NULL)
    return "SIP;cause=500;text=\"Internal Server Error\"";

  status = soa_error_as_sip_response(ss, &phrase);

  reason = su_sprintf(ss->ss_home, "SIP;cause=%u;text=\"%s\"", status, phrase);
  
  if (ss->ss_reason)
    su_free(ss->ss_home, reason);

  return ss->ss_reason = reason;
}


/** Return SIP @Warning code and text. */
int soa_get_warning(soa_session_t *ss, char const **return_text)
{
  if (!ss)
    return 0;

  if (!ss->ss_wcode)
    return 0;

  if (return_text)
    *return_text = ss->ss_warning;

  return ss->ss_wcode;
}

/** Return SDP description of capabilities.
 *
 * @param ss  pointer to @soa session
 * @param return_sdp      return value for capability SDP structure 
 * @param return_sdp_str  return value for capability SDP string
 * @param return_len  return value for length of capability SDP string
 *
 * @retval 0 if there is no description to return
 * @retval 1 if description is returned
 * @retval -1 upon an error
 *
 * @sa @RFC3261 section 11, soa_set_capability_sdp(), 
 * SOATAG_CAPS_SDP(), SOATAG_CAPS_SDP_STR(), 
 * nua_options(), #nua_i_options
 */
int soa_get_capability_sdp(soa_session_t const *ss,
			   struct sdp_session_s const **return_sdp,
			   char const **return_sdp_str,
			   isize_t *return_len)
{
  sdp_session_t const *sdp;
  char const *sdp_str;

  SU_DEBUG_9(("soa_get_capability_sdp(%s::%p, [%p], [%p], [%p]) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss,
	      (void *)return_sdp, (void *)return_sdp_str, (void *)return_len));

  if (ss == NULL)
    return (void)su_seterrno(EFAULT), -1;

  sdp = ss->ss_caps->ssd_sdp;
  sdp_str = ss->ss_caps->ssd_str;

  if (sdp == NULL)
    return 0;
  if (return_sdp)
    *return_sdp = sdp;
  if (return_sdp_str)
    *return_sdp_str = sdp_str;
  if (return_len)
    *return_len = strlen(sdp_str);

  return 1;
}


/** Set capability SDP. 
 *
 * Capability SDP is used instead of user SDP when generating OPTIONS
 * responses describing media capabilities. 
 *
 * @param ss  pointer to @soa session
 * @param sdp pointer to SDP session structure
 * @param str pointer to string containing SDP session description
 * @param len lenght of string @a str
 *
 * @retval 1 when SDP is stored and it differs from previously stored
 * @retval 0 when SDP is identical to previously stored one (and user version
 *           returned by soa_get_user_version() is not incremented)
 * @retval -1 upon an error
 *
 * @sa @RFC3261 section 11, soa_get_capability_sdp(),
 * SOATAG_CAPS_SDP(), SOATAG_CAPS_SDP_STR(), 
 * nua_options(), #nua_i_options
 */
int soa_set_capability_sdp(soa_session_t *ss, 
			   struct sdp_session_s const *sdp,
			   char const *str, issize_t len)
{
  SU_DEBUG_9(("soa_set_capability_sdp(%s::%p, %p, %p, "MOD_ZD") called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss, (void *)sdp, (void *)str, (ssize_t)len));

  return soa_set_sdp(ss, soa_capability_sdp_kind, sdp, str, len);
}

/** Set capabilities */
int 
soa_base_set_capability_sdp(soa_session_t *ss, 
			    sdp_session_t *sdp, char const *str0, isize_t len0)
{
  sdp_origin_t o[1] = {{ sizeof(o) }};
  sdp_connection_t *c, c0[1] = {{ sizeof(c0) }};
  char c_address[64];
  sdp_time_t t[1] = {{ sizeof(t) }};
  sdp_media_t *m;

  if (sdp->sdp_origin)
    *o = *sdp->sdp_origin;
  else
    o->o_address = c0;

  sdp->sdp_origin = o;

  if (soa_init_sdp_origin(ss, o, c_address) < 0)
    return -1;

  if (!sdp->sdp_subject)
    sdp->sdp_subject = "-";

  sdp->sdp_time = t;

  /* Set port to zero - or should we check that port is already zero? */
  for (m = sdp->sdp_media; m; m = m->m_next)
    m->m_port = 0;

  c = sdp->sdp_origin->o_address;

  if (sdp->sdp_connection == NULL) {
    for (m = sdp->sdp_media; m; m = m->m_next)
      if (m->m_connections == NULL)
	break;
    if (m)
      sdp->sdp_connection = c;
  }

  return soa_description_set(ss, ss->ss_caps, sdp, str0, len0);
}

/**Return user SDP description.
 *
 * <i>User SDP</i> is used as basis for SDP Offer/Answer negotiation. It can
 * be very minimal template, consisting just m= line containing media name,
 * transport protocol port number and list of supported codecs.
 *
 * The SDP used as an offer or answer (generated by soa_generate_answer() or
 * soa_generate_offer()) is known as <i>local SDP</i> and it is available
 * with soa_get_local_sdp() or SOATAG_LOCAL_SDP()/SOATAG_LOCAL_SDP_STR()
 * tags.
 *
 * @param ss  pointer to @soa session
 * @param return_sdp SDP  session structure return value
 * @param return_sdp_str  return value for pointer to string 
 *                        containing the user SDP session description
 * @param return_len  return value for user SDP session description string
 *                    length 
 *
 * Any of the parameters @a return_sdp, @a return_sdp_str, or @a return_len 
 * may be NULL.
 *
 * @retval 0 if there is no description to return
 * @retval 1 if description is returned
 * @retval -1 upon an error
 *
 * @sa soa_get_local_sdp(), soa_set_user_sdp(), soa_get_user_version(),
 * SOATAG_USER_SDP(), SOATAG_USER_SDP_STR(), soa_get_remote_sdp(),
 * soa_get_capability_sdp()
 */
int soa_get_user_sdp(soa_session_t const *ss,
		     struct sdp_session_s const **return_sdp,
		     char const **return_sdp_str,
		     isize_t *return_len)
{
  sdp_session_t const *sdp;
  char const *sdp_str;

  SU_DEBUG_9(("soa_get_user_sdp(%s::%p, [%p], [%p], [%p]) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss,
			  (void *)return_sdp, (void *)return_sdp_str, (void *)return_len));

  if (ss == NULL)
    return (void)su_seterrno(EFAULT), -1;

  sdp = ss->ss_user->ssd_sdp;
  sdp_str = ss->ss_user->ssd_str;

  if (sdp == NULL)
    return 0;
  if (return_sdp)
    *return_sdp = sdp;
  if (return_sdp_str)
    *return_sdp_str = sdp_str;
  if (return_len)
    *return_len = strlen(sdp_str);

  return 1;
}

/**Returns the version number of user session description. The version
 * numbering starts from zero and it is incremented each time
 * soa_set_user_sdp() or soa_set_params() modifies user SDP.
 *
 * @param ss  pointer to @soa session

 * @return Sequence number of user SDP.
 *
 * @sa soa_set_user_sdp(), soa_get_user_sdp(), soa_set_params(),
 * SOATAG_USER_SDP(), SOATAG_USER_SDP_STR()
 */
int soa_get_user_version(soa_session_t const *ss)
{
  assert(ss != NULL);
  return ss ? (int)ss->ss_user_version : -1;
} 

/**Store user SDP to soa session. 
 *
 * User SDP is used as basis for SDP Offer/Answer negotiation. It can be
 * very minimal, consisting just m= line containing media name, transport
 * protocol port number and list of supported codecs.
 *
 * The SDP used as an offer or answer (generated by soa_generate_answer() or
 * soa_generate_offer()) is known as <i>local SDP</i> and it is available
 * with soa_get_local_sdp() or SOATAG_LOCAL_SDP()/SOATAG_LOCAL_SDP_STR()
 * tags.
 *
 * @param ss  pointer to @soa session
 * @param sdp pointer to SDP session structure
 * @param str pointer to string containing SDP session description
 * @param len lenght of string @a str
 *
 * Either @a sdp or @a str must be non-NULL. If @a len is -1, length of
 * string @a str is calculated using strlen().
 *
 * @retval 1 when SDP is stored and it differs from previously stored
 * @retval 0 when SDP is identical to previously stored one (and user version
 *           returned by soa_get_user_version() is not incremented)
 * @retval -1 upon an error
 *
 * @sa soa_get_user_sdp(), soa_get_user_version(), soa_set_params(),
 * SOATAG_USER_SDP(), SOATAG_USER_SDP_STR(), soa_generate_offer(),
 * soa_generate_answer(), soa_get_local_sdp(), soa_set_capability_sdp(),
 * soa_set_remote_sdp()
 */
int soa_set_user_sdp(soa_session_t *ss, 
		     struct sdp_session_s const *sdp,
		     char const *str, issize_t len)
{
  SU_DEBUG_9(("soa_set_user_sdp(%s::%p, %p, %p, "MOD_ZD") called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss, (void *)sdp, (void *)str, (ssize_t)len));

  return soa_set_sdp(ss, soa_user_sdp_kind, sdp, str, len);
}

/** Set user SDP (base version). */
int soa_base_set_user_sdp(soa_session_t *ss, 
			  sdp_session_t *sdp, char const *str0, isize_t len0)
{
  ++ss->ss_user_version;
  return soa_description_set(ss, ss->ss_user, sdp, str0, len0);
}

/**Return remote SDP description of the session.
 *
 * <i>Remote SDP</i> is used, together with <i>User SDP</i> as basis for SDP
 * Offer/Answer negotiation.
 *
 * @param ss  pointer to @soa session
 * @param return_sdp SDP  session structure return value
 * @param return_sdp_str  return value for pointer to string 
 *                        containing the user SDP session description
 * @param return_len  return value for user SDP session descrioption string
 *                    length 
 *
 * Any of the parameters @a return_sdp, @a return_sdp_str, or @a return_len 
 * may be NULL.
 *
 * @retval 0 if there is no description to return
 * @retval 1 if description is returned
 * @retval -1 upon an error
 *
 * @sa soa_set_remote_sdp(), soa_get_remote_version(), soa_get_params(),
 * soa_get_paramlist(), SOATAG_REMOTE_SDP(), SOATAG_REMOTE_SDP_STR(),
 * soa_get_local_sdp(), soa_get_user_sdp(), soa_get_capability_sdp().
 */
int soa_get_remote_sdp(soa_session_t const *ss,
		       struct sdp_session_s const **return_sdp,
		       char const **return_sdp_str,
		       isize_t *return_len)
{
  sdp_session_t const *sdp;
  char const *sdp_str;

  SU_DEBUG_9(("soa_get_remote_sdp(%s::%p, [%p], [%p], [%p]) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss,
			  (void *)return_sdp, (void *)return_sdp_str, (void *)return_len));

  if (ss == NULL)
    return (void)su_seterrno(EFAULT), -1;

  sdp = ss->ss_remote->ssd_sdp;
  sdp_str = ss->ss_remote->ssd_str;

  if (sdp == NULL)
    return 0;
  if (return_sdp)
    *return_sdp = sdp;
  if (return_sdp_str)
    *return_sdp_str = sdp_str;
  if (return_len)
    *return_len = strlen(sdp_str);

  return 1;
}

/**Returns the version number of remote session description. The version
 * numbering starts from zero and it is incremented each time
 * soa_set_remote_sdp() or soa_set_params() modifies remote SDP.
 *
 * @param ss  pointer to @soa session

 * @return Sequence number of remote SDP.
 *
 * @sa soa_set_remote_sdp(), soa_get_remote_sdp(), soa_set_params(),
 * SOATAG_REMOTE_SDP(), SOATAG_REMOTE_SDP_STR()
 */
int soa_get_remote_version(soa_session_t const *ss)
{
  assert(ss != NULL);
  return ss->ss_remote_version;
} 

/** Set remote SDP (offer or answer).
 *
 * <i>Remote SDP</i> is an SDP offer or answer received from the remote end. 
 * It is used together with <i>User SDP</i> as basis for SDP Offer/Answer
 * negotiation in soa_generate_answer() or soa_process_answer(). Remote SDP
 * can be set using soa_set_params() and SOATAG_REMOTE_SDP() or
 * SOATAG_REMOTE_SDP_STR() tags, too.
 *
 * If the SDP Offer/Answer negotiation step cannot be completed and the
 * received remote offer or answer should be ignored, call
 * soa_clear_remote_sdp().
 *
 * @param ss  pointer to @soa session
 * @param sdp pointer to SDP session structure
 * @param str pointer to string containing SDP session description
 * @param len lenght of string @a str
 *
 * Either @a sdp or @a str must be non-NULL. If @a len is -1, length of
 * string @a str is calculated using strlen().
 *
 * @retval 1 when SDP is stored and it differs from previously stored
 * @retval 0 when SDP is identical to previously stored one (and remote version
 *           returned by soa_get_remote_version() is not incremented)
 * @retval -1 upon an error
 *
 * @sa soa_has_received_sdp(), soa_get_remote_sdp(),
 * soa_get_remote_version(), soa_set_params(), SOATAG_REMOTE_SDP(),
 * SOATAG_REMOTE_SDP_STR(), soa_generate_answer(), soa_process_answer(),
 * soa_clear_remote_sdp(), soa_init_offer_answer(), soa_get_local_sdp(),
 * soa_set_user_sdp(), soa_set_capability_sdp().
 */
int soa_set_remote_sdp(soa_session_t *ss, 
		       struct sdp_session_s const *sdp,
		       char const *str, issize_t len)
{
  SU_DEBUG_9(("soa_set_remote_sdp(%s::%p, %p, %p, "MOD_ZD") called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss, (void *)sdp, (void *)str, (ssize_t)len));

  return soa_set_sdp(ss, soa_remote_sdp_kind, sdp, str, len);
}

/** Base method for setting the remote SDP (offer or answer). */
int soa_base_set_remote_sdp(soa_session_t *ss,
			    int new_version,
			    sdp_session_t *sdp, char const *str0, isize_t len0)
{
  /* This is cleared in soa_generate_answer() or soa_process_answer(). */
  ss->ss_unprocessed_remote = 1;

  if (!new_version)
    return 0;
    
  soa_set_activity(ss, sdp->sdp_media, 1);

  ss->ss_remote_version++;
  
  return soa_description_set(ss, ss->ss_remote, sdp, str0, len0);
}

/** Clear remote SDP.
 *
 * Remote SDP (offer or answer) should be cleared after a it has been stored
 * in the SDP session object using soa_set_remote_sdp() or soa_set_params(),
 * but the SDP Offer/Answer negotiation step (soa_generate_answer() or
 * soa_process_answer()) cannot be completed.
 *
 * @param ss  pointer to @soa session
 *
 * @retval 0  when successful
 * @retval -1 upon an error
 *
 * @sa soa_init_offer_answer(), soa_set_remote_sdp(),
 * soa_has_received_sdp(), soa_set_params(), SOATAG_REMOTE_SDP(),
 * SOATAG_REMOTE_SDP_STR(), soa_generate_answer(), soa_process_answer(),
 * soa_process_reject().
 */
int soa_clear_remote_sdp(soa_session_t *ss)
{
  SU_DEBUG_9(("soa_clear_remote_sdp(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (!ss)
    return (void)su_seterrno(EFAULT), -1;

  ss->ss_unprocessed_remote = 0;

  return 0;
}

/** Check if remote SDP has been saved but it has not been processed.
 *
 * @sa soa_init_offer_answer(), soa_set_remote_sdp(), soa_generate_answer(),
 * soa_process_answer(), soa_clear_remote_sdp().
 */
int soa_has_received_sdp(soa_session_t const *ss)
{
  return ss && ss->ss_unprocessed_remote;
}


/**Get local SDP.
 *
 * The <i>local SDP</i> is used as an offer or answer and it is generated by
 * soa_generate_offer() or soa_generate_answer(), respectively. It can be
 * retrieved using soa_get_params() or soa_get_paramlist() with
 * SOATAG_LOCAL_SDP() or SOATAG_LOCAL_SDP_STR() tags.
 *
 * @param ss  pointer to @soa session
 * @param return_sdp SDP  session structure return value
 * @param return_sdp_str  return value for pointer to string 
 *                        containing the user SDP session description
 * @param return_len  return value for user SDP session descrioption string
 *                    length 
 *
 * Any of the parameters @a return_sdp, @a return_sdp_str, or @a return_len 
 * may be NULL.
 *
 * @retval 0 if there is no description to return
 * @retval 1 if description is returned
 * @retval -1 upon an error
 *
 * @sa soa_generate_offer(), soa_generate_answer(), soa_get_params(),
 * soa_get_paramlist(), SOATAG_LOCAL_SDP(), SOATAG_LOCAL_SDP_STR(),
 * soa_get_user_sdp(), soa_get_remote_sdp(), soa_get_capability_sdp().
 */
int soa_get_local_sdp(soa_session_t const *ss,
		      struct sdp_session_s const **return_sdp,
		      char const **return_sdp_str,
		      isize_t *return_len)
{
  sdp_session_t const *sdp;
  char const *sdp_str;

  SU_DEBUG_9(("soa_get_local_sdp(%s::%p, [%p], [%p], [%p]) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss,
			  (void *)return_sdp, (void *)return_sdp_str, (void *)return_len));

  if (ss == NULL)
    return (void)su_seterrno(EFAULT), -1;

  sdp = ss->ss_local->ssd_sdp;
  sdp_str = ss->ss_local->ssd_str;

  if (sdp == NULL)
    return 0;
  if (return_sdp)
    *return_sdp = sdp;
  if (return_sdp_str)
    *return_sdp_str = sdp_str;
  if (return_len)
    *return_len = strlen(sdp_str);

  return 1;
}

/**Initialize the offer/answer state machine.
 *
 * @param ss  pointer to @soa session
 *
 * @retval 0  when successful
 * @retval -1 upon an error
 */
int soa_init_offer_answer(soa_session_t *ss)
{
  int complete;

  SU_DEBUG_9(("soa_init_offer_answer(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (!ss)
    return 0;

  complete = ss->ss_complete;

  ss->ss_complete = 0;
  ss->ss_offer_sent = 0;
  ss->ss_offer_recv = 0;
  ss->ss_answer_sent = 0;
  ss->ss_answer_recv = 0;

  ss->ss_unprocessed_remote = 0;

  return complete;
}

/** Return list of media fetures. */
char **soa_media_features(soa_session_t *ss, int live, su_home_t *home)
{
  SU_DEBUG_9(("soa_media_features(%s::%p, %u, %p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss, live, (void *)home));

  if (ss)
    return ss->ss_actions->soa_media_features(ss, live, home);
  else
    return (void)su_seterrno(EFAULT), NULL;
}

char **soa_base_media_features(soa_session_t *ss, int live, su_home_t *home)
{
  return su_zalloc(home, 8 * sizeof (char **));
}

char const * const * soa_sip_require(soa_session_t const *ss)
{
  SU_DEBUG_9(("soa_sip_require(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (ss)
    return ss->ss_actions->soa_sip_require(ss);
  else
    return (void)su_seterrno(EFAULT), NULL;
}

char const * const * soa_base_sip_require(soa_session_t const *ss)
{
  static char const *null = NULL;
  return &null;
}

char const * const * soa_sip_supported(soa_session_t const *ss)
{
  SU_DEBUG_9(("soa_sip_supported(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  if (ss)
    return ss->ss_actions->soa_sip_supported(ss);
  else
    return (void)su_seterrno(EFAULT), NULL;
}

char const * const * soa_base_sip_supported(soa_session_t const *ss)
{
  static char const *null = NULL;
  return &null;
}

int soa_remote_sip_features(soa_session_t *ss,
			    char const * const * supported,
			    char const * const * require)
{
  SU_DEBUG_9(("soa_remote_sip_features(%s::%p, %p, %p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss, (void *)supported, (void *)require));

  if (ss)
    return ss->ss_actions->soa_remote_sip_features(ss, supported, require);
  else
    return (void)su_seterrno(EFAULT), -1;
}

int soa_base_remote_sip_features(soa_session_t *ss,
				    char const * const * supported,
				    char const * const * require)
{
  return 0;
}


/**Generate offer.
 *
 * When generating the offer the user SDP is augmented with the required SDP
 * lines (v=, o=, t=, c=, a=rtpmap, etc.).
 *
 * The resulting SDP is also known as <i>local SDP</i>. It is available with
 * soa_get_local_sdp() or with soa_get_params() and soa_get_paramlist() tags
 * SOATAG_LOCAL_SDP() or SOATAG_LOCAL_SDP_STR().
 *
 * The user SDP has been stored to the soa session with soa_set_user_sdp()
 * or with soa_set_params() tags SOATAG_USER_SDP() or SOATAG_USER_SDP_STR(). 
 * There are various other parameters directing the generation of offer, set
 * by soa_set_params().
 *
 * @param ss pointer to session object
 * @param always always send offer (even if offer/answer has been completed)
 * @param completed pointer to callback function which is invoked when
 *                  operation is completed (currently not in use)
 *
 * @retval 1 when operation is successful
 * @retval 0 when operation was not needed
 * @retval -1 upon an error
 *
 * @ERRORS
 */
int soa_generate_offer(soa_session_t *ss,
		       int always,
		       soa_callback_f *completed)
{
  SU_DEBUG_9(("soa_generate_offer(%s::%p, %u) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss, always));

  /** @ERROR EFAULT Bad address. */
  if (ss == NULL)
    return su_seterrno(EFAULT), -1;

  /** @ERROR EALREADY An operation is already in progress */
  if (ss->ss_in_progress)
    return su_seterrno(EALREADY), -1;

  /** @ERROR EPROTO We have received offer, now we should send answer */
  if (ss->ss_offer_recv && !ss->ss_answer_sent)
    return su_seterrno(EPROTO), -1;

  /** @ERROR EPROTO We have received SDP, but it has not been processed */
  if (soa_has_received_sdp(ss))
    return su_seterrno(EPROTO), -1;

  /** @ERROR EPROTO We have sent an offer, but have received no answer */
  if (ss->ss_offer_sent && !ss->ss_answer_recv)
    return su_seterrno(EPROTO), -1;

  /** @ERROR EPROTO We have received offer. */
  if (ss->ss_unprocessed_remote)
    return su_seterrno(EPROTO), -1;

  /* We should avoid actual operation unless always is true */
  (void)always;  /* We always regenerate offer */

  return ss->ss_actions->soa_generate_offer(ss, completed);
  /** @sa soa_init_offer_answer(), soa_set_user_sdp(), soa_get_local_sdp(),
   * soa_set_remote_sdp(), soa_process_answer(), soa_process_reject(),
   * soa_generate_answer(), soa_set_params(), soa_get_params(),
   * soa_get_paramlist(), SOATAG_USER_SDP(), SOATAG_USER_SDP_STR(),
   * SOATAG_REMOTE_SDP(), SOATAG_REMOTE_SDP_STR().
   */
}

int soa_base_generate_offer(soa_session_t *ss,
			    soa_callback_f *completed)
{
  sdp_session_t const *sdp = ss->ss_local->ssd_sdp;

  (void)completed;

  if (!sdp)
    return -1;

  soa_set_activity(ss, sdp->sdp_media, 0);

  ss->ss_offer_sent = 1;
  ss->ss_answer_recv = 0;

  return 0;
}

/**Process offer, generate answer.
 *
 * When generating the offer the soa session combines remote offer with
 * <i>user SDP</i>. There are various other parameters directing the
 * generation of answer, set by soa_set_params().
 *
 * Before calling soa_generate_answer() the remote SDP offer should have
 * been stored into the soa session @a ss with soa_set_remote_sdp() or with
 * the soa_set_params() tags SOATAG_REMOTE_SDP() or SOATAG_REMOTE_SDP_STR().
 *
 * Also, the <i>user SDP</i> should have been stored into @a ss with
 * soa_set_user_sdp() or with the soa_set_params() tags SOATAG_USER_SDP() or
 * SOATAG_USER_SDP_STR().
 *
 * The resulting SDP is also known as <i>local SDP</i>. It is available with
 * soa_get_local_sdp() or with the soa_get_params() or soa_get_paramlist()
 * tags SOATAG_LOCAL_SDP() and SOATAG_LOCAL_SDP_STR().
 *
 * @param ss  pointer to session object
 * @param completed  pointer to callback function which is invoked when
 *                   operation is completed (currently not in use)
 *
 * @retval 1 when operation is successful
 * @retval 0 when operation was not needed
 * @retval -1 upon an error
 *
 * @ERRORS
 */
int soa_generate_answer(soa_session_t *ss,
			soa_callback_f *completed)
{
  SU_DEBUG_9(("soa_generate_answer(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  /** @ERROR EFAULT Bad address as @a ss. */
  if (ss == NULL)
    return su_seterrno(EFAULT), -1;

  /** @ERROR EALREADY An operation is already in progress. */
  if (ss->ss_in_progress)
    return su_seterrno(EALREADY), -1;

  /** @ERROR EPROTO We have sent an offer, but have received no answer. */
  if (ss->ss_offer_sent && !ss->ss_answer_recv)
    return su_seterrno(EPROTO), -1;

  /** @ERROR EPROTO We have not received offer. */
  if (!ss->ss_unprocessed_remote)
    return su_seterrno(EPROTO), -1;

  return ss->ss_actions->soa_generate_answer(ss, completed);

  /**@sa soa_init_offer_answer(), soa_set_user_sdp(), soa_set_remote_sdp(),
   * soa_get_local_sdp(), soa_process_reject(), soa_generate_offer(),
   * soa_set_params(), soa_get_params(), soa_get_paramlist(),
   * SOATAG_USER_SDP(), SOATAG_USER_SDP_STR(), SOATAG_REMOTE_SDP(),
   * SOATAG_REMOTE_SDP_STR().
   */
}

/** Base method for processing offer, generating answer. */
int soa_base_generate_answer(soa_session_t *ss,
			     soa_callback_f *completed)
{
  sdp_session_t const *l_sdp = ss->ss_local->ssd_sdp;
  sdp_session_t const *r_sdp = ss->ss_remote->ssd_sdp;
  sdp_session_t *rsession;

  (void)completed;

  if (!l_sdp || !r_sdp)
    return -1;
  rsession = sdp_session_dup(ss->ss_home, r_sdp);
  if (!rsession)
    return -1;

  if (ss->ss_rsession)
    su_free(ss->ss_home, ss->ss_rsession);
  ss->ss_rsession = rsession;

  soa_set_activity(ss, l_sdp->sdp_media, 0);
  soa_set_activity(ss, r_sdp->sdp_media, 1);

  ss->ss_offer_recv = 1;
  ss->ss_answer_sent = 1;
  ss->ss_complete = 1;
  ss->ss_unprocessed_remote = 0;

  return 0;
}

/** Complete offer-answer after receiving answer. 
 *
 * The SDP offer/answer negotiation is completed after receiving answer from
 * remote end. The answer is combined with the offer, and the application
 * should activate the media and codecs according to the negotiation result,
 * available as <i>local SDP</i>.
 *
 * @param ss  pointer to session object
 * @param completed  pointer to callback function which is invoked when
 *                   operation is completed (currently not in use)
 *
 * @retval 1 when operation is successful
 * @retval 0 when operation was not needed
 * @retval -1 upon an error
 *
 * @ERRORS
 */
int soa_process_answer(soa_session_t *ss,
		       soa_callback_f *completed)
{
  SU_DEBUG_9(("soa_process_answer(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  /** @ERROR EFAULT Bad address as @a ss. */
  if (ss == NULL)
    return su_seterrno(EFAULT), -1;

  /** @ERROR EALREADY An operation is already in progress. */
  if (ss->ss_in_progress)
    return su_seterrno(EALREADY), -1;

  /** @ERROR EPROTO We have not sent an offer 
      or already have received answer. */
  if (!ss->ss_offer_sent || ss->ss_answer_recv)
    return su_seterrno(EPROTO), -1;

  /** @ERROR EPROTO We have not received answer. */
  if (!ss->ss_unprocessed_remote)
    return su_seterrno(EPROTO), -1;

  /**@sa soa_init_offer_answer(), soa_set_user_sdp(), soa_set_remote_sdp(),
   * soa_get_local_sdp(), soa_generate_offer(), soa_generate_answer(),
   * soa_process_reject(), soa_set_params(), soa_get_params(),
   * soa_get_paramlist(), SOATAG_USER_SDP(), SOATAG_USER_SDP_STR(),
   * SOATAG_REMOTE_SDP(), SOATAG_REMOTE_SDP_STR().
   */

  return ss->ss_actions->soa_process_answer(ss, completed);
}

/** Base method for completing offer-answer after receiving answer.
 */
int soa_base_process_answer(soa_session_t *ss,
			    soa_callback_f *completed)
{
  sdp_session_t const *l_sdp = ss->ss_local->ssd_sdp;
  sdp_session_t const *r_sdp = ss->ss_remote->ssd_sdp;
  sdp_session_t *rsession;

  (void)completed;

  if (!l_sdp || !r_sdp)
    return -1;
  rsession = sdp_session_dup(ss->ss_home, r_sdp);
  if (!rsession)
    return -1;

  if (ss->ss_rsession)
    su_free(ss->ss_home, ss->ss_rsession);
  ss->ss_rsession = rsession;

  soa_set_activity(ss, l_sdp->sdp_media, 0);
  soa_set_activity(ss, r_sdp->sdp_media, 1);

  ss->ss_answer_recv = 1;
  ss->ss_complete = 1;
  ss->ss_unprocessed_remote = 0;

  return 0;
}

/** Process rejection of offer. 
 *
 * If the SDP offer was rejected (e.g., an offer in re-INVITE asked remote
 * end to add video to the session but the request was rejected), the
 * session should be restored to the state it was before last offer-answer
 * negotation round with soa_process_reject().
 *
 * @param ss  pointer to session object
 * @param completed  pointer to callback function which is invoked when
 *                   operation is completed (currently not in use)
 *
 * @retval 1 when operation is successful
 * @retval 0 when operation was not needed
 * @retval -1 upon an error
 *
 * @ERRORS
 */
int soa_process_reject(soa_session_t *ss,
		       soa_callback_f *completed)
{
  SU_DEBUG_9(("soa_process_reject(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  /** @ERROR EFAULT Bad address as @a ss. */
  if (ss == NULL)
    return su_seterrno(EFAULT), -1;

  /** @ERROR EALREADY An operation is already in progress. */
  if (ss->ss_in_progress)
    return su_seterrno(EALREADY), -1;

  /** @ERROR EPROTO We have not sent an offer 
      or already have received answer. */
  if (!ss->ss_offer_sent || ss->ss_answer_recv)
    return su_seterrno(EPROTO), -1;

  /**@sa soa_init_offer_answer(), soa_set_user_sdp(), soa_set_remote_sdp(),
   * soa_get_local_sdp(), soa_generate_offer(), soa_generate_answer(),
   * soa_process_answer(), soa_set_params(), soa_get_params(),
   * soa_get_paramlist(), SOATAG_USER_SDP(), SOATAG_USER_SDP_STR(),
   * SOATAG_REMOTE_SDP(), SOATAG_REMOTE_SDP_STR().
   */

  return ss->ss_actions->soa_process_reject(ss, completed);
}

/** Base method for processing rejection of offer.
 */
int soa_base_process_reject(soa_session_t *ss,
			    soa_callback_f *completed)
{
  sdp_session_t const *l_sdp = ss->ss_local->ssd_sdp;

  (void)completed;

  if (!l_sdp)
    return -1;

  soa_set_activity(ss, l_sdp->sdp_media, 0);

  ss->ss_offer_sent = 0;

  return 0;
}

/** Activate session. 
 *
 * Mark soa session as active.
 *
 * @retval 0 when operation was successful
 * @retval -1 upon an error
 *
 * @ERRORS
 */
int soa_activate(soa_session_t *ss, char const *option)
{
  SU_DEBUG_9(("soa_activate(%s::%p, %s%s%s) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss, NICE(option)));

  /** @ERROR EFAULT Bad address as @a ss. */
  if (ss == NULL)
    return -1;

  ss->ss_active = 1;

  return ss->ss_actions->soa_activate_session(ss, option);
}

int soa_base_activate(soa_session_t *ss, char const *option)
{
  (void)ss;
  (void)option;
  return 0;
}

/** Deactivate session.
 *
 * Mark soa session as inactive.
 *
 * @retval 0 when operation was successful
 * @retval -1 upon an error
 *
 * @ERRORS
 */
int soa_deactivate(soa_session_t *ss, char const *option)
{
  SU_DEBUG_9(("soa_deactivate(%s::%p, %s%s%s) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss, NICE(option)));

  /** @ERROR EFAULT Bad address as @a ss. */
  if (ss == NULL)
    return -1;

  ss->ss_active = 0;

  return ss->ss_actions->soa_deactivate_session(ss, option);
}

int soa_base_deactivate(soa_session_t *ss, char const *option)
{
  (void)ss;
  (void)option;
  return 0;
}

/** Terminate session. */
void soa_terminate(soa_session_t *ss, char const *option)
{
  SU_DEBUG_9(("soa_terminate(%s::%p) called\n",
	      ss ? ss->ss_actions->soa_name : "", (void *)ss));

  /** @ERROR EFAULT Bad address as @a ss. */
  if (ss == NULL)
    return;

  ss->ss_active = 0;
  ss->ss_terminated++;

  ss->ss_actions->soa_terminate_session(ss, option);
}

void soa_base_terminate(soa_session_t *ss, char const *option)
{
  (void)option;

  soa_init_offer_answer(ss);
  ss->ss_oa_rounds = 0;

  soa_description_free(ss, ss->ss_remote);
  soa_set_activity(ss, NULL, 0);
  soa_set_activity(ss, NULL, 1);
}

/** Return true if the SDP Offer/Answer negotation is complete.
 *
 * The soa_init_offer_answer() clears the completion flag.
 */
int soa_is_complete(soa_session_t const *ss)
{
  return ss && ss->ss_complete;
}

/** Return true if audio has been activated. */
int soa_is_audio_active(soa_session_t const *ss)
{
  int ma = ss ? ss->ss_local_activity->ma_audio : SOA_ACTIVE_DISABLED;
  if (ma >= 4) ma |= -8;
  return ma;
}

/** Return true if video has been activated. */
int soa_is_video_active(soa_session_t const *ss)
{
  int ma = ss ? ss->ss_local_activity->ma_video : SOA_ACTIVE_DISABLED;
  if (ma >= 4) ma |= -8;
  return ma;
}

/** Return true if image sharing has been activated. */
int soa_is_image_active(soa_session_t const *ss)
{
  int ma = ss ? ss->ss_local_activity->ma_image : SOA_ACTIVE_DISABLED;
  if (ma >= 4) ma |= -8;
  return ma;
}

/** Return true if messaging session has been activated. */
int soa_is_chat_active(soa_session_t const *ss)
{
  int ma = ss ? ss->ss_local_activity->ma_chat : SOA_ACTIVE_DISABLED;
  if (ma >= 4) ma |= -8;
  return ma;
}

/** Return true if remote audio is active (not on hold). */
int soa_is_remote_audio_active(soa_session_t const *ss)
{
  int ma = ss ? ss->ss_remote_activity->ma_audio : SOA_ACTIVE_DISABLED;
  if (ma >= 4) ma |= -8;
  return ma;
}

/** Return true if remote video is active (not on hold). */
int soa_is_remote_video_active(soa_session_t const *ss)
{
  int ma = ss ? ss->ss_remote_activity->ma_video : SOA_ACTIVE_DISABLED;
  if (ma >= 4) ma |= -8;
  return ma;
}

/** Return true if image sharing is active (not on hold). */
int soa_is_remote_image_active(soa_session_t const *ss)
{
  int ma = ss ? ss->ss_remote_activity->ma_image : SOA_ACTIVE_DISABLED;
  if (ma >= 4) ma |= -8;
  return ma;
}

/** Return true if chat session is active (not on hold). */
int soa_is_remote_chat_active(soa_session_t const *ss)
{
  int ma = ss ? ss->ss_remote_activity->ma_chat : SOA_ACTIVE_DISABLED;
  if (ma >= 4) ma |= -8;
  return ma;
}

/* ======================================================================== */
/* Methods used by soa instances */

int soa_set_status(soa_session_t *ss, int status, char const *phrase)
{
  if (ss) {
    ss->ss_status = status, ss->ss_phrase = phrase;
    ss->ss_wcode = 0, ss->ss_warning = NULL;
  }
  return -1;
}

int soa_set_warning(soa_session_t *ss, int code, char const *text)
{
  if (ss)
    ss->ss_wcode = code, ss->ss_warning = text;
  return -1;
}

su_inline
int soa_media_is_ready(soa_session_t const *ss)
{
  XXX;
  return 0;
  /* return ss && ss->ss_session != NULL; */
}

void soa_set_activity(soa_session_t *ss,
		      sdp_media_t const *m,
		      int remote)
{
  struct soa_media_activity *ma;
  sdp_connection_t const *c;
  int mode;
  int ma_audio = SOA_ACTIVE_DISABLED;
  int ma_video = SOA_ACTIVE_DISABLED;
  int ma_chat = SOA_ACTIVE_DISABLED;
  int ma_image = SOA_ACTIVE_DISABLED;
  int *p;

  remote = !!remote;

  ma = remote ? ss->ss_remote_activity : ss->ss_local_activity;

  for (; m; m = m->m_next) {
    if (m->m_type == sdp_media_audio)
      p = &ma_audio;
    else if (m->m_type == sdp_media_video)
      p = &ma_video;
    else if (m->m_type == sdp_media_image)
      p = &ma_image;
    else if (strcasecmp(m->m_type_name, "message") == 0)
      p = &ma_chat;
    else
      continue;

    if (m->m_rejected) {
      if (*p < 0)
	*p = SOA_ACTIVE_REJECTED;
      continue;
    }

    mode = m->m_mode;

    c = sdp_media_connections((sdp_media_t *)m);

    if (remote != (c && c->c_mcast))
      mode = ((mode << 1) & 2) | ((mode >> 1) & 1);

    if (*p < 0)
      *p = mode;
    else
      *p |= mode;
  }

  ma->ma_audio = ma_audio;
  ma->ma_video = ma_video;
  ma->ma_image = ma_image;
  ma->ma_chat = ma_chat;
}

/* ----------------------------------------------------------------------*/
/* Handle SDP */


/**
 * Parses and stores session description
 * 
 * @param ss instance pointer
 * @param what caps, local or remote
 * @param sdp0 new sdp (parsed)
 * @param sdp_str new sdp (unparsed)
 * @param str_len length on unparsed data
 **/
static
int soa_set_sdp(soa_session_t *ss, 
		enum soa_sdp_kind what,
		sdp_session_t const *sdp0,
		char const *sdp_str, issize_t str_len)
{
  struct soa_description *ssd;
  int flags, new_version, retval;
  sdp_parser_t *parser = NULL;
  sdp_session_t sdp[1];

  switch (what) {
  case soa_capability_sdp_kind:
    ssd = ss->ss_caps;
    flags = sdp_f_config;
    break;
  case soa_user_sdp_kind:
    ssd = ss->ss_user;
    flags = sdp_f_config;
    break;
  case soa_remote_sdp_kind:
    ssd = ss->ss_remote;
    flags = sdp_f_mode_0000;
    break;
  default:
    return -1;
  }

  if (sdp_str && str_len == -1)
    str_len = strlen(sdp_str);

  if (sdp0)
    new_version = sdp_session_cmp(sdp0, ssd->ssd_sdp) != 0;
  else if (sdp_str)
    new_version = !ssd->ssd_unparsed ||
      str0ncmp(sdp_str, ssd->ssd_unparsed, str_len) != 0 ||
      ssd->ssd_unparsed[str_len] != '\0';
  else
    return (void)su_seterrno(EINVAL), -1;

  if (!new_version) {
    if (what == soa_remote_sdp_kind) {
      *sdp = *ssd->ssd_sdp;
      /* XXX - should check changes by soa_set_remote_sdp */
      return ss->ss_actions->soa_set_remote_sdp(ss, new_version, 
						sdp, sdp_str, str_len);
    }
    return 0;
  } 

  if (sdp0) {
    /* note: case 1 - src in parsed form */
    *sdp = *sdp0;
  } 
  else /* if (sdp_str) */ {
    /* note: case 2 - src in unparsed form */
    parser = sdp_parse(ss->ss_home, sdp_str, str_len, flags | sdp_f_anynet);

    if (sdp_parsing_error(parser)) {
      sdp_parser_free(parser);
      return soa_set_status(ss, 400, "Bad Session Description");
    }

    *sdp = *sdp_session(parser);
  }
 
  switch (what) {
  case soa_capability_sdp_kind:
    retval = ss->ss_actions->soa_set_capability_sdp(ss, sdp, sdp_str, str_len);
    break;
  case soa_user_sdp_kind:
    retval =  ss->ss_actions->soa_set_user_sdp(ss, sdp, sdp_str, str_len);
    break;
  case soa_remote_sdp_kind:
    retval = ss->ss_actions->soa_set_remote_sdp(ss, 1, sdp, sdp_str, str_len);
    break;
  default:
    retval = soa_set_status(ss, 500, "Internal Error");
    break;
  }

  if (parser)
    sdp_parser_free(parser);

  return retval;
}


/** Set session descriptions. */
int soa_description_set(soa_session_t *ss,
			struct soa_description *ssd,
			sdp_session_t *sdp,
			char const *sdp_str,
			isize_t str_len)
{
  int retval = -1;

  sdp_printer_t *printer = NULL;
  sdp_session_t *sdp_new;
  char *sdp_str_new;
  char *sdp_str0_new;

  void *tbf1, *tbf2, *tbf3, *tbf4;

  /* Store description in three forms: unparsed, parsed and reprinted */

  sdp_new = sdp_session_dup(ss->ss_home, sdp);
  printer = sdp_print(ss->ss_home, sdp, NULL, 0, 0);
  sdp_str_new = (char *)sdp_message(printer);
  if (sdp_str)
    sdp_str0_new = su_strndup(ss->ss_home, sdp_str, str_len);
  else
    sdp_str0_new = sdp_str_new;
  
  if (sdp_new && printer && sdp_str_new && sdp_str0_new) {
    tbf1 = ssd->ssd_sdp, tbf2 = ssd->ssd_printer;
    tbf3 = (void *)ssd->ssd_str, tbf4 = (void *)ssd->ssd_unparsed;

    ssd->ssd_sdp = sdp_new;
    ssd->ssd_printer = printer;
    ssd->ssd_str = sdp_str_new;
    ssd->ssd_unparsed = sdp_str0_new;      

    retval = 1;
  }
  else {
    tbf1 = sdp_new, tbf2 = printer, tbf3 = sdp_str_new, tbf4 = sdp_str0_new;
  }
  
  su_free(ss->ss_home, tbf1);
  sdp_printer_free(tbf2);
  if (tbf3 != tbf4)
    su_free(ss->ss_home, tbf4);

  return retval;
}


/** Duplicate a session descriptions. */
int soa_description_dup(su_home_t *home, 
			struct soa_description *ssd,
			struct soa_description const *ssd0)
{
  if (ssd0->ssd_sdp) {
    ssd->ssd_sdp = sdp_session_dup(home, ssd0->ssd_sdp);
    ssd->ssd_printer = sdp_print(home, ssd->ssd_sdp, NULL, 0, 0);
    ssd->ssd_str = (char *)sdp_message(ssd->ssd_printer);
    if (ssd0->ssd_str != ssd0->ssd_unparsed)
      ssd->ssd_unparsed = su_strdup(home, ssd0->ssd_unparsed);
    else
      ssd->ssd_unparsed = ssd->ssd_str;
  }

  return 0;
}

/** Free session descriptions. */
void soa_description_free(soa_session_t *ss, 
			  struct soa_description *ssd)
{
  void *tbf1, *tbf2, *tbf3, *tbf4;

  tbf1 = ssd->ssd_sdp, tbf2 = ssd->ssd_printer;
  tbf3 = (void *)ssd->ssd_str, tbf4 = (void *)ssd->ssd_unparsed;

  memset(ssd, 0, sizeof *ssd);

  su_free(ss->ss_home, tbf1);
  sdp_printer_free(tbf2);
  if (tbf3 != tbf4)
    su_free(ss->ss_home, tbf4);
}


/** Initialize SDP o= line */
int
soa_init_sdp_origin(soa_session_t *ss, sdp_origin_t *o, char buffer[64])
{
  sdp_connection_t *c;

  if (ss == NULL || o == NULL)
    return su_seterrno(EFAULT), -1;

  assert(o->o_address);

  if (!o->o_username)
    o->o_username = "-";

  if (o->o_id == 0)
    su_randmem(&o->o_id, sizeof o->o_id);
  o->o_id &= ((unsigned longlong)1 << 63) - 1;

  if (o->o_version == 0)
    su_randmem(&o->o_version, sizeof o->o_version);
  o->o_version &= ((unsigned longlong)1 << 63) - 1;

  c = o->o_address;

  if (!c->c_nettype ||
      !c->c_address ||
      strcmp(c->c_address, "") == 0 ||
      strcmp(c->c_address, "0.0.0.0") == 0 ||
      strcmp(c->c_address, "::") == 0 ||
      !host_is_local(c->c_address)) {
    return soa_init_sdp_connection(ss, c, buffer);
  }

  return 0;
}

/** Search for an local address item from string provided by user */
static
su_localinfo_t *li_in_list(su_localinfo_t *li0, char const **llist)
{
  char const *list = *llist;
  size_t n;

  if (!list)
    return NULL;

  while ((n = strcspn(list, ", "))) {
    su_localinfo_t *li;

    for (li = li0; li; li = li->li_next) {
      if (strncasecmp(li->li_canonname, list, n) == 0 &&
	  li->li_canonname[n] == '\0')
	break;
    }

    list += n; while (list[0] == ' ' || list[0] == ',') list++;
    *llist = list;

    if (li)
      return li;
  }

  return NULL;
}


/** Obtain a local address for SDP connection structure */
int
soa_init_sdp_connection(soa_session_t *ss,
			sdp_connection_t *c,
			char buffer[64])
{
  su_localinfo_t *res, hints[1] = {{ LI_CANONNAME | LI_NUMERIC }};
  su_localinfo_t *li, *li4, *li6;
  char const *address;
  int ip4, ip6, error;

  if (ss == NULL || c == NULL)
    return su_seterrno(EFAULT), -1;

  address = ss->ss_address;

  if (host_is_ip_address(address)) {
    /* Use the application-specified address -
     * do not check that it is found from the local address list */
    c->c_nettype = sdp_net_in;
    
    if (host_is_ip4_address(address))
      c->c_addrtype = sdp_addr_ip4;
    else
      c->c_addrtype = sdp_addr_ip6;
    
    if (!host_is_ip6_reference(address)) {
      c->c_address = strcpy(buffer, address);
    }
    else {
      /* Remove brackets [] around the reference */
      size_t len = strlen(address + 1);
      c->c_address = memcpy(buffer, address + 1, len - 1);
      buffer[len - 1] = '\0'; 
    }
    return 0;
  }

  /* XXX - using LI_SCOPE_LINK requires some tweaking */
  hints->li_scope = LI_SCOPE_GLOBAL | LI_SCOPE_SITE /* | LI_SCOPE_LINK */;

  switch (ss->ss_af) {
  case SOA_AF_IP4_ONLY:
    hints->li_family = AF_INET, ip4 = 1, ip6 = 0;
    break;

#if HAVE_SIN6
  case SOA_AF_IP6_ONLY:
    hints->li_family = AF_INET6, ip6 = 1, ip4 = 0;
    break;
  case SOA_AF_IP4_IP6:
    ip4 = 2, ip6 = 1;
    break;
  case SOA_AF_IP6_IP4:
    ip4 = 1, ip6 = 2;
    break;
#endif
  default:
    ip4 = ip6 = 1;
  }

  for (res = NULL; res == NULL;) {
    if ((error = su_getlocalinfo(hints, &res)) < 0 
	&& error != ELI_NOADDRESS) {
      SU_DEBUG_1(("%s: su_localinfo: %s\n", __func__,
		  su_gli_strerror(error)));
      return -1;
    }
    if (hints->li_scope & LI_SCOPE_HOST)
      break;
    hints->li_scope |= LI_SCOPE_HOST;
  }

  if (!(ip4 & ip6 && c->c_nettype == sdp_net_in))
    /* Use ss_af preference */;
  else if (c->c_addrtype == sdp_addr_ip4)
    ip4 = 2, ip6 = 1;
  else if (c->c_addrtype == sdp_addr_ip6)
    ip6 = 2, ip4 = 1;

  if (address)
    SU_DEBUG_3(("%s: searching for %s from list \"%s\"\n",
		__func__, ip6 && !ip4 ? "IP6 " : !ip6 && ip4 ? "IP4 " : "",
		address));

  li = res, li4 = NULL, li6 = NULL;

  for (;;) {
    if (address)
      li = li_in_list(li, &address);

    if (!li)
      break;
#if HAVE_SIN6
    else if (li->li_family == AF_INET6) {
      if (ip6 >= ip4)
	break;
      else if (!li6)
	li6 = li;		/* Best IP6 address */
    }
#endif
    else if (li->li_family == AF_INET) {
      if (ip4 > ip6)
	break;
      else if (!li4)
	li4 = li;		/* Best IP4 address */
    }

    if (!address)
      li = li->li_next;
  }

  if (li == NULL)
    li = li4;
  if (li == NULL)
    li = li6;

  if (li == NULL)
    ;
  else if (li->li_family == AF_INET)
    c->c_nettype = sdp_net_in,  c->c_addrtype = sdp_addr_ip4;
#if HAVE_SIN6
  else if (li->li_family == AF_INET6)
    c->c_nettype = sdp_net_in,  c->c_addrtype = sdp_addr_ip6;
#endif

  if (li) {
    assert(strlen(li->li_canonname) < 64);
    c->c_address = strcpy(buffer, li->li_canonname);
  }

  su_freelocalinfo(res);

  if (!li)
    return -1;
  else
    return 0;
}
