/*
  eXosip - This is the eXtended osip library.
  Copyright (C) 2002,2003,2004,2005  Aymeric MOIZARD  - jack@atosc.org
  
  eXosip is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  
  eXosip 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 General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/


#ifdef ENABLE_MPATROL
#include <mpatrol.h>
#endif


#include "eXosip2.h"

extern eXosip_t eXosip;

static int eXosip_create_transaction (eXosip_call_t * jc,
                                      eXosip_dialog_t * jd,
                                      osip_message_t * request);
static int eXosip_create_cancel_transaction (eXosip_call_t * jc,
                                             eXosip_dialog_t * jd,
                                             osip_message_t * request);

static int _eXosip_call_reuse_contact(osip_message_t *invite, osip_message_t *msg);

static int
eXosip_create_transaction (eXosip_call_t * jc,
                           eXosip_dialog_t * jd, osip_message_t * request)
{
  osip_event_t *sipevent;
  osip_transaction_t *tr;
  int i;

  i = _eXosip_transaction_init (&tr, NICT, eXosip.j_osip, request);
  if (i != 0)
    {
      /* TODO: release the j_call.. */

      osip_message_free (request);
      return -1;
    }

  if (jd != NULL)
    osip_list_add (jd->d_out_trs, tr, 0);

  sipevent = osip_new_outgoing_sipmessage (request);
  sipevent->transactionid = tr->transactionid;

  osip_transaction_set_your_instance (tr, __eXosip_new_jinfo (jc, jd, NULL, NULL));
  osip_transaction_add_event (tr, sipevent);
  __eXosip_wakeup ();
  return 0;
}

static int
eXosip_create_cancel_transaction (eXosip_call_t * jc,
                                  eXosip_dialog_t * jd, osip_message_t * request)
{
  osip_event_t *sipevent;
  osip_transaction_t *tr;
  int i;

  i = _eXosip_transaction_init (&tr, NICT, eXosip.j_osip, request);
  if (i != 0)
    {
      /* TODO: release the j_call.. */

      osip_message_free (request);
      return -2;
    }

  osip_list_add (eXosip.j_transactions, tr, 0);

  sipevent = osip_new_outgoing_sipmessage (request);
  sipevent->transactionid = tr->transactionid;

  osip_transaction_add_event (tr, sipevent);
  __eXosip_wakeup ();
  return 0;
}

static int _eXosip_call_reuse_contact(osip_message_t *invite, osip_message_t *msg)
{
    osip_contact_t *co_invite=NULL;
    osip_contact_t *co_msg=NULL;
    int i;
    i = osip_message_get_contact(invite, 0, &co_invite);
    if (i<0 || co_invite==NULL || co_invite->url==NULL)
    {
        return -1;
    }

    i = osip_message_get_contact(msg, 0, &co_msg);
    if (i>=0 && co_msg!=NULL)
    {
        osip_list_remove(&msg->contacts, 0);
        osip_contact_free(co_msg);
    }

    co_msg=NULL;
    i = osip_contact_clone(co_invite, &co_msg);
    if (i>=0 && co_msg!=NULL)
    {
        osip_list_add(&msg->contacts, co_msg, 0);
        return 0;
    }
    return -1;
}

int
_eXosip_call_transaction_find (int tid, eXosip_call_t ** jc,
                               eXosip_dialog_t ** jd, osip_transaction_t ** tr)
{
  for (*jc = eXosip.j_calls; *jc != NULL; *jc = (*jc)->next)
    {
      if ((*jc)->c_inc_tr != NULL && (*jc)->c_inc_tr->transactionid == tid)
        {
          *tr = (*jc)->c_inc_tr;
          *jd = (*jc)->c_dialogs;
          return 0;
        }
      if ((*jc)->c_out_tr != NULL && (*jc)->c_out_tr->transactionid == tid)
        {
          *tr = (*jc)->c_out_tr;
          *jd = (*jc)->c_dialogs;
          return 0;
        }
      for (*jd = (*jc)->c_dialogs; *jd != NULL; *jd = (*jd)->next)
        {
          osip_transaction_t *transaction;
          int pos = 0;

          while (!osip_list_eol ((*jd)->d_inc_trs, pos))
            {
              transaction =
                (osip_transaction_t *) osip_list_get ((*jd)->d_inc_trs, pos);
              if (transaction != NULL && transaction->transactionid == tid)
                {
                  *tr = transaction;
                  return 0;
                }
              pos++;
            }

          pos = 0;
          while (!osip_list_eol ((*jd)->d_out_trs, pos))
            {
              transaction =
                (osip_transaction_t *) osip_list_get ((*jd)->d_out_trs, pos);
              if (transaction != NULL && transaction->transactionid == tid)
                {
                  *tr = transaction;
                  return 0;
                }
              pos++;
            }
        }
    }
  *jd = NULL;
  *jc = NULL;
  return -1;
}

int
eXosip_call_set_reference (int id, void *reference)
{
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;

  if (id > 0)
    {
      eXosip_call_dialog_find (id, &jc, &jd);
      if (jc == NULL)
        {
          eXosip_call_find (id, &jc);
        }
    }
  if (jc == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No call here?\n"));
      return -1;
    }
  jc->external_reference = reference;
  return 0;
}

/* this method can't be called unless the previous
   INVITE transaction is over. */
