Commit e2d0579e authored by jehan's avatar jehan
Browse files

First working version

parent 44707631
......@@ -107,9 +107,7 @@ void sal_address_set_port_int(SalAddress *addr, int port){
}
void sal_address_clean(SalAddress *addr){
belle_sip_header_address_t* header_addr = BELLE_SIP_HEADER_ADDRESS(addr);
belle_sip_header_address_set_displayname(header_addr,NULL);
belle_sip_object_unref(belle_sip_header_address_get_uri(header_addr));
belle_sip_header_address_set_uri(header_addr,belle_sip_uri_new());
belle_sip_parameters_clean(BELLE_SIP_PARAMETERS(header_addr));
return ;
}
char *sal_address_as_string(const SalAddress *addr){
......
......@@ -34,28 +34,14 @@ static void sal_add_pending_auth(Sal *sal, SalOp *op){
sal->pending_auths=ms_list_remove(sal->pending_auths,op);
}
static void process_authentication(SalOp *op, belle_sip_message_t *response) {
/*only process a single header for now*/
belle_sip_header_www_authenticate_t* authenticate;
belle_sip_header_address_t* from = BELLE_SIP_HEADER_ADDRESS(belle_sip_message_get_header(response,BELLE_SIP_FROM));
belle_sip_uri_t* uri = belle_sip_header_address_get_uri(from);
authenticate = BELLE_SIP_HEADER_WWW_AUTHENTICATE(belle_sip_message_get_header(response,BELLE_SIP_WWW_AUTHENTICATE));
if (!authenticate) {
/*search for proxy authenticate*/
authenticate = BELLE_SIP_HEADER_WWW_AUTHENTICATE(belle_sip_message_get_header(response,BELLE_SIP_PROXY_AUTHENTICATE));
}
op->auth_info.realm=(char*)belle_sip_header_www_authenticate_get_realm(authenticate);
op->auth_info.username=(char*)belle_sip_uri_get_user(uri);
if (authenticate) {
if (op->base.root->callbacks.auth_requested(op,&op->auth_info)) {
sal_op_authenticate(op,&op->auth_info);
} else {
ms_message("No auth info found for [%s] at [%s]",op->auth_info.username,op->auth_info.realm);
sal_add_pending_auth(op->base.root,op);
}
} else {
ms_error(" missing authenticate header");
void sal_process_authentication(SalOp *op, belle_sip_response_t *response) {
belle_sip_message_remove_header(BELLE_SIP_MESSAGE(op->request),BELLE_SIP_AUTHORIZATION);
belle_sip_message_remove_header(BELLE_SIP_MESSAGE(op->request),BELLE_SIP_PROXY_AUTHORIZATION);
if (belle_sip_provider_add_authorization(op->base.root->prov,op->request,response)) {
sal_op_resend_request(op,op->request);
}else {
ms_message("No auth info found for [%s]",sal_op_get_from(op));
sal_add_pending_auth(op->base.root,op);
}
}
......@@ -66,7 +52,7 @@ static void process_io_error(void *user_ctx, const belle_sip_io_error_event_t *e
ms_error("process_io_error not implemented yet");
}
static void process_request_event(void *sal, const belle_sip_request_event_t *event) {
SalOp* op;
SalOp* op=NULL;
belle_sip_request_t* req = belle_sip_request_event_get_request(event);
belle_sip_dialog_t* dialog=belle_sip_request_event_get_dialog(event);
belle_sip_header_address_t* origin_address;
......@@ -78,9 +64,11 @@ static void process_request_event(void *sal, const belle_sip_request_event_t *ev
op=(SalOp*)belle_sip_dialog_get_application_data(dialog);
} else if (strcmp("INVITE",belle_sip_request_get_method(req))==0) {
op=sal_op_new((Sal*)sal);
op->dir=SalOpDirIncoming;
sal_op_call_fill_cbs(op);
} else {
ms_error("sal process_request_event not implemented yet for method [%s]",belle_sip_request_get_method(req));
return;
}
if (!op->base.from_address) {
......@@ -120,7 +108,7 @@ static void process_response_event(void *user_ctx, const belle_sip_response_even
belle_sip_client_transaction_t* client_transaction = belle_sip_response_event_get_client_transaction(event);
SalOp* op = (SalOp*)belle_sip_transaction_get_application_data(BELLE_SIP_TRANSACTION(client_transaction));
belle_sip_response_t* response = belle_sip_response_event_get_response(event);
belle_sip_header_address_t* contact_address;
belle_sip_header_address_t* contact_address=NULL;
belle_sip_header_via_t* via_header;
belle_sip_uri_t* contact_uri;
unsigned int contact_port;
......@@ -132,11 +120,31 @@ static void process_response_event(void *user_ctx, const belle_sip_response_even
belle_sip_response_t* old_response=NULL;;
int response_code = belle_sip_response_get_status_code(response);
if (!op->base.remote_ua) {
sal_op_set_remote_ua(op,BELLE_SIP_MESSAGE(response));
}
if (op->callbacks.process_response_event) {
/*Fix contact if needed*/
via_header= (belle_sip_header_via_t*)belle_sip_message_get_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_VIA);
received = belle_sip_header_via_get_received(via_header);
rport = belle_sip_header_via_get_rport(via_header);
if (!sal_op_get_contact(op)) {
/*hmm update contact from via*/
contact_address=belle_sip_header_address_new();
contact_uri=belle_sip_uri_create(NULL,belle_sip_header_via_get_host(via_header));
belle_sip_header_address_set_uri(contact_address,contact_uri);
if (strcasecmp(belle_sip_header_via_get_transport(via_header),"UDP")!=0) {
belle_sip_uri_set_transport_param(contact_uri,belle_sip_header_via_get_transport_lowercase(via_header));
}
if (belle_sip_header_via_get_listening_port(via_header)
!= belle_sip_listening_point_get_well_known_port(belle_sip_header_via_get_transport(via_header))) {
belle_sip_uri_set_port(contact_uri,belle_sip_header_via_get_listening_port(via_header) );
}
contact_updated=TRUE;
}
if (received!=NULL || rport>0) {
if (sal_op_get_contact(op)){
contact_address = BELLE_SIP_HEADER_ADDRESS(sal_address_clone(sal_op_get_contact_address(op)));
......@@ -152,16 +160,30 @@ static void process_response_event(void *user_ctx, const belle_sip_response_even
belle_sip_uri_set_port(contact_uri,rport);
contact_updated=TRUE;
}
if (contact_updated) {
new_contact=belle_sip_object_to_string(BELLE_SIP_OBJECT(contact_address));
ms_message("Updating contact from [%s] to [%s] for [%p]",sal_op_get_contact(op),new_contact,op);
sal_op_set_contact(op,new_contact);
belle_sip_free(new_contact);
/*try to fix transport if needed (very unlikely)*/
if (strcasecmp(belle_sip_header_via_get_transport(via_header),"UDP")!=0) {
if (!belle_sip_uri_get_transport_param(contact_uri)
||strcasecmp(belle_sip_uri_get_transport_param(contact_uri),belle_sip_header_via_get_transport(via_header))!=0) {
belle_sip_uri_set_transport_param(contact_uri,belle_sip_header_via_get_transport_lowercase(via_header));
contact_updated=TRUE;
}
} else {
if (belle_sip_uri_get_transport_param(contact_uri)) {
contact_updated=TRUE;
belle_sip_uri_set_transport_param(contact_uri,NULL);
}
}
belle_sip_object_unref(contact_address);
}
}
if (contact_updated) {
new_contact=belle_sip_object_to_string(BELLE_SIP_OBJECT(contact_address));
ms_message("Updating contact from [%s] to [%s] for [%p]",sal_op_get_contact(op),new_contact,op);
sal_op_set_contact(op,new_contact);
belle_sip_free(new_contact);
}
if (contact_address)belle_sip_object_unref(contact_address);
/*update request/response
* maybe only the transaction should be kept*/
old_request=op->request;
......@@ -182,8 +204,7 @@ static void process_response_event(void *user_ctx, const belle_sip_response_even
}
case 401:
case 407:{
process_authentication(op,BELLE_SIP_MESSAGE(response));
sal_process_authentication(op,response);
return;
}
}
......@@ -211,7 +232,17 @@ static void process_transaction_terminated(void *user_ctx, const belle_sip_trans
ms_error("Unhandled transaction terminated [%p]",event);
}
}
static void process_auth_requested(void *sal, belle_sip_auth_event_t *auth_event) {
SalAuthInfo auth_info;
memset(&auth_info,0,sizeof(SalAuthInfo));
auth_info.username=(char*)belle_sip_auth_event_get_username(auth_event);
auth_info.realm=(char*)belle_sip_auth_event_get_realm(auth_event);
((Sal*)sal)->callbacks.auth_requested(sal,&auth_info);
belle_sip_auth_event_set_passwd(auth_event,(const char*)auth_info.password);
belle_sip_auth_event_set_ha1(auth_event,(const char*)auth_info.ha1);
belle_sip_auth_event_set_userid(auth_event,(const char*)auth_info.userid);
return;
}
Sal * sal_init(){
char stack_string[64];
Sal * sal=ms_new0(Sal,1);
......@@ -228,6 +259,7 @@ Sal * sal_init(){
sal->listener_callbacks.process_response_event=process_response_event;
sal->listener_callbacks.process_timeout=process_timeout;
sal->listener_callbacks.process_transaction_terminated=process_transaction_terminated;
sal->listener_callbacks.process_auth_requested=process_auth_requested;
belle_sip_provider_add_sip_listener(sal->prov,belle_sip_listener_create_from_callbacks(&sal->listener_callbacks,sal));
return sal;
}
......@@ -351,8 +383,7 @@ void sal_reuse_authorization(Sal *ctx, bool_t enabled){
return ;
}
void sal_use_one_matching_codec_policy(Sal *ctx, bool_t one_matching_codec){
ms_error("sal_use_one_matching_codec_policy not implemented yet");
return ;
ctx->one_matching_codec=one_matching_codec;
}
void sal_use_rport(Sal *ctx, bool_t use_rports){
ms_error("sal_use_rport not implemented yet");
......
......@@ -32,8 +32,19 @@ struct Sal{
belle_sip_header_user_agent_t* user_agent;
void *up; /*user pointer*/
int session_expires;
bool_t one_matching_codec;
};
typedef enum SalOpSate {
SalOpStateEarly=0
,SalOpStateActive
,SalOpStateTerminated
}SalOpSate_t;
typedef enum SalOpDir {
SalOpDirIncoming=0
,SalOpDirOutgoing
}SalOpDir_t;
struct SalOp{
SalOpBase base;
......@@ -48,6 +59,11 @@ struct SalOp{
belle_sip_header_address_t *replaces;
belle_sip_header_address_t *referred_by;
bool_t auto_answer_asked;
SalMediaDescription *result;
belle_sdp_session_description_t *sdp_answer;
bool_t supports_session_timers;
SalOpSate_t state;
SalOpDir_t dir;
};
belle_sdp_session_description_t * media_description_to_sdp(const SalMediaDescription *sal);
......@@ -60,4 +76,6 @@ void sal_op_call_fill_cbs(SalOp*op);
void sal_op_set_remote_ua(SalOp*op,belle_sip_message_t* message);
void sal_op_send_request(SalOp* op, belle_sip_request_t* request);
void sal_op_resend_request(SalOp* op, belle_sip_request_t* request);
void sal_process_authentication(SalOp *op, belle_sip_response_t *response);
#endif /* SAL_IMPL_H_ */
......@@ -17,12 +17,193 @@ 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_impl.h"
#include "offeranswer.h"
static void sdp_process(SalOp *h){
ms_message("Doing SDP offer/answer process of type %s",h->sdp_offering ? "outgoing" : "incoming");
if (h->result){
sal_media_description_unref(h->result);
}
h->result=sal_media_description_new();
if (h->sdp_offering){
offer_answer_initiate_outgoing(h->base.local_media,h->base.remote_media,h->result);
}else{
int i;
if (h->sdp_answer){
belle_sip_object_unref(h->sdp_answer);
}
offer_answer_initiate_incoming(h->base.local_media,h->base.remote_media,h->result,h->base.root->one_matching_codec);
h->sdp_answer=media_description_to_sdp(h->result);
/*once we have generated the SDP answer, we modify the result description for processing by the upper layer.
It should contains media parameters constraint from the remote offer, not our response*/
strcpy(h->result->addr,h->base.remote_media->addr);
h->result->bandwidth=h->base.remote_media->bandwidth;
for(i=0;i<h->result->nstreams;++i){
if (h->result->streams[i].port>0){
strcpy(h->result->streams[i].addr,h->base.remote_media->streams[i].addr);
h->result->streams[i].ptime=h->base.remote_media->streams[i].ptime;
h->result->streams[i].bandwidth=h->base.remote_media->streams[i].bandwidth;
h->result->streams[i].port=h->base.remote_media->streams[i].port;
if (h->result->streams[i].proto == SalProtoRtpSavp) {
h->result->streams[i].crypto[0] = h->base.remote_media->streams[i].crypto[0];
}
}
}
}
}
static int set_sdp(belle_sip_message_t *msg,belle_sdp_session_description_t* session_desc) {
belle_sip_header_content_type_t* content_type ;
belle_sip_header_content_length_t* content_length;
int length;
char buff[1024];
if (session_desc) {
content_type = belle_sip_header_content_type_create("application","sdp");
length = belle_sip_object_marshal(BELLE_SIP_OBJECT(session_desc),buff,0,sizeof(buff));
if (length==sizeof(buff)) {
ms_error("Buffer too small or sdp too big");
}
content_length= belle_sip_header_content_length_create(length);
belle_sip_message_add_header(msg,BELLE_SIP_HEADER(content_type));
belle_sip_message_add_header(msg,BELLE_SIP_HEADER(content_length));
belle_sip_message_set_body(msg,buff,length);
return 0;
} else {
return -1;
}
}
static int set_sdp_from_desc(belle_sip_message_t *msg, const SalMediaDescription *desc){
return set_sdp(msg,media_description_to_sdp(desc));
}
static void call_process_io_error(void *user_ctx, const belle_sip_io_error_event_t *event){
ms_error("process_io_error not implemented yet");
}
static void call_response_event(void *user_ctx, const belle_sip_response_event_t *event){
ms_error("response_event not implemented yet");
static void handle_sdp_from_response(SalOp* op,belle_sip_response_t* response) {
belle_sdp_session_description_t* sdp;
if ((sdp=belle_sdp_session_description_create(BELLE_SIP_MESSAGE(response)))) {
op->base.remote_media=sal_media_description_new();
sdp_to_media_description(sdp,op->base.remote_media);
if (op->base.local_media) sdp_process(op);
}
}
static void call_response_event(void *op_base, const belle_sip_response_event_t *event){
SalOp* op = (SalOp*)op_base;
belle_sip_request_t* ack;
belle_sip_dialog_state_t dialog_state;
/*belle_sip_client_transaction_t* client_transaction = belle_sip_response_event_get_client_transaction(event);*/
belle_sip_response_t* response=belle_sip_response_event_get_response(event);
int code = belle_sip_response_get_status_code(response);
char* reason;
SalError error=SalErrorUnknown;
SalReason sr=SalReasonUnknown;
belle_sip_header_t* reason_header = belle_sip_message_get_header(BELLE_SIP_MESSAGE(response),"Reason");
reason=(char*)belle_sip_response_get_reason_phrase(response);
if (reason_header){
reason = ms_strdup_printf("%s %s",reason,belle_sip_header_extension_get_value(BELLE_SIP_HEADER_EXTENSION(reason_header)));
}
if (code >=400) {
switch(code) {
case 400:
error=SalErrorUnknown;
break;
case 404:
error=SalErrorFailure;
sr=SalReasonNotFound;
break;
case 415:
error=SalErrorFailure;
sr=SalReasonMedia;
break;
case 422:
ms_error ("422 not implemented yet");;
break;
case 480:
error=SalErrorFailure;
sr=SalReasonTemporarilyUnavailable;
break;
case 486:
error=SalErrorFailure;
sr=SalReasonBusy;
break;
case 487:
break;
case 600:
error=SalErrorFailure;
sr=SalReasonDoNotDisturb;
break;
case 603:
error=SalErrorFailure;
sr=SalReasonDeclined;
break;
default:
if (code>0){
error=SalErrorFailure;
sr=SalReasonUnknown;
}else error=SalErrorNoResponse;
/* no break */
}
op->base.root->callbacks.call_failure(op,error,sr,reason,code);
if (reason_header != NULL){
ms_free(reason);
}
return;
}
/*else dialog*/
dialog_state=belle_sip_dialog_get_state(op->dialog);
switch(dialog_state) {
case BELLE_SIP_DIALOG_NULL: {
ms_error("op [%p] receive an unexpected answer [%i]",op,code);
break;
}
case BELLE_SIP_DIALOG_EARLY: {
if (code >= 180) {
handle_sdp_from_response(op,response);
op->base.root->callbacks.call_ringing(op);
} else {
ms_error("op [%p] receive an unexpected answer [%i]",op,code);
}
break;
}
case BELLE_SIP_DIALOG_CONFIRMED: {
switch (op->state) {
case SalOpStateEarly:
handle_sdp_from_response(op,response);
ack=belle_sip_dialog_create_ack(op->dialog,belle_sip_dialog_get_local_seq_number(op->dialog));
if (ack==NULL) {
ms_error("This call has been already terminated.");
return ;
}
if (op->sdp_answer){
set_sdp(BELLE_SIP_MESSAGE(response),op->sdp_answer);
op->sdp_answer=NULL;
}
belle_sip_dialog_send_ack(op->dialog,ack);
op->base.root->callbacks.call_accepted(op);
break;
case SalOpStateActive:
case SalOpStateTerminated:
default:
ms_error("op [%p] receive answer [%i] not implemented",op,code);
}
break;
}
case BELLE_SIP_DIALOG_TERMINATED:
default: {
ms_error("op [%p] receive answer [%i] not implemented",op,code);
}
/* no break */
}
}
static void call_process_timeout(void *user_ctx, const belle_sip_timeout_event_t *event) {
ms_error("process_timeout not implemented yet");
......@@ -42,6 +223,7 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t
belle_sip_dialog_state_t dialog_state;
belle_sdp_session_description_t* sdp;
belle_sip_header_t* call_info;
belle_sip_response_t* resp;
if (!op->dialog) {
op->dialog=belle_sip_provider_create_dialog(op->base.root->prov,BELLE_SIP_TRANSACTION(op->pending_server_trans));
belle_sip_dialog_set_application_data(op->dialog,op);
......@@ -76,36 +258,41 @@ static void process_request_event(void *op_base, const belle_sip_request_event_t
break;
}
case BELLE_SIP_DIALOG_CONFIRMED:
/*great ACK received*/
if (strcmp("ACK",belle_sip_request_get_method(req))==0) {
if (op->sdp_offering){
if ((sdp=belle_sdp_session_description_create(BELLE_SIP_MESSAGE(req)))){
if (op->base.remote_media)
sal_media_description_unref(op->base.remote_media);
op->base.remote_media=sal_media_description_new();
sdp_to_media_description(sdp,op->base.remote_media);
sdp_process(op);
belle_sip_object_unref(sdp);
}
}
/*FIXME
if (op->reinvite){
op->reinvite=FALSE;
}*/
op->base.root->callbacks.call_ack(op);
} else if(strcmp("BYE",belle_sip_request_get_method(req))==0) {
op->base.root->callbacks.call_terminated(op,op->dir==SalOpDirIncoming?sal_op_get_from(op):sal_op_get_to(op));
resp=belle_sip_response_create_from_request(belle_sip_request_event_get_request(event),200);
belle_sip_server_transaction_send_response(server_transaction,resp);
} else {
ms_error("unexpected method [%s] for dialog [%p]",belle_sip_request_get_method(req),op->dialog);
}
break;
default: {
ms_error("unexpected dialog state [%s]",belle_sip_dialog_state_to_string(dialog_state));
}
/* no break */
}
}
static int set_sdp_from_desc(belle_sip_message_t *msg, const SalMediaDescription *desc){
belle_sdp_session_description_t* session_desc=media_description_to_sdp(desc);
belle_sip_header_content_type_t* content_type ;
belle_sip_header_content_length_t* content_length;
int length;
char buff[1024];
if (session_desc) {
content_type = belle_sip_header_content_type_create("application","sdp");
length = belle_sip_object_marshal(BELLE_SIP_OBJECT(session_desc),buff,0,sizeof(buff));
if (length==sizeof(buff)) {
ms_error("Buffer too small or sdp too big");
}
content_length= belle_sip_header_content_length_create(length);
belle_sip_message_add_header(msg,BELLE_SIP_HEADER(content_type));
belle_sip_message_add_header(msg,BELLE_SIP_HEADER(content_length));
belle_sip_message_set_body(msg,buff,length);
return 0;
} else {
return -1;
}
}
/*Call API*/
int sal_call_set_local_media_description(SalOp *op, SalMediaDescription *desc){
if (desc)
......@@ -121,7 +308,7 @@ int sal_call(SalOp *op, const char *from, const char *to){
belle_sip_client_transaction_t* client_transaction;
belle_sip_provider_t* prov=op->base.root->prov;
belle_sip_header_route_t* route_header;
op->dir=SalOpDirOutgoing;
sal_op_set_from(op,from);
sal_op_set_to(op,to);
......@@ -157,14 +344,58 @@ void sal_op_call_fill_cbs(SalOp*op) {
op->callbacks.process_transaction_terminated=call_process_transaction_terminated;
op->callbacks.process_request_event=process_request_event;
}
int sal_call_notify_ringing(SalOp *h, bool_t early_media){
ms_fatal("sal_call_notify_ringing not implemented yet");
return -1;
int sal_call_notify_ringing(SalOp *op, bool_t early_media){
belle_sip_response_t* ringing_response;
/*if early media send also 180 and 183 */
if (early_media){
ms_fatal("not implemented yet");
}else{
ringing_response = belle_sip_response_create_from_request(belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(op->pending_server_trans)),180);
belle_sip_server_transaction_send_response(op->pending_server_trans,ringing_response);
}
return 0;
}
/*accept an incoming call or, during a call accept a reINVITE*/
int sal_call_accept(SalOp*h){
ms_fatal("sal_call_accept not implemented yet");
return -1;
belle_sip_response_t *response;
belle_sip_header_address_t* contact= (belle_sip_header_address_t*)sal_op_get_contact_address(h);
belle_sip_header_contact_t* contact_header;
/* sends a 200 OK */
response = belle_sip_response_create_from_request(belle_sip_transaction_get_request(BELLE_SIP_TRANSACTION(h->pending_server_trans)),200);
if (response==NULL){
ms_error("Fail to build answer for call");
return -1;
}
if (h->base.root->session_expires!=0){
if (h->supports_session_timers) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),belle_sip_header_create( "Supported", "timer"));
}
}
if (contact && (contact_header=belle_sip_header_contact_create(contact))) {
belle_sip_message_add_header(BELLE_SIP_MESSAGE(response),BELLE_SIP_HEADER(contact_header));
}
if (h->base.local_media){
/*this is the case where we received an invite without SDP*/
if (h->sdp_offering) {
set_sdp_from_desc(BELLE_SIP_MESSAGE(response),h->base.local_media);
}else{
if (h->sdp_answer==NULL) sdp_process(h);
if (h->sdp_answer){
set_sdp(BELLE_SIP_MESSAGE(response),h->sdp_answer);
h->sdp_answer=NULL;
}
}
}else{
ms_error("You are accepting a call but not defined any media capabilities !");
}
belle_sip_server_transaction_send_response(h->pending_server_trans,response);
return 0;
}
int sal_call_decline(SalOp *op, SalReason reason, const char *redirection /*optional*/){
belle_sip_response_t* response;
......@@ -214,9 +445,13 @@ SalMediaDescription * sal_call_get_remote_media_description(SalOp *h){
ms_fatal("sal_call_get_remote_media_description not implemented yet");
return NULL;
}
SalMediaDescription * sal_call_get_final_media_description(SalOp *h){
ms_fatal("sal_call_get_final_media_description not implemented yet");
return NULL;
if (h->base.local_media && h->base.remote_media && !h->result){
sdp_process(h);
}
return h->result;
}
int sal_call_refer(SalOp *h, const char *refer_to){
ms_fatal("sal_call_refer not implemented yet");
......@@ -237,20 +472,32 @@ int sal_call_set_referer(SalOp *h, SalOp *refered_call){
}
/* returns the SalOp of a call that should be replaced by h, if any */
SalOp *sal_call_get_replaces(SalOp *h){
ms_fatal("sal_call_get_replaces not implemented yet");
if (h!=NULL && h->replaces!=NULL){
ms_fatal("sal_call_get_replaces not implemented yet");
}
return NULL;
}
int sal_call_send_dtmf(SalOp *h, char dtmf){
ms_fatal("sal_call_send_dtmf not implemented yet");
return -1;
}
int sal_call_terminate(SalOp *h){
ms_fatal("sal_call_terminate not implemented yet");
return -1;
int sal_call_terminate(SalOp *op){