work in progress

This commit is contained in:
Simon Morlat 2010-02-01 18:30:15 +01:00
parent 80e14f6a90
commit 0267c3213f
8 changed files with 554 additions and 277 deletions

View file

@ -21,7 +21,6 @@ liblinphone_la_SOURCES=\
sal_eXosip2_sdp.c \
sal_eXosip2_presence.c \
callbacks.c \
exevents.c sdphandler.c\
misc.c \
address.c \
enum.c enum.h \

View file

@ -23,28 +23,301 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "linphonecore.h"
#include "private.h"
static void linphone_connect_incoming(LinphoneCore *lc, LinphoneCall *call){
if (lc->vtable.show)
lc->vtable.show(lc);
if (lc->vtable.display_status)
lc->vtable.display_status(lc,_("Connected."));
call->state=LCStateAVRunning;
if (lc->ringstream!=NULL){
ring_stop(lc->ringstream);
lc->ringstream=NULL;
}
linphone_core_start_media_streams(lc,call);
}
static void call_received(SalOp *h){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_root(h));
char *barmesg;
int err;
LinphoneCall *call;
const char *from,*to;
char *tmp;
LinphoneAddress *from_parsed;
/* first check if we can answer successfully to this invite */
if (lc->presence_mode!=LINPHONE_STATUS_ONLINE){
ms_message("Not present !! presence mode : %d\n",lc->presence_mode);
if (lc->presence_mode==LINPHONE_STATUS_BUSY)
sal_call_decline(h,SalReasonBusy,NULL);
else if (lc->presence_mode==LINPHONE_STATUS_AWAY
||lc->presence_mode==LINPHONE_STATUS_BERIGHTBACK
||lc->presence_mode==LINPHONE_STATUS_ONTHEPHONE
||lc->presence_mode==LINPHONE_STATUS_OUTTOLUNCH
||lc->presence_mode==LINPHONE_STATUS_OFFLINE)
sal_call_decline(h,SalReasonTemporarilyUnavailable,NULL);
else if (lc->presence_mode==LINPHONE_STATUS_NOT_DISTURB)
sal_call_decline(h,SalReasonTemporarilyUnavailable,NULL);
else if (lc->alt_contact!=NULL && lc->presence_mode==LINPHONE_STATUS_MOVED)
sal_call_decline(h,SalReasonRedirect,lc->alt_contact);
else
sal_call_decline(h,SalReasonBusy,NULL);
sal_op_release(op);
return;
}
if (lc->call!=NULL){/*busy*/
sal_call_decline(h,SalReasonBusy,NULL);
sal_op_release(op);
return;
}
from=sal_op_get_from(op);
to=sal_op_get_to(op);
call=linphone_call_new_incoming(lc,linphone_address_new(from),linphone_address_new(to),op);
lc->call=call;
sal_call_set_local_media_description(op,call->localdesc);
call->resultdesc=sal_call_get_final_media_description(op);
if (call->resultdesc && sal_media_description_empty(call->resultdesc){
sal_call_decline(op,SalReasonMedia,NULL);
linphone_call_destroy(call);
lc->call=NULL;
return;
}
from_parsed=linphone_address_new(sal_op_get_from(op));
linphone_address_clean(from_parsed);
tmp=linphone_address_as_string(from_parsed);
linphone_address_destroy(from_parsed);
gstate_new_state(lc, GSTATE_CALL_IN_INVITE, tmp);
barmesg=ortp_strdup_printf("%s %s",tmp,_("is contacting you."));
if (lc->vtable.show) lc->vtable.show(lc);
if (lc->vtable.display_status)
lc->vtable.display_status(lc,barmesg);
/* play the ring */
if (lc->sound_conf.ring_sndcard!=NULL){
ms_message("Starting local ring...");
lc->ringstream=ring_start(lc->sound_conf.local_ring,2000,lc->sound_conf.ring_sndcard);
}
linphone_call_set_state(call,LCStateRinging);
sal_call_notify_ringing(op);
if (lc->vtable.inv_recv) lc->vtable.inv_recv(lc,tmp);
ms_free(barmesg);
ms_free(tmp);
}
static void call_ringing(SalOp *h){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_root(h));
LinphoneCall *call=lc->call;
SalMediaDescription *md;
if (call==NULL) return;
if (lc->vtable.display_status)
lc->vtable.display_status(lc,_("Remote ringing."));
md=sal_call_get_final_media_description(h);
if (md==NULL){
if (lc->ringstream!=NULL) return; /*already ringing !*/
if (lc->sound_conf.play_sndcard!=NULL){
ms_message("Remote ringing...");
lc->ringstream=ring_start(lc->sound_conf.remote_ring,2000,lc->sound_conf.play_sndcard);
}
}else{
/*accept early media */
if (lc->audiostream->ticker!=NULL){
/*streams already started */
ms_message("Early media already started.");
return;
}
sal_media_description_ref(md);
call->resultdesc=md;
if (lc->vtable.show) lc->vtable.show(lc);
if (lc->vtable.display_status)
lc->vtable.display_status(lc,_("Early media."));
gstate_new_state(lc, GSTATE_CALL_OUT_CONNECTED, NULL);
if (lc->ringstream!=NULL){
ring_stop(lc->ringstream);
lc->ringstream=NULL;
}
ms_message("Doing early media...");
linphone_core_start_media_streams(lc,call);
}
call->state=LCStateRinging;
}
static void call_accepted(SalOp *h){
static void call_accepted(SalOp *op){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_root(h));
LinphoneCall *call=lc->call;
if (call==NULL){
ms_warning("No call to accept.");
return 0;
}
if (sal_op_get_user_pointer(op)!=lc->call){
ms_warning("call_accepted: ignoring.");
return;
}
if (call->state==LCStateAVRunning){
return 0; /*already accepted*/
}
if (lc->audiostream->ticker!=NULL){
/*case where we accepted early media */
linphone_core_stop_media_streams(lc,call);
linphone_core_init_media_streams(lc,call);
}
if (call->resultdesc)
sal_media_description_unref(call->resultdesc);
call->resultdesc=sal_call_get_final_media_description(op);
if (call->resultdesc && !sal_media_description_empty(call->resultdesc)){
gstate_new_state(lc, GSTATE_CALL_OUT_CONNECTED, NULL);
linphone_connect_incoming(lc,call);
}else{
/*send a bye*/
ms_error("Incompatible SDP offer received in 200Ok, need to abort the call");
linphone_core_terminate_call(lc,NULL);
}
}
static void call_ack(SalOp *h){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_root(h));
LinphoneCall *call=lc->call;
if (call==NULL){
ms_warning("No call to be ACK'd");
return ;
}
if (sal_op_get_user_pointer(op)!=lc->call){
ms_warning("call_ack: ignoring.");
return;
}
if (lc->audiostream->ticker!=NULL){
/*case where we accepted early media */
linphone_core_stop_media_streams(lc,call);
linphone_core_init_media_streams(lc,call);
}
if (call->resultdesc)
sal_media_description_unref(call->resultdesc);
call->resultdesc=sal_call_get_final_media_description(op);
if (call->resultdesc && !sal_media_description_empty(call->resultdesc)){
gstate_new_state(lc, GSTATE_CALL_IN_CONNECTED, NULL);
linphone_connect_incoming(lc,call);
}else{
/*send a bye*/
ms_error("Incompatible SDP response received in ACK, need to abort the call");
linphone_core_terminate_call(lc,NULL);
}
}
static void call_updated(SalOp *){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_root(h));
linphone_core_stop_media_streams(lc,call);
linphone_core_init_media_streams(lc,call);
if (call->resultdesc)
sal_media_description_unref(call->resultdesc);
call->resultdesc=sal_call_get_final_media_description(op);
if (call->resultdesc && !sal_media_description_empty(call->resultdesc)){
linphone_connect_incoming(lc,call);
}
}
static void call_terminated(SalOp *h){
static void call_terminated(SalOp *h, const char *from){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_root(h));
if (sal_op_get_user_pointer(op)!=lc->call){
ms_warning("call_terminated: ignoring.");
return;
}
ms_message("Current call terminated...");
if (lc->ringstream!=NULL) {
ring_stop(lc->ringstream);
lc->ringstream=NULL;
}
linphone_core_stop_media_streams(lc,lc->call);
lc->vtable.show(lc);
lc->vtable.display_status(lc,_("Call terminated."));
gstate_new_state(lc, GSTATE_CALL_END, NULL);
if (lc->vtable.bye_recv!=NULL){
LinphoneAddress *addr=linphone_address_new(from);
char *tmp;
linphone_address_clean(addr);
tmp=linphone_address_as_string(from);
lc->vtable.bye_recv(lc,tmp);
ms_free(tmp);
linphone_address_destroy(addr);
}
linphone_call_destroy(lc->call);
lc->call=NULL;
}
static void call_failure(SalOp *h, SalError error, SalReason reason, const char *details){
static void call_failure(SalOp *op, SalError error, SalReason sr, const char *details){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_root(h));
const char *reason="";
char *msg486=_("User is busy.");
char *msg480=_("User is temporarily unavailable.");
/*char *retrymsg=_("%s. Retry after %i minute(s).");*/
char *msg600=_("User does not want to be disturbed.");
char *msg603=_("Call declined.");
char* tmpmsg=msg486;
int code;
LinphoneCall *call=lc->call;
if (sal_op_get_user_pointer(op)!=lc->call){
ms_warning("call_failure: ignoring.");
return;
}
if (lc->vtable.show) lc->vtable.show(lc);
if (error==SalErrorNoResponse){
if (lc->vtale.display_status)
lc->vtable.display_status(lc,_("No response."));
}else if (error==SalErrorProtocol){
if (lc->vtale.display_status)
lc->vtable.display_status(lc, details ? details : _("Error."));
}else if (error==SalErrorFailure){
switch(sr){
case SalReasonDeclined:
if (lc->vtable.display_status)
lc->vtable.display_status(lc,msg603);
break;
case SalReasonBusy:
if (lc->vtable.display_status)
lc->vtable.display_status(lc,msg486);
break;
case SalReasonRedirect:
if (lc->vtable.display_status)
lc->vtable.display_status(lc,_("Redirected"));
break;
case SalReasonTemporarilyUnavailable:
if (lc->vtable.display_status)
lc->vtable.display_status(lc,msg480);
break;
case SalReasonNotFound:
if (lc->vtable.display_status)
lc->vtable.display_status(lc,msg404);
break;
case SalReasonDoNotDisturb:
if (lc->vtable.display_status)
lc->vtable.display_status(lc,msg600);
break;
case SalReasonMedia:
if (lc->vtable.display_status)
lc->vtable.display_status(lc,_("No common codecs"));
break;
default:
if (lc->vtable.display_status)
lc->vtable.display_status(lc,_("Call failed."));
}
}
if (lc->ringstream!=NULL) {
ring_stop(lc->ringstream);
lc->ringstream=NULL;
}
linphone_core_stop_media_streams(lc);
if (call!=NULL) {
linphone_call_destroy(call);
gstate_new_state(lc, GSTATE_CALL_ERROR, NULL);
lc->call=NULL;
}
}
static void auth_requested(SalOp *h, const char *realm, const char *username){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_root(h));
LinphoneAuthInfo *ai=linphone_core_find_auth_info(lc);
}
static void auth_success(SalOp *h, const char *realm, const char *username){

View file

@ -115,7 +115,6 @@ static void linphone_call_init_common(LinphoneCall *call, LinphoneAddress *from,
linphone_core_notify_all_friends(call->core,LINPHONE_STATUS_ONTHEPHONE);
if (linphone_core_get_firewall_policy(call->core)==LINPHONE_POLICY_USE_STUN)
linphone_core_run_stun_tests(call->core,call);
call->profile=rtp_profile_new("Call RTP profile");
}
static void discover_mtu(LinphoneCore *lc, const char *remote){
@ -136,6 +135,7 @@ LinphoneCall * linphone_call_new_outgoing(struct _LinphoneCore *lc, LinphoneAddr
LinphoneCall *call=ms_new0(LinphoneCall,1);
call->dir=LinphoneCallOutgoing;
call->op=sal_op_new(lc->sal);
sal_op_set_user_pointer(call->op,lc->call);
call->core=lc;
linphone_core_get_local_ip(lc,linphone_address_get_domain(to),call->localip);
call->localdesc=create_local_media_description (lc,call->localip,
@ -150,6 +150,7 @@ LinphoneCall * linphone_call_new_incoming(LinphoneCore *lc, LinphoneAddress *fro
LinphoneAddress *me=linphone_core_get_primary_contact_parsed(lc);
call->dir=LinphoneCallIncoming;
sal_op_set_user_pointer(op,call);
call->op=op;
call->core=lc;
@ -168,7 +169,6 @@ void linphone_call_destroy(LinphoneCall *obj)
linphone_core_notify_all_friends(obj->core,obj->core->prev_mode);
linphone_call_log_completed(obj->log,obj);
linphone_core_update_allocated_audio_bandwidth(obj->core);
if (obj->profile!=NULL) rtp_profile_destroy(obj->profile);
if (obj->op!=NULL) sal_op_release(obj->op);
if (obj->resultdesc!=NULL) sal_media_description_unref(obj->resultdesc);
if (obj->localdesc!=NULL) sal_media_description_unref(obj->localdesc);
@ -910,6 +910,7 @@ static void linphone_core_init (LinphoneCore * lc, const LinphoneCoreVTable *vta
lc->rpc_enable = 0;
#endif
lc->sal=sal_init();
sal_set_user_pointer(lc->sal,lc);
if (lp_config_get_int(lc->config,"sip","use_session_timers",0)==1){
sal_use_session_timers(lc->sal,200);
}
@ -1313,7 +1314,7 @@ void linphone_core_set_sip_port(LinphoneCore *lc,int port)
ms_free(msg);
return;
}
apply_user_agent(lc->sal);
apply_user_agent(lc);
}
/**
@ -1368,7 +1369,7 @@ static void proxy_update(LinphoneCore *lc, time_t curtime){
if (last_check==0 || (curtime-last_check)>=5){
sal_get_default_local_ip(lc->sal,
lc->sip_conf.ipv6_enabled ? AF_INET6 : AF_INET,
,result,LINPHONE_IPADDR_SIZE);
result,LINPHONE_IPADDR_SIZE);
if (strcmp(result,"::1")!=0 && strcmp(result,"127.0.0.1")!=0){
last_status=TRUE;
ms_message("Network is up, registering now (%s)",result);
@ -1465,14 +1466,12 @@ static void linphone_core_do_plugin_tasks(LinphoneCore *lc){
* other liblinphone methods. In not the case make sure all liblinphone calls are
* serialized with a mutex.
**/
void linphone_core_iterate(LinphoneCore *lc)
{
eXosip_event_t *ev;
bool_t disconnected=FALSE;
void linphone_core_iterate(LinphoneCore *lc){
int disconnect_timeout = linphone_core_get_nortp_timeout(lc);
time_t curtime=time(NULL);
int elapsed;
bool_t one_second_elapsed=FALSE;
bool_t disconnected=FALSE;
if (curtime-lc->prevtime>=1){
lc->prevtime=curtime;
@ -1539,7 +1538,7 @@ void linphone_core_iterate(LinphoneCore *lc)
bool_t linphone_core_interpret_url(LinphoneCore *lc, const char *url, LinphoneAddress **real_parsed_url, char **route){
enum_lookup_res_t *enumres=NULL;
osip_to_t *parsed_url=NULL;
LinphoneAddress *parsed_url=NULL;
char *enum_domain=NULL;
LinphoneProxyConfig *proxy;
char *tmpurl;
@ -1590,7 +1589,7 @@ bool_t linphone_core_interpret_url(LinphoneCore *lc, const char *url, LinphoneAd
if (real_parsed_url!=NULL) *real_parsed_url=parsed_url;
else linphone_address_destroy(parsed_url);
if (tmproute) *route=ms_strdup(tmproute);
else *route=guess_route_if_any(lc,*real_parsed_url);
return TRUE;
}
/* else we could not do anything with url given by user, so display an error */
@ -1742,9 +1741,9 @@ int linphone_core_invite(LinphoneCore *lc, const char *url)
if (err<0){
ms_warning("Could not initiate call.");
lc->vtable.display_status(lc,_("could not call"));
linphone_core_stop_media_streams(lc,call);
linphone_call_destroy(call);
lc->call=NULL;
linphone_core_stop_media_streams(lc);
}else gstate_new_state(lc, GSTATE_CALL_OUT_INVITE, url);
goto end;
@ -1759,7 +1758,6 @@ int linphone_core_refer(LinphoneCore *lc, const char *url)
char *real_url=NULL;
LinphoneAddress *real_parsed_url=NULL;
LinphoneCall *call;
osip_message_t *msg=NULL;
char *route;
if (!linphone_core_interpret_url(lc,url,&real_parsed_url, &route)){
/* bad url */
@ -1773,11 +1771,8 @@ int linphone_core_refer(LinphoneCore *lc, const char *url)
}
lc->call=NULL;
real_url=linphone_address_as_string (real_parsed_url);
eXosip_call_build_refer(call->did, real_url, &msg);
sal_refer(call->op,real_url);
ms_free(real_url);
eXosip_lock();
eXosip_call_send_request(call->did, msg);
eXosip_unlock();
return 0;
}
@ -1793,79 +1788,6 @@ bool_t linphone_core_inc_invite_pending(LinphoneCore*lc){
return FALSE;
}
#ifdef VINCENT_MAURY_RSVP
/* on=1 for RPC_ENABLE=1...*/
int linphone_core_set_rpc_mode(LinphoneCore *lc, int on)
{
if (on==1)
printf("RPC_ENABLE set on\n");
else
printf("RPC_ENABLE set off\n");
lc->rpc_enable = (on==1);
/* need to tell eXosip the new setting */
if (eXosip_set_rpc_mode (lc->rpc_enable)!=0)
return -1;
return 0;
}
/* on=1 for RSVP_ENABLE=1...*/
int linphone_core_set_rsvp_mode(LinphoneCore *lc, int on)
{
if (on==1)
printf("RSVP_ENABLE set on\n");
else
printf("RSVP_ENABLE set off\n");
lc->rsvp_enable = (on==1);
/* need to tell eXosip the new setting */
if (eXosip_set_rsvp_mode (lc->rsvp_enable)!=0)
return -1;
return 0;
}
/* answer : 1 for yes, 0 for no */
int linphone_core_change_qos(LinphoneCore *lc, int answer)
{
char *sdpmesg;
if (lc->call==NULL){
return -1;
}
if (lc->rsvp_enable && answer==1)
{
/* answer is yes, local setting is with qos, so
* the user chose to continue with no qos ! */
/* so switch in normal mode : ring and 180 */
lc->rsvp_enable = 0; /* no more rsvp */
eXosip_set_rsvp_mode (lc->rsvp_enable);
/* send 180 */
eXosip_lock();
eXosip_answer_call(lc->call->did,180,NULL);
eXosip_unlock();
/* play the ring */
ms_message("Starting local ring...");
lc->ringstream=ring_start(lc->sound_conf.local_ring,
2000,ms_snd_card_manager_get_card(ms_snd_card_manager_get(),lc->sound_conf.ring_sndcard));
}
else if (!lc->rsvp_enable && answer==1)
{
/* switch to QoS mode on : answer 183 session progress */
lc->rsvp_enable = 1;
eXosip_set_rsvp_mode (lc->rsvp_enable);
/* take the sdp already computed, see osipuacb.c */
sdpmesg=lc->call->sdpctx->answerstr;
eXosip_lock();
eXosip_answer_call_with_body(lc->call->did,183,"application/sdp",sdpmesg);
eXosip_unlock();
}
else
{
/* decline offer (603) */
linphone_core_terminate_call(lc, NULL);
}
return 0;
}
#endif
void linphone_core_init_media_streams(LinphoneCore *lc, LinphoneCall *call){
SalMediaDescription *md=call->localdesc;
lc->audiostream=audio_stream_new(md->streams[0].port,linphone_core_ipv6_enabled(lc));
@ -1893,7 +1815,7 @@ void linphone_core_init_media_streams(LinphoneCore *lc, LinphoneCall *call){
rtp_session_set_transports(lc->audiostream->session,lc->a_rtp,lc->a_rtcp);
#ifdef VIDEO_ENABLED
if (lc->video_conf.display || lc->video_conf.capture && md->streams[1].port>0)
if ((lc->video_conf.display || lc->video_conf.capture) && md->streams[1].port>0)
lc->videostream=video_stream_new(md->streams[1].port,linphone_core_ipv6_enabled(lc));
#else
lc->videostream=NULL;
@ -1973,99 +1895,129 @@ static void post_configure_audio_streams(LinphoneCore *lc){
}
}
static RtpProfile *make_profile(LinphoneCore *lc, SalStreamDescription *desc, int *used_pt){
int bw;
MSList *elem;
RtpProfile *prof=rtp_profile_new("Call profile");
bool_t first=TRUE;
if (desc->type==SalAudio){
bw=get_min_bandwidth(lc->up_audio_bw,desc->bandwidth);
}
else bw=get_min_bandwidth(lc->up_video_bw,desc->bandwidth);
for(elem=desc->payloads;elem!=NULL;elem=elem->next){
PayloadType *pt=(PayloadType*)elem->data;
if (bw>0) pt->normal_bitrate=bw*1000;
else if (desc->type==SalAudio){
pt->normal_bitrate=-1;
}
if (first) {
*used_pt=payload_type_get_number(pt);
first=FALSE;
}
if (desc->ptime>0){
char tmp[40];
snprintf(tmp,sizeof(tmp),"ptime=%i",desc->ptime);
payload_type_append_send_fmtp(pt,tmp);
}
rtp_profile_set_payload(prof,payload_type_get_number(pt),pt);
}
return prof;
}
void linphone_core_start_media_streams(LinphoneCore *lc, LinphoneCall *call){
LinphoneAddress *me=linphone_core_get_primary_contact_parsed(lc);
const char *tool="linphone-" LINPHONE_VERSION;
char *cname;
int used_pt=-1;
/* adjust rtp jitter compensation. It must be at least the latency of the sound card */
int jitt_comp=MAX(lc->sound_conf.latency,lc->rtp_conf.audio_jitt_comp);
if (call->media_start_time==0) call->media_start_time=time(NULL);
cname=ortp_strdup_printf("%s@%s",me->url->username,me->url->host);
cname=linphone_address_as_string_uri_only(me);
{
StreamParams *audio_params=&call->audio_params;
if (!lc->use_files){
MSSndCard *playcard=lc->sound_conf.play_sndcard;
MSSndCard *captcard=lc->sound_conf.capt_sndcard;
if (playcard==NULL) {
ms_warning("No card defined for playback !");
goto end;
SalStreamDescription *stream=sal_media_description_find_stream(call->resultdesc,
SalProtoRtpAvp,SalAudio);
if (stream){
call->audio_profile=make_profile(lc,stream,&used_pt);
if (!lc->use_files){
MSSndCard *playcard=lc->sound_conf.play_sndcard;
MSSndCard *captcard=lc->sound_conf.capt_sndcard;
if (playcard==NULL) {
ms_warning("No card defined for playback !");
goto end;
}
if (captcard==NULL) {
ms_warning("No card defined for capture !");
goto end;
}
audio_stream_start_now(
lc->audiostream,
call->audio_profile,
stream->addr[0]!='\0' ? stream->addr : call->resultdesc->addr,
stream->port,
stream->port+1,
used_pt,
jitt_comp,
playcard,
captcard,
linphone_core_echo_cancellation_enabled(lc));
}else{
audio_stream_start_with_files(
lc->audiostream,
call->audio_profile,
stream->addr[0]!='\0' ? stream->addr : call->resultdesc->addr,
stream->port,
stream->port+1,
used_pt,
100,
lc->play_file,
lc->rec_file);
}
if (captcard==NULL) {
ms_warning("No card defined for capture !");
goto end;
}
if (audio_params->relay_session_id!=NULL)
audio_stream_set_relay_session_id(lc->audiostream,audio_params->relay_session_id);
audio_stream_start_now(
lc->audiostream,
call->profile,
audio_params->remoteaddr,
audio_params->remoteport,
audio_params->remotertcpport,
audio_params->pt,
jitt_comp,
playcard,
captcard,
linphone_core_echo_cancellation_enabled(lc));
}else{
audio_stream_start_with_files(
lc->audiostream,
call->profile,
audio_params->remoteaddr,
audio_params->remoteport,
audio_params->remotertcpport,
audio_params->pt,
100,
lc->play_file,
lc->rec_file);
}
post_configure_audio_streams(lc);
audio_stream_set_rtcp_information(lc->audiostream, cname, tool);
post_configure_audio_streams(lc);
audio_stream_set_rtcp_information(lc->audiostream, cname, tool);
}else ms_warning("No audio stream defined ?");
}
#ifdef VIDEO_ENABLED
{
SalStreamDescription *stream=sal_media_description_find_stream(call->resultdesc,
SalProtoRtpAvp,SalVideo);
/* shutdown preview */
if (lc->previewstream!=NULL) {
video_preview_stop(lc->previewstream);
lc->previewstream=NULL;
}
if (lc->video_conf.display || lc->video_conf.capture) {
StreamParams *video_params=&call->video_params;
if (video_params->remoteport>0){
if (video_params->relay_session_id!=NULL)
video_stream_set_relay_session_id(lc->videostream,video_params->relay_session_id);
video_stream_set_sent_video_size(lc->videostream,linphone_core_get_preferred_video_size(lc));
video_stream_enable_self_view(lc->videostream,lc->video_conf.selfview);
if (lc->video_conf.display && lc->video_conf.capture)
video_stream_start(lc->videostream,
call->profile, video_params->remoteaddr, video_params->remoteport,
video_params->remotertcpport,
video_params->pt, jitt_comp, lc->video_conf.device);
else if (lc->video_conf.display)
video_stream_recv_only_start(lc->videostream,
call->profile, video_params->remoteaddr, video_params->remoteport,
video_params->pt, jitt_comp);
else if (lc->video_conf.capture)
video_stream_send_only_start(lc->videostream,
call->profile, video_params->remoteaddr, video_params->remoteport,
video_params->remotertcpport,
video_params->pt, jitt_comp, lc->video_conf.device);
video_stream_set_rtcp_information(lc->videostream, cname,tool);
}
if (stream && (lc->video_conf.display || lc->video_conf.capture)) {
const char *addr=stream->addr[0]!='\0' ? stream->addr : call->resultdesc->addr;
call->video_profile=make_profile(lc,stream,&used_pt);
video_stream_set_sent_video_size(lc->videostream,linphone_core_get_preferred_video_size(lc));
video_stream_enable_self_view(lc->videostream,lc->video_conf.selfview);
if (lc->video_conf.display && lc->video_conf.capture)
video_stream_start(lc->videostream,
call->video_profile, addr, stream->port,
stream->port+1,
used_pt, jitt_comp, lc->video_conf.device);
else if (lc->video_conf.display)
video_stream_recv_only_start(lc->videostream,
call->video_profile, addr, stream->port,
used_pt, jitt_comp);
else if (lc->video_conf.capture)
video_stream_send_only_start(lc->videostream,
call->video_profile, addr, stream->port,
stream->port+1,
used_pt, jitt_comp, lc->video_conf.device);
video_stream_set_rtcp_information(lc->videostream, cname,tool);
}
}
#endif
goto end;
end:
ms_free(cname);
linphone_address_destroy(me);
lc->call->state=LCStateAVRunning;
ms_free(cname);
linphone_address_destroy(me);
lc->call->state=LCStateAVRunning;
}
void linphone_core_stop_media_streams(LinphoneCore *lc){
void linphone_core_stop_media_streams(LinphoneCore *lc, LinphoneCall *call){
if (lc->audiostream!=NULL) {
audio_stream_stop(lc->audiostream);
lc->audiostream=NULL;
@ -2086,6 +2038,14 @@ void linphone_core_stop_media_streams(LinphoneCore *lc){
}
}
#endif
if (call->audio_profile){
rtp_profile_destroy(call->audio_profile);
call->audio_profile=NULL;
}
if (call->video_profile){
rtp_profile_destroy(call->video_profile);
call->video_profile=NULL;
}
}
/**
@ -2104,17 +2064,14 @@ void linphone_core_stop_media_streams(LinphoneCore *lc){
**/
int linphone_core_accept_call(LinphoneCore *lc, const char *url)
{
char *sdpmesg;
osip_message_t *msg=NULL;
LinphoneCall *call=lc->call;
int err;
bool_t offering=FALSE;
const char *contact=NULL;
if (call==NULL){
return -1;
}
if (lc->call->state==LCStateAVRunning){
if (call->state==LCStateAVRunning){
/*call already accepted*/
return -1;
}
@ -2126,42 +2083,22 @@ int linphone_core_accept_call(LinphoneCore *lc, const char *url)
ms_message("ring stopped");
lc->ringstream=NULL;
}
/* sends a 200 OK */
err=eXosip_call_build_answer(call->tid,200,&msg);
if (err<0 || msg==NULL){
ms_error("Fail to build answer for call: err=%i",err);
return -1;
}
if (lp_config_get_int(lc->config,"sip","use_session_timers",0)==1){
if (call->supports_session_timers) osip_message_set_supported(msg, "timer");
}
/*try to be best-effort in giving real local or routable contact address,
except when the user choosed to override the ipaddress */
if (linphone_core_get_firewall_policy(lc)!=LINPHONE_POLICY_USE_NAT_ADDRESS)
fix_contact(lc,msg,call->localip,NULL);
/*if a sdp answer is computed, send it, else send an offer */
sdpmesg=call->sdpctx->answerstr;
if (sdpmesg==NULL){
offering=TRUE;
ms_message("generating sdp offer");
sdpmesg=sdp_context_get_offer(call->sdpctx);
if (sdpmesg==NULL){
ms_error("fail to generate sdp offer !");
return -1;
}
linphone_set_sdp(msg,sdpmesg);
linphone_core_init_media_streams(lc);
}else{
linphone_set_sdp(msg,sdpmesg);
}
eXosip_lock();
eXosip_call_send_answer(call->tid,200,msg);
eXosip_unlock();
contact=get_fixed_contact(lc,call->localip,NULL);
if (contact)
sal_op_set_contact(call->op,contact);
sal_call_accept(call->op);
lc->vtable.display_status(lc,_("Connected."));
gstate_new_state(lc, GSTATE_CALL_IN_CONNECTED, NULL);
if (!offering) linphone_core_start_media_streams(lc, lc->call);
call->resultdesc=sal_call_get_final_media_description(call->op);
if (call->resultdesc){
sal_media_description_ref(call->resultdesc);
linphone_core_start_media_streams(lc, call);
}
ms_message("call answered.");
return 0;
}
@ -2181,17 +2118,14 @@ int linphone_core_terminate_call(LinphoneCore *lc, const char *url)
return -1;
}
lc->call=NULL;
eXosip_lock();
eXosip_call_terminate(call->cid,call->did);
eXosip_unlock();
sal_call_terminate(call->op);
/*stop ringing*/
if (lc->ringstream!=NULL) {
ring_stop(lc->ringstream);
lc->ringstream=NULL;
}
linphone_core_stop_media_streams(lc);
linphone_core_stop_media_streams(lc,call);
lc->vtable.display_status(lc,_("Call ended") );
gstate_new_state(lc, GSTATE_CALL_END, NULL);
linphone_call_destroy(call);
@ -2243,22 +2177,13 @@ void linphone_core_set_presence_info(LinphoneCore *lc,int minutes_away,
const char *contact,
LinphoneOnlineStatus presence_mode)
{
int contactok=-1;
if (minutes_away>0) lc->minutes_away=minutes_away;
if (contact!=NULL) {
osip_from_t *url;
osip_from_init(&url);
contactok=osip_from_parse(url,contact);
if (contactok>=0) {
ms_message("contact url is correct.");
}
osip_from_free(url);
}
if (contactok>=0){
if (lc->alt_contact!=NULL) ms_free(lc->alt_contact);
lc->alt_contact=ms_strdup(contact);
if (lc->alt_contact!=NULL) {
ms_free(lc->alt_contact);
lc->alt_contact=NULL;
}
if (contact) lc->alt_contact=ms_strdup(contact);
if (lc->presence_mode!=presence_mode){
linphone_core_notify_all_friends(lc,presence_mode);
/*
@ -2270,7 +2195,6 @@ void linphone_core_set_presence_info(LinphoneCore *lc,int minutes_away,
}
lc->prev_mode=lc->presence_mode;
lc->presence_mode=presence_mode;
}
LinphoneOnlineStatus linphone_core_get_presence_info(const LinphoneCore *lc){
@ -2642,7 +2566,7 @@ bool_t linphone_core_agc_enabled(const LinphoneCore *lc){
* @param dtmf The dtmf name specified as a char, such as '0', '#' etc...
*
**/
void linphone_core_send_dtmf(LinphoneCore *lc,char dtmf)
void linphone_core_send_dtmf(LinphoneCore *lc, char dtmf)
{
/*By default we send DTMF RFC2833 if we do not have enabled SIP_INFO but we can also send RFC2833 and SIP_INFO*/
if (linphone_core_get_use_rfc2833_for_dtmf(lc)!=0 || linphone_core_get_use_info_for_dtmf(lc)==0)
@ -2656,26 +2580,13 @@ void linphone_core_send_dtmf(LinphoneCore *lc,char dtmf)
ms_error("we cannot send RFC2833 dtmf when we are not in communication");
}
}
if (linphone_core_get_use_info_for_dtmf(lc)!=0)
{
char dtmf_body[1000];
char clen[10];
osip_message_t *msg=NULL;
if (linphone_core_get_use_info_for_dtmf(lc)!=0){
/* Out of Band DTMF (use INFO method) */
LinphoneCall *call=lc->call;
if (call==NULL){
return;
}
eXosip_call_build_info(call->did,&msg);
snprintf(dtmf_body, 999, "Signal=%c\r\nDuration=250\r\n", dtmf);
osip_message_set_body(msg,dtmf_body,strlen(dtmf_body));
osip_message_set_content_type(msg,"application/dtmf-relay");
snprintf(clen,sizeof(clen),"%lu",(unsigned long)strlen(dtmf_body));
osip_message_set_content_length(msg,clen);
eXosip_lock();
eXosip_call_send_request(call->did,msg);
eXosip_unlock();
sal_call_send_dtmf(call->op,dtmf);
}
}
@ -2742,7 +2653,7 @@ static void apply_nat_settings(LinphoneCore *lc){
if (err!=0){
wmsg=ortp_strdup_printf(_("Invalid nat address '%s' : %s"),
addr, gai_strerror(err));
ms_warning(wmsg); // what is this for ?
ms_warning("%s",wmsg); // what is this for ?
lc->vtable.display_warning(lc, wmsg);
ms_free(wmsg);
ms_free(tmp);
@ -2756,22 +2667,16 @@ static void apply_nat_settings(LinphoneCore *lc){
if (lc->net_conf.firewall_policy==LINPHONE_POLICY_USE_NAT_ADDRESS){
if (tmp!=NULL){
if (!lc->net_conf.nat_sdp_only){
eXosip_set_option(EXOSIP_OPT_SET_IPV4_FOR_GATEWAY,tmp);
/* the following does not work in all cases */
/*
eXosip_masquerade_contact(tmp,lc->sip_conf.sip_port);
*/
sal_masquerade(lc->sal,tmp);
}
ms_free(tmp);
}
else{
eXosip_set_option(EXOSIP_OPT_SET_IPV4_FOR_GATEWAY,NULL);
eXosip_masquerade_contact("",0);
sal_masquerade(lc->sal,NULL);
}
}
else {
eXosip_set_option(EXOSIP_OPT_SET_IPV4_FOR_GATEWAY,NULL);
eXosip_masquerade_contact("",0);
sal_masquerade(lc->sal,NULL);
}
}
@ -3197,23 +3102,17 @@ void sip_config_uninit(LinphoneCore *lc)
linphone_proxy_config_edit(cfg); /* to unregister */
}
if (exosip_running)
{
if (lc->sal){
int i;
for (i=0;i<20;i++)
{
eXosip_event_t *ev;
while((ev=eXosip_event_wait(0,0))!=NULL){
linphone_core_process_event(lc,ev);
}
eXosip_automatic_action();
for (i=0;i<20;i++){
sal_iterate(lc->sal);
#ifndef WIN32
usleep(100000);
usleep(100000);
#else
Sleep(100);
Sleep(100);
#endif
}
}
}
}
linphone_proxy_config_write_to_config_file(lc->config,NULL,i); /*mark the end */
@ -3222,6 +3121,8 @@ void sip_config_uninit(LinphoneCore *lc)
linphone_auth_info_write_config(lc->config,ai,i);
}
linphone_auth_info_write_config(lc->config,NULL,i); /* mark the end */
sal_uninit(lc->sal);
lc->sal=NULL;
}
void rtp_config_uninit(LinphoneCore *lc)
@ -3341,7 +3242,6 @@ static void linphone_core_uninit(LinphoneCore *lc)
linphone_core_free_payload_types();
ortp_exit();
eXosip_quit();
exosip_running=FALSE;
gstate_new_state(lc, GSTATE_POWER_OFF, NULL);
}

View file

@ -67,7 +67,8 @@ typedef struct _LinphoneCall
SalMediaDescription *localdesc;
SalMediaDescription *resultdesc;
LinphoneCallDir dir;
struct _RtpProfile *profile; /*points to the local_profile or to the remote "guessed" profile*/
struct _RtpProfile *audio_profile;
struct _RtpProfile *video_profile;
struct _LinphoneCallLog *log;
SalOp *op;
char localip[LINPHONE_IPADDR_SIZE]; /* our best guess for local ipaddress for this call */
@ -87,7 +88,7 @@ void linphone_call_log_completed(LinphoneCallLog *calllog, LinphoneCall *call);
void linphone_call_log_destroy(LinphoneCallLog *cl);
void linphone_core_init_media_streams(LinphoneCore *lc);
void linphone_core_init_media_streams(LinphoneCore *lc, LinphoneCall *call);
void linphone_auth_info_write_config(struct _LpConfig *config, LinphoneAuthInfo *obj, int pos);
@ -112,8 +113,8 @@ bool_t host_has_ipv6_network();
bool_t lp_spawn_command_line_sync(const char *command, char **result,int *command_ret);
static inline int get_min_bandwidth(int dbw, int ubw){
if (dbw<0) return ubw;
if (ubw<0) return dbw;
if (dbw<=0) return ubw;
if (ubw<=0) return dbw;
return MIN(dbw,ubw);
}
@ -165,7 +166,7 @@ int linphone_proxy_config_normalize_number(LinphoneProxyConfig *cfg, const char
/*internal use only */
void linphone_core_start_media_streams(LinphoneCore *lc, struct _LinphoneCall *call);
void linphone_core_stop_media_streams(LinphoneCore *lc);
void linphone_core_stop_media_streams(LinphoneCore *lc, struct _LinphoneCall *call);
const char * linphone_core_get_identity(LinphoneCore *lc);
const char * linphone_core_get_route(LinphoneCore *lc);
bool_t linphone_core_interpret_url(LinphoneCore *lc, const char *url, LinphoneAddress **real_parsed_url, char **route);

View file

@ -28,6 +28,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
SalMediaDescription *sal_media_description_new(){
SalMediaDescription *md=ms_new0(SalMediaDescription,1);
md->refcount=1;
return md;
}
static void sal_media_description_destroy(SalMediaDescription *md){
@ -60,6 +61,14 @@ SalStreamDescription *sal_media_description_find_stream(SalMediaDescription *md,
return NULL;
}
bool_t sal_media_description_empty(SalMediaDescription *md){
int i;
for(i=0;i<md->nstreams;++i){
SalStreamDescription *ss=&md->streams[i];
if (ss->port!=0) return FALSE;
}
return TRUE;
}
static void assign_string(char **str, const char *arg){
if (*str){

View file

@ -62,6 +62,8 @@ void sal_address_destroy(SalAddress *u);
Sal * sal_init();
void sal_uninit(Sal* sal);
void sal_set_user_pointer(Sal *sal, void *user_data);
void *sal_get_user_pointer(const Sal *sal);
typedef enum {
SalTransportDatagram,
@ -103,6 +105,7 @@ typedef struct SalMediaDescription{
SalMediaDescription *sal_media_description_new();
void sal_media_description_ref(SalMediaDescription *md);
void sal_media_description_unref(SalMediaDescription *md);
bool_t sal_media_description_empty(SalMediaDescription *md);
SalStreamDescription *sal_media_description_find_stream(SalMediaDescription *md,
SalMediaProto proto, SalStreamType type);
@ -121,7 +124,7 @@ typedef struct SalOpBase{
typedef enum SalError{
SalErrorNoResponse,
SalErrorMedia,
SalErrorProtocol,
SalErrorFailure, /* see SalReason for more details */
SalErrorUnknown
} SalError;
@ -133,6 +136,7 @@ typedef enum SalReason{
SalReasonTemporarilyUnavailable,
SalReasonNotFound,
SalReasonDoNotDisturb,
SalReasonMedia,
SalReasonForbidden,
SalReasonUnknown
}SalReason;
@ -155,7 +159,7 @@ typedef void (*SalOnCallRinging)(SalOp *op);
typedef void (*SalOnCallAccepted)(SalOp *op);
typedef void (*SalOnCallAck)(SalOp *op);
typedef void (*SalOnCallUpdated)(SalOp *op);
typedef void (*SalOnCallTerminated)(SalOp *op);
typedef void (*SalOnCallTerminated)(SalOp *op, const char *from);
typedef void (*SalOnCallFailure)(SalOp *op, SalError error, SalReason reason, const char *details);
typedef void (*SalOnAuthRequested)(SalOp *op, const char *realm, const char *username);
typedef void (*SalOnAuthSuccess)(SalOp *op, const char *realm, const char *username);
@ -200,6 +204,7 @@ typedef struct SalAuthInfo{
void sal_set_callbacks(Sal *ctx, const SalCallbacks *cbs);
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_masquerade(Sal *ctx, const char *ip);
void sal_use_session_timers(Sal *ctx, int expires);
int sal_iterate(Sal *sal);
@ -223,9 +228,12 @@ void *sal_op_get_user_pointer(const SalOp *op);
/*Call API*/
int sal_call_set_local_media_description(SalOp *h, SalMediaDescription *desc);
int sal_call(SalOp *h, const char *from, const char *to);
int sal_call_notify_ringing(SalOp *h);
int sal_call_accept(SalOp*h);
int sal_call_decline(SalOp *h, SalReason reason, const char *redirection /*optional*/);
const SalMediaDescription * sal_call_get_final_media_description(SalOp *h);
SalMediaDescription * sal_call_get_final_media_description(SalOp *h);
int sal_refer(SalOp *h, const char *refer_to);
int sal_call_send_dtmf(SalOp *h, char dtmf);
int sal_call_terminate(SalOp *h);
/*Registration*/

View file

@ -134,6 +134,18 @@ void sal_uninit(Sal* sal){
ms_free(sal);
}
void sal_set_user_pointer(Sal *sal, void *user_data){
sal->up=user_data;
}
void *sal_get_user_pointer(const Sal *sal){
return sal->up;
}
void sal_masquerade(Sal *ctx, const char *ip){
eXosip_set_option(EXOSIP_OPT_SET_IPV4_FOR_GATEWAY,ip);
}
static void unimplemented_stub(){
ms_warning("Unimplemented SAL callback");
}
@ -293,8 +305,16 @@ int sal_call(SalOp *h, const char *from, const char *to){
return 0;
}
int sal_call_notify_ringing(SalOp *h){
eXosip_lock();
eXosip_call_send_answer(h->tid,180,NULL);
eXosip_unlock();
return 0;
}
int sal_call_accept(SalOp * h){
osip_message_t *msg;
const char *contact=sal_op_get_contact(h);
/* sends a 200 OK */
int err=eXosip_call_build_answer(h->tid,200,&msg);
if (err<0 || msg==NULL){
@ -304,6 +324,8 @@ int sal_call_accept(SalOp * h){
if (h->base.root->session_expires!=0){
if (h->supports_session_timers) osip_message_set_supported(msg, "timer");
}
if (contact) osip_message_set_contact(msg,contact);
if (h->base.local_media){
/*this is the case where we received an invite without SDP*/
@ -320,18 +342,80 @@ int sal_call_accept(SalOp * h){
return 0;
}
const SalMediaDescription * sal_call_get_final_media_description(SalOp *h){
int sal_call_decline(SalOp *h, SalReason reason, const char *redirect){
if (reason==SalReasonBusy){
eXosip_lock();
eXosip_call_send_answer(h->tid,486,NULL);
eXosip_unlock();
}
else if (reason==SalReasonTemporarilyUnavailable){
eXosip_lock();
eXosip_call_send_answer(h->tid,480,NULL);
eXosip_unlock();
}else if (reason==SalReasonDoNotDisturb){
eXosip_lock();
eXosip_call_send_answer(h->tid,600,NULL);
eXosip_unlock();
}else if (reason==SalReasonMedia){
eXosip_lock();
eXosip_call_send_answer(h->tid,415,NULL);
eXosip_unlock();
}else if (redirect!=NULL && reason==SalReasonRedirect){
osip_message_t *msg;
int code;
if (strstr(redirect,"sip:")!=0) code=302;
else code=380;
eXosip_lock();
eXosip_call_build_answer(h->tid,code,&msg);
osip_message_set_contact(msg,redirect);
eXosip_call_send_answer(h->tid,code,msg);
eXosip_unlock();
}else sal_call_terminate(h);
return 0;
}
SalMediaDescription * sal_call_get_final_media_description(SalOp *h){
if (h->base.local_media && h->base.remote_media && !h->result){
sdp_process(h);
}
return h->result;
}
int sal_refer(SalOp *h, const char *refer_to){
osip_message_t *msg=NULL;
int err=0;
eXosip_lock();
eXosip_call_build_refer(h->did,refer_to, &msg);
if (msg) err=eXosip_call_send_request(h->did, msg);
else err=-1;
eXosip_unlock();
return err;
}
int sal_call_send_dtmf(SalOp *h, char dtmf){
osip_message_t *msg=NULL;
char dtmf_body[128];
char clen[10];
eXosip_lock();
eXosip_call_build_info(h->did,&msg);
if (msg){
snprintf(dtmf_body, sizeof(dtmf_body), "Signal=%c\r\nDuration=250\r\n", dtmf);
osip_message_set_body(msg,dtmf_body,strlen(dtmf_body));
osip_message_set_content_type(msg,"application/dtmf-relay");
snprintf(clen,sizeof(clen),"%lu",(unsigned long)strlen(dtmf_body));
osip_message_set_content_length(msg,clen);
eXosip_call_send_request(h->did,msg);
}
eXosip_unlock();
return 0;
}
int sal_call_terminate(SalOp *h){
eXosip_lock();
eXosip_call_terminate(h->cid,h->did);
eXosip_unlock();
eXosip_call_set_reference(h->cid,NULL);
eXosip_unlock();
return 0;
}
@ -494,13 +578,17 @@ static void call_accepted(Sal *sal, eXosip_event_t *ev){
static void call_terminated(Sal *sal, eXosip_event_t *ev){
SalOp *op;
char *from;
op=(SalOp*)ev->external_reference;
if (op==NULL){
ms_warning("Call terminated for already closed call ?");
return;
}
osip_from_to_str(ev->request->from,&from);
eXosip_call_set_reference(ev->cid,NULL);
sal->callbacks.call_terminated(op);
op->cid=-1;
sal->callbacks.call_terminated(op,from);
osip_free(from);
}
static void call_released(Sal *sal, eXosip_event_t *ev){
@ -510,7 +598,7 @@ static void call_released(Sal *sal, eXosip_event_t *ev){
return;
}
eXosip_call_set_reference(ev->cid,NULL);
sal->callbacks.call_terminated(op);
/*sal->callbacks.call_terminated(op);*/
}
static int get_auth_data(eXosip_event_t *ev, const char **realm, const char **username){
@ -600,7 +688,8 @@ static bool_t call_failure(Sal *sal, eXosip_event_t *ev){
sr=SalReasonNotFound;
break;
case 415:
error=SalErrorMedia;
error=SalErrorFailure;
sr=SalReasonMedia;
break;
case 422:
eXosip_default_action(ev);
@ -1031,11 +1120,9 @@ int sal_iterate(Sal *sal){
if (process_event(sal,ev))
eXosip_event_free(ev);
}
if (sal->automatic_action==0) {
eXosip_lock();
eXosip_automatic_refresh();
eXosip_unlock();
}
eXosip_lock();
eXosip_automatic_refresh();
eXosip_unlock();
}
return 0;
}

View file

@ -35,7 +35,7 @@ struct Sal{
MSList *in_subscribes;/*MSList of SalOp */
int running;
int session_expires;
int automatic_action;
void *up;
};
struct SalOp{