Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
sipcall.cpp 19.05 KiB
/*
 *  Copyright (C) 2004-2007 Savoir-Faire Linux inc.
 *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
 *  Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com>
 *  Author: Yan Morin <yan.morin@savoirfairelinux.com>
 *  Author : Laurielle Lea <laurielle.lea@savoirfairelinux.com>
 *
 *  This program 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "sipcall.h"
#include "global.h" // for _debug
#include <sstream> // for media buffer
#include <string>

#define _SENDRECV 0
#define _SENDONLY 1
#define _RECVONLY 2

SIPCall::SIPCall(const CallID& id, Call::CallType type) : Call(id, type)
{
  _cid = 0;
  _did = 0;
  _tid = 0;
  _xferSub = NULL;
  _invSession = NULL;
}

SIPCall::~SIPCall() 
{
}


bool 
SIPCall::SIPCallInvite(pjsip_rx_data *rdata, pj_pool_t *pool)
{
  pj_status_t status;
  
  pjmedia_sdp_session* remote_sdp = getRemoteSDPFromRequest(rdata);
  if (remote_sdp == 0) {
    return false;
  }

  // Have to do some stuff here with the SDP
  // We retrieve the remote sdp offer in the rdata struct to begin the negociation
  _localSDP = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session);
  _localSDP->conn =  PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
  
  _localSDP->origin.version = 0;
  sdpAddOrigin();
  _localSDP->name = pj_str("sflphone");
  sdpAddConnectionInfo();
  _localSDP->time.start = _localSDP->time.stop = 0;
  sdpAddMediaDescription(pool);
  
  _debug("Before validate SDP!\n");
  status = pjmedia_sdp_validate( _localSDP );
  if (status != PJ_SUCCESS) {
    _debug("Can not generate valid local sdp\n");
    return false;
  }
  
  _debug("Before create negociator!\n");
  status = pjmedia_sdp_neg_create_w_remote_offer(pool, _localSDP, remote_sdp, &_negociator);
  if (status != PJ_SUCCESS) {
      _debug("Can not create negociator\n");
      return false;
  }
  _debug("After create negociator!\n");
  
  pjmedia_sdp_media* remote_med = getRemoteMedia(remote_sdp);
  if (remote_med == 0) {
    _debug("SIP Failure: unable to get remote media\n");
    return false;
  }

  _debug("Before set audio!\n");
  if (!setRemoteAudioFromSDP(remote_sdp, remote_med)) {
    _debug("SIP Failure: unable to set IP address and port from SDP\n");
    return false;
  }

  _debug("Before set codec!\n");
  if (!setAudioCodecFromSDP(remote_med)) {
    _debug("SIP Failure: unable to set audio codecs from the remote SDP\n");
    return false;
  }

  return true;
}

bool 
SIPCall::SIPCallReinvite(eXosip_event_t *event)
{
  if (event->cid < 1 && event->did < 1) {
    _debug("SIP Failure: Invalid cid and did\n");
    return false;
  }

  if (event->request == NULL) {
    _debug("SIP Failure: No request into the event\n");
    return false;
  }

  setCid(event->cid);
  setDid(event->did);
  setTid(event->tid);

  setPeerInfoFromRequest(event);

  sdp_message_t* remote_sdp = getRemoteSDPFromRequest(event);
  if (remote_sdp == 0) {
    return false;
  }

  sdp_media_t* remote_med = getRemoteMedia(event->tid, remote_sdp);
  if (remote_med == 0) {
    sdp_message_free (remote_sdp);
    return false;
  }

  /*if (!setRemoteAudioFromSDP(remote_med, remote_sdp)) {
    _debug("SIP Failure: unable to set IP address and port from SDP\n");
    sdp_message_free (remote_sdp);
    return false;
  }

  if (!setAudioCodecFromSDP(remote_med, event->tid)) {
    sdp_message_free (remote_sdp);
    return false;
  }*/

  osip_message_t *answer = 0;
  eXosip_lock();
  _debug("< Building Answer 200\n");
  if (0 == eXosip_call_build_answer (event->tid, 200, &answer)) {
    if ( 0 != sdp_complete_message(remote_sdp, answer)) {
      osip_message_free(answer);
      // Send 415 Unsupported media type
      eXosip_call_send_answer (event->tid, 415, NULL);
      _debug("< Sending Answer 415\n");
    } else {

      sdp_message_t *local_sdp = eXosip_get_sdp_info(answer);
      sdp_media_t *local_med = NULL;
      if (local_sdp != NULL) {
         local_med = eXosip_get_audio_media(local_sdp);
      }
      if (local_sdp != NULL && local_med != NULL) {
        /* search if stream is sendonly or recvonly */
        int _remote_sendrecv = sdp_analyse_attribute (remote_sdp, remote_med);
        int _local_sendrecv =  sdp_analyse_attribute (local_sdp, local_med);
        _debug("            Remote SendRecv: %d\n", _remote_sendrecv);
        _debug("            Local  SendRecv: %d\n", _local_sendrecv);
        if (_local_sendrecv == _SENDRECV) {
          if (_remote_sendrecv == _SENDONLY)      { _local_sendrecv = _RECVONLY; }
          else if (_remote_sendrecv == _RECVONLY) { _local_sendrecv = _SENDONLY; }
        }
        _debug("            Final Local SendRecv: %d\n", _local_sendrecv);
        sdp_message_free (local_sdp);
      }
      _debug("< Sending answer 200\n");
      if (0 != eXosip_call_send_answer (event->tid, 200, answer)) {
        _debug("SipCall::newIncomingCall: cannot send 200 OK?\n");
      }
    }
  }
  eXosip_unlock ();
  sdp_message_free (remote_sdp);
  return true;
}