int
eXosip_call_build_initial_invite (osip_message_t ** invite,
                                  const char *to,
                                  const char *from,
                                  const char *route, const char *subject)
{
  int i;
  osip_uri_param_t *uri_param = NULL;
  osip_to_t *_to = NULL;
  char transport[10];

  *invite = NULL;

  if (to != NULL && *to == '\0')
    return -1;
  if (route != NULL && *route == '\0')
    route = NULL;
  if (subject != NULL && *subject == '\0')
    subject = NULL;

  i = osip_to_init (&_to);
  if (i != 0)
    return -1;

  i = osip_to_parse (_to, to);
  if (i != 0)
    {
      osip_to_free (_to);
      return -1;
    }

  osip_uri_uparam_get_byname (_to->url, "transport", &uri_param);
  if (uri_param != NULL && uri_param->gvalue != NULL)
	  snprintf(transport, sizeof(transport), "%s", uri_param->gvalue);
  else if (eXosip.net_interfaces[0].net_socket > 0)
	  snprintf(transport, sizeof(transport), "%s", "UDP");
  else
	  snprintf(transport, sizeof(transport), "%s", "TCP");

  i = generating_request_out_of_dialog (invite, "INVITE", to,
                                          transport, from, route);
  osip_to_free (_to);
  if (i != 0)
    return -1;
  _eXosip_dialog_add_contact(*invite, NULL);

  if (subject != NULL)
    osip_message_set_subject (*invite, subject);

  /* after this delay, we should send a CANCEL */
  osip_message_set_expires (*invite, "120");
  return 0;
}

int
eXosip_call_send_initial_invite (osip_message_t * invite)
{
  eXosip_call_t *jc;
  osip_transaction_t *transaction;
  osip_event_t *sipevent;
  int i;

  eXosip_call_init (&jc);

  i = _eXosip_transaction_init (&transaction, ICT, eXosip.j_osip, invite);
  if (i != 0)
    {
      eXosip_call_free (jc);
      osip_message_free (invite);
      return -1;
    }

  jc->c_out_tr = transaction;

  sipevent = osip_new_outgoing_sipmessage (invite);
  sipevent->transactionid = transaction->transactionid;

  osip_transaction_set_your_instance (transaction,
                                      __eXosip_new_jinfo (jc, NULL, NULL, NULL));
  osip_transaction_add_event (transaction, sipevent);

  jc->external_reference = NULL;
  ADD_ELEMENT (eXosip.j_calls, jc);

  eXosip_update ();             /* fixed? */
  __eXosip_wakeup ();
  return jc->c_id;
}

int
eXosip_call_build_ack (int did, osip_message_t ** _ack)
{
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;
  osip_transaction_t *tr = NULL;

  osip_message_t *ack;
  char *transport;
  int i;

  *_ack = NULL;

  if (did > 0)
    {
      eXosip_call_dialog_find (did, &jc, &jd);
    }
  if (jc == NULL || jd == NULL || jd->d_dialog == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No call here?\n"));
      return -1;
    }

  tr = eXosip_find_last_invite (jc, jd);

  if (tr == NULL || tr->orig_request == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No transaction for call?\n"));
      return -1;
    }

  if (0 != osip_strcasecmp (tr->orig_request->sip_method, "INVITE"))
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: ACK are only sent for invite transactions\n"));
      return -1;
    }

  transport = NULL;
  transport = _eXosip_transport_protocol (tr->orig_request);
  if (transport == NULL)
    i = _eXosip_build_request_within_dialog (&ack, "ACK", jd->d_dialog, "UDP");
  else
    i = _eXosip_build_request_within_dialog (&ack, "ACK", jd->d_dialog, transport);

  if (i != 0)
    {
      return -1;
    }

  _eXosip_call_reuse_contact(tr->orig_request, ack);

  /* Fix CSeq Number when request has been exchanged during INVITE transactions */
  if (tr->orig_request->cseq != NULL && tr->orig_request->cseq->number != NULL)
    {
      if (ack != NULL && ack->cseq != NULL && ack->cseq->number != NULL)
        {
          osip_free (ack->cseq->number);
          ack->cseq->number = osip_strdup (tr->orig_request->cseq->number);
        }
    }

  /* copy all credentials from INVITE! */
  {
    int pos = 0;
    int i;
    osip_proxy_authorization_t *pa = NULL;

    i = osip_message_get_proxy_authorization (tr->orig_request, pos, &pa);
    while (i == 0 && pa != NULL)
      {
        osip_proxy_authorization_t *pa2;

        i = osip_proxy_authorization_clone (pa, &pa2);
        if (i != 0)
          {
            OSIP_TRACE (osip_trace (__FILE__, __LINE__, OSIP_ERROR, NULL,
                                    "Error in credential from INVITE\n"));
            break;
          }
        osip_list_add (&ack->proxy_authorizations, pa2, -1);
        pa = NULL;
        pos++;
        i = osip_message_get_proxy_authorization (tr->orig_request, pos, &pa);
      }
  }

  *_ack = ack;
  return 0;
}

