doubango/trunk/tinySIP/src/dialogs/tsip_dialog.c

803 lines
23 KiB
C

/*
* Copyright (C) 2009 Mamadou Diop.
*
* Contact: Mamadou Diop <diopmamadou@yahoo.fr>
*
* 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)yahoo.fr>
*
* @date Created: Sat Nov 8 16:54:58 2009 mdiop
*/
#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/headers/tsip_header_Authorization.h"
#include "tinysip/headers/tsip_header_Contact.h"
#include "tinysip/headers/tsip_header_Expires.h"
#include "tinysip/headers/tsip_header_Proxy_Authenticate.h"
#include "tinysip/headers/tsip_header_Proxy_Authorization.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"
int tsip_dialog_update_challenges(tsip_dialog_t *self, const tsip_response_t* response, int acceptNewVector);
tsip_request_t *tsip_dialog_request_new(const tsip_dialog_t *self, const char* method)
{
tsip_request_t *request = 0;
tsip_uri_t *to_uri, *from_uri, *request_uri;
const char *call_id;
int copy_routes_start = -1; /* NONE */
/*
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->routes || TSK_LIST_IS_EMPTY(self->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
{
tsip_uri_t *first_route = self->routes->head->data;
if(tsk_params_has_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(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);
/*
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.
*/
{
char* contact = 0;
tsip_header_Contacts_L_t *hdr_contacts;
tsk_sprintf(&contact, "m: <%s:%s@%s:%d>;expires=%d;doubs\r\n", /*self->issecure*/0?"sips":"sip", from_uri->user_name, "127.0.0.1", 5060, self->expires);
hdr_contacts = tsip_header_Contact_parse(contact, 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);
}
/* P-Access-Network-Info */
if(TSIP_STACK(self->stack)->netinfo)
{
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_P_ACCESS_NETWORK_INFO_VA_ARGS(TSIP_STACK(self->stack)->netinfo));
}
/* Update authorizations */
if(TSK_LIST_IS_EMPTY(self->challenges))
{
if(tsk_striequals("REGISTER", method) && !TSIP_STACK(self->stack)->enable_earlyIMS)
{
/* 3GPP TS 34.229 - 5.1.1.1A === 3GPP TS 33.978 - 6.2.3.1
On sending a REGISTER request, the UE shall populate the header fields as follows:
a) the Authorization header, with:
- the username directive, set to the value of the private user identity;
- the realm directive, set to the domain name of the home network;
- the uri directive, set to the SIP URI of the domain name of the home network;
- the nonce directive, set to an empty value; and
- the response directive, set to an empty value.
*/
tsip_header_t* auth_hdr = tsip_challenge_create_empty_header_authorization("sip:fixme@micromethod.com", "fixme:realm", "uri");
tsip_message_add_header(request, auth_hdr);
tsk_object_unref(auth_hdr), auth_hdr = 0;
}
}
else
{
tsk_list_item_t *item;
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 = 0;
}
}
}
/* Update CSeq */
if(!tsk_striequals(method, "ACK") && !tsk_striequals(method, "CANCEL"))
{
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.
*/
if(!tsk_striequals("REGISTER", method))
{ // According to the above link ==> Initial/Re/De registration do not have routes.
tsk_list_item_t* item;
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->routes)
{
const tsip_uri_t* uri = item->data;
if(++index < copy_routes_start || !uri){
continue;
}
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_ROUTE_VA_ARGS(uri));
}
}
}
else
{ /* No routes associated to this dialog. */
if(self->state == tsip_initial || self->state == tsip_early)
{
tsip_uri_t *uri = tsip_stack_get_pcscf_uri(TSIP_DIALOG_GET_STACK(self), 1);
// Proxy-CSCF as first route
if(uri){
TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_ROUTE_VA_ARGS(uri));
TSK_OBJECT_SAFE_FREE(uri);
}
// 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));
}
}
}
}
TSK_OBJECT_SAFE_FREE(request_uri);
TSK_OBJECT_SAFE_FREE(from_uri);
TSK_OBJECT_SAFE_FREE(to_uri);
return request;
}
/**
* @fn int tsip_dialog_request_send(const tsip_dialog_t *self, tsip_request_t* request)
*
* @brief Sends a SIP/IMS request. This function is responsible for transaction creation.
*
* @author Mamadou
* @date 1/4/2010
*
* @param [in,out] self The parent dialog. All callback events will be notified to this dialog.
* @param [in,out] 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)
{
if(self && self->stack)
{
const tsip_transac_layer_t *layer = tsip_stack_get_transac_layer(self->stack);
if(layer)
{
/* Create new transaction. The new transaction will be added to the dialog 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.
*/
/*const*/ tsip_transac_t *transac = tsip_transac_layer_new(layer, 1, request);
/* Set the transaction's dialog. All events comming from the transaction (timeouts, errors ...) will be signaled to this dialog.
*/
/*TSIP_TRANSAC*/(transac)->dialog = self;
if(transac)
{
switch(transac->type)
{
case tsip_ict:
{
/* Start the newly create IC transaction.
*/
break;
}
case tsip_nict:
{
/* Start the newly created NIC transaction.
*/
tsip_transac_start(transac, request);
break;
}
default: return -1;
}
return 0;
}
}
}
return -1;
}
tsip_response_t *tsip_dialog_response_new(const 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 = tsip_response_new(status, phrase, request);
return response;
}
int tsip_dialog_response_send(const tsip_dialog_t *self, tsip_response_t* response)
{
int ret = -1;
if(self && self->stack)
{
const tsip_transac_layer_t *layer = tsip_stack_get_transac_layer(self->stack);
if(layer)
{
/* As this is a response ...then there should be a matching server transaction.
*/
const tsip_transac_t *transac = tsip_transac_layer_find_server(layer, response);
if(transac)
{
ret = transac->callback(transac, tsip_transac_outgoing_msg, response);
}
}
}
return ret;
}
/**
* @fn int tsip_dialog_get_newdelay(tsip_dialog_t *self, const tsip_response_t* response)
*
* @brief Gets the number of milliseconds to wait before retransmission.
* e.g. ==> delay before refreshing registrations (REGISTER), subscribtions (SUBSCRIBE), publication (PUBLISH) ...
*
* @author Mamadou
* @date 1/4/2010
*
* @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.
**/
int tsip_dialog_get_newdelay(tsip_dialog_t *self, const tsip_response_t* response)
{
int expires = self->expires;
int newdelay = expires; /* default value */
const tsip_header_t* hdr;
size_t i;
/*== NOTIFY with subscription-state header with expires parameter.
*/
if(response->CSeq && tsk_striequals(response->CSeq->method, "NOTIFY")){
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 = hdr_state->expires;
goto compute;
}
}
}
/*== Expires header.
*/
if((hdr = tsip_message_get_header(response, tsip_htype_Expires))){
expires = ((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)
{
tsip_uri_t* contactUri = tsip_stack_get_contacturi(self->stack, tsk_params_get_param_value(contact->uri->params, "transport"));
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 = 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:
newdelay = (expires > 1200) ? (expires-600) : (expires/2);
return newdelay;
}
/**
* @fn int tsip_dialog_update(tsip_dialog_t *self, const tsip_response_t* response)
*
* @brief Updates the dialog state:
* - Authorizations (using challenges from the @ref response message)
* - State (early, established, disconnected, ...)
* - Routes (and Service-Route)
* - Target (remote)
* - ...
*
* @author Mamadou
* @date 1/4/2010
*
* @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 = response->status_code;
const char *tag = response->To->tag;
int isRegister = response->CSeq ? tsk_striequals(response->CSeq->method, "REGISTER") : 0;
/*
* 1xx (!100) or 2xx
*/
/*
* 401 or 407 or 421 or 494
*/
if(code == 401 || code == 407 || code == 421 || code == 494)
{
int acceptNewVector;
/* 3GPP IMS - Each authentication vector is used only once.
* ==> Re-registration/De-registration ==> Allow 401/407 challenge.
*/
acceptNewVector = (isRegister && 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(TSIP_RESPONSE_CODE(response) <= 199)
{
if(tsk_strempty(response->To->tag)) return -1;
state = tsip_early;
}
/* 2xx */
else
{
state = tsip_established;
}
/* Remote target */
if(!isRegister && response->Contact)
{
TSK_OBJECT_SAFE_FREE(self->uri_remote_target);
if(response->Contact->uri)
{
self->uri_remote_target = tsip_uri_clone(response->Contact->uri, 0, 0);
}
}
/* Route sets
*/
{
size_t index;
const tsip_header_t *hdr;
TSK_OBJECT_SAFE_FREE(self->routes);
for(index = 0; (hdr = tsip_message_get_headerAt(response, tsip_htype_Record_Route, index)); index++){
if(!self->routes){
self->routes = TSK_LIST_CREATE();
}
hdr = tsk_object_ref((void*)hdr);
tsk_list_push_front_data(self->routes, (void**)&hdr); /* Copy reversed. */
}
}
/* cseq + tags + ... */
if(self->state == tsip_established && tsk_striequals(self->tag_remote, tag)){
return 0;
}
else{
if(!isRegister){
}
tsk_strupdate(&self->tag_remote, tag);
self->cseq_value = response->CSeq ? response->CSeq->seq : self->cseq_value;
}
self->state = state;
return 0;
}
}
return -1;
}
int tsip_dialog_update_challenges(tsip_dialog_t *self, const tsip_response_t* response, int acceptNewVector)
{
int ret = -1;
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 - Digest Operation
* (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.
*/
/* 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++)
{
int isnew = 1;
tsk_list_foreach(item, self->challenges)
{
challenge = item->data;
if(challenge->isproxy) continue;
if(tsk_strequals(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 = 0;
continue;
}
}
else return -1;
}
if(isnew)
{
if((challenge = TSIP_CHALLENGE_CREATE(self->stack,
0,
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 return -1;
}
}
for(i=0; (Proxy_Authenticate = (const tsip_header_Proxy_Authenticate_t*)tsip_message_get_headerAt(response, tsip_htype_Proxy_Authenticate, i)); i++)
{
int isnew = 1;
tsk_list_foreach(item, self->challenges)
{
challenge = item->data;
if(!challenge->isproxy) continue;
if(tsk_strequals(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 = 0;
continue;
}
}
else return -1;
}
if(isnew)
{
if((challenge = TSIP_CHALLENGE_CREATE(self->stack,
1,
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 return -1;
}
}
return 0;
}
int tsip_dialog_init(tsip_dialog_t *self, tsip_dialog_type_t type, tsip_stack_handle_t * stack, const char* call_id, tsip_operation_handle_t* operation)
{
if(self)
{
const tsk_param_t* param;
if(self->initialized)
{
TSK_DEBUG_WARN("Dialog already initialized.");
return -2;
}
tsk_safeobj_init(self);
self->state = tsip_initial;
self->type = type;
self->stack = tsk_object_ref(stack);
if(!self->routes){
self->routes = TSK_LIST_CREATE();
}
if(!self->challenges){
self->challenges = TSK_LIST_CREATE();
}
self->expires = TSIP_DIALOG_EXPIRES_DEFAULT;
if(call_id){
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);
}
self->operation = tsk_object_ref(operation);
/* Local tag */
{
tsk_istr_t tag;
tsk_strrandom(&tag);
tsk_strupdate(&self->tag_local, tag);
}
/* CSeq */
self->cseq_value = rand();
/* Expires */
if((param = tsip_operation_get_param(TSIP_DIALOG(self)->operation, "expires"))){
self->expires = atoi(param->value);
}
else{
self->expires = TSIP_DIALOG_EXPIRES_DEFAULT;
}
self->initialized = 1;
return 0;
}
return -1;
}
int tsip_dialog_hangup(tsip_dialog_t *self)
{
if(self)
{
return self->callback(self, tsip_dialog_hang_up, 0);
}
return -1;
}
int tsip_dialog_remove(const tsip_dialog_t* self)
{
return tsip_dialog_layer_remove(TSIP_STACK(self->stack)->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;
}
tsk_object_unref(self->stack);
tsk_object_unref(self->operation);
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_OBJECT_SAFE_FREE(self->routes);
TSK_OBJECT_SAFE_FREE(self->challenges);
self->initialized = 0;
tsk_safeobj_deinit(self);
return 0;
}
return -1;
}