bool 
SIPCall::SIPCallAnswered(eXosip_event_t *event)
{
  if (event->cid < 1 && event->did < 1) {
    _debug("SIP Failure: Invalid cid and did\n");
    return false;
  }

  if (event->request == NULL) {
    _debug("SIP Failure: No request into the event\n");
    return false;
  }

  setCid(event->cid);
  setDid(event->did);

  //setPeerInfoFromResponse()

  eXosip_lock ();
  {
    osip_message_t *ack = NULL;
    int i;
    i = eXosip_call_build_ack (event->did, &ack);
    if (i != 0) {
      _debug("SipCall::answeredCall: Cannot build ACK for call!\n");
    } else {
      sdp_message_t *local_sdp = NULL;
      sdp_message_t *remote_sdp = NULL;

      if (event->request != NULL && event->response != NULL) {
        local_sdp = eXosip_get_sdp_info (event->request);
        remote_sdp = eXosip_get_sdp_info (event->response);
      }
      if (local_sdp == NULL && remote_sdp != NULL) {
        /* sdp in ACK */
        i = sdp_complete_message (remote_sdp, ack);
        if (i != 0) {
            _debug("SipCall::answeredCall: Cannot complete ACK with sdp body?!\n");
        }
      }
      sdp_message_free (local_sdp);
      sdp_message_free (remote_sdp);

      _debug("< Send ACK\n");
      eXosip_call_send_ack (event->did, ack);
    }
  }
  eXosip_unlock ();
  return true;  
}


bool 
SIPCall::SIPCallAnsweredWithoutHold(eXosip_event_t* event)
{
    return true;
}

bool
SIPCall::SIPCallAnsweredWithoutHold(pjsip_rx_data *rdata)
{
  pjmedia_sdp_session* remote_sdp = getRemoteSDPFromRequest(rdata);
  if (remote_sdp == NULL) {
    _debug("SIP Failure: no remote sdp\n");
    return false;
  }

  pjmedia_sdp_media* remote_med = getRemoteMedia(remote_sdp);
  if (remote_med==NULL) {
    return false;
  }
  
  _debug("Before set audio!\n");
  if (!setRemoteAudioFromSDP(remote_sdp, remote_med)) {
    _debug("SIP Failure: unable to set IP address and port from SDP\n");
    return false;
  }

  _debug("Before set codec!\n");
  if (!setAudioCodecFromSDP(remote_med)) {
    _debug("SIP Failure: unable to set audio codecs from the remote SDP\n");
    return false;
  }

  return true;
}