int
eXosip_call_send_ack (int did, osip_message_t * ack)
{
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;

  osip_route_t *route;
  char *host;
  int port;

  if (did > 0)
    {
      eXosip_call_dialog_find (did, &jc, &jd);
    }

  if (jc == NULL || jd == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No call here?\n"));
      if (ack != NULL)
        osip_message_free (ack);
      return -1;
    }

  if (ack == NULL)
    {
      int i;

      i = eXosip_call_build_ack (did, &ack);
      if (i != 0)
        {
          return -1;
        }
    }

  osip_message_get_route (ack, 0, &route);
  if (route != NULL)
    {
      osip_uri_param_t *lr_param = NULL;

      osip_uri_uparam_get_byname (route->url, "lr", &lr_param);
      if (lr_param == NULL)
        route = NULL;
    }

  if (route != NULL)
    {
      port = 5060;
      if (route->url->port != NULL)
        port = osip_atoi (route->url->port);
      host = route->url->host;
  } else
    {
      port = 5060;
      if (ack->req_uri->port != NULL)
        port = osip_atoi (ack->req_uri->port);
      host = ack->req_uri->host;
    }

  cb_snd_message (NULL, ack, host, port, -1);

  if (jd->d_ack != NULL)
    osip_message_free (jd->d_ack);
  jd->d_ack = ack;
  return 0;
}

int
eXosip_call_build_request (int jid, const char *method, osip_message_t ** request)
{
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;

  osip_transaction_t *transaction;
  char *transport;
  int i;

  *request = NULL;
  if (method == NULL || method[0] == '\0')
    return -1;

  if (jid > 0)
    {
      eXosip_call_dialog_find (jid, &jc, &jd);
    }
  if (jd == NULL || jd->d_dialog == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No call here?\n"));
      return -1;
    }

  transaction = NULL;
  if (0 == osip_strcasecmp (method, "INVITE"))
    {
      transaction = eXosip_find_last_invite (jc, jd);
  } else                        /* OPTIONS, UPDATE, INFO, REFER, ?... */
    {
      transaction = eXosip_find_last_transaction (jc, jd, method);
    }

  if (transaction != NULL)
    {
      if (0 != osip_strcasecmp (method, "INVITE"))
        {
          if (transaction->state != NICT_TERMINATED &&
              transaction->state != NIST_TERMINATED &&
              transaction->state != NICT_COMPLETED &&
              transaction->state != NIST_COMPLETED)
            return -1;
      } else
        {
          if (transaction->state != ICT_TERMINATED &&
              transaction->state != IST_TERMINATED &&
              transaction->state != IST_CONFIRMED &&
              transaction->state != ICT_COMPLETED)
            return -1;
        }
    }

  transport = NULL;
  transaction = eXosip_find_last_invite (jc, jd);
  if (transaction != NULL && transaction->orig_request != NULL)
    transport = _eXosip_transport_protocol (transaction->orig_request);

  transaction = NULL;

  if (transport == NULL)
    i = _eXosip_build_request_within_dialog (request, method, jd->d_dialog, "UDP");
  else
    i =
      _eXosip_build_request_within_dialog (request, method, jd->d_dialog,
                                           transport);
  if (i != 0)
    return -2;

  if (jc->response_auth != NULL)
    eXosip_add_authentication_information (*request, jc->response_auth);

  return 0;
}

int
eXosip_call_send_request (int jid, osip_message_t * request)
{
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;

  osip_transaction_t *transaction;
  osip_event_t *sipevent;

  int i;

  if (request == NULL)
    return -1;

  if (request->sip_method == NULL)
    {
      osip_message_free (request);
      return -1;
    }

  if (jid > 0)
    {
      eXosip_call_dialog_find (jid, &jc, &jd);
    }
  if (jd == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No call here?\n"));
      osip_message_free (request);
      return -1;
    }

  transaction = NULL;
  if (0 == osip_strcasecmp (request->sip_method, "INVITE"))
    {
      transaction = eXosip_find_last_invite (jc, jd);
  } else                        /* OPTIONS, UPDATE, INFO, REFER, ?... */
    {
      transaction = eXosip_find_last_transaction (jc, jd, request->sip_method);
    }

  if (transaction != NULL)
    {
      if (0 != osip_strcasecmp (request->sip_method, "INVITE"))
        {
          if (transaction->state != NICT_TERMINATED &&
              transaction->state != NIST_TERMINATED &&
              transaction->state != NICT_COMPLETED &&
              transaction->state != NIST_COMPLETED)
            {
              osip_message_free (request);
              return -1;
            }
      } else
        {
          if (transaction->state != ICT_TERMINATED &&
              transaction->state != IST_TERMINATED &&
              transaction->state != IST_CONFIRMED &&
              transaction->state != ICT_COMPLETED)
            {
              osip_message_free (request);
              return -1;
            }
        }
    }

  transaction = NULL;
  if (0 != osip_strcasecmp (request->sip_method, "INVITE"))
    {
      i = _eXosip_transaction_init (&transaction, NICT, eXosip.j_osip, request);
  } else
    {
      i = _eXosip_transaction_init (&transaction, ICT, eXosip.j_osip, request);
    }

  if (i != 0)
    {
      osip_message_free (request);
      return -2;
    }

  osip_list_add (jd->d_out_trs, transaction, 0);

  sipevent = osip_new_outgoing_sipmessage (request);
  sipevent->transactionid = transaction->transactionid;

  osip_transaction_set_your_instance (transaction,
                                      __eXosip_new_jinfo (jc, jd, NULL, NULL));
  osip_transaction_add_event (transaction, sipevent);
  __eXosip_wakeup ();
  return 0;
}

