Commit 39ff61f8 authored by Simon Morlat's avatar Simon Morlat

SAL (signaling abstraction layer) in progress.

parent 1d96392a
......@@ -16,6 +16,9 @@ lib_LTLIBRARIES=liblinphone.la
liblinphone_la_SOURCES=\
linphonecore.c linphonecore.h private.h\
exevents.c exevents.h \
offeranswer.c offeranswer.h\
sal_eXosip2.c sal.h \
sal_eXosip2_sdp.c \
misc.c \
address.c \
enum.c enum.h \
......
/*
linphone
Copyright (C) 2010 Simon MORLAT (simon.morlat@free.fr)
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 2
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "sal.h"
#include "offeranswer.h"
static PayloadType * find_payload_type_best_match(const MSList *l, const PayloadType *refpt){
PayloadType *pt;
char value[10];
const MSList *elem;
PayloadType *candidate=NULL;
for (elem=l;elem!=NULL;elem=elem->next){
pt=(PayloadType*)elem->data;
if (strcasecmp(pt->mime_type,refpt->mime_type)==0 && pt->clock_rate==refpt->clock_rate){
candidate=pt;
/*good candidate, check fmtp for H264 */
if (strcasecmp(pt->mime_type,"H264")==0){
if (pt->recv_fmtp!=NULL && refpt->recv_fmtp!=NULL){
int mode1=0,mode2=0;
if (fmtp_get_value(pt->recv_fmtp,"packetization-mode",value,sizeof(value))){
mode1=atoi(value);
}
if (fmtp_get_value(refpt->recv_fmtp,"packetization-mode",value,sizeof(value))){
mode2=atoi(value);
}
if (mode1==mode2)
break; /*exact match */
}
}else break;
}
}
return candidate;
}
static MSList *match_payloads(const MSList *local, const MSList *remote){
const MSList *e2;
MSList *res=NULL;
PayloadType *matched;
for(e2=remote;e2!=NULL;e2=e2->next){
PayloadType *p2=(PayloadType*)e2->data;
matched=find_payload_type_best_match(local,p2);
if (matched){
matched=payload_type_clone(matched);
if (p2->recv_fmtp)
payload_type_set_send_fmtp(matched,p2->recv_fmtp);
res=ms_list_append(res,matched);
payload_type_set_number(matched,payload_type_get_number(p2));
}else{
ms_message("No match for %s/%i",p2->mime_type,p2->clock_rate);
}
}
return res;
}
static bool_t only_telephone_event(const MSList *l){
for(;l!=NULL;l=l->next){
PayloadType *p=(PayloadType*)l->data;
if (strcasecmp(p->mime_type,"telephone-event")!=0){
return FALSE;
}
}
return TRUE;
}
static void initiate_outgoing(const SalStreamDescription *local_offer,
const SalStreamDescription *remote_answer,
SalStreamDescription *result){
result->payloads=match_payloads(local_offer->payloads,remote_answer->payloads);
if (result->payloads && !only_telephone_event(result->payloads)){
result->port=remote_answer->port;
result->bandwidth=remote_answer->bandwidth;
result->ptime=remote_answer->ptime;
}else{
result->port=0;
}
}
static void initiate_incoming(const SalStreamDescription *local_cap,
const SalStreamDescription *remote_offer,
SalStreamDescription *result){
result->payloads=match_payloads(local_cap->payloads,remote_offer->payloads);
if (result->payloads && !only_telephone_event(result->payloads)){
result->port=remote_offer->port;
result->bandwidth=remote_offer->bandwidth;
result->ptime=remote_offer->ptime;
}else{
result->port=0;
}
}
/**
* Returns a media description to run the streams with, based on a local offer
* and the returned response (remote).
**/
int offer_answer_initiate_outgoing(const SalMediaDescription *local_offer,
const SalMediaDescription *remote_answer,
SalMediaDescription *result){
int i;
for(i=0;i<local_offer->nstreams;++i){
initiate_outgoing(&local_offer->streams[i],&remote_answer->streams[i],&result->streams[i]);
}
result->nstreams=local_offer->nstreams;
strcpy(result->addr,remote_answer->addr);
return 0;
}
/**
* Returns a media description to run the streams with, based on the local capabilities and
* and the received offer.
* The returned media description is an answer and should be sent to the offerer.
**/
int offer_answer_initiate_incoming(const SalMediaDescription *local_capabilities,
const SalMediaDescription *remote_offer,
SalMediaDescription *result){
int i;
for(i=0;i<local_capabilities->nstreams;++i){
initiate_incoming(&local_capabilities->streams[i],&remote_offer->streams[i],&result->streams[i]);
}
result->nstreams=local_capabilities->nstreams;
strcpy(result->addr,remote_offer->addr);
return 0;
}
/*
linphone
Copyright (C) 2010 Simon MORLAT (simon.morlat@free.fr)
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 2
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef offeranswer_h
#define offeranswer_h
/**
This header files defines the SDP offer answer API.
It can be used by implementations of SAL directly.
**/
/**
* Returns a media description to run the streams with, based on a local offer
* and the returned response (remote).
**/
int offer_answer_initiate_outgoing(const SalMediaDescription *local_offer,
const SalMediaDescription *remote_answer,
SalMediaDescription *result);
/**
* Returns a media description to run the streams with, based on the local capabilities and
* and the received offer.
* The returned media description is an answer and should be sent to the offerer.
**/
int offer_answer_initiate_incoming(const SalMediaDescription *local_capabilities,
const SalMediaDescription *remote_offer,
SalMediaDescription *result);
#endif
/*
linphone
Copyright (C) 2010 Simon MORLAT (simon.morlat@free.fr)
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 2
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/**
This header files defines the Signaling Abstraction Layer.
The purpose of this layer is too allow experiment different call signaling
protocols and implementations under linphone, for example SIP, JINGLE...
**/
#ifndef sal_h
#define sal_h
#include "mediastreamer2/mscommon.h"
struct Sal;
typedef struct Sal Sal;
struct SalOp;
typedef struct SalOp SalOp;
Sal * sal_init();
void sal_uninit(Sal* sal);
typedef enum {
SAL_TRANSPORT_DATAGRAM,
SAL_TRANSPORT_STREAM
}SalTransport;
typedef enum {
SAL_AUDIO,
SAL_VIDEO,
SAL_OTHER
} SalStreamType;
typedef struct SalStreamDescription{
SalStreamType type;
int port;
MSList *payloads; //<list of PayloadType
int bandwidth;
int ptime;
} SalStreamDescription;
typedef struct SalMediaDescription{
char addr[64];
char username[64];
int nstreams;
SalStreamDescription streams[2];
} SalMediaDescription;
void sal_media_description_free(SalMediaDescription *md);
typedef enum SalError{
SalErrorNetwork,
SalErrorMedia,
SalErrorAuth,
SalErrorForbidden
} SalError;
typedef void (*SalOnCallReceived)(SalOp *op);
typedef void (*SalOnCallRinging)(SalOp *op);
typedef void (*SalOnCallAccepted)(SalOp *op);
typedef void (*SalOnCallTerminated)(SalOp *op);
typedef void (*SalOnCallFailure)(SalOp *op, SalError error, const char *details);
int sal_listen_port(Sal *ctx, const char *addr, int port, SalTransport tr, int is_secure);
void sal_set_user_agent(Sal *ctx, const char *user_agent);
void sal_use_session_timers(Sal *ctx, int expires);
SalOp * sal_call_create(Sal *sal, const char *from, const char *to, const char *route, const char *contact);
int sal_call_set_local_media_description(SalOp *h, const SalMediaDescription *desc);
int sal_call(SalOp *h);
int sal_call_accept(SalOp*h);
int sal_call_get_final_media_description(SalOp *h, SalMediaDescription *result);
int sal_call_terminate(SalOp *h);
int sal_iterate(Sal *sal);
SalOp *sal_register_create(Sal *ctx, const char *from, const char *contact, int expires);
int sal_register(SalOp *h);
void sal_op_release(SalOp *h);
#define payload_type_set_number(pt,n) (pt)->user_data=(void*)((long)n);
#define payload_type_get_number(pt) ((int)(long)(pt)->user_data)
#endif
/*
linphone
Copyright (C) 2010 Simon MORLAT (simon.morlat@free.fr)
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 2
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "sal.h"
#include <eXosip2/eXosip.h>
extern char *media_description_to_sdp(const SalMediaDescription *sal);
struct Sal{
int running;
int session_expires;
};
struct SalOp{
int cid;
int did;
int tid;
osip_message_t *request;
};
static SalOp * sal_op_new(){
SalOp *op=ms_new(SalOp,1);
op->cid=op->did=op->tid=-1;
op->request=NULL;
return op;
}
void sal_op_release(SalOp *op){
ms_free(op);
}
Sal * sal_init(){
eXosip_init();
return ms_new0(Sal,1);
}
void sal_uninit(Sal* sal){
eXosip_quit();
ms_free(sal);
}
int sal_listen_port(Sal *ctx, const char *addr, int port, SalTransport tr, int is_secure){
int err;
bool_t ipv6;
int proto=IPPROTO_UDP;
if (ctx->running) eXosip_quit();
eXosip_init();
err=0;
eXosip_set_option(13,&err); /*13=EXOSIP_OPT_SRV_WITH_NAPTR, as it is an enum value, we can't use it unless we are sure of the
version of eXosip, which is not the case*/
/*see if it looks like an IPv6 address*/
ipv6=strchr(addr,':')!=NULL;
eXosip_enable_ipv6(ipv6);
if (tr!=SAL_TRANSPORT_DATAGRAM || is_secure){
ms_fatal("SIP over TCP or TLS or DTLS is not supported yet.");
return -1;
}
err=eXosip_listen_addr(proto, addr, port, ipv6 ? PF_INET6 : PF_INET, 0);
return err;
}
void sal_set_user_agent(Sal *ctx, const char *user_agent){
eXosip_set_user_agent(user_agent);
}
void sal_use_session_timers(Sal *ctx, int expires){
ctx->session_expires=expires;
}
SalOp * sal_call_create(Sal *sal, const char *from, const char *to, const char *route, const char *contact){
int err;
SalOp *op;
osip_message_t *invite=NULL;
err=eXosip_call_build_initial_invite(&invite,to,from,
route,"Phone call");
if (err!=0){
ms_error("Could not create call.");
return NULL;
}
if (contact)
osip_message_set_contact(invite,contact);
if (sal->session_expires!=0){
osip_message_set_header(invite, "Session-expires", "200");
osip_message_set_supported(invite, "timer");
}
op=sal_op_new();
op->request=invite;
return op;
}
static void set_sdp(osip_message_t *sip, const SalMediaDescription *desc){
int sdplen;
char clen[10];
char *sdp=media_description_to_sdp(desc);
if (sdp==NULL) {
ms_error("Fail to print sdp message !");
return;
}
sdplen=strlen(sdp);
snprintf(clen,sizeof(clen),"%i",sdplen);
osip_message_set_body(sip,sdp,sdplen);
osip_message_set_content_type(sip,"application/sdp");
osip_message_set_content_length(sip,clen);
osip_free(sdp);
}
int sal_call_set_local_media_description(SalOp *h, const SalMediaDescription *desc){
set_sdp(h->request,desc);
return 0;
}
int sal_call(SalOp *h){
int err;
eXosip_lock();
err=eXosip_call_send_initial_invite(h->request);
eXosip_unlock();
h->cid=err;
if (err<0){
ms_error("Fail to send invite !");
return -1;
}
return 0;
}
int sal_call_accept(SalOp*h);
int sal_call_get_final_media_description(SalOp *h, SalMediaDescription *result);
int sal_call_terminate(SalOp *h);
int sal_iterate(Sal *sal);
SalOp *sal_register_create(Sal *ctx, const char *from, const char *contact, int expires);
int sal_register(SalOp *h);
/*
linphone
Copyright (C) 2010 Simon MORLAT (simon.morlat@free.fr)
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 2
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "ortp/b64.h"
#include "sal.h"
#include <eXosip2/eXosip.h>
#define keywordcmp(key,b) strncmp(key,b,sizeof(key))
#ifdef FOR_LATER
static char *make_relay_session_id(const char *username, const char *relay){
/*ideally this should be a hash of the parameters with a random part*/
char tmp[128];
int s1=(int)random();
int s2=(int)random();
long long int res=((long long int)s1)<<32 | (long long int) s2;
void *src=&res;
b64_encode(src, sizeof(long long int), tmp, sizeof(tmp));
return osip_strdup(tmp);
}
static void add_relay_info(sdp_message_t *sdp, int mline, const char *relay, const char *relay_session_id){
if (relay) sdp_message_a_attribute_add(sdp, mline,
osip_strdup ("relay-addr"),osip_strdup(relay));
if (relay_session_id) sdp_message_a_attribute_add(sdp, mline,
osip_strdup ("relay-session-id"), osip_strdup(relay_session_id));
}
#endif
char * int_2char(int a){
char *p=osip_malloc(16);
snprintf(p,16,"%i",a);
return p;
}
/* return the value of attr "field" for payload pt at line pos (field=rtpmap,fmtp...)*/
char *sdp_message_a_attr_value_get_with_pt(sdp_message_t *sdp,int pos,int pt,const char *field)
{
int i,tmppt=0,scanned=0;
char *tmp;
sdp_attribute_t *attr;
for (i=0;(attr=sdp_message_attribute_get(sdp,pos,i))!=NULL;i++){
if (keywordcmp(field,attr->a_att_field)==0 && attr->a_att_value!=NULL){
int nb = sscanf(attr->a_att_value,"%i %n",&tmppt,&scanned);
/* the return value may depend on how %n is interpreted by the libc: see manpage*/
if (nb == 1 || nb==2 ){
if (pt==tmppt){
tmp=attr->a_att_value+scanned;
if (strlen(tmp)>0)
return tmp;
}
}else ms_warning("sdp has a strange a= line (%s) nb=%i",attr->a_att_value,nb);
}
}
return NULL;
}
/* return the value of attr "field" */
char *sdp_message_a_attr_value_get(sdp_message_t *sdp,int pos,const char *field)
{
int i;
sdp_attribute_t *attr;
for (i=0;(attr=sdp_message_attribute_get(sdp,pos,i))!=NULL;i++){
if (keywordcmp(field,attr->a_att_field)==0 && attr->a_att_value!=NULL){
return attr->a_att_value;
}
}
return NULL;
}
static int _sdp_message_get_a_ptime(sdp_message_t *sdp, int mline){
int i,ret;
sdp_attribute_t *attr;
for (i=0;(attr=sdp_message_attribute_get(sdp,mline,i))!=NULL;i++){
if (keywordcmp("ptime",attr->a_att_field)==0){
int nb = sscanf(attr->a_att_value,"%i",&ret);
/* the return value may depend on how %n is interpreted by the libc: see manpage*/
if (nb == 1){
return ret;
}else ms_warning("sdp has a strange a=ptime line (%s) ",attr->a_att_value);
}
}
return 0;
}
static sdp_message_t *create_generic_sdp(const SalMediaDescription *desc)
{
sdp_message_t *local;
int inet6;
sdp_message_init (&local);
if (strchr(desc->addr,':')!=NULL){
inet6=1;
}else inet6=0;
sdp_message_v_version_set (local, osip_strdup ("0"));
sdp_message_o_origin_set (local, osip_strdup (desc->username),
osip_strdup ("123456"), osip_strdup ("654321"),
osip_strdup ("IN"), inet6 ? osip_strdup("IP6") : osip_strdup ("IP4"),
osip_strdup (desc->addr));
sdp_message_s_name_set (local, osip_strdup ("A conversation"));
sdp_message_c_connection_add (local, -1,
osip_strdup ("IN"), inet6 ? osip_strdup ("IP6") : osip_strdup ("IP4"),
osip_strdup (desc->addr), NULL, NULL);
sdp_message_t_time_descr_add (local, osip_strdup ("0"), osip_strdup ("0"));
return local;
}
void add_payload(sdp_message_t *msg, int line, const PayloadType *pt)
{
char attr[256];
sdp_message_m_payload_add (msg,line, int_2char (payload_type_get_number(pt)));
snprintf (attr,sizeof(attr),"%i %s", payload_type_get_number(pt), pt->mime_type);
sdp_message_a_attribute_add (msg, line,
osip_strdup ("rtpmap"), osip_strdup(attr));
if (pt->recv_fmtp != NULL)
{
snprintf (attr,sizeof(attr),"%i %s", payload_type_get_number(pt),pt->recv_fmtp);
sdp_message_a_attribute_add (msg, line, osip_strdup ("fmtp"),
osip_strdup(attr));
}
}
static void add_line(sdp_message_t *msg, int lineno, const SalStreamDescription *desc){
const char *mt=desc->type==SAL_AUDIO ? "audio" : "video";
const MSList *elem;
sdp_message_m_media_add (msg, osip_strdup (mt),
int_2char (desc->port), NULL,
osip_strdup ("RTP/AVP"));