summaryrefslogtreecommitdiffstats
path: root/tinySIP/src/dialogs
diff options
context:
space:
mode:
Diffstat (limited to 'tinySIP/src/dialogs')
-rw-r--r--tinySIP/src/dialogs/tsip_dialog.c1354
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_info.c556
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.c1942
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.cdiv.c0
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.client.c333
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.conf.c1
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.ect.c487
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.hold.c252
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.ice.c617
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.qos.c92
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.server.c790
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_invite.timers.c302
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_layer.c776
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_message.c552
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_options.c578
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_publish.client.c701
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_register.c507
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_register.client.c424
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_register.server.c237
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_subscribe.client.c764
-rw-r--r--tinySIP/src/dialogs/tsip_dialog_subscribe.server.c1
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, &regtype)){
+ /* There are non-register dialogs ==> phase-1 */
+ goto phase1;
+ }
+ else if(tsk_list_count(self->dialogs, pred_find_dialog_by_type, &regtype)){
+ /* 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, &regtype) == 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, &regtype) == 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 @@
+
OpenPOWER on IntegriCloud