int
eXosip_call_build_refer (int did, const char *refer_to, osip_message_t ** request)
{
  int i;

  *request = NULL;
  i = eXosip_call_build_request (did, "REFER", request);
  if (i != 0)
    return -1;

  if (refer_to == NULL || refer_to[0] == '\0')
    return 0;

  osip_message_set_header (*request, "Refer-to", refer_to);
  return 0;
}

int
eXosip_call_build_options (int did, osip_message_t ** request)
{
  int i;

  *request = NULL;
  i = eXosip_call_build_request (did, "OPTIONS", request);
  if (i != 0)
    return -1;

  return 0;
}

int
eXosip_call_build_info (int did, osip_message_t ** request)
{
  int i;

  *request = NULL;
  i = eXosip_call_build_request (did, "INFO", request);
  if (i != 0)
    return -1;

  return 0;
}

int
eXosip_call_build_update (int did, osip_message_t ** request)
{
  int i;

  *request = NULL;
  i = eXosip_call_build_request (did, "UPDATE", request);
  if (i != 0)
    return -1;

  return 0;
}

int
eXosip_call_build_notify (int did, int subscription_status,
                          osip_message_t ** request)
{
  char subscription_state[50];
  char *tmp;
  int i;

  *request = NULL;
  i = eXosip_call_build_request (did, "NOTIFY", request);
  if (i != 0)
    return -1;

  if (subscription_status == EXOSIP_SUBCRSTATE_PENDING)
    osip_strncpy (subscription_state, "pending;expires=", 16);
  else if (subscription_status == EXOSIP_SUBCRSTATE_ACTIVE)
    osip_strncpy (subscription_state, "active;expires=", 15);
  else if (subscription_status == EXOSIP_SUBCRSTATE_TERMINATED)
    {
      int reason = NORESOURCE;

      if (reason == DEACTIVATED)
        osip_strncpy (subscription_state, "terminated;reason=deactivated", 29);
      else if (reason == PROBATION)
        osip_strncpy (subscription_state, "terminated;reason=probation", 27);
      else if (reason == REJECTED)
        osip_strncpy (subscription_state, "terminated;reason=rejected", 26);
      else if (reason == TIMEOUT)
        osip_strncpy (subscription_state, "terminated;reason=timeout", 25);
      else if (reason == GIVEUP)
        osip_strncpy (subscription_state, "terminated;reason=giveup", 24);
      else if (reason == NORESOURCE)
        osip_strncpy (subscription_state, "terminated;reason=noresource", 29);
    }
  tmp = subscription_state + strlen (subscription_state);
  if (subscription_status != EXOSIP_SUBCRSTATE_TERMINATED)
    sprintf (tmp, "%i", 180);
  osip_message_set_header (*request, "Subscription-State", subscription_state);

  return 0;
}

int
eXosip_call_build_answer (int tid, int status, osip_message_t ** answer)
{
  int i = -1;
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;
  osip_transaction_t *tr = NULL;

  *answer = NULL;

  if (tid > 0)
    {
      _eXosip_call_transaction_find (tid, &jc, &jd, &tr);
    }
  if (tr == NULL || jd == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No call here?\n"));
      return -1;
    }

  if (0 == osip_strcasecmp (tr->orig_request->sip_method, "INVITE"))
    {
      if (status > 100 && status < 200)
        {
          i = _eXosip_answer_invite_1xx (jc, jd, status, answer);
      } else if (status > 199 && status < 300)
        {
          i = _eXosip_answer_invite_2xx (jc, jd, status, answer);
      } else if (status > 300 && status <= 699)
        {
          i = _eXosip_answer_invite_3456xx (jc, jd, status, answer);
      } else
        {
          OSIP_TRACE (osip_trace
                      (__FILE__, __LINE__, OSIP_ERROR, NULL,
                       "eXosip: wrong status code (101<status<=699)\n"));
          return -1;
        }
  } else
    {
      if (jd != NULL)
        {
          i =
            _eXosip_build_response_default (answer, jd->d_dialog, status,
                                            tr->orig_request);
      } else
        {
          i =
            _eXosip_build_response_default (answer, NULL, status,
                                            tr->orig_request);
        }
      if (status > 100 && status < 300)
        i = complete_answer_that_establish_a_dialog (*answer, tr->orig_request);
    }

  if (i != 0)
    {
      OSIP_TRACE (osip_trace (__FILE__, __LINE__, OSIP_ERROR, NULL,
                              "ERROR: Could not create response for %s\n",
                              tr->orig_request->sip_method));
      return -1;
    }
  return 0;
}