int 
SIPCall::sdp_complete_message(sdp_message_t * remote_sdp, osip_message_t * msg)
{
  // Format port to a char*
  if (remote_sdp == NULL) {
    _debug("SipCall::sdp_complete_message: No remote SDP body found for call\n");
    return -1;
  }
  if (msg == NULL) {
    _debug("SipCall::sdp_complete_message: No message to complete\n");
    return -1;
  }

  std::ostringstream media;

  // for each medias
  int iMedia = 0;
  char *tmp = NULL;
  #ifdef LIBOSIP2_WITHPOINTER 
  const osip_list_t* remote_sdp_m_medias = remote_sdp->m_medias; // old abi
  #else
  const osip_list_t* remote_sdp_m_medias = &(remote_sdp->m_medias);
  #endif
  osip_list_t* remote_med_m_payloads = 0;

  while (!osip_list_eol(remote_sdp_m_medias, iMedia)) {
    sdp_media_t *remote_med = (sdp_media_t *)osip_list_get(remote_sdp_m_medias, iMedia);
    if (remote_med == 0) { continue; }

    if (0 != osip_strcasecmp (remote_med->m_media, "audio")) {
      // if this is not an "audio" media, we set it to 0
      media << "m=" << remote_med->m_media << " 0 " << remote_med->m_proto << " \r\n";
    } else {
      std::ostringstream listCodec;
      std::ostringstream listRtpMap;

      // search for compatible codec: foreach payload
      int iPayload = 0;
      #ifdef LIBOSIP2_WITHPOINTER 
      remote_med_m_payloads = remote_med->m_payloads; // old abi
      #else
      remote_med_m_payloads = &(remote_med->m_payloads);
      #endif

      //while (!osip_list_eol(remote_med_m_payloads, iPayload) && iPayload < 2) {
      while (!osip_list_eol(remote_med_m_payloads, iPayload)) {
        tmp = (char *)osip_list_get(remote_med_m_payloads, iPayload);
        if (tmp!=NULL) {
          int payload = atoi(tmp);
	  _debug("remote payload = %s\n", tmp);
          AudioCodecType audiocodec = (AudioCodecType)payload;
          if (audiocodec != (AudioCodecType)-1 && _codecMap.isActive(audiocodec))  { 
            listCodec << payload << " ";
            //listRtpMap << "a=rtpmap:" << payload << " " << audiocodec->getCodecName() << "/" << audiocodec->getClockRate();
            listRtpMap << "a=rtpmap:" << payload << " " << _codecMap.getCodecName(audiocodec) << "/" << _codecMap.getSampleRate(audiocodec);
	    if (_codecMap.getChannel(audiocodec) != 1) {
              listRtpMap << "/" << _codecMap.getChannel(audiocodec);
            }
            listRtpMap << "\r\n";
          }
        }
        iPayload++;
      }
      if (listCodec.str().empty()) {
        media << "m=" << remote_med->m_media << " 0 " << remote_med->m_proto << " \r\n";
      } else {
        // we add the media line + a=rtpmap list
        media << "m=" << remote_med->m_media << " " << getLocalExternAudioPort() << " RTP/AVP " << listCodec.str() << "\r\n";
        media << listRtpMap.str();
      }
    }
    iMedia++;
  }
  char buf[4096];
  snprintf (buf, 4096,
    "v=0\r\n"
    "o=user 0 0 IN IP4 %s\r\n"
    "s=session\r\n"
    "c=IN IP4 %s\r\n"
    "t=0 0\r\n"
    "%s\n", getLocalIp().c_str(), getLocalIp().c_str(), media.str().c_str());

  osip_message_set_body (msg, buf, strlen (buf));
  osip_message_set_content_type (msg, "application/sdp");
  _debug("          sdp: %s", buf);
  return 0;
}



