diff options
author | Mamadou DIOP <bossiel@yahoo.fr> | 2015-08-17 01:56:35 +0200 |
---|---|---|
committer | Mamadou DIOP <bossiel@yahoo.fr> | 2015-08-17 01:56:35 +0200 |
commit | 631fffee8a28b1bec5ed1f1d26a20e0135967f99 (patch) | |
tree | 74afe3bf3efe15aa82bcd0272b2b0f4d48c2d837 /tinySIP/src/dialogs | |
parent | 7908865936604036e6f200f1b5e069f8752f3a3a (diff) | |
download | doubango-631fffee8a28b1bec5ed1f1d26a20e0135967f99.zip doubango-631fffee8a28b1bec5ed1f1d26a20e0135967f99.tar.gz |
-
Diffstat (limited to 'tinySIP/src/dialogs')
21 files changed, 11266 insertions, 0 deletions
diff --git a/tinySIP/src/dialogs/tsip_dialog.c b/tinySIP/src/dialogs/tsip_dialog.c new file mode 100644 index 0000000..13dcdf4 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog.c @@ -0,0 +1,1354 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog.h" + +#include "tinysip/dialogs/tsip_dialog_layer.h" +#include "tinysip/transactions/tsip_transac_layer.h" +#include "tinysip/transports/tsip_transport_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_session_headers(const tsip_dialog_t *self, tsip_request_t* request); +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, tnet_socket_type_t type, tsk_bool_t lr); +extern tsip_uri_t* tsip_stack_get_contacturi(const tsip_stack_t *self, const char* protocol); + +#define TSIP_DIALOG_ADD_HEADERS(headers) {\ + const tsk_list_item_t* item;\ + tsk_list_foreach(item, headers){ \ + if(!TSK_PARAM(item->data)->tag){ \ + /* 'Route' is special header as it's used to find next destination address */ \ + if(tsk_striequals(TSK_PARAM(item->data)->name, "route")){ \ + tsip_uri_t* route_uri; \ + char* route_uri_str = tsk_strdup(TSK_PARAM(item->data)->value); \ + tsk_strunquote_2(&route_uri_str, '<', '>'); \ + route_uri = tsip_uri_parse(route_uri_str, tsk_strlen(route_uri_str)); \ + if(route_uri){ \ + tsip_message_add_headers(request, \ + TSIP_HEADER_ROUTE_VA_ARGS(route_uri), \ + tsk_null); \ + TSK_OBJECT_SAFE_FREE(route_uri); \ + } \ + TSK_FREE(route_uri_str); \ + } \ + else{ \ + TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_DUMMY_VA_ARGS(TSK_PARAM(item->data)->name, TSK_PARAM(item->data)->value)); \ + } \ + } \ + }\ + } + + +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(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%s%s%s%s%s%s%s%s%s>\r\n", + "sip", + from_uri->user_name, + "127.0.0.1", + 5060, + + self->ss->ws.src.host ? ";" : "", + self->ss->ws.src.host ? "ws-src-ip=" : "", + self->ss->ws.src.host ? self->ss->ws.src.host : "", + self->ss->ws.src.port[0] ? ";" : "", + self->ss->ws.src.port[0] ? "ws-src-port=" : "", + self->ss->ws.src.port[0] ? self->ss->ws.src.port : "", + self->ss->ws.src.proto ? ";" : "", + self->ss->ws.src.proto ? "ws-src-proto=" : "", + self->ss->ws.src.proto ? self->ss->ws.src.proto : "" + ); + } + 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) */ +#elif 0 + 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 session */ + tsip_dialog_add_session_headers(self, request); + + /* Add headers associated to the dialog's stack */ + TSIP_DIALOG_ADD_HEADERS(self->ss->stack->headers); + + /* 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; + /* Connected FD */ + if(request->local_fd <= 0) { + request->local_fd = self->connected_fd; + } + + 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. + */ + static const tsk_bool_t isCT = tsk_true; + tsip_transac_t* transac; + tsip_transac_dst_t* dst; + + + if(TSIP_STACK_MODE_IS_CLIENT(TSIP_DIALOG_GET_STACK(self))){ + const tsip_transport_t* transport = tsip_transport_layer_find_by_idx(TSIP_DIALOG_GET_STACK(self)->layer_transport, TSIP_DIALOG_GET_STACK(self)->network.transport_idx_default); + if(!transport){ + TSK_DEBUG_ERROR("Failed to find a valid default transport [%d]", TSIP_DIALOG_GET_STACK(self)->network.transport_idx_default); + } + else{ + request->dst_net_type = transport->type; + } + } + dst = tsip_transac_dst_dialog_create(TSIP_DIALOG(self)); + transac = tsip_transac_layer_new( + layer, + isCT, + request, + dst + ); + TSK_OBJECT_SAFE_FREE(dst); + + /* 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_transac_type_ict: + case tsip_transac_type_nict: + { + /* Start the newly create IC/NIC transaction */ + ret = tsip_transac_start(transac, request); + break; + } + default: break; + } + TSK_OBJECT_SAFE_FREE(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); + } + /* Connected FD */ + if(response->local_fd <= 0) { + response->local_fd = self->connected_fd; + } + /* 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_message_t* message) +{ + 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_REQUEST_IS_NOTIFY(message)){ + const tsip_header_Subscription_State_t *hdr_state; + if((hdr_state = (const tsip_header_Subscription_State_t*)tsip_message_get_header(message, 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(message, 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(message, 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_strnullORempty(response->To->tag)){ + TSK_DEBUG_WARN("Invalid tag parameter"); + return 0; + } + 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_back_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))) + { + if(TSIP_DIALOG_GET_SS(self)->auth_ha1 && TSIP_DIALOG_GET_SS(self)->auth_impi){ + tsip_challenge_set_cred(challenge, TSIP_DIALOG_GET_SS(self)->auth_impi, TSIP_DIALOG_GET_SS(self)->auth_ha1); + } + 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))) + { + if(TSIP_DIALOG_GET_SS(self)->auth_ha1 && TSIP_DIALOG_GET_SS(self)->auth_impi){ + tsip_challenge_set_cred(challenge, TSIP_DIALOG_GET_SS(self)->auth_impi, TSIP_DIALOG_GET_SS(self)->auth_ha1); + } + 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_session_headers(const tsip_dialog_t *self, tsip_request_t* request) +{ + if(!self || !request){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + TSIP_DIALOG_ADD_HEADERS(self->ss->headers); + 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){ + TSK_DEBUG_ERROR("Invalid parameter"); + 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 && TSIP_STACK_MODE_IS_CLIENT(TSIP_DIALOG_GET_STACK(self))){ + /* 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; + } + default: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; + } + default: 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) +{ + static tsip_dialog_id_t unique_id = 0; + if(self){ + if(self->initialized){ + TSK_DEBUG_WARN("Dialog already initialized."); + return -2; + } + + self->state = tsip_initial; + self->type = type; + self->id = ++unique_id; + self->connected_fd = TNET_INVALID_FD; + 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."); + } + + tsk_safeobj_init(self); + + 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) +{ + int ret; + tsip_dialog_t* copy; + if(!self || !self->fsm){ + TSK_DEBUG_ERROR("Invalid parameter."); + return -1; + } + + tsk_safeobj_lock(self); + copy = tsk_object_ref(self); /* keep a copy because tsk_fsm_act() could destroy the dialog */ + ret = tsip_dialog_set_curr_action(copy, action); + ret = tsk_fsm_act(copy->fsm, action_id, copy, message, copy, message, action); + tsk_safeobj_unlock(copy); + tsk_object_unref(copy); + + return ret; +} + +/* +This function is used to know if we need to keep the same action handle after receiving a response to our last action. +*/ +tsk_bool_t tsip_dialog_keep_action(const tsip_dialog_t* self, const tsip_response_t *response) +{ + if(self && response){ + const short code = TSIP_RESPONSE_CODE(response); + return + TSIP_RESPONSE_IS_1XX(response) || + (code == 401 || code == 407 || code == 421 || code == 494) || + (code == 422 || code == 423); + } + return tsk_false; +} + +int tsip_dialog_set_connected_fd(tsip_dialog_t* self, tnet_fd_t fd) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + self->connected_fd = fd; + return 0; +} + +int tsip_dialog_set_curr_action(tsip_dialog_t* self, const tsip_action_t* action) +{ + tsip_action_t* new_action; + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter."); + return -1; + } + + new_action = tsk_object_ref((void*)action); + TSK_OBJECT_SAFE_FREE(self->curr_action); + self->curr_action = new_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){ + // CANCEL should only be sent for INVITE dialog + if(self->type != tsip_dialog_INVITE || 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); + } + } + TSK_DEBUG_ERROR("Invalid parameter"); + 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); + } + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; +} + +int tsip_dialog_signal_transport_error(tsip_dialog_t *self) +{ + if(self){ + return tsip_dialog_fsm_act(self, tsip_atype_transport_error, tsk_null, tsk_null); + } + TSK_DEBUG_ERROR("Invalid parameter"); + 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); + + /* Remove the dialog from the Stream peers */ + tsip_dialog_layer_remove_callid_from_stream_peers(TSIP_DIALOG_GET_STACK(self)->layer_dialog, self->callid); + + 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); + + tsk_safeobj_deinit(self); + + self->initialized = 0; + + return 0; + } + return -1; +} + diff --git a/tinySIP/src/dialogs/tsip_dialog_info.c b/tinySIP/src/dialogs/tsip_dialog_info.c new file mode 100644 index 0000000..0ff2536 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_info.c @@ -0,0 +1,556 @@ +/* Copyright (C) 2011 Doubango Telecom <http://www.doubango.org> +* Copyright (C) 2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango(dot)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_info.c + * @brief SIP dialog message (Client side). + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_info.h" +#include "tinysip/parsers/tsip_parser_uri.h" + +#include "tinysip/api/tsip_api_info.h" + +#include "tinysip/headers/tsip_header_Dummy.h" +#include "tinysip/headers/tsip_header_Min_Expires.h" + +#include "tinysip/transactions/tsip_transac_layer.h" + +#include "tsk_memory.h" +#include "tsk_debug.h" +#include "tsk_time.h" + +#define DEBUG_STATE_MACHINE 1 +#define TSIP_DIALOG_INFO_SIGNAL(self, type, code, phrase, message) \ + tsip_info_event_signal(type, TSIP_DIALOG(self)->ss, code, phrase, message) + +/* ======================== internal functions ======================== */ +static int send_INFO(tsip_dialog_info_t *self); +static int tsip_dialog_info_OnTerminated(tsip_dialog_info_t *self); + +/* ======================== transitions ======================== */ +static int tsip_dialog_info_Started_2_Sending_X_sendINFO(va_list *app); +static int tsip_dialog_info_Started_2_Receiving_X_recvINFO(va_list *app); +static int tsip_dialog_info_Sending_2_Sending_X_1xx(va_list *app); +static int tsip_dialog_info_Sending_2_Terminated_X_2xx(va_list *app); +static int tsip_dialog_info_Sending_2_Sending_X_401_407_421_494(va_list *app); +static int tsip_dialog_info_Sending_2_Terminated_X_300_to_699(va_list *app); +static int tsip_dialog_info_Sending_2_Terminated_X_cancel(va_list *app); +static int tsip_dialog_info_Receiving_2_Terminated_X_accept(va_list *app); +static int tsip_dialog_info_Receiving_2_Terminated_X_reject(va_list *app); +static int tsip_dialog_info_Any_2_Terminated_X_transportError(va_list *app); +static int tsip_dialog_info_Any_2_Terminated_X_Error(va_list *app); + +/* ======================== conds ======================== */ + +/* ======================== actions ======================== */ +typedef enum _fsm_action_e +{ + _fsm_action_sendINFO = tsip_atype_info_send, + _fsm_action_accept = tsip_atype_accept, + _fsm_action_reject = tsip_atype_reject, + _fsm_action_cancel = tsip_atype_cancel, + _fsm_action_shutdown = tsip_atype_shutdown, + _fsm_action_transporterror = tsip_atype_transport_error, + + _fsm_action_receiveINFO = 0xFF, + _fsm_action_1xx, + _fsm_action_2xx, + _fsm_action_401_407_421_494, + _fsm_action_300_to_699, + _fsm_action_error, +} +_fsm_action_t; + +/* ======================== states ======================== */ +typedef enum _fsm_state_e +{ + _fsm_state_Started, + _fsm_state_Sending, + _fsm_state_Receiving, + _fsm_state_Terminated +} +_fsm_state_t; + + +static int tsip_dialog_info_event_callback(const tsip_dialog_info_t *self, tsip_dialog_event_type_t type, const tsip_message_t *msg) +{ + int ret = -1; + + switch(type) + { + case tsip_dialog_i_msg: + { + if(msg) + { + if(TSIP_MESSAGE_IS_RESPONSE(msg)) + { + if(TSIP_RESPONSE_IS_1XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_1xx, msg, tsk_null); + } + else if(TSIP_RESPONSE_IS_2XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_2xx, msg, tsk_null); + } + else if(TSIP_RESPONSE_CODE(msg) == 401 || TSIP_RESPONSE_CODE(msg) == 407 || TSIP_RESPONSE_CODE(msg) == 421 || TSIP_RESPONSE_CODE(msg) == 494){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_401_407_421_494, msg, tsk_null); + } + else if(TSIP_RESPONSE_IS_3456(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_300_to_699, msg, tsk_null); + } + else{ /* Should never happen */ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_error, msg, tsk_null); + } + } + else if (TSIP_REQUEST_IS_INFO(msg)){ /* have been checked by dialog layer...but */ + // REQUEST ==> Incoming INFO + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_receiveINFO, msg, tsk_null); + } + } + break; + } + + case tsip_dialog_canceled: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_cancel, msg, tsk_null); + break; + } + + case tsip_dialog_terminated: + case tsip_dialog_timedout: + case tsip_dialog_error: + case tsip_dialog_transport_error: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_transporterror, msg, tsk_null); + break; + } + + default: break; + } + + return ret; +} + +tsip_dialog_info_t* tsip_dialog_info_create(const tsip_ssession_handle_t* ss) +{ + return tsk_object_new(tsip_dialog_info_def_t, ss); +} + +int tsip_dialog_info_init(tsip_dialog_info_t *self) +{ + //const tsk_param_t* param; + + /* Initialize the state machine. */ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (send) -> Sending + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_sendINFO, _fsm_state_Sending, tsip_dialog_info_Started_2_Sending_X_sendINFO, "tsip_dialog_info_Started_2_Sending_X_sendINFO"), + // Started -> (receive) -> Receiving + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_receiveINFO, _fsm_state_Receiving, tsip_dialog_info_Started_2_Receiving_X_recvINFO, "tsip_dialog_info_Started_2_Receiving_X_recvINFO"), + // Started -> (Any) -> Started + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Started, "tsip_dialog_info_Started_2_Started_X_any"), + + + /*======================= + * === Sending === + */ + // Sending -> (1xx) -> Sending + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_1xx, _fsm_state_Sending, tsip_dialog_info_Sending_2_Sending_X_1xx, "tsip_dialog_info_Sending_2_Sending_X_1xx"), + // Sending -> (2xx) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_2xx, _fsm_state_Terminated, tsip_dialog_info_Sending_2_Terminated_X_2xx, "tsip_dialog_info_Sending_2_Terminated_X_2xx"), + // Sending -> (401/407/421/494) -> Sending + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_401_407_421_494, _fsm_state_Sending, tsip_dialog_info_Sending_2_Sending_X_401_407_421_494, "tsip_dialog_info_Sending_2_Sending_X_401_407_421_494"), + // Sending -> (300_to_699) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_300_to_699, _fsm_state_Terminated, tsip_dialog_info_Sending_2_Terminated_X_300_to_699, "tsip_dialog_info_Sending_2_Terminated_X_300_to_699"), + // Sending -> (cancel) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_cancel, _fsm_state_Terminated, tsip_dialog_info_Sending_2_Terminated_X_cancel, "tsip_dialog_info_Sending_2_Terminated_X_cancel"), + // Sending -> (shutdown) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_shutdown, _fsm_state_Terminated, tsk_null, "tsip_dialog_info_Sending_2_Terminated_X_shutdown"), + // Sending -> (Any) -> Sending + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Sending, "tsip_dialog_info_Sending_2_Sending_X_any"), + + /*======================= + * === Receiving === + */ + // Receiving -> (accept) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Receiving, _fsm_action_accept, _fsm_state_Terminated, tsip_dialog_info_Receiving_2_Terminated_X_accept, "tsip_dialog_info_Receiving_2_Terminated_X_accept"), + // Receiving -> (rejected) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Receiving, _fsm_action_reject, _fsm_state_Terminated, tsip_dialog_info_Receiving_2_Terminated_X_reject, "tsip_dialog_info_Receiving_2_Terminated_X_reject"), + // Receiving -> (Any) -> Receiving + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Receiving, "tsip_dialog_info_Receiving_2_Receiving_X_any"), + + /*======================= + * === Any === + */ + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_transporterror, _fsm_state_Terminated, tsip_dialog_info_Any_2_Terminated_X_transportError, "tsip_dialog_info_Any_2_Terminated_X_transportError"), + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_error, _fsm_state_Terminated, tsip_dialog_info_Any_2_Terminated_X_Error, "tsip_dialog_info_Any_2_Terminated_X_Error"), + + TSK_FSM_ADD_NULL()); + + TSIP_DIALOG(self)->callback = TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_info_event_callback); + + return 0; +} + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + + +/* Started -> (sendINFO) -> Sending +*/ +int tsip_dialog_info_Started_2_Sending_X_sendINFO(va_list *app) +{ + tsip_dialog_info_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_info_t *); + va_arg(*app, tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + TSIP_DIALOG(self)->running = tsk_true; + + return send_INFO(self); +} + +/* Started -> (recvINFO) -> Receiving +*/ +int tsip_dialog_info_Started_2_Receiving_X_recvINFO(va_list *app) +{ + tsip_dialog_info_t *self = va_arg(*app, tsip_dialog_info_t *); + const tsip_request_t *request = va_arg(*app, const tsip_request_t *); + + /* Alert the user. */ + TSIP_DIALOG_INFO_SIGNAL(self, tsip_i_info, + tsip_event_code_dialog_request_incoming, "Incoming Request.", request); + + /* Update last incoming INFO */ + TSK_OBJECT_SAFE_FREE(self->last_iMessage); + self->last_iMessage = tsk_object_ref((void*)request); + + return 0; +} + +/* Sending -> (1xx) -> Sending +*/ +int tsip_dialog_info_Sending_2_Sending_X_1xx(va_list *app) +{ + /*tsip_dialog_info_t *self = va_arg(*app, tsip_dialog_info_t *);*/ + /*const tsip_response_t *response = va_arg(*app, const tsip_response_t *);*/ + + return 0; +} + +/* Sending -> (2xx) -> Sending +*/ +int tsip_dialog_info_Sending_2_Terminated_X_2xx(va_list *app) +{ + tsip_dialog_info_t *self = va_arg(*app, tsip_dialog_info_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* Alert the user. */ + TSIP_DIALOG_INFO_SIGNAL(self, tsip_ao_info, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + /* Reset curr action */ + tsip_dialog_set_curr_action(TSIP_DIALOG(self), tsk_null); + + return 0; +} + +/* Sending -> (401/407/421/494) -> Sending +*/ +int tsip_dialog_info_Sending_2_Sending_X_401_407_421_494(va_list *app) +{ + tsip_dialog_info_t *self = va_arg(*app, tsip_dialog_info_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + int ret; + + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + // Alert the user + TSIP_DIALOG_INFO_SIGNAL(self, tsip_ao_info, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return ret; + } + + return send_INFO(self); +} + +/* Sending -> (300 to 699) -> Terminated +*/ +int tsip_dialog_info_Sending_2_Terminated_X_300_to_699(va_list *app) +{ + tsip_dialog_info_t *self = va_arg(*app, tsip_dialog_info_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), TSIP_RESPONSE_PHRASE(response), TSIP_RESPONSE_CODE(response)); + + /* Alert the user. */ + TSIP_DIALOG_INFO_SIGNAL(self, tsip_ao_info, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return 0; +} + +/* Sending -> (cancel) -> Terminated +*/ +int tsip_dialog_info_Sending_2_Terminated_X_cancel(va_list *app) +{ + tsip_dialog_info_t *self = va_arg(*app, tsip_dialog_info_t *); + /* const tsip_message_t *message = va_arg(*app, const tsip_message_t *); */ + + /* RFC 3261 - 9.1 Client Behavior + A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + */ + + /* Cancel all transactions associated to this dialog (will also be done when the dialog is destroyed (worth nothing)) */ + tsip_transac_layer_cancel_by_dialog(TSIP_DIALOG_GET_STACK(self)->layer_transac, TSIP_DIALOG(self)); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_request_cancelled, "INFO cancelled"); + + return 0; +} + +/* Receiving -> (accept) -> Terminated +*/ +int tsip_dialog_info_Receiving_2_Terminated_X_accept(va_list *app) +{ + tsip_dialog_info_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_info_t *); + va_arg(*app, tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(!self->last_iMessage){ + TSK_DEBUG_ERROR("There is non INFO to accept()"); + /* Not an error ...but do not update current action */ + } + else{ + tsip_response_t *response; + int ret; + + /* curr_action is only used for outgoing requests */ + /* tsip_dialog_set_curr_action(TSIP_DIALOG(self), action); */ + + /* send 200 OK */ + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 200, "OK", self->last_iMessage))){ + tsip_dialog_apply_action(response, action); /* apply action params to "this" response */ + if((ret = tsip_dialog_response_send(TSIP_DIALOG(self), response))){ + TSK_DEBUG_ERROR("Failed to send SIP response."); + TSK_OBJECT_SAFE_FREE(response); + return ret; + } + TSK_OBJECT_SAFE_FREE(response); + } + else{ + TSK_DEBUG_ERROR("Failed to create SIP response."); + return -1; + } + } + + return 0; +} + +/* Receiving -> (reject) -> Terminated +*/ +int tsip_dialog_info_Receiving_2_Terminated_X_reject(va_list *app) +{ + tsip_dialog_info_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_info_t *); + va_arg(*app, tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(!self->last_iMessage){ + TSK_DEBUG_ERROR("There is non INFO to reject()"); + /* Not an error ...but do not update current action */ + } + else{ + tsip_response_t *response; + int ret; + + /* curr_action is only used for outgoing requests */ + /* tsip_dialog_set_curr_action(TSIP_DIALOG(self), action); */ + + /* send 486 Rejected */ + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 486, "Rejected", self->last_iMessage))){ + tsip_dialog_apply_action(response, action); /* apply action params to "this" response */ + if((ret = tsip_dialog_response_send(TSIP_DIALOG(self), response))){ + TSK_DEBUG_ERROR("Failed to send SIP response."); + TSK_OBJECT_SAFE_FREE(response); + return ret; + } + TSK_OBJECT_SAFE_FREE(response); + } + else{ + TSK_DEBUG_ERROR("Failed to create SIP response."); + return -1; + } + } + + return 0; +} + +/* Any -> (transport error) -> Terminated +*/ +int tsip_dialog_info_Any_2_Terminated_X_transportError(va_list *app) +{ + /*tsip_dialog_info_t *self = va_arg(*app, tsip_dialog_info_t *);*/ + /*const tsip_message_t *message = va_arg(*app, const tsip_message_t *);*/ + + return 0; +} + +/* Any -> (error) -> Terminated +*/ +int tsip_dialog_info_Any_2_Terminated_X_Error(va_list *app) +{ + /*tsip_dialog_info_t *self = va_arg(*app, tsip_dialog_info_t *);*/ + /*const tsip_message_t *message = va_arg(*app, const tsip_message_t *);*/ + + return 0; +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +int send_INFO(tsip_dialog_info_t *self) +{ + tsip_request_t* request = tsk_null; + int ret = -1; + + if(!self){ + return -1; + } + + if(!(request = tsip_dialog_request_new(TSIP_DIALOG(self), "INFO"))){ + return -2; + } + + /* apply action params to the request */ + if(TSIP_DIALOG(self)->curr_action){ + tsip_dialog_apply_action(request, TSIP_DIALOG(self)->curr_action); + } + + ret = tsip_dialog_request_send(TSIP_DIALOG(self), request); + TSK_OBJECT_SAFE_FREE(request); + + return ret; +} + + +int tsip_dialog_info_OnTerminated(tsip_dialog_info_t *self) +{ + TSK_DEBUG_INFO("=== INFO Dialog terminated ==="); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminated, + TSIP_DIALOG(self)->last_error.phrase ? TSIP_DIALOG(self)->last_error.phrase : "Dialog terminated"); + + /* Remove from the dialog layer. */ + return tsip_dialog_remove(TSIP_DIALOG(self)); +} + + + + + + + + + + + + + + + + + + + + + + +//======================================================== +// SIP dialog INFO object definition +// +static tsk_object_t* tsip_dialog_info_ctor(tsk_object_t * self, va_list * app) +{ + tsip_dialog_info_t *dialog = self; + if(dialog){ + tsip_ssession_handle_t *ss = va_arg(*app, tsip_ssession_handle_t *); + + /* Initialize base class */ + tsip_dialog_init(TSIP_DIALOG(self), tsip_dialog_INFO, tsk_null, ss, _fsm_state_Started, _fsm_state_Terminated); + + /* FSM */ + TSIP_DIALOG_GET_FSM(self)->debug = DEBUG_STATE_MACHINE; + tsk_fsm_set_callback_terminated(TSIP_DIALOG_GET_FSM(self), TSK_FSM_ONTERMINATED_F(tsip_dialog_info_OnTerminated), (const void*)dialog); + + /* Initialize the class itself */ + tsip_dialog_info_init(self); + } + return self; +} + +static tsk_object_t* tsip_dialog_info_dtor(tsk_object_t * self) +{ + tsip_dialog_info_t *dialog = self; + if(dialog){ + /* DeInitialize base class (will cancel all transactions) */ + tsip_dialog_deinit(TSIP_DIALOG(self)); + + /* DeInitialize self */ + TSK_OBJECT_SAFE_FREE(dialog->last_iMessage); + + TSK_DEBUG_INFO("*** INFO Dialog destroyed ***"); + } + return self; +} + +static int tsip_dialog_info_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2) +{ + return tsip_dialog_cmp(obj1, obj2); +} + +static const tsk_object_def_t tsip_dialog_info_def_s = +{ + sizeof(tsip_dialog_info_t), + tsip_dialog_info_ctor, + tsip_dialog_info_dtor, + tsip_dialog_info_cmp, +}; +const tsk_object_def_t *tsip_dialog_info_def_t = &tsip_dialog_info_def_s; diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.c b/tinySIP/src/dialogs/tsip_dialog_invite.c new file mode 100644 index 0000000..ed7b3d5 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.c @@ -0,0 +1,1942 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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_invite.c + * @brief SIP dialog INVITE as per RFC 3261. + * The SOA machine is designed as per RFC 3264 and draft-ietf-sipping-sip-offeranswer-12. + * MMTel services implementation follow 3GPP TS 24.173. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_invite.h" + +#include "tinysip/dialogs/tsip_dialog_invite.common.h" + +#include "tinysip/transactions/tsip_transac_layer.h" +#include "tinysip/transports/tsip_transport_layer.h" +#include "tinysip/dialogs/tsip_dialog_layer.h" + +#include "tinysip/headers/tsip_header_Allow.h" +#include "tinysip/headers/tsip_header_Dummy.h" +#include "tinysip/headers/tsip_header_Max_Forwards.h" +#include "tinysip/headers/tsip_header_Min_SE.h" +#include "tinysip/headers/tsip_header_RAck.h" +#include "tinysip/headers/tsip_header_Require.h" +#include "tinysip/headers/tsip_header_RSeq.h" +#include "tinysip/headers/tsip_header_Session_Expires.h" +#include "tinysip/headers/tsip_header_Supported.h" + +#include "tinysdp/parsers/tsdp_parser_message.h" + +#include "tinymedia/tmedia_defaults.h" + +#include "tsk_debug.h" + +#if METROPOLIS +# define TSIP_INFO_FASTUPDATE_OUT_INTERVAL_MIN 0 // millis +#else +# define TSIP_INFO_FASTUPDATE_OUT_INTERVAL_MIN 1500 // millis +#endif + +#if HAVE_LIBXML2 +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> +#endif + +// http://cdnet.stpi.org.tw/techroom/market/_pdf/2009/eetelecomm_09_009_OneVoiceProfile.pdf +// 3GPP TS 26.114 (MMTel): Media handling and interaction +// 3GPP TS 24.173 (MMTel): Supplementary Services +// +/* ======================== MMTel Supplementary Services ======================== +3GPP TS 24.607 : Originating Identification Presentation +3GPP TS 24.608 : Terminating Identification Presentation +3GPP TS 24.607 : Originating Identification Restriction +3GPP TS 24.608 : Terminating Identification Restriction + +3GPP TS 24.604 : Communication Diversion Unconditional +3GPP TS 24.604 : Communication Diversion on not Logged +3GPP TS 24.604 : Communication Diversion on Busy +3GPP TS 24.604 : Communication Diversion on not Reachable +3GPP TS 24.604 : Communication Diversion on No Reply +3GPP TS 24.611 : Barring of All Incoming Calls +3GPP TS 24.611 : Barring of All Outgoing Calls +3GPP TS 24.611 : Barring of Outgoing International Calls +3GPP TS 24.611 : Barring of Incoming Calls - When Roaming +3GPP TS 24.610 : Communication Hold +3GPP TS 24.606 : Message Waiting Indication +3GPP TS 24.615 : Communication Waiting +3GPP TS 24.605 : Ad-Hoc Multi Party Conference +*/ + +extern int tsip_dialog_add_session_headers(const tsip_dialog_t *self, tsip_request_t* request); + +/* ======================== internal functions ======================== */ +/*static*/ int tsip_dialog_invite_msession_start(tsip_dialog_invite_t *self); +/*static*/ int tsip_dialog_invite_msession_configure(tsip_dialog_invite_t *self); +/*static*/ int send_INVITEorUPDATE(tsip_dialog_invite_t *self, tsk_bool_t is_INVITE, tsk_bool_t force_sdp); +/*static*/ int send_PRACK(tsip_dialog_invite_t *self, const tsip_response_t* r1xx); +/*static*/ int send_ACK(tsip_dialog_invite_t *self, const tsip_response_t* r2xxINVITE); +/*static*/ int send_RESPONSE(tsip_dialog_invite_t *self, const tsip_request_t* request, short code, const char* phrase, tsk_bool_t force_sdp); +/*static*/ int send_ERROR(tsip_dialog_invite_t* self, const tsip_request_t* request, short code, const char* phrase, const char* reason); +/*static*/ int send_BYE(tsip_dialog_invite_t *self); +/*static*/ int send_CANCEL(tsip_dialog_invite_t *self); +/*static*/ int tsip_dialog_invite_notify_parent(tsip_dialog_invite_t *self, const tsip_response_t* response); +/*static*/ int send_INFO(tsip_dialog_invite_t *self, const char* content_type, const void* content_ptr, tsk_size_t content_size); +static int tsip_dialog_invite_OnTerminated(tsip_dialog_invite_t *self); +static int tsip_dialog_invite_msession_onerror_cb(const void* usrdata, const struct tmedia_session_s* session, const char* reason, tsk_bool_t is_fatal); +static int tsip_dialog_invite_msession_rfc5168_cb(const void* usrdata, const struct tmedia_session_s* session, const char* reason, enum tmedia_session_rfc5168_cmd_e command); + +/* ======================== external functions ======================== */ +extern int tsip_dialog_invite_ice_process_ro(tsip_dialog_invite_t * self, const tsdp_message_t* sdp_ro, tsk_bool_t is_remote_offer); +extern int tsip_dialog_invite_ice_set_media_type(tsip_dialog_invite_t * self, tmedia_type_t media_type); +extern int tsip_dialog_invite_stimers_cancel(tsip_dialog_invite_t* self); +extern int tsip_dialog_invite_qos_timer_cancel(tsip_dialog_invite_t* self); +extern int tsip_dialog_invite_qos_timer_schedule(tsip_dialog_invite_t* self); +extern int tsip_dialog_invite_stimers_schedule(tsip_dialog_invite_t* self, uint64_t timeout); +extern int tsip_dialog_invite_stimers_handle(tsip_dialog_invite_t* self, const tsip_message_t* message); +extern int tsip_dialog_invite_hold_handle(tsip_dialog_invite_t* self, const tsip_request_t* rINVITEorUPDATE); + +extern int tsip_dialog_invite_ice_timers_set(tsip_dialog_invite_t *self, int64_t timeout); +extern tsk_bool_t tsip_dialog_invite_ice_is_enabled(const tsip_dialog_invite_t * self); +extern tsk_bool_t tsip_dialog_invite_ice_is_connected(const tsip_dialog_invite_t * self); +extern int tsip_dialog_invite_ice_process_lo(tsip_dialog_invite_t * self, const tsdp_message_t* sdp_lo); + +/* ======================== transitions ======================== */ +static int x0000_Connected_2_Connected_X_oDTMF(va_list *app); +static int x0000_Connected_2_Connected_X_oLMessage(va_list *app); +static int x0000_Connected_2_Connected_X_iACK(va_list *app); +static int x0000_Connected_2_Connected_X_iINVITEorUPDATE(va_list *app); +static int x0000_Connected_2_Connected_X_oINVITE(va_list *app); + + +static int x0000_Any_2_Any_X_i1xx(va_list *app); +static int x0000_Any_2_Any_X_oINFO(va_list *app); +static int x0000_Any_2_Any_X_iINFO(va_list *app); +static int x0000_Any_2_Any_X_i401_407_Challenge(va_list *app); +static int x0000_Any_2_Any_X_i2xxINVITEorUPDATE(va_list *app); + +static int x0000_Any_2_Any_X_iPRACK(va_list *app); +static int x0000_Any_2_Any_X_iOPTIONS(va_list *app); +static int x0000_Any_2_Trying_X_oBYE(va_list *app); /* If not Connected => Cancel will be called instead. See tsip_dialog_hangup() */ +static int x0000_Any_2_Terminated_X_iBYE(va_list *app); +static int x0000_Any_2_Trying_X_shutdown(va_list *app); + +static int x9998_Any_2_Terminated_X_transportError(va_list *app); +static int x9999_Any_2_Any_X_Error(va_list *app); + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_is_resp2INVITE(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return TSIP_RESPONSE_IS_TO_INVITE(message); +} +static tsk_bool_t _fsm_cond_is_resp2UPDATE(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return TSIP_RESPONSE_IS_TO_UPDATE(message); +} +static tsk_bool_t _fsm_cond_is_resp2BYE(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return TSIP_RESPONSE_IS_TO_BYE(message); +} +static tsk_bool_t _fsm_cond_is_resp2PRACK(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return TSIP_RESPONSE_IS_TO_PRACK(message); +} +static tsk_bool_t _fsm_cond_is_resp2INFO(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return TSIP_RESPONSE_IS_TO_INFO(message); +} + +/* ======================== actions ======================== */ +/* #include "tinysip/dialogs/tsip_dialog_invite.common.h" */ + +/* ======================== states ======================== */ +/* #include "tinysip/dialogs/tsip_dialog_invite.common.h" */ + +/* ICE handler */ +extern int tsip_dialog_invite_ice_init(tsip_dialog_invite_t *self); +/* Client-Side dialog */ +extern int tsip_dialog_invite_client_init(tsip_dialog_invite_t *self); +/* Server-Side dialog */ +extern int tsip_dialog_invite_server_init(tsip_dialog_invite_t *self); +/* 3GPP TS 24.610: Communication Hold */ +extern int tsip_dialog_invite_hold_init(tsip_dialog_invite_t *self); +/* 3GPP TS 24.629: Explicit Communication Transfer (ECT) using IP Multimedia (IM) Core Network (CN) subsystem */ +extern int tsip_dialog_invite_ect_init(tsip_dialog_invite_t *self); +/* RFC 4028: Session Timers */ +extern int tsip_dialog_invite_stimers_init(tsip_dialog_invite_t *self); +/* RFC 3312: Integration of Resource Management and Session Initiation Protocol (SIP) */ +extern int tsip_dialog_invite_qos_init(tsip_dialog_invite_t *self); + +int tsip_dialog_invite_event_callback(const tsip_dialog_invite_t *self, tsip_dialog_event_type_t type, const tsip_message_t *msg) +{ + int ret = -1; + + switch(type) + { + case tsip_dialog_i_msg: + { + if(msg){ + if(TSIP_MESSAGE_IS_RESPONSE(msg)){ /* Response */ + const tsip_action_t* action = tsip_dialog_keep_action(TSIP_DIALOG(self), msg) ? TSIP_DIALOG(self)->curr_action : tsk_null; + if(TSIP_RESPONSE_IS_1XX(msg)){ // 100-199 + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_i1xx, msg, action); + } + else if(TSIP_RESPONSE_IS_2XX(msg)){ // 200-299 + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_i2xx, msg, action); + } + else if(TSIP_RESPONSE_CODE(msg) == 401 || TSIP_RESPONSE_CODE(msg) == 407){ // 401,407 + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_i401_i407, msg, action); + } + else if(TSIP_RESPONSE_CODE(msg) == 422){ // 422 + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_i422, msg, action); + } + else if(TSIP_RESPONSE_IS_3456(msg)){ // 300-699 + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_i300_to_i699, msg, action); + } + else; // Ignore + } + else{ /* Request */ + if(TSIP_REQUEST_IS_INVITE(msg)){ // INVITE + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iINVITE, msg, tsk_null); + } + else if(TSIP_REQUEST_IS_UPDATE(msg)){ // UPDATE + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iUPDATE, msg, tsk_null); + } + else if(TSIP_REQUEST_IS_PRACK(msg)){ // PRACK + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iPRACK, msg, tsk_null); + } + else if(TSIP_REQUEST_IS_ACK(msg)){ // ACK + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iACK, msg, tsk_null); + } + else if(TSIP_REQUEST_IS_OPTIONS(msg)){ // OPTIONS + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iOPTIONS, msg, tsk_null); + } + else if(TSIP_REQUEST_IS_BYE(msg)){ // BYE + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iBYE, msg, tsk_null); + } + else if(TSIP_REQUEST_IS_CANCEL(msg)){ // CANCEL + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iCANCEL, msg, tsk_null); + } + else if(TSIP_REQUEST_IS_INFO(msg)){ // INFO + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iINFO, msg, tsk_null); + } + else if(TSIP_REQUEST_IS_NOTIFY(msg)){ // NOTIFY + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iNOTIFY, msg, tsk_null); + } + else if(TSIP_REQUEST_IS_REFER(msg)){ // REFER + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iREFER, msg, tsk_null); + } + } + } + break; + } + + case tsip_dialog_canceled: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_oCANCEL, msg, tsk_null); + break; + } + + case tsip_dialog_timedout: + { + // Do nothing if request type is "INFO" + if(!TSIP_MESSAGE_IS_REQUEST(msg) || !TSIP_REQUEST_IS_INFO(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_transporterror, msg, tsk_null); + } + break; + } + case tsip_dialog_terminated: + case tsip_dialog_error: + case tsip_dialog_transport_error: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_transporterror, msg, tsk_null); + break; + } + + default: break; + } + + return ret; +} + +/**Timer manager callback. + * + * @param self The owner of the signaled timer. + * @param timer_id The identifier of the signaled timer. + * + * @return Zero if succeed and non-zero error code otherwise. +**/ +int tsip_dialog_invite_timer_callback(const tsip_dialog_invite_t* self, tsk_timer_id_t timer_id) +{ + int ret = -1; + + if(self){ + if(timer_id == self->stimers.timer.id){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_timerRefresh, tsk_null, tsk_null); + } + else if(timer_id == self->timer100rel.id){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_timer100rel, tsk_null, tsk_null); + } + else if(timer_id == self->qos.timer.id){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_timerRSVP, tsk_null, tsk_null); + } + else if(timer_id == self->timershutdown.id){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_shutdown_timedout, tsk_null, tsk_null); + } + } + return ret; +} + +tsip_dialog_invite_t* tsip_dialog_invite_create(const tsip_ssession_handle_t* ss, const char* call_id) +{ + return tsk_object_new(tsip_dialog_invite_def_t, ss, call_id); +} + +int tsip_dialog_invite_init(tsip_dialog_invite_t *self) +{ + /* special cases (fsm) should be tried first */ + + /* ICE */ + tsip_dialog_invite_ice_init(self); + /* Client-Side dialog */ + tsip_dialog_invite_client_init(self); + /* Server-Side dialog */ + tsip_dialog_invite_server_init(self); + /* 3GPP TS 24.610: Communication Hold */ + tsip_dialog_invite_hold_init(self); + /* 3GPP TS 24.629: Explicit Communication Transfer (ECT) using IP Multimedia (IM) Core Network (CN) subsystem */ + tsip_dialog_invite_ect_init(self); + /* RFC 4028: Session Timers */ + tsip_dialog_invite_stimers_init(self); + /* RFC 3312: Integration of Resource Management and Session Initiation Protocol (SIP) */ + tsip_dialog_invite_qos_init(self); + + /* Initialize the state machine (all other cases) */ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (Any) -> Started + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Started, "tsip_dialog_invite_Started_2_Started_X_any"), + + /*======================= + * === Connected === + */ + // Connected -> (Send DTMF) -> Connected + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_dtmf_send, _fsm_state_Connected, x0000_Connected_2_Connected_X_oDTMF, "x0000_Connected_2_Connected_X_oDTMF"), + // Connected -> (Send MSRP message) -> Connected + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_msrp_send_msg, _fsm_state_Connected, x0000_Connected_2_Connected_X_oLMessage, "x0000_Connected_2_Connected_X_oLMessage"), + // Connected -> (iACK) -> Connected + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_iACK, _fsm_state_Connected, x0000_Connected_2_Connected_X_iACK, "x0000_Connected_2_Connected_X_iACK"), + // Connected -> (iINVITE) -> Connected + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_iINVITE, _fsm_state_Connected, x0000_Connected_2_Connected_X_iINVITEorUPDATE, "x0000_Connected_2_Connected_X_iINVITE"), + // Connected -> (iUPDATE) -> Connected + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_iUPDATE, _fsm_state_Connected, x0000_Connected_2_Connected_X_iINVITEorUPDATE, "x0000_Connected_2_Connected_X_iUPDATE"), + // Connected -> (send reINVITE) -> Connected + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_oINVITE, _fsm_state_Connected, x0000_Connected_2_Connected_X_oINVITE, "x0000_Connected_2_Connected_X_oINVITE"), + + /*======================= + * === BYE/SHUTDOWN === + */ + // Any -> (oBYE) -> Trying + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_oBYE, _fsm_state_Trying, x0000_Any_2_Trying_X_oBYE, "x0000_Any_2_Trying_X_oBYE"), + // Any -> (iBYE) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_iBYE, _fsm_state_Terminated, x0000_Any_2_Terminated_X_iBYE, "x0000_Any_2_Terminated_X_iBYE"), + // Any -> (i401/407 BYE) -> Any + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i401_i407, _fsm_cond_is_resp2BYE, tsk_fsm_state_any, x0000_Any_2_Any_X_i401_407_Challenge, "x0000_Any_2_Any_X_i401_407_Challenge"), + // Any -> (i3xx-i6xx BYE) -> Terminated + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i300_to_i699, _fsm_cond_is_resp2BYE, _fsm_state_Terminated, tsk_null, "x0000_Any_2_Terminated_X_i3xxTOi6xxBYE"), + // Any -> (i2xxx BYE) -> Terminated + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i2xx, _fsm_cond_is_resp2BYE, _fsm_state_Terminated, tsk_null, "x0000_Any_2_Terminated_X_i2xxBYE"), + // Any -> (Shutdown) -> Trying + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_oShutdown, _fsm_state_Trying, x0000_Any_2_Trying_X_shutdown, "x0000_Any_2_Trying_X_shutdown"), + // Any -> (shutdown timedout) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_shutdown_timedout, _fsm_state_Terminated, tsk_null, "tsip_dialog_invite_shutdown_timedout"), + + + /*======================= + * === Any === + */ + // Any -> (i1xx) -> Any + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_i1xx, tsk_fsm_state_any, x0000_Any_2_Any_X_i1xx, "x0000_Any_2_Any_X_i1xx"), + // Any -> (oINFO) -> Any + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_oINFO, tsk_fsm_state_any, x0000_Any_2_Any_X_oINFO, "x0000_Any_2_Any_X_oINFO"), + // Any -> (iINFO) -> Any + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_iINFO, tsk_fsm_state_any, x0000_Any_2_Any_X_iINFO, "x0000_Any_2_Any_X_iINFO"), + // Any -> (i401/407) + // + // Any -> (iPRACK) -> Any + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_iPRACK, tsk_fsm_state_any, x0000_Any_2_Any_X_iPRACK, "x0000_Any_2_Any_X_iPRACK"), + // Any -> (iOPTIONS) -> Any + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_iOPTIONS, tsk_fsm_state_any, x0000_Any_2_Any_X_iOPTIONS, "x0000_Any_2_Any_X_iOPTIONS"), + // Any -> (i2xx INVITE) -> Any + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i2xx, _fsm_cond_is_resp2INVITE, tsk_fsm_state_any, x0000_Any_2_Any_X_i2xxINVITEorUPDATE, "x0000_Any_2_Any_X_i2xxINVITE"), + // Any -> (i2xx UPDATE) -> Any + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i2xx, _fsm_cond_is_resp2UPDATE, tsk_fsm_state_any, x0000_Any_2_Any_X_i2xxINVITEorUPDATE, "x0000_Any_2_Any_X_i2xxUPDATE"), + // Any -> (i401/407 INVITE) -> Any + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i401_i407, _fsm_cond_is_resp2INVITE, tsk_fsm_state_any, x0000_Any_2_Any_X_i401_407_Challenge, "x0000_Any_2_Any_X_i401_407_Challenge"), + // Any -> (i401/407 UPDATE) -> Any + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i401_i407, _fsm_cond_is_resp2UPDATE, tsk_fsm_state_any, x0000_Any_2_Any_X_i401_407_Challenge, "x0000_Any_2_Any_X_i401_407_Challenge"), + // Any -> (i2xx PRACK) -> Any + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i2xx, _fsm_cond_is_resp2PRACK, tsk_fsm_state_any, tsk_null, "x0000_Any_2_Any_X_i2xxPRACK"), + // Any -> (i2xx INFO) -> Any + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_i2xx, _fsm_cond_is_resp2INFO, tsk_fsm_state_any, tsk_null, "x0000_Any_2_Any_X_i2xxINFO"), + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_transporterror, _fsm_state_Terminated, x9998_Any_2_Terminated_X_transportError, "x9998_Any_2_Terminated_X_transportError"), + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_error, _fsm_state_Terminated, x9999_Any_2_Any_X_Error, "x9999_Any_2_Any_X_Error"), + + TSK_FSM_ADD_NULL()); + + /* Sets callback function */ + TSIP_DIALOG(self)->callback = TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_invite_event_callback); + + /* Timers */ + self->timer100rel.id = TSK_INVALID_TIMER_ID; + self->stimers.timer.id = TSK_INVALID_TIMER_ID; + self->timershutdown.id = TSK_INVALID_TIMER_ID; + self->timershutdown.timeout = TSIP_DIALOG_SHUTDOWN_TIMEOUT; + + return 0; +} + +// start sending +int tsip_dialog_invite_start(tsip_dialog_invite_t *self) +{ + int ret = -1; + if(self && !TSIP_DIALOG(self)->running){ + if(!(ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_oINVITE, tsk_null, tsk_null))){ + TSIP_DIALOG(self)->running = tsk_true; + } + } + return ret; +} + +int tsip_dialog_invite_process_ro(tsip_dialog_invite_t *self, const tsip_message_t* message) +{ + tsdp_message_t* sdp_ro = tsk_null; + tmedia_type_t old_media_type; + tmedia_type_t new_media_type; + tsk_bool_t media_session_was_null; + int ret = 0; + tmedia_ro_type_t ro_type = tmedia_ro_type_none; + + if(!self || !message){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if (self->is_cancelling) { + TSK_DEBUG_INFO("Cancelling the INVITE...ignore the incoming SDP"); + return 0; + } + + /* Parse SDP content */ + if(TSIP_MESSAGE_HAS_CONTENT(message)){ + if(tsk_striequals("application/sdp", TSIP_MESSAGE_CONTENT_TYPE(message))){ + if(!(sdp_ro = tsdp_message_parse(TSIP_MESSAGE_CONTENT_DATA(message), TSIP_MESSAGE_CONTENT_DATA_LENGTH(message)))){ + TSK_DEBUG_ERROR("Failed to parse remote sdp message:\n [%s]", (const char*)TSIP_MESSAGE_CONTENT_DATA(message)); + return -2; + } + // ICE processing + if(self->supported.ice){ + tsip_dialog_invite_ice_process_ro(self, sdp_ro, TSIP_MESSAGE_IS_REQUEST(message)); + } + } + else{ + TSK_DEBUG_ERROR("[%s] content-type is not supportted", TSIP_MESSAGE_CONTENT_TYPE(message)); + return -3; + } + } + else{ + if(TSIP_DIALOG(self)->state == tsip_initial && TSIP_REQUEST_IS_INVITE(message)){ /* Bodiless initial INVITE */ + TSIP_DIALOG_GET_SS(self)->media.type = tmedia_defaults_get_media_type(); // Default media for initial INVITE to send with the first reliable answer + } + else{ + return 0; + } + } + + ro_type = (TSIP_REQUEST_IS_INVITE(message) || TSIP_REQUEST_IS_UPDATE(message)) // ACK/PRACK can only contain a response if the initial INVITE was bodiless + ? tmedia_ro_type_offer + :(TSIP_RESPONSE_IS_1XX(message) ? tmedia_ro_type_provisional : tmedia_ro_type_answer); + media_session_was_null = (self->msession_mgr == tsk_null); + old_media_type = TSIP_DIALOG_GET_SS(self)->media.type; + new_media_type = sdp_ro ? tmedia_type_from_sdp(sdp_ro) : old_media_type; + + /* Create session Manager if not already done */ + if(!self->msession_mgr){ + int32_t transport_idx = TSIP_DIALOG_GET_STACK(self)->network.transport_idx_default; + TSIP_DIALOG_GET_SS(self)->media.type = new_media_type; + self->msession_mgr = tmedia_session_mgr_create(TSIP_DIALOG_GET_SS(self)->media.type, TSIP_DIALOG_GET_STACK(self)->network.local_ip[transport_idx], + TNET_SOCKET_TYPE_IS_IPV6(TSIP_DIALOG_GET_STACK(self)->network.proxy_cscf_type[transport_idx]), (sdp_ro == tsk_null)); + if(TSIP_DIALOG_GET_STACK(self)->natt.ctx){ + tmedia_session_mgr_set_natt_ctx(self->msession_mgr, TSIP_DIALOG_GET_STACK(self)->natt.ctx, TSIP_DIALOG_GET_STACK(self)->network.aor.ip[transport_idx]); + } + ret = tmedia_session_mgr_set_ice_ctx(self->msession_mgr, self->ice.ctx_audio, self->ice.ctx_video); + } + + if(sdp_ro){ + if (tmedia_session_mgr_is_new_ro(self->msession_mgr, sdp_ro)) { + ret = tsip_dialog_invite_msession_configure(self); + } + if((ret = tmedia_session_mgr_set_ro(self->msession_mgr, sdp_ro, ro_type))){ + TSK_DEBUG_ERROR("Failed to set remote offer"); + goto bail; + } + } + + // is media update? + // (old_media_type == new_media_type) means the new session are rejected. This is way we match the CSeq + if(!media_session_was_null && (old_media_type != new_media_type || (TSIP_MESSAGE_IS_RESPONSE(message) && self->cseq_out_media_update == message->CSeq->seq)) && (self->msession_mgr->sdp.lo && self->msession_mgr->sdp.ro)){ + // at this point the media session manager has been succeffuly started and all is ok + TSIP_DIALOG_GET_SS(self)->media.type = new_media_type; + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_m_updated, + TSIP_RESPONSE_CODE(message), TSIP_RESPONSE_PHRASE(message), message); + } + + /* start session manager */ + if(!self->msession_mgr->started && (self->msession_mgr->sdp.lo && self->msession_mgr->sdp.ro)){ + /* Set MSRP Callback */ + if((self->msession_mgr->type & tmedia_msrp) == tmedia_msrp){ + tmedia_session_mgr_set_msrp_cb(self->msession_mgr, TSIP_DIALOG_GET_SS(self)->userdata, TSIP_DIALOG_GET_SS(self)->media.msrp.callback); + } + /* starts session manager*/ + ret = tsip_dialog_invite_msession_start(self); + + if(ret == 0 && TSIP_DIALOG(self)->state == tsip_early){ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_m_early_media, + TSIP_RESPONSE_CODE(message), TSIP_RESPONSE_PHRASE(message), message); + } + } + +bail: + TSK_OBJECT_SAFE_FREE(sdp_ro); + + return ret; +} + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +int x0000_Connected_2_Connected_X_oDTMF(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(action){ + ret = tmedia_session_mgr_send_dtmf(self->msession_mgr, action->dtmf.event); + } + else{ + TSK_DEBUG_ERROR("Invalid action"); + } + + return 0; /* always */ +} + +int x0000_Connected_2_Connected_X_oLMessage(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(action && action->payload){ + ret = tmedia_session_mgr_send_message(self->msession_mgr, action->payload->data, action->payload->size, + action->media.params); + } + else{ + TSK_DEBUG_ERROR("Invalid action"); + } + + return 0; +} + +/* Connected -> (iACK) -> Connected */ +int x0000_Connected_2_Connected_X_iACK(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_request_t *rACK = va_arg(*app, const tsip_request_t *); + int ret; + + // Nothing to do (in future will be used to ensure the session) + + /* No longer waiting for the initial ACK */ + self->is_initial_iack_pending = tsk_false; + + /* Process remote offer */ + if((ret = tsip_dialog_invite_process_ro(self, rACK))){ + /* Send error */ + return ret; + } + + /* Starts media session if not already done */ + if(!self->msession_mgr->started && (self->msession_mgr->sdp.lo && self->msession_mgr->sdp.ro)){ + ret = tsip_dialog_invite_msession_start(self); + } + + // starts ICE timers now that both parties receive the "candidates" + if(tsip_dialog_invite_ice_is_enabled(self)){ + tsip_dialog_invite_ice_timers_set(self, (self->required.ice ? -1 : TSIP_DIALOG_INVITE_ICE_CONNCHECK_TIMEOUT)); + } + + /* alert the user */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request, + tsip_event_code_dialog_request_incoming, "Incoming Request", rACK); + + return 0; +} + +/* Connected -> (iINVITE or iUPDATE) -> Connected */ +int x0000_Connected_2_Connected_X_iINVITEorUPDATE(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_request_t *rINVITEorUPDATE = va_arg(*app, const tsip_request_t *); + + int ret = 0; + tsk_bool_t bodiless_invite; + tmedia_type_t old_media_type = self->msession_mgr ? self->msession_mgr->type : tmedia_none; + tmedia_type_t new_media_type; + tsk_bool_t force_sdp; + + /* process remote offer */ + if((ret = tsip_dialog_invite_process_ro(self, rINVITEorUPDATE))){ + /* Send error */ + return ret; + } + + // force SDP in 200 OK even if the request has the same SDP version + force_sdp = TSIP_MESSAGE_HAS_CONTENT(rINVITEorUPDATE); + + // get new media_type after processing the remote offer + new_media_type = self->msession_mgr ? self->msession_mgr->type : tmedia_none; + + /** response to bodiless iINVITE always contains SDP as explained below + RFC3261 - 14.1 UAC Behavior + The same offer-answer model that applies to session descriptions in + INVITEs (Section 13.2.1) applies to re-INVITEs. As a result, a UAC + that wants to add a media stream, for example, will create a new + offer that contains this media stream, and send that in an INVITE + request to its peer. It is important to note that the full + description of the session, not just the change, is sent. This + supports stateless session processing in various elements, and + supports failover and recovery capabilities. Of course, a UAC MAY + send a re-INVITE with no session description, in which case the first + reliable non-failure response to the re-INVITE will contain the offer + (in this specification, that is a 2xx response). + */ + bodiless_invite = !TSIP_MESSAGE_HAS_CONTENT(rINVITEorUPDATE) && TSIP_REQUEST_IS_INVITE(rINVITEorUPDATE); + + /* session timers (must be before sending response) */ + if(self->stimers.timer.timeout){ + tsip_dialog_invite_stimers_handle(self, rINVITEorUPDATE); + } + + /* hold/resume */ + tsip_dialog_invite_hold_handle(self, rINVITEorUPDATE); + + // send the response + ret = send_RESPONSE(self, rINVITEorUPDATE, 200, "OK", + (self->msession_mgr && (force_sdp || bodiless_invite || self->msession_mgr->ro_changed || self->msession_mgr->state_changed || (old_media_type != new_media_type)))); + + /* alert the user */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request, + tsip_event_code_dialog_request_incoming, "Incoming Request.", rINVITEorUPDATE); + + return ret; +} + +/* Connected -> (send reINVITE) -> Connected */ +static int x0000_Connected_2_Connected_X_oINVITE(va_list *app) +{ + int ret; + tsk_bool_t mediaType_changed; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + /* Get Media type from the action */ + mediaType_changed = (TSIP_DIALOG_GET_SS(self)->media.type != action->media.type && action->media.type != tmedia_none); + if (mediaType_changed){ + if (self->msession_mgr) { + ret = tmedia_session_mgr_set_media_type(self->msession_mgr, action->media.type); + } + self->cseq_out_media_update = TSIP_DIALOG(self)->cseq_value + 1; + } + + /* Appy media params received from the user */ + if(!TSK_LIST_IS_EMPTY(action->media.params)){ + ret = tmedia_session_mgr_set_3(self->msession_mgr, action->media.params); + } + + /* send the request */ + ret = send_INVITE(self, mediaType_changed); + + /* alert the user */ + if(mediaType_changed){ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_m_updating, + tsip_event_code_dialog_request_outgoing, "Updating media type", self->last_oInvite); + } + + return ret; +} + +/* Any -> (iPRACK) -> Any */ +int x0000_Any_2_Any_X_iPRACK(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_request_t *rPRACK = va_arg(*app, const tsip_request_t *); + + const tsip_header_RAck_t* RAck; + + if((RAck = (const tsip_header_RAck_t*)tsip_message_get_header(rPRACK, tsip_htype_RAck))){ + if((RAck->seq == self->rseq) && + (tsk_striequals(RAck->method, self->last_o1xxrel->CSeq->method)) && + (RAck->cseq == self->last_o1xxrel->CSeq->seq)){ + + ++self->rseq; + return send_RESPONSE(self, rPRACK, 200, "OK", tsk_false); + } + } + + /* Send 488 */ + return send_ERROR(self, rPRACK, 488, "Failed to match PRACK request", "SIP; cause=488; text=\"Failed to match PRACK request\""); +} + +/* Any -> (iOPTIONS) -> Any */ +int x0000_Any_2_Any_X_iOPTIONS(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_request_t *rOPTIONS = va_arg(*app, const tsip_request_t *); + + /* Alert user */ + + /* Send 2xx */ + send_RESPONSE(self, rOPTIONS, 200, "OK", tsk_false); + + return 0; +} + + +/* Any --> (i401/407 INVITE or UPDATE) --> Any */ +int x0000_Any_2_Any_X_i401_407_Challenge(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + int ret; + + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + /* Alert the user. */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_ao_request, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return ret; + } + + if(TSIP_RESPONSE_IS_TO_INVITE(response) || TSIP_RESPONSE_IS_TO_UPDATE(response)){ + return send_INVITEorUPDATE(self, TSIP_RESPONSE_IS_TO_INVITE(response), tsk_false); + } + else if(TSIP_RESPONSE_IS_TO_BYE(response)){ + return send_BYE(self); + } + else{ + TSK_DEBUG_ERROR("Unexpected code called"); + return 0; + } +} + +/* Any --> (i2xx INVITE or i2xx UPDATE) --> Any */ +int x0000_Any_2_Any_X_i2xxINVITEorUPDATE(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_response_t *r2xx = va_arg(*app, const tsip_response_t *); + int ret = 0; + + /* Update the dialog state */ + if((ret = tsip_dialog_update(TSIP_DIALOG(self), r2xx))){ + return ret; + } + + /* session timers */ + if(self->stimers.timer.timeout){ + tsip_dialog_invite_stimers_handle(self, r2xx); + } + + /* Process remote offer */ + if((ret = tsip_dialog_invite_process_ro(self, r2xx))){ + send_BYE(self); + return ret; + } + + /* send ACK */ + if(TSIP_RESPONSE_IS_TO_INVITE(r2xx)){ + ret = send_ACK(self, r2xx); + } + + // starts ICE timers now that both parties received the "candidates" + if(tsip_dialog_invite_ice_is_enabled(self)){ + tsip_dialog_invite_ice_timers_set(self, (self->required.ice ? -1 : TSIP_DIALOG_INVITE_ICE_CONNCHECK_TIMEOUT)); + } + + return ret; +} + + +/* Any -> (oBYE) -> Trying */ +int x0000_Any_2_Trying_X_oBYE(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + int ret; + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog"); + + /* send BYE */ + if((ret = send_BYE(self)) == 0){ +#if !TSIP_UNDER_APPLE // FIXME: hangs up on iOS (RTP transport runnable join never exits) + // stop session manager + if(self->msession_mgr && self->msession_mgr->started){ + tmedia_session_mgr_stop(self->msession_mgr); + } +#endif + } + return ret; +} + +/* Any -> (iBYE) -> Terminated */ +int x0000_Any_2_Terminated_X_iBYE(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_request_t *rBYE = va_arg(*app, const tsip_request_t *); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Call Terminated", tsip_event_code_dialog_terminated); + + /* send 200 OK */ + return send_RESPONSE(self, rBYE, 200, "OK", tsk_false); +} + +/* Any -> Shutdown -> Terminated */ +int x0000_Any_2_Trying_X_shutdown(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + + /* schedule shutdow timeout */ + TSIP_DIALOG_INVITE_TIMER_SCHEDULE(shutdown); + + /* alert user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog"); + + if(TSIP_DIALOG(self)->state == tsip_established){ + return send_BYE(self); + } + else if(TSIP_DIALOG(self)->state == tsip_early){ + return send_CANCEL(self); + } + + return 0; +} + + +/* Any -> (i1xx) -> Any */ +int x0000_Any_2_Any_X_i1xx(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_response_t *r1xx = va_arg(*app, const tsip_response_t *); + int ret = 0; + + /* Update the dialog state */ + if((ret = tsip_dialog_update(TSIP_DIALOG(self), r1xx))){ + return ret; + } + + /* RFC 3262 - 4 UAC Behavior + If a provisional response is received for an initial request, and + that response contains a Require header field containing the option + tag 100rel, the response is to be sent reliably. If the response is + a 100 (Trying) (as opposed to 101 to 199), this option tag MUST be + ignored, and the procedures below MUST NOT be used. + + Assuming the response is to be transmitted reliably, the UAC MUST + create a new request with method PRACK. This request is sent within + the dialog associated with the provisional response (indeed, the + provisional response may have created the dialog). PRACK requests + MAY contain bodies, which are interpreted according to their type and + disposition. + + Note that the PRACK is like any other non-INVITE request within a + dialog. In particular, a UAC SHOULD NOT retransmit the PRACK request + when it receives a retransmission of the provisional response being + acknowledged, although doing so does not create a protocol error. + + Additional information: We should only process the SDP from reliable responses (require:100rel) + but there was many problem with some clients sending SDP with this tag: tiscali, DTAG, samsung, ... + */ + if((TSIP_RESPONSE_CODE(r1xx) >= 101 && TSIP_RESPONSE_CODE(r1xx) <=199)){ + /* Process Remote offer */ + if(TSIP_MESSAGE_HAS_CONTENT(r1xx) && (ret = tsip_dialog_invite_process_ro(self, r1xx))){ + /* Send Error */ + return ret; + } + // don't send PRACK if 100rel is only inside "supported" header + if(tsip_message_required(r1xx, "100rel") && (ret = send_PRACK(self, r1xx))){ + return ret; + } + } + + /* QoS Reservation */ + if((self->qos.timer.id == TSK_INVALID_TIMER_ID) && tsip_message_required(r1xx, "precondition") && !tmedia_session_mgr_canresume(self->msession_mgr)){ + tsip_dialog_invite_qos_timer_schedule(self); + } + + /* alert the user */ + ret = TSIP_DIALOG_INVITE_SIGNAL(self, tsip_ao_request, + TSIP_RESPONSE_CODE(r1xx), TSIP_RESPONSE_PHRASE(r1xx), r1xx); + if(self->is_transf){ + ret = tsip_dialog_invite_notify_parent(self, r1xx); + } + + return ret; +} + +/* Any -> (oINFO) -> Any */ +int x0000_Any_2_Any_X_oINFO(va_list *app) +{ + tsip_dialog_invite_t *self; + const tsip_action_t* action; + tsip_request_t* rINFO; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if((rINFO = tsip_dialog_request_new(TSIP_DIALOG(self), "INFO"))){ + int ret; + if(action){ + ret = tsip_dialog_apply_action(TSIP_MESSAGE(rINFO), action); + } + ret = tsip_dialog_request_send(TSIP_DIALOG(self), rINFO); + TSK_OBJECT_SAFE_FREE(rINFO); + return ret; + } + else{ + TSK_DEBUG_ERROR("Failed to create new INFO request"); + return -1; + } +} + +int x0000_Any_2_Any_X_iINFO(va_list *app) +{ + tsip_dialog_invite_t * self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t* rINFO = (tsip_request_t*)va_arg(*app, const tsip_message_t *); + int ret = -1; + + if (rINFO){ + ret = send_RESPONSE(self, rINFO, 200, "Ok", tsk_false); + { + // int tmedia_session_mgr_recv_rtcp_event(tmedia_session_mgr_t* self, tmedia_type_t media_type, tmedia_rtcp_event_type_t event_type, uint32_t ssrc_media); + if (self->msession_mgr && TSIP_MESSAGE_HAS_CONTENT(rINFO)){ + if (tsk_striequals("application/media_control+xml", TSIP_MESSAGE_CONTENT_TYPE(rINFO))){ /* rfc5168: XML Schema for Media Control */ + static uint32_t __ssrc_media_fake = 0; + static tmedia_type_t __tmedia_type_video = tmedia_video; // TODO: add bfcpvideo? + const char* content_ptr = (const char*)TSIP_MESSAGE_CONTENT_DATA(rINFO); + tsk_size_t content_size = (tsk_size_t)TSIP_MESSAGE_CONTENT_DATA_LENGTH(rINFO); + tsk_bool_t is_fir = tsk_false; + uint64_t sessionId = 0; +#if HAVE_LIBXML2 + { + xmlDoc *pDoc; + xmlNode *pRootElement; + xmlXPathContext *pPathCtx; + xmlXPathObject *pPathObj; + static const xmlChar* __xpath_expr_picture_fast_update = (const xmlChar*)"/media_control/vc_primitive/to_encoder/picture_fast_update"; + static const xmlChar* __xpath_expr_stream_id = (const xmlChar*)"/media_control/vc_primitive/stream_id"; + + if (!(pDoc = xmlParseDoc(content_ptr))) { + TSK_DEBUG_ERROR("Failed to parse XML content [%s]", content_ptr); + return 0; + } + if (!(pRootElement = xmlDocGetRootElement(pDoc))) { + TSK_DEBUG_ERROR("Failed to get root element from XML content [%s]", content_ptr); + xmlFreeDoc(pDoc); + return 0; + } + if (!(pPathCtx = xmlXPathNewContext(pDoc))) { + TSK_DEBUG_ERROR("Failed to create path context from XML content [%s]", content_ptr); + xmlFreeDoc(pDoc); + return 0; + } + // picture_fast_update + if (!(pPathObj = xmlXPathEvalExpression(__xpath_expr_picture_fast_update, pPathCtx))) { + TSK_DEBUG_ERROR("Error: unable to evaluate xpath expression: %s", __xpath_expr_picture_fast_update); + xmlXPathFreeContext(pPathCtx); + xmlFreeDoc(pDoc); + return 0; + } + is_fir = (pPathObj->type == XPATH_NODESET && pPathObj->nodesetval->nodeNr > 0); + xmlXPathFreeObject(pPathObj); + // stream_id + if (!(pPathObj = xmlXPathEvalExpression(__xpath_expr_stream_id, pPathCtx))) { + TSK_DEBUG_ERROR("Error: unable to evaluate xpath expression: %s", __xpath_expr_stream_id); + xmlXPathFreeContext(pPathCtx); + xmlFreeDoc(pDoc); + } + else if (pPathObj->type == XPATH_NODESET && pPathObj->nodesetval->nodeNr > 0 && pPathObj->nodesetval->nodeTab[0]->children && pPathObj->nodesetval->nodeTab[0]->children->content) { + sessionId = tsk_atoi64((const char*)pPathObj->nodesetval->nodeTab[0]->children->content); + } + xmlXPathFreeObject(pPathObj); + + xmlXPathFreeContext(pPathCtx); + xmlFreeDoc(pDoc); + } +#else + is_fir = (tsk_strcontains(content_ptr, content_size, "to_encoder") && tsk_strcontains(content_ptr, content_size, "picture_fast_update")); +#endif + if (is_fir) { + TSK_DEBUG_INFO("Incoming SIP INFO(picture_fast_update)"); + ret = sessionId + ? tmedia_session_mgr_recv_rtcp_event_2(self->msession_mgr, tmedia_rtcp_event_type_fir, sessionId) + : tmedia_session_mgr_recv_rtcp_event(self->msession_mgr, __tmedia_type_video, tmedia_rtcp_event_type_fir, __ssrc_media_fake); + } + else { + TSK_DEBUG_INFO("Incoming SIP INFO(unknown)"); + } + } + } + } + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request, + tsip_event_code_dialog_request_incoming, "Incoming Request", rINFO); + } + + return ret; +} + +int x9998_Any_2_Terminated_X_transportError(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Transport error", tsip_event_code_dialog_terminated); + + return 0; +} + +int x9999_Any_2_Any_X_Error(va_list *app) +{ + return 0; +} + + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +int tsip_dialog_invite_msession_configure(tsip_dialog_invite_t *self) +{ + tmedia_srtp_mode_t srtp_mode; + tmedia_mode_t avpf_mode; + tsk_bool_t is_rtcweb_enabled; + tsk_bool_t is_webrtc2sip_mode_enabled; + + if(!self || !self->msession_mgr){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + is_webrtc2sip_mode_enabled = (TSIP_DIALOG_GET_STACK(self)->network.mode == tsip_stack_mode_webrtc2sip); + is_rtcweb_enabled = (((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.profile == tmedia_profile_rtcweb); + srtp_mode = is_rtcweb_enabled ? tmedia_srtp_mode_mandatory : ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.srtp_mode; + avpf_mode = is_rtcweb_enabled ? tmedia_mode_mandatory : ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.avpf_mode; + + // set callback functions + tmedia_session_mgr_set_onerror_cbfn(self->msession_mgr, self, tsip_dialog_invite_msession_onerror_cb); + tmedia_session_mgr_set_rfc5168_cbfn(self->msession_mgr, self, tsip_dialog_invite_msession_rfc5168_cb); + + // set params + return tmedia_session_mgr_set(self->msession_mgr, + TMEDIA_SESSION_SET_INT32(self->msession_mgr->type, "srtp-mode", srtp_mode), + TMEDIA_SESSION_SET_INT32(self->msession_mgr->type, "avpf-mode", avpf_mode), + TMEDIA_SESSION_SET_INT32(self->msession_mgr->type, "webrtc2sip-mode-enabled", is_webrtc2sip_mode_enabled), // hack the media stack + TMEDIA_SESSION_SET_INT32(self->msession_mgr->type, "rtcp-enabled", self->use_rtcp), + TMEDIA_SESSION_SET_INT32(self->msession_mgr->type, "rtcpmux-enabled", self->use_rtcpmux), + TMEDIA_SESSION_SET_INT32(self->msession_mgr->type, "codecs-supported", ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.codecs), + + TMEDIA_SESSION_SET_INT32(self->msession_mgr->type, "bypass-encoding", ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.bypass_encoding), + TMEDIA_SESSION_SET_INT32(self->msession_mgr->type, "bypass-decoding", ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.bypass_decoding), + + TMEDIA_SESSION_SET_INT32(tmedia_audio, "rtp-ssrc", ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.rtp.ssrc.audio), + TMEDIA_SESSION_SET_INT32(tmedia_video, "rtp-ssrc", ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.rtp.ssrc.video), + + TMEDIA_SESSION_SET_STR(self->msession_mgr->type, "dtls-file-ca", TSIP_DIALOG_GET_STACK(self)->security.tls.ca), + TMEDIA_SESSION_SET_STR(self->msession_mgr->type, "dtls-file-pbk", TSIP_DIALOG_GET_STACK(self)->security.tls.pbk), + TMEDIA_SESSION_SET_STR(self->msession_mgr->type, "dtls-file-pvk", TSIP_DIALOG_GET_STACK(self)->security.tls.pvk), + TMEDIA_SESSION_SET_INT32(self->msession_mgr->type, "dtls-cert-verify", TSIP_DIALOG_GET_STACK(self)->security.tls.verify), + + TMEDIA_SESSION_SET_INT32(tmedia_video, "fps", ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.video_fps), + TMEDIA_SESSION_SET_INT32(tmedia_video, "bandwidth-max-upload", ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.video_bw_up), + TMEDIA_SESSION_SET_INT32(tmedia_video, "bandwidth-max-download", ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.video_bw_down), + TMEDIA_SESSION_SET_INT32(tmedia_video, "pref-size", ((tsip_ssession_t*)TSIP_DIALOG(self)->ss)->media.video_pref_size), + + tsk_null); +} + +int tsip_dialog_invite_msession_start(tsip_dialog_invite_t *self) +{ + if(!self || !self->msession_mgr){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(tsip_dialog_invite_ice_is_enabled(self) && !tsip_dialog_invite_ice_is_connected(self)){ + if(self->msession_mgr->type != self->ice.media_type){ + TSK_DEBUG_INFO("Media session type(%d)<>ICE media type(%d)", self->msession_mgr->type, self->ice.media_type); + // make sure to use the right media types + tsip_dialog_invite_ice_set_media_type(self, self->msession_mgr->type); + } + self->ice.start_smgr = tsk_true; + } + else{ + self->ice.start_smgr = tsk_false; + return tmedia_session_mgr_start(self->msession_mgr); + } + return 0; +} + +// send INVITE/UPDATE request +int send_INVITEorUPDATE(tsip_dialog_invite_t *self, tsk_bool_t is_INVITE, tsk_bool_t force_sdp) +{ + int ret = -1; + tsip_request_t *request = tsk_null; + tsk_bool_t bodiless = tsk_false; + +#if BODILESS_INVITE + if(TSIP_DIALOG(self)->state == tsip_initial){ + bodiless = tsk_true; + } +#endif + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + goto bail; + } + + if((request = tsip_dialog_request_new(TSIP_DIALOG(self), is_INVITE ? "INVITE" : "UPDATE"))){ + /* apply action params to the request (will add a content if the action contains one) */ + if(TSIP_DIALOG(self)->curr_action){ + tsip_dialog_apply_action(request, TSIP_DIALOG(self)->curr_action); + } + + if(!bodiless){ + /* add our payload if current action does not have one */ + if((force_sdp || is_INVITE) || ((self->msession_mgr && self->msession_mgr->state_changed) || (TSIP_DIALOG(self)->state == tsip_initial))){ + if(!TSIP_DIALOG(self)->curr_action || !TSIP_DIALOG(self)->curr_action->payload){ + const tsdp_message_t* sdp_lo; + char* sdp; + if((sdp_lo = tmedia_session_mgr_get_lo(self->msession_mgr)) && (sdp = tsdp_message_tostring(sdp_lo))){ + tsip_message_add_content(request, "application/sdp", sdp, tsk_strlen(sdp)); + if(tsip_dialog_invite_ice_is_enabled(self)){ + ret = tsip_dialog_invite_ice_process_lo(self, sdp_lo); + } + TSK_FREE(sdp); + } + } + } + } + + /* Session timers */ + if(self->stimers.timer.timeout){ + if(self->required.timer){ + tsip_message_add_headers(request, + TSIP_HEADER_SESSION_EXPIRES_VA_ARGS(self->stimers.timer.timeout, !self->stimers.is_refresher), + TSIP_HEADER_REQUIRE_VA_ARGS("timer"), + tsk_null + ); + } + else if(self->supported.timer){ + tsip_message_add_headers(request, + TSIP_HEADER_SESSION_EXPIRES_VA_ARGS(self->stimers.timer.timeout, !self->stimers.is_refresher), + TSIP_HEADER_SUPPORTED_VA_ARGS("timer"), + tsk_null + ); + } + + } + + if(self->stimers.minse){ + tsip_message_add_headers(request, + TSIP_HEADER_MIN_SE_VA_ARGS(self->stimers.minse), + tsk_null + ); + } + + /* 100rel */ + if(self->required._100rel){ + tsip_message_add_headers(request, + TSIP_HEADER_REQUIRE_VA_ARGS("100rel"), + tsk_null + ); + } + else if(self->supported._100rel){ + tsip_message_add_headers(request, + TSIP_HEADER_SUPPORTED_VA_ARGS("100rel"), + tsk_null + ); + } + + /* QoS */ + if(self->required.precondition){ + tsip_message_add_headers(request, + TSIP_HEADER_REQUIRE_VA_ARGS("precondition"), + tsk_null + ); + } + else if(self->supported.precondition){ + tsip_message_add_headers(request, + TSIP_HEADER_SUPPORTED_VA_ARGS("precondition"), + tsk_null + ); + } + + + /* Always added headers */ + // Explicit Communication Transfer (3GPP TS 24.629) + /*tsip_message_add_headers(request, + TSIP_HEADER_SUPPORTED_VA_ARGS("norefersub,replaces"), + tsk_null + );*/ + + /* send the request */ + ret = tsip_dialog_request_send(TSIP_DIALOG(self), request); + if(ret == 0){ + /* update last INVITE */ + TSK_OBJECT_SAFE_FREE(self->last_oInvite); + self->last_oInvite = request; + } + else{ + TSK_OBJECT_SAFE_FREE(request); + } + } + +bail: + return ret; +} + +// Send PRACK +int send_PRACK(tsip_dialog_invite_t *self, const tsip_response_t* r1xx) +{ + // "Require: 100rel\r\n" should be checked by the caller of this function + int ret = -1; + tsip_request_t *request = tsk_null; + const tsip_header_RSeq_t* RSeq; + + if(!self || !r1xx || !r1xx->CSeq){ + TSK_DEBUG_ERROR("Invalid parameter"); + goto bail; + } + + + /* RFC 3262 - 4 UAC Behavior + The UAC MUST maintain a sequence number that indicates the most recently + received in-order reliable provisional response for the initial request. + */ + if((RSeq = (const tsip_header_RSeq_t*)tsip_message_get_header(r1xx, tsip_htype_RSeq))){ + + /* RFC 3262 - 4 UAC Behavior + If the UAC receives another reliable provisional + response to the same request, and its RSeq value is not one higher + than the value of the sequence number, that response MUST NOT be + acknowledged with a PRACK, and MUST NOT be processed further by the + UAC. An implementation MAY discard the response, or MAY cache the + response in the hopes of receiving the missing responses. + */ + if(self->rseq && (RSeq->seq <= self->rseq)){ + TSK_DEBUG_WARN("1xx.RSeq value is not one higher than lastINVITE.RSeq."); + ret = 0; /* Not error */ + goto bail; + } + self->rseq = RSeq->seq; + } + + /* RFC 3262 - 4 UAC Behavior + Assuming the response is to be transmitted reliably, the UAC MUST + create a new request with method PRACK. + */ + if(!(request = tsip_dialog_request_new(TSIP_DIALOG(self), "PRACK"))){ + goto bail; + } + + /* RFC 3262 - 7.2 RAck + The first number is the value from the RSeq header in the provisional + response that is being acknowledged. The next number, and the + method, are copied from the CSeq in the response that is being + acknowledged. The method name in the RAck header is case sensitive. + */ + TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_RACK_VA_ARGS(self->rseq, r1xx->CSeq->seq, r1xx->CSeq->method)); + //TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_DUMMY_VA_ARGS("Test", "value")); + + /* Initial INVITE was a bodiless request and 100rel is supported (I'm Alice) + 1. Alice sends an initial INVITE without offer + 2. Bob's answer is sent in the first reliable provisional response, in this case it's a 1xx INVITE response + 3. Alice's answer is sent in the PRACK response + */ + if(self->is_client && (self->last_oInvite && !TSIP_MESSAGE_HAS_CONTENT(self->last_oInvite))){ + const tsdp_message_t* sdp_lo; + char* sdp; + if((sdp_lo = tmedia_session_mgr_get_lo(self->msession_mgr)) && (sdp = tsdp_message_tostring(sdp_lo))){ + tsip_message_add_content(request, "application/sdp", sdp, tsk_strlen(sdp)); + TSK_FREE(sdp); + } + } + + // Send request + ret = tsip_dialog_request_send(TSIP_DIALOG(self), request); + +bail: + TSK_OBJECT_SAFE_FREE(request); + return ret; +} + +// Send CANCEL +int send_CANCEL(tsip_dialog_invite_t *self) +{ + int ret = -1; + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + goto bail; + } + /* RFC 3261 - 9 Canceling a Request + If the request being cancelled contains a Route header field, the + CANCEL request MUST include that Route header field's values. + ==> up to tsip_dialog_request_new() + */ + + /* RFC 3261 - 9 Canceling a Request + Once the CANCEL is constructed, the client SHOULD check whether it + has received any response (provisional or final) for the request + being cancelled (herein referred to as the "original request"). + + If no provisional response has been received, the CANCEL request MUST + NOT be sent; rather, the client MUST wait for the arrival of a + provisional response before sending the request. + ==> up to the caller to check that we are not in the initial state and the FSM + is in Trying state. + */ + + /* RFC 3261 - 9 Canceling a Request + The following procedures are used to construct a CANCEL request. The + Request-URI, Call-ID, To, the numeric part of CSeq, and From header + fields in the CANCEL request MUST be identical to those in the + request being cancelled, including tags. A CANCEL constructed by a + client MUST have only a single Via header field value matching the + top Via value in the request being cancelled. Using the same values + for these header fields allows the CANCEL to be matched with the + request it cancels (Section 9.2 indicates how such matching occurs). + However, the method part of the CSeq header field MUST have a value + of CANCEL. This allows it to be identified and processed as a + transaction in its own right (See Section 17) + */ + if(self->last_oInvite){ + /* to avoid concurrent access, take a reference to the request */ + tsip_request_t* last_oInvite = tsk_object_ref(self->last_oInvite); + tsip_request_t* cancel; + + if((cancel = tsip_request_create("CANCEL", last_oInvite->line.request.uri))){ + const tsk_list_item_t* item; + const tsip_header_t* header; + + tsip_message_add_headers(cancel, + TSIP_HEADER_CSEQ_VA_ARGS(last_oInvite->CSeq->seq, "CANCEL"), + TSIP_HEADER_MAX_FORWARDS_VA_ARGS(TSIP_HEADER_MAX_FORWARDS_DEFAULT), + TSIP_HEADER_CONTENT_LENGTH_VA_ARGS(0), + tsk_null); + + cancel->Call_ID = tsk_object_ref(last_oInvite->Call_ID); + cancel->To = tsk_object_ref(last_oInvite->To); + cancel->From = tsk_object_ref(last_oInvite->From); + cancel->firstVia = tsk_object_ref(last_oInvite->firstVia); + cancel->sigcomp_id = tsk_strdup(TSIP_DIALOG_GET_SS(self)->sigcomp_id); + + // Copy Authorizations, Routes and Proxy-Auth + tsk_list_foreach(item, last_oInvite->headers){ + if(!(header = TSIP_HEADER(item->data))){ + continue; + } + + switch(header->type){ + case tsip_htype_Route: + case tsip_htype_Proxy_Authorization: + case tsip_htype_Authorization: + header = tsk_object_ref((void*)header); + if(!cancel->headers){ + cancel->headers = tsk_list_create(); + } + tsk_list_push_back_data(cancel->headers, (void**)&header); + break; + default: break; + } + } + + ret = tsip_dialog_add_session_headers(TSIP_DIALOG(self), cancel); + ret = tsip_dialog_request_send(TSIP_DIALOG(self), cancel); + TSK_OBJECT_SAFE_FREE(cancel); + } + else{ + TSK_DEBUG_ERROR("Failed to create CANCEL request"); + ret = -2; + } + + TSK_OBJECT_SAFE_FREE(last_oInvite); + return ret; + } + else{ + TSK_DEBUG_WARN("There is no INVITE request to cancel"); + return 0; + } + +bail: + return ret; +} + +int tsip_dialog_invite_notify_parent(tsip_dialog_invite_t *self, const tsip_response_t* response) +{ + int ret = -1; + tsip_dialog_t* dlg_parent = tsip_dialog_layer_find_by_ssid(TSIP_DIALOG_GET_STACK(self)->layer_dialog, TSIP_DIALOG_GET_SS(self)->id_parent); + if(dlg_parent){ + tsip_action_t* action = tsip_action_create(tsip_atype_ect_lnotify, + TSIP_ACTION_SET_NULL()); + if(action){ + ret = tsip_dialog_fsm_act(dlg_parent, action->type, response, action); + TSK_OBJECT_SAFE_FREE(action); + } + else{ + TSK_DEBUG_ERROR("Failed to create action object"); + } + TSK_OBJECT_SAFE_FREE(dlg_parent); + } + else{ + TSK_DEBUG_ERROR("Failed to find parent with id = %llu", TSIP_DIALOG_GET_SS(self)->id_parent); + } + return ret; +} + +// Send BYE +int send_BYE(tsip_dialog_invite_t *self) +{ + int ret = -1; + tsip_request_t *bye = tsk_null; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + goto bail; + } + /* RFC 3261 - 15.1.1 UAC Behavior + A BYE request is constructed as would any other request within a + dialog, as described in Section 12. + + Once the BYE is constructed, the UAC core creates a new non-INVITE + client transaction, and passes it the BYE request. The UAC MUST + consider the session terminated (and therefore stop sending or + listening for media) as soon as the BYE request is passed to the + client transaction. If the response for the BYE is a 481 + (Call/Transaction Does Not Exist) or a 408 (Request Timeout) or no + + response at all is received for the BYE (that is, a timeout is + returned by the client transaction), the UAC MUST consider the + session and the dialog terminated. + */ + if((bye = tsip_dialog_request_new(TSIP_DIALOG(self), "BYE"))){ + if(TSIP_DIALOG(self)->curr_action){ + tsip_dialog_apply_action(bye, TSIP_DIALOG(self)->curr_action); + } + ret = tsip_dialog_request_send(TSIP_DIALOG(self), bye); + TSK_OBJECT_SAFE_FREE(bye); + } + +bail: + return ret; +} + +// Send INFO +int send_INFO(tsip_dialog_invite_t *self, const char* content_type, const void* content_ptr, tsk_size_t content_size) +{ + int ret = -1; + tsip_request_t *info = tsk_null; + + if (!self) { + TSK_DEBUG_ERROR("Invalid parameter"); + goto bail; + } + if ((info = tsip_dialog_request_new(TSIP_DIALOG(self), "INFO"))) { + if (TSIP_DIALOG(self)->curr_action) { + ret = tsip_dialog_apply_action(info, TSIP_DIALOG(self)->curr_action); + if (ret) { + goto bail; + } + } + if (content_type && content_ptr && content_size) { + ret = tsip_message_add_content(info, content_type, content_ptr, content_size); + if (ret) { + goto bail; + } + } + ret = tsip_dialog_request_send(TSIP_DIALOG(self), info); + if (ret) { + goto bail; + } + } + +bail: + TSK_OBJECT_SAFE_FREE(info); + return ret; +} + +// Send ACK +int send_ACK(tsip_dialog_invite_t *self, const tsip_response_t* r2xxINVITE) +{ + int ret = -1; + tsip_request_t *request = tsk_null; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + goto bail; + } + + if((request = tsip_dialog_request_new(TSIP_DIALOG(self), "ACK"))){ + + /* The initial INVITE sent by us was a bodiless request and we don't support 100rel (We are Alice) + 1. Alice sends initial INVITE without offer + 2. Bob's offer is sent in the 2xx INVITE response + 3. Alice's answer is sent in the ACK request + */ + if(self->is_client && (self->last_oInvite && !TSIP_MESSAGE_HAS_CONTENT(self->last_oInvite))){ + const tsdp_message_t* sdp_lo; + char* sdp; + if((sdp_lo = tmedia_session_mgr_get_lo(self->msession_mgr)) && (sdp = tsdp_message_tostring(sdp_lo))){ + tsip_message_add_content(request, "application/sdp", sdp, tsk_strlen(sdp)); + TSK_FREE(sdp); + } + + // Start media session if not done + if(!self->msession_mgr->started && (self->msession_mgr->sdp.lo && self->msession_mgr->sdp.ro)){ + /* Set MSRP Callback */ + if((self->msession_mgr->type & tmedia_msrp) == tmedia_msrp){ + tmedia_session_mgr_set_msrp_cb(self->msession_mgr, TSIP_DIALOG_GET_SS(self)->userdata, TSIP_DIALOG_GET_SS(self)->media.msrp.callback); + } + // starts session manager + ret = tsip_dialog_invite_msession_start(self); + } + } + + /* RFC 3261 - 13.2.2.4 2xx Responses + The UAC core MUST generate an ACK request for each 2xx received from + the transaction layer. The header fields of the ACK are constructed + in the same way as for any request sent within a dialog (see Section + 12) with the exception of the CSeq and the header fields related to + authentication. 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. If the offer in the 2xx response is not + acceptable, the UAC core MUST generate a valid answer in the ACK and + then send a BYE immediately. + ==> Credentials will be added by tsip_dialog_request_new() because they are + associated to the dialog itself. + ==> It's up to us to add/update the CSeq number. + ==> ACK requests sent here will create new client transactions, which means that + they will have there own branches. This is not the case for ACK requests sent from + the transaction layer. + */ + request->CSeq->seq = r2xxINVITE->CSeq->seq; /* As the 2xx has the same CSeq than the INVITE */ + + /* RFC 3261 - 13.2.2.4 2xx Responses + Once the ACK has been constructed, the procedures of [4] are used to + determine the destination address, port and transport. However, the + request is passed to the transport layer directly for transmission, + rather than a client transaction. This is because the UAC core + handles retransmissions of the ACK, not the transaction layer. The + ACK MUST be passed to the client transport every time a + retransmission of the 2xx final response that triggered the ACK + arrives. + */ + if(TSIP_DIALOG_GET_STACK(self)->layer_transport){ + ret = tsip_transport_layer_send(TSIP_DIALOG_GET_STACK(self)->layer_transport, tsk_null, request); + } + else{ + ret = -1; + TSK_DEBUG_ERROR("Not Transport layer associated to this stack"); + } + TSK_OBJECT_SAFE_FREE(request); + } + +bail: + return ret; +} + +// Send any response +int send_RESPONSE(tsip_dialog_invite_t *self, const tsip_request_t* request, short code, const char* phrase, tsk_bool_t force_sdp) +{ + tsip_response_t *response; + int ret = -1; + + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), code, phrase, request))){ + if(TSIP_REQUEST_IS_INVITE(request) || TSIP_REQUEST_IS_UPDATE(request)){ + /* Session timers (for 2xx to INVITE or UPDATE) */ + if(self->required.timer){ + tsip_message_add_headers(response, + TSIP_HEADER_REQUIRE_VA_ARGS("timer"), + TSIP_HEADER_SESSION_EXPIRES_VA_ARGS(self->stimers.timer.timeout, tsk_striequals(self->stimers.refresher, "uas")), + tsk_null + ); + } + else if(self->supported.timer){ + tsip_message_add_headers(response, + TSIP_HEADER_SUPPORTED_VA_ARGS("timer"), + TSIP_HEADER_SESSION_EXPIRES_VA_ARGS(self->stimers.timer.timeout, tsk_striequals(self->stimers.refresher, "uas")), + tsk_null + ); + } + if(self->stimers.minse){ + tsip_message_add_headers(response, + TSIP_HEADER_MIN_SE_VA_ARGS(self->stimers.minse), + tsk_null + ); + } + if(code == 422){ + tsip_message_add_headers(response, + TSIP_HEADER_DUMMY_VA_ARGS("Reason", "SIP; cause=422; text=\"Session Interval Too Small\""), + tsk_null + ); + } + + /* 180 Ringing */ + /* 183 Session in Progress */ + if(code == 180 || code == 183){ + if(self->required._100rel){ + if(self->rseq == 0){ + self->rseq = TSK_ABS((rand() ^ rand()) + 1); + } + tsip_message_add_headers(response, + TSIP_HEADER_REQUIRE_VA_ARGS("100rel"), + TSIP_HEADER_RSEQ_VA_ARGS(self->rseq), + tsk_null + ); + TSK_OBJECT_SAFE_FREE(self->last_o1xxrel); + self->last_o1xxrel = tsk_object_ref(response); + + /* No-Initial reliable 1xx will use tsip_dialog_response_send() instead of this function + * ==> can reseset timeout value and make initial schedule */ + TSIP_DIALOG_TIMER_CANCEL(100rel); + self->timer100rel.timeout = tsip_timers_getA(); + TSIP_DIALOG_INVITE_TIMER_SCHEDULE(100rel); + } + } + + /* Precondition */ + if(code == 180 || code == 183){ + if(self->required.precondition){ + tsip_message_add_headers(response, + TSIP_HEADER_REQUIRE_VA_ARGS("precondition"), + tsk_null + ); + } + } + + + /* SDP content */ + if(self->msession_mgr && force_sdp){ + const tsdp_message_t* sdp_lo; + char* sdp = tsk_null; + if((sdp_lo = tmedia_session_mgr_get_lo(self->msession_mgr)) && (sdp = tsdp_message_tostring(sdp_lo))){ + ret = tsip_message_add_content(response, "application/sdp", sdp, tsk_strlen(sdp)); + if(tsip_dialog_invite_ice_is_enabled(self)){ + ret = tsip_dialog_invite_ice_process_lo(self, sdp_lo); + } + } + TSK_FREE(sdp); + } + + /* Add Allow header */ + tsip_message_add_headers(response, + TSIP_HEADER_DUMMY_VA_ARGS("Allow", TSIP_HEADER_ALLOW_DEFAULT), + tsk_null + ); + } + else if(TSIP_REQUEST_IS_REFER(request)){ + if(self->required.norefersub){ + tsip_message_add_headers(response, + TSIP_HEADER_REQUIRE_VA_ARGS("norefersub"), + tsk_null + ); + } + if(self->supported.norefersub){ + tsip_message_add_headers(response, + TSIP_HEADER_SUPPORTED_VA_ARGS("norefersub"), + tsk_null + ); + } + } + + + ret = tsip_dialog_response_send(TSIP_DIALOG(self), response); + TSK_OBJECT_SAFE_FREE(response); + } + return ret; +} + +// Send error response +int send_ERROR(tsip_dialog_invite_t* self, const tsip_request_t* request, short code, const char* phrase, const char* reason) +{ + tsip_response_t *response; + + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), code, phrase, request))){ + tsip_message_add_headers(response, + TSIP_HEADER_DUMMY_VA_ARGS("Reason", reason), + tsk_null + ); + + tsip_dialog_response_send(TSIP_DIALOG(self), response); + TSK_OBJECT_SAFE_FREE(response); + } + else{ + TSK_DEBUG_ERROR("Failed to create new message"); + } + return 0; +} + +// FSM callback to signal that the dialog is in the terminated state +int tsip_dialog_invite_OnTerminated(tsip_dialog_invite_t *self) +{ + TSK_DEBUG_INFO("=== INVITE Dialog terminated ==="); + + /* Cancel all transactions associated to this dialog (will also be done when the dialog is destroyed ) + worth nothing to do it here in order to cancel in-dialog request (such as INFO, REFER...) + */ + tsip_transac_layer_cancel_by_dialog(TSIP_DIALOG_GET_STACK(self)->layer_transac, TSIP_DIALOG(self)); + + /* stop session manager */ + if(self->msession_mgr && self->msession_mgr->started){ + tmedia_session_mgr_stop(self->msession_mgr); + } + // because of C# and Java garbage collectors, the ICE context could + // be destroyed (then stoppped) very late + if(self->ice.ctx_audio){ + tnet_ice_ctx_stop(self->ice.ctx_audio); + } + if(self->ice.ctx_video){ + tnet_ice_ctx_stop(self->ice.ctx_video); + } + + /* alert the user */ + TSIP_DIALOG_SIGNAL_2(self, tsip_event_code_dialog_terminated, + TSIP_DIALOG(self)->last_error.phrase ? TSIP_DIALOG(self)->last_error.phrase : "Call Terminated", + TSIP_DIALOG(self)->last_error.message); + + /* Remove from the dialog layer. */ + return tsip_dialog_remove(TSIP_DIALOG(self)); +} + +// callback function called when media session error occures asynchronously +static int tsip_dialog_invite_msession_onerror_cb(const void* usrdata, const struct tmedia_session_s* session, const char* reason, tsk_bool_t is_fatal) +{ + tsip_dialog_t *self = (tsip_dialog_t*)usrdata; + + if(self && is_fatal){ + char* str = tsk_null; + tsip_action_t* action; + tsk_sprintf(&str, "SIP; cause=488; text=\"%s\"", (reason ? reason : "Internal error")); + action = tsip_action_create(tsip_atype_hangup, + TSIP_ACTION_SET_HEADER("Reason", str), + TSIP_ACTION_SET_NULL()); + TSK_FREE(str); + + tsip_dialog_hangup(self, action); + TSK_OBJECT_SAFE_FREE(action); + } + + return 0; +} + +// callback function called when media session events (related to rfc5168) occures asynchronously +static int tsip_dialog_invite_msession_rfc5168_cb(const void* usrdata, const struct tmedia_session_s* session, const char* reason, enum tmedia_session_rfc5168_cmd_e command) +{ + tsip_dialog_invite_t *self = (tsip_dialog_invite_t*)usrdata; + + if (self) { + if (command == tmedia_session_rfc5168_cmd_picture_fast_update) { + uint64_t now = tsk_time_now(); + if ((now - self->last_out_fastupdate_time) > TSIP_INFO_FASTUPDATE_OUT_INTERVAL_MIN) { + char* content_ptr = tsk_null; + static const char* __content_type = "application/media_control+xml"; + static const void* __content_format = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + " <media_control>\r\n" + " <vc_primitive>\r\n" + " <to_encoder>\r\n" + " <picture_fast_update>\r\n" + " </picture_fast_update>\r\n" + " </to_encoder>\r\n" + " <stream_id>%llu</stream_id>\r\n" + " </vc_primitive>\r\n" + " </media_control>\r\n"; + TSK_DEBUG_INFO("Media session is asking the sigaling layer to send SIP INFO('picture_fast_update')"); + tsk_sprintf(&content_ptr, __content_format, session->id); + self->last_out_fastupdate_time = now; + return send_INFO(self, __content_type, content_ptr, tsk_strlen(content_ptr)); + } + else { + /* if too close don't update "last_fir_time" to "now" to be sure interval will increase */ + TSK_DEBUG_INFO("Outgoing SIP INFO ('picture_fast_update') requested but delay too close"); + } + } + } + return 0; +} + + + + + + + + + + + + + + + + + + + +//======================================================== +// SIP dialog INVITE object definition +// +static tsk_object_t* tsip_dialog_invite_ctor(tsk_object_t * self, va_list * app) +{ + tsip_dialog_invite_t *dialog = self; + if(dialog){ + tsip_ssession_handle_t *ss = va_arg(*app, tsip_ssession_handle_t *); + const char* call_id = va_arg(*app, const char *); + + /* Initialize base class */ + tsip_dialog_init(TSIP_DIALOG(self), tsip_dialog_INVITE, call_id, ss, _fsm_state_Started, _fsm_state_Terminated); + + /* FSM */ + TSIP_DIALOG_GET_FSM(dialog)->debug = DEBUG_STATE_MACHINE; + tsk_fsm_set_callback_terminated(TSIP_DIALOG_GET_FSM(dialog), TSK_FSM_ONTERMINATED_F(tsip_dialog_invite_OnTerminated), (const void*)dialog); + + /* default values */ + dialog->supported._100rel = ((tsip_ssession_t*)ss)->media.enable_100rel; + dialog->supported.norefersub = tsk_true; + dialog->required.ice = (((tsip_ssession_t*)ss)->media.profile == tmedia_profile_rtcweb); + dialog->supported.ice = (dialog->required.ice || ((tsip_ssession_t*)ss)->media.enable_ice); +#if 0 /* This was a patch for chrome but no longer needed when using version 23.0.1271.64 or later */ + dialog->ice.is_jingle = (((tsip_ssession_t*)ss)->media.profile == tmedia_profile_rtcweb); +#else + dialog->ice.is_jingle = tsk_false; +#endif + dialog->ice.last_sdp_ro_ver = -1; + dialog->use_rtcp = (((tsip_ssession_t*)ss)->media.profile == tmedia_profile_rtcweb) ? tsk_true : ((tsip_ssession_t*)ss)->media.enable_rtcp; + dialog->use_rtcpmux = (((tsip_ssession_t*)ss)->media.profile == tmedia_profile_rtcweb) ? tsk_true : ((tsip_ssession_t*)ss)->media.enable_rtcpmux; + + dialog->ice.last_action_id = tsk_fsm_state_none; + dialog->refersub = tsk_true; + // ... do the same for preconditions, replaces, .... + + /* Initialize the class itself */ + tsip_dialog_invite_init(self); + } + return self; +} + +static tsk_object_t* tsip_dialog_invite_dtor(tsk_object_t * _self) +{ + tsip_dialog_invite_t *self = _self; + if(self){ + // Cancel all timers + tsip_dialog_invite_stimers_cancel(self); + tsip_dialog_invite_qos_timer_cancel(self); + TSIP_DIALOG_TIMER_CANCEL(shutdown); + TSIP_DIALOG_TIMER_CANCEL(100rel); + + // DeInitialize base class + tsip_dialog_deinit(TSIP_DIALOG(self)); + + // DeInitialize self + TSK_OBJECT_SAFE_FREE(self->ss_transf); + TSK_OBJECT_SAFE_FREE(self->msession_mgr); + + TSK_OBJECT_SAFE_FREE(self->last_oInvite); + TSK_OBJECT_SAFE_FREE(self->last_iInvite); + TSK_OBJECT_SAFE_FREE(self->last_o1xxrel); + TSK_OBJECT_SAFE_FREE(self->last_iRefer); + TSK_FREE(self->stimers.refresher); + + TSK_OBJECT_SAFE_FREE(self->ice.ctx_audio); + TSK_OBJECT_SAFE_FREE(self->ice.ctx_video); + TSK_OBJECT_SAFE_FREE(self->ice.last_action); + TSK_OBJECT_SAFE_FREE(self->ice.last_message); + //... + + TSK_DEBUG_INFO("*** INVITE Dialog destroyed ***"); + } + return self; +} + +static int tsip_dialog_invite_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2) +{ + return tsip_dialog_cmp(obj1, obj2); +} + +static const tsk_object_def_t tsip_dialog_invite_def_s = +{ + sizeof(tsip_dialog_invite_t), + tsip_dialog_invite_ctor, + tsip_dialog_invite_dtor, + tsip_dialog_invite_cmp, +}; +const tsk_object_def_t *tsip_dialog_invite_def_t = &tsip_dialog_invite_def_s; diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.cdiv.c b/tinySIP/src/dialogs/tsip_dialog_invite.cdiv.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.cdiv.c diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.client.c b/tinySIP/src/dialogs/tsip_dialog_invite.client.c new file mode 100644 index 0000000..c1be3c5 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.client.c @@ -0,0 +1,333 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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_invite.client.c + * @brief SIP dialog INVITE as per RFC 3261. + * The SOA machine is designed as per RFC 3264 and draft-ietf-sipping-sip-offeranswer-12. + * MMTel services implementation follow 3GPP TS 24.173. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_invite.h" + +#include "tinysip/dialogs/tsip_dialog_invite.common.h" + +#include "tinysip/headers/tsip_header_Session_Expires.h" + +#include "tinysdp/parsers/tsdp_parser_message.h" + +#include "tnet_transport.h" + +#include "tsk_debug.h" + +/* ======================== external functions ======================== */ +extern int send_INVITEorUPDATE(tsip_dialog_invite_t *self, tsk_bool_t is_INVITE, tsk_bool_t force_sdp); +extern int send_ACK(tsip_dialog_invite_t *self, const tsip_response_t* r2xxINVITE); +extern int send_CANCEL(tsip_dialog_invite_t *self); +extern int send_BYE(tsip_dialog_invite_t *self); +extern int send_RESPONSE(tsip_dialog_invite_t *self, const tsip_request_t* request, short code, const char* phrase, tsk_bool_t force_sdp); +extern int tsip_dialog_invite_msession_configure(tsip_dialog_invite_t *self); +extern int tsip_dialog_invite_process_ro(tsip_dialog_invite_t *self, const tsip_message_t* message); +extern int tsip_dialog_invite_stimers_handle(tsip_dialog_invite_t* self, const tsip_message_t* message); +extern int tsip_dialog_invite_notify_parent(tsip_dialog_invite_t *self, const tsip_response_t* response); + +extern int tsip_dialog_invite_ice_timers_set(tsip_dialog_invite_t *self, int64_t timeout); +extern tsk_bool_t tsip_dialog_invite_ice_is_enabled(const tsip_dialog_invite_t * self); + +extern int x0000_Any_2_Any_X_i1xx(va_list *app); + +/* ======================== transitions ======================== */ +static int c0000_Started_2_Outgoing_X_oINVITE(va_list *app); +static int c0000_Outgoing_2_Outgoing_X_iINVITEorUPDATE(va_list *app); +static int c0000_Outgoing_2_Connected_X_i2xxINVITE(va_list *app); +static int c0000_Outgoing_2_Terminated_X_i300_to_i699INVITE(va_list *app); +static int c0000_Outgoing_2_Cancelling_X_oCANCEL(va_list *app); +static int c0000_Cancelling_2_Terminated_X_i300_to_699(va_list *app); /* 487 INVITE (To have more chances, any 300-699) */ + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_is_resp2INVITE(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return TSIP_RESPONSE_IS_TO_INVITE(message); +} + +static tsk_bool_t _fsm_cond_is_resp2CANCEL(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return TSIP_RESPONSE_IS_TO_CANCEL(message); +} + +/* Init FSM */ +int tsip_dialog_invite_client_init(tsip_dialog_invite_t *self) +{ + return tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (send INVITE) -> Outgoing + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_oINVITE, _fsm_state_Outgoing, c0000_Started_2_Outgoing_X_oINVITE, "c0000_Started_2_Outgoing_X_oINVITE"), + + /*======================= + * === Outgoing === + */ + // Outgoing -> (i2xx INVITE) -> Connected + TSK_FSM_ADD(_fsm_state_Outgoing, _fsm_action_i2xx, _fsm_cond_is_resp2INVITE, _fsm_state_Connected, c0000_Outgoing_2_Connected_X_i2xxINVITE, "c0000_Outgoing_2_Connected_X_i2xxINVITE"), + // Outgoing -> (iINVITE ) -> Outgoing + TSK_FSM_ADD_ALWAYS(_fsm_state_Outgoing, _fsm_action_iINVITE, _fsm_state_Outgoing, c0000_Outgoing_2_Outgoing_X_iINVITEorUPDATE, "c0000_Outgoing_2_Outgoing_X_iINVITEorUPDATE"), + // Outgoing -> (iUPDATE) -> Outgoing + TSK_FSM_ADD_ALWAYS(_fsm_state_Outgoing, _fsm_action_iUPDATE, _fsm_state_Outgoing, c0000_Outgoing_2_Outgoing_X_iINVITEorUPDATE, "c0000_Outgoing_2_Outgoing_X_iINVITEorUPDATE"), + // Outgoing -> (oCANCEL) -> Cancelling + TSK_FSM_ADD_ALWAYS(_fsm_state_Outgoing, _fsm_action_oCANCEL, _fsm_state_Cancelling, c0000_Outgoing_2_Cancelling_X_oCANCEL, "c0000_Outgoing_2_Cancelling_X_oCANCEL"), + // Cancelling -> (any response to cancel CANCEL) -> Cancelling + TSK_FSM_ADD(_fsm_state_Cancelling, _fsm_action_i300_to_i699, _fsm_cond_is_resp2CANCEL, _fsm_state_Terminated, tsk_null, "c0000_Cancelling_2_Terminated_X_i300_to_699"), + TSK_FSM_ADD(_fsm_state_Cancelling, _fsm_action_i2xx, _fsm_cond_is_resp2CANCEL, _fsm_state_Cancelling, tsk_null, "c0000_Cancelling_2_Cancelling_X_i2xx"), + // Cancelling -> (i300-699 INVITE) -> Terminated + TSK_FSM_ADD(_fsm_state_Cancelling, _fsm_action_i300_to_i699, _fsm_cond_is_resp2INVITE, _fsm_state_Terminated, c0000_Cancelling_2_Terminated_X_i300_to_699, "c0000_Cancelling_2_Terminated_X_i300_to_699"), + // Outgoing -> (300-699 INVITE) -> Terminated + TSK_FSM_ADD(_fsm_state_Outgoing, _fsm_action_i300_to_i699, _fsm_cond_is_resp2INVITE, _fsm_state_Terminated, c0000_Outgoing_2_Terminated_X_i300_to_i699INVITE, "c0000_Outgoing_2_Terminated_X_i300_to_i699INVITE"), + + TSK_FSM_ADD_NULL()); +} + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +/* Started -> (oINVITE) -> Outgoing +*/ +int c0000_Started_2_Outgoing_X_oINVITE(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + /* This is the first FSM transaction when you try to make an audio/video/msrp call */ + if(!self->msession_mgr){ + int32_t transport_idx = TSIP_DIALOG_GET_STACK(self)->network.transport_idx_default; + self->msession_mgr = tmedia_session_mgr_create(action ? action->media.type : tmedia_all, + TSIP_DIALOG_GET_STACK(self)->network.local_ip[transport_idx], TNET_SOCKET_TYPE_IS_IPV6(TSIP_DIALOG_GET_STACK(self)->network.proxy_cscf_type[transport_idx]), tsk_true); + if(TSIP_DIALOG_GET_STACK(self)->natt.ctx){ + ret = tmedia_session_mgr_set_natt_ctx(self->msession_mgr, TSIP_DIALOG_GET_STACK(self)->natt.ctx, TSIP_DIALOG_GET_STACK(self)->network.aor.ip[transport_idx]); + } + + ret = tmedia_session_mgr_set_ice_ctx(self->msession_mgr, self->ice.ctx_audio, self->ice.ctx_video); + ret = tsip_dialog_invite_msession_configure(self); + } + + /* We are the client */ + self->is_client = tsk_true; + /* Whether it's a client dialog for call transfer */ + self->is_transf = (TSIP_DIALOG_GET_SS(self)->id_parent != TSIP_SSESSION_INVALID_ID); + + /* Get Media type from the action */ + TSIP_DIALOG_GET_SS(self)->media.type = action->media.type; + /* Appy media params received from the user */ + if(!TSK_LIST_IS_EMPTY(action->media.params)){ + tmedia_session_mgr_set_3(self->msession_mgr, action->media.params); + } + + /* RFC 4028 - 7.1. Generating an Initial Session Refresh Request + + A UAC MAY include a Session-Expires header field in an initial + session refresh request if it wants a session timer applied to the + session. The value of this header field indicates the session + interval desired by the UAC. If a Min-SE header is included in the + initial session refresh request, the value of the Session-Expires + MUST be greater than or equal to the value in Min-SE. + + The UAC MAY include the refresher parameter with value 'uac' if it + wants to perform the refreshes. However, it is RECOMMENDED that the + parameter be omitted so that it can be selected by the negotiation + mechanisms described below. + */ + if(TSIP_DIALOG_GET_SS(self)->media.timers.timeout){ + self->stimers.timer.timeout = TSIP_DIALOG_GET_SS(self)->media.timers.timeout; + tsk_strupdate(&self->stimers.refresher, TSIP_DIALOG_GET_SS(self)->media.timers.refresher); + self->stimers.is_refresher = tsk_striequals(self->stimers.refresher, "uac"); + self->supported.timer = tsk_true; + } + + /* QoS + * One Voice Profile - 5.4.1 SIP Precondition Considerations + * The UE shall use the Supported header, and not the Require header, to indicate the support of precondition in + * accordance with Section 5.1.3.1 of 3GPP TS 24.229. + */ + self->qos.type = TSIP_DIALOG_GET_SS(self)->media.qos.type; + self->qos.strength = TSIP_DIALOG_GET_SS(self)->media.qos.strength; + tmedia_session_mgr_set_qos(self->msession_mgr, self->qos.type, self->qos.strength); + self->supported.precondition = (self->qos.strength == tmedia_qos_strength_optional); + self->required.precondition = (self->qos.strength == tmedia_qos_strength_mandatory); + + /* send the request */ + ret = send_INVITE(self, tsk_false); + + /* alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connecting, "Dialog connecting"); + + return ret; +} + +/* Outgoing -> (i2xx INVITE) -> Connected +*/ +int c0000_Outgoing_2_Connected_X_i2xxINVITE(va_list *app) +{ + int ret; + + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_response_t *r2xxINVITE = va_arg(*app, const tsip_response_t *); + /* const tsip_action_t* action = */ va_arg(*app, const tsip_action_t *); + + /* Update the dialog state */ + if((ret = tsip_dialog_update(TSIP_DIALOG(self), r2xxINVITE))){ + return ret; + } + + /* Process remote offer */ + if((ret = tsip_dialog_invite_process_ro(self, r2xxINVITE))){ + send_BYE(self); + return ret; + } + else{ + /* send ACK */ + ret = send_ACK(self, r2xxINVITE); + } + + /* Determine whether the remote party support UPDATE */ + self->support_update = tsip_message_allowed(r2xxINVITE, "UPDATE"); + + /* Session Timers */ + if(self->stimers.timer.timeout){ + tsip_dialog_invite_stimers_handle(self, r2xxINVITE); + } + + // starts ICE timers now that both parties received the "candidates" + if(tsip_dialog_invite_ice_is_enabled(self)){ + tsip_dialog_invite_ice_timers_set(self, (self->required.ice ? -1 : TSIP_DIALOG_INVITE_ICE_CONNCHECK_TIMEOUT)); + } + + /* Alert the user (session) */ + ret = TSIP_DIALOG_INVITE_SIGNAL(self, tsip_ao_request, + TSIP_RESPONSE_CODE(r2xxINVITE), TSIP_RESPONSE_PHRASE(r2xxINVITE), r2xxINVITE); + /* Alert the user (dialog) */ + ret = TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connected, "Dialog connected"); + + if(self->is_transf){ + ret = tsip_dialog_invite_notify_parent(self, r2xxINVITE); + self->is_transf = tsk_false;//final response -> no longer need to notify the parent + } + + /* MSRP File Transfer */ + /*if(TSIP_DIALOG(self)->curr_action && ((TSIP_DIALOG(self)->curr_action->media.type & tmedia_msrp) == tmedia_msrp)){ + // FIXME + tmedia_session_mgr_send_file(self->msession_mgr, "C:\\avatar.png", + TMEDIA_SESSION_SET_NULL()); + }*/ + + return ret; +} + +/* Outgoing -> (iINVITE or iINVITE) -> Outgoing +*/ +int c0000_Outgoing_2_Outgoing_X_iINVITEorUPDATE(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_request_t *rINVITEorUPDATE = va_arg(*app, const tsip_request_t *); + tsk_bool_t force_sdp; + int ret = 0; + + /* process remote offer */ + if((ret = tsip_dialog_invite_process_ro(self, rINVITEorUPDATE))){ + /* Send error */ + return ret; + } + + /* Send 200 OK */ + // force SDP in 200 OK even if the request has the same SDP version + force_sdp = TSIP_MESSAGE_HAS_CONTENT(rINVITEorUPDATE); + ret = send_RESPONSE(self, rINVITEorUPDATE, 200, "OK", + (self->msession_mgr && (force_sdp || self->msession_mgr->ro_changed || self->msession_mgr->state_changed))); + + /* alert the user */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request, + tsip_event_code_dialog_request_incoming, "Incoming Request.", rINVITEorUPDATE); + + + return ret; +} + +/* Outgoing -> (i300-i699 INVITE) -> Terminated +*/ +int c0000_Outgoing_2_Terminated_X_i300_to_i699INVITE(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* set last error (or info) */ + tsip_dialog_set_lasterror_2(TSIP_DIALOG(self), + TSIP_RESPONSE_PHRASE(response), TSIP_RESPONSE_CODE(response), response); + + /* alert the user */ + ret = TSIP_DIALOG_INVITE_SIGNAL(self, tsip_ao_request, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + if(self->is_transf){ + ret = tsip_dialog_invite_notify_parent(self, response); + self->is_transf = tsk_false;//final response -> no longer need to notify the parent + } + + return ret; +} + +/* Outgoing -> (oCANCEL ) -> Cancelling +*/ +int c0000_Outgoing_2_Cancelling_X_oCANCEL(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + + self->is_cancelling = tsk_true; + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog"); + + return send_CANCEL(self); +} + +int c0000_Cancelling_2_Terminated_X_i300_to_699(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Request cancelled", TSIP_RESPONSE_CODE(response)); + + return 0; +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++
\ No newline at end of file diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.conf.c b/tinySIP/src/dialogs/tsip_dialog_invite.conf.c new file mode 100644 index 0000000..4ced2f8 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.conf.c @@ -0,0 +1 @@ +//http://www.quintillion.co.jp/3GPP/Specs/24147-900.pdf
\ No newline at end of file diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.ect.c b/tinySIP/src/dialogs/tsip_dialog_invite.ect.c new file mode 100644 index 0000000..8c21dd2 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.ect.c @@ -0,0 +1,487 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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_invite.ect.c + * @brief Explicit Communication Transfer (ECT) using IP Multimedia (IM) Core Network (CN) subsystem (3GPP TS 24.629) + * The Explicit Communication transfer (ECT) service provides a party involved in a communication to transfer that + * communication to a third party. + * This code inplement Consultative transfer mode (A.2). + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_invite.h" + +#include "tinysip/dialogs/tsip_dialog_invite.common.h" + +#include "tinysip/parsers/tsip_parser_message.h" + +#include "tinysip/headers/tsip_header_Refer_To.h" +#include "tinysip/headers/tsip_header_Referred_By.h" +#include "tinysip/headers/tsip_header_Refer_Sub.h" +#include "tinysip/headers/tsip_header_Supported.h" + +#include "tinysip/parsers/tsip_parser_uri.h" + +#include "tinymedia/tmedia_defaults.h" + +#include "tsk_debug.h" + +static int send_NOTIFY(tsip_dialog_invite_t *self, short code, const char* phrase); +static int send_REFER(tsip_dialog_invite_t *self, const char* to); +static short get_SipFragResponseCode(const tsip_request_t* notify); +static tsip_response_t * get_SipFragMessage(const tsip_request_t* notify); + +extern int send_RESPONSE(tsip_dialog_invite_t *self, const tsip_request_t* request, short code, const char* phrase, tsk_bool_t force_sdp); +extern int send_ERROR(tsip_dialog_invite_t* self, const tsip_request_t* request, short code, const char* phrase, const char* reason); +extern int send_BYE(tsip_dialog_invite_t *self); + +/* ======================== transitions ======================== */ +static int x0400_Connected_2_oECTing_X_oECT(va_list *app); +static int x0400_oECTing_2_oECTing_X_i2xx(va_list *app); // should be 202? +static int x0400_oECTing_2_Connected_X_i3456(va_list *app); +static int x0400_oECTing_2_oECTing_X_iNOTIFY(va_list *app); // Notify 1xx +static int x0400_oECTing_2_Connected_X_iNOTIFY(va_list *app); // Notify 200_to_699 + +static int x0400_Connected_2_iECTreq_X_iREFER(va_list *app); +static int x0400_iECTreq_2_Connected_X_reject(va_list *app); +static int x0400_iECTreq_2_iECTing_X_accept(va_list *app); +static int x0400_iECTing_2_iECTing_X_1xxfNOTIFY(va_list *app); +static int x0400_iECTing_2_Connected_X_23456fNOTIFY(va_list *app); +#define x0400_Connected_2_Connected_X_fREFER tsk_null + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_is_resp2REFER(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return TSIP_RESPONSE_IS_TO_REFER(message); +} +static tsk_bool_t _fsm_cond_is_1xxNOTIFY(tsip_dialog_invite_t* self, tsip_request_t* notify) +{ + short code = get_SipFragResponseCode(notify); + return (code >= 100 && code <= 199); +} +static tsk_bool_t _fsm_cond_is_23456NOTIFY(tsip_dialog_invite_t* self, tsip_request_t* notify) +{ + short code = get_SipFragResponseCode(notify); + return (code >= 200 && code <= 699); +} +static tsk_bool_t _fsm_cond_is_fREFER(tsip_dialog_invite_t* self, tsip_request_t* refer) +{ + const tsip_header_Refer_To_t* Refer_To = (const tsip_header_Refer_To_t*)tsip_message_get_header(refer, tsip_htype_Refer_To); + return (!Refer_To || !Refer_To->uri); +} +static tsk_bool_t _fsm_cond_is_1xxfNOTIFY(tsip_dialog_invite_t* self, tsip_response_t* response) +{ + short code = TSIP_RESPONSE_CODE(response); + return (code >= 100 && code <= 199); +} +static tsk_bool_t _fsm_cond_is_23456fNOTIFY(tsip_dialog_invite_t* self, tsip_response_t* response) +{ + short code = TSIP_RESPONSE_CODE(response); + return (code >= 200 && code <= 699); +} + +int tsip_dialog_invite_ect_init(tsip_dialog_invite_t *self) +{ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Outgoing Transfer === + */ + // Connected -> (oREFER) -> oECTing + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_oECT, _fsm_state_oECTing, x0400_Connected_2_oECTing_X_oECT, "x0400_Connected_2_oECTing_X_oECT"), + // oECTing -> (i2xx REFER) -> oECTing + TSK_FSM_ADD(_fsm_state_oECTing, _fsm_action_i2xx, _fsm_cond_is_resp2REFER, _fsm_state_oECTing, x0400_oECTing_2_oECTing_X_i2xx, "x0400_oECTing_2_oECTing_X_i2xx"), + // oECTing -> (i300-699 REFER) -> Connected + TSK_FSM_ADD(_fsm_state_oECTing, _fsm_action_i300_to_i699, _fsm_cond_is_resp2REFER, _fsm_state_Connected, x0400_oECTing_2_Connected_X_i3456, "x0400_ECTing_2_Connected_X_i36"), + // oECTing -> (iNotify 1xx sipfrag) -> oECTing + TSK_FSM_ADD(_fsm_state_oECTing, _fsm_action_iNOTIFY, _fsm_cond_is_1xxNOTIFY, _fsm_state_oECTing, x0400_oECTing_2_oECTing_X_iNOTIFY, "x0400_oECTing_2_oECTing_X_iNOTIFY"), + // oECTing -> (iNotify 23456 sipfrag) -> Connected + TSK_FSM_ADD(_fsm_state_oECTing, _fsm_action_iNOTIFY, _fsm_cond_is_23456NOTIFY, _fsm_state_Connected, x0400_oECTing_2_Connected_X_iNOTIFY, "x0400_oECTing_2_Connected_X_iNOTIFY"), + + /*======================= + * === Incoming Transfer === + */ + // Connected -> (iREFER invalid) -> Connected + TSK_FSM_ADD(_fsm_state_Connected, _fsm_action_iREFER, _fsm_cond_is_fREFER, _fsm_state_Connected, x0400_Connected_2_Connected_X_fREFER, "x0400_Connected_2_Connected_X_fREFER"), + // Connected -> (iREFER) -> iECTreq + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_iREFER, _fsm_state_iECTreq, x0400_Connected_2_iECTreq_X_iREFER, "x0400_Connected_2_iECTreq_X_iREFER"), + // iECTreq -> (reject) -> Connected + TSK_FSM_ADD_ALWAYS(_fsm_state_iECTreq, _fsm_action_iECT_REJECT, _fsm_state_Connected, x0400_iECTreq_2_Connected_X_reject, "x0400_iECTreq_2_Connected_X_reject"), + // iECTreq -> (accept) -> iECTing + TSK_FSM_ADD_ALWAYS(_fsm_state_iECTreq, _fsm_action_iECT_ACCEPT, _fsm_state_iECTing, x0400_iECTreq_2_iECTing_X_accept, "x0400_iECTreq_2_iECTing_X_accept"), + // iECTing -> (1xx lnotify) -> iECTing + TSK_FSM_ADD(_fsm_state_iECTing, _fsm_action_iECT_lNOTIFY, _fsm_cond_is_1xxfNOTIFY, _fsm_state_iECTing, x0400_iECTing_2_iECTing_X_1xxfNOTIFY, "x0400_iECTing_2_iECTing_X_1xxfNOTIFY"), + // iECTing -> (23456 lnotify) -> Connected + TSK_FSM_ADD(_fsm_state_iECTing, _fsm_action_iECT_lNOTIFY, _fsm_cond_is_23456fNOTIFY, _fsm_state_Connected, x0400_iECTing_2_Connected_X_23456fNOTIFY, "x0400_iECTing_2_Connected_X_23456fNOTIFY"), + + TSK_FSM_ADD_NULL()); + + return 0; +} + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +// Connected -> (oREFER) -> oECTing +static int x0400_Connected_2_oECTing_X_oECT(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + ret = send_REFER(self, action->ect.to); + + if(ret == 0){ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_o_ect_trying, + tsip_event_code_dialog_request_sent, "Call Transfer Initiated", tsk_null); + } + else; //Must never happen + + return ret; +} + +// ECTing -> (i2xx REFER) -> oECTing +static int x0400_oECTing_2_oECTing_X_i2xx(va_list *app) +{ + tsip_dialog_invite_t *self; + const tsip_response_t* response; + const tsip_header_Refer_Sub_t* Refer_Sub; + + self = va_arg(*app, tsip_dialog_invite_t *); + response = va_arg(*app, const tsip_message_t *); + Refer_Sub = (const tsip_header_Refer_Sub_t*)tsip_message_get_header(response, tsip_htype_Refer_Sub); + if(Refer_Sub){ + self->refersub = Refer_Sub->sub; + } + if(tsip_message_required(response, "norefersub")){ + self->required.norefersub = tsk_true; + } + + return TSIP_DIALOG_INVITE_SIGNAL(self, tsip_o_ect_accepted, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); +} + +// oECTing -> (i300-699 REFER) -> Connected +static int x0400_oECTing_2_Connected_X_i3456(va_list *app) +{ + tsip_dialog_invite_t *self; + const tsip_response_t* response; + + self = va_arg(*app, tsip_dialog_invite_t *); + response = va_arg(*app, const tsip_message_t *); + + return TSIP_DIALOG_INVITE_SIGNAL(self, tsip_o_ect_failed, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); +} + +// oECTing -> (iNotify 1xx sipfrag) -> oECTing +static int x0400_oECTing_2_oECTing_X_iNOTIFY(va_list *app) +{ + int ret = 0; + tsip_dialog_invite_t *self; + const tsip_request_t* notify; + tsip_response_t *sipfrag = tsk_null; + + self = va_arg(*app, tsip_dialog_invite_t *); + notify = va_arg(*app, const tsip_message_t *); + + sipfrag = get_SipFragMessage(notify); + if(sipfrag){ + send_RESPONSE(self, notify, 200, "Ok", tsk_false); + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_o_ect_notify, + TSIP_RESPONSE_CODE(sipfrag), TSIP_RESPONSE_PHRASE(sipfrag), notify); + } + + TSK_OBJECT_SAFE_FREE(sipfrag); + + return ret; +} + +// oECTing -> (iNotify 23456 sipfrag) -> Connected +static int x0400_oECTing_2_Connected_X_iNOTIFY(va_list *app) +{ + int ret = 0; + tsip_dialog_invite_t *self; + const tsip_request_t* notify; + tsip_response_t *sipfrag = tsk_null; + + self = va_arg(*app, tsip_dialog_invite_t *); + notify = va_arg(*app, const tsip_message_t *); + + sipfrag = get_SipFragMessage(notify); + if(sipfrag){ + send_RESPONSE(self, notify, 200, "Ok", tsk_false); + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_o_ect_notify, + TSIP_RESPONSE_CODE(sipfrag), TSIP_RESPONSE_PHRASE(sipfrag), notify); + } + + TSK_OBJECT_SAFE_FREE(sipfrag); + + return ret; +} + + +// Connected -> (iREFER) -> iECTreq +static int x0400_Connected_2_iECTreq_X_iREFER(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_request_t* refer; + + self = va_arg(*app, tsip_dialog_invite_t *); + refer = va_arg(*app, const tsip_message_t *); + + TSK_OBJECT_SAFE_FREE(self->last_iRefer); + self->last_iRefer = tsk_object_ref((tsk_object_t*)refer); + + ret = send_RESPONSE(self, self->last_iRefer, 100, "Asking for Transfer", tsk_false); + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_ect_requested, + tsip_event_code_dialog_request_incoming, "Incoming Request", self->last_iRefer); + + return ret; +} + +// iECTreq -> (reject) -> Connected +static int x0400_iECTreq_2_Connected_X_reject(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + short code; + const char* phrase; + char* reason = tsk_null; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + // Send Reject + code = action->line_resp.code>=300 ? action->line_resp.code : 603; + phrase = action->line_resp.phrase ? action->line_resp.phrase : "Decline Transfer"; + tsk_sprintf(&reason, "SIP; cause=%hi; text=\"%s\"", code, phrase); + ret = send_ERROR(self, self->last_iRefer, code, phrase, reason); + TSK_FREE(reason); + + return ret; +} + +// iECTreq -> (accept) -> iECTing +static int x0400_iECTreq_2_iECTing_X_accept(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_header_Refer_To_t* Refer_To; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + // Send 200 OK + ret = send_RESPONSE(self, self->last_iRefer, 200, "Transfering", tsk_false); + Refer_To = (const tsip_header_Refer_To_t*)tsip_message_get_header(self->last_iRefer, tsip_htype_Refer_To); // Not null: already checked + // Make call to the referToUri + TSK_OBJECT_SAFE_FREE(self->ss_transf); + self->ss_transf = tsip_ssession_create(TSIP_DIALOG_GET_STACK(self), + TSIP_SSESSION_SET_PARENT_ID(TSIP_DIALOG_GET_SS(self)->id), + TSIP_SSESSION_SET_NULL()); +#if TSIP_UNDER_WINDOWS // because of DirectShow Attach() + self->ss_transf->media.type = tmedia_audio; +#else + self->ss_transf->media.type = self->msession_mgr ? self->msession_mgr->type : tmedia_defaults_get_media_type(); +#endif + self->ss_transf->owner = tsk_false;// not owned by the end-user -> will be destroyed as soon as the dialog dtor is called + + if(ret == 0){ + ret = tsip_invite_event_signal(tsip_i_ect_newcall, self->ss_transf, + tsip_event_code_dialog_request_outgoing, "ECTing", self->last_iRefer); + } + + ret = tsip_ssession_set(self->ss_transf, + TSIP_SSESSION_SET_TO_OBJ(Refer_To->uri), + TSIP_SSESSION_SET_NULL()); + ret = tsip_api_invite_send_invite(self->ss_transf, self->ss_transf->media.type, + TSIP_ACTION_SET_NULL()); + + return ret; +} + + +// iECTing -> (1xx lnotify) -> iECTing +static int x0400_iECTing_2_iECTing_X_1xxfNOTIFY(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_response_t* response; + + self = va_arg(*app, tsip_dialog_invite_t *); + response = va_arg(*app, const tsip_message_t *); + + // send NOTIFY (event if norefersub enabled) and alert user + ret = send_NOTIFY(self, TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response)); + + return ret; +} + +// iECTing -> (23456 lnotify) -> Connected +static int x0400_iECTing_2_Connected_X_23456fNOTIFY(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_response_t* response; + short code; + + self = va_arg(*app, tsip_dialog_invite_t *); + response = va_arg(*app, const tsip_message_t *); + code = TSIP_RESPONSE_CODE(response); + + // send NOTIFY (event if norefersub enabled) and alert user + ret = send_NOTIFY(self, code, TSIP_RESPONSE_PHRASE(response)); + + if(code >= 200 && code <= 299){ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_ect_completed, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), self->last_iRefer); + // hang up the call + ret = send_BYE(self); + } + else{ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_ect_failed, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), self->last_iRefer); + } + + return ret; +} + + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +static int send_NOTIFY(tsip_dialog_invite_t *self, short code, const char* phrase) +{ + tsip_request_t *notify = tsk_null; + int ret = -1; + + if((notify = tsip_dialog_request_new(TSIP_DIALOG(self), "NOTIFY"))){ + char* sipfrag = tsk_null; + tsk_sprintf(&sipfrag, "%s %hi %s\r\n", TSIP_MESSAGE_VERSION_DEFAULT, code, phrase); + ret = tsip_message_add_content(notify, "message/sipfrag", sipfrag, tsk_strlen(sipfrag)); + ret = tsip_dialog_request_send(TSIP_DIALOG(self), notify); + if(ret == 0){ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_o_ect_notify, code, phrase, notify); + } + TSK_FREE(sipfrag); + TSK_OBJECT_SAFE_FREE(notify); + } + else{ + TSK_DEBUG_ERROR("Failed to create request"); + } + return ret; +} + +static int send_REFER(tsip_dialog_invite_t *self, const char* to) +{ + int ret = 0; + tsip_request_t *refer = tsk_null; + tsip_uri_t* toUri = tsk_null; + + if(!self || !to){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(!(toUri = tsip_uri_parse(to, tsk_strlen(to)))){ + TSK_DEBUG_ERROR("Failed to parse %s", to); + return -1; + } + else{ + // tsk_params_add_param(&toUri->params, "method", "INVITE"); + } + + if((refer = tsip_dialog_request_new(TSIP_DIALOG(self), "REFER"))){ + tsk_istr_t cid; + tsk_strrandom(&cid); + /* Add headers */ + tsip_message_add_headers(refer, + TSIP_HEADER_REFER_TO_VA_ARGS(toUri), + TSIP_HEADER_REFERRED_BY_VA_ARGS(TSIP_DIALOG_GET_STACK(self)->identity.impu, cid), + TSIP_HEADER_REFER_SUB_VA_ARGS(self->refersub), + tsk_null); + if(self->supported.norefersub){ + tsip_message_add_headers(refer, + TSIP_HEADER_SUPPORTED_VA_ARGS("norefersub"), + tsk_null); + } + + ret = tsip_dialog_request_send(TSIP_DIALOG(self), refer); + TSK_OBJECT_SAFE_FREE(refer); + } + + TSK_OBJECT_SAFE_FREE(toUri); + return ret; +} + +static tsip_response_t * get_SipFragMessage(const tsip_request_t* notify) +{ + tsip_response_t *sipfrag = tsk_null; + if(TSIP_MESSAGE_HAS_CONTENT(notify) && tsk_striequals(notify->Content_Type->type, "message/sipfrag")){ + tsk_ragel_state_t state; + tsk_bool_t ret; + char* content = tsk_null; + + // sipfrag is a "tsip_message_t" with an extra \r\n + content = tsk_strndup(notify->Content->data, notify->Content->size); + if(tsk_strLastIndexOf(content, tsk_strlen(content), "\r\n") != tsk_strlen(content) - 2){//Hack for XXX buggy client + tsk_strcat(&content, "\r\n"); + } + tsk_strcat(&content, "\r\n"); + + tsk_ragel_state_init(&state, content, tsk_strlen(content)); + ret = tsip_message_parse(&state, &sipfrag, tsk_false); + TSK_FREE(content); + if(ret && TSIP_MESSAGE_IS_RESPONSE(sipfrag)){ + return sipfrag; + } + TSK_OBJECT_SAFE_FREE(sipfrag); + } + return sipfrag; +} + +static short get_SipFragResponseCode(const tsip_request_t* notify) +{ + tsip_response_t *sipfrag = get_SipFragMessage(notify); + short code = 0; + if(sipfrag){ + code = TSIP_RESPONSE_CODE(sipfrag); + TSK_OBJECT_SAFE_FREE(sipfrag); + } + return code; +}
\ No newline at end of file diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.hold.c b/tinySIP/src/dialogs/tsip_dialog_invite.hold.c new file mode 100644 index 0000000..7845bab --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.hold.c @@ -0,0 +1,252 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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_invite.hold.c + * @brief Communication Hold (3GPP TS 24.610) + * The Communication Hold supplementary service enables a user to suspend the reception of media stream(s) of an established IP multimedia session, + * and resume the media stream(s) at a later time. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_invite.h" + +#include "tinysip/dialogs/tsip_dialog_invite.common.h" + +#include "tsk_debug.h" + +/* ======================== transitions ======================== */ +static int x0100_Connected_2_Holding_X_oHold(va_list *app); +static int x0101_Holding_2_Connected_X_ixxx(va_list *app); +static int x0102_Connected_2_Resuming_X_oResume(va_list *app); +static int x0103_Resuming_2_Connected_X_ixxx(va_list *app); + +static int x0150_Any_2_Any_X_i422(va_list *app); + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_is_resp2INVITEorUPDATE(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return (TSIP_RESPONSE_IS_TO_INVITE(message) || TSIP_RESPONSE_IS_TO_UPDATE(message)); +} + +/* ======================== external functions ======================== */ +extern int send_INVITEorUPDATE(tsip_dialog_invite_t *self, tsk_bool_t is_INVITE, tsk_bool_t force_sdp); +extern int tsip_dialog_invite_process_ro(tsip_dialog_invite_t *self, const tsip_message_t* message); +extern int send_ACK(tsip_dialog_invite_t *self, const tsip_response_t* r2xxINVITE); + +int tsip_dialog_invite_hold_init(tsip_dialog_invite_t *self) +{ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Hold === + */ + // Connected -> (send HOLD) -> Holding + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_oHold, _fsm_state_Holding, x0100_Connected_2_Holding_X_oHold, "x0100_Connected_2_Holding_X_oHold"), + // Holding -> (i2xx) -> Connected + TSK_FSM_ADD(_fsm_state_Holding, _fsm_action_i2xx, _fsm_cond_is_resp2INVITEorUPDATE, _fsm_state_Connected, x0101_Holding_2_Connected_X_ixxx, "x0101_Holding_2_Connected_X_ixxx"), + // Holding -> (i300-699) -> Connected + TSK_FSM_ADD(_fsm_state_Holding, _fsm_action_i300_to_i699, _fsm_cond_is_resp2INVITEorUPDATE, _fsm_state_Connected, x0101_Holding_2_Connected_X_ixxx, "x0101_Holding_2_Connected_X_ixxx"), + + /*======================= + * === Resume === + */ + // Connected -> (send RESUME) -> Resuming + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_oResume, _fsm_state_Resuming, x0102_Connected_2_Resuming_X_oResume, "x0102_Connected_2_Resuming_X_oResume"), + // Resuming -> (i2xx) -> Connected + TSK_FSM_ADD(_fsm_state_Resuming, _fsm_action_i2xx, _fsm_cond_is_resp2INVITEorUPDATE, _fsm_state_Connected, x0103_Resuming_2_Connected_X_ixxx, "x0103_Resuming_2_Connected_X_ixxx"), + // Resuming -> (i300-699) -> Connected + TSK_FSM_ADD(_fsm_state_Resuming, _fsm_action_i300_to_i699, _fsm_cond_is_resp2INVITEorUPDATE, _fsm_state_Connected, x0103_Resuming_2_Connected_X_ixxx, "x0103_Resuming_2_Connected_X_ixxx"), + + TSK_FSM_ADD_NULL()); + + return 0; +} + + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +// Connected -> (send HOLD) -> Holding +int x0100_Connected_2_Holding_X_oHold(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(!self->msession_mgr){ + TSK_DEBUG_WARN("Media Session manager is Null"); + return 0; + } + + /* put on hold */ + ret = tmedia_session_mgr_hold(self->msession_mgr, action->media.type); + + /* send the request */ + if((ret = send_INVITE(self, tsk_false))){ + // FIXME: signal error without breaking the state machine + } + + return 0; +} + +// Holding -> (ixxx) -> Connected +int x0101_Holding_2_Connected_X_ixxx(va_list *app) +{ + int ret; + + tsip_dialog_invite_t* self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_response_t* response = va_arg(*app, const tsip_response_t *); + + /* reset current action */ + tsip_dialog_set_curr_action(TSIP_DIALOG(self), tsk_null); + + /* Process remote offer */ + if((ret = tsip_dialog_invite_process_ro(self, response))){ + /* Send error */ + return ret; + } + else if(TSIP_RESPONSE_IS_TO_INVITE(response)){ + /* send ACK */ + ret = send_ACK(self, response); + } + + /* alert the user */ + if(TSIP_RESPONSE_IS_2XX(response)){ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_m_local_hold_ok, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + self->hold.local = tsk_true; + } + else{ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_m_local_hold_nok, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + self->hold.local = tsk_false; + } + + return ret; +} + +// Connected -> (send RESUME) -> Resuming +int x0102_Connected_2_Resuming_X_oResume(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(!self->msession_mgr){ + TSK_DEBUG_WARN("Media Session manager is Null"); + return 0; + } + + /* Resume both */ + ret = tmedia_session_mgr_resume(self->msession_mgr, action->media.type, tsk_true); + ret = tmedia_session_mgr_resume(self->msession_mgr, action->media.type, tsk_false); + + /* send the request */ + if((ret = send_INVITE(self, tsk_false))){ + // FIXME: signal error without breaking the state machine + } + + return 0; +} + +// Resuming -> (ixxx) -> Connected +int x0103_Resuming_2_Connected_X_ixxx(va_list *app) +{ + int ret; + + tsip_dialog_invite_t* self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_response_t* response = va_arg(*app, const tsip_response_t *); + + /* reset current action */ + tsip_dialog_set_curr_action(TSIP_DIALOG(self), tsk_null); + + /* Process remote offer */ + if((ret = tsip_dialog_invite_process_ro(self, response))){ + /* Send error */ + return ret; + } + else if(TSIP_RESPONSE_IS_TO_INVITE(response)){ + /* send ACK */ + ret = send_ACK(self, response); + } + + /* alert the user */ + if(TSIP_RESPONSE_IS_2XX(response)){ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_m_local_resume_ok, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + self->hold.local = tsk_false; + } + else{ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_m_local_resume_nok, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + self->hold.local = tsk_true; + } + + return ret; +} + +/* handle requests/responses (MUST be called after set_ro()) */ +int tsip_dialog_invite_hold_handle(tsip_dialog_invite_t* self, const tsip_request_t* rINVITEorUPDATE) +{ + tsk_bool_t remote_hold, bodiless_invite; + int ret = 0; + + if(!self || !rINVITEorUPDATE || !self->msession_mgr){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + remote_hold = tmedia_session_mgr_is_held(self->msession_mgr, self->msession_mgr->type, tsk_false); + + // resume the call if we receive bodiless INVITE + bodiless_invite = !TSIP_MESSAGE_HAS_CONTENT(rINVITEorUPDATE) && TSIP_REQUEST_IS_INVITE(rINVITEorUPDATE); + if(bodiless_invite && remote_hold){ + // resume remote + if((ret = tmedia_session_mgr_resume(self->msession_mgr, self->msession_mgr->type, tsk_false)) == 0){ + remote_hold = tsk_false; + } + } + + if(ret == 0 && (remote_hold != self->hold.remote)){ + self->hold.remote = remote_hold; + TSIP_DIALOG_INVITE_SIGNAL(self, self->hold.remote ? tsip_m_remote_hold : tsip_m_remote_resume, + tsip_event_code_dialog_request_incoming, "Hold/Resume state changed", rINVITEorUPDATE); + } + + return ret; +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.ice.c b/tinySIP/src/dialogs/tsip_dialog_invite.ice.c new file mode 100644 index 0000000..823e548 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.ice.c @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2012 Doubango Telecom <http://www.doubango.org> + * + * Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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. + * + */ +#include "tinysip/dialogs/tsip_dialog_invite.h" + +#include "tinysip/dialogs/tsip_dialog_invite.common.h" + +#include "tinysdp/parsers/tsdp_parser_message.h" +#include "tinysdp/tsdp_message.h" +#include "tinysdp/headers/tsdp_header_S.h" +#include "tinysdp/headers/tsdp_header_O.h" + +#include "stun/tnet_stun_types.h" +#include "ice/tnet_ice_ctx.h" + +#include "tsk_debug.h" + +extern int tsip_dialog_invite_msession_start(tsip_dialog_invite_t *self); + +static int tsip_dialog_invite_ice_create_ctx(tsip_dialog_invite_t * self, tmedia_type_t media_type); +static int tsip_dialog_invite_ice_audio_callback(const tnet_ice_event_t *e); +static int tsip_dialog_invite_ice_video_callback(const tnet_ice_event_t *e); +int tsip_dialog_invite_ice_set_media_type(tsip_dialog_invite_t * self, tmedia_type_t media_type); +tsk_bool_t tsip_dialog_invite_ice_got_local_candidates(const tsip_dialog_invite_t * self); +int tsip_dialog_invite_ice_process_ro(tsip_dialog_invite_t * self, const tsdp_message_t* sdp_ro, tsk_bool_t is_remote_offer); + +#define tsip_dialog_invite_ice_cancel_silent_and_sync_ctx(_self) \ +tsip_dialog_invite_ice_set_sync_mode_ctx((_self), tsk_true); \ +tsip_dialog_invite_ice_set_silent_mode_ctx((_self), tsk_true); \ +tsip_dialog_invite_ice_cancel_ctx((_self)); /* "cancelled" event will not be sent and we're sure that cancel operation will be done when the function exit */ \ +tsip_dialog_invite_ice_set_sync_mode_ctx((_self), tsk_false); \ +tsip_dialog_invite_ice_set_silent_mode_ctx((_self), tsk_false); \ + +/* ======================== transitions ======================== */ +// Use "Current" instead of "Any" to avoid priority reordering +static int x0500_Current_2_Current_X_oINVITE(va_list *app); +static int x0500_Current_2_Current_X_iINVITE(va_list *app); + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_get_local_candidates(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + if(self->supported.ice){ + tsk_bool_t use_ice = tsk_false; + // "action->media.type" will be defined for locally initiated media update + tmedia_type_t new_media = TSIP_DIALOG(self)->curr_action ? TSIP_DIALOG(self)->curr_action->media.type : tmedia_none; + + if(message && TSIP_MESSAGE_HAS_CONTENT(message) && tsk_striequals("application/sdp", TSIP_MESSAGE_CONTENT_TYPE(message))){ + // If this code is called this means that we are the "answerer" + // only gets the candidates if ICE is enabled and the remote peer supports ICE + tsdp_message_t* sdp_ro; + const tsdp_header_M_t* M; + int index; + if(!(sdp_ro = tsdp_message_parse(TSIP_MESSAGE_CONTENT_DATA(message), TSIP_MESSAGE_CONTENT_DATA_LENGTH(message)))){ + TSK_DEBUG_ERROR("Failed to parse remote sdp message"); + return tsk_false; + } + + index = 0; + while((M = (const tsdp_header_M_t*)tsdp_message_get_headerAt(sdp_ro, tsdp_htype_M, index++))){ + if(!tsdp_header_M_findA(M, "candidate")){ + use_ice = tsk_false; // do not use ICE if at least on media is ICE-less (e.g. MSRP) + break; + } + use_ice = tsk_true; // only use ICE if there is a least one media line + } + + new_media = tmedia_type_from_sdp(sdp_ro); + + TSK_OBJECT_SAFE_FREE(sdp_ro); + } + else if(!message){ + // we are the "offerer" -> use ICE only for audio or video medias (ignore ice for MSRP) + use_ice = (new_media & tmedia_audio) || (new_media & tmedia_video); + } + + if(use_ice){ + if(!self->ice.ctx_audio && !self->ice.ctx_video){ // First time + return tsk_true; + } + else{ + if(self->ice.media_type != new_media){ + return tsk_true; + } + return !tsip_dialog_invite_ice_got_local_candidates(self); + } + } + } + return tsk_false; +} + +int tsip_dialog_invite_ice_init(tsip_dialog_invite_t *self) +{ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + // Current -> (oINVITE) -> Current + TSK_FSM_ADD(tsk_fsm_state_current, _fsm_action_oINVITE, _fsm_cond_get_local_candidates, tsk_fsm_state_current, x0500_Current_2_Current_X_oINVITE, "x0500_Current_2_Current_X_oINVITE"), + // Current -> (iINVITE) -> Current + TSK_FSM_ADD(tsk_fsm_state_current, _fsm_action_iINVITE, _fsm_cond_get_local_candidates, tsk_fsm_state_current, x0500_Current_2_Current_X_iINVITE, "x0500_Current_2_Current_X_iINVITE") + ); + + return 0; +} + +int tsip_dialog_invite_ice_timers_set(tsip_dialog_invite_t *self, int64_t timeout) +{ + if(/*tnet_ice_ctx_is_active*/(self->ice.ctx_audio)){ + tnet_ice_ctx_set_concheck_timeout(self->ice.ctx_audio, timeout); + } + if(/*tnet_ice_ctx_is_active*/(self->ice.ctx_video)){ + tnet_ice_ctx_set_concheck_timeout(self->ice.ctx_video, timeout); + } + return 0; +} + +static int tsip_dialog_invite_ice_create_ctx(tsip_dialog_invite_t * self, tmedia_type_t media_type) +{ + int32_t transport_idx; + int ret = 0; + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + transport_idx = TSIP_DIALOG_GET_STACK(self)->network.transport_idx_default; + if (!self->ice.ctx_audio && (media_type & tmedia_audio)) { + self->ice.ctx_audio = tnet_ice_ctx_create(self->ice.is_jingle, TNET_SOCKET_TYPE_IS_IPV6(TSIP_DIALOG_GET_STACK(self)->network.proxy_cscf_type[transport_idx]), + self->use_rtcp, tsk_false, tsip_dialog_invite_ice_audio_callback, self); + if (!self->ice.ctx_audio) { + TSK_DEBUG_ERROR("Failed to create ICE audio context"); + return -2; + } +#if 0 // @deprecated + ret = tnet_ice_ctx_set_stun(self->ice.ctx_audio, TSIP_DIALOG_GET_SS(self)->media.stun.hostname, TSIP_DIALOG_GET_SS(self)->media.stun.port, kStunSoftware, TSIP_DIALOG_GET_SS(self)->media.stun.username, TSIP_DIALOG_GET_SS(self)->media.stun.password); +#else + ret = tnet_ice_ctx_add_server( + self->ice.ctx_audio, + "udp", // "tcp", "tls", "ws", "wss"... + TSIP_DIALOG_GET_SS(self)->media.stun.hostname, + TSIP_DIALOG_GET_SS(self)->media.stun.port, + TSIP_DIALOG_GET_SS(self)->media.enable_iceturn, + TSIP_DIALOG_GET_SS(self)->media.enable_icestun, + TSIP_DIALOG_GET_SS(self)->media.stun.username, + TSIP_DIALOG_GET_SS(self)->media.stun.password); +#endif + ret = tnet_ice_ctx_set_turn_enabled(self->ice.ctx_audio, TSIP_DIALOG_GET_SS(self)->media.enable_iceturn); + ret = tnet_ice_ctx_set_stun_enabled(self->ice.ctx_audio, TSIP_DIALOG_GET_SS(self)->media.enable_icestun); + ret = tnet_ice_ctx_set_rtcpmux(self->ice.ctx_audio, self->use_rtcpmux); + } + if (!self->ice.ctx_video && (media_type & tmedia_video)) { + self->ice.ctx_video = tnet_ice_ctx_create(self->ice.is_jingle, TNET_SOCKET_TYPE_IS_IPV6(TSIP_DIALOG_GET_STACK(self)->network.proxy_cscf_type[transport_idx]), + self->use_rtcp, tsk_true, tsip_dialog_invite_ice_video_callback, self); + if (!self->ice.ctx_video) { + TSK_DEBUG_ERROR("Failed to create ICE video context"); + return -2; + } +#if 0 // @deprecated + ret = tnet_ice_ctx_set_stun(self->ice.ctx_video, TSIP_DIALOG_GET_SS(self)->media.stun.hostname, TSIP_DIALOG_GET_SS(self)->media.stun.port, kStunSoftware, TSIP_DIALOG_GET_SS(self)->media.stun.username, TSIP_DIALOG_GET_SS(self)->media.stun.password); +#else + ret = tnet_ice_ctx_add_server( + self->ice.ctx_video, + "udp", // "tcp", "tls", "ws", "wss"... + TSIP_DIALOG_GET_SS(self)->media.stun.hostname, + TSIP_DIALOG_GET_SS(self)->media.stun.port, + TSIP_DIALOG_GET_SS(self)->media.enable_iceturn, + TSIP_DIALOG_GET_SS(self)->media.enable_icestun, + TSIP_DIALOG_GET_SS(self)->media.stun.username, + TSIP_DIALOG_GET_SS(self)->media.stun.password); +#endif + ret = tnet_ice_ctx_set_turn_enabled(self->ice.ctx_video, TSIP_DIALOG_GET_SS(self)->media.enable_iceturn); + ret = tnet_ice_ctx_set_stun_enabled(self->ice.ctx_video, TSIP_DIALOG_GET_SS(self)->media.enable_icestun); + ret = tnet_ice_ctx_set_rtcpmux(self->ice.ctx_video, self->use_rtcpmux); + } + + // set media type + ret = tsip_dialog_invite_ice_set_media_type(self, media_type); + + // update session manager with the right ICE contexts + if (self->msession_mgr) { + ret = tmedia_session_mgr_set_ice_ctx(self->msession_mgr, self->ice.ctx_audio, self->ice.ctx_video); + } + + return ret; +} + +int tsip_dialog_invite_ice_set_media_type(tsip_dialog_invite_t * self, tmedia_type_t _media_type) +{ + if(self){ + tmedia_type_t av_media_type = (_media_type & tmedia_audiovideo); // filter to keep audio and video only + // "none" comparison is used to exclude the "first call" + if(self->ice.media_type != tmedia_none && self->ice.media_type != av_media_type){ + // cancels contexts associated to old medias + if(self->ice.ctx_audio && !(av_media_type & tmedia_audio)){ + tnet_ice_ctx_cancel(self->ice.ctx_audio); + } + if(self->ice.ctx_video && !(av_media_type & tmedia_video)){ + tnet_ice_ctx_cancel(self->ice.ctx_video); + } + // cancels contexts associated to new medias (e.g. session "remove" then "add") + // cancel() on newly created contexts don't have any effect + if(self->ice.ctx_audio && (!(av_media_type & tmedia_audio) && (self->ice.media_type & tmedia_audio))){ + //tnet_ice_ctx_cancel(self->ice.ctx_audio); + } + if(self->ice.ctx_video && (!(av_media_type & tmedia_video) && (self->ice.media_type & tmedia_video))){ + //tnet_ice_ctx_cancel(self->ice.ctx_video); + } + } + self->ice.media_type = av_media_type; + } + return 0; +} + +static int tsip_dialog_invite_ice_start_ctx(tsip_dialog_invite_t * self) +{ + int ret = 0; + if(self){ + if((self->ice.media_type & tmedia_audio)){ + if(self->ice.ctx_audio && (ret = tnet_ice_ctx_start(self->ice.ctx_audio)) != 0){ + return ret; + } + } + if((self->ice.media_type & tmedia_video)){ + if(self->ice.ctx_video && (ret = tnet_ice_ctx_start(self->ice.ctx_video)) != 0){ + return ret; + } + } + } + return 0; +} + +static int tsip_dialog_invite_ice_cancel_ctx(tsip_dialog_invite_t * self) +{ + int ret = 0; + if(self){ + if((self->ice.media_type & tmedia_audio)){ + if(self->ice.ctx_audio && (ret = tnet_ice_ctx_cancel(self->ice.ctx_audio)) != 0){ + return ret; + } + } + if((self->ice.media_type & tmedia_video)){ + if(self->ice.ctx_video && (ret = tnet_ice_ctx_cancel(self->ice.ctx_video)) != 0){ + return ret; + } + } + } + return 0; +} + +static int tsip_dialog_invite_ice_set_sync_mode_ctx(tsip_dialog_invite_t * self, tsk_bool_t sync_mode) +{ + int ret = 0; + if(self){ + if((self->ice.media_type & tmedia_audio)){ + if(self->ice.ctx_audio && (ret = tnet_ice_ctx_set_sync_mode(self->ice.ctx_audio, sync_mode)) != 0){ + return ret; + } + } + if((self->ice.media_type & tmedia_video)){ + if(self->ice.ctx_video && (ret = tnet_ice_ctx_set_sync_mode(self->ice.ctx_video, sync_mode)) != 0){ + return ret; + } + } + } + return 0; +} + +static int tsip_dialog_invite_ice_set_silent_mode_ctx(tsip_dialog_invite_t * self, tsk_bool_t silent_mode) +{ + int ret = 0; + if(self){ + if((self->ice.media_type & tmedia_audio)){ + if(self->ice.ctx_audio && (ret = tnet_ice_ctx_set_silent_mode(self->ice.ctx_audio, silent_mode)) != 0){ + return ret; + } + } + if((self->ice.media_type & tmedia_video)){ + if(self->ice.ctx_video && (ret = tnet_ice_ctx_set_silent_mode(self->ice.ctx_video, silent_mode)) != 0){ + return ret; + } + } + } + return 0; +} + +tsk_bool_t tsip_dialog_invite_ice_is_enabled(const tsip_dialog_invite_t * self) +{ + if(self){ + return (self->supported.ice && (tnet_ice_ctx_is_active(self->ice.ctx_audio) || tnet_ice_ctx_is_active(self->ice.ctx_video))); + } + return tsk_false; +} + +tsk_bool_t tsip_dialog_invite_ice_is_connected(const tsip_dialog_invite_t * self) +{ + if(self){ + return (!tnet_ice_ctx_is_active(self->ice.ctx_audio) || tnet_ice_ctx_is_connected(self->ice.ctx_audio)) + && (!tnet_ice_ctx_is_active(self->ice.ctx_video) || tnet_ice_ctx_is_connected(self->ice.ctx_video)); + } + return tsk_false; +} + +tsk_bool_t tsip_dialog_invite_ice_got_local_candidates(const tsip_dialog_invite_t * self) +{ + if(self){ + return (!tnet_ice_ctx_is_active(self->ice.ctx_audio) || tnet_ice_ctx_got_local_candidates(self->ice.ctx_audio)) + && (!tnet_ice_ctx_is_active(self->ice.ctx_video) || tnet_ice_ctx_got_local_candidates(self->ice.ctx_video)); + } + return tsk_false; +} + +int tsip_dialog_invite_ice_save_action(tsip_dialog_invite_t * self, tsk_fsm_action_id action_id, const tsip_action_t* action, const tsip_message_t* message) +{ + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + // There are good reasons to ref() the action and message before safe_free() + // /!\ do not change + + self->ice.last_action_id = action_id; + action = tsk_object_ref((tsk_object_t*)action); + TSK_OBJECT_SAFE_FREE(self->ice.last_action); + self->ice.last_action = (tsip_action_t*)action; + + message = tsk_object_ref((tsk_object_t*)message); + TSK_OBJECT_SAFE_FREE(self->ice.last_message); + self->ice.last_message = (tsip_message_t*)message; + return 0; +} + +int tsip_dialog_invite_ice_process_lo(tsip_dialog_invite_t * self, const tsdp_message_t* sdp_lo) +{ + const tsdp_header_M_t* M; + const tsdp_header_A_t *A; + int ret = 0, i; + + if(!self || !sdp_lo){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + // cancels all ICE contexts without candidates + // this happens if codecs negotiations mismatch for one media out of two or three + for(i = 0; i < 2; ++i){ + struct tnet_ice_ctx_s *ctx = i == 0 ? self->ice.ctx_audio : self->ice.ctx_video; + const char* media = i == 0 ? "audio" : "video"; + if(tnet_ice_ctx_is_active(ctx)){ + tsk_bool_t cancel = tsk_true; + if((M = tsdp_message_find_media(sdp_lo, media))){ + if((A = tsdp_header_M_findA(M, "candidate"))){ + cancel = tsk_false; + } + } + if(cancel){ + ret = tnet_ice_ctx_cancel(ctx); + } + } + } + + return ret; +} + +int tsip_dialog_invite_ice_process_ro(tsip_dialog_invite_t * self, const tsdp_message_t* sdp_ro, tsk_bool_t is_remote_offer) +{ + char* ice_remote_candidates; + const tsdp_header_M_t* M; + tsk_size_t index; + const tsdp_header_A_t *A; + const tsdp_header_O_t *O; + const char* sess_ufrag = tsk_null; + const char* sess_pwd = tsk_null; + int ret = 0, i; + struct tnet_ice_ctx_s *ctx; + + if(!self || !sdp_ro){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + if(!self->ice.ctx_audio && !self->ice.ctx_video){ + return 0; + } + + // make sure this is different SDP + if((O = (const tsdp_header_O_t*)tsdp_message_get_header(sdp_ro, tsdp_htype_O))){ + if(self->ice.last_sdp_ro_ver == (int32_t)O->sess_version){ + TSK_DEBUG_INFO("ICE: ignore processing SDP RO because version haven't changed"); + return 0; + } + self->ice.last_sdp_ro_ver = (int32_t)O->sess_version; + } + + // session level attributes + + if((A = tsdp_message_get_headerA(sdp_ro, "ice-ufrag"))){ + sess_ufrag = A->value; + } + if((A = tsdp_message_get_headerA(sdp_ro, "ice-pwd"))){ + sess_pwd = A->value; + } + +#if 0 // Use RTCWeb Profile (tmedia_profile_rtcweb) + { + const tsdp_header_S_t *S; + if((S = (const tsdp_header_S_t *)tsdp_message_get_header(sdp_ro, tsdp_htype_S)) && S->value){ + self->ice.is_jingle = tsk_strcontains(S->value, tsk_strlen(S->value), "webrtc"); + } + } +#endif + + for(i = 0; i < 2; ++i){ + if((M = tsdp_message_find_media(sdp_ro, i==0 ? "audio": "video"))){ + const char *ufrag = sess_ufrag, *pwd = sess_pwd; + tsk_bool_t remote_use_rtcpmux = (tsdp_header_M_findA(M, "rtcp-mux") != tsk_null); + ctx = (i==0 ? self->ice.ctx_audio : self->ice.ctx_video); + ice_remote_candidates = tsk_null; + index = 0; + if((A = tsdp_header_M_findA(M, "ice-ufrag"))){ + ufrag = A->value; + } + if((A = tsdp_header_M_findA(M, "ice-pwd"))){ + pwd = A->value; + } + + while((A = tsdp_header_M_findA_at(M, "candidate", index++))){ + tsk_strcat_2(&ice_remote_candidates, "%s\r\n", A->value); + } + // ICE processing will be automatically stopped if the remote candidates are not valid + // ICE-CONTROLLING role if we are the offerer + ret = tnet_ice_ctx_set_remote_candidates_2(ctx, ice_remote_candidates, ufrag, pwd, !is_remote_offer, self->ice.is_jingle, (self->use_rtcpmux && remote_use_rtcpmux)); + TSK_SAFE_FREE(ice_remote_candidates); + } + } + + return ret; +} + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + + +// Current -> (oINVITE) -> Current +static int x0500_Current_2_Current_X_oINVITE(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + const tsip_message_t *message; + tmedia_type_t media_type; + static const tsk_bool_t __force_restart_is_yes = tsk_true; + + self = va_arg(*app, tsip_dialog_invite_t *); + message = va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + media_type = (action && action->media.type != tmedia_none) ? action->media.type : TSIP_DIALOG_GET_SS(self)->media.type; + self->is_client = tsk_true; + tsip_dialog_invite_ice_save_action(self, _fsm_action_oINVITE, action, message); + + // Cancel without notifying ("silent mode") and perform the operation right now ("sync mode") + tsip_dialog_invite_ice_cancel_silent_and_sync_ctx(self); + + // create ICE context + if((ret = tsip_dialog_invite_ice_create_ctx(self, media_type))){ + TSK_DEBUG_ERROR("tsip_dialog_invite_ice_create_ctx() failed"); + return ret; + } + + // For now disable ICE timers until we receive the 2xx + ret = tsip_dialog_invite_ice_timers_set(self, -1); + + // Start ICE + ret = tsip_dialog_invite_ice_start_ctx(self); + + // alert the user only if we are in initial state which means that it's not media update + if(TSIP_DIALOG(self)->state == tsip_initial){ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connecting, "Dialog connecting"); + } + + return ret; +} + +// Current -> (iINVITE) -> Current +static int x0500_Current_2_Current_X_iINVITE(va_list *app) +{ + int ret; + tsip_dialog_invite_t *self; + const tsip_action_t* action; + const tsip_message_t *message; + + self = va_arg(*app, tsip_dialog_invite_t *); + message = va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + self->is_client = tsk_false; + ret = tsip_dialog_invite_ice_save_action(self, _fsm_action_iINVITE, action, message); + + // Cancel without notifying ("silent mode") and perform the operation right now ("sync mode") + tsip_dialog_invite_ice_cancel_silent_and_sync_ctx(self); + + // set remote candidates + if(TSIP_MESSAGE_HAS_CONTENT(message)){ + if(tsk_striequals("application/sdp", TSIP_MESSAGE_CONTENT_TYPE(message))){ + tsdp_message_t* sdp_ro; + if(!(sdp_ro = tsdp_message_parse(TSIP_MESSAGE_CONTENT_DATA(message), TSIP_MESSAGE_CONTENT_DATA_LENGTH(message)))){ + TSK_DEBUG_ERROR("Failed to parse remote sdp message"); + return -2; + } + // create ICE context + if((ret = tsip_dialog_invite_ice_create_ctx(self, tmedia_type_from_sdp(sdp_ro)))){ + TSK_DEBUG_ERROR("tsip_dialog_invite_ice_create_ctx() failed"); + return ret; + } + ret = tsip_dialog_invite_ice_process_ro(self, sdp_ro, tsk_true); + TSK_OBJECT_SAFE_FREE(sdp_ro); + } + else{ + TSK_DEBUG_ERROR("[%s] content-type is not supportted", TSIP_MESSAGE_CONTENT_TYPE(message)); + return -3; + } + } + + // For now disable ICE timers until we send the 2xx and receive the ACK + ret = tsip_dialog_invite_ice_timers_set(self, -1); + + // Start ICE + ret = tsip_dialog_invite_ice_start_ctx(self); + + return ret; +} + + + + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +static int tsip_dialog_invite_ice_callback(const tnet_ice_event_t *e) +{ + int ret = 0; + tsip_dialog_invite_t *dialog; + + TSK_DEBUG_INFO("ICE callback: %s", e->phrase); + + dialog = tsk_object_ref(TSK_OBJECT(e->userdata)); + + // Do not lock: caller is thread safe + + switch(e->type){ + case tnet_ice_event_type_gathering_completed: + case tnet_ice_event_type_conncheck_succeed: + case tnet_ice_event_type_conncheck_failed: + case tnet_ice_event_type_cancelled: + { + if(dialog->ice.last_action_id != tsk_fsm_state_none){ + if(tsip_dialog_invite_ice_got_local_candidates(dialog)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(dialog), dialog->ice.last_action_id, dialog->ice.last_message, dialog->ice.last_action); + dialog->ice.last_action_id = tsk_fsm_state_none; + } + } + if(dialog->ice.start_smgr){ + ret = tsip_dialog_invite_msession_start(dialog); + } + break; + } + // fatal errors which discard ICE process + case tnet_ice_event_type_gathering_host_candidates_failed: + case tnet_ice_event_type_gathering_reflexive_candidates_failed: + case tnet_ice_event_type_gathering_relay_candidates_failed: + { + if (dialog->ice.last_action_id != tsk_fsm_state_none) { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(dialog), dialog->ice.last_action_id, dialog->ice.last_message, dialog->ice.last_action); + dialog->ice.last_action_id = tsk_fsm_state_none; + } + break; + } + // TURN session disconnected while we're in call + case tnet_ice_event_type_turn_connection_broken: + { + ret = tsip_dialog_fsm_act_2(TSIP_DIALOG(dialog), _fsm_action_oBYE); + break; + } + default: break; + } + + TSK_OBJECT_SAFE_FREE(dialog); + + return ret; +} + +static int tsip_dialog_invite_ice_audio_callback(const tnet_ice_event_t *e) +{ + return tsip_dialog_invite_ice_callback(e); +} + +static int tsip_dialog_invite_ice_video_callback(const tnet_ice_event_t *e) +{ + return tsip_dialog_invite_ice_callback(e); +}
\ No newline at end of file diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.qos.c b/tinySIP/src/dialogs/tsip_dialog_invite.qos.c new file mode 100644 index 0000000..8cc7a2a --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.qos.c @@ -0,0 +1,92 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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_invite.qos.c + * @brief Integration of Resource Management and Session Initiation Protocol (SIP) (RFC 3312) + * QoS Reservation. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_invite.h" + +#include "tinysip/dialogs/tsip_dialog_invite.common.h" + +#include "tsk_debug.h" + +#define TSIP_DIALOG_INVITE_QOS_RES_TIMEOUT 20 + +/* ======================== external functions ======================== */ +extern int tsip_dialog_invite_timer_callback(const tsip_dialog_invite_t* self, tsk_timer_id_t timer_id); +extern int send_INVITEorUPDATE(tsip_dialog_invite_t *self, tsk_bool_t is_INVITE, tsk_bool_t force_sdp); + +/* ======================== transitions ======================== */ +static int x0300_Any_2_Any_X_timerRSVP(va_list *app); + +/* Init FSM */ +int tsip_dialog_invite_qos_init(tsip_dialog_invite_t *self) +{ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + // Any -> (timerRSVP) -> Any + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_timerRSVP, tsk_fsm_state_any, x0300_Any_2_Any_X_timerRSVP, "x0300_Any_2_Any_X_timerRSVP"), + + + TSK_FSM_ADD_NULL()); + + return 0; +} + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +// Any -> (tiner RSVP) -> Any +int x0300_Any_2_Any_X_timerRSVP(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + return send_UPDATE(self, tsk_true); +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + + + +/* cancel the timer */ +int tsip_dialog_invite_qos_timer_cancel(tsip_dialog_invite_t* self) +{ + return tsk_timer_mgr_global_cancel(self->qos.timer.id); +} + +/* schedule the timer */ +int tsip_dialog_invite_qos_timer_schedule(tsip_dialog_invite_t* self) +{ + /* To emulate bandwidth reservation (Because RSVP protocol is not supported) */ + self->qos.timer.id = tsk_timer_mgr_global_schedule(TSIP_DIALOG_INVITE_QOS_RES_TIMEOUT, TSK_TIMER_CALLBACK_F(tsip_dialog_invite_timer_callback), self); + + return 0; +} + diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.server.c b/tinySIP/src/dialogs/tsip_dialog_invite.server.c new file mode 100644 index 0000000..2a7a37c --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.server.c @@ -0,0 +1,790 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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_invite.client.c + * @brief SIP dialog INVITE as per RFC 3261. + * The SOA machine is designed as per RFC 3264 and draft-ietf-sipping-sip-offeranswer-12. + * MMTel services implementation follow 3GPP TS 24.173. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_invite.h" + +#include "tinysip/dialogs/tsip_dialog_invite.common.h" + +#include "tinysip/transports/tsip_transport_layer.h" + +#include "tinysip/headers/tsip_header_Dummy.h" +#include "tinysip/headers/tsip_header_Min_SE.h" +#include "tinysip/headers/tsip_header_RAck.h" +#include "tinysip/headers/tsip_header_Require.h" +#include "tinysip/headers/tsip_header_Session_Expires.h" + +#include "tsk_debug.h" + +static const char* supported_options[] = { "100rel", "precondition", "timer" }; + +/* ======================== external functions ======================== */ +extern int tsip_dialog_invite_msession_start(tsip_dialog_invite_t *self); +extern int send_RESPONSE(tsip_dialog_invite_t *self, const tsip_request_t* request, short code, const char* phrase, tsk_bool_t force_sdp); +extern int tsip_dialog_invite_process_ro(tsip_dialog_invite_t *self, const tsip_message_t* message); +extern int tsip_dialog_invite_stimers_schedule(tsip_dialog_invite_t* self, uint64_t timeout); +extern int send_ERROR(tsip_dialog_invite_t* self, const tsip_request_t* request, short code, const char* phrase, const char* reason); + +extern int tsip_dialog_invite_timer_callback(const tsip_dialog_invite_t* self, tsk_timer_id_t timer_id); +extern tsk_bool_t tsip_dialog_invite_ice_is_enabled(const tsip_dialog_invite_t * self); +extern tsk_bool_t tsip_dialog_invite_ice_is_connected(const tsip_dialog_invite_t * self); + +/* ======================== internal functions ======================== */ +static int send_UNSUPPORTED(tsip_dialog_invite_t* self, const tsip_request_t* request, const char* option); + +/* ======================== transitions ======================== */ +static int s0000_Started_2_Terminated_X_iINVITE(va_list *app); // Failure +static int s0000_Started_2_Started_X_iINVITE(va_list *app); // Session Interval Too Small +static int s0000_Started_2_InProgress_X_iINVITE(va_list *app); // 100rel supported +static int s0000_Started_2_Ringing_X_iINVITE(va_list *app); // Neither 100rel nor QoS +static int s0000_InProgress_2_InProgress_X_iPRACK(va_list *app); // PRACK for our 18x response (with QoS) +static int s0000_InProgress_2_Ringing_X_iPRACK(va_list *app); // PRACK for our 18x response (without QoS) +static int s0000_InProgress_2_InProgress_X_iUPDATE(va_list *app); // QoS cannot resume +static int s0000_InProgress_2_Ringing_X_iUPDATE(va_list *app); // QoS can resume (do not alert user, wait for PRACK) +static int s0000_Inprogress_2_Terminated_X_iCANCEL(va_list *app); +static int s0000_Ringing_2_Ringing_X_iPRACK(va_list *app); // Alert user +static int s0000_Ringing_2_Connected_X_Accept(va_list *app); +static int s0000_Ringing_2_Terminated_X_Reject(va_list *app); +static int s0000_Ringing_2_Terminated_X_iCANCEL(va_list *app); +static int s0000_Any_2_Any_X_timer100rel(va_list *app); + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_bad_extension(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + const tsip_header_Require_t* requireHdr; + const tsk_list_item_t* item; + tsk_size_t i, j; + + /* Check if we support all extensions */ + for(i = 0; (requireHdr = (const tsip_header_Require_t*)tsip_message_get_headerAt(message, tsip_htype_Require, i)); i++){ + tsk_bool_t bad_extension = tsk_false; + const tsk_string_t* option = tsk_null; + tsk_list_foreach(item, requireHdr->options){ + option = item->data; + bad_extension = tsk_true; + for(j = 0; option && j<sizeof(supported_options)/sizeof(const char*); j++){ + if(tsk_striequals(option->value, supported_options[j])){ + bad_extension = tsk_false; + break; + } + } + if(bad_extension){ + break; + } + } + if(bad_extension && option){ + send_UNSUPPORTED(self, message, option->value); + return tsk_true; + } + } + + + return tsk_false; +} + +static tsk_bool_t _fsm_cond_bad_content(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + int ret; + const tsdp_message_t* sdp_lo; + tsk_bool_t bodiless_INVITE = (TSIP_DIALOG(self)->state == tsip_initial && !TSIP_MESSAGE_HAS_CONTENT(message)); // Initial Bodiless INVITE + + /* Check remote offer */ + if((ret = tsip_dialog_invite_process_ro(self, message))){ + ret = send_ERROR(self, message, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\""); + return tsk_true; + } + /* generate local offer and check it's validity */ + if(self->msession_mgr && (sdp_lo = tmedia_session_mgr_get_lo(self->msession_mgr))){ + /* check that we have at least one valid session (Only if no bodiless initial INVITE) */ + if(!bodiless_INVITE && !tmedia_session_mgr_has_active_session(self->msession_mgr)){ + ret = send_ERROR(self, message, 488, "Not Acceptable", "SIP; cause=488; text=\"No common codecs\""); + return tsk_true; + } + // media type could change if there are zombies (medias with port equal to zero) + TSIP_DIALOG_GET_SS(self)->media.type = self->msession_mgr->type; + } + else{ + ret = send_ERROR(self, message, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\""); + return tsk_true; + } + + return tsk_false; +} + +static tsk_bool_t _fsm_cond_toosmall(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + if(TSIP_DIALOG_GET_SS(self)->media.timers.timeout && (tsip_message_supported(message, "timer") || tsip_message_required(message, "timer"))){ + const tsip_header_Session_Expires_t* Session_Expires; + if((Session_Expires = (const tsip_header_Session_Expires_t*)tsip_message_get_header(message, tsip_htype_Session_Expires))){ + if(Session_Expires->delta_seconds < TSIP_SESSION_EXPIRES_MIN_VALUE){ + self->stimers.minse = TSIP_SESSION_EXPIRES_MIN_VALUE; + send_RESPONSE(self, message, 422, "Session Interval Too Small", tsk_false); + return tsk_true; + } + else{ + const tsip_header_Min_SE_t* Min_SE; + self->stimers.timer.timeout = Session_Expires->delta_seconds; + tsk_strupdate(&self->stimers.refresher, Session_Expires->refresher_uas ? "uas" : "uac"); + self->stimers.is_refresher = tsk_striequals(self->stimers.refresher, "uas"); + if((Min_SE = (const tsip_header_Min_SE_t*)tsip_message_get_header(message, tsip_htype_Min_SE))){ + self->stimers.minse = Min_SE->delta_seconds; + } + } + } + } + return tsk_false; +} + +// 100rel && (QoS or ICE) +static tsk_bool_t _fsm_cond_use_early_media(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + if((tsip_message_supported(message, "100rel") && self->supported._100rel) || tsip_message_required(message, "100rel")){ + if((tsip_message_supported(message, "precondition") && self->supported.precondition) || tsip_message_required(message, "precondition")){ + return tsk_true; + } + } +#if 0 + if(tsip_dialog_invite_ice_is_enabled(self)){ + return tsk_true; + } +#endif + return tsk_false; +} + + +static tsk_bool_t _fsm_cond_prack_match(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + const tsip_header_RAck_t* RAck; + + if(!self->last_o1xxrel){ + return tsk_false; + } + + if((RAck = (const tsip_header_RAck_t*)tsip_message_get_header(message, tsip_htype_RAck))){ + if((RAck->seq == self->rseq) && + (tsk_striequals(RAck->method, self->last_o1xxrel->CSeq->method)) && + (RAck->cseq == self->last_o1xxrel->CSeq->seq)){ + self->rseq++; + return tsk_true; + } + else{ + TSK_DEBUG_WARN("Failed to match PRACK request"); + } + } + + return tsk_false; +} +static tsk_bool_t _fsm_cond_negociates_preconditions(tsip_dialog_invite_t* self, tsip_message_t* rPRACK) +{ + //tsip_message_supported(self->last_iInvite, "precondition") || tsip_message_required(self->last_iInvite, "precondition") + if(tsip_message_required(self->last_iInvite, "precondition") || (self->msession_mgr && self->msession_mgr->qos.strength == tmedia_qos_strength_mandatory)){ + return tsk_true; + } + return tsk_false; +} +static tsk_bool_t _fsm_cond_cannotresume(tsip_dialog_invite_t* self, tsip_message_t* rUPDATE) +{ + if(!tsip_dialog_invite_process_ro(self, rUPDATE)){ + return !tmedia_session_mgr_canresume(self->msession_mgr); + } + else{ + return tsk_false; + } +} + +static tsk_bool_t _fsm_cond_initial_iack_pending(tsip_dialog_invite_t* self, tsip_message_t* rACK) +{ + return self->is_initial_iack_pending; +} + + + +/* Init FSM */ +int tsip_dialog_invite_server_init(tsip_dialog_invite_t *self) +{ + return tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (Bad Extendion) -> Terminated + TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iINVITE, _fsm_cond_bad_extension, _fsm_state_Terminated, s0000_Started_2_Terminated_X_iINVITE, "s0000_Started_2_Terminated_X_iINVITE"), + // Started -> (Bad content) -> Terminated + TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iINVITE, _fsm_cond_bad_content, _fsm_state_Terminated, s0000_Started_2_Terminated_X_iINVITE, "s0000_Started_2_Terminated_X_iINVITE"), + // Started -> (Session Interval Too Small) -> Started + TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iINVITE, _fsm_cond_toosmall, _fsm_state_Started, s0000_Started_2_Started_X_iINVITE, "s0000_Started_2_Started_X_iINVITE"), + // Started -> (100rel && (QoS or ICE)) -> InProgress + TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iINVITE, _fsm_cond_use_early_media, _fsm_state_InProgress, s0000_Started_2_InProgress_X_iINVITE, "s0000_Started_2_InProgress_X_iINVITE"), + // Started -> (non-100rel and non-QoS, referred to as "basic") -> Ringing + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_iINVITE, _fsm_state_Ringing, s0000_Started_2_Ringing_X_iINVITE, "s0000_Started_2_Ringing_X_iINVITE"), + + + /*======================= + * === InProgress === + */ + // InProgress ->(iPRACK with QoS) -> InProgress + TSK_FSM_ADD(_fsm_state_InProgress, _fsm_action_iPRACK, _fsm_cond_negociates_preconditions, _fsm_state_InProgress, s0000_InProgress_2_InProgress_X_iPRACK, "s0000_InProgress_2_InProgress_X_iPRACK"), + // InProgress ->(iPRACK without QoS) -> Ringing + TSK_FSM_ADD(_fsm_state_InProgress, _fsm_action_iPRACK, _fsm_cond_prack_match, _fsm_state_Ringing, s0000_InProgress_2_Ringing_X_iPRACK, "s0000_InProgress_2_Ringing_X_iPRACK"), + // InProgress ->(iUPDATE but cannot resume) -> InProgress + TSK_FSM_ADD(_fsm_state_InProgress, _fsm_action_iUPDATE, _fsm_cond_cannotresume, _fsm_state_InProgress, s0000_InProgress_2_InProgress_X_iUPDATE, "s0000_InProgress_2_InProgress_X_iUPDATE"), + // InProgress ->(iUPDATE can resume) -> Ringing + TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_iUPDATE, _fsm_state_Ringing, s0000_InProgress_2_Ringing_X_iUPDATE, "s0000_InProgress_2_Ringing_X_iUPDATE"), + // InProgress ->(iCANCEL) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_iCANCEL, _fsm_state_Terminated, s0000_Inprogress_2_Terminated_X_iCANCEL, "s0000_Inprogress_2_Terminated_X_iCANCEL"), + + + /*======================= + * === Ringing === + */ + // Ringing -> (iPRACK) -> Ringing + TSK_FSM_ADD(_fsm_state_Ringing, _fsm_action_iPRACK, _fsm_cond_prack_match, _fsm_state_Ringing, s0000_Ringing_2_Ringing_X_iPRACK, "s0000_Ringing_2_Ringing_X_iPRACK"), + // Ringing -> (oAccept) -> Connected + TSK_FSM_ADD_ALWAYS(_fsm_state_Ringing, _fsm_action_accept, _fsm_state_Connected, s0000_Ringing_2_Connected_X_Accept, "s0000_Ringing_2_Connected_X_Accept"), + // Ringing -> (oReject) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Ringing, _fsm_action_reject, _fsm_state_Terminated, s0000_Ringing_2_Terminated_X_Reject, "s0000_Ringing_2_Terminated_X_Reject"), + // Ringing ->(iCANCEL) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Ringing, _fsm_action_iCANCEL, _fsm_state_Terminated, s0000_Ringing_2_Terminated_X_iCANCEL, "s0000_Ringing_2_Terminated_X_iCANCEL"), + + /*======================= + * === FRESH CONNECTED === + */ + // Fresh Connected [ACK is pending] ->(iCANCEL) -> Terminated + TSK_FSM_ADD(_fsm_state_Connected, _fsm_action_iCANCEL, _fsm_cond_initial_iack_pending, _fsm_state_Terminated, s0000_Ringing_2_Terminated_X_iCANCEL, "s0000_FreshConnected_2_Terminated_X_iCANCEL"), + + /*======================= + * === ANY === + */ + // Any ->(timer100rel) -> Any + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_timer100rel, tsk_fsm_state_any, s0000_Any_2_Any_X_timer100rel, "s0000_Any_2_Any_X_timer100rel"), + + + TSK_FSM_ADD_NULL()); +} + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + + +/* Started -> (Failure) -> Terminated */ +int s0000_Started_2_Terminated_X_iINVITE(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + /* tsip_request_t *request = va_arg(*app, tsip_request_t *); */ + + /* We are not the client */ + self->is_client = tsk_false; + + return 0; +} + +/* Started -> (Too Small) -> Started */ +int s0000_Started_2_Started_X_iINVITE(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + + /* We are not the client */ + self->is_client = tsk_false; + + return 0; +} + +/* Started -> (non-100rel and non-QoS, referred to as "basic") -> Ringing */ +int s0000_Started_2_Ringing_X_iINVITE(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + const tsip_header_Session_Expires_t* hdr_SessionExpires; + + /* we are not the client */ + self->is_client = tsk_false; + + /* update last INVITE */ + TSK_OBJECT_SAFE_FREE(self->last_iInvite); + self->last_iInvite = tsk_object_ref(request); + + // add "require:100rel" tag if the incoming INVITE contains "100rel" tag in "supported" header + if(self->last_iInvite && (tsip_message_supported(self->last_iInvite, "100rel") || tsip_message_required(self->last_iInvite, "100rel")) && self->supported._100rel){ + self->required._100rel = tsk_true; + } + + // add "require:timer" tag if incoming INVITE contains "timer" tag in "supported" header and session timers is enabled + if(TSIP_DIALOG_GET_SS(self)->media.timers.timeout){ + if((hdr_SessionExpires = (const tsip_header_Session_Expires_t*)tsip_message_get_header(request, tsip_htype_Session_Expires))){ + // "hdr_SessionExpires->delta_seconds" smallnest already checked + self->stimers.timer.timeout = hdr_SessionExpires->delta_seconds; + tsk_strupdate(&self->stimers.refresher, hdr_SessionExpires->refresher_uas ? "uas" : "uac"); + self->stimers.is_refresher = tsk_striequals(self->stimers.refresher, "uas"); + self->required.timer = tsk_true; + } + } + + /* update state */ + tsip_dialog_update_2(TSIP_DIALOG(self), request); + + /* send Ringing */ + /*if(TSIP_DIALOG_GET_STACK(self)->network.mode != tsip_stack_mode_webrtc2sip)*/{ + send_RESPONSE(self, request, 180, "Ringing", tsk_false); + } + + /* alert the user (session) */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_newcall, + tsip_event_code_dialog_request_incoming, "Incoming Call", request); + + return 0; +} + +/* Started -> (QoS (preconditions)) -> InProgress */ +int s0000_Started_2_InProgress_X_iINVITE(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + + /* We are not the client */ + self->is_client = tsk_false; + + /* update last INVITE */ + TSK_OBJECT_SAFE_FREE(self->last_iInvite); + self->last_iInvite = tsk_object_ref(request); + + /* Update state */ + tsip_dialog_update_2(TSIP_DIALOG(self), request); + + /* Send In Progress + RFC 3262 - 3 UAS Behavior + + The provisional response to be sent reliably is constructed by the + UAS core according to the procedures of Section 8.2.6 of RFC 3261. + In addition, it MUST contain a Require header field containing the + option tag 100rel, and MUST include an RSeq header field. The value + of the header field for the first reliable provisional response in a + transaction MUST be between 1 and 2**31 - 1. + */ + self->rseq = (rand() ^ rand()) % (0x00000001 << 31); + self->required._100rel = tsk_true; + self->required.precondition = (tsip_message_supported(self->last_iInvite, "precondition") || tsip_message_required(self->last_iInvite, "precondition")); + send_RESPONSE(self, request, 183, "Session in Progress", tsk_true); + + return 0; +} + +/* InProgress ->(iPRACK with QoS) -> InProgress */ +int s0000_InProgress_2_InProgress_X_iPRACK(va_list *app) +{ + int ret; + + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + + /* Cancel 100rel timer */ + TSIP_DIALOG_TIMER_CANCEL(100rel); + + /* In all cases: Send 2xx PRACK */ + if(!(ret = send_RESPONSE(self, request, 200, "OK", tsk_false))){ + ++self->rseq; + } + + /* + 1. Alice sends an initial INVITE without offer + 2. Bob's answer is sent in the first reliable provisional response, in this case it's a 1xx INVITE response + 3. Alice's answer is sent in the PRACK response + */ + if(!self->msession_mgr->sdp.ro){ + if(TSIP_MESSAGE_HAS_CONTENT(request)){ + if((ret = tsip_dialog_invite_process_ro(self, request))){ + /* Send Error and break the FSM */ + ret = send_ERROR(self, self->last_iInvite, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\""); + return -4; + } + } + else{ + /* 488 INVITE */ + ret = send_ERROR(self, self->last_iInvite, 488, "Not Acceptable", "SIP; cause=488; text=\"Offer expected in the PRACK\""); + return -3; + } + } + + return ret; +} + +/* InProgress ->(iPRACK without QoS) -> Ringing */ +int s0000_InProgress_2_Ringing_X_iPRACK(va_list *app) +{ + int ret; + + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + + /* Cancel 100rel timer */ + TSIP_DIALOG_TIMER_CANCEL(100rel); + + /* In all cases: Send 2xx PRACK */ + if(!(ret = send_RESPONSE(self, request, 200, "OK", tsk_false))){ + ++self->rseq; + } + + /* + 1. Alice sends an initial INVITE without offer + 2. Bob's answer is sent in the first reliable provisional response, in this case it's a 1xx INVITE response + 3. Alice's answer is sent in the PRACK response + */ + if(self->msession_mgr && !self->msession_mgr->sdp.ro){ + if(TSIP_MESSAGE_HAS_CONTENT(request)){ + if((ret = tsip_dialog_invite_process_ro(self, request))){ + /* Send Error and break the FSM */ + ret = send_ERROR(self, self->last_iInvite, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\""); + return -4; + } + } + else{ + /* 488 INVITE */ + ret = send_ERROR(self, self->last_iInvite, 488, "Not Acceptable", "SIP; cause=488; text=\"Offer expected in the PRACK\""); + return -3; + } + } + + /* Send Ringing */ + /*if(TSIP_DIALOG_GET_STACK(self)->network.mode != tsip_stack_mode_webrtc2sip)*/{ + ret = send_RESPONSE(self, self->last_iInvite, 180, "Ringing", tsk_false); + } + + /* Alert the user (session) */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_newcall, + tsip_event_code_dialog_request_incoming, "Incoming Call", request); + + return ret; +} + +/* InProgress ->(iUPDATE but cannot resume) -> InProgress */ +int s0000_InProgress_2_InProgress_X_iUPDATE(va_list *app) +{ + int ret; + + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + + if((ret = tsip_dialog_invite_process_ro(self, request))){ + /* Send Error and break the FSM */ + ret = send_ERROR(self, request, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\""); + return -4; + } + else{ + // force SDP in 200 OK even if the request has the same SDP version + tsk_bool_t force_sdp = TSIP_MESSAGE_HAS_CONTENT(request); + ret = send_RESPONSE(self, request, 200, "OK", + (self->msession_mgr && (force_sdp || self->msession_mgr->ro_changed || self->msession_mgr->state_changed))); + } + + return ret; +} + +/* InProgress ->(iUPDATE can resume) -> Ringing */ +int s0000_InProgress_2_Ringing_X_iUPDATE(va_list *app) +{ + int ret; + + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + tsk_bool_t force_sdp; + + if((ret = tsip_dialog_invite_process_ro(self, request))){ + /* Send Error and break the FSM */ + ret = send_ERROR(self, request, 488, "Not Acceptable", "SIP; cause=488; text=\"Bad content\""); + return -4; + } + + /* Send 200 UPDATE */ + // force SDP in 200 OK even if the request has the same SDP version + force_sdp = TSIP_MESSAGE_HAS_CONTENT(request); + ret = send_RESPONSE(self, request, 200, "OK", + (self->msession_mgr && (force_sdp || self->msession_mgr->ro_changed || self->msession_mgr->state_changed))); + + /* Send Ringing */ + /*if(TSIP_DIALOG_GET_STACK(self)->network.mode != tsip_stack_mode_webrtc2sip)*/{ + ret = send_RESPONSE(self, self->last_iInvite, 180, "Ringing", tsk_false); + } + + /* alert the user */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_newcall, + tsip_event_code_dialog_request_incoming, "Incoming Call", request); + + return ret; +} + +/* InProgress ->(iCANCEL) -> Terminated */ +int s0000_Inprogress_2_Terminated_X_iCANCEL(va_list *app) +{ + tsip_response_t* response; + int ret = -1; + + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + + /* Send 2xx for the CANCEL (Direct to Transport layer beacause CANCEL is a special case) */ + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 200, "OK", request))){ + ret = tsip_transport_layer_send(TSIP_DIALOG_GET_STACK(self)->layer_transport, tsk_null, response); + TSK_OBJECT_SAFE_FREE(response); + } + + /* Send Request Cancelled */ + ret = send_ERROR(self, self->last_iInvite, 487, "Request Cancelled", "SIP; cause=487; text=\"Request Cancelled\""); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Call Cancelled", tsip_event_code_dialog_terminated); + + /* alert the user */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request, + tsip_event_code_dialog_request_incoming, "Incoming Request.", request); + + return ret; +} + +/* Ringing -> (iPRACK) -> Ringing */ +int s0000_Ringing_2_Ringing_X_iPRACK(va_list *app) +{ + int ret; + + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + + if(!self->last_iInvite){ + /* silently ignore */ + return 0; + } + + /* Cancel 100rel timer */ + TSIP_DIALOG_TIMER_CANCEL(100rel); + + /* Send 2xx PRACK */ + ret = send_RESPONSE(self, request, 200, "OK", tsk_false); + + /* alert the user */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request, + tsip_event_code_dialog_request_incoming, "Incoming Request.", request); + + return ret; +} + +/* Ringing -> (oAccept) -> Connected */ +int s0000_Ringing_2_Connected_X_Accept(va_list *app) +{ + int ret; + + tsip_dialog_invite_t *self; + const tsip_action_t* action; + tsk_bool_t mediaType_changed; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + /* Determine whether the remote party support UPDATE */ + self->support_update = tsip_message_allowed(self->last_iInvite, "UPDATE"); + + /* Get Media type from the action */ + mediaType_changed = (TSIP_DIALOG_GET_SS(self)->media.type != action->media.type && action->media.type != tmedia_none); + if(self->msession_mgr && mediaType_changed){ + ret = tmedia_session_mgr_set_media_type(self->msession_mgr, action->media.type); + } + + /* Appy media params received from the user */ + if(!TSK_LIST_IS_EMPTY(action->media.params)){ + ret = tmedia_session_mgr_set_3(self->msession_mgr, action->media.params); + } + + /* set MSRP callback */ + if((self->msession_mgr->type & tmedia_msrp) == tmedia_msrp){ + ret = tmedia_session_mgr_set_msrp_cb(self->msession_mgr, TSIP_DIALOG_GET_SS(self)->userdata, TSIP_DIALOG_GET_SS(self)->media.msrp.callback); + } + + /* Cancel 100rel timer */ + TSIP_DIALOG_TIMER_CANCEL(100rel); + + /* send 2xx OK */ + ret = send_RESPONSE(self, self->last_iInvite, 200, "OK", tsk_true); + + /* say we're waiting for the incoming ACK */ + self->is_initial_iack_pending = tsk_true; + + /* do not start the session until we get the ACK message + * http://code.google.com/p/doubango/issues/detail?id=157 + */ + /* do not start the session until we get at least one remote SDP + * https://code.google.com/p/doubango/issues/detail?id=438 + */ + // FIXME: (chrome) <-RTCWeb Breaker-> (chrome) do not work if media session is not started on i200 + // http://code.google.com/p/webrtc2sip/issues/detail?id=45 + if(/*TSIP_DIALOG_GET_STACK(self)->network.mode == tsip_stack_mode_webrtc2sip*/ TSIP_MESSAGE_HAS_CONTENT(self->last_iInvite)){ + ret = tsip_dialog_invite_msession_start(self); + } + + /* Session Timers */ + if(self->stimers.timer.timeout){ + if(self->stimers.is_refresher){ + /* RFC 4028 - 9. UAS Behavior + It is RECOMMENDED that this refresh be sent oncehalf the session interval has elapsed. + Additional procedures for this refresh are described in Section 10. + */ + tsip_dialog_invite_stimers_schedule(self, (self->stimers.timer.timeout*1000)/2); + } + else{ + tsip_dialog_invite_stimers_schedule(self, (self->stimers.timer.timeout*1000)); + } + } + + /* alert the user (dialog) */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connected, "Dialog connected"); + + return ret; +} + +/* Ringing -> (oReject) -> Terminated */ +int s0000_Ringing_2_Terminated_X_Reject(va_list *app) +{ + int ret; + short code; + const char* phrase; + char* reason = tsk_null; + + tsip_dialog_invite_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_invite_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + /* Cancel 100rel timer */ + TSIP_DIALOG_TIMER_CANCEL(100rel); + + /* Send Reject */ + code = action->line_resp.code>=300 ? action->line_resp.code : 603; + phrase = action->line_resp.phrase ? action->line_resp.phrase : "Decline"; + tsk_sprintf(&reason, "SIP; cause=%hi; text=\"%s\"", code, phrase); + ret = send_ERROR(self, self->last_iInvite, code, phrase, reason); + TSK_FREE(reason); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Call Terminated", tsip_event_code_dialog_terminated); + + return ret; +} + +/* Ringing ->(iCANCEL) -> Terminated */ +int s0000_Ringing_2_Terminated_X_iCANCEL(va_list *app) +{ + int ret; + tsip_response_t* response; + + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + + if(!self->last_iInvite){ + /* silently ignore */ + return 0; + } + + /* Send 2xx for the CANCEL (Direct to Transport layer beacause CANCEL is a special case) */ + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 200, "OK", request))){ + ret = tsip_transport_layer_send(TSIP_DIALOG_GET_STACK(self)->layer_transport, tsk_null, response); + TSK_OBJECT_SAFE_FREE(response); + } + + /* Send Request Cancelled */ + ret = send_ERROR(self, self->last_iInvite, 487, "Request Cancelled", "SIP; cause=487; text=\"Request Cancelled\""); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Call Cancelled", tsip_event_code_dialog_terminated); + + /* alert the user */ + TSIP_DIALOG_INVITE_SIGNAL(self, tsip_i_request, + tsip_event_code_dialog_request_incoming, "Incoming Request.", request); + + return ret; +} + +/* Any ->(timer 100rel) -> Any */ +int s0000_Any_2_Any_X_timer100rel(va_list *app) +{ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + + int ret; + + if(!self->last_o1xxrel){ + /* silently ignore */ + return 0; + } + + /* resync timer */ + if((self->timer100rel.timeout *= 2) >= (64 * tsip_timers_getA())){ + TSK_DEBUG_ERROR("Sending reliable 1xx failed"); + return -2; + } + + /* resend reliable 1xx */ + if((ret = tsip_dialog_response_send(TSIP_DIALOG(self), self->last_o1xxrel))){ + return ret; + } + else{ + /* schedule timer */ + TSIP_DIALOG_INVITE_TIMER_SCHEDULE(100rel); + } + + return ret; +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +int send_UNSUPPORTED(tsip_dialog_invite_t* self, const tsip_request_t* request, const char* option) +{ + tsip_response_t *response; + + if(!self || !option){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 420, "Bad Extension", request))){ + // Add UnSupported header + tsip_message_add_headers(response, + TSIP_HEADER_DUMMY_VA_ARGS("Unsupported", option), + TSIP_HEADER_DUMMY_VA_ARGS("Reason", "SIP; cause=420; text=\"Bad Extension\""), + tsk_null + ); + + tsip_dialog_response_send(TSIP_DIALOG(self), response); + TSK_OBJECT_SAFE_FREE(response); + } + return 0; +} + + diff --git a/tinySIP/src/dialogs/tsip_dialog_invite.timers.c b/tinySIP/src/dialogs/tsip_dialog_invite.timers.c new file mode 100644 index 0000000..a8b279c --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_invite.timers.c @@ -0,0 +1,302 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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_invite.timers.c + * @brief SIP dialog INVITE as per RFC 3261. + * The SOA machine is designed as per RFC 3264 and draft-ietf-sipping-sip-offeranswer-12. + * Session Timers as per RFC 4028. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_invite.h" + +#include "tinysip/headers/tsip_header_Session_Expires.h" +#include "tinysip/headers/tsip_header_Min_SE.h" + +#include "tinysip/dialogs/tsip_dialog_invite.common.h" + +#include "tsk_debug.h" + +/* ======================== internal functions ======================== */ + +/* ======================== external functions ======================== */ +extern int tsip_dialog_invite_timer_callback(const tsip_dialog_invite_t* self, tsk_timer_id_t timer_id); +extern int send_RESPONSE(tsip_dialog_invite_t *self, const tsip_request_t* request, short code, const char* phrase); +extern int send_BYE(tsip_dialog_invite_t *self); +extern int send_INVITEorUPDATE(tsip_dialog_invite_t *self, tsk_bool_t is_INVITE, tsk_bool_t force_sdp); + +/* ======================== transitions ======================== */ +static int x0200_Connected_2_Connected_X_timerRefresh(va_list *app); +static int x0201_Connected_2_Trying_X_timerRefresh(va_list *app); +static int x0250_Any_2_Any_X_i422(va_list *app); + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_is_refresher(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return self->stimers.is_refresher; +} +static tsk_bool_t _fsm_cond_is_not_refresher(tsip_dialog_invite_t* self, tsip_message_t* message) +{ + return !_fsm_cond_is_refresher(self, message); +} + + +/* Init FSM */ +int tsip_dialog_invite_stimers_init(tsip_dialog_invite_t *self) +{ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + // Connected -> (timerRefresh && isRefresher) -> Connected + TSK_FSM_ADD(_fsm_state_Connected, _fsm_action_timerRefresh, _fsm_cond_is_refresher, _fsm_state_Connected, x0200_Connected_2_Connected_X_timerRefresh, "x0200_Connected_2_Connected_X_timerRefresh"), + // Connected -> (timerRefresh && !isRefresher) -> Trying (because we will send BYE) + TSK_FSM_ADD(_fsm_state_Connected, _fsm_action_timerRefresh, _fsm_cond_is_not_refresher, _fsm_state_Trying, x0201_Connected_2_Trying_X_timerRefresh, "x0201_Connected_2_Trying_X_timerRefresh"), + // Any -> (i422) -> Any + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_i422, tsk_fsm_state_any, x0250_Any_2_Any_X_i422, "x0250_Any_2_Any_X_i422"), + + TSK_FSM_ADD_NULL()); + + return 0; +} + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +int x0200_Connected_2_Connected_X_timerRefresh(va_list *app) +{ + /* We are the refresher and the session timedout + ==> Refresh the session + */ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + int ret; + + /* RFC 4028 - 7.4. Generating Subsequent Session Refresh Requests + + A re-INVITE generated to refresh the session is a normal re-INVITE, + and an UPDATE generated to refresh a session is a normal UPDATE. If + a UAC knows that its peer supports the UPDATE method, it is + RECOMMENDED that UPDATE be used instead of a re-INVITE. A UA can + make this determination if it has seen an Allow header field from its + peer with the value 'UPDATE', or through a mid-dialog OPTIONS + request. It is RECOMMENDED that the UPDATE request not contain an + offer [4], but a re-INVITE SHOULD contain one, even if the details of + the session have not changed + */ + /* 2xx will be handled by tsip_dialog_invite_stimers_handle() */ + ret = send_INVITEorUPDATE(self, !self->support_update, tsk_false); + + return ret; +} + +int x0201_Connected_2_Trying_X_timerRefresh(va_list *app) +{ + /* We are not the refresher and the session timedout + ==> send BYE + */ + tsip_dialog_invite_t *self = va_arg(*app, tsip_dialog_invite_t *); + int ret; + + /* send BYE */ + ret = send_BYE(self); + + /* alert the user that the session timedout */ + + return ret; +} + +// Any -> (i422) -> Any +int x0250_Any_2_Any_X_i422(va_list *app) +{ + tsip_dialog_invite_t* self = va_arg(*app, tsip_dialog_invite_t *); + const tsip_response_t* r422 = va_arg(*app, const tsip_response_t *); + + const tsip_header_Min_SE_t* Min_SE; + + /* RFC 4825 - 3. Overview of Operation + If the Session-Expires interval is too low for a proxy (i.e., lower + than the value of Min-SE that the proxy would wish to assert), the + proxy rejects the request with a 422 response. That response + contains a Min-SE header field identifying the minimum session + interval it is willing to support. The UAC will try again, this time + including the Min-SE header field in the request. The header field + contains the largest Min-SE header field it observed in all 422 + responses previously received. This way, the minimum timer meets the + constraints of all proxies along the path. + + RFC 4825 - 6. 422 Response Code Definition + The 422 response MUST contain a Min-SE header field with the minimum timer for that server. + */ + + if((Min_SE = (const tsip_header_Min_SE_t* )tsip_message_get_header(r422, tsip_htype_Min_SE))){ + self->stimers.minse = Min_SE->delta_seconds; + self->stimers.timer.timeout = Min_SE->delta_seconds; + } + else{ + TSK_DEBUG_ERROR("Invalid response (422 need Min-SE header)"); + return 0; /* Do not end the dialog */ + } + + /* send again the INVITE */ + return send_INVITE(self, tsk_false); +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + + + + +/* cancel the timer */ +int tsip_dialog_invite_stimers_cancel(tsip_dialog_invite_t* self) +{ + return tsk_timer_mgr_global_cancel(self->stimers.timer.id); +} + +/* schedule the timer */ +int tsip_dialog_invite_stimers_schedule(tsip_dialog_invite_t* self, uint64_t timeout) +{ + /* Used in SIP requests ==> do not change the value + self->stimers.timer.timeout = timeout; + */ + self->stimers.timer.id = tsk_timer_mgr_global_schedule(timeout, TSK_TIMER_CALLBACK_F(tsip_dialog_invite_timer_callback), self); + + return 0; +} + +/* handle requests/responses */ +int tsip_dialog_invite_stimers_handle(tsip_dialog_invite_t* self, const tsip_message_t* message) +{ + /* It's up to the caller to check that (self->stimers.timer.timeout is >0) + and message is INVITE or UPDATE or 2xxINVITE or 2xxUPDATE + */ + + int ret = 0; + const tsip_header_Session_Expires_t* hdr_SessionExpires; + + if(!self || !message){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if(!self->stimers.timer.timeout){ + /* guard for stupide callers */ + return 0; + } + /* reUPDATE or reINVITE */ + if(TSIP_MESSAGE_IS_REQUEST(message) && (TSIP_REQUEST_IS_UPDATE(message) || TSIP_REQUEST_IS_INVITE(message))){ + if((hdr_SessionExpires = (const tsip_header_Session_Expires_t*)tsip_message_get_header(message, tsip_htype_Session_Expires))){ + if(hdr_SessionExpires->delta_seconds < TSIP_SESSION_EXPIRES_MIN_VALUE){ + self->stimers.minse = TSIP_SESSION_EXPIRES_MIN_VALUE; + ret = send_RESPONSE(self, message, 422, "Session Interval Too Small"); + } + else{ + self->stimers.timer.timeout = hdr_SessionExpires->delta_seconds; + tsk_strupdate(&self->stimers.refresher, hdr_SessionExpires->refresher_uas ? "uas" : "uac"); + self->stimers.is_refresher = tsk_striequals(self->stimers.refresher, "uas"); + } + } + } + /* 2xx */ + else if(TSIP_MESSAGE_IS_RESPONSE(message) && (TSIP_RESPONSE_IS_TO_INVITE(message) || TSIP_RESPONSE_IS_TO_UPDATE(message))){ + if(!TSIP_RESPONSE_IS_2XX(message)){ + /* guard for stupide callers */ + return 0; + } + /* Process the response only if it includes "Require: timer" + + RFC 4028 - 7.2. Processing a 2xx Response + When a 2xx response to a session refresh request arrives, it may or + may not contain a Require header field with the value 'timer'. If it + does, the UAC MUST look for the Session-Expires header field to + process the response. + + If there was a Require header field in the response with the value + 'timer', the Session-Expires header field will always be present. + UACs MUST be prepared to receive a Session-Expires header field in a + response, even if none were present in the request. The 'refresher' + parameter will be present in the Session-Expires header field, + indicating who will perform the refreshes. The UAC MUST set the + identity of the refresher to the value of this parameter. If the + parameter contains the value 'uac', the UAC will perform them. + */ + if(tsip_message_required(message, "timer")){ + if((hdr_SessionExpires = (const tsip_header_Session_Expires_t*)tsip_message_get_header(message, tsip_htype_Session_Expires))){ + if(hdr_SessionExpires->delta_seconds < TSIP_SESSION_EXPIRES_MIN_VALUE){ + self->stimers.minse = TSIP_SESSION_EXPIRES_MIN_VALUE; + ret = send_RESPONSE(self, message, 422, "Interval Too short"); + } + else{ + self->stimers.timer.timeout = hdr_SessionExpires->delta_seconds; + tsk_strupdate(&self->stimers.refresher, hdr_SessionExpires->refresher_uas ? "uas" : "uac"); + self->stimers.is_refresher = tsk_striequals(self->stimers.refresher, "uac"); + self->supported.timer = (self->stimers.timer.timeout != 0); + self->required.timer = self->supported.timer; + } + } + else{ + self->stimers.timer.timeout = 0; /* turned-off */ + self->supported.timer = tsk_false; + self->required.timer = tsk_false; + ret = send_RESPONSE(self, message, 481, "Session-Expires header is missing"); + return 0; + } + } + else{ + /* + RFC 4028 - 7.2. Processing a 2xx Response + If the 2xx response did not contain a Session-Expires header field, + there is no session expiration. In this case, no refreshes need to + be sent. A 2xx without a Session-Expires can come for both initial + and subsequent session refresh requests. This means that the session + timer can be 'turned-off' in mid dialog by receiving a response + without a Session-Expires header field. + */ + self->stimers.timer.timeout = 0; /* turned-off */ + self->supported.timer = tsk_false; + self->required.timer = tsk_false; + } + } + + /* Cancel timeout */ + tsip_dialog_invite_stimers_cancel(self); + + /* schedule timer */ + if(self->stimers.timer.timeout){ + if(self->stimers.is_refresher){ + /* RFC 4028 - 9. UAS Behavior + It is RECOMMENDED that this refresh be sent oncehalf the session interval has elapsed. + Additional procedures for this refresh are described in Section 10. + */ + tsip_dialog_invite_stimers_schedule(self, (self->stimers.timer.timeout*1000)/2); + } + else{ + tsip_dialog_invite_stimers_schedule(self, (self->stimers.timer.timeout*1000)); + } + } + + return ret; +} diff --git a/tinySIP/src/dialogs/tsip_dialog_layer.c b/tinySIP/src/dialogs/tsip_dialog_layer.c new file mode 100644 index 0000000..9a33fd3 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_layer.c @@ -0,0 +1,776 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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_layer.c + * @brief SIP dialog layer. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_layer.h" + +#include "tinysip/dialogs/tsip_dialog_invite.h" +#include "tinysip/dialogs/tsip_dialog_message.h" +#include "tinysip/dialogs/tsip_dialog_info.h" +#include "tinysip/dialogs/tsip_dialog_options.h" +#include "tinysip/dialogs/tsip_dialog_publish.h" +#include "tinysip/dialogs/tsip_dialog_register.h" +#include "tinysip/dialogs/tsip_dialog_subscribe.h" + +#include "tinysip/transactions/tsip_transac_layer.h" +#include "tinysip/transports/tsip_transport_layer.h" + +#include "tsk_debug.h" + +extern tsip_ssession_handle_t *tsip_ssession_create_2(const tsip_stack_t* stack, const struct tsip_message_s* message); + +/*== Predicate function to find dialog by type */ +static int pred_find_dialog_by_type(const tsk_list_item_t *item, const void *type) +{ + if(item && item->data){ + tsip_dialog_t *dialog = item->data; + return (dialog->type - *((tsip_dialog_type_t*)type)); + } + return -1; +} + +/*== Predicate function to find dialog by not type */ +static int pred_find_dialog_by_not_type(const tsk_list_item_t *item, const void *type) +{ + if(item && item->data){ + tsip_dialog_t *dialog = item->data; + if(dialog->type != *((tsip_dialog_type_t*)type)){ + return 0; + } + } + return -1; +} + +/*== Predicate function to find dialog by callid */ +static int pred_find_dialog_by_callid(const tsk_list_item_t *item, const void *callid) +{ + if(item && item->data && callid){ + return tsk_strcmp(((tsip_dialog_t*)item->data)->callid, ((const char*)callid)); + } + return -1; +} + +tsip_dialog_layer_t* tsip_dialog_layer_create(tsip_stack_t* stack) +{ + return tsk_object_new(tsip_dialog_layer_def_t, stack); +} + +// it's up to the caller to release the returned object +tsip_dialog_t* tsip_dialog_layer_find_by_ss(tsip_dialog_layer_t *self, const tsip_ssession_handle_t *ss) +{ + return tsip_dialog_layer_find_by_ssid(self, tsip_ssession_get_id(ss)); +} + +// it's up to the caller to release the returned object +tsip_dialog_t* tsip_dialog_layer_find_by_ssid(tsip_dialog_layer_t *self, tsip_ssession_id_t ssid) +{ + tsip_dialog_t *ret = 0; + tsip_dialog_t *dialog; + tsk_list_item_t *item; + + tsk_safeobj_lock(self); + + tsk_list_foreach(item, self->dialogs){ + dialog = item->data; + if(tsip_ssession_get_id(dialog->ss) == ssid){ + ret = dialog; + break; + } + } + + tsk_safeobj_unlock(self); + + return tsk_object_ref(ret); +} + +// it's up to the caller to release the returned object +tsip_dialog_t* tsip_dialog_layer_find_by_callid(tsip_dialog_layer_t *self, const char* callid) +{ + if(!self || !callid){ + TSK_DEBUG_ERROR("Invalid parameter"); + return tsk_null; + } + else{ + tsip_dialog_t *dialog = tsk_null; + tsk_list_item_t *item; + //--tsk_safeobj_lock(self); + tsk_list_foreach(item, self->dialogs){ + if(tsk_striequals(TSIP_DIALOG(item->data)->callid, callid)){ + dialog = tsk_object_ref(item->data); + break; + } + } + //--tsk_safeobj_unlock(self); + return dialog; + } +} + +tsk_bool_t tsip_dialog_layer_have_dialog_with_callid(const tsip_dialog_layer_t *self, const char* callid) +{ + tsk_bool_t found = tsk_false; + if(self){ + tsk_safeobj_lock(self); + if(tsk_list_find_item_by_pred(self->dialogs, pred_find_dialog_by_callid, callid) != tsk_null){ + found = tsk_true; + } + tsk_safeobj_unlock(self); + } + return found; +} + +// it's up to the caller to release the returned object +tsip_dialog_t* tsip_dialog_layer_find(const tsip_dialog_layer_t *self, const char* callid, const char* to_tag, const char* from_tag, tsip_request_type_t type, tsk_bool_t *cid_matched) +{ + tsip_dialog_t *ret = tsk_null; + tsip_dialog_t *dialog; + tsk_list_item_t *item; + + *cid_matched = tsk_false; + + tsk_safeobj_lock(self); + + tsk_list_foreach(item, self->dialogs){ + dialog = item->data; + if(tsk_strequals(dialog->callid, callid)){ + tsk_bool_t is_cancel = (type == tsip_CANCEL); // Incoming CANCEL + tsk_bool_t is_register = (type == tsip_REGISTER); // Incoming REGISTER + tsk_bool_t is_notify = (type == tsip_NOTIFY); // Incoming NOTIFY + *cid_matched = tsk_true; + /* CANCEL Request will have the same local tag than the INVITE request + the remote tag could be null if the CANCEL request is received immediately after a 100 Trying + */ + if((is_cancel || tsk_strequals(dialog->tag_local, from_tag)) && (!dialog->tag_remote || tsk_strequals(dialog->tag_remote, to_tag))){ + ret = tsk_object_ref(dialog); + break; + } + /* REGISTER is dialogless which means that each reREGISTER or unREGISTER will have empty to tag */ + if(is_register /* Do not check tags */){ + ret = tsk_object_ref(dialog); + break; + } + /* NOTIFY could arrive before the 200 SUBSCRIBE => This is why we don't try to match both tags + + RFC 3265 - 3.1.4.4. Confirmation of Subscription Creation + Due to the potential for both out-of-order messages and forking, the + subscriber MUST be prepared to receive NOTIFY messages before the + SUBSCRIBE transaction has completed. + */ + if(is_notify /* Do not check tags */){ + ret = tsk_object_ref(dialog); + break; + } + } + } + + tsk_safeobj_unlock(self); + + return ret; +} + +tsk_size_t tsip_dialog_layer_count_active_calls(tsip_dialog_layer_t *self) +{ + tsk_size_t count = 0; + + tsip_dialog_t *dialog; + tsk_list_item_t *item; + + tsk_safeobj_lock(self); + + tsk_list_foreach(item, self->dialogs) { + if ((dialog = item->data) && dialog->type == tsip_dialog_INVITE && dialog->state != tsip_initial && dialog->state != tsip_terminated) { + ++count; + } + } + + tsk_safeobj_unlock(self); + + return count; +} + +/** Hangup all dialogs starting by non-REGISTER */ +int tsip_dialog_layer_shutdownAll(tsip_dialog_layer_t *self) +{ + if(self){ + tsk_bool_t wait = tsk_false; + tsk_list_item_t *item; + tsip_dialog_t *dialog; + tsip_dialog_type_t regtype = tsip_dialog_REGISTER; + + if(!self->shutdown.inprogress){ + self->shutdown.inprogress = tsk_true; + if (!self->shutdown.condwait) { + self->shutdown.condwait = tsk_condwait_create(); + } + } + + tsk_safeobj_lock(self); + if(tsk_list_count(self->dialogs, pred_find_dialog_by_not_type, ®type)){ + /* There are non-register dialogs ==> phase-1 */ + goto phase1; + } + else if(tsk_list_count(self->dialogs, pred_find_dialog_by_type, ®type)){ + /* There are one or more register dialogs ==> phase-2 */ + goto phase2; + } + else{ + tsk_safeobj_unlock(self); + goto done; + } + +phase1: + /* Phase 1 - shutdown all except register and silent_hangup */ + TSK_DEBUG_INFO("== Shutting down - Phase-1 started =="); +phase1_loop: + tsk_list_foreach(item, self->dialogs){ + dialog = item->data; + if(dialog->type != tsip_dialog_REGISTER && !dialog->ss->silent_hangup){ + item = tsk_object_ref(item); + if(!tsip_dialog_shutdown(dialog, tsk_null)){ + wait = tsk_true; + } + + // if "tsip_dialog_shutdown()" remove the dialog, then + // "self->dialogs" will be unsafe + if(!(item = tsk_object_unref(item))){ + goto phase1_loop; + } + } + } + tsk_safeobj_unlock(self); + + /* wait until phase-1 is completed */ + if(wait){ + tsk_condwait_timedwait(self->shutdown.condwait, TSIP_DIALOG_SHUTDOWN_TIMEOUT); + } + + /* lock and goto phase2 */ + tsk_safeobj_lock(self); + wait = tsk_false; + goto phase2; + +phase2: + /* Phase 2 - unregister */ + TSK_DEBUG_INFO("== Shutting down - Phase-2 started =="); + self->shutdown.phase2 = tsk_true; +phase2_loop: + tsk_list_foreach(item, self->dialogs){ + dialog = item->data; + if(dialog->type == tsip_dialog_REGISTER){ + item = tsk_object_ref(item); + if(!tsip_dialog_shutdown(dialog, tsk_null)){ + wait = tsk_true; + } + // if "tsip_dialog_shutdown()" remove the dialog, then + // "self->dialogs" will be unsafe + if(!(item = tsk_object_unref(item))){ + goto phase2_loop; + } + } + } + tsk_safeobj_unlock(self); + + /* wait until phase-2 is completed */ + if(wait){ + tsk_condwait_timedwait(self->shutdown.condwait, TSIP_DIALOG_SHUTDOWN_TIMEOUT); + } + + + /* Phase 3 - silenthangup (dialogs will be terminated immediately) */ + TSK_DEBUG_INFO("== Shutting down - Phase-3 =="); +phase3_loop: + tsk_list_foreach(item, self->dialogs){ + dialog = item->data; + if(dialog->ss->silent_hangup){ + item = tsk_object_ref(item); + tsip_dialog_shutdown(dialog, tsk_null); + + // if "tsip_dialog_shutdown()" remove the dialog, then + // "self->dialogs" will became unsafe while looping + if(!(item = tsk_object_unref(item))){ + goto phase3_loop; + } + } + } + +done: + TSK_DEBUG_INFO("== Shutting down - Terminated =="); + return 0; + } + return -1; +} + +static void* TSK_STDCALL _tsip_dialog_signal_transport_error_async(void* dialog) +{ + tsip_dialog_signal_transport_error(TSIP_DIALOG(dialog)); + return tsk_null; +} + +int tsip_dialog_layer_signal_stack_disconnected(tsip_dialog_layer_t *self) +{ + tsk_list_item_t *item; + // use copy for lock-free code and faster code. also fix issue 172 (https://code.google.com/p/idoubs/issues/detail?id=172) + tsip_dialogs_L_t *dialogs_copy; + tsip_dialog_t *dialog; + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + if (!(dialogs_copy = tsk_list_create())) { + TSK_DEBUG_ERROR("Failed to create list"); + return -1; + } + + tsk_safeobj_lock(self); + tsk_list_pushback_list(dialogs_copy, self->dialogs); + tsk_safeobj_unlock(self); + + tsk_list_foreach(item, dialogs_copy){ + if((dialog = TSIP_DIALOG(item->data))){ + tsip_dialog_signal_transport_error(dialog); + } + } + TSK_OBJECT_SAFE_FREE(dialogs_copy); + return 0; +} + +int tsip_dialog_layer_signal_peer_disconnected(tsip_dialog_layer_t *self, const struct tsip_transport_stream_peer_s* peer) +{ + tsip_dialog_t *dialog; + const tsk_list_item_t *item; + + if(!self || !peer){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + //!\ must not lock the entire layer + + // tsk_safeobj_lock(self); + + tsk_list_lock(peer->dialogs_cids); + tsk_list_foreach(item, peer->dialogs_cids){ + if((dialog = tsip_dialog_layer_find_by_callid(self, TSK_STRING_STR(item->data)))){ + tsip_dialog_signal_transport_error(dialog); + TSK_OBJECT_SAFE_FREE(dialog); + } + else{ + // To avoid this WARN, you should call tsip_dialog_layer_have_dialog_with_callid() before adding a callid to a peer + TSK_DEBUG_WARN("Stream peer holds call-id='%s' but the dialog layer doesn't know it", TSK_STRING_STR(item->data)); + } + } + tsk_list_unlock(peer->dialogs_cids); + + // tsk_safeobj_unlock(self); + + return 0; +} + +int tsip_dialog_layer_remove_callid_from_stream_peers(tsip_dialog_layer_t *self, const char* callid) +{ + if(self){ + return tsip_transport_layer_remove_callid_from_stream_peers(self->stack->layer_transport, callid); + } + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; +} + +/* the caller of this function must unref() the returned object */ +tsip_dialog_t* tsip_dialog_layer_new(tsip_dialog_layer_t *self, tsip_dialog_type_t type, const tsip_ssession_t *ss) +{ + tsip_dialog_t* ret = tsk_null; + tsip_dialog_t* dialog; + if(!self){ + TSK_DEBUG_ERROR("Invalid parameter"); + goto bail; + } + + switch(type){ + case tsip_dialog_INVITE: + { + if((dialog = (tsip_dialog_t*)tsip_dialog_invite_create(ss, tsk_null))){ + ret = tsk_object_ref(dialog); + tsk_list_push_back_data(self->dialogs, (void**)&dialog); + } + break; + } + case tsip_dialog_MESSAGE: + { + if((dialog = (tsip_dialog_t*)tsip_dialog_message_create(ss))){ + ret = tsk_object_ref(dialog); + tsk_list_push_back_data(self->dialogs, (void**)&dialog); + } + break; + } + case tsip_dialog_INFO: + { + if((dialog = (tsip_dialog_t*)tsip_dialog_info_create(ss))){ + ret = tsk_object_ref(dialog); + tsk_list_push_back_data(self->dialogs, (void**)&dialog); + } + break; + } + case tsip_dialog_OPTIONS: + { + if((dialog = (tsip_dialog_t*)tsip_dialog_options_create(ss))){ + ret = tsk_object_ref(dialog); + tsk_list_push_back_data(self->dialogs, (void**)&dialog); + } + break; + } + case tsip_dialog_PUBLISH: + { + if((dialog = (tsip_dialog_t*)tsip_dialog_publish_create(ss))){ + ret = tsk_object_ref(dialog); + tsk_list_push_back_data(self->dialogs, (void**)&dialog); + } + break; + } + case tsip_dialog_REGISTER: + { + if((dialog = (tsip_dialog_t*)tsip_dialog_register_create(ss, tsk_null))){ + ret = tsk_object_ref(dialog); + tsk_list_push_back_data(self->dialogs, (void**)&dialog); + } + break; + } + case tsip_dialog_SUBSCRIBE: + { + if((dialog = (tsip_dialog_t*)tsip_dialog_subscribe_create(ss))){ + ret = tsk_object_ref(dialog); + tsk_list_push_back_data(self->dialogs, (void**)&dialog); + } + break; + } + + default: + { + TSK_DEBUG_ERROR("Dialog type not supported."); + break; + } + } + +bail: + return ret; +} + +int tsip_dialog_layer_remove(tsip_dialog_layer_t *self, const tsip_dialog_t *dialog) +{ + if(dialog && self){ + tsip_dialog_type_t regtype = tsip_dialog_REGISTER; + tsk_safeobj_lock(self); + + /* remove the dialog */ + tsk_list_remove_item_by_data(self->dialogs, dialog); + + /* whether shutting down? */ + if(self->shutdown.inprogress){ + if(self->shutdown.phase2){ /* Phase 2 (all non-REGISTER and silent dialogs have been removed) */ + if(tsk_list_count(self->dialogs, pred_find_dialog_by_type, ®type) == 0){ + /* alert only if there is not REGISTER dialog (ignore silents) */ + TSK_DEBUG_INFO("== Shutting down - Phase-2 completed =="); + tsk_condwait_broadcast(self->shutdown.condwait); + } + } + else{ /* Phase 1 */ + if(tsk_list_count(self->dialogs, pred_find_dialog_by_not_type, ®type) == 0){ + /* alert only if all dialogs except REGISTER have been removed */ + TSK_DEBUG_INFO("== Shutting down - Phase-1 completed =="); + tsk_condwait_broadcast(self->shutdown.condwait); + } + } + } + + tsk_safeobj_unlock(self); + + return 0; + } + + return -1; +} + +// this function is only called if no transaction match +// for responses, the transaction will always match +int tsip_dialog_layer_handle_incoming_msg(const tsip_dialog_layer_t *self, tsip_message_t* message) +{ + int ret = -1; + tsk_bool_t cid_matched; + tsip_dialog_t* dialog; + tsip_transac_t* transac = tsk_null; + const tsip_transac_layer_t *layer_transac = self->stack->layer_transac; + + if(!layer_transac){ + goto bail; + } + + //tsk_safeobj_lock(self); + dialog = tsip_dialog_layer_find(self, message->Call_ID->value, + TSIP_MESSAGE_IS_RESPONSE(message) ? message->To->tag : message->From->tag, + TSIP_MESSAGE_IS_RESPONSE(message) ? message->From->tag : message->To->tag, + TSIP_MESSAGE_IS_REQUEST(message) ? TSIP_MESSAGE_AS_REQUEST(message)->line.request.request_type : tsip_NONE, + &cid_matched); + //tsk_safeobj_unlock(self); + + if(dialog){ + if(TSIP_REQUEST_IS_CANCEL(message) || TSIP_REQUEST_IS_ACK(message)){ + ret = dialog->callback(dialog, tsip_dialog_i_msg, message); + tsk_object_unref(dialog); + goto bail; + } + else{ + static tsk_bool_t isCT = tsk_false; + tsip_transac_dst_t* dst = tsip_transac_dst_dialog_create(dialog); + transac = tsip_transac_layer_new( + layer_transac, + isCT, + message, + dst + ); + TSK_OBJECT_SAFE_FREE(dst); + TSK_OBJECT_SAFE_FREE(dialog); + } + } + else{ + /* MediaProxyMode : forward all non-INVITE messages */ + if(self->stack->network.mode == tsip_stack_mode_webrtc2sip){ + tsk_bool_t b2bua; + + if(TSIP_MESSAGE_IS_REQUEST(message)){ + // requests received over TCP/TLS/UDP must contain "ws-src-ip" and "ws-src-port" parameters + if(!TNET_SOCKET_TYPE_IS_WS(message->src_net_type) && !TNET_SOCKET_TYPE_IS_WSS(message->src_net_type)){ + const char* ws_src_ip = tsk_params_get_param_value(message->line.request.uri->params, "ws-src-ip"); + const tnet_port_t ws_src_port = (tnet_port_t)tsk_params_get_param_value_as_int(message->line.request.uri->params, "ws-src-port"); + if(!tsip_transport_layer_have_stream_peer_with_remote_ip(self->stack->layer_transport, ws_src_ip, ws_src_port)){ + if(!TSIP_REQUEST_IS_ACK(message)){ // ACK do not expect response +#if 0 // code commented because when using mjserver, rejecting the forked INVITE terminate all dialogs: have to check if it's conform to RFC 3261 or not + tsip_response_t* response = tsip_response_new(488, "WebSocket Peer not connected", message); + ret = tsip_transport_layer_send(self->stack->layer_transport, "no-branch", response); + TSK_OBJECT_SAFE_FREE(response); + return ret; +#else + TSK_DEBUG_INFO("Request for peer at %s:%d cannot be delivered", ws_src_ip, ws_src_port); +#endif + } + return 0; + } + } + } + + // "rtcweb-breaker" parameter will be in the Contact header for outgoing request and in the request-uri for incoming requests + b2bua = TSIP_REQUEST_IS_INVITE(message) && message->Contact && message->Contact->uri && + (tsk_striequals(tsk_params_get_param_value(message->Contact->uri->params, "rtcweb-breaker"), "yes") + || tsk_striequals(tsk_params_get_param_value(message->line.request.uri->params, "rtcweb-breaker"), "yes")); + + if(!b2bua){ + // forward the message + static tsk_bool_t isCT = tsk_true; + tsip_transac_dst_t* dst; + tsip_transac_t* transac; + + TSIP_MESSAGE(message)->update = tsk_true; // update AoR and Via + if((dst = tsip_transac_dst_net_create(TSIP_STACK(self->stack)))){ + if((transac = tsip_transac_layer_new(self->stack->layer_transac, isCT, message, dst))){ + ret = tsip_transac_start(transac, message); + TSK_OBJECT_SAFE_FREE(transac); + } + TSK_OBJECT_SAFE_FREE(dst); + } + return ret; + } + } + + if(TSIP_MESSAGE_IS_REQUEST(message)){ + tsip_ssession_t* ss = tsk_null; + tsip_dialog_t* newdialog = tsk_null; + + switch(message->line.request.request_type){ + case tsip_MESSAGE: + { /* Server incoming MESSAGE */ + if((ss = tsip_ssession_create_2(self->stack, message))){ + newdialog = (tsip_dialog_t*)tsip_dialog_message_create(ss); + } + break; + } + case tsip_INFO: + { /* Server incoming INFO */ + if((ss = tsip_ssession_create_2(self->stack, message))){ + newdialog = (tsip_dialog_t*)tsip_dialog_info_create(ss); + } + break; + } + case tsip_OPTIONS: + { /* Server incoming OPTIONS */ + if((ss = tsip_ssession_create_2(self->stack, message))){ + newdialog = (tsip_dialog_t*)tsip_dialog_options_create(ss); + } + break; + } + + case tsip_REGISTER: + { /* incoming REGISTER */ + if((ss = tsip_ssession_create_2(self->stack, message))){ + newdialog = (tsip_dialog_t*)tsip_dialog_register_create(ss, message->Call_ID ? message->Call_ID->value : tsk_null); + } + break; + } + + case tsip_INVITE: + { /* incoming INVITE */ + if((ss = tsip_ssession_create_2(self->stack, message))){ + newdialog = (tsip_dialog_t*)tsip_dialog_invite_create(ss, message->Call_ID ? message->Call_ID->value : tsk_null); + } + break; + } + + default: + { + break; + } + }//switch + + // for new dialog, create a new transac and start it later + if(newdialog){ + static const tsk_bool_t isCT = tsk_false; + tsip_transac_dst_t* dst = tsip_transac_dst_dialog_create(newdialog); + transac = tsip_transac_layer_new( + layer_transac, + isCT, + message, + dst + ); + if(message->local_fd > 0 && TNET_SOCKET_TYPE_IS_STREAM(message->src_net_type)) { + tsip_dialog_set_connected_fd(newdialog, message->local_fd); + } + tsk_list_push_back_data(self->dialogs, (void**)&newdialog); /* add new dialog to the layer */ + TSK_OBJECT_SAFE_FREE(dst); + } + + /* The dialog will become the owner of the SIP session + * => when destoyed => SIP session will be destroyed, unless the user-end takes ownership() */ + TSK_OBJECT_SAFE_FREE(ss); + } + } + + if(transac){ + ret = tsip_transac_start(transac, message); + tsk_object_unref(transac); + } + /* - No transaction match for the SIP request + - ACK do not expect any response (http://code.google.com/p/imsdroid/issues/detail?id=420) + */ + else if(TSIP_MESSAGE_IS_REQUEST(message) && !TSIP_REQUEST_IS_ACK(message)){ + const tsip_transport_layer_t *layer; + tsip_response_t* response = tsk_null; + if(!dialog && cid_matched){ + dialog = tsip_dialog_layer_find_by_callid((tsip_dialog_layer_t *)self, message->Call_ID->value); + } + + if((layer = self->stack->layer_transport)){ + if(cid_matched){ /* We are receiving our own message. */ + response = tsip_response_new(482, "Loop Detected (Check your iFCs)", message); + if(response && !response->To->tag){/* Early dialog? */ + response->To->tag = tsk_strdup("doubango"); + } + } + else{ + switch(message->line.request.request_type){ + case tsip_OPTIONS: // Hacked to work on Tiscali IMS networks + case tsip_INFO: + response = tsip_response_new(405, "Method Not Allowed", message); + break; + default: + response = tsip_response_new(481, "Dialog/Transaction Does Not Exist", message); + break; + } + } + if(response){ + if(dialog && TSIP_DIALOG_GET_SS(dialog)){ + tsk_strupdate(&response->sigcomp_id, TSIP_DIALOG_GET_SS(dialog)->sigcomp_id); + } + ret = tsip_transport_layer_send(layer, response->firstVia ? response->firstVia->branch : "no-branch", response); + TSK_OBJECT_SAFE_FREE(response); + } + } + + TSK_OBJECT_SAFE_FREE(dialog); + } + +bail: + return ret; +} + + + + + +//======================================================== +// Dialog layer object definition +// +static tsk_object_t* tsip_dialog_layer_ctor(tsk_object_t * self, va_list * app) +{ + tsip_dialog_layer_t *layer = self; + if(layer){ + layer->stack = va_arg(*app, const tsip_stack_t *); + layer->dialogs = tsk_list_create(); + + tsk_safeobj_init(layer); + } + return self; +} + +static tsk_object_t* tsip_dialog_layer_dtor(tsk_object_t * self) +{ + tsip_dialog_layer_t *layer = self; + if(layer){ + TSK_OBJECT_SAFE_FREE(layer->dialogs); + + /* condwait */ + if(layer->shutdown.condwait){ + tsk_condwait_destroy(&layer->shutdown.condwait); + } + + tsk_safeobj_deinit(layer); + + TSK_DEBUG_INFO("*** Dialog Layer destroyed ***"); + } + return self; +} + +static int tsip_dialog_layer_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2) +{ + return -1; +} + +static const tsk_object_def_t tsip_dialog_layer_def_s = +{ + sizeof(tsip_dialog_layer_t), + tsip_dialog_layer_ctor, + tsip_dialog_layer_dtor, + tsip_dialog_layer_cmp, +}; +const tsk_object_def_t *tsip_dialog_layer_def_t = &tsip_dialog_layer_def_s; diff --git a/tinySIP/src/dialogs/tsip_dialog_message.c b/tinySIP/src/dialogs/tsip_dialog_message.c new file mode 100644 index 0000000..9619eda --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_message.c @@ -0,0 +1,552 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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_message.c + * @brief SIP dialog message (Client side). + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_message.h" +#include "tinysip/parsers/tsip_parser_uri.h" + +#include "tinysip/api/tsip_api_message.h" + +#include "tinysip/headers/tsip_header_Dummy.h" +#include "tinysip/headers/tsip_header_Min_Expires.h" + +#include "tinysip/transactions/tsip_transac_layer.h" + +#include "tsk_memory.h" +#include "tsk_debug.h" +#include "tsk_time.h" + +#define DEBUG_STATE_MACHINE 1 +#define TSIP_DIALOG_MESSAGE_SIGNAL(self, type, code, phrase, message) \ + tsip_message_event_signal(type, TSIP_DIALOG(self)->ss, code, phrase, message) + +/* ======================== internal functions ======================== */ +static int send_MESSAGE(tsip_dialog_message_t *self); +static int tsip_dialog_message_OnTerminated(tsip_dialog_message_t *self); + +/* ======================== transitions ======================== */ +static int tsip_dialog_message_Started_2_Sending_X_sendMESSAGE(va_list *app); +static int tsip_dialog_message_Started_2_Receiving_X_recvMESSAGE(va_list *app); +static int tsip_dialog_message_Sending_2_Sending_X_1xx(va_list *app); +static int tsip_dialog_message_Sending_2_Terminated_X_2xx(va_list *app); +static int tsip_dialog_message_Sending_2_Sending_X_401_407_421_494(va_list *app); +static int tsip_dialog_message_Sending_2_Terminated_X_300_to_699(va_list *app); +static int tsip_dialog_message_Sending_2_Terminated_X_cancel(va_list *app); +static int tsip_dialog_message_Receiving_2_Terminated_X_accept(va_list *app); +static int tsip_dialog_message_Receiving_2_Terminated_X_reject(va_list *app); +static int tsip_dialog_message_Any_2_Terminated_X_transportError(va_list *app); +static int tsip_dialog_message_Any_2_Terminated_X_Error(va_list *app); + +/* ======================== conds ======================== */ + +/* ======================== actions ======================== */ +typedef enum _fsm_action_e +{ + _fsm_action_sendMESSAGE = tsip_atype_message_send, + _fsm_action_accept = tsip_atype_accept, + _fsm_action_reject = tsip_atype_reject, + _fsm_action_cancel = tsip_atype_cancel, + _fsm_action_shutdown = tsip_atype_shutdown, + _fsm_action_transporterror = tsip_atype_transport_error, + + _fsm_action_receiveMESSAGE = 0xFF, + _fsm_action_1xx, + _fsm_action_2xx, + _fsm_action_401_407_421_494, + _fsm_action_300_to_699, + _fsm_action_error, +} +_fsm_action_t; + +/* ======================== states ======================== */ +typedef enum _fsm_state_e +{ + _fsm_state_Started, + _fsm_state_Sending, + _fsm_state_Receiving, + _fsm_state_Terminated +} +_fsm_state_t; + + +static int tsip_dialog_message_event_callback(const tsip_dialog_message_t *self, tsip_dialog_event_type_t type, const tsip_message_t *msg) +{ + int ret = -1; + + switch(type) + { + case tsip_dialog_i_msg: + { + if(msg){ + if(TSIP_MESSAGE_IS_RESPONSE(msg)){ + const tsip_action_t* action = tsip_dialog_keep_action(TSIP_DIALOG(self), msg) ? TSIP_DIALOG(self)->curr_action : tsk_null; + if(TSIP_RESPONSE_IS_1XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_1xx, msg, action); + } + else if(TSIP_RESPONSE_IS_2XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_2xx, msg, action); + } + else if(TSIP_RESPONSE_CODE(msg) == 401 || TSIP_RESPONSE_CODE(msg) == 407 || TSIP_RESPONSE_CODE(msg) == 421 || TSIP_RESPONSE_CODE(msg) == 494){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_401_407_421_494, msg, action); + } + else if(TSIP_RESPONSE_IS_3456(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_300_to_699, msg, action); + } + else{ /* Should never happen */ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_error, msg, action); + } + } + else if (TSIP_REQUEST_IS_MESSAGE(msg)){ /* have been checked by dialog layer...but */ + // REQUEST ==> Incoming MESSAGE + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_receiveMESSAGE, msg, tsk_null); + } + } + break; + } + + case tsip_dialog_canceled: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_cancel, msg, tsk_null); + break; + } + + case tsip_dialog_terminated: + case tsip_dialog_timedout: + case tsip_dialog_error: + case tsip_dialog_transport_error: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_transporterror, msg, tsk_null); + break; + } + + default: break; + } + + return ret; +} + +tsip_dialog_message_t* tsip_dialog_message_create(const tsip_ssession_handle_t* ss) +{ + return tsk_object_new(tsip_dialog_message_def_t, ss); +} + +int tsip_dialog_message_init(tsip_dialog_message_t *self) +{ + //const tsk_param_t* param; + + /* Initialize the state machine. */ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (send) -> Sending + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_sendMESSAGE, _fsm_state_Sending, tsip_dialog_message_Started_2_Sending_X_sendMESSAGE, "tsip_dialog_message_Started_2_Sending_X_sendMESSAGE"), + // Started -> (receive) -> Receiving + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_receiveMESSAGE, _fsm_state_Receiving, tsip_dialog_message_Started_2_Receiving_X_recvMESSAGE, "tsip_dialog_message_Started_2_Receiving_X_recvMESSAGE"), + // Started -> (Any) -> Started + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Started, "tsip_dialog_message_Started_2_Started_X_any"), + + + /*======================= + * === Sending === + */ + // Sending -> (1xx) -> Sending + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_1xx, _fsm_state_Sending, tsip_dialog_message_Sending_2_Sending_X_1xx, "tsip_dialog_message_Sending_2_Sending_X_1xx"), + // Sending -> (2xx) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_2xx, _fsm_state_Terminated, tsip_dialog_message_Sending_2_Terminated_X_2xx, "tsip_dialog_message_Sending_2_Terminated_X_2xx"), + // Sending -> (401/407/421/494) -> Sending + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_401_407_421_494, _fsm_state_Sending, tsip_dialog_message_Sending_2_Sending_X_401_407_421_494, "tsip_dialog_message_Sending_2_Sending_X_401_407_421_494"), + // Sending -> (300_to_699) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_300_to_699, _fsm_state_Terminated, tsip_dialog_message_Sending_2_Terminated_X_300_to_699, "tsip_dialog_message_Sending_2_Terminated_X_300_to_699"), + // Sending -> (cancel) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_cancel, _fsm_state_Terminated, tsip_dialog_message_Sending_2_Terminated_X_cancel, "tsip_dialog_message_Sending_2_Terminated_X_cancel"), + // Sending -> (shutdown) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_shutdown, _fsm_state_Terminated, tsk_null, "tsip_dialog_message_Sending_2_Terminated_X_shutdown"), + // Sending -> (Any) -> Sending + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Sending, "tsip_dialog_message_Sending_2_Sending_X_any"), + + /*======================= + * === Receiving === + */ + // Receiving -> (accept) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Receiving, _fsm_action_accept, _fsm_state_Terminated, tsip_dialog_message_Receiving_2_Terminated_X_accept, "tsip_dialog_message_Receiving_2_Terminated_X_accept"), + // Receiving -> (rejected) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Receiving, _fsm_action_reject, _fsm_state_Terminated, tsip_dialog_message_Receiving_2_Terminated_X_reject, "tsip_dialog_message_Receiving_2_Terminated_X_reject"), + // Receiving -> (Any) -> Receiving + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Receiving, "tsip_dialog_message_Receiving_2_Receiving_X_any"), + + /*======================= + * === Any === + */ + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_transporterror, _fsm_state_Terminated, tsip_dialog_message_Any_2_Terminated_X_transportError, "tsip_dialog_message_Any_2_Terminated_X_transportError"), + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_error, _fsm_state_Terminated, tsip_dialog_message_Any_2_Terminated_X_Error, "tsip_dialog_message_Any_2_Terminated_X_Error"), + + TSK_FSM_ADD_NULL()); + + TSIP_DIALOG(self)->callback = TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_message_event_callback); + + return 0; +} + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + + +/* Started -> (sendMESSAGE) -> Sending +*/ +int tsip_dialog_message_Started_2_Sending_X_sendMESSAGE(va_list *app) +{ + tsip_dialog_message_t *self; + + self = va_arg(*app, tsip_dialog_message_t *); + + TSIP_DIALOG(self)->running = tsk_true; + + return send_MESSAGE(self); +} + +/* Started -> (recvMESSAGE) -> Receiving +*/ +int tsip_dialog_message_Started_2_Receiving_X_recvMESSAGE(va_list *app) +{ + tsip_dialog_message_t *self = va_arg(*app, tsip_dialog_message_t *); + const tsip_request_t *request = va_arg(*app, const tsip_request_t *); + + /* Alert the user. */ + TSIP_DIALOG_MESSAGE_SIGNAL(self, tsip_i_message, + tsip_event_code_dialog_request_incoming, "Incoming Request.", request); + + /* Update last incoming MESSAGE */ + TSK_OBJECT_SAFE_FREE(self->request); + self->request = tsk_object_ref((void*)request); + + return 0; +} + +/* Sending -> (1xx) -> Sending +*/ +int tsip_dialog_message_Sending_2_Sending_X_1xx(va_list *app) +{ + /*tsip_dialog_message_t *self = va_arg(*app, tsip_dialog_message_t *);*/ + /*const tsip_response_t *response = va_arg(*app, const tsip_response_t *);*/ + + return 0; +} + +/* Sending -> (2xx) -> Sending +*/ +int tsip_dialog_message_Sending_2_Terminated_X_2xx(va_list *app) +{ + tsip_dialog_message_t *self = va_arg(*app, tsip_dialog_message_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* Alert the user. */ + TSIP_DIALOG_MESSAGE_SIGNAL(self, tsip_ao_message, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + /* Reset curr action */ + tsip_dialog_set_curr_action(TSIP_DIALOG(self), tsk_null); + + return 0; +} + +/* Sending -> (401/407/421/494) -> Sending +*/ +int tsip_dialog_message_Sending_2_Sending_X_401_407_421_494(va_list *app) +{ + tsip_dialog_message_t *self = va_arg(*app, tsip_dialog_message_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + int ret; + + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + // Alert the user + TSIP_DIALOG_MESSAGE_SIGNAL(self, tsip_ao_message, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return ret; + } + + return send_MESSAGE(self); +} + +/* Sending -> (300 to 699) -> Terminated +*/ +int tsip_dialog_message_Sending_2_Terminated_X_300_to_699(va_list *app) +{ + tsip_dialog_message_t *self = va_arg(*app, tsip_dialog_message_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), TSIP_RESPONSE_PHRASE(response), TSIP_RESPONSE_CODE(response)); + + /* Alert the user. */ + TSIP_DIALOG_MESSAGE_SIGNAL(self, tsip_ao_message, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return 0; +} + +/* Sending -> (cancel) -> Terminated +*/ +int tsip_dialog_message_Sending_2_Terminated_X_cancel(va_list *app) +{ + tsip_dialog_message_t *self = va_arg(*app, tsip_dialog_message_t *); + /* const tsip_message_t *message = va_arg(*app, const tsip_message_t *); */ + + /* RFC 3261 - 9.1 Client Behavior + A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + */ + + /* Cancel all transactions associated to this dialog (will also be done when the dialog is destroyed (worth nothing)) */ + tsip_transac_layer_cancel_by_dialog(TSIP_DIALOG_GET_STACK(self)->layer_transac, TSIP_DIALOG(self)); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_request_cancelled, "MESSAGE cancelled"); + + return 0; +} + +/* Receiving -> (accept) -> Terminated +*/ +int tsip_dialog_message_Receiving_2_Terminated_X_accept(va_list *app) +{ + tsip_dialog_message_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_message_t *); + va_arg(*app, tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(!self->request){ + TSK_DEBUG_ERROR("There is non MESSAGE to accept()"); + /* Not an error ...but do not update current action */ + } + else{ + tsip_response_t *response; + int ret; + + /* curr_action is only used for outgoing requests */ + /* tsip_dialog_set_curr_action(TSIP_DIALOG(self), action); */ + + /* send 200 OK */ + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 200, "OK", self->request))){ + tsip_dialog_apply_action(response, action); /* apply action params to "this" response */ + if((ret = tsip_dialog_response_send(TSIP_DIALOG(self), response))){ + TSK_DEBUG_ERROR("Failed to send SIP response."); + TSK_OBJECT_SAFE_FREE(response); + return ret; + } + TSK_OBJECT_SAFE_FREE(response); + } + else{ + TSK_DEBUG_ERROR("Failed to create SIP response."); + return -1; + } + } + + return 0; +} + +/* Receiving -> (reject) -> Terminated +*/ +int tsip_dialog_message_Receiving_2_Terminated_X_reject(va_list *app) +{ + tsip_dialog_message_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_message_t *); + va_arg(*app, tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(!self->request){ + TSK_DEBUG_ERROR("There is non MESSAGE to reject()"); + /* Not an error ...but do not update current action */ + } + else{ + tsip_response_t *response; + int ret; + + /* curr_action is only used for outgoing requests */ + /* tsip_dialog_set_curr_action(TSIP_DIALOG(self), action); */ + + /* send 486 Rejected */ + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 486, "Rejected", self->request))){ + tsip_dialog_apply_action(response, action); /* apply action params to "this" response */ + if((ret = tsip_dialog_response_send(TSIP_DIALOG(self), response))){ + TSK_DEBUG_ERROR("Failed to send SIP response."); + TSK_OBJECT_SAFE_FREE(response); + return ret; + } + TSK_OBJECT_SAFE_FREE(response); + } + else{ + TSK_DEBUG_ERROR("Failed to create SIP response."); + return -1; + } + } + + return 0; +} + +/* Any -> (transport error) -> Terminated +*/ +int tsip_dialog_message_Any_2_Terminated_X_transportError(va_list *app) +{ + /*tsip_dialog_message_t *self = va_arg(*app, tsip_dialog_message_t *);*/ + /*const tsip_message_t *message = va_arg(*app, const tsip_message_t *);*/ + + return 0; +} + +/* Any -> (error) -> Terminated +*/ +int tsip_dialog_message_Any_2_Terminated_X_Error(va_list *app) +{ + /*tsip_dialog_message_t *self = va_arg(*app, tsip_dialog_message_t *);*/ + /*const tsip_message_t *message = va_arg(*app, const tsip_message_t *);*/ + + return 0; +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +int send_MESSAGE(tsip_dialog_message_t *self) +{ + tsip_request_t* request = tsk_null; + int ret = -1; + + if(!self){ + return -1; + } + + if(!(request = tsip_dialog_request_new(TSIP_DIALOG(self), "MESSAGE"))){ + return -2; + } + + /* apply action params to the request */ + if(TSIP_DIALOG(self)->curr_action){ + tsip_dialog_apply_action(request, TSIP_DIALOG(self)->curr_action); + } + + ret = tsip_dialog_request_send(TSIP_DIALOG(self), request); + TSK_OBJECT_SAFE_FREE(request); + + return ret; +} + + +int tsip_dialog_message_OnTerminated(tsip_dialog_message_t *self) +{ + TSK_DEBUG_INFO("=== MESSAGE Dialog terminated ==="); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminated, + TSIP_DIALOG(self)->last_error.phrase ? TSIP_DIALOG(self)->last_error.phrase : "Dialog terminated"); + + /* Remove from the dialog layer. */ + return tsip_dialog_remove(TSIP_DIALOG(self)); +} + + + + + + + + + + + + + + + + + + + + + + +//======================================================== +// SIP dialog MESSAGE object definition +// +static tsk_object_t* tsip_dialog_message_ctor(tsk_object_t * self, va_list * app) +{ + tsip_dialog_message_t *dialog = self; + if(dialog){ + tsip_ssession_handle_t *ss = va_arg(*app, tsip_ssession_handle_t *); + + /* Initialize base class */ + tsip_dialog_init(TSIP_DIALOG(self), tsip_dialog_MESSAGE, tsk_null, ss, _fsm_state_Started, _fsm_state_Terminated); + + /* FSM */ + TSIP_DIALOG_GET_FSM(self)->debug = DEBUG_STATE_MACHINE; + tsk_fsm_set_callback_terminated(TSIP_DIALOG_GET_FSM(self), TSK_FSM_ONTERMINATED_F(tsip_dialog_message_OnTerminated), (const void*)dialog); + + /* Initialize the class itself */ + tsip_dialog_message_init(self); + } + return self; +} + +static tsk_object_t* tsip_dialog_message_dtor(tsk_object_t * self) +{ + tsip_dialog_message_t *dialog = self; + if(dialog){ + /* DeInitialize base class (will cancel all transactions) */ + tsip_dialog_deinit(TSIP_DIALOG(self)); + + /* DeInitialize self */ + TSK_OBJECT_SAFE_FREE(dialog->request); + + TSK_DEBUG_INFO("*** MESSAGE Dialog destroyed ***"); + } + return self; +} + +static int tsip_dialog_message_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2) +{ + return tsip_dialog_cmp(obj1, obj2); +} + +static const tsk_object_def_t tsip_dialog_message_def_s = +{ + sizeof(tsip_dialog_message_t), + tsip_dialog_message_ctor, + tsip_dialog_message_dtor, + tsip_dialog_message_cmp, +}; +const tsk_object_def_t *tsip_dialog_message_def_t = &tsip_dialog_message_def_s; diff --git a/tinySIP/src/dialogs/tsip_dialog_options.c b/tinySIP/src/dialogs/tsip_dialog_options.c new file mode 100644 index 0000000..135be69 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_options.c @@ -0,0 +1,578 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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_options.c + * @brief SIP dialog OPTIONS as per RFC 3261 section 11. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_options.h" +#include "tinysip/parsers/tsip_parser_uri.h" + +#include "tinysip/api/tsip_api_options.h" + +#include "tinysip/headers/tsip_header_Dummy.h" +#include "tinysip/headers/tsip_header_Min_Expires.h" + +#include "tinysip/transactions/tsip_transac_layer.h" + +#include "tsk_memory.h" +#include "tsk_debug.h" +#include "tsk_time.h" + +#define DEBUG_STATE_MACHINE 1 +#define TSIP_DIALOG_OPTIONS_SIGNAL(self, type, code, phrase, options) \ + tsip_options_event_signal(type, TSIP_DIALOG(self)->ss, code, phrase, options) + +/* ======================== internal functions ======================== */ +static int send_OPTIONS(tsip_dialog_options_t *self); +static int send_response(tsip_dialog_options_t *self, short status, const char* phrase, const tsip_request_t *request); +static int tsip_dialog_options_OnTerminated(tsip_dialog_options_t *self); + +/* ======================== transitions ======================== */ +static int tsip_dialog_options_Started_2_Sending_X_sendOPTIONS(va_list *app); +static int tsip_dialog_options_Started_2_Receiving_X_recvOPTIONS(va_list *app); +static int tsip_dialog_options_Sending_2_Sending_X_1xx(va_list *app); +static int tsip_dialog_options_Sending_2_Terminated_X_2xx(va_list *app); +static int tsip_dialog_options_Sending_2_Sending_X_401_407_421_494(va_list *app); +static int tsip_dialog_options_Sending_2_Terminated_X_300_to_699(va_list *app); +static int tsip_dialog_options_Sending_2_Terminated_X_cancel(va_list *app); +static int tsip_dialog_options_Receiving_2_Terminated_X_accept(va_list *app); +static int tsip_dialog_options_Receiving_2_Terminated_X_reject(va_list *app); +static int tsip_dialog_options_Any_2_Terminated_X_transportError(va_list *app); +static int tsip_dialog_options_Any_2_Terminated_X_Error(va_list *app); + +/* ======================== conds ======================== */ + +/* ======================== actions ======================== */ +typedef enum _fsm_action_e +{ + _fsm_action_sendOPTIONS = tsip_atype_options_send, + _fsm_action_accept = tsip_atype_accept, + _fsm_action_reject = tsip_atype_reject, + _fsm_action_cancel = tsip_atype_cancel, + _fsm_action_shutdown = tsip_atype_shutdown, + _fsm_action_transporterror = tsip_atype_transport_error, + + _fsm_action_receiveOPTIONS = 0xFF, + _fsm_action_1xx, + _fsm_action_2xx, + _fsm_action_401_407_421_494, + _fsm_action_300_to_699, + _fsm_action_error, +} +_fsm_action_t; + +/* ======================== states ======================== */ +typedef enum _fsm_state_e +{ + _fsm_state_Started, + _fsm_state_Sending, + _fsm_state_Receiving, + _fsm_state_Terminated +} +_fsm_state_t; + + +int tsip_dialog_options_event_callback(const tsip_dialog_options_t *self, tsip_dialog_event_type_t type, const tsip_message_t *msg) +{ + int ret = -1; + + switch(type) + { + case tsip_dialog_i_msg: + { + if(msg){ + if(TSIP_MESSAGE_IS_RESPONSE(msg)){ + const tsip_action_t* action = tsip_dialog_keep_action(TSIP_DIALOG(self), msg) ? TSIP_DIALOG(self)->curr_action : tsk_null; + if(TSIP_RESPONSE_IS_1XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_1xx, msg, action); + } + else if(TSIP_RESPONSE_IS_2XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_2xx, msg, action); + } + else if(TSIP_RESPONSE_CODE(msg) == 401 || TSIP_RESPONSE_CODE(msg) == 407 || TSIP_RESPONSE_CODE(msg) == 421 || TSIP_RESPONSE_CODE(msg) == 494){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_401_407_421_494, msg, action); + } + else if(TSIP_RESPONSE_IS_3456(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_300_to_699, msg, action); + } + else{ /* should never happen */ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_error, msg, action); + } + } + else if (TSIP_REQUEST_IS_OPTIONS(msg)){ /* have been checked by dialog layer...but */ + // REQUEST ==> Incoming OPTIONS + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_receiveOPTIONS, msg, tsk_null); + } + } + break; + } + + case tsip_dialog_canceled: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_cancel, msg, tsk_null); + break; + } + + case tsip_dialog_terminated: + case tsip_dialog_timedout: + case tsip_dialog_error: + case tsip_dialog_transport_error: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_transporterror, msg, tsk_null); + break; + } + + default: break; + } + + return ret; +} + +tsip_dialog_options_t* tsip_dialog_options_create(const tsip_ssession_handle_t* ss) +{ + return tsk_object_new(tsip_dialog_options_def_t, ss); +} + +int tsip_dialog_options_init(tsip_dialog_options_t *self) +{ + //const tsk_param_t* param; + + /* Initialize the state machine. */ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (send) -> Sending + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_sendOPTIONS, _fsm_state_Sending, tsip_dialog_options_Started_2_Sending_X_sendOPTIONS, "tsip_dialog_options_Started_2_Sending_X_sendOPTIONS"), + // Started -> (receive) -> Receiving + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_receiveOPTIONS, _fsm_state_Receiving, tsip_dialog_options_Started_2_Receiving_X_recvOPTIONS, "tsip_dialog_options_Started_2_Receiving_X_recvOPTIONS"), + // Started -> (Any) -> Started + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Started, "tsip_dialog_options_Started_2_Started_X_any"), + + + /*======================= + * === Sending === + */ + // Sending -> (1xx) -> Sending + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_1xx, _fsm_state_Sending, tsip_dialog_options_Sending_2_Sending_X_1xx, "tsip_dialog_options_Sending_2_Sending_X_1xx"), + // Sending -> (2xx) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_2xx, _fsm_state_Terminated, tsip_dialog_options_Sending_2_Terminated_X_2xx, "tsip_dialog_options_Sending_2_Terminated_X_2xx"), + // Sending -> (401/407/421/494) -> Sending + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_401_407_421_494, _fsm_state_Sending, tsip_dialog_options_Sending_2_Sending_X_401_407_421_494, "tsip_dialog_options_Sending_2_Sending_X_401_407_421_494"), + // Sending -> (300_to_699) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_300_to_699, _fsm_state_Terminated, tsip_dialog_options_Sending_2_Terminated_X_300_to_699, "tsip_dialog_options_Sending_2_Terminated_X_300_to_699"), + // Sending -> (cancel) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Sending, _fsm_action_cancel, _fsm_state_Terminated, tsip_dialog_options_Sending_2_Terminated_X_cancel, "tsip_dialog_options_Sending_2_Terminated_X_cancel"), + // Sending -> (Any) -> Sending + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Sending, "tsip_dialog_options_Sending_2_Sending_X_any"), + + /*======================= + * === Receiving === + */ + // Receiving -> (accept) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Receiving, _fsm_action_accept, _fsm_state_Terminated, tsip_dialog_options_Receiving_2_Terminated_X_accept, "tsip_dialog_options_Receiving_2_Terminated_X_accept"), + // Receiving -> (rejected) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Receiving, _fsm_action_reject, _fsm_state_Terminated, tsip_dialog_options_Receiving_2_Terminated_X_reject, "tsip_dialog_options_Receiving_2_Terminated_X_reject"), + // Receiving -> (Any) -> Receiving + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Receiving, "tsip_dialog_options_Receiving_2_Receiving_X_any"), + + /*======================= + * === Any === + */ + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_transporterror, _fsm_state_Terminated, tsip_dialog_options_Any_2_Terminated_X_transportError, "tsip_dialog_options_Any_2_Terminated_X_transportError"), + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_error, _fsm_state_Terminated, tsip_dialog_options_Any_2_Terminated_X_Error, "tsip_dialog_options_Any_2_Terminated_X_Error"), + + TSK_FSM_ADD_NULL()); + + TSIP_DIALOG(self)->callback = TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_options_event_callback); + + return 0; +} + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + + +/* Started -> (sendOPTIONS) -> Sending +*/ +int tsip_dialog_options_Started_2_Sending_X_sendOPTIONS(va_list *app) +{ + tsip_dialog_options_t *self; + + self = va_arg(*app, tsip_dialog_options_t *); + + TSIP_DIALOG(self)->running = tsk_true; + + /* alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connecting, "Dialog connecting"); + + return send_OPTIONS(self); +} + +/* Started -> (recvOPTIONS) -> Receiving +*/ +int tsip_dialog_options_Started_2_Receiving_X_recvOPTIONS(va_list *app) +{ + tsip_dialog_options_t *self = va_arg(*app, tsip_dialog_options_t *); + const tsip_request_t *request = va_arg(*app, const tsip_request_t *); + + /* Update last incoming MESSAGE */ + TSK_OBJECT_SAFE_FREE(self->last_iMessage); + self->last_iMessage = tsk_object_ref((void*)request); + + /* Alert the user. */ + TSIP_DIALOG_OPTIONS_SIGNAL(self, tsip_i_options, + tsip_event_code_dialog_request_incoming, "Incoming Request.", request); + + return 0; +} + +/* Sending -> (1xx) -> Sending +*/ +int tsip_dialog_options_Sending_2_Sending_X_1xx(va_list *app) +{ + /*tsip_dialog_options_t *self = va_arg(*app, tsip_dialog_options_t *);*/ + /*const tsip_response_t *response = va_arg(*app, const tsip_response_t *);*/ + + return 0; +} + +/* Sending -> (2xx) -> Sending +*/ +int tsip_dialog_options_Sending_2_Terminated_X_2xx(va_list *app) +{ + tsip_dialog_options_t *self = va_arg(*app, tsip_dialog_options_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* Alert the user. */ + TSIP_DIALOG_OPTIONS_SIGNAL(self, tsip_ao_options, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + /* Reset curr action */ + tsip_dialog_set_curr_action(TSIP_DIALOG(self), tsk_null); + + return 0; +} + +/* Sending -> (401/407/421/494) -> Sending +*/ +int tsip_dialog_options_Sending_2_Sending_X_401_407_421_494(va_list *app) +{ + tsip_dialog_options_t *self = va_arg(*app, tsip_dialog_options_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + int ret; + + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + /* Alert the user. */ + TSIP_DIALOG_OPTIONS_SIGNAL(self, tsip_ao_options, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return ret; + } + + return send_OPTIONS(self); +} + +/* Sending -> (300 to 699) -> Terminated +*/ +int tsip_dialog_options_Sending_2_Terminated_X_300_to_699(va_list *app) +{ + tsip_dialog_options_t *self = va_arg(*app, tsip_dialog_options_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* Alert the user. */ + TSIP_DIALOG_OPTIONS_SIGNAL(self, tsip_ao_options, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return 0; +} + +/* Sending -> (cancel) -> Terminated +*/ +int tsip_dialog_options_Sending_2_Terminated_X_cancel(va_list *app) +{ + int ret; + + tsip_dialog_options_t *self = va_arg(*app, tsip_dialog_options_t *); + /* const tsip_message_t *message = va_arg(*app, const tsip_message_t *); */ + + /* RFC 3261 - 9.1 Client Behavior + A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + */ + + /* Cancel all transactions associated to this dialog (will also be done when the dialog is destroyed (worth nothing)) */ + ret = tsip_transac_layer_cancel_by_dialog(TSIP_DIALOG_GET_STACK(self)->layer_transac, TSIP_DIALOG(self)); + + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_request_cancelled, "OPTIONS cancelled"); + + return ret; +} + +/* Receiving -> (accept) -> Terminated +*/ +int tsip_dialog_options_Receiving_2_Terminated_X_accept(va_list *app) +{ + tsip_dialog_options_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_options_t *); + va_arg(*app, tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(!self->last_iMessage){ + TSK_DEBUG_ERROR("There is non OPTIONS to accept()"); + /* Not an error ...but do not update current action */ + } + else{ + tsip_response_t *response; + int ret; + + /* curr_action is only used for outgoing requests */ + /* tsip_dialog_set_curr_action(TSIP_DIALOG(self), action); */ + + /* send 200 OK */ + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 200, "OK", self->last_iMessage))){ + tsip_dialog_apply_action(response, action); /* apply action params to "this" response */ + if((ret = tsip_dialog_response_send(TSIP_DIALOG(self), response))){ + TSK_DEBUG_ERROR("Failed to send SIP response."); + TSK_OBJECT_SAFE_FREE(response); + return ret; + } + TSK_OBJECT_SAFE_FREE(response); + } + else{ + TSK_DEBUG_ERROR("Failed to create SIP response."); + return -1; + } + } + + return 0; +} + +/* Receiving -> (reject) -> Terminated +*/ +int tsip_dialog_options_Receiving_2_Terminated_X_reject(va_list *app) +{ + tsip_dialog_options_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_options_t *); + va_arg(*app, tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + if(!self->last_iMessage){ + TSK_DEBUG_ERROR("There is non OPTIONS to reject()"); + /* Not an error ...but do not update current action */ + } + else{ + tsip_response_t *response; + int ret; + + /* curr_action is only used for outgoing requests */ + /* tsip_dialog_set_curr_action(TSIP_DIALOG(self), action); */ + + /* send 486 Rejected */ + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 486, "Rejected", self->last_iMessage))){ + tsip_dialog_apply_action(response, action); /* apply action params to "this" response */ + if((ret = tsip_dialog_response_send(TSIP_DIALOG(self), response))){ + TSK_DEBUG_ERROR("Failed to send SIP response."); + TSK_OBJECT_SAFE_FREE(response); + return ret; + } + TSK_OBJECT_SAFE_FREE(response); + } + else{ + TSK_DEBUG_ERROR("Failed to create SIP response."); + return -1; + } + } + + return 0; +} + +/* Any -> (transport error) -> Terminated +*/ +int tsip_dialog_options_Any_2_Terminated_X_transportError(va_list *app) +{ + /*tsip_dialog_options_t *self = va_arg(*app, tsip_dialog_options_t *);*/ + /*const tsip_message_t *message = va_arg(*app, const tsip_message_t *);*/ + + return 0; +} + +/* Any -> (error) -> Terminated +*/ +int tsip_dialog_options_Any_2_Terminated_X_Error(va_list *app) +{ + /*tsip_dialog_options_t *self = va_arg(*app, tsip_dialog_options_t *);*/ + /*const tsip_message_t *message = va_arg(*app, const tsip_message_t *);*/ + + return 0; +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +int send_OPTIONS(tsip_dialog_options_t *self) +{ + tsip_request_t* request = tsk_null; + int ret = -1; + + if(!self){ + return -1; + } + + if(!(request = tsip_dialog_request_new(TSIP_DIALOG(self), "OPTIONS"))){ + return -2; + } + + /* action parameters and payload*/ + if(TSIP_DIALOG(self)->curr_action){ + const tsk_list_item_t* item; + tsk_list_foreach(item, TSIP_DIALOG(self)->curr_action->headers){ + TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_DUMMY_VA_ARGS(TSK_PARAM(item->data)->name, TSK_PARAM(item->data)->value)); + } + if(TSIP_DIALOG(self)->curr_action->payload){ + tsip_message_add_content(request, tsk_null, TSK_BUFFER_DATA(TSIP_DIALOG(self)->curr_action->payload), TSK_BUFFER_SIZE(TSIP_DIALOG(self)->curr_action->payload)); + } + } + + ret = tsip_dialog_request_send(TSIP_DIALOG(self), request); + TSK_OBJECT_SAFE_FREE(request); + + return ret; +} + +int send_response(tsip_dialog_options_t *self, short status, const char* phrase, const tsip_request_t *request) +{ + tsip_response_t *response; + int ret = -1; + + response = tsip_dialog_response_new(TSIP_DIALOG(self), status, phrase, request); + if(response){ + if(response->To && !response->To->tag){ + tsk_istr_t tag; + tsk_strrandom(&tag); + response->To->tag = tsk_strdup(tag); + } + ret = tsip_dialog_response_send(TSIP_DIALOG(self), response); + TSK_OBJECT_SAFE_FREE(response); + } + + return ret; +} + + +int tsip_dialog_options_OnTerminated(tsip_dialog_options_t *self) +{ + TSK_DEBUG_INFO("=== OPTIONS Dialog terminated ==="); + + /* alert user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminated, + TSIP_DIALOG(self)->last_error.phrase ? TSIP_DIALOG(self)->last_error.phrase : "Dialog terminated"); + + /* Remove from the dialog layer. */ + return tsip_dialog_remove(TSIP_DIALOG(self)); +} + + + + + + + + + + + + + + + + + + + + + + +//======================================================== +// SIP dialog OPTIONS object definition +// +static tsk_object_t* tsip_dialog_options_ctor(tsk_object_t * self, va_list * app) +{ + tsip_dialog_options_t *dialog = self; + if(dialog){ + tsip_ssession_handle_t *ss = va_arg(*app, tsip_ssession_handle_t *); + + /* Initialize base class */ + tsip_dialog_init(TSIP_DIALOG(self), tsip_dialog_OPTIONS, tsk_null, ss, _fsm_state_Started, _fsm_state_Terminated); + + /* FSM */ + TSIP_DIALOG_GET_FSM(self)->debug = DEBUG_STATE_MACHINE; + tsk_fsm_set_callback_terminated(TSIP_DIALOG_GET_FSM(self), TSK_FSM_ONTERMINATED_F(tsip_dialog_options_OnTerminated), (const void*)dialog); + + /* Initialize the class itself */ + tsip_dialog_options_init(self); + } + return self; +} + +static tsk_object_t* tsip_dialog_options_dtor(tsk_object_t * self) +{ + tsip_dialog_options_t *dialog = self; + if(dialog){ + + /* DeInitialize base class (will cancel all transactions) */ + tsip_dialog_deinit(TSIP_DIALOG(self)); + + /* DeInitialize self */ + TSK_OBJECT_SAFE_FREE(dialog->last_iMessage); + + TSK_DEBUG_INFO("*** OPTIONS Dialog destroyed ***"); + } + return self; +} + +static int tsip_dialog_options_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2) +{ + return tsip_dialog_cmp(obj1, obj2); +} + +static const tsk_object_def_t tsip_dialog_options_def_s = +{ + sizeof(tsip_dialog_options_t), + tsip_dialog_options_ctor, + tsip_dialog_options_dtor, + tsip_dialog_options_cmp, +}; +const tsk_object_def_t *tsip_dialog_options_def_t = &tsip_dialog_options_def_s; diff --git a/tinySIP/src/dialogs/tsip_dialog_publish.client.c b/tinySIP/src/dialogs/tsip_dialog_publish.client.c new file mode 100644 index 0000000..aa22737 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_publish.client.c @@ -0,0 +1,701 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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_publish.client.c + * @brief SIP dialog PUBLISH as per RFC 3903. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_publish.h" + +#include "tinysip/headers/tsip_header_Dummy.h" +#include "tinysip/headers/tsip_header_Min_Expires.h" +#include "tinysip/headers/tsip_header_SIP_ETag.h" +#include "tinysip/headers/tsip_header_SIP_If_Match.h" + +#include "tinysip/transactions/tsip_transac_layer.h" + +#include "tinysip/tsip_message.h" + +#include "tinysip/api/tsip_api_publish.h" + +#include "tsk_debug.h" +#include "tsk_time.h" + +#define DEBUG_STATE_MACHINE 0 +#define TSIP_DIALOG_PUBLISH_TIMER_SCHEDULE(TX) TSIP_DIALOG_TIMER_SCHEDULE(publish, TX) +#define TSIP_DIALOG_PUBLISH_SIGNAL(self, type, code, phrase, message) \ + tsip_publish_event_signal(type, TSIP_DIALOG(self)->ss, code, phrase, message) + +/* ======================== internal functions ======================== */ +static int send_PUBLISH(tsip_dialog_publish_t *self); +static int tsip_dialog_publish_OnTerminated(tsip_dialog_publish_t *self); + +/* ======================== transitions ======================== */ +static int tsip_dialog_publish_Started_2_Trying_X_publish(va_list *app); +static int tsip_dialog_publish_Trying_2_Trying_X_1xx(va_list *app); +static int tsip_dialog_publish_Trying_2_Terminated_X_2xx(va_list *app); +static int tsip_dialog_publish_Trying_2_Connected_X_2xx(va_list *app); +static int tsip_dialog_publish_Trying_2_Trying_X_401_407_421_494(va_list *app); +static int tsip_dialog_publish_Trying_2_Trying_X_423(va_list *app); +static int tsip_dialog_publish_Trying_2_Terminated_X_300_to_699(va_list *app); +static int tsip_dialog_publish_Trying_2_Terminated_X_cancel(va_list *app); +static int tsip_dialog_publish_Connected_2_Trying_X_publish(va_list *app); +static int tsip_dialog_publish_Any_2_Trying_X_hangup(va_list *app); +static int tsip_dialog_publish_Any_2_Trying_X_shutdown(va_list *app); +static int tsip_dialog_publish_Any_2_Terminated_X_transportError(va_list *app); +static int tsip_dialog_publish_Any_2_Terminated_X_Error(va_list *app); + + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_unpublishing(tsip_dialog_publish_t* dialog, tsip_message_t* message) +{ + return dialog->unpublishing; +} +static tsk_bool_t _fsm_cond_publishing(tsip_dialog_publish_t* dialog, tsip_message_t* message) +{ + return !_fsm_cond_unpublishing(dialog, message); +} + +static tsk_bool_t _fsm_cond_silent_hangup(tsip_dialog_publish_t* dialog, tsip_message_t* message) +{ + return TSIP_DIALOG(dialog)->ss->silent_hangup; +} +static tsk_bool_t _fsm_cond_not_silent_hangup(tsip_dialog_publish_t* dialog, tsip_message_t* message) +{ + return !TSIP_DIALOG(dialog)->ss->silent_hangup; +} +#define _fsm_cond_silent_shutdown _fsm_cond_silent_hangup +#define _fsm_cond_not_silent_shutdown _fsm_cond_not_silent_hangup + + +/* ======================== actions ======================== */ +typedef enum _fsm_action_e +{ + _fsm_action_publish = tsip_atype_publish, + _fsm_action_cancel = tsip_atype_cancel, + _fsm_action_hangup = tsip_atype_unpublish, + _fsm_action_shutdown = tsip_atype_shutdown, + _fsm_action_transporterror = tsip_atype_transport_error, + + _fsm_action_1xx = 0xFF, + _fsm_action_2xx, + _fsm_action_401_407_421_494, + _fsm_action_423, + _fsm_action_300_to_699, + _fsm_action_shutdown_timedout, /* Any -> Terminated */ + _fsm_action_error, +} +_fsm_action_t; + +/* ======================== states ======================== */ +typedef enum _fsm_state_e +{ + _fsm_state_Started, + _fsm_state_Trying, + _fsm_state_Connected, + _fsm_state_Terminated +} +_fsm_state_t; + + +/** + * Callback function called to alert the dialog for new events from the transaction/transport layers. + * + * @param [in,out] self A reference to the dialog. + * @param type The event type. + * @param [in,out] msg The incoming SIP/IMS message. + * + * @return Zero if succeed and non-zero error code otherwise. +**/ +int tsip_dialog_publish_event_callback(const tsip_dialog_publish_t *self, tsip_dialog_event_type_t type, const tsip_message_t *msg) +{ + int ret = -1; + + switch(type) + { + case tsip_dialog_i_msg: + { + if(msg && TSIP_MESSAGE_IS_RESPONSE(msg)){ + // + // RESPONSE + // + const tsip_action_t* action = tsip_dialog_keep_action(TSIP_DIALOG(self), msg) ? TSIP_DIALOG(self)->curr_action : tsk_null; + if(TSIP_RESPONSE_IS_1XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_1xx, msg, action); + } + else if(TSIP_RESPONSE_IS_2XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_2xx, msg, action); + } + else if(TSIP_RESPONSE_IS(msg,401) || TSIP_RESPONSE_IS(msg,407) || TSIP_RESPONSE_IS(msg,421) || TSIP_RESPONSE_IS(msg,494)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_401_407_421_494, msg, action); + } + else if(TSIP_RESPONSE_IS(msg,423)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_423, msg, action); + } + else{ + // Alert User + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_error, msg, action); + /* TSK_DEBUG_WARN("Not supported status code: %d", TSIP_RESPONSE_CODE(msg)); */ + } + } + else{ + // + // REQUEST + // + } + break; + } + + case tsip_dialog_canceled: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_cancel, msg, tsk_null); + break; + } + + case tsip_dialog_terminated: + case tsip_dialog_timedout: + case tsip_dialog_error: + case tsip_dialog_transport_error: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_transporterror, msg, tsk_null); + break; + } + + default: break; + } + + return ret; +} + +/** + * Timer manager callback. + * + * @param [in,out] self The owner of the signaled timer. + * @param timer_id The identifier of the signaled timer. + * + * @return Zero if succeed and non-zero error code otherwise. +**/ +int tsip_dialog_publish_timer_callback(const tsip_dialog_publish_t* self, tsk_timer_id_t timer_id) +{ + int ret = -1; + + if(self) + { + if(timer_id == self->timerrefresh.id){ + tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_publish, tsk_null, tsk_null); + ret = 0; + } + else if(timer_id == self->timershutdown.id){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_error, tsk_null, tsk_null); + } + } + return ret; +} + + +tsip_dialog_publish_t* tsip_dialog_publish_create(const tsip_ssession_handle_t* ss) +{ + return tsk_object_new(tsip_dialog_publish_def_t, ss); +} + +/** + * Initializes the dialog. + * + * @param [in,out] self The dialog to initialize. +**/ +int tsip_dialog_publish_init(tsip_dialog_publish_t *self) +{ + /* Initialize the State Machine. */ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (PUBLISH) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_publish, _fsm_state_Trying, tsip_dialog_publish_Started_2_Trying_X_publish, "tsip_dialog_publish_Started_2_Trying_X_publish"), + // Started -> (Any) -> Started + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Started, "tsip_dialog_publish_Started_2_Started_X_any"), + + + /*======================= + * === Trying === + */ + // Trying -> (1xx) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_1xx, _fsm_state_Trying, tsip_dialog_publish_Trying_2_Trying_X_1xx, "tsip_dialog_publish_Trying_2_Trying_X_1xx"), + // Trying -> (2xx) -> Terminated + TSK_FSM_ADD(_fsm_state_Trying, _fsm_action_2xx, _fsm_cond_unpublishing, _fsm_state_Terminated, tsip_dialog_publish_Trying_2_Terminated_X_2xx, "tsip_dialog_publish_Trying_2_Terminated_X_2xx"), + // Trying -> (2xx) -> Connected + TSK_FSM_ADD(_fsm_state_Trying, _fsm_action_2xx, _fsm_cond_publishing, _fsm_state_Connected, tsip_dialog_publish_Trying_2_Connected_X_2xx, "tsip_dialog_publish_Trying_2_Connected_X_2xx"), + // Trying -> (401/407/421/494) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_401_407_421_494, _fsm_state_Trying, tsip_dialog_publish_Trying_2_Trying_X_401_407_421_494, "tsip_dialog_publish_Trying_2_Trying_X_401_407_421_494"), + // Trying -> (423) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_423, _fsm_state_Trying, tsip_dialog_publish_Trying_2_Trying_X_423, "tsip_dialog_publish_Trying_2_Trying_X_423"), + // Trying -> (300_to_699) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_300_to_699, _fsm_state_Terminated, tsip_dialog_publish_Trying_2_Terminated_X_300_to_699, "tsip_dialog_publish_Trying_2_Terminated_X_300_to_699"), + // Trying -> (cancel) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_cancel, _fsm_state_Terminated, tsip_dialog_publish_Trying_2_Terminated_X_cancel, "tsip_dialog_publish_Trying_2_Terminated_X_cancel"), + // Trying -> (hangup) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_hangup, _fsm_state_Terminated, tsk_null, "tsip_dialog_publish_Trying_2_Terminated_X_hangup"), + // Trying -> (shutdown) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_shutdown, _fsm_state_Terminated, tsk_null, "tsip_dialog_publish_Trying_2_Terminated_X_shutdown"), + // Trying -> (Any) -> Trying + // TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Trying, "tsip_dialog_publish_Trying_2_Trying_X_any"), + + + /*======================= + * === Connected === + */ + // Connected -> (PUBLISH) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_publish, _fsm_state_Trying, tsip_dialog_publish_Connected_2_Trying_X_publish, "tsip_dialog_publish_Connected_2_Trying_X_publish"), + + /*======================= + * === Any === + */ + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_transporterror, _fsm_state_Terminated, tsip_dialog_publish_Any_2_Terminated_X_transportError, "tsip_dialog_publish_Any_2_Terminated_X_transportError"), + // Any -> (error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_error, _fsm_state_Terminated, tsip_dialog_publish_Any_2_Terminated_X_Error, "tsip_dialog_publish_Any_2_Terminated_X_Error"), + // Any -> (hangup) -> Trying + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_hangup, _fsm_cond_not_silent_hangup, _fsm_state_Trying, tsip_dialog_publish_Any_2_Trying_X_hangup, "tsip_dialog_publish_Any_2_Trying_X_hangup"), + // Any -> (silenthangup) -> Terminated + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_hangup, _fsm_cond_silent_hangup, _fsm_state_Terminated, tsk_null, "tsip_dialog_publish_Any_2_Trying_X_silenthangup"), + // Any -> (shutdown) -> Trying + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_shutdown, _fsm_cond_not_silent_shutdown, _fsm_state_Trying, tsip_dialog_publish_Any_2_Trying_X_shutdown, "tsip_dialog_publish_Any_2_Trying_X_shutdown"), + // Any -> (silentshutdown) -> Terminated + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_shutdown, _fsm_cond_silent_shutdown, _fsm_state_Terminated, tsk_null, "tsip_dialog_publishe_Any_2_Trying_X_silentshutdown"), + // Any -> (shutdown timedout) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_shutdown_timedout, _fsm_state_Terminated, tsk_null, "tsip_dialog_publish_shutdown_timedout"), + + TSK_FSM_ADD_NULL()); + + /* Sets callback function */ + TSIP_DIALOG(self)->callback = TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_publish_event_callback); + + /* Timers */ + self->timerrefresh.id = TSK_INVALID_TIMER_ID; + self->timerrefresh.timeout = TSIP_DIALOG(self)->expires; + self->timershutdown.id = TSK_INVALID_TIMER_ID; + self->timershutdown.timeout = TSIP_DIALOG_SHUTDOWN_TIMEOUT; + + return 0; +} + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + + +/* Started -> (send) -> Trying +*/ +int tsip_dialog_publish_Started_2_Trying_X_publish(va_list *app) +{ + tsip_dialog_publish_t *self; + + self = va_arg(*app, tsip_dialog_publish_t *); + + TSIP_DIALOG(self)->running = tsk_true; + + /* alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connecting, "Dialog connecting"); + + return send_PUBLISH(self); +} + +/* Trying -> (1xx) -> Trying +*/ +int tsip_dialog_publish_Trying_2_Trying_X_1xx(va_list *app) +{ + /*tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *);*/ + /*const tsip_response_t *response = va_arg(*app, const tsip_response_t *);*/ + + return 0; +} + +/* Trying -> (2xx) -> Terminated +*/ +int tsip_dialog_publish_Trying_2_Terminated_X_2xx(va_list *app) +{ + tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* Alert the user. */ + TSIP_DIALOG_PUBLISH_SIGNAL(self, self->unpublishing ? tsip_ao_unpublish : tsip_ao_publish, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return 0; +} + +/* Trying -> (2xx) -> Connected +*/ +int tsip_dialog_publish_Trying_2_Connected_X_2xx(va_list *app) +{ + tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + int ret; + + tsk_bool_t first_time_to_connect = (TSIP_DIALOG(self)->state == tsip_initial); + + /* RFC 3903 - 4.1. Identification of Published Event State + For each successful PUBLISH request, the ESC will generate and assign + an entity-tag and return it in the SIP-ETag header field of the 2xx + response. + */ + const tsip_header_SIP_ETag_t *SIP_ETag; + if((SIP_ETag = (const tsip_header_SIP_ETag_t*)tsip_message_get_header(response, tsip_htype_SIP_ETag))){ + tsk_strupdate(&self->etag, SIP_ETag->value); + } + + /* Alert the user (session)*/ + TSIP_DIALOG_PUBLISH_SIGNAL(self, self->unpublishing ? tsip_ao_unpublish : tsip_ao_publish, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + /* Alert the user (dialog)*/ + if(first_time_to_connect){ /* PUBLISH not dialog oriented ...but */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connected, "Dialog connected"); + } + + /* Update the dialog state */ + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + return ret; + } + + /* Reset current action */ + tsip_dialog_set_curr_action(TSIP_DIALOG(self), tsk_null); + + /* Request timeout for dialog refresh (re-publish). */ + self->timerrefresh.timeout = tsip_dialog_get_newdelay(TSIP_DIALOG(self), response); + TSIP_DIALOG_PUBLISH_TIMER_SCHEDULE(refresh); + + return 0; +} + +/* Trying -> (401/407/421/494) -> Trying +*/ +int tsip_dialog_publish_Trying_2_Trying_X_401_407_421_494(va_list *app) +{ + tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + int ret; + + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + /* Alert the user. */ + TSIP_DIALOG_PUBLISH_SIGNAL(self, self->unpublishing ? tsip_ao_unpublish : tsip_ao_publish, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return ret; + } + + return send_PUBLISH(self); +} + +/* Trying -> (423) -> Trying +*/ +int tsip_dialog_publish_Trying_2_Trying_X_423(va_list *app) +{ + tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + tsip_header_Min_Expires_t *hdr; + + /* + RFC 3261 - 10.2.8 Error Responses + + If a UA receives a 423 (Interval Too Brief) response, it MAY retry + the registration after making the expiration interval of all contact + addresses in the PUBLISH request equal to or greater than the + expiration interval within the Min-Expires header field of the 423 + (Interval Too Brief) response. + */ + hdr = (tsip_header_Min_Expires_t*)tsip_message_get_header(response, tsip_htype_Min_Expires); + if(hdr){ + TSIP_DIALOG(self)->expires = TSK_TIME_S_2_MS(hdr->value); + send_PUBLISH(self); + } + else{ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_message_error, "Received invalid SIP response"); + + return -1; + } + + return 0; +} + +/* Trying -> (300 to 699) -> Terminated +*/ +int tsip_dialog_publish_Trying_2_Terminated_X_300_to_699(va_list *app) +{ + tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), TSIP_RESPONSE_PHRASE(response), TSIP_RESPONSE_CODE(response)); + + /* Alert the user. */ + TSIP_DIALOG_PUBLISH_SIGNAL(self, self->unpublishing ? tsip_ao_unpublish : tsip_ao_publish, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return 0; +} + +/* Trying -> (cancel) -> Terminated +*/ +int tsip_dialog_publish_Trying_2_Terminated_X_cancel(va_list *app) +{ + int ret; + tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *); + /* const tsip_message_t *message = va_arg(*app, const tsip_message_t *); */ + + /* Cancel all transactions associated to this dialog (will also be done when the dialog is destroyed (worth nothing)) */ + ret = tsip_transac_layer_cancel_by_dialog(TSIP_DIALOG_GET_STACK(self)->layer_transac, TSIP_DIALOG(self)); + + /* RFC 3261 - 9.1 Client Behavior + A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + */ + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_request_cancelled, "Subscription cancelled"); + + return ret; +} + +/* Connected -> (PUBLISH) -> Trying +*/ +int tsip_dialog_publish_Connected_2_Trying_X_publish(va_list *app) +{ + tsip_dialog_publish_t *self; + + self = va_arg(*app, tsip_dialog_publish_t *); + + return send_PUBLISH(self); +} + +/* Connected -> (hangup) -> Trying +*/ +int tsip_dialog_publish_Any_2_Trying_X_hangup(va_list *app) +{ + tsip_dialog_publish_t *self; + + self = va_arg(*app, tsip_dialog_publish_t *); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog"); + + self->unpublishing = tsk_true; + return send_PUBLISH(self); +} + +/* Any -> (shutdown) -> Trying +*/ +int tsip_dialog_publish_Any_2_Trying_X_shutdown(va_list *app) +{ + tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *); + + /* schedule shutdow timeout */ + TSIP_DIALOG_PUBLISH_TIMER_SCHEDULE(shutdown); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog"); + + self->unpublishing = tsk_true; + return send_PUBLISH(self); +} + +/* Any -> (transport error) -> Terminated +*/ +int tsip_dialog_publish_Any_2_Terminated_X_transportError(va_list *app) +{ + tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *); + /*const tsip_message_t *message = va_arg(*app, const tsip_message_t *);*/ + + /* Alert the user. */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_transport_error, "Transport error."); + + return 0; +} + +/* Any -> (Error) -> Terminated +*/ +int tsip_dialog_publish_Any_2_Terminated_X_Error(va_list *app) +{ + tsip_dialog_publish_t *self = va_arg(*app, tsip_dialog_publish_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* Alert the user. */ + if(response){ + TSIP_DIALOG_PUBLISH_SIGNAL(self, self->unpublishing ? tsip_ao_unpublish : tsip_ao_publish, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + } + else{ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_global_error, "Global error."); + } + + return 0; +} + + + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +/** + * Sends a PUBLISH request. +**/ +int send_PUBLISH(tsip_dialog_publish_t *self) +{ + tsip_request_t *request = tsk_null; + int ret = -1; + const tsip_action_t* action; + + if(!self){ + return -1; + } + + // action + action = TSIP_DIALOG(self)->curr_action; + + if(self->unpublishing){ + TSIP_DIALOG(self)->expires = 0; + } + + /* RFC 3903 - 4.1. Identification of Published Event State + The presence of a body and the SIP-If-Match header field determine + the specific SSESSION that the request is performing, as described in Table 1. + +-----------+-------+---------------+---------------+ + | SSESSION | Body? | SIP-If-Match? | Expires Value | + +-----------+-------+---------------+---------------+ + | Initial | yes | no | > 0 | + | Refresh | no | yes | > 0 | + | Modify | yes | yes | > 0 | + | Remove | no | yes | 0 | + +-----------+-------+---------------+---------------+ + Table 1: Publication ssessions + */ + if((request = tsip_dialog_request_new(TSIP_DIALOG(self), "PUBLISH"))){ + /*Etag. If initial then etag is null. */ + if(self->etag){ + TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_SIP_IF_MATCH_VA_ARGS(self->etag)); + } + /*Body*/ + if(action && action->payload && !self->unpublishing){ + const tsk_list_item_t* item; + tsk_list_foreach(item, action->headers){ + TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_DUMMY_VA_ARGS(TSK_PARAM(item->data)->name, TSK_PARAM(item->data)->value)); + } + if(action->payload){ + tsip_message_add_content(request, tsk_null, TSK_BUFFER_DATA(action->payload), TSK_BUFFER_SIZE(action->payload)); + } + } + + ret = tsip_dialog_request_send(TSIP_DIALOG(self), request); + TSK_OBJECT_SAFE_FREE(request); + } + + return ret; +} + +/** + * Callback function called by the state machine manager to signal that the final state has been reached. + * + * @param [in,out] self The state machine owner. +**/ +int tsip_dialog_publish_OnTerminated(tsip_dialog_publish_t *self) +{ + TSK_DEBUG_INFO("=== PUBLISH Dialog terminated ==="); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminated, + TSIP_DIALOG(self)->last_error.phrase ? TSIP_DIALOG(self)->last_error.phrase : "Dialog terminated"); + + /* Remove from the dialog layer. */ + return tsip_dialog_remove(TSIP_DIALOG(self)); +} + + + + + + + + + + + + + +//======================================================== +// SIP dialog PUBLISH object definition +// +static tsk_object_t* tsip_dialog_publish_ctor(tsk_object_t * self, va_list * app) +{ + tsip_dialog_publish_t *dialog = self; + if(dialog){ + tsip_ssession_handle_t *ss = va_arg(*app, tsip_ssession_handle_t *); + + /* init base class */ + tsip_dialog_init(TSIP_DIALOG(self), tsip_dialog_PUBLISH, tsk_null, ss, _fsm_state_Started, _fsm_state_Terminated); + + /* FSM */ + TSIP_DIALOG_GET_FSM(self)->debug = DEBUG_STATE_MACHINE; + tsk_fsm_set_callback_terminated(TSIP_DIALOG_GET_FSM(self), TSK_FSM_ONTERMINATED_F(tsip_dialog_publish_OnTerminated), (const void*)dialog); + + /* init the class itself */ + tsip_dialog_publish_init(self); + } + return self; +} + +static tsk_object_t* tsip_dialog_publish_dtor(tsk_object_t * _self) +{ + tsip_dialog_publish_t *self = _self; + if(self){ + TSK_DEBUG_INFO("*** PUBLISH Dialog destroyed ***"); + + /* Cancel all timers */ + TSIP_DIALOG_TIMER_CANCEL(refresh); + TSIP_DIALOG_TIMER_CANCEL(shutdown); + + /* deinit base class (will cancel all transactions) */ + tsip_dialog_deinit(TSIP_DIALOG(self)); + + /* deinit self*/ + TSK_FREE(self->etag); + } + return self; +} + +static int tsip_dialog_publish_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2) +{ + return tsip_dialog_cmp(obj1, obj2); +} + +static const tsk_object_def_t tsip_dialog_publish_def_s = +{ + sizeof(tsip_dialog_publish_t), + tsip_dialog_publish_ctor, + tsip_dialog_publish_dtor, + tsip_dialog_publish_cmp, +}; +const tsk_object_def_t *tsip_dialog_publish_def_t = &tsip_dialog_publish_def_s; diff --git a/tinySIP/src/dialogs/tsip_dialog_register.c b/tinySIP/src/dialogs/tsip_dialog_register.c new file mode 100644 index 0000000..a1c1424 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_register.c @@ -0,0 +1,507 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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. +* +*/ +#include "tinysip/dialogs/tsip_dialog_register.h" +#include "tinysip/dialogs/tsip_dialog_register.common.h" + +#include "tinysip/parsers/tsip_parser_uri.h" + +#include "tinysip/transports/tsip_transport_layer.h" + +#include "tinysip/headers/tsip_header_Dummy.h" +#include "tinysip/headers/tsip_header_Supported.h" + +#include "tinysip/transactions/tsip_transac_layer.h" + +#include "tipsec.h" + +#include "tsk_memory.h" +#include "tsk_debug.h" +#include "tsk_time.h" + + +/* ======================== internal functions ======================== */ +/*static*/ int tsip_dialog_register_send_REGISTER(tsip_dialog_register_t *self, tsk_bool_t initial); +/*static*/ int tsip_dialog_register_send_RESPONSE(tsip_dialog_register_t *self, const tsip_request_t* request, short code, const char* phrase); +/*static*/ int tsip_dialog_register_OnTerminated(tsip_dialog_register_t *self); + +/* ======================== transitions ======================== */ +static int tsip_dialog_register_Any_2_InProgress_X_hangup(va_list *app); +static int tsip_dialog_register_Any_2_InProgress_X_shutdown(va_list *app); +static int tsip_dialog_register_Any_2_Terminated_X_transportError(va_list *app); +static int tsip_dialog_register_Any_2_Terminated_X_Error(va_list *app); + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_silent_hangup(tsip_dialog_register_t* dialog, tsip_message_t* message) +{ + return TSIP_DIALOG(dialog)->ss->silent_hangup; +} +static tsk_bool_t _fsm_cond_not_silent_hangup(tsip_dialog_register_t* dialog, tsip_message_t* message) +{ + return !TSIP_DIALOG(dialog)->ss->silent_hangup; +} + + +/* Client-Side dialog */ +extern int tsip_dialog_register_client_init(tsip_dialog_register_t *self); +/* Server-Side dialog */ +extern int tsip_dialog_register_server_init(tsip_dialog_register_t *self); + +/** + * @fn int tsip_dialog_register_event_callback(const tsip_dialog_register_t *self, tsip_dialog_event_type_t type, + * const tsip_message_t *msg) + * + * @brief Callback function called to alert the dialog for new events from the transaction/transport layers. + * + * @param [in,out] self A reference to the dialog. + * @param type The event type. + * @param [in,out] msg The incoming SIP/IMS message. + * + * @return Zero if succeed and non-zero error code otherwise. +**/ +int tsip_dialog_register_event_callback(const tsip_dialog_register_t *self, tsip_dialog_event_type_t type, const tsip_message_t *msg) +{ + int ret = -1; + + switch(type){ + case tsip_dialog_i_msg: + { + if(msg){ + if(TSIP_MESSAGE_IS_RESPONSE(msg)){ + // + // RESPONSE + // + const tsip_action_t* action = tsip_dialog_keep_action(TSIP_DIALOG(self), msg) ? TSIP_DIALOG(self)->curr_action : tsk_null; + if(TSIP_RESPONSE_IS_1XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_1xx, msg, action); + } + else if(TSIP_RESPONSE_IS_2XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_2xx, msg, action); + } + else if(TSIP_RESPONSE_IS(msg,401) || TSIP_RESPONSE_IS(msg,407) || TSIP_RESPONSE_IS(msg,421) || TSIP_RESPONSE_IS(msg,494)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_401_407_421_494, msg, action); + } + else if(TSIP_RESPONSE_IS(msg,423)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_423, msg, action); + } + else{ + // Alert User + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_error, msg, action); + /* TSK_DEBUG_WARN("Not supported status code: %d", TSIP_RESPONSE_CODE(msg)); */ + } + } + else{ + // + // REQUEST + // + if(TSIP_REQUEST_IS_REGISTER(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_iREGISTER, msg, tsk_null); + } + } + } + break; + } + + case tsip_dialog_canceled: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_cancel, msg, tsk_null); + break; + } + + case tsip_dialog_terminated: + case tsip_dialog_timedout: + case tsip_dialog_error: + case tsip_dialog_transport_error: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_transporterror, msg, tsk_null); + break; + } + + default: break; + } + + return ret; +} + +/**Timer manager callback. + * + * @param self The owner of the signaled timer. + * @param timer_id The identifier of the signaled timer. + * + * @return Zero if succeed and non-zero error code otherwise. +**/ +int tsip_dialog_register_timer_callback(const tsip_dialog_register_t* self, tsk_timer_id_t timer_id) +{ + int ret = -1; + + if(self){ + if(timer_id == self->timerrefresh.id){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_oREGISTER, tsk_null, tsk_null); + } + else if(timer_id == self->timershutdown.id){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_shutdown_timedout, tsk_null, tsk_null); + } + } + return ret; +} + +/** Create SIP REGISTER dialog. */ +tsip_dialog_register_t* tsip_dialog_register_create(const tsip_ssession_handle_t* ss, const char* call_id) +{ + return tsk_object_new(tsip_dialog_register_def_t, ss, call_id); +} + + +/** Initializes the dialog. + * + * @param [in,out] self The dialog to initialize. +**/ +int tsip_dialog_register_init(tsip_dialog_register_t *self) +{ + // Initialize client side + tsip_dialog_register_client_init(self); + // initialize server side + tsip_dialog_register_server_init(self); + + /* Initialize common side */ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Any === + */ + // Any -> (hangup) -> InProgress + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_hangup, _fsm_cond_not_silent_hangup, _fsm_state_InProgress, tsip_dialog_register_Any_2_InProgress_X_hangup, "tsip_dialog_register_Any_2_InProgress_X_hangup"), + // Any -> (silenthangup) -> Terminated + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_hangup, _fsm_cond_silent_hangup, _fsm_state_Terminated, tsk_null, "tsip_dialog_register_Any_2_InProgress_X_silenthangup"), + // Any -> (shutdown) -> InProgress + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_shutdown, _fsm_cond_not_silent_shutdown, _fsm_state_InProgress, tsip_dialog_register_Any_2_InProgress_X_shutdown, "tsip_dialog_register_Any_2_InProgress_X_shutdown"), + // Any -> (silentshutdown) -> Terminated + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_shutdown, _fsm_cond_silent_shutdown, _fsm_state_Terminated, tsk_null, "tsip_dialog_register_Any_2_InProgress_X_silentshutdown"), + // Any -> (shutdown timedout) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_shutdown_timedout, _fsm_state_Terminated, tsk_null, "tsip_dialog_register_shutdown_timedout"), + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_transporterror, _fsm_state_Terminated, tsip_dialog_register_Any_2_Terminated_X_transportError, "tsip_dialog_register_Any_2_Terminated_X_transportError"), + // Any -> (error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_error, _fsm_state_Terminated, tsip_dialog_register_Any_2_Terminated_X_Error, "tsip_dialog_register_Any_2_Terminated_X_Error"), + + TSK_FSM_ADD_NULL()); + + /* Sets callback function */ + TSIP_DIALOG(self)->callback = TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_register_event_callback); + + /* Timers */ + self->timerrefresh.id = TSK_INVALID_TIMER_ID; + self->timerrefresh.timeout = TSIP_DIALOG(self)->expires; + self->timershutdown.id = TSK_INVALID_TIMER_ID; + self->timershutdown.timeout = TSIP_DIALOG_SHUTDOWN_TIMEOUT; + + return 0; +} + + + + +/* Any -> (hangup) -> InProgress +*/ +int tsip_dialog_register_Any_2_InProgress_X_hangup(va_list *app) +{ + tsip_dialog_register_t *self; + + self = va_arg(*app, tsip_dialog_register_t *); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog"); + + self->unregistering = tsk_true; + return tsip_dialog_register_send_REGISTER(self, tsk_true); +} + +/* Any -> (shutdown) -> InProgress +*/ +int tsip_dialog_register_Any_2_InProgress_X_shutdown(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + + /* schedule shutdow timeout */ + TSIP_DIALOG_REGISTER_TIMER_SCHEDULE(shutdown); + + /* alert user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog"); + + self->unregistering = tsk_true; + return tsip_dialog_register_send_REGISTER(self, tsk_true); +} + +/* Any -> (transport error) -> Terminated +*/ +int tsip_dialog_register_Any_2_Terminated_X_transportError(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + /*const tsip_message_t *message = va_arg(*app, const tsip_message_t *);*/ + + /* Alert the user. */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_transport_error, "Transport error."); + + return 0; +} + +/* Any -> (error) -> Terminated +*/ +int tsip_dialog_register_Any_2_Terminated_X_Error(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* save last error */ + tsip_dialog_set_lasterror_2(TSIP_DIALOG(self), TSIP_RESPONSE_PHRASE(response), TSIP_RESPONSE_CODE(response), response); + + /* Alert the user. */ + if(response){ + TSIP_DIALOG_REGISTER_SIGNAL(self, self->unregistering ? tsip_ao_unregister : tsip_ao_register, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + } + else{ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_global_error, "Global error."); + } + + return 0; +} + + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +/** + * + * Sends a REGISTER request. + * + * @param [in,out] self The caller. + * @param [in] initial Indicates whether it's an initial (new CSeq) REGISTER or not. + * Initial REGISTER request will creates new IPSec temporary SAs. + * + * @return Zero if succeed and non-zero error code otherwise. +**/ +int tsip_dialog_register_send_REGISTER(tsip_dialog_register_t *self, tsk_bool_t initial) +{ + tsip_request_t *request; + int ret = -1; + + /* whether we are unregistering */ + if(self->unregistering){ + TSIP_DIALOG(self)->expires = 0; + } + + /* creates REGISTER request */ + if((request = tsip_dialog_request_new(TSIP_DIALOG(self), "REGISTER"))){ + /* == RCS phase 2 + */ + /*if(TSIP_DIALOG_GET_STACK(self)->enable_gsmarcs){ + TSIP_HEADER_ADD_PARAM(request->Contact, "+g.oma.sip-im.large-message", 0); + TSIP_HEADER_ADD_PARAM(request->Contact, "audio", 0); + TSIP_HEADER_ADD_PARAM(request->Contact, "video", 0); + TSIP_HEADER_ADD_PARAM(request->Contact, "+g.3gpp.cs-voice", 0); + TSIP_HEADER_ADD_PARAM(request->Contact, "+g.3gpp.icsi-ref", TSIP_ICSI_QUOTED_MMTEL_PSVOICE); + }*/ + + ///* mobility */ + //if(TSIP_DIALOG_GET_STACK(self)->mobility){ + // TSIP_HEADER_ADD_PARAM(request->Contact, "mobility", TSIP_DIALOG_GET_STACK(self)->mobility); + //} + + ///* deviceID - FIXME: find reference. */ + //if(TSIP_DIALOG_GET_STACK(self)->device_id){ + // TSIP_HEADER_ADD_PARAM(request->Contact, "+deviceID", TSIP_DIALOG_GET_STACK(self)->device_id); + //} + + ///* GSMA Image Sharing */ + //if(TSIP_DIALOG_GET_STACK(self)->enable_gsmais){ + // TSIP_HEADER_ADD_PARAM(request->Contact, "+g.3gpp.app_ref", TSIP_IARI_QUOTED_GSMAIS); + //} + + ///* 3GPP TS 24.341 subclause 5.3.2.2 */ + //if(TSIP_DIALOG_GET_STACK(self)->enable_3gppsms){ + // TSIP_HEADER_ADD_PARAM(request->Contact, "+g.3gpp.smsip", 0); + //} + + /* 3GPP TS 24.229 - 5.1.1.2 Initial registration */ + if(TSIP_DIALOG(self)->state ==tsip_initial){ + /* + g) the Supported header field containing the option-tag "path", and + 1) if GRUU is supported, the option-tag "gruu"; and + 2) if multiple registrations is supported, the option-tag "outbound". + */ + TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_SUPPORTED_VA_ARGS("path")); + //if(1==2/* gruu*/){ + //} + //else if(2 == 3 /* multiple registrations */){ + //} + } + + /* action parameters and payload */ + if(TSIP_DIALOG(self)->curr_action){ + const tsk_list_item_t* item; + tsk_list_foreach(item, TSIP_DIALOG(self)->curr_action->headers){ + TSIP_MESSAGE_ADD_HEADER(request, TSIP_HEADER_DUMMY_VA_ARGS(TSK_PARAM(item->data)->name, TSK_PARAM(item->data)->value)); + } + if(TSIP_DIALOG(self)->curr_action->payload){ + tsip_message_add_content(request, tsk_null, TSK_BUFFER_DATA(TSIP_DIALOG(self)->curr_action->payload), TSK_BUFFER_SIZE(TSIP_DIALOG(self)->curr_action->payload)); + } + } + + /* Create temorary SAs if initial register. */ + if(TSIP_DIALOG_GET_STACK(self)->security.secagree_mech){ + if(tsk_striequals(TSIP_DIALOG_GET_STACK(self)->security.secagree_mech, "ipsec-3gpp")){ + if(initial){ + tsip_transport_createTempSAs(TSIP_DIALOG_GET_STACK(self)->layer_transport); + } + else{ + AKA_CK_T ck; + AKA_IK_T ik; + tsip_dialog_getCKIK(TSIP_DIALOG(self), &ck, &ik); + tsip_transport_startSAs(TSIP_DIALOG_GET_STACK(self)->layer_transport, (const tipsec_key_t*)ik, (const tipsec_key_t*)ck); + } + } + } + + if(!(ret = tsip_dialog_request_send(TSIP_DIALOG(self), request))){ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_request_sent, "(un)REGISTER request successfully sent."); + } + else{ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_transport_error, "Transport error."); + } + + TSK_OBJECT_SAFE_FREE(request); + } + + return ret; +} + + + +// Send any response +int tsip_dialog_register_send_RESPONSE(tsip_dialog_register_t *self, const tsip_request_t* request, short code, const char* phrase) +{ + tsip_response_t *response; + int ret = -1; + + if(!self || !request){ + TSK_DEBUG_ERROR("Invalid parameter"); + return 1; + } + + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), code, phrase, request))){ + ret = tsip_dialog_response_send(TSIP_DIALOG(self), response); + TSK_OBJECT_SAFE_FREE(response); + } + return ret; +} + + + +/** + * @fn int tsip_dialog_register_OnTerminated(tsip_dialog_register_t *self) + * + * @brief Callback function called by the state machine manager to signal that the final state has been reached. + * + * @param [in,out] self The state machine owner. +**/ +int tsip_dialog_register_OnTerminated(tsip_dialog_register_t *self) +{ + TSK_DEBUG_INFO("=== REGISTER Dialog terminated ==="); + + /* Cleanup IPSec SAs */ + if(TSIP_DIALOG_GET_STACK(self)->security.secagree_mech && tsk_striequals(TSIP_DIALOG_GET_STACK(self)->security.secagree_mech, "ipsec-3gpp")){ + tsip_transport_cleanupSAs(TSIP_DIALOG_GET_STACK(self)->layer_transport); + } + /* Reset values to avoid issues when the session is reused */ + self->unregistering = tsk_false; + TSK_OBJECT_SAFE_FREE(self->last_iRegister); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL_2(self, tsip_event_code_dialog_terminated, + TSIP_DIALOG(self)->last_error.phrase ? TSIP_DIALOG(self)->last_error.phrase : "Dialog terminated", + TSIP_DIALOG(self)->last_error.message); + + /* Remove from the dialog layer. */ + return tsip_dialog_remove(TSIP_DIALOG(self)); +} + + + + +//======================================================== +// SIP dialog REGISTER object definition +// +static tsk_object_t* tsip_dialog_register_ctor(tsk_object_t * self, va_list * app) +{ + tsip_dialog_register_t *dialog = self; + if(dialog){ + tsip_ssession_t *ss = va_arg(*app, tsip_ssession_t *); + const char* call_id = va_arg(*app, const char *); + + /* Initialize base class */ + tsip_dialog_init(TSIP_DIALOG(self), tsip_dialog_REGISTER, call_id, ss, _fsm_state_Started, _fsm_state_Terminated); + + /* create FSM */ + TSIP_DIALOG_GET_FSM(self)->debug = DEBUG_STATE_MACHINE; + tsk_fsm_set_callback_terminated(TSIP_DIALOG_GET_FSM(self), TSK_FSM_ONTERMINATED_F(tsip_dialog_register_OnTerminated), (const void*)dialog); + + /* Initialize the class itself */ + tsip_dialog_register_init(self); + } + return self; +} + +static tsk_object_t* tsip_dialog_register_dtor(tsk_object_t * _self) +{ + tsip_dialog_register_t *self = _self; + if(self){ + + /* Cancel all timers */ + TSIP_DIALOG_TIMER_CANCEL(refresh); + TSIP_DIALOG_TIMER_CANCEL(shutdown); + + /* DeInitialize base class (will cancel all transactions) */ + tsip_dialog_deinit(TSIP_DIALOG(self)); + + // Delete resources + TSK_OBJECT_SAFE_FREE(self->last_iRegister); + + TSK_DEBUG_INFO("*** REGISTER Dialog destroyed ***"); + } + return self; +} + +static int tsip_dialog_register_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2) +{ + return tsip_dialog_cmp(obj1, obj2); +} + +static const tsk_object_def_t tsip_dialog_register_def_s = +{ + sizeof(tsip_dialog_register_t), + tsip_dialog_register_ctor, + tsip_dialog_register_dtor, + tsip_dialog_register_cmp, +}; +const tsk_object_def_t *tsip_dialog_register_def_t = &tsip_dialog_register_def_s; diff --git a/tinySIP/src/dialogs/tsip_dialog_register.client.c b/tinySIP/src/dialogs/tsip_dialog_register.client.c new file mode 100644 index 0000000..fa5e99b --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_register.client.c @@ -0,0 +1,424 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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_register.client.c + * @brief SIP dialog register (Client side). + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_register.h" +#include "tinysip/dialogs/tsip_dialog_register.common.h" + +#include "tinysip/transports/tsip_transport_layer.h" +#include "tinysip/transactions/tsip_transac_layer.h" + +#include "tinysip/headers/tsip_header_Path.h" +#include "tinysip/headers/tsip_header_Service_Route.h" +#include "tinysip/headers/tsip_header_P_Associated_URI.h" +#include "tinysip/headers/tsip_header_Min_Expires.h" + +#include "tinysip/parsers/tsip_parser_uri.h" + +/* ======================== external functions ======================== */ +extern int tsip_dialog_register_timer_callback(const tsip_dialog_register_t* self, tsk_timer_id_t timer_id); +extern int tsip_dialog_register_send_REGISTER(tsip_dialog_register_t *self, tsk_bool_t initial); + +/* ======================== transitions ======================== */ +static int tsip_dialog_register_Started_2_InProgress_X_oRegister(va_list *app); +static int tsip_dialog_register_InProgress_2_InProgress_X_1xx(va_list *app); +static int tsip_dialog_register_InProgress_2_Terminated_X_2xx(va_list *app); +static int tsip_dialog_register_InProgress_2_Connected_X_2xx(va_list *app); +static int tsip_dialog_register_InProgress_2_InProgress_X_401_407_421_494(va_list *app); +static int tsip_dialog_register_InProgress_2_InProgress_X_423(va_list *app); +static int tsip_dialog_register_InProgress_2_Terminated_X_300_to_699(va_list *app); +static int tsip_dialog_register_InProgress_2_Terminated_X_cancel(va_list *app); +static int tsip_dialog_register_Connected_2_InProgress_X_oRegister(va_list *app); + + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_client_unregistering(tsip_dialog_register_t* dialog, tsip_message_t* message) +{ + return !dialog->is_server && dialog->unregistering; +} +static tsk_bool_t _fsm_cond_client_registering(tsip_dialog_register_t* dialog, tsip_message_t* message) +{ + return !_fsm_cond_client_unregistering(dialog, message); +} + + +/** Initializes the dialog. + * + * @param [in,out] self The dialog to initialize. +**/ +int tsip_dialog_register_client_init(tsip_dialog_register_t *self) +{ + /* Initialize the state machine. */ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (REGISTER) -> InProgress + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_oREGISTER, _fsm_state_InProgress, tsip_dialog_register_Started_2_InProgress_X_oRegister, "tsip_dialog_register_Started_2_InProgress_X_oRegister"), + // Started -> (Any) -> Started + //TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Started, "tsip_dialog_register_Started_2_Started_X_any"), + + + /*======================= + * === InProgress === + */ + // InProgress -> (1xx) -> InProgress + TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_1xx, _fsm_state_InProgress, tsip_dialog_register_InProgress_2_InProgress_X_1xx, "tsip_dialog_register_InProgress_2_InProgress_X_1xx"), + // InProgress -> (2xx) -> Terminated + TSK_FSM_ADD(_fsm_state_InProgress, _fsm_action_2xx, _fsm_cond_client_unregistering, _fsm_state_Terminated, tsip_dialog_register_InProgress_2_Terminated_X_2xx, "tsip_dialog_register_InProgress_2_Terminated_X_2xx"), + // InProgress -> (2xx) -> Connected + TSK_FSM_ADD(_fsm_state_InProgress, _fsm_action_2xx, _fsm_cond_client_registering, _fsm_state_Connected, tsip_dialog_register_InProgress_2_Connected_X_2xx, "tsip_dialog_register_InProgress_2_Connected_X_2xx"), + // InProgress -> (401/407/421/494) -> InProgress + TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_401_407_421_494, _fsm_state_InProgress, tsip_dialog_register_InProgress_2_InProgress_X_401_407_421_494, "tsip_dialog_register_InProgress_2_InProgress_X_401_407_421_494"), + // InProgress -> (423) -> InProgress + TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_423, _fsm_state_InProgress, tsip_dialog_register_InProgress_2_InProgress_X_423, "tsip_dialog_register_InProgress_2_InProgress_X_423"), + // InProgress -> (300_to_699) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_300_to_699, _fsm_state_Terminated, tsip_dialog_register_InProgress_2_Terminated_X_300_to_699, "tsip_dialog_register_InProgress_2_Terminated_X_300_to_699"), + // InProgress -> (cancel) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_cancel, _fsm_state_Terminated, tsip_dialog_register_InProgress_2_Terminated_X_cancel, "tsip_dialog_register_InProgress_2_Terminated_X_cancel"), + // InProgress -> (hangup) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_hangup, _fsm_state_Terminated, tsk_null, "tsip_dialog_register_InProgress_2_Terminated_X_hangup"), + // InProgress -> (shutdown) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_InProgress, _fsm_action_shutdown, _fsm_state_Terminated, tsk_null, "tsip_dialog_register_InProgress_2_Terminated_X_shutdown"), + // InProgress -> (Any) -> InProgress + //TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_InProgress, "tsip_dialog_register_InProgress_2_InProgress_X_any"), + + + /*======================= + * === Connected === + */ + // Connected -> (register) -> InProgress [refresh case] + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_oREGISTER, _fsm_state_InProgress, tsip_dialog_register_Connected_2_InProgress_X_oRegister, "tsip_dialog_register_Connected_2_InProgress_X_oRegister"), + + + TSK_FSM_ADD_NULL()); + + return 0; +} + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +/* Started -> (REGISTER) -> InProgress +*/ +int tsip_dialog_register_Started_2_InProgress_X_oRegister(va_list *app) +{ + tsip_dialog_register_t *self; + + self = va_arg(*app, tsip_dialog_register_t *); + + TSIP_DIALOG(self)->running = tsk_true; + + /* alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connecting, "Dialog connecting"); + + return tsip_dialog_register_send_REGISTER(self, tsk_true); +} + +/* InProgress -> (1xx) -> InProgress +*/ +int tsip_dialog_register_InProgress_2_InProgress_X_1xx(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* Alert the user (session) */ + TSIP_DIALOG_REGISTER_SIGNAL(self, tsip_ao_register, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return tsip_dialog_update(TSIP_DIALOG(self), response); +} + +/* InProgress -> (2xx) -> Connected +*/ +//#include "tsk_thread.h" +int tsip_dialog_register_InProgress_2_Connected_X_2xx(va_list *app) +{ + int ret; + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + tsk_bool_t first_time_to_connect = (TSIP_DIALOG(self)->state == tsip_initial); + + /* - Set P-associated-uriS + * - Update service-routes + * - Update Pats + */ + { + tsk_size_t index; + const tsip_header_Path_t *hdr_Path; + const tsip_header_Service_Route_t *hdr_Service_Route; + const tsip_header_P_Associated_URI_t *hdr_P_Associated_URI_t; + tsip_uri_t *uri; + + /* To avoid memory leaks ==> delete all concerned objects (it worth nothing) */ + TSK_OBJECT_SAFE_FREE(TSIP_DIALOG_GET_STACK(self)->associated_uris); + TSK_OBJECT_SAFE_FREE(TSIP_DIALOG_GET_STACK(self)->service_routes); + TSK_OBJECT_SAFE_FREE(TSIP_DIALOG_GET_STACK(self)->paths); + + /* Associated URIs */ + for(index = 0; (hdr_P_Associated_URI_t = (const tsip_header_P_Associated_URI_t*)tsip_message_get_headerAt(response, tsip_htype_P_Associated_URI, index)); index++){ + if(!TSIP_DIALOG_GET_STACK(self)->associated_uris){ + TSIP_DIALOG_GET_STACK(self)->associated_uris = tsk_list_create(); + } + uri = tsk_object_ref(hdr_P_Associated_URI_t->uri); + tsk_list_push_back_data(TSIP_DIALOG_GET_STACK(self)->associated_uris, (void**)&uri); + } + + /* Service-Route (3GPP TS 24.229) + store the list of service route values contained in the Service-Route header field and bind the list to the contact + address used in registration, in order to build a proper preloaded Route header field value for new dialogs and + standalone transactions when using the respective contact address. + */ + for(index = 0; (hdr_Service_Route = (const tsip_header_Service_Route_t*)tsip_message_get_headerAt(response, tsip_htype_Service_Route, index)); index++){ + if(!TSIP_DIALOG_GET_STACK(self)->service_routes){ + TSIP_DIALOG_GET_STACK(self)->service_routes = tsk_list_create(); + } + uri = tsk_object_ref(hdr_Service_Route->uri); + tsk_list_push_back_data(TSIP_DIALOG_GET_STACK(self)->service_routes, (void**)&uri); + } + + /* Paths */ + for(index = 0; (hdr_Path = (const tsip_header_Path_t*)tsip_message_get_headerAt(response, tsip_htype_Path, index)); index++){ + if(TSIP_DIALOG_GET_STACK(self)->paths == 0){ + TSIP_DIALOG_GET_STACK(self)->paths = tsk_list_create(); + } + uri = tsk_object_ref(hdr_Path->uri); + tsk_list_push_back_data(TSIP_DIALOG_GET_STACK(self)->paths, (void**)&uri); + } + } + + /* 3GPP TS 24.229 - 5.1.1.2 Initial registration */ + if(first_time_to_connect){ + tsk_bool_t barred = tsk_true; + const tsk_list_item_t *item; + const tsip_uri_t *uri; + const tsip_uri_t *uri_first = 0; + + /* + b) store as the default public user identity the first URI on the list of URIs present in the P-Associated-URI header + field and bind it to the respective contact address of the UE and the associated set of security associations or TLS + session; + NOTE 4: When using the respective contact address and associated set of security associations or TLS session, the + UE can utilize additional URIs contained in the P-Associated-URI header field and bound it to the + respective contact address of the UE and the associated set of security associations or TLS session, e.g. for + application purposes. + c) treat the identity under registration as a barred public user identity, if it is not included in the P-Associated-URI + header field; + */ + tsk_list_foreach(item, TSIP_DIALOG_GET_STACK(self)->associated_uris){ + uri = item->data; + if(item == TSIP_DIALOG_GET_STACK(self)->associated_uris->head){ + uri_first = item->data; + } + if(!tsk_object_cmp(TSIP_DIALOG_GET_STACK(self)->identity.preferred, uri)){ + barred = 0; + break; + } + } + + if(barred && uri_first){ + TSK_OBJECT_SAFE_FREE(TSIP_DIALOG_GET_STACK(self)->identity.preferred); + TSIP_DIALOG_GET_STACK(self)->identity.preferred = tsk_object_ref((void*)uri_first); + } + } + + /* Update the dialog state */ + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + return ret; + } + + /* Reset current action */ + tsip_dialog_set_curr_action(TSIP_DIALOG(self), tsk_null); + + /* Request timeout for dialog refresh (re-registration). */ + self->timerrefresh.timeout = tsip_dialog_get_newdelay(TSIP_DIALOG(self), response); + TSIP_DIALOG_REGISTER_TIMER_SCHEDULE(refresh); + + /* Alert the user (session) */ + TSIP_DIALOG_REGISTER_SIGNAL(self, tsip_ao_register, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + /* Alert the user (dialog) */ + if(first_time_to_connect){ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connected, "Dialog connected"); + } + + return ret; +} + +/* InProgress -> (2xx) -> Terminated +*/ +int tsip_dialog_register_InProgress_2_Terminated_X_2xx(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* save last error */ + tsip_dialog_set_lasterror_2(TSIP_DIALOG(self), TSIP_RESPONSE_PHRASE(response), TSIP_RESPONSE_CODE(response), response); + + /* Alert the user */ + TSIP_DIALOG_REGISTER_SIGNAL(self, tsip_ao_unregister, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return 0; +} + +/* InProgress --> (401/407/421/494) --> InProgress +*/ +int tsip_dialog_register_InProgress_2_InProgress_X_401_407_421_494(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + int ret; + + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + /* Alert the user. */ + TSIP_DIALOG_REGISTER_SIGNAL(self, self->unregistering ? tsip_ao_unregister : tsip_ao_register, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + /* set last error (or info) */ + tsip_dialog_set_lasterror(TSIP_DIALOG(self), "Authentication failed", TSIP_RESPONSE_CODE(response)); + + return ret; + } + + /* Ensure IPSec SAs */ + if(TSIP_DIALOG_GET_STACK(self)->security.secagree_mech && tsk_striequals(TSIP_DIALOG_GET_STACK(self)->security.secagree_mech, "ipsec-3gpp")){ + tsip_transport_ensureTempSAs(TSIP_DIALOG_GET_STACK(self)->layer_transport, response, TSIP_DIALOG(self)->expires); + } + + return tsip_dialog_register_send_REGISTER(self, tsk_false); +} + +/* InProgress -> (423) -> InProgress +*/ +int tsip_dialog_register_InProgress_2_InProgress_X_423(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + const tsip_message_t *message = va_arg(*app, const tsip_message_t *); + + tsip_header_Min_Expires_t *hdr; + int ret = 0; + + /* + RFC 3261 - 10.2.8 Error Responses + + If a UA receives a 423 (Interval Too Brief) response, it MAY retry + the registration after making the expiration interval of all contact + addresses in the REGISTER request equal to or greater than the + expiration interval within the Min-Expires header field of the 423 + (Interval Too Brief) response. + */ + hdr = (tsip_header_Min_Expires_t*)tsip_message_get_header(message, tsip_htype_Min_Expires); + if(hdr){ + TSIP_DIALOG(self)->expires = TSK_TIME_S_2_MS(hdr->value); + + if(tsk_striequals(TSIP_DIALOG_GET_STACK(self)->security.secagree_mech, "ipsec-3gpp")){ + tsip_transport_cleanupSAs(TSIP_DIALOG_GET_STACK(self)->layer_transport); + ret = tsip_dialog_register_send_REGISTER(self, tsk_true); + } + else{ + ret = tsip_dialog_register_send_REGISTER(self, tsk_false); + } + } + else{ + TSK_DEBUG_ERROR("Missing header: Min_Expires"); + ret = -1; + } + + return ret; +} + +/* InProgress -> (300-699) -> Terminated +*/ +int tsip_dialog_register_InProgress_2_Terminated_X_300_to_699(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* save last error */ + tsip_dialog_set_lasterror_2(TSIP_DIALOG(self), TSIP_RESPONSE_PHRASE(response), TSIP_RESPONSE_CODE(response), response); + + /* Alert the user. */ + TSIP_DIALOG_REGISTER_SIGNAL(self, self->unregistering ? tsip_ao_unregister : tsip_ao_register, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return 0; +} + +/* InProgress -> (cancel) -> Terminated +*/ +int tsip_dialog_register_InProgress_2_Terminated_X_cancel(va_list *app) +{ + int ret; + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + /* const tsip_message_t *message = va_arg(*app, const tsip_message_t *); */ + + /* Cancel all transactions associated to this dialog (will also be done when the dialog is destroyed (worth nothing)) */ + ret = tsip_transac_layer_cancel_by_dialog(TSIP_DIALOG_GET_STACK(self)->layer_transac, TSIP_DIALOG(self)); + + /* RFC 3261 - 9.1 Client Behavior + A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + */ + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_request_cancelled, "Registration cancelled"); + + return ret; +} + +/* Connected -> (REGISTER) -> InProgress +*/ +int tsip_dialog_register_Connected_2_InProgress_X_oRegister(va_list *app) +{ + tsip_dialog_register_t *self; + + self = va_arg(*app, tsip_dialog_register_t *); + + return tsip_dialog_register_send_REGISTER(self, tsk_true); +} + + + + + + + + + + + + + + + + + + diff --git a/tinySIP/src/dialogs/tsip_dialog_register.server.c b/tinySIP/src/dialogs/tsip_dialog_register.server.c new file mode 100644 index 0000000..de912ff --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_register.server.c @@ -0,0 +1,237 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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 publishd 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. +* +*/ +#include "tinysip/dialogs/tsip_dialog_register.h" +#include "tinysip/dialogs/tsip_dialog_register.common.h" + + +/* ======================== external functions ======================== */ +extern int tsip_dialog_register_send_RESPONSE(tsip_dialog_register_t *self, const tsip_request_t* request, short code, const char* phrase); + +/* ======================== transitions ======================== */ +static int s0000_Started_2_Terminated_X_iREGISTER(va_list *app); +static int s0000_Started_2_Incoming_X_iREGISTER(va_list *app); +static int s0000_Incoming_2_Connected_X_Accept(va_list *app); +static int s0000_Incoming_2_Terminated_X_Terminates(va_list *app); +static int s0000_Connected_2_Connected_X_iREGISTER(va_list *app); +static int s0000_Connected_2_Terminated_X_iREGISTER(va_list *app); + + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_not_served_here(tsip_dialog_register_t* dialog, tsip_message_t* message) +{ +#if 0 // FIXME: Have to disabled only when in proxy mode + if(message && TSIP_REQUEST_IS_REGISTER(message)){ + if(tsk_object_cmp(TSIP_DIALOG_GET_STACK(dialog)->network.realm, message->line.request.uri) != 0){ + tsip_dialog_register_send_RESPONSE(dialog, TSIP_MESSAGE_AS_REQUEST(message), 404, "Domain not served here"); + return tsk_true; + } + } +#endif + return tsk_false; +} +static tsk_bool_t _fsm_cond_server_unregistering(tsip_dialog_register_t* dialog, tsip_message_t* message) +{ + if(message && dialog->is_server){ + int64_t expires = tsip_message_getExpires(message); + dialog->unregistering = (expires == 0); + return dialog->unregistering; + } + return tsk_false; +} +static tsk_bool_t _fsm_cond_server_registering(tsip_dialog_register_t* dialog, tsip_message_t* message) +{ + return !_fsm_cond_server_unregistering(dialog, message); +} + + +int tsip_dialog_register_server_init(tsip_dialog_register_t *self) +{ + return tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (Domain Not Served here) -> Terminated + TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iREGISTER, _fsm_cond_not_served_here, _fsm_state_Terminated, s0000_Started_2_Terminated_X_iREGISTER, "s0000_Started_2_Terminated_X_iREGISTER"), + // Started -> (All is OK and we are not unRegistering) -> Trying + TSK_FSM_ADD(_fsm_state_Started, _fsm_action_iREGISTER, _fsm_cond_server_registering, _fsm_state_Incoming, s0000_Started_2_Incoming_X_iREGISTER, "s0000_Started_2_Incoming_X_iREGISTER"), + + /*======================= + * === Incoming === + */ + // Incoming -> (Accept) -> Connected + TSK_FSM_ADD_ALWAYS(_fsm_state_Incoming, _fsm_action_accept, _fsm_state_Connected, s0000_Incoming_2_Connected_X_Accept, "s0000_Incoming_2_Connected_X_Accept"), + // Incoming -> (iRegister) -> Incoming + TSK_FSM_ADD(_fsm_state_Incoming, _fsm_action_iREGISTER, _fsm_cond_server_registering, _fsm_state_Incoming, tsk_null, "s0000_Incoming_2_Incoming_X_iREGISTER"), + // Incoming -> (iRegister, expires=0) -> Terminated + TSK_FSM_ADD(_fsm_state_Incoming, _fsm_action_iREGISTER, _fsm_cond_server_unregistering, _fsm_state_Terminated, tsk_null, "s0000_Incoming_2_Terminated_X_iREGISTER"), + // Incoming -> (Reject) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Incoming, _fsm_action_reject, _fsm_state_Terminated, s0000_Incoming_2_Terminated_X_Terminates, "s0000_Incoming_2_Terminated_X_Terminates"), + // Incoming -> (Hangup) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Incoming, _fsm_action_hangup, _fsm_state_Terminated, s0000_Incoming_2_Terminated_X_Terminates, "s0000_Incoming_2_Terminated_X_Terminates"), + + /*======================= + * === Connected === + */ + // Connected -> (Register) -> Connected + TSK_FSM_ADD(_fsm_state_Connected, _fsm_action_iREGISTER, _fsm_cond_server_registering, _fsm_state_Connected, s0000_Connected_2_Connected_X_iREGISTER, "s0000_Connected_2_Connected_X_iREGISTER"), + // Connected -> (UnRegister) -> Terminated + TSK_FSM_ADD(_fsm_state_Connected, _fsm_action_iREGISTER, _fsm_cond_server_unregistering, _fsm_state_Terminated, s0000_Connected_2_Terminated_X_iREGISTER, "s0000_Connected_2_Terminated_X_iREGISTER"), + // Connected -> (TimedOut) -> Terminated + // Connected -> (Refresh OK) -> Connected + // Connected -> (Refresh NOK) -> Terminated + + TSK_FSM_ADD_NULL()); +} + + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +/* Started -> (Failure) -> Terminated +*/ +int s0000_Started_2_Terminated_X_iREGISTER(va_list *app) +{ + return 0; + /*tsip_dialog_register_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_register_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + TSIP_DIALOG(self)->running = tsk_true; + tsip_dialog_set_curr_action(TSIP_DIALOG(self), action); + + // alert the user + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connecting, "Dialog connecting"); + + return send_REGISTER(self, tsk_true);*/ +} + +/* Started -> (All is OK and we are Registering) -> Incoming +*/ +int s0000_Started_2_Incoming_X_iREGISTER(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + + // set as server side dialog + TSIP_DIALOG_REGISTER(self)->is_server = tsk_true; + + /* update last REGISTER */ + TSK_OBJECT_SAFE_FREE(self->last_iRegister); + self->last_iRegister = tsk_object_ref(request); + + /* alert the user (session) */ + TSIP_DIALOG_REGISTER_SIGNAL(self, tsip_i_newreg, + tsip_event_code_dialog_request_incoming, "Incoming New Register", request); + + return 0; +} + +/* Incoming -> (Accept) -> Connected +*/ +int s0000_Incoming_2_Connected_X_Accept(va_list *app) +{ + int ret; + + tsip_dialog_register_t *self; + + self = va_arg(*app, tsip_dialog_register_t *); + + /* send 2xx OK */ + if((ret = tsip_dialog_register_send_RESPONSE(self, self->last_iRegister, 200, "OK"))){ + return ret; + } + + /* alert the user (dialog) */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connected, "Dialog connected"); + + return ret; +} + +/* Incoming -> (Reject) -> Terminated +*/ +int s0000_Incoming_2_Terminated_X_Terminates(va_list *app) +{ + int ret; + short code; + const char* phrase; + char* reason = tsk_null; + + tsip_dialog_register_t *self; + const tsip_action_t* action; + + self = va_arg(*app, tsip_dialog_register_t *); + va_arg(*app, const tsip_message_t *); + action = va_arg(*app, const tsip_action_t *); + + /* Send Reject */ + code = action->line_resp.code>=300 ? action->line_resp.code : 600; + phrase = action->line_resp.phrase ? action->line_resp.phrase : "Not Supported"; + tsk_sprintf(&reason, "SIP; cause=%hi; text=\"%s\"", code, phrase); + ret = tsip_dialog_register_send_RESPONSE(self, self->last_iRegister, code, phrase); + TSK_FREE(reason); + + return ret; +} + +/* Connected -> (register) -> Connected +*/ +static int s0000_Connected_2_Connected_X_iREGISTER(va_list *app) +{ + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + const tsip_request_t *request = va_arg(*app, const tsip_request_t *); + + TSK_OBJECT_SAFE_FREE(self->last_iRegister); + self->last_iRegister = tsk_object_ref((tsk_object_t*)request); + + /* send 2xx OK */ + return tsip_dialog_register_send_RESPONSE(self, self->last_iRegister, 200, "OK"); +} + +/* Connected -> (Unregister) -> Terminated +*/ +int s0000_Connected_2_Terminated_X_iREGISTER(va_list *app) +{ + int ret; + tsip_dialog_register_t *self = va_arg(*app, tsip_dialog_register_t *); + tsip_request_t *request = va_arg(*app, tsip_request_t *); + + /* update last REGISTER */ + TSK_OBJECT_SAFE_FREE(self->last_iRegister); + self->last_iRegister = tsk_object_ref(request); + + /* send 2xx OK */ + if((ret = tsip_dialog_register_send_RESPONSE(self, self->last_iRegister, 200, "OK"))){ + return ret; + } + + /* alert the user (session) */ + TSIP_DIALOG_REGISTER_SIGNAL(self, tsip_i_unregister, + tsip_event_code_dialog_request_incoming, "Incoming Request", request); + + return 0; +}
\ No newline at end of file diff --git a/tinySIP/src/dialogs/tsip_dialog_subscribe.client.c b/tinySIP/src/dialogs/tsip_dialog_subscribe.client.c new file mode 100644 index 0000000..28f34e4 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_subscribe.client.c @@ -0,0 +1,764 @@ +/* +* Copyright (C) 2010-2011 Mamadou Diop. +* +* Contact: Mamadou Diop <diopmamadou(at)doubango[dot]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_subscribe.client.c + * @brief SIP dialog SUBSCRIBE (Client side) as per RFC 3265. + * + * @author Mamadou Diop <diopmamadou(at)doubango[dot]org> + * + + */ +#include "tinysip/dialogs/tsip_dialog_subscribe.h" + +#include "tinysip/headers/tsip_header_Dummy.h" +#include "tinysip/headers/tsip_header_Event.h" +#include "tinysip/headers/tsip_header_Min_Expires.h" +#include "tinysip/headers/tsip_header_Subscription_State.h" + +#include "tinysip/transactions/tsip_transac_layer.h" + +#include "tinysip/api/tsip_api_subscribe.h" + +#include "tsk_debug.h" +#include "tsk_time.h" + + + +#define DEBUG_STATE_MACHINE 0 +#define TSIP_DIALOG_SUBSCRIBE_TIMER_SCHEDULE(TX) TSIP_DIALOG_TIMER_SCHEDULE(subscribe, TX) +#define TSIP_DIALOG_SUBSCRIBE_SIGNAL(self, type, code, phrase, message) \ + tsip_subscribe_event_signal(type, TSIP_DIALOG(self)->ss, code, phrase, message) + +/* ======================== internal functions ======================== */ +static int process_i_notify(tsip_dialog_subscribe_t *self, const tsip_request_t* notify); +static int send_SUBSCRIBE(tsip_dialog_subscribe_t *self); +static int send_200NOTIFY(tsip_dialog_subscribe_t *self, const tsip_request_t* request); +static int tsip_dialog_subscribe_OnTerminated(tsip_dialog_subscribe_t *self); + +/* ======================== transitions ======================== */ +static int tsip_dialog_subscribe_Started_2_Trying_X_subscribe(va_list *app); +static int tsip_dialog_subscribe_Trying_2_Trying_X_1xx(va_list *app); +static int tsip_dialog_subscribe_Trying_2_Terminated_X_2xx(va_list *app); +static int tsip_dialog_subscribe_Trying_2_Connected_X_2xx(va_list *app); +static int tsip_dialog_subscribe_Trying_2_Trying_X_401_407_421_494(va_list *app); +static int tsip_dialog_subscribe_Trying_2_Trying_X_423(va_list *app); +static int tsip_dialog_subscribe_Trying_2_Terminated_X_300_to_699(va_list *app); +static int tsip_dialog_subscribe_Trying_2_Terminated_X_cancel(va_list *app); +static int tsip_dialog_subscribe_Trying_2_Trying_X_NOTIFY(va_list *app); +static int tsip_dialog_subscribe_Connected_2_Trying_X_unsubscribe(va_list *app); +static int tsip_dialog_subscribe_Connected_2_Trying_X_subscribe(va_list *app); +static int tsip_dialog_subscribe_Connected_2_Connected_X_NOTIFY(va_list *app); +static int tsip_dialog_subscribe_Connected_2_Terminated_X_NOTIFY(va_list *app); +static int tsip_dialog_subscribe_Any_2_Trying_X_hangup(va_list *app); +static int tsip_dialog_subscribe_Any_2_Trying_X_shutdown(va_list *app); +static int tsip_dialog_subscribe_Any_2_Terminated_X_transportError(va_list *app); +static int tsip_dialog_subscribe_Any_2_Terminated_X_Error(va_list *app); + + +/* ======================== conds ======================== */ +static tsk_bool_t _fsm_cond_unsubscribing(tsip_dialog_subscribe_t* dialog, tsip_message_t* message) +{ + return dialog->unsubscribing; +} +static tsk_bool_t _fsm_cond_subscribing(tsip_dialog_subscribe_t* dialog, tsip_message_t* message) +{ + return !_fsm_cond_unsubscribing(dialog, message); +} + +static tsk_bool_t _fsm_cond_notify_terminated(tsip_dialog_subscribe_t* dialog, tsip_message_t* message) +{ + const tsip_header_Subscription_State_t *hdr_state; + if((hdr_state = (const tsip_header_Subscription_State_t*)tsip_message_get_header(message, tsip_htype_Subscription_State))) + { + return tsk_striequals(hdr_state->state, "terminated") && + (hdr_state->expires < 0 || tsk_striequals(hdr_state->reason, "rejected") || tsk_striequals(hdr_state->reason, "noresource")); + } + return tsk_false; +} +static tsk_bool_t _fsm_cond_notify_not_terminated(tsip_dialog_subscribe_t* dialog, tsip_message_t* message) +{ + return !_fsm_cond_notify_terminated(dialog, message); +} + +static tsk_bool_t _fsm_cond_silent_hangup(tsip_dialog_subscribe_t* dialog, tsip_message_t* message) +{ + return TSIP_DIALOG(dialog)->ss->silent_hangup; +} +static tsk_bool_t _fsm_cond_not_silent_hangup(tsip_dialog_subscribe_t* dialog, tsip_message_t* message) +{ + return !TSIP_DIALOG(dialog)->ss->silent_hangup; +} +#define _fsm_cond_silent_shutdown _fsm_cond_silent_hangup +#define _fsm_cond_not_silent_shutdown _fsm_cond_not_silent_hangup + +/* ======================== actions ======================== */ +typedef enum _fsm_action_e +{ + _fsm_action_subscribe = tsip_atype_subscribe, + _fsm_action_hangup = tsip_atype_hangup, + _fsm_action_cancel = tsip_atype_cancel, + _fsm_action_shutdown = tsip_atype_shutdown, + _fsm_action_transporterror = tsip_atype_transport_error, + + _fsm_action_1xx = 0xFF, + _fsm_action_2xx, + _fsm_action_401_407_421_494, + _fsm_action_423, + _fsm_action_300_to_699, + _fsm_action_shutdown_timedout, /* Any -> Terminated */ + _fsm_action_notify, + _fsm_action_error, +} +_fsm_action_t; + +/* ======================== states ======================== */ +typedef enum _fsm_state_e +{ + _fsm_state_Started, + _fsm_state_Trying, + _fsm_state_Connected, + _fsm_state_Terminated +} +_fsm_state_t; + +/** + * Callback function called to alert the dialog for new events from the transaction/transport layers. + * + * @param [in,out] self A reference to the dialog. + * @param type The event type. + * @param [in,out] msg The incoming SIP/IMS message. + * + * @return Zero if succeed and non-zero error code otherwise. +**/ +int tsip_dialog_subscribe_event_callback(const tsip_dialog_subscribe_t *self, tsip_dialog_event_type_t type, const tsip_message_t *msg) +{ + int ret = -1; + + switch(type) + { + case tsip_dialog_i_msg: + { + if(msg && TSIP_MESSAGE_IS_RESPONSE(msg)){ + // + // RESPONSE + // + const tsip_action_t* action = tsip_dialog_keep_action(TSIP_DIALOG(self), msg) ? TSIP_DIALOG(self)->curr_action : tsk_null; + if(TSIP_RESPONSE_IS_1XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_1xx, msg, action); + } + else if(TSIP_RESPONSE_IS_2XX(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_2xx, msg, action); + } + else if(TSIP_RESPONSE_IS(msg,401) || TSIP_RESPONSE_IS(msg,407) || TSIP_RESPONSE_IS(msg,421) || TSIP_RESPONSE_IS(msg,494)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_401_407_421_494, msg, action); + } + else if(TSIP_RESPONSE_IS(msg,423)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_423, msg, action); + } + else{ + // Alert User + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_error, msg, action); + /* TSK_DEBUG_WARN("Not supported status code: %d", TSIP_RESPONSE_CODE(msg)); */ + } + } + else{ + // + // REQUEST + // + if(TSIP_REQUEST_IS_NOTIFY(msg)){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_notify, msg, tsk_null); + } + } + break; + } + + case tsip_dialog_canceled: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_cancel, msg, tsk_null); + break; + } + + case tsip_dialog_terminated: + case tsip_dialog_timedout: + case tsip_dialog_error: + case tsip_dialog_transport_error: + { + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_transporterror, msg, tsk_null); + break; + } + + default: break; + } + + return ret; +} + +/** Timer manager callback. + * + * @param [in,out] self The owner of the signaled timer. + * @param timer_id The identifier of the signaled timer. + * + * @return Zero if succeed and non-zero error code otherwise. +**/ +int tsip_dialog_subscribe_timer_callback(const tsip_dialog_subscribe_t* self, tsk_timer_id_t timer_id) +{ + int ret = -1; + + if(self) + { + if(timer_id == self->timerrefresh.id){ + tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_subscribe, tsk_null, tsk_null); + ret = 0; + } + else if(timer_id == self->timershutdown.id){ + ret = tsip_dialog_fsm_act(TSIP_DIALOG(self), _fsm_action_shutdown_timedout, tsk_null, tsk_null); + } + } + return ret; +} + +tsip_dialog_subscribe_t* tsip_dialog_subscribe_create(const tsip_ssession_handle_t* ss) +{ + return tsk_object_new(tsip_dialog_subscribe_def_t, ss); +} + +/** Initializes the dialog. + * + * @param [in,out] self The dialog to initialize. +**/ +int tsip_dialog_subscribe_init(tsip_dialog_subscribe_t *self) +{ + /* Initialize the State Machine. */ + tsk_fsm_set(TSIP_DIALOG_GET_FSM(self), + + /*======================= + * === Started === + */ + // Started -> (Send) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Started, _fsm_action_subscribe, _fsm_state_Trying, tsip_dialog_subscribe_Started_2_Trying_X_subscribe, "tsip_dialog_subscribe_Started_2_Trying_X_subscribe"), + // Started -> (Any) -> Started + TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Started, "tsip_dialog_subscribe_Started_2_Started_X_any"), + + + /*======================= + * === Trying === + */ + // Trying -> (1xx) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_1xx, _fsm_state_Trying, tsip_dialog_subscribe_Trying_2_Trying_X_1xx, "tsip_dialog_subscribe_Trying_2_Trying_X_1xx"), + // Trying -> (2xx) -> Terminated + TSK_FSM_ADD(_fsm_state_Trying, _fsm_action_2xx, _fsm_cond_unsubscribing, _fsm_state_Terminated, tsip_dialog_subscribe_Trying_2_Terminated_X_2xx, "tsip_dialog_subscribe_Trying_2_Terminated_X_2xx"), + // Trying -> (2xx) -> Connected + TSK_FSM_ADD(_fsm_state_Trying, _fsm_action_2xx, _fsm_cond_subscribing, _fsm_state_Connected, tsip_dialog_subscribe_Trying_2_Connected_X_2xx, "tsip_dialog_subscribe_Trying_2_Connected_X_2xx"), + // Trying -> (401/407/421/494) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_401_407_421_494, _fsm_state_Trying, tsip_dialog_subscribe_Trying_2_Trying_X_401_407_421_494, "tsip_dialog_subscribe_Trying_2_Trying_X_401_407_421_494"), + // Trying -> (423) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_423, _fsm_state_Trying, tsip_dialog_subscribe_Trying_2_Trying_X_423, "tsip_dialog_subscribe_Trying_2_Trying_X_423"), + // Trying -> (300_to_699) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_300_to_699, _fsm_state_Terminated, tsip_dialog_subscribe_Trying_2_Terminated_X_300_to_699, "tsip_dialog_subscribe_Trying_2_Terminated_X_300_to_699"), + // Trying -> (cancel) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_cancel, _fsm_state_Terminated, tsip_dialog_subscribe_Trying_2_Terminated_X_cancel, "tsip_dialog_subscribe_Trying_2_Terminated_X_cancel"), + // Trying -> (Notify) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_notify, _fsm_state_Trying, tsip_dialog_subscribe_Trying_2_Trying_X_NOTIFY, "tsip_dialog_subscribe_Trying_2_Trying_X_NOTIFY"), + // Trying -> (hangup) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_hangup, _fsm_state_Terminated, tsk_null, "tsip_dialog_subscribe_Trying_2_Terminated_X_hangup"), + // Trying -> (shutdown) -> Terminated + TSK_FSM_ADD_ALWAYS(_fsm_state_Trying, _fsm_action_shutdown, _fsm_state_Terminated, tsk_null, "tsip_dialog_subscribe_Trying_2_Terminated_X_shutdown"), + // Trying -> (Any) -> Trying + //TSK_FSM_ADD_ALWAYS_NOTHING(_fsm_state_Trying, "tsip_dialog_subscribe_Trying_2_Trying_X_any"), + + + /*======================= + * === Connected === + */ + // Connected -> (SUBSCRIBE) -> Trying + TSK_FSM_ADD_ALWAYS(_fsm_state_Connected, _fsm_action_subscribe, _fsm_state_Trying, tsip_dialog_subscribe_Connected_2_Trying_X_subscribe, "tsip_dialog_subscribe_Connected_2_Trying_X_subscribe"), + // Connected -> (NOTIFY) -> Connected + TSK_FSM_ADD(_fsm_state_Connected, _fsm_action_notify, _fsm_cond_notify_not_terminated, _fsm_state_Connected, tsip_dialog_subscribe_Connected_2_Connected_X_NOTIFY, "tsip_dialog_subscribe_Connected_2_Connected_X_NOTIFY"), + // Connected -> (NOTIFY) -> Terminated + TSK_FSM_ADD(_fsm_state_Connected, _fsm_action_notify, _fsm_cond_notify_terminated, _fsm_state_Terminated, tsip_dialog_subscribe_Connected_2_Terminated_X_NOTIFY, "tsip_dialog_subscribe_Connected_2_Terminated_X_NOTIFY"), + + /*======================= + * === Any === + */ + // Any -> (hangup) -> Trying + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_hangup, _fsm_cond_not_silent_hangup, _fsm_state_Trying, tsip_dialog_subscribe_Any_2_Trying_X_hangup, "tsip_dialog_subscribe_Any_2_Trying_X_hangup"), + // Any -> (silenthangup) -> Terminated + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_hangup, _fsm_cond_silent_hangup, _fsm_state_Terminated, tsk_null, "tsip_dialog_subscribe_Any_2_Trying_X_silenthangup"), + // Any -> (shutdown) -> Trying + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_shutdown, _fsm_cond_not_silent_shutdown, _fsm_state_Trying, tsip_dialog_subscribe_Any_2_Trying_X_shutdown, "tsip_dialog_subscribe_Any_2_Trying_X_shutdown"), + // Any -> (silentshutdown) -> Terminated + TSK_FSM_ADD(tsk_fsm_state_any, _fsm_action_shutdown, _fsm_cond_silent_shutdown, _fsm_state_Terminated, tsk_null, "tsip_dialog_subscribe_Any_2_Trying_X_silentshutdown"), + // Any -> (shutdown timedout) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_shutdown_timedout, _fsm_state_Terminated, tsk_null, "tsip_dialog_subscribe_shutdown_timedout"), + // Any -> (transport error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_transporterror, _fsm_state_Terminated, tsip_dialog_subscribe_Any_2_Terminated_X_transportError, "tsip_dialog_subscribe_Any_2_Terminated_X_transportError"), + // Any -> (error) -> Terminated + TSK_FSM_ADD_ALWAYS(tsk_fsm_state_any, _fsm_action_error, _fsm_state_Terminated, tsip_dialog_subscribe_Any_2_Terminated_X_Error, "tsip_dialog_subscribe_Any_2_Terminated_X_Error"), + + TSK_FSM_ADD_NULL()); + + /* Sets callback function */ + TSIP_DIALOG(self)->callback = TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_subscribe_event_callback); + + /* Timers */ + self->timerrefresh.id = TSK_INVALID_TIMER_ID; + self->timerrefresh.timeout = TSIP_DIALOG(self)->expires; + self->timershutdown.id = TSK_INVALID_TIMER_ID; + self->timershutdown.timeout = TSIP_DIALOG_SHUTDOWN_TIMEOUT; + + return 0; +} + + +//-------------------------------------------------------- +// == STATE MACHINE BEGIN == +//-------------------------------------------------------- + +/* Started -> (SUBSCRIBE) -> Trying +*/ +int tsip_dialog_subscribe_Started_2_Trying_X_subscribe(va_list *app) +{ + tsip_dialog_subscribe_t *self; + + self = va_arg(*app, tsip_dialog_subscribe_t *); + + TSIP_DIALOG(self)->running = tsk_true; + + /* alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connecting, "Dialog connecting"); + + return send_SUBSCRIBE(self); +} + +/* Trying -> (1xx) -> Trying +*/ +int tsip_dialog_subscribe_Trying_2_Trying_X_1xx(va_list *app) +{ + /*tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *);*/ + /*const tsip_response_t *response = va_arg(*app, const tsip_response_t *);*/ + + return 0; +} + +/* Trying -> (2xx) -> Terminated +*/ +int tsip_dialog_subscribe_Trying_2_Terminated_X_2xx(va_list *app) +{ + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* Alert the user. */ + TSIP_DIALOG_SUBSCRIBE_SIGNAL(self, self->unsubscribing ? tsip_ao_unsubscribe : tsip_ao_subscribe, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return 0; +} + +/* Trying -> (2xx) -> Connected +*/ +int tsip_dialog_subscribe_Trying_2_Connected_X_2xx(va_list *app) +{ + int ret; + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + tsk_bool_t first_time_to_connect = (TSIP_DIALOG(self)->state == tsip_initial); + + /* Update the dialog state. */ + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + return ret; + } + + /* Alert the user(session) */ + TSIP_DIALOG_SUBSCRIBE_SIGNAL(self, self->unsubscribing ? tsip_ao_unsubscribe : tsip_ao_subscribe, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + /* Alert the user(dialog) */ + if(first_time_to_connect){ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_connected, "Dialog connected"); + } + + /* Reset current action */ + tsip_dialog_set_curr_action(TSIP_DIALOG(self), tsk_null); + + /* Request timeout for dialog refresh (re-subscribtion). */ + self->timerrefresh.timeout = tsip_dialog_get_newdelay(TSIP_DIALOG(self), response); + TSIP_DIALOG_SUBSCRIBE_TIMER_SCHEDULE(refresh); + + return 0; +} + +/* Trying -> (401/407/421/494) -> Trying +*/ +int tsip_dialog_subscribe_Trying_2_Trying_X_401_407_421_494(va_list *app) +{ + int ret; + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + if((ret = tsip_dialog_update(TSIP_DIALOG(self), response))){ + /* Alert the user. */ + TSIP_DIALOG_SUBSCRIBE_SIGNAL(self, self->unsubscribing ? tsip_ao_unsubscribe : tsip_ao_subscribe, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return ret; + } + + return send_SUBSCRIBE(self); +} + +/* Trying -> (423) -> Trying +*/ +int tsip_dialog_subscribe_Trying_2_Trying_X_423(va_list *app) +{ + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + tsip_header_Min_Expires_t *hdr; + + /* + RFC 3261 - 10.2.8 Error Responses + + If a UA receives a 423 (Interval Too Brief) response, it MAY retry + the registration after making the expiration interval of all contact + addresses in the SUBSCRIBE request equal to or greater than the + expiration interval within the Min-Expires header field of the 423 + (Interval Too Brief) response. + */ + hdr = (tsip_header_Min_Expires_t*)tsip_message_get_header(response, tsip_htype_Min_Expires); + if(hdr){ + TSIP_DIALOG(self)->expires = TSK_TIME_S_2_MS(hdr->value); + send_SUBSCRIBE(self); + } + else{ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_message_error, "Received invalid SIP response."); + + return -1; + } + + return 0; +} + +/* Trying -> (300-699) -> Terminated +*/ +int tsip_dialog_subscribe_Trying_2_Terminated_X_300_to_699(va_list *app) +{ + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + /* save last error */ + tsip_dialog_set_lasterror_2(TSIP_DIALOG(self), TSIP_RESPONSE_PHRASE(response), TSIP_RESPONSE_CODE(response), response); + + /* alert the user */ + TSIP_DIALOG_SUBSCRIBE_SIGNAL(self, self->unsubscribing ? tsip_ao_unsubscribe : tsip_ao_subscribe, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + + return 0; +} + +/* Trying -> (cancel) -> Terminated +*/ +int tsip_dialog_subscribe_Trying_2_Terminated_X_cancel(va_list *app) +{ + int ret; + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + /* const tsip_response_t *response = va_arg(*app, const tsip_response_t *); */ + + /* Cancel all transactions associated to this dialog (will also be done when the dialog is destroyed (worth nothing)) */ + ret = tsip_transac_layer_cancel_by_dialog(TSIP_DIALOG_GET_STACK(self)->layer_transac, TSIP_DIALOG(self)); + + /* RFC 3261 - 9.1 Client Behavior + A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. + */ + + /* alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_request_cancelled, "Subscription cancelled"); + + return ret; +} + +/* Trying -> (NOTIFY) -> Trying +*/ +int tsip_dialog_subscribe_Trying_2_Trying_X_NOTIFY(va_list *app) +{ + int ret; + + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + const tsip_request_t *request = va_arg(*app, const tsip_request_t *); + + ret = send_200NOTIFY(self, request); + ret = process_i_notify(self, request); + + return ret; +} + +/* Connected -> (SUBSCRIBE) -> Trying +*/ +int tsip_dialog_subscribe_Connected_2_Trying_X_subscribe(va_list *app) +{ + tsip_dialog_subscribe_t *self; + + self = va_arg(*app, tsip_dialog_subscribe_t *); + + return send_SUBSCRIBE(self); +} + +/* Connected -> (NOTIFY) -> Connected +*/ +int tsip_dialog_subscribe_Connected_2_Connected_X_NOTIFY(va_list *app) +{ + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + const tsip_request_t *request = va_arg(*app, const tsip_request_t *); + int ret; + + ret = send_200NOTIFY(self, request); + ret = process_i_notify(self, request); + + return ret; +} + +/* Connected -> (NOTIFY) -> Terminated +*/ +int tsip_dialog_subscribe_Connected_2_Terminated_X_NOTIFY(va_list *app) +{ + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + const tsip_request_t *request = va_arg(*app, const tsip_request_t *); + + /* Alert the user */ + TSIP_DIALOG_SUBSCRIBE_SIGNAL(self, tsip_i_notify, + tsip_event_code_dialog_request_incoming, "Incoming NOTIFY.", request); + + return send_200NOTIFY(self, request); +} + +/* Any -> (hangup) -> Trying +*/ +int tsip_dialog_subscribe_Any_2_Trying_X_hangup(va_list *app) +{ + tsip_dialog_subscribe_t *self; + + self = va_arg(*app, tsip_dialog_subscribe_t *); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog"); + + self->unsubscribing = tsk_true; + return send_SUBSCRIBE(self); +} + +/* Any -> (shutdown) -> Trying +*/ +int tsip_dialog_subscribe_Any_2_Trying_X_shutdown(va_list *app) +{ + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + + /* schedule shutdow timeout */ + TSIP_DIALOG_SUBSCRIBE_TIMER_SCHEDULE(shutdown); + + /* alert user */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_terminating, "Terminating dialog"); + + self->unsubscribing = tsk_true; + return send_SUBSCRIBE(self); +} + +/* Any -> (transport error) -> Terminated +*/ +int tsip_dialog_subscribe_Any_2_Terminated_X_transportError(va_list *app) +{ + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + /* const tsip_response_t *response = va_arg(*app, const tsip_response_t *); */ + + /* Alert the user. */ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_transport_error, "Transport error."); + + return 0; +} + +/* Any -> (error) -> Terminated +*/ +int tsip_dialog_subscribe_Any_2_Terminated_X_Error(va_list *app) +{ + tsip_dialog_subscribe_t *self = va_arg(*app, tsip_dialog_subscribe_t *); + const tsip_response_t *response = va_arg(*app, const tsip_response_t *); + + // save last error + tsip_dialog_set_lasterror_2(TSIP_DIALOG(self), TSIP_RESPONSE_PHRASE(response), TSIP_RESPONSE_CODE(response), response); + + /* Alert user */ + if(response){ + TSIP_DIALOG_SUBSCRIBE_SIGNAL(self, self->unsubscribing ? tsip_ao_unsubscribe : tsip_ao_subscribe, + TSIP_RESPONSE_CODE(response), TSIP_RESPONSE_PHRASE(response), response); + } + else{ + TSIP_DIALOG_SIGNAL(self, tsip_event_code_dialog_global_error, "Global error."); + } + + return 0; +} + + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// == STATE MACHINE END == +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +/** + * Sends a SUBSCRIBE request. + * + * @param [in,out] self The caller. + * + * @return Zero if succeed and non-zero error code otherwise. +**/ +int send_SUBSCRIBE(tsip_dialog_subscribe_t *self) +{ + tsip_request_t *request; + int ret = -1; + + if(self->unsubscribing){ + TSIP_DIALOG(self)->expires = 0; + } + + if((request = tsip_dialog_request_new(TSIP_DIALOG(self), "SUBSCRIBE"))){ + /* apply action params to the request */ + if(TSIP_DIALOG(self)->curr_action){ + tsip_dialog_apply_action(request, TSIP_DIALOG(self)->curr_action); + } + /* send the request */ + ret = tsip_dialog_request_send(TSIP_DIALOG(self), request); + TSK_OBJECT_SAFE_FREE(request); + } + + return ret; +} + +int send_200NOTIFY(tsip_dialog_subscribe_t *self, const tsip_request_t* request) +{ + tsip_response_t *response; + int ret = -1; + + if((response = tsip_dialog_response_new(TSIP_DIALOG(self), 200, "OK", request))){ + ret = tsip_dialog_response_send(TSIP_DIALOG(self), response); + TSK_OBJECT_SAFE_FREE(response); + } + return ret; +} + +// process incoming notify: refresh delay and alert the user +int process_i_notify(tsip_dialog_subscribe_t *self, const tsip_request_t* notify) +{ + if(!self || !notify){ + TSK_DEBUG_ERROR("Invalid parameter"); + return -1; + } + + /* Request timeout for dialog refresh (re-registration). */ + self->timerrefresh.timeout = tsip_dialog_get_newdelay(TSIP_DIALOG(self), notify); + TSIP_DIALOG_SUBSCRIBE_TIMER_SCHEDULE(refresh); + + /* Alert the user */ + TSIP_DIALOG_SUBSCRIBE_SIGNAL(self, tsip_i_notify, + tsip_event_code_dialog_request_incoming, "Incoming NOTIFY.", notify); + + return 0; +} + +/** + * Callback function called by the state machine manager to signal that the final state has been reached. + * + * @param [in,out] self The state machine owner. +**/ +int tsip_dialog_subscribe_OnTerminated(tsip_dialog_subscribe_t *self) +{ + TSK_DEBUG_INFO("=== SUBSCRIBE Dialog terminated ==="); + + /* Alert the user */ + TSIP_DIALOG_SIGNAL_2(self, tsip_event_code_dialog_terminated, + TSIP_DIALOG(self)->last_error.phrase ? TSIP_DIALOG(self)->last_error.phrase : "Dialog terminated", + TSIP_DIALOG(self)->last_error.message); + + /* Remove from the dialog layer. */ + return tsip_dialog_remove(TSIP_DIALOG(self)); +} + + + + + + + + + + + + + + +//======================================================== +// SIP dialog SUBSCRIBE object definition +// +static tsk_object_t* tsip_dialog_subscribe_ctor(tsk_object_t * self, va_list * app) +{ + tsip_dialog_subscribe_t *dialog = self; + if(dialog){ + tsip_ssession_handle_t *ss = va_arg(*app, tsip_ssession_handle_t *); + + /* Initialize base class */ + tsip_dialog_init(TSIP_DIALOG(self), tsip_dialog_SUBSCRIBE, tsk_null, ss, _fsm_state_Started, _fsm_state_Terminated); + + /* FSM */ + TSIP_DIALOG_GET_FSM(self)->debug = DEBUG_STATE_MACHINE; + tsk_fsm_set_callback_terminated(TSIP_DIALOG_GET_FSM(self), TSK_FSM_ONTERMINATED_F(tsip_dialog_subscribe_OnTerminated), (const void*)dialog); + + /* Initialize the class itself */ + tsip_dialog_subscribe_init(self); + } + return self; +} + +static tsk_object_t* tsip_dialog_subscribe_dtor(tsk_object_t * _self) +{ + tsip_dialog_subscribe_t *self = _self; + if(self){ + /* Cancel all timers */ + TSIP_DIALOG_TIMER_CANCEL(refresh); + TSIP_DIALOG_TIMER_CANCEL(shutdown); + + /* DeInitialize base class (will cancel all transactions) */ + tsip_dialog_deinit(TSIP_DIALOG(self)); + + TSK_DEBUG_INFO("*** SUBSCRIBE Dialog destroyed ***"); + } + return self; +} + +static int tsip_dialog_subscribe_cmp(const tsk_object_t *obj1, const tsk_object_t *obj2) +{ + return tsip_dialog_cmp(obj1, obj2); +} + +static const tsk_object_def_t tsip_dialog_subscribe_def_s = +{ + sizeof(tsip_dialog_subscribe_t), + tsip_dialog_subscribe_ctor, + tsip_dialog_subscribe_dtor, + tsip_dialog_subscribe_cmp, +}; +const tsk_object_def_t *tsip_dialog_subscribe_def_t = &tsip_dialog_subscribe_def_s; diff --git a/tinySIP/src/dialogs/tsip_dialog_subscribe.server.c b/tinySIP/src/dialogs/tsip_dialog_subscribe.server.c new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tinySIP/src/dialogs/tsip_dialog_subscribe.server.c @@ -0,0 +1 @@ + |