int
eXosip_call_send_answer (int tid, int status, osip_message_t * answer)
{
  int i = -1;
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;
  osip_transaction_t *tr = NULL;
  osip_event_t *evt_answer;

  if (tid > 0)
    {
      _eXosip_call_transaction_find (tid, &jc, &jd, &tr);
    }
  if (jd == NULL || tr == NULL || tr->orig_request == NULL
      || tr->orig_request->sip_method == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No call here or no transaction for call\n"));
      osip_message_free (answer);
      return -1;
    }

  if (answer == NULL)
    {
      if (0 == osip_strcasecmp (tr->orig_request->sip_method, "INVITE"))
        {
          if (status >= 100 && status <= 199)
            {
          } else if (status >= 300 && status <= 699)
            {
          } else
            {
              OSIP_TRACE (osip_trace
                          (__FILE__, __LINE__, OSIP_ERROR, NULL,
                           "eXosip: Wrong parameter?\n"));
              osip_message_free (answer);
              return -1;
            }
        }
    }

  /* is the transaction already answered? */
  if (tr->state == IST_COMPLETED
      || tr->state == IST_CONFIRMED
      || tr->state == IST_TERMINATED
      || tr->state == NIST_COMPLETED || tr->state == NIST_TERMINATED)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: transaction already answered\n"));
      osip_message_free (answer);
      return -1;
    }

  if (answer == NULL)
    {
      if (0 == osip_strcasecmp (tr->orig_request->sip_method, "INVITE"))
        {
          if (status < 200)
            i = _eXosip_default_answer_invite_1xx (jc, jd, status);
          else
            i = _eXosip_default_answer_invite_3456xx (jc, jd, status);
          if (i != 0)
            {
              OSIP_TRACE (osip_trace
                          (__FILE__, __LINE__, OSIP_ERROR, NULL,
                           "eXosip: cannot send response!\n"));
              return -1;
            }
      } else
        {
          /* TODO */
          OSIP_TRACE (osip_trace
                      (__FILE__, __LINE__, OSIP_ERROR, NULL,
                       "eXosip: a response must be given!\n"));
          return -1;
        }
      return 0;
  } else
    {
      i = 0;
    }

  if (0 == osip_strcasecmp (tr->orig_request->sip_method, "INVITE"))
    {
      if (MSG_IS_STATUS_1XX (answer))
        {
          if (jd == NULL)
            {
              i = eXosip_dialog_init_as_uas (&jd, tr->orig_request, answer);
              if (i != 0)
                {
                  OSIP_TRACE (osip_trace
                              (__FILE__, __LINE__, OSIP_ERROR, NULL,
                               "eXosip: cannot create dialog!\n"));
                  i = 0;
              } else
                {
                  ADD_ELEMENT (jc->c_dialogs, jd);
                }
            }
      } else if (MSG_IS_STATUS_2XX (answer))
        {
          if (jd == NULL)
            {
              i = eXosip_dialog_init_as_uas (&jd, tr->orig_request, answer);
              if (i != 0)
                {
                  OSIP_TRACE (osip_trace
                              (__FILE__, __LINE__, OSIP_ERROR, NULL,
                               "eXosip: cannot create dialog!\n"));
                  osip_message_free (answer);
                  return -1;
                }
              ADD_ELEMENT (jc->c_dialogs, jd);
          } else
            i = 0;

          eXosip_dialog_set_200ok (jd, answer);
		  /* wait for a ACK */

          osip_dialog_set_state (jd->d_dialog, DIALOG_CONFIRMED);
      } else if (answer->status_code >= 300 && answer->status_code <= 699)
        {
          i = 0;
      } else
        {
          OSIP_TRACE (osip_trace
                      (__FILE__, __LINE__, OSIP_ERROR, NULL,
                       "eXosip: wrong status code (101<status<=699)\n"));
          osip_message_free (answer);
          return -1;
        }
      if (i != 0)
        {
          osip_message_free (answer);
          return -1;
        }
    }

  evt_answer = osip_new_outgoing_sipmessage (answer);
  evt_answer->transactionid = tr->transactionid;

  osip_transaction_add_event (tr, evt_answer);
  eXosip_update ();
  __eXosip_wakeup ();
  return 0;
}

int
eXosip_call_terminate (int cid, int did)
{
  int i;
  osip_transaction_t *tr;
  osip_message_t *request = NULL;
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;
  char *transport = NULL;

  if (did > 0)
    {
      eXosip_call_dialog_find (did, &jc, &jd);
      if (jd == NULL)
        {
          OSIP_TRACE (osip_trace
                      (__FILE__, __LINE__, OSIP_ERROR, NULL,
                       "eXosip: No call here?\n"));
          return -1;
        }
  } else
    {
      eXosip_call_find (cid, &jc);
    }

  if (jc == NULL)
    {
      return -1;
    }

  tr = eXosip_find_last_out_invite (jc, jd);
  if (tr != NULL && tr->last_response != NULL
      && MSG_IS_STATUS_1XX (tr->last_response))
    {
      i = generating_cancel (&request, tr->orig_request);
      if (i != 0)
        {
          OSIP_TRACE (osip_trace
                      (__FILE__, __LINE__, OSIP_ERROR, NULL,
                       "eXosip: cannot terminate this call!\n"));
          return -2;
        }
      i = eXosip_create_cancel_transaction (jc, jd, request);
      if (i != 0)
        {
          OSIP_TRACE (osip_trace
                      (__FILE__, __LINE__, OSIP_ERROR, NULL,
                       "eXosip: cannot initiate SIP transaction!\n"));
          return i;
        }
      if (jd != NULL)
        {
          osip_dialog_free (jd->d_dialog);
          jd->d_dialog = NULL;
          eXosip_update ();     /* AMD 30/09/05 */
        }
      return 0;
    }

  if (jd == NULL || jd->d_dialog == NULL)
    {
      /* Check if some dialog exists */
      if (jd != NULL && jd->d_dialog != NULL)
        {
          transport = NULL;
          if (tr != NULL && tr->orig_request != NULL)
            transport = _eXosip_transport_protocol (tr->orig_request);
          if (transport == NULL)
            i = generating_bye (&request, jd->d_dialog, "UDP");
          else
            i = generating_bye (&request, jd->d_dialog, transport);
          if (i != 0)
            {
              OSIP_TRACE (osip_trace
                          (__FILE__, __LINE__, OSIP_ERROR, NULL,
                           "eXosip: cannot terminate this call!\n"));
              return -2;
            }

          if (jc->response_auth != NULL)
            eXosip_add_authentication_information (request, jc->response_auth);

          i = eXosip_create_transaction (jc, jd, request);
          if (i != 0)
            {
              OSIP_TRACE (osip_trace
                          (__FILE__, __LINE__, OSIP_ERROR, NULL,
                           "eXosip: cannot initiate SIP transaction!\n"));
              return -2;
            }

          osip_dialog_free (jd->d_dialog);
          jd->d_dialog = NULL;
          eXosip_update ();     /* AMD 30/09/05 */
          return 0;
        }

      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No established dialog!\n"));
      return -1;
    }

  if (tr == NULL)
    {
      /*this may not be enough if it's a re-INVITE! */
      tr = eXosip_find_last_inc_invite (jc, jd);
      if (tr != NULL && tr->last_response != NULL &&
          MSG_IS_STATUS_1XX (tr->last_response))
        {                       /* answer with 603 */
          i = eXosip_call_send_answer (tr->transactionid, 603, NULL);
          return i;
        }
    }

  transport = NULL;
  if (tr != NULL && tr->orig_request != NULL)
    transport = _eXosip_transport_protocol (tr->orig_request);
  if (transport == NULL)
    i = generating_bye (&request, jd->d_dialog, "UDP");
  else
    i = generating_bye (&request, jd->d_dialog, transport);

  if (i != 0)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: cannot terminate this call!\n"));
      return -2;
    }
  if (jc->response_auth != NULL)
    eXosip_add_authentication_information (request, jc->response_auth);

  i = eXosip_create_transaction (jc, jd, request);
  if (i != 0)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: cannot initiate SIP transaction!\n"));
      return -2;
    }

  osip_dialog_free (jd->d_dialog);
  jd->d_dialog = NULL;
  eXosip_update ();             /* AMD 30/09/05 */
  return 0;
}