int 
SIPCall::sdp_analyse_attribute (sdp_message_t * sdp, sdp_media_t * med)
{
  int pos;
  int pos_media;

  /* test media attributes */
  pos = 0;
  #ifdef LIBOSIP2_WITHPOINTER 
  const osip_list_t* med_a_attributes = med->a_attributes; // old abi
  #else
  const osip_list_t* med_a_attributes = &(med->a_attributes);
  #endif
  while (!osip_list_eol (med_a_attributes, pos)) {
      sdp_attribute_t *at;

      at = (sdp_attribute_t *) osip_list_get (med_a_attributes, pos);
      if (at->a_att_field != NULL && 
      0 == strcmp (at->a_att_field, "sendonly")) {
      return _SENDONLY;
      } else if (at->a_att_field != NULL &&
                0 == strcmp (at->a_att_field, "recvonly")) {
          return _RECVONLY;
      } else if (at->a_att_field != NULL &&
                0 == strcmp (at->a_att_field, "sendrecv")) {
          return _SENDRECV;
      }
      pos++;
  }

  /* test global attributes */
  pos_media = -1;
  pos = 0;
  #ifdef LIBOSIP2_WITHPOINTER 
  const osip_list_t* sdp_a_attributes = sdp->a_attributes; // old abi
  #else
  const osip_list_t* sdp_a_attributes = &(sdp->a_attributes);
  #endif
  while (!osip_list_eol (sdp_a_attributes, pos)) {
      sdp_attribute_t *at;

      at = (sdp_attribute_t *) osip_list_get (sdp_a_attributes, pos);
      if (at->a_att_field != NULL && 
      0 == strcmp (at->a_att_field, "sendonly")) {
          return _SENDONLY;
      } else if (at->a_att_field != NULL &&
                0 == strcmp (at->a_att_field, "recvonly")) {
          return _RECVONLY;
      } else if (at->a_att_field != NULL &&
                0 == strcmp (at->a_att_field, "sendrecv")) {
          return _SENDRECV;
      }
      pos++;
  }

  return _SENDRECV;
}
bool 
SIPCall::setPeerInfoFromRequest(eXosip_event_t *event)
{
  // event->request should not be NULL!
  char remote_uri[256] = "";
  std::string name("");
  std::string number("");

  char *tmp = NULL;
  osip_from_to_str(event->request->from, &tmp);
  if (tmp != NULL) {
    snprintf (remote_uri, 255, "%s", tmp);
    remote_uri[255] = '\0';
    osip_free (tmp);

    // Get the name/number
    osip_from_t *from;
    osip_from_init(&from);
    osip_from_parse(from, remote_uri);
    char *tmpname = osip_from_get_displayname(from);
    if ( tmpname != NULL ) {
      name = tmpname;
    }
    osip_uri_t* url = osip_from_get_url(from); 
    if ( url != NULL && url->username != NULL) {
      number = url->username;
    }
    osip_from_free(from);
  }

  _debug("            Name: %s\n", name.c_str());
  _debug("            Number: %s\n", number.c_str());
  _debug("            Remote URI: %s\n", remote_uri);

  setPeerName(name);
  setPeerNumber(number);  
  return true;
}

pjmedia_sdp_session* 
SIPCall::getRemoteSDPFromRequest(pjsip_rx_data *rdata)
{
    pjmedia_sdp_session *sdp;
    pjsip_msg *msg;
    pjsip_msg_body *body;

    msg = rdata->msg_info.msg;
    body = msg->body;

    pjmedia_sdp_parse( rdata->tp_info.pool, (char*)body->data, body->len, &sdp );

    return sdp;
}

sdp_media_t* 
SIPCall::getRemoteMedia(int tid, sdp_message_t* remote_sdp)
{
  // Remote Media Port
  eXosip_lock();
  sdp_media_t *remote_med = eXosip_get_audio_media(remote_sdp);
  eXosip_unlock();

  if (remote_med == NULL || remote_med->m_port == NULL) {
    // no audio media proposed
    _debug("SIP Failure: unsupported media\n");
    _debug("< Sending 415 Unsupported media type\n");
    eXosip_lock();
    eXosip_call_send_answer (tid, 415, NULL);
    eXosip_unlock();
    sdp_message_free (remote_sdp);
    return 0;
  }
  return remote_med;
}

bool 
SIPCall::setRemoteAudioFromSDP(pjmedia_sdp_session* remote_sdp, pjmedia_sdp_media *remote_med)
{
  std::string remoteIP(remote_sdp->conn->addr.ptr, remote_sdp->conn->addr.slen);
  _debug("            Remote Audio IP: %s\n", remoteIP.data());
  setRemoteIP(remoteIP);
  int remotePort = remote_med->desc.port; 
  _debug("            Remote Audio Port: %d\n", remotePort);
  setRemoteAudioPort(remotePort);
  
  return true;
}

bool 
SIPCall::setAudioCodecFromSDP(pjmedia_sdp_media* remote_med)
{
  // Remote Payload
  int payLoad = -1;
  int codecCount = remote_med->desc.fmt_count;
  for(int i = 0; i < codecCount; i++) {
      payLoad = atoi(remote_med->desc.fmt[i].ptr);
      if (_codecMap.isActive((AudioCodecType)payLoad))
          break;
          
      payLoad = -1;
  }
  
  if(payLoad != -1) {
    _debug("            Payload: %d\n", payLoad);
    setAudioCodec((AudioCodecType)payLoad);
  } else
    return false;
  
  return true;
}

void SIPCall::sdpAddOrigin( void )
{
    pj_time_val tv;
    pj_gettimeofday(&tv);

    _localSDP->origin.user = pj_str(pj_gethostname()->ptr);
    // Use Network Time Protocol format timestamp to ensure uniqueness.
    _localSDP->origin.id = tv.sec + 2208988800UL;
    // The type of network ( IN for INternet )
    _localSDP->origin.net_type = pj_str("IN"); //STR_IN;
    // The type of address
    _localSDP->origin.addr_type = pj_str("IP4"); //STR_IP4;
    // The address of the machine from which the session was created
    _localSDP->origin.addr = pj_str( (char*)_ipAddr.c_str() );    
}

