1206 lines
40 KiB
C
1206 lines
40 KiB
C
/*
|
|
* Copyright (C) 2010-2011 Mamadou Diop.
|
|
*
|
|
* Contact: Mamadou Diop <diopmamadou(at)doubango.org>
|
|
*
|
|
* This file is part of Open Source Doubango Framework.
|
|
*
|
|
* DOUBANGO 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.
|
|
*
|
|
* DOUBANGO 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 DOUBANGO.
|
|
*
|
|
*/
|
|
|
|
/**@file tsip_dialog.c
|
|
* @brief SIP dialog base class as per RFC 3261 subclause 17.
|
|
*
|
|
* @author Mamadou Diop <diopmamadou(at)doubango.org>
|
|
*
|
|
|
|
*/
|
|
#include "tinysip/dialogs/tsip_dialog.h"
|
|
|
|
#include "tinysip/dialogs/tsip_dialog_layer.h"
|
|
#include "tinysip/transactions/tsip_transac_layer.h"
|
|
|
|
#include "tinysip/transactions/tsip_transac_nict.h"
|
|
|
|
#include "tinysip/parsers/tsip_parser_uri.h"
|
|
|
|
#include "tinysip/headers/tsip_header_Authorization.h"
|
|
#include "tinysip/headers/tsip_header_Contact.h"
|
|
#include "tinysip/headers/tsip_header_Dummy.h"
|
|
#include "tinysip/headers/tsip_header_Expires.h"
|
|
#include "tinysip/headers/tsip_header_P_Preferred_Identity.h"
|
|
#include "tinysip/headers/tsip_header_Proxy_Authenticate.h"
|
|
#include "tinysip/headers/tsip_header_Proxy_Authorization.h"
|
|
#include "tinysip/headers/tsip_header_Record_Route.h"
|
|
#include "tinysip/headers/tsip_header_Route.h"
|
|
#include "tinysip/headers/tsip_header_Subscription_State.h"
|
|
#include "tinysip/headers/tsip_header_WWW_Authenticate.h"
|
|
|
|
#include "tsk_debug.h"
|
|
#include "tsk_time.h"
|
|
|
|
int tsip_dialog_update_challenges(tsip_dialog_t *self, const tsip_response_t* response, tsk_bool_t acceptNewVector);
|
|
int tsip_dialog_add_common_headers(const tsip_dialog_t *self, tsip_request_t* request);
|
|
|
|
extern tsip_uri_t* tsip_stack_get_pcscf_uri(const tsip_stack_t *self, tsk_bool_t lr);
|
|
extern tsip_uri_t* tsip_stack_get_contacturi(const tsip_stack_t *self, const char* protocol);
|
|
|
|
tsip_request_t *tsip_dialog_request_new(const tsip_dialog_t *self, const char* method)
|
|
{
|
|
tsip_request_t *request = tsk_null;
|
|
tsip_uri_t *to_uri, *from_uri, *request_uri;
|
|
const char *call_id;
|
|
int copy_routes_start = -1; /* NONE */
|
|
const tsk_list_item_t* item;
|
|
|
|
/*
|
|
RFC 3261 - 12.2.1.1 Generating the Request
|
|
|
|
The Call-ID of the request MUST be set to the Call-ID of the dialog.
|
|
*/
|
|
call_id = self->callid;
|
|
|
|
/*
|
|
RFC 3261 - 12.2.1.1 Generating the Request
|
|
|
|
Requests within a dialog MUST contain strictly monotonically
|
|
increasing and contiguous CSeq sequence numbers (increasing-by-one)
|
|
in each direction (excepting ACK and CANCEL of course, whose numbers
|
|
equal the requests being acknowledged or cancelled). Therefore, if
|
|
the local sequence number is not empty, the value of the local
|
|
sequence number MUST be incremented by one, and this value MUST be
|
|
placed into the CSeq header field.
|
|
*/
|
|
/*if(!tsk_striequals(method, "ACK") && !tsk_striequals(method, "CANCEL"))
|
|
{
|
|
TSIP_DIALOG(self)->cseq_value +=1;
|
|
}
|
|
===> See send method (cseq will be incremented before sending the request)
|
|
*/
|
|
|
|
|
|
/*
|
|
RFC 3261 - 12.2.1.1 Generating the Request
|
|
|
|
The URI in the To field of the request MUST be set to the remote URI
|
|
from the dialog state. The tag in the To header field of the request
|
|
MUST be set to the remote tag of the dialog ID. The From URI of the
|
|
request MUST be set to the local URI from the dialog state. The tag
|
|
in the From header field of the request MUST be set to the local tag
|
|
of the dialog ID. If the value of the remote or local tags is null,
|
|
the tag parameter MUST be omitted from the To or From header fields,
|
|
respectively.
|
|
*/
|
|
to_uri = tsk_object_ref((void*)self->uri_remote);
|
|
from_uri = tsk_object_ref((void*)self->uri_local);
|
|
|
|
|
|
/*
|
|
RFC 3261 - 12.2.1.1 Generating the Request
|
|
|
|
If the route set is empty, the UAC MUST place the remote target URI
|
|
into the Request-URI. The UAC MUST NOT add a Route header field to
|
|
the request.
|
|
*/
|
|
if(!self->record_routes || TSK_LIST_IS_EMPTY(self->record_routes)){
|
|
request_uri = tsk_object_ref((void*)self->uri_remote_target);
|
|
}
|
|
|
|
/*
|
|
RFC 3261 - 12.2.1.1 Generating the Request
|
|
|
|
If the route set is not empty, and the first URI in the route set
|
|
contains the lr parameter (see Section 19.1.1), the UAC MUST place
|
|
the remote target URI into the Request-URI and MUST include a Route
|
|
header field containing the route set values in order, including all
|
|
parameters.
|
|
|
|
If the route set is not empty, and its first URI does not contain the
|
|
lr parameter, the UAC MUST place the first URI from the route set
|
|
into the Request-URI, stripping any parameters that are not allowed
|
|
in a Request-URI. The UAC MUST add a Route header field containing
|
|
the remainder of the route set values in order, including all
|
|
parameters. The UAC MUST then place the remote target URI into the
|
|
Route header field as the last value.
|
|
|
|
For example, if the remote target is sip:user@remoteua and the route
|
|
set contains:
|
|
|
|
<sip:proxy1>,<sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>
|
|
*/
|
|
else{
|
|
const tsip_uri_t *first_route = ((tsip_header_Record_Route_t*)TSK_LIST_FIRST_DATA(self->record_routes))->uri;
|
|
if(tsk_params_have_param(first_route->params, "lr")){
|
|
request_uri = tsk_object_ref(self->uri_remote_target);
|
|
copy_routes_start = 0; /* Copy all */
|
|
}
|
|
else{
|
|
request_uri = tsk_object_ref((void*)first_route);
|
|
copy_routes_start = 1; /* Copy starting at index 1. */
|
|
}
|
|
}
|
|
|
|
/*=====================================================================
|
|
*/
|
|
request = tsip_request_new(method, request_uri, from_uri, to_uri, call_id, self->cseq_value);
|
|
request->To->tag = tsk_strdup(self->tag_remote);
|
|
request->From->tag = tsk_strdup(self->tag_local);
|
|
request->update = tsk_true; /* Now signal that the message should be updated by the transport layer (Contact, SigComp, IPSec, ...) */
|
|
|
|
|
|
/*
|
|
RFC 3261 - 12.2.1.1 Generating the Request
|
|
|
|
A UAC SHOULD include a Contact header field in any target refresh
|
|
requests within a dialog, and unless there is a need to change it,
|
|
the URI SHOULD be the same as used in previous requests within the
|
|
dialog. If the "secure" flag is true, that URI MUST be a SIPS URI.
|
|
As discussed in Section 12.2.2, a Contact header field in a target
|
|
refresh request updates the remote target URI. This allows a UA to
|
|
provide a new contact address, should its address change during the
|
|
duration of the dialog.
|
|
*/
|
|
switch(request->line.request.request_type){
|
|
case tsip_MESSAGE:
|
|
case tsip_PUBLISH:
|
|
case tsip_BYE:
|
|
{
|
|
if(request->line.request.request_type == tsip_PUBLISH) {
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_EXPIRES_VA_ARGS(TSK_TIME_MS_2_S(self->expires)));
|
|
}
|
|
/* add caps in Accept-Contact headers */
|
|
tsk_list_foreach(item, self->ss->caps) {
|
|
const tsk_param_t* param = TSK_PARAM(item->data);
|
|
char* value = tsk_null;
|
|
tsk_sprintf(&value, "*;%s%s%s",
|
|
param->name,
|
|
param->value ? "=" : "",
|
|
param->value ? param->value : "");
|
|
if(value) {
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_DUMMY_VA_ARGS("Accept-Contact", value));
|
|
TSK_FREE(value);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
char* contact = tsk_null;
|
|
tsip_header_Contacts_L_t *hdr_contacts;
|
|
|
|
if(request->line.request.request_type == tsip_OPTIONS ||
|
|
request->line.request.request_type == tsip_PUBLISH ||
|
|
request->line.request.request_type == tsip_REGISTER){
|
|
/**** with expires */
|
|
tsk_sprintf(&contact, "m: <%s:%s@%s:%d>;expires=%d\r\n", "sip", from_uri->user_name, "127.0.0.1", 5060, TSK_TIME_MS_2_S(self->expires));
|
|
}
|
|
else{
|
|
/**** without expires */
|
|
if(request->line.request.request_type == tsip_SUBSCRIBE){
|
|
/* RFC 3265 - 3.1.1. Subscription Duration
|
|
An "expires" parameter on the "Contact" header has no semantics for SUBSCRIBE and is explicitly
|
|
not equivalent to an "Expires" header in a SUBSCRIBE request or response.
|
|
*/
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_EXPIRES_VA_ARGS(TSK_TIME_MS_2_S(self->expires)));
|
|
}
|
|
tsk_sprintf(&contact, "m: <%s:%s@%s:%d>\r\n", "sip", from_uri->user_name, "127.0.0.1", 5060);
|
|
}
|
|
hdr_contacts = tsip_header_Contact_parse(contact, tsk_strlen(contact));
|
|
if(!TSK_LIST_IS_EMPTY(hdr_contacts)){
|
|
request->Contact = tsk_object_ref(hdr_contacts->head->data);
|
|
}
|
|
TSK_OBJECT_SAFE_FREE(hdr_contacts);
|
|
TSK_FREE(contact);
|
|
|
|
/* Add capabilities as per RFC 3840 */
|
|
if(request->Contact) {
|
|
tsk_list_foreach(item, self->ss->caps){
|
|
tsk_params_add_param(&TSIP_HEADER(request->Contact)->params, TSK_PARAM(item->data)->name, TSK_PARAM(item->data)->value);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Update authorizations */
|
|
if(self->state == tsip_initial && TSK_LIST_IS_EMPTY(self->challenges)){
|
|
/* 3GPP TS 33.978 6.2.3.1 Procedures at the UE
|
|
On sending a REGISTER request in order to indicate support for early IMS security procedures, the UE shall not
|
|
include an Authorization header field and not include header fields or header field values as required by RFC3329.
|
|
*/
|
|
if(TSIP_REQUEST_IS_REGISTER(request) && !TSIP_DIALOG_GET_STACK(self)->security.earlyIMS){
|
|
/* 3GPP TS 24.229 - 5.1.1.2.2 Initial registration using IMS AKA
|
|
On sending a REGISTER request, the UE shall populate the header fields as follows:
|
|
a) an Authorization header field, with:
|
|
- the "username" header field parameter, set to the value of the private user identity;
|
|
- the "realm" header field parameter, set to the domain name of the home network;
|
|
- the "uri" header field parameter, set to the SIP URI of the domain name of the home network;
|
|
- the "nonce" header field parameter, set to an empty value; and
|
|
- the "response" header field parameter, set to an empty value;
|
|
*/
|
|
const char* realm = TSIP_DIALOG_GET_STACK(self)->network.realm ? TSIP_DIALOG_GET_STACK(self)->network.realm->host : "(null)";
|
|
char* request_uri = tsip_uri_tostring(request->line.request.uri, tsk_false, tsk_false);
|
|
tsip_header_t* auth_hdr = tsip_challenge_create_empty_header_authorization(TSIP_DIALOG_GET_STACK(self)->identity.impi, realm, request_uri);
|
|
tsip_message_add_header(request, auth_hdr);
|
|
tsk_object_unref(auth_hdr), auth_hdr = tsk_null;
|
|
TSK_FREE(request_uri);
|
|
}
|
|
}
|
|
else if(!TSK_LIST_IS_EMPTY(self->challenges)){
|
|
tsip_challenge_t *challenge;
|
|
tsip_header_t* auth_hdr;
|
|
tsk_list_foreach(item, self->challenges){
|
|
challenge = item->data;
|
|
auth_hdr = tsip_challenge_create_header_authorization(challenge, request);
|
|
if(auth_hdr){
|
|
tsip_message_add_header(request, auth_hdr);
|
|
tsk_object_unref(auth_hdr), auth_hdr = tsk_null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update CSeq */
|
|
/* RFC 3261 - 13.2.2.4 2xx Responses
|
|
Generating ACK: The sequence number of the CSeq header field MUST be
|
|
the same as the INVITE being acknowledged, but the CSeq method MUST
|
|
be ACK. The ACK MUST contain the same credentials as the INVITE. If
|
|
the 2xx contains an offer (based on the rules above), the ACK MUST
|
|
carry an answer in its body.
|
|
==> CSeq number will be added/updated by the caller of this function,
|
|
credentials were added above.
|
|
*/
|
|
if(!TSIP_REQUEST_IS_ACK(request) && !TSIP_REQUEST_IS_CANCEL(request)){
|
|
request->CSeq->seq = ++(TSIP_DIALOG(self)->cseq_value);
|
|
}
|
|
|
|
/* Route generation
|
|
* ==> http://betelco.blogspot.com/2008/11/proxy-and-service-route-discovery-in.html
|
|
* The dialog Routes have been copied above.
|
|
|
|
3GPP TS 24.229 - 5.1.2A.1 UE-originating case
|
|
|
|
The UE shall build a proper preloaded Route header field value for all new dialogs and standalone transactions. The UE
|
|
shall build a list of Route header field values made out of the following, in this order:
|
|
a) the P-CSCF URI containing the IP address or the FQDN learnt through the P-CSCF discovery procedures; and
|
|
b) the P-CSCF port based on the security mechanism in use:
|
|
|
|
- if IMS AKA or SIP digest with TLS is in use as a security mechanism, the protected server port learnt during
|
|
the registration procedure;
|
|
- if SIP digest without TLS, NASS-IMS bundled authentciation or GPRS-IMS-Bundled authentication is in
|
|
use as a security mechanism, the unprotected server port used during the registration procedure;
|
|
c) and the values received in the Service-Route header field saved from the 200 (OK) response to the last
|
|
registration or re-registration of the public user identity with associated contact address.
|
|
*/
|
|
if(!TSIP_REQUEST_IS_REGISTER(request))
|
|
{ // According to the above link ==> Initial/Re/De registration do not have routes.
|
|
if(copy_routes_start != -1)
|
|
{ /* The dialog already have routes ==> copy them. */
|
|
if(self->state == tsip_early || self->state == tsip_established){
|
|
int32_t index = -1;
|
|
tsk_list_foreach(item, self->record_routes){
|
|
tsip_header_Record_Route_t *record_Route = ((tsip_header_Record_Route_t*)item->data);
|
|
const tsip_uri_t* uri = record_Route->uri;
|
|
tsip_header_Route_t *route = tsk_null;
|
|
if(++index < copy_routes_start || !uri){
|
|
continue;
|
|
}
|
|
|
|
if((route = tsip_header_Route_create(uri))){
|
|
// copy parameters: see http://code.google.com/p/imsdroid/issues/detail?id=52
|
|
if(!TSK_LIST_IS_EMPTY(TSIP_HEADER_PARAMS(record_Route))){
|
|
if(!TSIP_HEADER_PARAMS(route)){
|
|
TSIP_HEADER_PARAMS(route) = tsk_list_create();
|
|
}
|
|
tsk_list_pushback_list(TSIP_HEADER_PARAMS(route), TSIP_HEADER_PARAMS(record_Route));
|
|
}
|
|
|
|
tsip_message_add_header(request, TSIP_HEADER(route));
|
|
TSK_OBJECT_SAFE_FREE(route);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ /* No routes associated to this dialog. */
|
|
if(self->state == tsip_initial || self->state == tsip_early){
|
|
/* GPP TS 24.229 section 5.1.2A [Generic procedures applicable to all methods excluding the REGISTER method]:
|
|
The UE shall build a proper preloaded Route header field value for all new dialogs and standalone transactions. The UE
|
|
shall build a list of Route header field values made out of the following, in this order:
|
|
a) the P-CSCF URI containing the IP address or the FQDN learnt through the P-CSCF discovery procedures; and
|
|
b) the P-CSCF port based on the security mechanism in use:
|
|
- if IMS AKA or SIP digest with TLS is in use as a security mechanism, the protected server port learnt during
|
|
the registration procedure;
|
|
- if SIP digest without TLS, NASS-IMS bundled authentciation or GPRS-IMS-Bundled authentication is in
|
|
use as a security mechanism, the unprotected server port used during the registration procedure;
|
|
c) and the values received in the Service-Route header field saved from the 200 (OK) response to the last
|
|
registration or re-registration of the public user identity with associated contact address.
|
|
*/
|
|
#if _DEBUG && defined(SDS_HACK)/* FIXME: remove this */
|
|
/* Ericsson SDS hack (INVITE with Proxy-CSCF as First route fail) */
|
|
#else
|
|
tsip_uri_t *uri = tsip_stack_get_pcscf_uri(TSIP_DIALOG_GET_STACK(self), tsk_true);
|
|
// Proxy-CSCF as first route
|
|
if(uri){
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_ROUTE_VA_ARGS(uri));
|
|
TSK_OBJECT_SAFE_FREE(uri);
|
|
}
|
|
#endif
|
|
// Service routes
|
|
tsk_list_foreach(item, TSIP_DIALOG_GET_STACK(self)->service_routes){
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_ROUTE_VA_ARGS(item->data));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add headers associated to the dialog's session */
|
|
tsk_list_foreach(item, self->ss->headers){
|
|
if(!TSK_PARAM(item->data)->tag){
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_DUMMY_VA_ARGS(TSK_PARAM(item->data)->name, TSK_PARAM(item->data)->value));
|
|
}
|
|
}
|
|
|
|
/* Add headers associated to the dialog's stack */
|
|
tsk_list_foreach(item, self->ss->stack->headers){
|
|
if(!TSK_PARAM(item->data)->tag){
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_DUMMY_VA_ARGS(TSK_PARAM(item->data)->name, TSK_PARAM(item->data)->value));
|
|
}
|
|
}
|
|
|
|
/* Add common headers */
|
|
tsip_dialog_add_common_headers(self, request);
|
|
|
|
/* SigComp */
|
|
if(self->ss->sigcomp_id){
|
|
/* should be added in this field instead of 'Contact' or 'Via' headers
|
|
* it's up to the transport layer to copy it to these headers */
|
|
request->sigcomp_id = tsk_strdup(self->ss->sigcomp_id);
|
|
}
|
|
|
|
/* Remote Address: Used if "Server mode" otherwise Proxy-CSCF will be used */
|
|
request->remote_addr = self->remote_addr;
|
|
|
|
TSK_OBJECT_SAFE_FREE(request_uri);
|
|
TSK_OBJECT_SAFE_FREE(from_uri);
|
|
TSK_OBJECT_SAFE_FREE(to_uri);
|
|
|
|
return request;
|
|
}
|
|
|
|
|
|
/** Sends a SIP/IMS request. This function is responsible for transaction creation.
|
|
*
|
|
* @param self The parent dialog. All callback events will be notified to this dialog.
|
|
* @param request The request to send.
|
|
*
|
|
* @return Zero if succeed and no-zero error code otherwise.
|
|
**/
|
|
int tsip_dialog_request_send(const tsip_dialog_t *self, tsip_request_t* request)
|
|
{
|
|
int ret = -1;
|
|
|
|
if(self && TSIP_DIALOG_GET_STACK(self)){
|
|
const tsip_transac_layer_t *layer = TSIP_DIALOG_GET_STACK(self)->layer_transac;
|
|
if(layer){
|
|
/* Create new transaction. The new transaction will be added to the transaction layer.
|
|
The transaction has all information to create the right transaction type (NICT or ICT).
|
|
As this is an outgoing request ==> It shall be a client transaction (NICT or ICT).
|
|
For server transactions creation see @ref tsip_dialog_response_send.
|
|
*/
|
|
tsip_transac_t *transac = tsip_transac_layer_new(layer, tsk_true, request, TSIP_DIALOG(self));
|
|
|
|
/* Set the transaction's dialog. All events comming from the transaction (timeouts, errors ...) will be signaled to this dialog.
|
|
*/
|
|
if(transac){
|
|
switch(transac->type)
|
|
{
|
|
case tsip_ict:
|
|
case tsip_nict:
|
|
{
|
|
/* Start the newly create IC/NIC transaction */
|
|
ret = tsip_transac_start(transac, request);
|
|
break;
|
|
}
|
|
}
|
|
tsk_object_unref(transac);
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
tsip_response_t *tsip_dialog_response_new(tsip_dialog_t *self, short status, const char* phrase, const tsip_request_t* request)
|
|
{
|
|
/* Reponse is created as per RFC 3261 subclause 8.2.6 and (headers+tags) are copied
|
|
* as per subclause 8.2.6.2.
|
|
*/
|
|
tsip_response_t* response;
|
|
if((response = tsip_response_new(status, phrase, request))){
|
|
switch(request->line.request.request_type){
|
|
case tsip_MESSAGE:
|
|
case tsip_PUBLISH:
|
|
break;
|
|
default:
|
|
/* Is there a To tag? */
|
|
if(response->To && !response->To->tag){
|
|
response->To->tag = tsk_strdup(self->tag_local);
|
|
}
|
|
/* Contact Header (for 101-299 reponses) */
|
|
if(self->uri_local && TSIP_RESPONSE_CODE(response) >= 101 && TSIP_RESPONSE_CODE(response) <= 299){
|
|
char* contact = tsk_null;
|
|
tsip_header_Contacts_L_t *hdr_contacts;
|
|
|
|
tsk_sprintf(&contact, "m: <%s:%s@%s:%d>\r\n", "sip", self->uri_local->user_name, "127.0.0.1", 5060);
|
|
hdr_contacts = tsip_header_Contact_parse(contact, tsk_strlen(contact));
|
|
if(!TSK_LIST_IS_EMPTY(hdr_contacts)){
|
|
response->Contact = tsk_object_ref(hdr_contacts->head->data);
|
|
response->update = tsk_true; /* Now signal that the message should be updated by the transport layer (Contact header) */
|
|
}
|
|
TSK_OBJECT_SAFE_FREE(hdr_contacts);
|
|
TSK_FREE(contact);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* SigComp */
|
|
if(self->ss->sigcomp_id){
|
|
/* should be added in this field instead of 'Contact' or 'Via' headers
|
|
* it's up to the transport layer to copy it to these headers */
|
|
response->sigcomp_id = tsk_strdup(self->ss->sigcomp_id);
|
|
}
|
|
/* Remote Addr: used to send requests if "Server Mode" otherwise Proxy-CSCF address will be used */
|
|
self->remote_addr = request->remote_addr;
|
|
}
|
|
return response;
|
|
}
|
|
|
|
int tsip_dialog_response_send(const tsip_dialog_t *self, tsip_response_t* response)
|
|
{
|
|
int ret = -1;
|
|
|
|
if(self && TSIP_DIALOG_GET_STACK(self)){
|
|
const tsip_transac_layer_t *layer = TSIP_DIALOG_GET_STACK(self)->layer_transac;
|
|
if(layer){
|
|
/* As this is a response ...then use the associate server transaction */
|
|
tsip_transac_t *transac = tsip_transac_layer_find_server(layer, response);
|
|
if(transac){
|
|
ret = transac->callback(transac, tsip_transac_outgoing_msg, response);
|
|
tsk_object_unref(transac);
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Failed to find associated server transaction.");
|
|
// Send "408 Request Timeout" (should be done by the transaction layer)?
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int tsip_dialog_apply_action(tsip_message_t* message, const tsip_action_t* action)
|
|
{
|
|
const tsk_list_item_t* item;
|
|
|
|
if(!message || !action){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
/* SIP headers */
|
|
tsk_list_foreach(item, action->headers){
|
|
TSIP_MESSAGE_ADD_HEADER(message, TSIP_HEADER_DUMMY_VA_ARGS(TSK_PARAM(item->data)->name, TSK_PARAM(item->data)->value));
|
|
}
|
|
/* Payload */
|
|
if(action->payload){
|
|
tsip_message_add_content(message, tsk_null, TSK_BUFFER_DATA(action->payload), TSK_BUFFER_SIZE(action->payload));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Gets the number of milliseconds to wait before retransmission.
|
|
* e.g. ==> delay before refreshing registrations (REGISTER), subscribtions (SUBSCRIBE), publication (PUBLISH) ...
|
|
*
|
|
*
|
|
* @param [in,out] self The calling dialog.
|
|
* @param [in,out] response The SIP/IMS response containing the new delay (expires, subscription-state ...).
|
|
*
|
|
* @return Zero if succeed and no-zero error code otherwise.
|
|
**/
|
|
int64_t tsip_dialog_get_newdelay(tsip_dialog_t *self, const tsip_response_t* response)
|
|
{
|
|
int64_t expires = self->expires;
|
|
int64_t newdelay = expires; /* default value */
|
|
const tsip_header_t* hdr;
|
|
tsk_size_t i;
|
|
|
|
/*== NOTIFY with subscription-state header with expires parameter.
|
|
*/
|
|
if(TSIP_RESPONSE_IS_TO_NOTIFY(response)){
|
|
const tsip_header_Subscription_State_t *hdr_state;
|
|
if((hdr_state = (const tsip_header_Subscription_State_t*)tsip_message_get_header(response, tsip_htype_Subscription_State))){
|
|
if(hdr_state->expires >0){
|
|
expires = TSK_TIME_S_2_MS(hdr_state->expires);
|
|
goto compute;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*== Expires header.
|
|
*/
|
|
if((hdr = tsip_message_get_header(response, tsip_htype_Expires))){
|
|
expires = TSK_TIME_S_2_MS(((const tsip_header_Expires_t*)hdr)->delta_seconds);
|
|
goto compute;
|
|
}
|
|
|
|
/*== Contact header.
|
|
*/
|
|
for(i=0; (hdr = tsip_message_get_headerAt(response, tsip_htype_Contact, i)); i++){
|
|
const tsip_header_Contact_t* contact = (const tsip_header_Contact_t*)hdr;
|
|
if(contact && contact->uri)
|
|
{
|
|
const char* transport = tsk_params_get_param_value(contact->uri->params, "transport");
|
|
tsip_uri_t* contactUri = tsip_stack_get_contacturi(TSIP_DIALOG_GET_STACK(self), transport ? transport : "udp");
|
|
if(contactUri)
|
|
{
|
|
if(tsk_strequals(contact->uri->user_name, contactUri->user_name)
|
|
&& tsk_strequals(contact->uri->host, contactUri->host)
|
|
&& contact->uri->port == contactUri->port)
|
|
{
|
|
if(contact->expires>=0){ /* No expires parameter ==> -1*/
|
|
expires = TSK_TIME_S_2_MS(contact->expires);
|
|
|
|
TSK_OBJECT_SAFE_FREE(contactUri);
|
|
goto compute;
|
|
}
|
|
}
|
|
TSK_OBJECT_SAFE_FREE(contactUri);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 3GPP TS 24.229 -
|
|
*
|
|
* The UE shall reregister the public user identity either 600 seconds before the expiration time if the initial
|
|
* registration was for greater than 1200 seconds, or when half of the time has expired if the initial registration
|
|
* was for 1200 seconds or less.
|
|
*/
|
|
compute:
|
|
expires = TSK_TIME_MS_2_S(expires);
|
|
newdelay = (expires > 1200) ? (expires - 600) : (expires/2);
|
|
|
|
return TSK_TIME_S_2_MS(newdelay);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Updates the dialog state:
|
|
* - Authorizations (using challenges from the @a response message)
|
|
* - State (early, established, disconnected, ...)
|
|
* - Routes (and Service-Route)
|
|
* - Target (remote)
|
|
* - ...
|
|
*
|
|
* @param [in,out] self The calling dialog.
|
|
* @param [in,out] response The SIP/IMS response from which to get the new information.
|
|
*
|
|
* @return Zero if succeed and no-zero error code otherwise.
|
|
**/
|
|
int tsip_dialog_update(tsip_dialog_t *self, const tsip_response_t* response)
|
|
{
|
|
if(self && TSIP_MESSAGE_IS_RESPONSE(response) && response->To){
|
|
short code = TSIP_RESPONSE_CODE(response);
|
|
const char *tag = response->To->tag;
|
|
|
|
/*
|
|
* 1xx (!100) or 2xx
|
|
*/
|
|
/*
|
|
* 401 or 407 or 421 or 494
|
|
*/
|
|
if(code == 401 || code == 407 || code == 421 || code == 494)
|
|
{
|
|
tsk_bool_t acceptNewVector;
|
|
|
|
/* 3GPP IMS - Each authentication vector is used only once.
|
|
* ==> Re-registration/De-registration ==> Allow 401/407 challenge.
|
|
*/
|
|
acceptNewVector = (TSIP_RESPONSE_IS_TO_REGISTER(response) && self->state == tsip_established);
|
|
return tsip_dialog_update_challenges(self, response, acceptNewVector);
|
|
}
|
|
else if(100 < code && code < 300)
|
|
{
|
|
tsip_dialog_state_t state = self->state;
|
|
|
|
/* 1xx */
|
|
if(code <= 199){
|
|
if(tsk_strempty(response->To->tag)){
|
|
TSK_DEBUG_ERROR("Invalid tag parameter");
|
|
return -1;
|
|
}
|
|
state = tsip_early;
|
|
}
|
|
/* 2xx */
|
|
else{
|
|
state = tsip_established;
|
|
}
|
|
|
|
/* Remote target */
|
|
{
|
|
/* RFC 3261 12.2.1.2 Processing the Responses
|
|
When a UAC receives a 2xx response to a target refresh request, it
|
|
MUST replace the dialog's remote target URI with the URI from the
|
|
Contact header field in that response, if present.
|
|
|
|
FIXME: Because PRACK/UPDATE sent before the session is established MUST have
|
|
the rigth target URI to be delivered to the UAS ==> Do not not check that we are connected
|
|
*/
|
|
if(!TSIP_RESPONSE_IS_TO_REGISTER(response) && response->Contact && response->Contact->uri){
|
|
TSK_OBJECT_SAFE_FREE(self->uri_remote_target);
|
|
self->uri_remote_target = tsip_uri_clone(response->Contact->uri, tsk_true, tsk_false);
|
|
}
|
|
}
|
|
|
|
/* Route sets */
|
|
{
|
|
tsk_size_t index;
|
|
const tsip_header_Record_Route_t *recordRoute;
|
|
tsip_header_Record_Route_t *route;
|
|
|
|
TSK_OBJECT_SAFE_FREE(self->record_routes);
|
|
|
|
for(index = 0; (recordRoute = (const tsip_header_Record_Route_t *)tsip_message_get_headerAt(response, tsip_htype_Record_Route, index)); index++){
|
|
if(!self->record_routes){
|
|
self->record_routes = tsk_list_create();
|
|
}
|
|
if((route = tsk_object_ref((void*)recordRoute))){
|
|
tsk_list_push_front_data(self->record_routes, (void**)&route); /* Copy reversed. */
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* cseq + tags + ... */
|
|
if(self->state == tsip_established && tsk_striequals(self->tag_remote, tag)){
|
|
return 0;
|
|
}
|
|
else{
|
|
if(!TSIP_RESPONSE_IS_TO_REGISTER(response) && !TSIP_RESPONSE_IS_TO_PUBLISH(response)){ /* REGISTER and PUBLISH don't establish dialog */
|
|
tsk_strupdate(&self->tag_remote, tag);
|
|
}
|
|
#if 0 // PRACK and BYE will have same CSeq value ==> Let CSeq value to be incremented by "tsip_dialog_request_new()"
|
|
self->cseq_value = response->CSeq ? response->CSeq->seq : self->cseq_value;
|
|
#endif
|
|
}
|
|
|
|
self->state = state;
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int tsip_dialog_update_2(tsip_dialog_t *self, const tsip_request_t* invite)
|
|
{
|
|
if(!self || !invite){
|
|
TSK_DEBUG_ERROR("Invalid parameter");
|
|
return -1;
|
|
}
|
|
|
|
/* Remote target */
|
|
if(invite->Contact && invite->Contact->uri){
|
|
TSK_OBJECT_SAFE_FREE(self->uri_remote_target);
|
|
self->uri_remote_target = tsip_uri_clone(invite->Contact->uri, tsk_true, tsk_false);
|
|
}
|
|
|
|
/* cseq + tags + remote-uri */
|
|
tsk_strupdate(&self->tag_remote, invite->From?invite->From->tag:"doubango");
|
|
/* self->cseq_value = invite->CSeq ? invite->CSeq->seq : self->cseq_value; */
|
|
if(invite->From && invite->From->uri){
|
|
TSK_OBJECT_SAFE_FREE(self->uri_remote);
|
|
self->uri_remote = tsk_object_ref(invite->From->uri);
|
|
}
|
|
|
|
/* Route sets */
|
|
{
|
|
tsk_size_t index;
|
|
const tsip_header_Record_Route_t *recordRoute;
|
|
tsip_header_Record_Route_t* route;
|
|
|
|
TSK_OBJECT_SAFE_FREE(self->record_routes);
|
|
|
|
for(index = 0; (recordRoute = (const tsip_header_Record_Route_t *)tsip_message_get_headerAt(invite, tsip_htype_Record_Route, index)); index++){
|
|
if(!self->record_routes){
|
|
self->record_routes = tsk_list_create();
|
|
}
|
|
if((route = tsk_object_ref((void*)recordRoute))){
|
|
tsk_list_push_front_data(self->record_routes, (void**)&route); /* Copy non-reversed. */
|
|
}
|
|
}
|
|
}
|
|
|
|
self->state = tsip_established;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tsip_dialog_getCKIK(tsip_dialog_t *self, AKA_CK_T *ck, AKA_IK_T *ik)
|
|
{
|
|
tsk_list_item_t *item;
|
|
tsip_challenge_t *challenge;
|
|
|
|
if(!self){
|
|
return -1;
|
|
}
|
|
|
|
tsk_list_foreach(item, self->challenges)
|
|
{
|
|
if((challenge = item->data)){
|
|
memcpy(*ck, challenge->ck, AKA_CK_SIZE);
|
|
memcpy(*ik, challenge->ik, AKA_IK_SIZE);
|
|
return 0;
|
|
}
|
|
}
|
|
TSK_DEBUG_ERROR("No challenge found. Fail to set IK and CK.");
|
|
return -2;
|
|
}
|
|
|
|
int tsip_dialog_update_challenges(tsip_dialog_t *self, const tsip_response_t* response, int acceptNewVector)
|
|
{
|
|
int ret = -1;
|
|
tsk_size_t i;
|
|
|
|
tsk_list_item_t *item;
|
|
|
|
tsip_challenge_t *challenge;
|
|
|
|
const tsip_header_WWW_Authenticate_t *WWW_Authenticate;
|
|
const tsip_header_Proxy_Authenticate_t *Proxy_Authenticate;
|
|
|
|
/* RFC 2617 - HTTP Digest Session
|
|
|
|
* (A) The client response to a WWW-Authenticate challenge for a protection
|
|
space starts an authentication session with that protection space.
|
|
The authentication session lasts until the client receives another
|
|
WWW-Authenticate challenge from any server in the protection space.
|
|
|
|
(B) The server may return a 401 response with a new nonce value, causing the client
|
|
to retry the request; by specifying stale=TRUE with this response,
|
|
the server tells the client to retry with the new nonce, but without
|
|
prompting for a new username and password.
|
|
*/
|
|
/* RFC 2617 - 1.2 Access Authentication Framework
|
|
The realm directive (case-insensitive) is required for all authentication schemes that issue a challenge.
|
|
*/
|
|
|
|
/* FIXME: As we perform the same task ==> Use only one loop.
|
|
*/
|
|
|
|
for(i =0; (WWW_Authenticate = (const tsip_header_WWW_Authenticate_t*)tsip_message_get_headerAt(response, tsip_htype_WWW_Authenticate, i)); i++){
|
|
tsk_bool_t isnew = tsk_true;
|
|
|
|
tsk_list_foreach(item, self->challenges){
|
|
challenge = item->data;
|
|
if(challenge->isproxy) continue;
|
|
|
|
if(tsk_striequals(challenge->realm, WWW_Authenticate->realm) && (WWW_Authenticate->stale || acceptNewVector)){
|
|
/*== (B) ==*/
|
|
if((ret = tsip_challenge_update(challenge,
|
|
WWW_Authenticate->scheme,
|
|
WWW_Authenticate->realm,
|
|
WWW_Authenticate->nonce,
|
|
WWW_Authenticate->opaque,
|
|
WWW_Authenticate->algorithm,
|
|
WWW_Authenticate->qop)))
|
|
{
|
|
return ret;
|
|
}
|
|
else{
|
|
isnew = tsk_false;
|
|
continue;
|
|
}
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Failed to handle new challenge");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if(isnew){
|
|
if((challenge = tsip_challenge_create(TSIP_DIALOG_GET_STACK(self),
|
|
tsk_false,
|
|
WWW_Authenticate->scheme,
|
|
WWW_Authenticate->realm,
|
|
WWW_Authenticate->nonce,
|
|
WWW_Authenticate->opaque,
|
|
WWW_Authenticate->algorithm,
|
|
WWW_Authenticate->qop)))
|
|
{
|
|
tsk_list_push_back_data(self->challenges, (void**)&challenge);
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Failed to handle new challenge");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(i=0; (Proxy_Authenticate = (const tsip_header_Proxy_Authenticate_t*)tsip_message_get_headerAt(response, tsip_htype_Proxy_Authenticate, i)); i++){
|
|
tsk_bool_t isnew = tsk_true;
|
|
|
|
tsk_list_foreach(item, self->challenges){
|
|
challenge = item->data;
|
|
if(!challenge->isproxy){
|
|
continue;
|
|
}
|
|
|
|
if(tsk_striequals(challenge->realm, Proxy_Authenticate->realm) && (Proxy_Authenticate->stale || acceptNewVector)){
|
|
/*== (B) ==*/
|
|
if((ret = tsip_challenge_update(challenge,
|
|
Proxy_Authenticate->scheme,
|
|
Proxy_Authenticate->realm,
|
|
Proxy_Authenticate->nonce,
|
|
Proxy_Authenticate->opaque,
|
|
Proxy_Authenticate->algorithm,
|
|
Proxy_Authenticate->qop)))
|
|
{
|
|
return ret;
|
|
}
|
|
else{
|
|
isnew = tsk_false;
|
|
continue;
|
|
}
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Failed to handle new challenge");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if(isnew){
|
|
if((challenge = tsip_challenge_create(TSIP_DIALOG_GET_STACK(self),
|
|
tsk_true,
|
|
Proxy_Authenticate->scheme,
|
|
Proxy_Authenticate->realm,
|
|
Proxy_Authenticate->nonce,
|
|
Proxy_Authenticate->opaque,
|
|
Proxy_Authenticate->algorithm,
|
|
Proxy_Authenticate->qop)))
|
|
{
|
|
tsk_list_push_back_data(self->challenges, (void**)&challenge);
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Failed to handle new challenge");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
int tsip_dialog_add_common_headers(const tsip_dialog_t *self, tsip_request_t* request)
|
|
{
|
|
tsk_bool_t earlyIMS = tsk_false;
|
|
const tsip_uri_t* preferred_identity = tsk_null;
|
|
const char* netinfo = tsk_null;
|
|
|
|
if(!self || !request){
|
|
return -1;
|
|
}
|
|
|
|
earlyIMS = TSIP_DIALOG_GET_STACK(self)->security.earlyIMS;
|
|
preferred_identity = TSIP_DIALOG_GET_STACK(self)->identity.preferred;
|
|
|
|
//
|
|
// P-Preferred-Identity
|
|
//
|
|
if(preferred_identity){
|
|
/* 3GPP TS 33.978 6.2.3.1 Procedures at the UE
|
|
The UE shall use the temporary public user identity (IMSI-derived IMPU, cf. section 6.1.2) only in registration
|
|
messages (i.e. initial registration, re-registration or de-registration), but not in any other type of SIP requests.
|
|
*/
|
|
switch(request->line.request.request_type){
|
|
case tsip_BYE:
|
|
case tsip_INVITE:
|
|
case tsip_OPTIONS:
|
|
case tsip_SUBSCRIBE:
|
|
case tsip_NOTIFY:
|
|
case tsip_REFER:
|
|
case tsip_MESSAGE:
|
|
case tsip_PUBLISH:
|
|
case tsip_REGISTER:
|
|
{
|
|
if(!earlyIMS || (earlyIMS && TSIP_REQUEST_IS_REGISTER(request))){
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_P_PREFERRED_IDENTITY_VA_ARGS(preferred_identity));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// P-Access-Network-Info
|
|
//
|
|
if(netinfo)
|
|
{
|
|
switch(request->line.request.request_type){
|
|
case tsip_BYE:
|
|
case tsip_INVITE:
|
|
case tsip_OPTIONS:
|
|
case tsip_REGISTER:
|
|
case tsip_SUBSCRIBE:
|
|
case tsip_NOTIFY:
|
|
case tsip_PRACK:
|
|
case tsip_INFO:
|
|
case tsip_UPDATE:
|
|
case tsip_REFER:
|
|
case tsip_MESSAGE:
|
|
case tsip_PUBLISH:
|
|
{
|
|
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_P_ACCESS_NETWORK_INFO_VA_ARGS(netinfo));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tsip_dialog_init(tsip_dialog_t *self, tsip_dialog_type_t type, const char* call_id, tsip_ssession_t* ss, tsk_fsm_state_id curr, tsk_fsm_state_id term)
|
|
{
|
|
if(self){
|
|
if(self->initialized){
|
|
TSK_DEBUG_WARN("Dialog already initialized.");
|
|
return -2;
|
|
}
|
|
|
|
self->state = tsip_initial;
|
|
self->type = type;
|
|
if(!self->record_routes){
|
|
self->record_routes = tsk_list_create();
|
|
}
|
|
if(!self->challenges){
|
|
self->challenges = tsk_list_create();
|
|
}
|
|
|
|
/* Sets some defalt values */
|
|
self->expires = TSIP_SSESSION_EXPIRES_DEFAULT;
|
|
|
|
if(call_id){
|
|
/* "server-side" session */
|
|
tsk_strupdate(&self->callid, call_id);
|
|
}
|
|
else{
|
|
tsk_uuidstring_t uuid; /* Call-id is a random UUID */
|
|
tsip_header_Call_ID_random(&uuid);
|
|
tsk_strupdate(&self->callid, uuid);
|
|
}
|
|
|
|
/* ref SIP session */
|
|
self->ss = tsk_object_ref(ss);
|
|
|
|
/* Local tag */{
|
|
tsk_istr_t tag;
|
|
tsk_strrandom(&tag);
|
|
tsk_strupdate(&self->tag_local, tag);
|
|
}
|
|
|
|
/* CSeq */
|
|
self->cseq_value = (rand() + 1);
|
|
|
|
/* FSM */
|
|
self->fsm = tsk_fsm_create(curr, term);
|
|
|
|
/*=== SIP Session ===*/
|
|
if(self->ss != TSIP_SSESSION_INVALID_HANDLE){
|
|
|
|
/* Expires */
|
|
self->expires = ss->expires;
|
|
|
|
/* From */
|
|
self->uri_local = tsk_object_ref(call_id/* "server-side" */ ? ss->to : ss->from);
|
|
|
|
/* To */
|
|
if(ss->to){
|
|
self->uri_remote = tsk_object_ref(ss->to);
|
|
self->uri_remote_target = tsk_object_ref(ss->to); /* Request-URI. */
|
|
}
|
|
else{
|
|
self->uri_remote = tsk_object_ref(ss->from);
|
|
self->uri_remote_target = tsk_object_ref((void*)TSIP_DIALOG_GET_STACK(self)->network.realm);
|
|
}
|
|
}
|
|
else{
|
|
TSK_DEBUG_ERROR("Invalid SIP Session id.");
|
|
}
|
|
|
|
self->initialized = tsk_true;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int tsip_dialog_fsm_act(tsip_dialog_t* self, tsk_fsm_action_id action_id, const tsip_message_t* message, const tsip_action_handle_t* action)
|
|
{
|
|
if(!self || !self->fsm){
|
|
TSK_DEBUG_ERROR("Invalid parameter.");
|
|
return -1;
|
|
}
|
|
return tsk_fsm_act(self->fsm, action_id, self, message, self, message, action);
|
|
}
|
|
|
|
int tsip_dialog_set_curr_action(tsip_dialog_t* self, const tsip_action_t* action)
|
|
{
|
|
if(!self){
|
|
TSK_DEBUG_ERROR("Invalid parameter.");
|
|
return -1;
|
|
}
|
|
TSK_OBJECT_SAFE_FREE(self->curr_action);
|
|
if(action){
|
|
self->curr_action = tsk_object_ref((void*)action);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int tsip_dialog_set_lasterror_2(tsip_dialog_t* self, const char* phrase, short code, const tsip_message_t *message)
|
|
{
|
|
if(!self || tsk_strnullORempty(phrase)){
|
|
TSK_DEBUG_ERROR("Invalid parameter.");
|
|
return -1;
|
|
}
|
|
|
|
tsk_strupdate(&self->last_error.phrase, phrase);
|
|
self->last_error.code = code;
|
|
TSK_OBJECT_SAFE_FREE(self->last_error.message);
|
|
if(message){
|
|
self->last_error.message = (tsip_message_t*)tsk_object_ref((void*)message);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int tsip_dialog_set_lasterror(tsip_dialog_t* self, const char* phrase, short code)
|
|
{
|
|
return tsip_dialog_set_lasterror_2(self, phrase, code, tsk_null);
|
|
}
|
|
|
|
int tsip_dialog_get_lasterror(const tsip_dialog_t* self, short *code, const char** phrase, const tsip_message_t **message)
|
|
{
|
|
if(!self){
|
|
TSK_DEBUG_ERROR("Invalid parameter.");
|
|
return -1;
|
|
}
|
|
|
|
if(code){
|
|
*code = self->last_error.code;
|
|
}
|
|
if(phrase){
|
|
*phrase = self->last_error.phrase;
|
|
}
|
|
|
|
if(message){
|
|
*message = self->last_error.message;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tsip_dialog_hangup(tsip_dialog_t *self, const tsip_action_t* action)
|
|
{
|
|
if(self){
|
|
if(self->state == tsip_established){
|
|
return tsip_dialog_fsm_act(self, tsip_atype_hangup, tsk_null, action);
|
|
}
|
|
else{
|
|
return tsip_dialog_fsm_act(self, tsip_atype_cancel, tsk_null, action);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int tsip_dialog_shutdown(tsip_dialog_t *self, const tsip_action_t* action)
|
|
{
|
|
if(self){
|
|
return tsip_dialog_fsm_act(self, tsip_atype_shutdown, tsk_null, action);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int tsip_dialog_remove(const tsip_dialog_t* self)
|
|
{
|
|
return tsip_dialog_layer_remove(TSIP_DIALOG_GET_STACK(self)->layer_dialog, TSIP_DIALOG(self));
|
|
}
|
|
|
|
int tsip_dialog_cmp(const tsip_dialog_t *d1, const tsip_dialog_t *d2)
|
|
{
|
|
if(d1 && d2){
|
|
if(
|
|
tsk_strequals(d1->callid, d2->callid)
|
|
&& (tsk_strequals(d1->tag_local, d2->tag_local))
|
|
&& (tsk_strequals(d1->tag_remote, d2->tag_remote))
|
|
)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int tsip_dialog_deinit(tsip_dialog_t *self)
|
|
{
|
|
if(self){
|
|
if(!self->initialized){
|
|
TSK_DEBUG_WARN("Dialog not initialized.");
|
|
return -2;
|
|
}
|
|
|
|
/* Cancel all transactions associated to this dialog (do it here before the dialog becomes unsafe) */
|
|
tsip_transac_layer_cancel_by_dialog(TSIP_DIALOG_GET_STACK(self)->layer_transac, self);
|
|
|
|
TSK_OBJECT_SAFE_FREE(self->ss);
|
|
TSK_OBJECT_SAFE_FREE(self->curr_action);
|
|
|
|
TSK_OBJECT_SAFE_FREE(self->uri_local);
|
|
TSK_FREE(self->tag_local);
|
|
TSK_OBJECT_SAFE_FREE(self->uri_remote);
|
|
TSK_FREE(self->tag_remote);
|
|
|
|
TSK_OBJECT_SAFE_FREE(self->uri_remote_target);
|
|
|
|
TSK_FREE(self->cseq_method);
|
|
TSK_FREE(self->callid);
|
|
|
|
TSK_FREE(self->last_error.phrase);
|
|
TSK_OBJECT_SAFE_FREE(self->last_error.message);
|
|
|
|
TSK_OBJECT_SAFE_FREE(self->record_routes);
|
|
TSK_OBJECT_SAFE_FREE(self->challenges);
|
|
|
|
TSK_OBJECT_SAFE_FREE(self->fsm);
|
|
|
|
self->initialized = 0;
|
|
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|