int
eXosip_call_build_prack (int tid, osip_message_t ** prack)
{
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;
  osip_transaction_t *tr = NULL;

  osip_header_t *rseq;
  char *transport;
  int i;

  *prack = NULL;

  if (tid > 0)
    {
      _eXosip_call_transaction_find (tid, &jc, &jd, &tr);
    }
  if (jc == NULL || jd == NULL || jd->d_dialog == NULL
      || tr == NULL || tr->orig_request == NULL
      || tr->orig_request->sip_method == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No call here or no transaction for call\n"));
      return -1;
    }

  if (0 != osip_strcasecmp (tr->orig_request->sip_method, "INVITE"))
    return -1;

  /* PRACK are only send in the PROCEEDING state */
  if (tr->state != ICT_PROCEEDING)
    return -1;

  if (tr->orig_request->cseq == NULL
      || tr->orig_request->cseq->number == NULL
      || tr->orig_request->cseq->method == NULL)
    return -1;

  transport = NULL;
  if (tr != NULL && tr->orig_request != NULL)
    transport = _eXosip_transport_protocol (tr->orig_request);

  if (transport == NULL)
    i = _eXosip_build_request_within_dialog (prack, "PRACK", jd->d_dialog, "UDP");
  else
    i =
      _eXosip_build_request_within_dialog (prack, "PRACK", jd->d_dialog,
                                           transport);

  if (i != 0)
    return -2;

  osip_message_header_get_byname (tr->last_response, "RSeq", 0, &rseq);
  if (rseq != NULL && rseq->hvalue != NULL)
    {
      char tmp[128];

      memset (tmp, '\0', sizeof (tmp));
      snprintf (tmp, 127, "%s %s %s", rseq->hvalue,
                tr->orig_request->cseq->number, tr->orig_request->cseq->method);
      osip_message_set_header (*prack, "RAck", tmp);
    }

  return 0;
}

int
eXosip_call_send_prack (int tid, osip_message_t * prack)
{
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;
  osip_transaction_t *tr = NULL;

  osip_event_t *sipevent;
  int i;

  if (prack == NULL)
    return -1;

  if (tid > 0)
    {
      _eXosip_call_transaction_find (tid, &jc, &jd, &tr);
    }
  if (jc == NULL || jd == NULL || jd->d_dialog == NULL
      || tr == NULL || tr->orig_request == NULL
      || tr->orig_request->sip_method == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No call here or no transaction for call\n"));
      osip_message_free (prack);
      return -1;
    }

  if (0 != osip_strcasecmp (tr->orig_request->sip_method, "INVITE"))
    {
      osip_message_free (prack);
      return -1;
    }

  /* PRACK are only send in the PROCEEDING state */
  if (tr->state != ICT_PROCEEDING)
    {
      osip_message_free (prack);
      return -1;
    }

  tr = NULL;
  i = _eXosip_transaction_init (&tr, NICT, eXosip.j_osip, prack);

  if (i != 0)
    {
      osip_message_free (prack);
      return -2;
    }

  osip_list_add (jd->d_out_trs, tr, 0);

  sipevent = osip_new_outgoing_sipmessage (prack);
  sipevent->transactionid = tr->transactionid;

  osip_transaction_set_your_instance (tr, __eXosip_new_jinfo (jc, jd, NULL, NULL));
  osip_transaction_add_event (tr, sipevent);
  __eXosip_wakeup ();
  return 0;
}