void SIPCall::sdpAddConnectionInfo( void )
{
    _localSDP->conn->net_type = _localSDP->origin.net_type;
    _localSDP->conn->addr_type = _localSDP->origin.addr_type;
    _localSDP->conn->addr = _localSDP->origin.addr;
}

void SIPCall::sdpAddMediaDescription(pj_pool_t* pool)
{
    pjmedia_sdp_media* med;
    pjmedia_sdp_attr *attr;
    //int nbMedia, i;
    med = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
    //nbMedia = getSDPMediaList().size();
    _localSDP->media_count = 1;
    
    med->desc.media = pj_str("audio");
    med->desc.port_count = 1;
    med->desc.port = getLocalExternAudioPort();
    med->desc.transport = pj_str("RTP/AVP");
    
    CodecsMap::iterator itr;
    itr = _codecMap.getCodecsMap().begin();
    int count = _codecMap.getCodecsNumber();
    med->desc.fmt_count = count;
    
    int i = 0;
    while(itr != _codecMap.getCodecsMap().end()) {
        std::ostringstream format;
        format << (*itr).first;
        pj_strdup2(pool, &med->desc.fmt[i], format.str().data());
        
        AudioCodec *codec = (*itr).second;
        pjmedia_sdp_rtpmap rtpMap;
        rtpMap.pt = med->desc.fmt[i];
        rtpMap.enc_name = pj_str((char *)codec->getCodecName().data());
        rtpMap.clock_rate = codec->getClockRate();
        if(codec->getChannel() > 1) {
            std::ostringstream channel;
            channel << codec->getChannel();
            rtpMap.param = pj_str((char *)channel.str().data());
        } else
            rtpMap.param.slen = 0;
        
        pjmedia_sdp_rtpmap_to_attr( pool, &rtpMap, &attr );
        med->attr[i] = attr;
        i++;
        itr++;
    }
    
    //FIXME! Add the direction stream
    attr = (pjmedia_sdp_attr*)pj_pool_zalloc( pool, sizeof(pjmedia_sdp_attr) );
    pj_strdup2( pool, &attr->name, "sendrecv");
    med->attr[ i++] = attr;
    med->attr_count = i;

    _localSDP->media[0] = med;
    /*for( i=0; i<nbMedia; i++ ){
        getMediaDescriptorLine( getSDPMediaList()[i], pool, &med );
        this->_local_offer->media[i] = med;
    } */
    
}

pjmedia_sdp_media* SIPCall::getRemoteMedia(pjmedia_sdp_session *remote_sdp)
{
    int count, i;
    
    count = remote_sdp->media_count;
    for(i = 0; i < count; ++i) {
        if(pj_stricmp2(&remote_sdp->media[i]->desc.media, "audio") == 0)
            return remote_sdp->media[i];
    }
    
    return NULL;
}

bool SIPCall::startNegociation(pj_pool_t *pool)
{
    pj_status_t status;
    _debug("Before negotiate!\n");
    status = pjmedia_sdp_neg_negotiate(pool, _negociator, 0);
    
    return (status == PJ_SUCCESS);
}

bool SIPCall::createInitialOffer(pj_pool_t *pool)
{
  pj_status_t status;

  // Have to do some stuff here with the SDP
  _localSDP = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session);
  _localSDP->conn =  PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
  
  _localSDP->origin.version = 0;
  sdpAddOrigin();
  _localSDP->name = pj_str("sflphone");
  sdpAddConnectionInfo();
  _localSDP->time.start = _localSDP->time.stop = 0;
  sdpAddMediaDescription(pool);
  
  _debug("Before validate SDP!\n");
  status = pjmedia_sdp_validate( _localSDP );
  if (status != PJ_SUCCESS) {
    _debug("Can not generate valid local sdp %d\n", status);
    return false;
  }
  
  _debug("Before create negociator!\n");
  // Create the SDP negociator instance with local offer
  status = pjmedia_sdp_neg_create_w_local_offer( pool, _localSDP, &_negociator);
  //state = pjmedia_sdp_neg_get_state( _negociator );

  PJ_ASSERT_RETURN( status == PJ_SUCCESS, 1 );

  return true;
    
}