int
_eXosip_call_redirect_request (eXosip_call_t * jc,
                               eXosip_dialog_t * jd, osip_transaction_t * out_tr)
{
  osip_transaction_t *tr = NULL;
  osip_message_t *msg = NULL;
  osip_event_t *sipevent;

  int cseq;
  osip_via_t *via;
  osip_contact_t *co;
  int pos;
  int i;
  int protocol = IPPROTO_UDP;

  if (jc == NULL)
    return -1;
  if (jd != NULL)
    {
      if (jd->d_out_trs == NULL)
        return -1;
    }
  if (out_tr == NULL
      || out_tr->orig_request == NULL || out_tr->last_response == NULL)
    return -1;

  osip_message_clone (out_tr->orig_request, &msg);
  if (msg == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: could not clone msg for authentication\n"));
      return -1;
    }

  via = (osip_via_t *) osip_list_get (&msg->vias, 0);
  if (via == NULL || msg->cseq == NULL || msg->cseq->number == NULL)
    {
      osip_message_free (msg);
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: missing via or cseq header\n"));
      return -1;
    }

  co = NULL;
  pos = 0;
  while (!osip_list_eol (&out_tr->last_response->contacts, pos))
    {
      co = (osip_contact_t *) osip_list_get (&out_tr->last_response->contacts, pos);
      if (co != NULL && co->url != NULL)
        {
          /* check tranport? Only allow UDP, right now */
          osip_uri_param_t *u_param;
          int pos2;

          u_param = NULL;
          pos2 = 0;
          while (!osip_list_eol (&co->url->url_params, pos2))
            {
              u_param =
                (osip_uri_param_t *) osip_list_get (&co->url->url_params, pos2);
              if (u_param == NULL || u_param->gname == NULL
                  || u_param->gvalue == NULL)
                {
                  u_param = NULL;
                  /* skip */
              } else if (0 == osip_strcasecmp (u_param->gvalue, "transport"))
                {
                  if (0 == osip_strcasecmp (u_param->gvalue, "udp"))
                    {
#if 0
                      /* remove the UDP parameter */
                      osip_list_remove (co->url->url_params, pos2);
                      osip_uri_param_free (u_param);
#endif
                      u_param = NULL;
                      protocol = IPPROTO_UDP;
                      break;    /* ok */
                  } else if (0 == osip_strcasecmp (u_param->gvalue, "tcp"))
                    {
#if 0
                      osip_list_remove (co->url->url_params, pos2);
                      osip_uri_param_free (u_param);
#endif
                      protocol = IPPROTO_TCP;
                      u_param = NULL;
                    }
                  break;
                }
              pos2++;
            }

          if (u_param == NULL || u_param->gname == NULL || u_param->gvalue == NULL)
            {
              break;            /* default is udp! */
            }
        }
      pos++;
      co = NULL;
    }

  if (co == NULL || co->url == NULL)
    {
      osip_message_free (msg);
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: contact header\n"));
      return -1;
    }

  /* TODO:
     remove extra parameter from new request-uri
     check usual parameter like "transport"
   */

  /* replace request-uri with NEW contact address */
  osip_uri_free (msg->req_uri);
  msg->req_uri = NULL;
  osip_uri_clone (co->url, &msg->req_uri);

  /* increment cseq */
  cseq = atoi (msg->cseq->number);
  osip_free (msg->cseq->number);
  msg->cseq->number = strdup_printf ("%i", cseq + 1);
  if (jd != NULL && jd->d_dialog != NULL)
    {
      jd->d_dialog->local_cseq++;
    }

  i = eXosip_update_top_via(msg);
  if (i!=0)
    {
      osip_message_free (msg);
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: unsupported protocol\n"));
      return -1;
    }

  eXosip_add_authentication_information (msg, out_tr->last_response);
  osip_message_force_update (msg);

  if (0 != osip_strcasecmp (msg->sip_method, "INVITE"))
    {
      i = _eXosip_transaction_init (&tr, NICT, eXosip.j_osip, msg);
  } else
    {
      i = _eXosip_transaction_init (&tr, ICT, eXosip.j_osip, msg);
    }

  if (i != 0)
    {
      osip_message_free (msg);
      return -1;
    }

  if (out_tr == jc->c_out_tr)
    {
      /* replace with the new tr */
      osip_list_add (eXosip.j_transactions, jc->c_out_tr, 0);
      jc->c_out_tr = tr;

      /* fix dialog issue */
      if (jd != NULL)
        {
          REMOVE_ELEMENT (jc->c_dialogs, jd);
          eXosip_dialog_free (jd);
          jd = NULL;
        }
  } else
    {
      /* add the new tr for the current dialog */
      osip_list_add (jd->d_out_trs, tr, 0);
    }

  sipevent = osip_new_outgoing_sipmessage (msg);

  osip_transaction_set_your_instance (tr, __eXosip_new_jinfo (jc, jd, NULL, NULL));
  osip_transaction_add_event (tr, sipevent);

  eXosip_update ();             /* fixed? */
  __eXosip_wakeup ();
  return 0;
}

int
_eXosip_call_send_request_with_credential (eXosip_call_t * jc,
                                           eXosip_dialog_t * jd,
                                           osip_transaction_t * out_tr)
{
  osip_transaction_t *tr = NULL;
  osip_message_t *msg = NULL;
  osip_event_t *sipevent;

  int cseq;
  osip_via_t *via;
  int i;
  int pos;

  if (jc == NULL)
    return -1;
  if (jd != NULL)
    {
      if (jd->d_out_trs == NULL)
        return -1;
    }
  if (out_tr == NULL
      || out_tr->orig_request == NULL || out_tr->last_response == NULL)
    return -1;

  osip_message_clone (out_tr->orig_request, &msg);
  if (msg == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: could not clone msg for authentication\n"));
      return -1;
    }

  /* remove all previous authentication headers */
  pos = 0;
  while (!osip_list_eol (&msg->authorizations, pos))
    {
      osip_authorization_t *auth;

      auth = (osip_authorization_t *) osip_list_get (&msg->authorizations, pos);
      osip_list_remove (&msg->authorizations, pos);
      osip_authorization_free (auth);
      pos++;
    }

  pos = 0;
  while (!osip_list_eol (&msg->proxy_authorizations, pos))
    {
      osip_proxy_authorization_t *auth;

      auth =
        (osip_proxy_authorization_t *) osip_list_get (&msg->
                                                      proxy_authorizations, pos);
      osip_list_remove (&msg->proxy_authorizations, pos);
      osip_authorization_free (auth);
      pos++;
    }


  via = (osip_via_t *) osip_list_get (&msg->vias, 0);
  if (via == NULL || msg->cseq == NULL || msg->cseq->number == NULL)
    {
      osip_message_free (msg);
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: missing via or cseq header\n"));
      return -1;
    }

  /* increment cseq */
  cseq = atoi (msg->cseq->number);
  osip_free (msg->cseq->number);
  msg->cseq->number = strdup_printf ("%i", cseq + 1);
  if (jd != NULL && jd->d_dialog != NULL)
    {
      jd->d_dialog->local_cseq++;
    }

  i = eXosip_update_top_via(msg);
  if (i!=0)
    {
      osip_message_free (msg);
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: unsupported protocol\n"));
      return -1;
    }

  eXosip_add_authentication_information (msg, out_tr->last_response);
  osip_message_force_update (msg);

  if (0 != osip_strcasecmp (msg->sip_method, "INVITE"))
    {
      i = _eXosip_transaction_init (&tr, NICT, eXosip.j_osip, msg);
  } else
    {
      i = _eXosip_transaction_init (&tr, ICT, eXosip.j_osip, msg);
    }

  if (i != 0)
    {
      osip_message_free (msg);
      return -1;
    }

  if (out_tr == jc->c_out_tr)
    {
      /* replace with the new tr */
      osip_list_add (eXosip.j_transactions, jc->c_out_tr, 0);
      jc->c_out_tr = tr;

      /* fix dialog issue */
      if (jd != NULL)
        {
          REMOVE_ELEMENT (jc->c_dialogs, jd);
          eXosip_dialog_free (jd);
          jd = NULL;
        }
  } else
    {
      /* add the new tr for the current dialog */
      osip_list_add (jd->d_out_trs, tr, 0);
    }

  sipevent = osip_new_outgoing_sipmessage (msg);

  osip_transaction_set_your_instance (tr, __eXosip_new_jinfo (jc, jd, NULL, NULL));
  osip_transaction_add_event (tr, sipevent);

  eXosip_update ();             /* fixed? */
  __eXosip_wakeup ();
  return 0;
}

int
eXosip_call_get_referto(int did, char *refer_to, size_t refer_to_len)
{
  eXosip_dialog_t *jd = NULL;
  eXosip_call_t *jc = NULL;
  osip_transaction_t *tr = NULL;
  osip_uri_t *referto_uri;
  char atmp[256];
  char *referto_tmp=NULL;
  int i;

  eXosip_call_dialog_find (did, &jc, &jd);
  if (jc == NULL || jd == NULL || jd->d_dialog == NULL)
    {
      OSIP_TRACE (osip_trace
		  (__FILE__, __LINE__, OSIP_ERROR, NULL,
		   "eXosip: No call here?\n"));
      return -1;
    }

  tr = eXosip_find_last_invite (jc, jd);

  if (tr == NULL || tr->orig_request == NULL)
    {
      OSIP_TRACE (osip_trace
                  (__FILE__, __LINE__, OSIP_ERROR, NULL,
                   "eXosip: No transaction for call?\n"));
      return -1;
    }

  i = osip_uri_clone(jd->d_dialog->remote_uri->url, &referto_uri);
  if (i!=0)
    return -1;

  if (jd->d_dialog->type == CALLER)
    {
      snprintf(atmp, sizeof(atmp), "%s;to-tag=%s;from-tag=%s",
	       jd->d_dialog->call_id,
	       jd->d_dialog->remote_tag,
	       jd->d_dialog->local_tag);      
    }
  else
    {
      snprintf(atmp, sizeof(atmp), "%s;to-tag=%s;from-tag=%s",
	       jd->d_dialog->call_id,
	       jd->d_dialog->local_tag,
	       jd->d_dialog->remote_tag);
    }

  osip_uri_uheader_add(referto_uri, osip_strdup("Replaces"), osip_strdup(atmp));
  i = osip_uri_to_str(referto_uri, &referto_tmp);
  if (i!=0)
    {
      osip_uri_free(referto_uri);      
      return -1;
    }

  snprintf(refer_to, refer_to_len, "%s", referto_tmp);
  osip_uri_free(referto_uri);

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1