summaryrefslogtreecommitdiffstats
path: root/src/dialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dialog.cpp')
-rw-r--r--src/dialog.cpp3825
1 files changed, 3825 insertions, 0 deletions
diff --git a/src/dialog.cpp b/src/dialog.cpp
new file mode 100644
index 0000000..f6e1790
--- /dev/null
+++ b/src/dialog.cpp
@@ -0,0 +1,3825 @@
+/*
+ Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com>
+
+ This program 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 2 of the License, or
+ (at your option) any later version.
+
+ This program 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 this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <cstdlib>
+#include <assert.h>
+#include <iostream>
+#include "call_history.h"
+#include "call_script.h"
+#include "dialog.h"
+#include "exceptions.h"
+#include "line.h"
+#include "log.h"
+#include "phone_user.h"
+#include "sub_refer.h"
+#include "util.h"
+#include "userintf.h"
+#include "audio/rtp_telephone_event.h"
+#include "audits/memman.h"
+#include "im/im_iscomposing_body.h"
+#include "sdp/sdp.h"
+#include "sockets/socket.h"
+#include "stun/stun_transaction.h"
+
+extern t_event_queue *evq_sender;
+extern t_event_queue *evq_trans_mgr;
+extern string user_host;
+extern string local_hostname;
+extern t_phone *phone;
+
+// Protected
+
+// Create a request within a dialog
+// RFC 3261 12.2.1.1
+t_request *t_dialog::create_request(t_method m) {
+ assert(state != DS_NULL);
+ t_user *user_config = phone_user->get_user_profile();
+
+ // RFC 3261 9.1
+ if (m == CANCEL) {
+ t_request *r = new t_request(m);
+ MEMMAN_NEW(r);
+
+ assert(req_out_invite);
+ t_request *orig_req = req_out_invite->get_request();
+ r->hdr_to = orig_req->hdr_to;
+ r->hdr_from = orig_req->hdr_from;
+ r->hdr_call_id = orig_req->hdr_call_id;
+ r->hdr_cseq.set_seqnr(orig_req->hdr_cseq.seqnr);
+ r->hdr_cseq.set_method(CANCEL);
+ r->hdr_via = orig_req->hdr_via; // RFC 3261 8.1.1.7
+ r->hdr_max_forwards.set_max_forwards(MAX_FORWARDS);
+ r->hdr_route = orig_req->hdr_route;
+ SET_HDR_USER_AGENT(r->hdr_user_agent);
+ r->uri = orig_req->uri;
+
+ // RFC 3263 4
+ // CANCEL for a particular SIP request MUST be sent to the same SIP
+ // server that the SIP request was delivered to.
+ t_ip_port ip_port;
+ orig_req->get_destination(ip_port, *user_config);
+ r->set_destination(ip_port);
+ return r;
+ }
+
+ t_request *r = t_abstract_dialog::create_request(m);
+
+ // CSeq header
+ if (m == ACK) {
+ assert(req_out_invite);
+
+ // Local sequence number was incremented by t_abstract_dialog.
+ // Decrement as it ACK does not take a new sequence number.
+ local_seqnr--;
+
+ // ACK has the same sequence number
+ // as the INVITE.
+ r->hdr_cseq.set_seqnr(req_out_invite->get_request()->hdr_cseq.seqnr);
+
+ // RFC 3261 22.1
+ // Authorization and Proxy-Authorization headers in INVITE
+ // must be repeated in ACK
+ r->hdr_authorization = req_out_invite->get_request()->
+ hdr_authorization;
+ r->hdr_proxy_authorization = req_out_invite->get_request()->
+ hdr_proxy_authorization;
+ }
+
+ // Contact header
+ t_contact_param contact;
+ switch (m) {
+ case REFER:
+ case SUBSCRIBE:
+ case NOTIFY:
+ // RFC 3265 7.1, RFC 3515 2.2
+ // Contact header is mandatory
+ contact.uri.set_url(line->create_user_contact(h_ip2str(r->get_local_ip())));
+ r->hdr_contact.add_contact(contact);
+ break;
+ default:
+ break;
+ }
+
+ // Privacy header
+ if (line->get_hide_user()) {
+ r->hdr_privacy.add_privacy(PRIVACY_ID);
+ }
+
+ return r;
+}
+
+// NULL state. Waiting for incoming INVITE
+void t_dialog::state_null(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (r->method != INVITE) {
+ state = DS_TERMINATED;
+ return;
+ }
+
+ // Set local tag
+ if (r->hdr_to.tag.size() == 0) {
+ local_tag = NEW_TAG;
+ } else {
+ local_tag = r->hdr_to.tag;
+ }
+
+ // If STUN is enabled, then first send a STUN binding request to
+ // discover the IP adderss and port for media.
+ if (phone->use_stun(user_config)) {
+ // The STUN transaction may take a while.
+ // Send 100 Trying
+ resp = r->create_response(R_100_TRYING);
+ resp->hdr_to.set_tag("");
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ if (!stun_bind_media()) {
+ // STUN request failed. Send a 500 on the INVITE.
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR);
+ resp->hdr_to.set_tag(local_tag);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ state = DS_TERMINATED;
+ return;
+ }
+ }
+
+ call_id = r->hdr_call_id.call_id;
+
+ // Initialize local seqnr
+ local_seqnr = NEW_SEQNR;
+ local_resp_nr = NEW_SEQNR;
+
+ remote_tag = r->hdr_from.tag;
+ local_uri = r->hdr_to.uri;
+ local_display = r->hdr_to.display;
+ remote_uri = r->hdr_from.uri;
+ remote_display = r->hdr_from.display;
+
+ // Set remote target URI and display name
+ remote_target_uri = r->hdr_contact.contact_list.front().uri;
+ remote_target_display = r->
+ hdr_contact.contact_list.front().display;
+
+ // Set route set
+ if (r->hdr_record_route.is_populated()) {
+ route_set = r->hdr_record_route.route_list;
+ }
+
+ // RFC 3261 13.2.1
+ // An initial INVITE should list all supported extensions.
+ // Set supported extensions
+ if (r->hdr_supported.is_populated()) {
+ remote_extensions.insert(r->hdr_supported.features.begin(),
+ r->hdr_supported.features.end());
+ }
+
+ // Media information
+ int warn_code;
+ string warn_text;
+ if (r->body) {
+ switch(r->body->get_type()) {
+ case BODY_SDP:
+ if (session->process_sdp_offer((t_sdp*)r->body,
+ warn_code, warn_text)) {
+ session->recvd_offer = true;
+ break;
+ }
+
+ // Unsupported media
+ resp = r->create_response(
+ R_488_NOT_ACCEPTABLE_HERE);
+ resp->hdr_to.set_tag(local_tag);
+ resp->hdr_warning.add_warning(t_warning(LOCAL_HOSTNAME,
+ 0, warn_code, warn_text));
+ line->send_response(resp, tuid, tid);
+
+ // Create call history record
+ line->call_hist_record.start_call(r, t_call_record::DIR_IN,
+ user_config->get_profile_name());
+ line->call_hist_record.fail_call(resp);
+
+ MEMMAN_DELETE(resp);
+ delete resp;
+ state = DS_TERMINATED;
+ return;
+ default:
+ // Unsupported body type. Reject call.
+ resp = r->create_response(
+ R_415_UNSUPPORTED_MEDIA_TYPE);
+ resp->hdr_to.set_tag(local_tag);
+
+ // RFC 3261 21.4.13
+ SET_HDR_ACCEPT(resp->hdr_accept);
+
+ // Create call history record
+ line->call_hist_record.start_call(r, t_call_record::DIR_IN,
+ user_config->get_profile_name());
+ line->call_hist_record.fail_call(resp);
+
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ state = DS_TERMINATED;
+ return;
+ }
+ }
+
+ resp = r->create_response(R_180_RINGING);
+ resp->hdr_to.set_tag(local_tag);
+
+ // RFC 3261 13.3.1.1
+ // A provisional response creates an early dialog, so
+ // copy the Record-Route header and add a Contact
+ // header.
+
+ // Copy the Record-Route header from request to response
+ if (r->hdr_record_route.is_populated()) {
+ resp->hdr_record_route = r->hdr_record_route;
+ }
+
+ // Set Contact header
+ t_contact_param contact;
+ contact.uri.set_url(line->create_user_contact(h_ip2str(resp->get_local_ip())));
+ resp->hdr_contact.add_contact(contact);
+
+ // RFC 3262 3
+ // Send 180 response reliable if needed
+ if (r->hdr_require.contains(EXT_100REL) ||
+ (r->hdr_supported.contains(EXT_100REL) &&
+ (user_config->get_ext_100rel() == EXT_PREFERRED ||
+ user_config->get_ext_100rel() == EXT_REQUIRED)))
+ {
+ resp->hdr_require.add_feature(EXT_100REL);
+ resp->hdr_rseq.set_resp_nr(++local_resp_nr);
+
+ // RFC 3262 5
+ // Create SDP offer in first reliable response if no offer
+ // was received in INVITE.
+ // This implentation does not create an answer in an
+ // reliable 1xx response if an offer was received.
+ if (!session->recvd_offer) {
+ session->create_sdp_offer(resp, SDP_O_USER);
+ }
+
+ // Keep a copy of the response for retransmission
+ resp_1xx_invite = (t_response *)resp->copy();
+
+ // Start 100rel timeout and guard timers
+ line->start_timer(LTMR_100REL_GUARD, get_object_id());
+ line->start_timer(LTMR_100REL_TIMEOUT, get_object_id());
+ }
+
+ line->send_response(resp, req_in_invite->get_tuid(),
+ req_in_invite->get_tid());
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ ui->cb_incoming_call(user_config, line->get_line_number(), r);
+ line->call_hist_record.start_call(r, t_call_record::DIR_IN,
+ user_config->get_profile_name());
+
+ state = DS_W4ANSWER;
+}
+
+// A provisional answer has been sent. Waiting for user to answer.
+void t_dialog::state_w4answer(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+ bool tear_down = false;
+ bool answer_call = false;
+
+ t_call_script script_in_call_failed(user_config, t_call_script::TRIGGER_IN_CALL_FAILED,
+ line->get_line_number() + 1);
+
+ switch (r->method) {
+ case CANCEL:
+ // Cancel the request and terminate the dialog.
+ // A response on the CANCEL is already given by dialog::recvd_cancel
+ resp = req_in_invite->get_request()->
+ create_response(R_487_REQUEST_TERMINATED);
+ resp->hdr_to.set_tag(local_tag);
+ line->send_response(resp, req_in_invite->get_tuid(),
+ req_in_invite->get_tid());
+ line->call_hist_record.fail_call(resp);
+
+ // Trigger call script
+ script_in_call_failed.exec_notify(resp);
+
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ ui->cb_call_cancelled(line->get_line_number());
+ state = DS_TERMINATED;
+ break;
+ case BYE:
+ // Send 200 on the BYE request
+ resp = r->create_response(R_200_OK);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ // Send a 487 response to terminate the pending request
+ resp = req_in_invite->get_request()->create_response(
+ R_487_REQUEST_TERMINATED);
+ resp->hdr_to.set_tag(local_tag);
+ line->send_response(resp, req_in_invite->get_tuid(),
+ req_in_invite->get_tid());
+ line->call_hist_record.fail_call(resp);
+
+ // Trigger call script
+ script_in_call_failed.exec_notify(resp);
+
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ ui->cb_far_end_hung_up(line->get_line_number());
+ state = DS_TERMINATED;
+ break;
+ case PRACK:
+ // RFC 3262 3
+ if (respond_prack(r, tuid, tid)) {
+ answer_call = answer_after_prack;
+
+ // RFC 3262 5
+ // If an offer was sent in the 1xx response, then PRACK must
+ // contain an answer
+ if (session->sent_offer && r->body) {
+ int warn_code;
+ string warn_text;
+ if (r->body->get_type() != BODY_SDP) {
+ // Only SDP bodies are supported
+ ui->cb_unsupported_content_type(
+ line->get_line_number(), r);
+ tear_down = true;
+ } else if (session->process_sdp_answer((t_sdp *)r->body,
+ warn_code, warn_text))
+ {
+ session->recvd_answer = true;
+ session->start_rtp();
+ } else {
+ // SDP answer is not supported.
+ // Tear down the call.
+ ui->cb_sdp_answer_not_supported(
+ line->get_line_number(), warn_text);
+ tear_down = true;
+ }
+ }
+
+ if (session->sent_offer && !r->body) {
+ ui->cb_sdp_answer_missing(line->get_line_number());
+ tear_down = true;
+ }
+ }
+
+ if (tear_down) {
+ resp = req_in_invite->get_request()->create_response(
+ R_400_BAD_REQUEST,
+ "SDP answer in PRACK missing or unsupported");
+ resp->hdr_to.set_tag(local_tag);
+ line->send_response(resp, req_in_invite->get_tuid(),
+ req_in_invite->get_tid());
+ line->call_hist_record.fail_call(resp);
+
+ // Trigger call script
+ t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL_FAILED,
+ line->get_line_number() + 1);
+ script.exec_notify(resp);
+
+ MEMMAN_DELETE(resp);
+ delete resp;
+ state = DS_TERMINATED;
+ } else if (answer_call) {
+ answer();
+ }
+
+ break;
+ default:
+ // INVITE transaction has not been completed. Deny
+ // other requests within the dialog.
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR,
+ "Session not yet established");
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ break;
+ }
+}
+
+void t_dialog::state_w4answer(t_line_timer timer) {
+ t_ip_port ip_port;
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+
+ t_call_script script_in_call_failed(user_config, t_call_script::TRIGGER_IN_CALL_FAILED,
+ line->get_line_number() + 1);
+
+ // RFC 3262 3
+ switch(timer) {
+ case LTMR_100REL_TIMEOUT:
+ // Retransmit 1xx response.
+ // Send the response directly to the sender thread
+ // bypassing the transaction layer. As this is a retransmission
+ // from the TU, the transaction layer does not need to know.
+ resp_1xx_invite->get_destination(ip_port);
+ if (ip_port.ipaddr == 0) {
+ // This should not happen. The response has been
+ // sent before so it should be possible to sent
+ // it again. Ignore the timeout. When the 100rel
+ // guard timer expires, the dialog will be
+ // cleaned up.
+ break;
+ }
+ evq_sender->push_network(resp_1xx_invite, ip_port);
+ line->start_timer(LTMR_100REL_TIMEOUT, get_object_id());
+ break;
+ case LTMR_100REL_GUARD:
+ line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id());
+
+ // PRACK was not received in time. Tear down the call.
+ resp = req_in_invite->get_request()->create_response(
+ R_500_INTERNAL_SERVER_ERROR, "100rel timeout");
+ resp->hdr_to.set_tag(local_tag);
+ line->send_response(resp, req_in_invite->get_tuid(),
+ req_in_invite->get_tid());
+ line->call_hist_record.fail_call(resp);
+
+ // Trigger call script
+ script_in_call_failed.exec_notify(resp);
+
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ remove_client_request(&req_in_invite);
+ MEMMAN_DELETE(resp_1xx_invite);
+ delete resp_1xx_invite;
+ resp_1xx_invite = NULL;
+
+ state = DS_TERMINATED;
+ log_file->write_report("LTMR_100REL_GUARD expired.",
+ "t_dialog::state_w4answer");
+
+ ui->cb_100rel_timeout(line->get_line_number());
+ break;
+ default:
+ // Other timeouts are not expected. Ignore.
+ break;
+ }
+}
+
+// 200 OK has been sent. Waiting for ACK
+void t_dialog::state_w4ack(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+ bool tear_down = false;
+ t_client_request *cr;
+
+ t_call_script script_out_call_failed(user_config, t_call_script::TRIGGER_OUT_CALL_FAILED,
+ line->get_line_number() + 1);
+
+ switch(r->method) {
+ case ACK:
+ // Dialog is established now.
+ line->stop_timer(LTMR_ACK_TIMEOUT, get_object_id());
+ line->stop_timer(LTMR_ACK_GUARD, get_object_id());
+ remove_client_request(&req_in_invite);
+ MEMMAN_DELETE(resp_invite);
+ delete resp_invite;
+ resp_invite = NULL;
+
+ // If no offer was received in INVITE, then an offer
+ // has been sent in 200 OK or reliable 1xx (RFC 3262).
+ // Therefor an answer must be present in ACK
+ if (!session->recvd_offer && r->body) {
+ int warn_code;
+ string warn_text;
+ if (r->body->get_type() != BODY_SDP) {
+ // Only SDP bodies are supported
+ ui->cb_unsupported_content_type(
+ line->get_line_number(), r);
+ tear_down = true;
+ } else if (session->process_sdp_answer((t_sdp *)r->body,
+ warn_code, warn_text))
+ {
+ session->recvd_answer = true;
+ session->start_rtp();
+ } else {
+ // SDP answer is not supported.
+ // Tear down the call.
+ ui->cb_sdp_answer_not_supported(
+ line->get_line_number(), warn_text);
+ tear_down = true;
+ }
+ }
+
+ if (!session->recvd_offer && !r->body) {
+ ui->cb_sdp_answer_missing(line->get_line_number());
+ tear_down = true;
+ }
+
+ if (end_after_ack) {
+ ui->cb_far_end_hung_up(line->get_line_number());
+ state = DS_TERMINATED;
+ } else {
+ state = DS_CONFIRMED;
+ if (tear_down) {
+ send_bye();
+ }
+ }
+
+ ui->cb_call_established(line->get_line_number());
+ break;
+ case BYE:
+ // Send 200 on the BYE request
+ resp = r->create_response(R_200_OK);
+ line->send_response(resp, tuid, tid);
+
+ // Trigger call script
+ script_out_call_failed.exec_notify(resp);
+
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ line->call_hist_record.end_call(true);
+
+ // The session will be ended when an ACK has been
+ // received.
+ end_after_ack = true;
+ break;
+ case PRACK:
+ // RFC 3262 3
+ // This is a late PRACK as the call is answered already.
+ // Respond in a normal way to the PRACK
+ respond_prack(r, tuid, tid);
+ break;
+ default:
+ // Queue the request as ACK needs to be received first.
+ // Note that the tuid value is not stored in the queue.
+ // For an incoming request tuid is always 0.
+ cr = new t_client_request(user_config, r, tid);
+ MEMMAN_NEW(cr);
+ inc_req_queue.push_back(cr);
+ log_file->write_header("t_dialog::state_w4ack",
+ LOG_NORMAL, LOG_INFO);
+ log_file->write_raw("Waiting for ACK.\n");
+ log_file->write_raw("Queue incoming ");
+ log_file->write_raw(method2str(r->method, r->unknown_method));
+ log_file->write_endl();
+ log_file->write_footer();
+ break;
+ }
+}
+
+void t_dialog::state_w4ack_re_invite(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+ bool tear_down = false;
+
+ t_call_script script_out_call_failed(user_config, t_call_script::TRIGGER_OUT_CALL_FAILED,
+ line->get_line_number() + 1);
+
+ switch(r->method) {
+ case ACK:
+ // re_INVITE is finished now
+ line->stop_timer(LTMR_ACK_TIMEOUT, get_object_id());
+ line->stop_timer(LTMR_ACK_GUARD, get_object_id());
+ remove_client_request(&req_in_invite);
+ MEMMAN_DELETE(resp_invite);
+ delete resp_invite;
+ resp_invite = NULL;
+
+ // If no offer was received in INVITE, then an offer
+ // has been sent in 200 OK or reliable 1xx (RFC 3262).
+ // Therefor an answer must be present in ACK
+ if (!session_re_invite->recvd_offer && r->body) {
+ int warn_code;
+ string warn_text;
+
+ if (r->body->get_type() != BODY_SDP) {
+ // Only SDP bodies are supported
+ ui->cb_unsupported_content_type(
+ line->get_line_number(), r);
+ tear_down = true;
+ } else if (session_re_invite->process_sdp_answer(
+ (t_sdp *)r->body, warn_code, warn_text))
+ {
+ session_re_invite->recvd_answer = true;
+ } else {
+ // SDP answer is not supported.
+ // Tear down the call.
+ ui->cb_sdp_answer_not_supported(
+ line->get_line_number(), warn_text);
+ tear_down = true;
+ }
+ }
+
+ if (!session_re_invite->recvd_offer && !r->body) {
+ ui->cb_sdp_answer_missing(line->get_line_number());
+ tear_down = true;
+ }
+
+ if (end_after_ack) {
+ ui->cb_far_end_hung_up(line->get_line_number());
+ state = DS_TERMINATED;
+ } else {
+ state = DS_CONFIRMED;
+ if (tear_down) {
+ send_bye();
+ } else {
+ // Make the new session description current
+ activate_new_session();
+ }
+ }
+ break;
+ case BYE:
+ // Send 200 on the BYE request
+ resp = r->create_response(R_200_OK);
+ line->send_response(resp, tuid, tid);
+
+ // Trigger call script
+ script_out_call_failed.exec_notify(resp);
+
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ line->call_hist_record.end_call(true);
+
+ // The session will be ended when an ACK has been
+ // received.
+ end_after_ack = true;
+ break;
+ default:
+ // ACK has not been received. Handle other incoming request
+ // as if we are in the confirmed state. These incoming requests
+ // should not change state.
+ state_confirmed(r, tuid, tid);
+ assert(state == DS_W4ACK_RE_INVITE);
+ break;
+ }
+}
+
+// RFC 3261 13.3.1.4
+void t_dialog::state_w4ack(t_line_timer timer) {
+ t_ip_port ip_port;
+
+ // NOTE: this code is also executed for re-INVITE ACK time-outs
+ // timeout handling for INVITE/re-INVITE is the same
+
+ switch(timer) {
+ case LTMR_ACK_TIMEOUT:
+ // Retransmit 2xx response.
+ // Send the response directly to the sender thread
+ // as the INVITE transaction completed already.
+ // (see RFC 3261 17.2.1)
+ if (!resp_invite) break; // there is no response to send
+ resp_invite->get_destination(ip_port);
+ if (ip_port.ipaddr == 0) {
+ // This should not happen. The response has been
+ // sent before so it should be possible to sent
+ // it again. Ignore the timeout. When the ACK
+ // guard timer expires, the dialog will be
+ // cleaned up.
+ break;
+ }
+ evq_sender->push_network(resp_invite, ip_port);
+ line->start_timer(LTMR_ACK_TIMEOUT, get_object_id());
+ break;
+ case LTMR_ACK_GUARD:
+ line->stop_timer(LTMR_ACK_TIMEOUT, get_object_id());
+ // Consider dialog as established and tear down call
+ remove_client_request(&req_in_invite);
+ MEMMAN_DELETE(resp_invite);
+ delete resp_invite;
+ resp_invite = NULL;
+ state = DS_CONFIRMED;
+ log_file->write_report("LTMR_ACK_GUARD expired.",
+ "t_dialog::state_w4ack");
+ if (end_after_ack) {
+ state = DS_TERMINATED;
+ } else {
+ send_bye();
+ }
+ ui->cb_ack_timeout(line->get_line_number());
+ break;
+ default:
+ // Other timeouts are not expected. Ignore.
+ break;
+ }
+}
+
+void t_dialog::state_w4ack_re_invite(t_line_timer timer) {
+ state_w4ack(timer);
+}
+
+void t_dialog::state_w4re_invite_resp(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+
+ t_call_script script_remote_release(user_config, t_call_script::TRIGGER_REMOTE_RELEASE,
+ line->get_line_number() + 1);
+
+ switch(r->method) {
+ case BYE:
+ resp = r->create_response(R_200_OK);
+ line->send_response(resp, tuid, tid);
+
+ // Trigger call script
+ script_remote_release.exec_notify(r);
+
+ MEMMAN_DELETE(resp);
+ delete resp;
+ ui->cb_far_end_hung_up(line->get_line_number());
+ line->call_hist_record.end_call(true);
+
+ if (!sub_refer) {
+ state = DS_TERMINATED;
+ } else {
+ state = DS_CONFIRMED_SUB;
+ if (sub_refer->get_role() == SR_SUBSCRIBER) {
+ // End subscription
+ sub_refer->unsubscribe();
+ }
+ }
+ break;
+ case ACK:
+ // Ignore ACK
+ break;
+ case OPTIONS:
+ resp = line->create_options_response(r, true);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ case PRACK:
+ // RFC 3262 3
+ // This is a late PRACK. Respond in a normal way.
+ respond_prack(r, tuid, tid);
+ break;
+ case SUBSCRIBE:
+ process_subscribe(r, tuid, tid);
+ break;
+ case NOTIFY:
+ process_notify(r, tuid, tid);
+ break;
+ default:
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR,
+ "Waiting for re-INVITE response");
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ break;
+ }
+}
+
+// In the confirmed state, requests will be responded.
+void t_dialog::state_confirmed(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+
+ t_call_script script_remote_release(user_config, t_call_script::TRIGGER_REMOTE_RELEASE,
+ line->get_line_number() + 1);
+
+ switch(r->method) {
+ case INVITE:
+ // re-INVITE
+ process_re_invite(r, tuid, tid);
+ break;
+ case BYE:
+ resp = r->create_response(R_200_OK);
+ line->send_response(resp, tuid, tid);
+
+ // Trigger call script
+ script_remote_release.exec_notify(r);
+
+ MEMMAN_DELETE(resp);
+ delete resp;
+ ui->cb_far_end_hung_up(line->get_line_number());
+ line->call_hist_record.end_call(true);
+
+ if (!sub_refer) {
+ state = DS_TERMINATED;
+ } else {
+ state = DS_CONFIRMED_SUB;
+ if (sub_refer->get_role() == SR_SUBSCRIBER) {
+ // End subscription
+ sub_refer->unsubscribe();
+ }
+ }
+ break;
+ case ACK:
+ // Ignore ACK
+ break;
+ case OPTIONS:
+ resp = line->create_options_response(r, true);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ case PRACK:
+ // RFC 3262 3
+ // This is a late PRACK. Respond in a normal way.
+ respond_prack(r, tuid, tid);
+ break;
+ case REFER:
+ process_refer(r, tuid, tid);
+ break;
+ case SUBSCRIBE:
+ process_subscribe(r, tuid, tid);
+ break;
+ case NOTIFY:
+ process_notify(r, tuid, tid);
+ break;
+ case INFO:
+ process_info(r, tuid, tid);
+ break;
+ case MESSAGE:
+ process_message(r, tuid, tid);
+ break;
+ default:
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ break;
+ }
+}
+
+void t_dialog::state_confirmed(t_line_timer timer) {
+ switch(timer) {
+ case LTMR_GLARE_RETRY:
+ switch(reinvite_purpose) {
+ case REINVITE_HOLD:
+ hold();
+ break;
+ case REINVITE_RETRIEVE:
+ retrieve();
+ line->retry_retrieve_succeeded();
+ // Note that the re-INVITE is not completed here yet.
+ // If re-INVITE fails then line->failed_retrieve will
+ // be called later.
+ break;
+ default:
+ assert(false);
+ }
+
+ break;
+ default:
+ // Other timeouts are not exepcted. Ignore.
+ break;
+ }
+}
+
+void t_dialog::state_confirmed_sub(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+
+ switch(r->method) {
+ case OPTIONS:
+ resp = line->create_options_response(r, true);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ case SUBSCRIBE:
+ process_subscribe(r, tuid, tid);
+ break;
+ case NOTIFY:
+ process_notify(r, tuid, tid);
+ break;
+ default:
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ break;
+ }
+
+ if (!sub_refer) {
+ // The subscription has been terminated already.
+ state = DS_TERMINATED;
+ } else if (sub_refer->get_state() == SS_TERMINATED) {
+ MEMMAN_DELETE(sub_refer);
+ delete sub_refer;
+ sub_refer = NULL;
+ state = DS_TERMINATED;
+ }
+}
+
+void t_dialog::process_re_invite(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+
+ session_re_invite = session->create_clean_copy();
+
+ // Media information
+ int warn_code;
+ string warn_text;
+ if (r->body) {
+ switch(r->body->get_type()) {
+ case BODY_SDP:
+ if (session_re_invite->
+ process_sdp_offer((t_sdp*)r->body,
+ warn_code, warn_text))
+ {
+ session_re_invite->recvd_offer = true;
+ break;
+ }
+
+ // Unsupported media
+ resp = r->create_response(
+ R_488_NOT_ACCEPTABLE_HERE);
+ resp->hdr_warning.add_warning(t_warning(LOCAL_HOSTNAME,
+ 0, warn_code, warn_text));
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ MEMMAN_DELETE(session_re_invite);
+ delete session_re_invite;
+ session_re_invite = NULL;
+
+ // Stay in the confirmed state. The sender of the
+ // request has to determine if the dialog needs to
+ // be torn down by sending a BYE.
+ return;
+ default:
+ // Unsupported body type. Reject call.
+ resp = r->create_response(
+ R_415_UNSUPPORTED_MEDIA_TYPE);
+
+ // RFC 3261 21.4.13
+ SET_HDR_ACCEPT(resp->hdr_accept);
+
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ MEMMAN_DELETE(session_re_invite);
+ delete session_re_invite;
+ session_re_invite = NULL;
+
+ // Stay in the confirmed state.
+ return;
+ }
+ }
+
+ // If STUN is enabled, then first send a STUN binding request to
+ // discover the IP adderss and port for media if no RTP stream
+ // is currently active.
+ if (phone->use_stun(user_config) && !session->is_rtp_active()) {
+ // The STUN transaction may take a while.
+ // Send 100 Trying
+ resp = r->create_response(R_100_TRYING);
+ resp->hdr_to.set_tag("");
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ if (!stun_bind_media()) {
+ // STUN request failed. Send a 500 on the INVITE.
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR);
+ resp->hdr_to.set_tag(local_tag);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ state = DS_TERMINATED;
+ return;
+ }
+ }
+
+ // Refresh target
+ if (r->hdr_contact.is_populated() &&
+ r->hdr_contact.contact_list.size() > 0)
+ {
+ remote_target_uri = r->hdr_contact.contact_list.front().uri;
+ remote_target_display = r->
+ hdr_contact.contact_list.front().display;
+ }
+
+ // Send 200 OK
+ resp_invite = r->create_response(R_200_OK);
+ resp_invite->hdr_to.set_tag(local_tag);
+
+ // Set Contact header
+ t_contact_param contact;
+ contact.uri.set_url(line->create_user_contact(h_ip2str(resp_invite->get_local_ip())));
+ resp_invite->hdr_contact.add_contact(contact);
+
+ // Set Allow and Supported headers
+ SET_HDR_ALLOW(resp_invite->hdr_allow, user_config);
+ SET_HDR_SUPPORTED(resp_invite->hdr_supported, user_config);
+
+ // RFC 3261 13.3.1.4
+ // Create SDP offer if no offer was received in INVITE and no offer
+ // was sent in a reliable 1xx response (RFC 3262 5).
+ // Otherwise create an SDP answer.
+ if (!session_re_invite->recvd_offer && !session_re_invite->sent_offer) {
+ session_re_invite->create_sdp_offer(resp_invite, SDP_O_USER);
+ } else {
+ session_re_invite->create_sdp_answer(resp_invite, SDP_O_USER);
+ }
+
+ line->send_response(resp_invite, tuid, tid);
+ line->start_timer(LTMR_ACK_GUARD, get_object_id());
+ line->start_timer(LTMR_ACK_TIMEOUT, get_object_id());
+
+ state = DS_W4ACK_RE_INVITE;
+}
+
+void t_dialog::process_refer(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+ t_contact_param contact;
+
+ refer_accepted = true;
+
+ // RFC 3515
+ if (sub_refer || !user_config->get_allow_refer()) {
+ // A reference is already in progress or REFER is not
+ // allowed.
+ resp = r->create_response(R_603_DECLINE);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ refer_accepted = false;
+ return;
+ }
+
+ // Check if the URI scheme is supported
+ if (r->hdr_refer_to.uri.get_scheme() != "sip") {
+ resp = r->create_response(R_416_UNSUPPORTED_URI_SCHEME);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ refer_accepted = false;
+ return;
+ }
+
+ resp = r->create_response(R_202_ACCEPTED);
+
+ // RFC 3515 2.2
+ // Contact header is mandatory
+ contact.uri.set_url(line->create_user_contact(h_ip2str(resp->get_local_ip())));
+ resp->hdr_contact.add_contact(contact);
+
+ if (r->hdr_refer_sub.is_populated() && !r->hdr_refer_sub.create_refer_sub) {
+ // RFC 4488 4
+ resp->hdr_refer_sub.set_create_refer_sub(false);
+ }
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ if (r->hdr_refer_sub.is_populated() && !r->hdr_refer_sub.create_refer_sub) {
+ // RFC 4488
+ // The REFER-issuer requested not to create an implicit refer
+ // subscription.
+ log_file->write_report(
+ "REFER-issuer requested not to create a refer subscription.",
+ "t_dialog::process_refer");
+ } else {
+ // RFC 3515
+ // The event header of a NOTIFY to a first REFER MAY
+ // include the id paramter. NOTIFY's to subsequent
+ // REFERs MUST include the id parameter (CSeq from REFER).
+ sub_refer = new t_sub_refer(this, SR_NOTIFIER,
+ ulong2str(r->hdr_cseq.seqnr));
+ MEMMAN_NEW(sub_refer);
+
+ // Send immediate NOTIFY
+ resp = new t_response(R_100_TRYING);
+ MEMMAN_NEW(resp);
+ if (user_config->get_ask_user_to_refer()) {
+ // If the user has to grant permission, then the
+ // subscription is pending.
+ sub_refer->send_notify(resp, SUBSTATE_PENDING);
+ } else {
+ sub_refer->send_notify(resp, SUBSTATE_ACTIVE);
+ }
+ MEMMAN_DELETE(resp);
+ delete resp;
+ }
+
+ // Ask permission to refer
+ if (user_config->get_ask_user_to_refer()) {
+ if (r->hdr_referred_by.is_populated()) {
+ ui->cb_ask_user_to_refer(user_config,
+ r->hdr_refer_to.uri,
+ r->hdr_refer_to.display,
+ r->hdr_referred_by.uri,
+ r->hdr_referred_by.display);
+ } else {
+ ui->cb_ask_user_to_refer(user_config,
+ r->hdr_refer_to.uri,
+ r->hdr_refer_to.display,
+ t_url(), "");
+ }
+ } else {
+ ui->send_refer_permission(true);
+ }
+
+ // NOTE: refer_accepted = true, though the answer to permission
+ // is not given yet. So this means, that the refer is not
+ // rejected at this moment. It may be rejected by the user.
+}
+
+void t_dialog::recvd_refer_permission(bool permission, t_request *r) {
+ t_response *resp;
+
+ // NOTE: if the REFER-issuer requested not to create a refer
+ // subscription (RFC 4488), then no NOTIFY can be sent to signal
+ // the rejection.
+ if (!permission && sub_refer) {
+ // User denied REFER
+ // RFC 3515 2.4.5
+ resp = new t_response(R_603_DECLINE);
+ MEMMAN_NEW(resp);
+ sub_refer->send_notify(resp, SUBSTATE_TERMINATED,
+ EV_REASON_REJECTED);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ }
+
+ refer_accepted = permission;
+}
+
+void t_dialog::process_subscribe(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+
+ if (sub_refer && sub_refer->match(r)) {
+ sub_refer->recv_subscribe(r, tuid, tid);
+ if (sub_refer->get_state() == SS_TERMINATED) {
+ MEMMAN_DELETE(sub_refer);
+ delete sub_refer;
+ sub_refer = NULL;
+ }
+
+ return;
+ }
+
+ resp = r->create_response(R_481_TRANSACTION_NOT_EXIST,
+ REASON_481_SUBSCRIPTION_NOT_EXIST);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+}
+
+void t_dialog::process_notify(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+
+ if (!sub_refer &&
+ (refer_state == REFST_W4RESP || refer_state == REFST_W4NOTIFY))
+ {
+ // First NOTIFY after sending a REFER
+ sub_refer = new t_sub_refer(this, SR_SUBSCRIBER, r->hdr_event.id);
+ MEMMAN_NEW(sub_refer);
+ refer_state = REFST_PENDING;
+ }
+
+ if (sub_refer && sub_refer->match(r)) {
+ sub_refer->recv_notify(r, tuid, tid);
+ if (sub_refer->get_state() == SS_TERMINATED) {
+ // Set the refer state to NULL before calling the UI
+ // call back functions as the user interface might use
+ // the refer state to render the correct status to the
+ // user.
+ refer_state = REFST_NULL;
+
+ // Determine outcome of the reference
+ switch(sub_refer->get_sr_result()) {
+ case SRR_INPROG:
+ // The outcome of the reference is unknown.
+ // Treat it as a success as no new info will
+ // come to the referrer.
+ refer_succeeded = true;
+ ui->cb_refer_result_inprog(line->get_line_number());
+ break;
+ case SRR_FAILED:
+ refer_succeeded = false;
+ ui->cb_refer_result_failed(line->get_line_number());
+ break;
+ case SRR_SUCCEEDED:
+ refer_succeeded = true;
+ ui->cb_refer_result_success(line->get_line_number());
+ break;
+ default:
+ assert(false);
+ }
+
+ MEMMAN_DELETE(sub_refer);
+ delete sub_refer;
+ sub_refer = NULL;
+ } else if (!sub_refer->is_pending()) {
+ refer_state = REFST_ACTIVE;
+ }
+
+ return;
+ }
+
+ // RFC 3265 3.2.4
+ resp = r->create_response(R_481_TRANSACTION_NOT_EXIST,
+ REASON_481_SUBSCRIPTION_NOT_EXIST);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+}
+
+void t_dialog::process_info(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+
+ // RFC 2976 2.2
+ // A 200 OK response MUST be sent by a UAS for an INFO request with
+ // no message body if the INFO request was successfully received for
+ // an existing call.
+ if (!r->body) {
+ resp = r->create_response(R_200_OK);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return;
+ }
+
+ if (r->body->get_type() != BODY_DTMF_RELAY) {
+ resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE);
+ resp->hdr_accept.add_media(t_media("application", "dtmf-relay"));
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return;
+ }
+
+ char dtmf_signal = ((t_sip_body_dtmf_relay *)r->body)->signal;
+ if (!VALID_DTMF_SYM(dtmf_signal)) {
+ resp = r->create_response(R_400_BAD_REQUEST, "Invalid DTMF signal");
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return;
+ }
+
+ resp = r->create_response(R_200_OK);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ ui->cb_dtmf_detected(line->get_line_number(), char2dtmf_ev(dtmf_signal));
+}
+
+void t_dialog::process_message(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+
+ log_file->write_report("Received in-dialog MESSAGE.",
+ "t_dialog::process_message", LOG_NORMAL, LOG_DEBUG);
+
+ if (!r->body || !MESSAGE_CONTENT_TYPE_SUPPORTED(*r)) {
+ resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE);
+ // RFC 3261 21.4.13
+ SET_MESSAGE_HDR_ACCEPT(resp->hdr_accept);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return;
+ }
+
+ if (r->body && r->body->get_type() == BODY_IM_ISCOMPOSING_XML) {
+ // Message composing indication
+ t_im_iscomposing_xml_body *sb = dynamic_cast<t_im_iscomposing_xml_body *>(r->body);
+ im::t_composing_state state = im::string2composing_state(sb->get_state());
+ time_t refresh = sb->get_refresh();
+
+ ui->cb_im_iscomposing_request(line->get_user(), r, state, refresh);
+ resp = r->create_response(R_200_OK);
+ } else {
+ // Instant message
+ bool accepted = ui->cb_message_request(line->get_user(), r);
+ if (accepted) {
+ resp = r->create_response(R_200_OK);
+ } else {
+ if (user_config->get_im_max_sessions() == 0) {
+ resp = r->create_response(R_603_DECLINE);
+ } else {
+ resp = r->create_response(R_486_BUSY_HERE);
+ }
+ }
+ }
+
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+}
+
+// INVITE sent. Waiting for a first non-100 response.
+void t_dialog::state_w4invite_resp(t_response *r, t_tuid tuid, t_tid tid) {
+ if (r->hdr_cseq.method != INVITE) return;
+ t_user *user_config = phone_user->get_user_profile();
+
+ // 1XX (except 100) and 2XX establish the dialog.
+ // Update the state for dialog establishment.
+ // RFC 3261 12.1.2
+ switch (r->get_class()) {
+ case R_1XX:
+ if (r->code == R_100_TRYING) break;
+
+ // RFC 3262 4
+ // Discard retransmissions and out-of-sequence reliable
+ // provisional responses.
+ if (must_discard_100rel(r)) return;
+
+ // fall thru
+ case R_2XX:
+ // Set remote tag
+ remote_tag = r->hdr_to.tag;
+
+ create_route_set(r);
+ create_remote_target(r);
+
+ // Set remote URI and display name
+ remote_uri = r->hdr_to.uri;
+ remote_display = r->hdr_to.display;
+
+ process_1xx_2xx_invite_resp(r);
+ break;
+ default:
+ break;
+ }
+
+ // RFC 3262
+ // Send PRACK if required
+ send_prack_if_required(r);
+
+ t_call_script script_out_call_answered(user_config,
+ t_call_script::TRIGGER_OUT_CALL_ANSWERED,
+ line->get_line_number() + 1);
+ t_call_script script_out_call_failed(user_config,
+ t_call_script::TRIGGER_OUT_CALL_FAILED,
+ line->get_line_number() + 1);
+
+ switch (r->get_class()) {
+ case R_1XX:
+ // Provisional response received.
+ line->ci_set_last_provisional_reason(r->reason);
+ ui->cb_provisional_resp_invite(line->get_line_number(), r);
+ if (r->code > R_100_TRYING && r->hdr_to.tag.size() > 0) {
+ state = DS_EARLY;
+ } else {
+ state = DS_W4INVITE_RESP2;
+ }
+
+ // User indicated that the request should be cancelled.
+ // Now that the first provisional response has been received,
+ // a CANCEL can be sent.
+ if (request_cancelled) {
+ send_cancel(true);
+ }
+
+ break;
+ case R_2XX:
+ // Stop cancel guard timer if it was running
+ line->stop_timer(LTMR_CANCEL_GUARD, get_object_id());
+
+ // Success received.
+ ack_2xx_invite(r);
+
+ // Check for REFER support
+ // If the Allow header is not present then assume REFER
+ // is supported.
+ if (!r->hdr_allow.is_populated() ||
+ r->hdr_allow.contains_method(REFER))
+ {
+ line->ci_set_refer_supported(true);
+ }
+
+ // Trigger call script
+ script_out_call_answered.exec_notify(r);
+
+ ui->cb_call_answered(user_config, line->get_line_number(), r);
+ line->call_hist_record.answer_call(r);
+ state = DS_CONFIRMED;
+
+ if (request_cancelled) {
+ // User indicated that the request should be cancelled,
+ // but no response was received yet. A final response
+ // has been received. Instead of CANCEL a BYE will be
+ // sent now.
+ send_bye();
+ } else if (end_after_2xx_invite) {
+ // Or user cancelled the request already, but the 2XX
+ // glared with CANCEL.
+ log_file->write_report("CANCEL / 2XX INVITE glare.",
+ "t_dialog::state_w4invite_resp");
+ send_bye();
+ }
+
+ break;
+ case R_3XX:
+ case R_4XX:
+ case R_5XX:
+ case R_6XX:
+ default:
+ // Stop cancel guard timer if it was running
+ line->stop_timer(LTMR_CANCEL_GUARD, get_object_id());
+
+ // Final response (failure) received.
+ // Treat unknown response classes as failure.
+
+ // Trigger call script
+ script_out_call_failed.exec_notify(r);
+
+ ui->cb_stop_call_notification(line->get_line_number());
+ ui->cb_call_failed(user_config, line->get_line_number(), r);
+ line->call_hist_record.fail_call(r);
+ remove_client_request(&req_out_invite);
+ state = DS_TERMINATED;
+ break;
+ }
+
+ // Notify progress to the referror if this is a referred call
+ if (is_referred_call) {
+ get_phone()->notify_refer_progress(r, line->get_line_number());
+ }
+}
+
+void t_dialog::state_w4invite_resp(t_line_timer timer) {
+ switch (timer) {
+ case LTMR_CANCEL_GUARD:
+ log_file->write_report("Timer LTMR_CANCEL_GUARD expired.",
+ "t_dialog::state_w4invite_resp", LOG_NORMAL, LOG_WARNING);
+
+ // CANCEL has been responded to, but 487 on INVITE was never
+ // received. Abort the INVITE transaction.
+ if (req_out_invite) {
+ t_tid _tid = req_out_invite->get_tid();
+ if (_tid > 0) {
+ evq_trans_mgr->push_abort_trans(_tid);
+ }
+ }
+ break;
+ default:
+ // Ignore other timeouts
+ break;
+ }
+}
+
+// INVITE response sent. At least 1 provisional response (not 100 Trying)
+// received.
+void t_dialog::state_early(t_response *r, t_tuid tuid, t_tid tid) {
+ if (r->hdr_cseq.method != INVITE) return;
+ t_user *user_config = phone_user->get_user_profile();
+
+ switch (r->get_class()) {
+ case R_1XX:
+ // RFC 3262 4
+ // Discard retransmissiona and out-of-sequence reliable
+ // provisional responses.
+ if (must_discard_100rel(r)) return;
+
+ // fall thru
+ case R_2XX:
+ create_route_set(r);
+ create_remote_target(r);
+ process_1xx_2xx_invite_resp(r);
+ break;
+ default:
+ break;
+ }
+
+ // RFC 3262
+ // Send PRACK if required
+ send_prack_if_required(r);
+
+ t_call_script script_out_call_answered(user_config,
+ t_call_script::TRIGGER_OUT_CALL_ANSWERED,
+ line->get_line_number() + 1);
+ t_call_script script_out_call_failed(user_config,
+ t_call_script::TRIGGER_OUT_CALL_FAILED,
+ line->get_line_number() + 1);
+
+ switch (r->get_class()) {
+ case R_1XX:
+ // Provisional response received.
+ line->ci_set_last_provisional_reason(r->reason);
+ ui->cb_provisional_resp_invite(line->get_line_number(), r);
+
+ if (request_cancelled) {
+ send_cancel(true);
+ }
+
+ break;
+ case R_2XX:
+ // Stop cancel guard timer if it was running
+ line->stop_timer(LTMR_CANCEL_GUARD, get_object_id());
+
+ // Success received.
+ ack_2xx_invite(r);
+
+ // Check for REFER support
+ // If the Allow header is not present then assume REFER
+ // is supported.
+ if (!r->hdr_allow.is_populated() ||
+ r->hdr_allow.contains_method(REFER))
+ {
+ line->ci_set_refer_supported(true);
+ }
+
+ // Trigger call script
+ script_out_call_answered.exec_notify(r);
+
+ ui->cb_call_answered(user_config, line->get_line_number(), r);
+ line->call_hist_record.answer_call(r);
+ state = DS_CONFIRMED;
+
+ if (request_cancelled) {
+ // User indicated that the request should be cancelled,
+ // but no response was received yet. A final response
+ // has been received. Instead of CANCEL a BYE will be
+ // sent now.
+ send_bye();
+ } else if (end_after_2xx_invite) {
+ // Or user cancelled the request already, but the 2XX
+ // glared with CANCEL.
+ log_file->write_report("CANCEL / 2XX INVITE glare.",
+ "t_dialog::state_w4invite_resp");
+ send_bye();
+ }
+
+ break;
+ case R_3XX:
+ case R_4XX:
+ case R_5XX:
+ case R_6XX:
+ default:
+ // Stop cancel guard timer if it was running
+ line->stop_timer(LTMR_CANCEL_GUARD, get_object_id());
+
+ // Final response (failure) received.
+ // Treat unknown response classes as failure.
+
+ // Trigger call script
+ script_out_call_failed.exec_notify(r);
+
+ ui->cb_stop_call_notification(line->get_line_number());
+ ui->cb_call_failed(user_config, line->get_line_number(), r);
+ line->call_hist_record.fail_call(r);
+ remove_client_request(&req_out_invite);
+ state = DS_TERMINATED;
+ break;
+ }
+
+ // Notify progress to the referror if this is a referred call
+ if (is_referred_call) {
+ get_phone()->notify_refer_progress(r, line->get_line_number());
+ }
+}
+
+void t_dialog::state_early(t_line_timer timer) {
+ switch (timer) {
+ case LTMR_CANCEL_GUARD:
+ log_file->write_report("Timer LTMR_CANCEL_GUARD expired.",
+ "t_dialog::state_early", LOG_NORMAL, LOG_WARNING);
+
+ // CANCEL has been responded to, but 487 on INVITE was never
+ // received. Abort the INVITE transaction.
+ if (req_out_invite) {
+ t_tid _tid = req_out_invite->get_tid();
+ if (_tid > 0) {
+ evq_trans_mgr->push_abort_trans(_tid);
+ }
+ }
+ break;
+ default:
+ // Ignore other timeouts
+ break;
+ }
+}
+
+// BYE sent. Waiting for response.
+void t_dialog::state_w4bye_resp(t_response *r, t_tuid tuid, t_tid tid) {
+ if (r->hdr_cseq.method != BYE) return;
+
+ switch (r->get_class()) {
+ case R_1XX:
+ // Provisional response received. Wait for final response.
+ break;
+ default:
+ // All final responses terminate the dialog.
+ remove_client_request(&req_out);
+ if (!sub_refer) {
+ state = DS_TERMINATED;
+ } else {
+ state = DS_CONFIRMED_SUB;
+ if (sub_refer->get_role() == SR_SUBSCRIBER) {
+ // End subscription
+ sub_refer->unsubscribe();
+ }
+ }
+ break;
+ }
+}
+
+void t_dialog::state_w4bye_resp(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+
+ switch(r->method) {
+ case BYE:
+ resp = r->create_response(R_200_OK);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ // A BYE glare situation. Keep waiting for the BYE
+ // response.
+ break;
+ default:
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ break;
+ }
+}
+
+// Confirmed dialog. Responses are for mid-dialog requests.
+void t_dialog::state_confirmed_resp(t_response *r, t_tuid tuid, t_tid tid) {
+ // 1XX responses are not expected. If they are received
+ // then simply ignore them.
+ if (r->is_provisional()) return;
+
+ switch (r->hdr_cseq.method) {
+ case OPTIONS:
+ ui->cb_options_response(r);
+ remove_client_request(&req_out);
+ break;
+ case REFER:
+ remove_client_request(&req_refer);
+
+ if (refer_state != REFST_W4RESP) {
+ // NOTIFY has already been received. No need to
+ // process the REFER response anymore. Interesting
+ // issue might be: what if NOTIFY has been received and
+ // now a failure response comes in?
+ break;
+ }
+
+ if (!r->is_success()) {
+ // REFER failed
+ refer_state = REFST_NULL;
+ refer_succeeded = false;
+
+ // KLUDGE: only signal REFER failure in case of
+ // non-408/481 responses. These responses
+ // clear the line, so the upper layers should not
+ // take action on the failed refer.
+ if (r->code != R_408_REQUEST_TIMEOUT ||
+ r->code == R_481_TRANSACTION_NOT_EXIST)
+ {
+ out_refer_req_failed = true;
+ }
+
+ ui->cb_refer_failed(line->get_line_number(), r);
+ break;
+ }
+
+ refer_state = REFST_W4NOTIFY;
+ break;
+ case INFO:
+ remove_client_request(&req_info);
+
+ if (!dtmf_queue.empty()) {
+ char digit = dtmf_queue.front();
+ dtmf_queue.pop();
+ send_dtmf(digit, false, true);
+ }
+
+ break;
+ default:
+ // The received response should match the pending request.
+ // So this point should never be reached.
+ assert(false);
+ break;
+ }
+
+ // RFC 3261 12.2.1.2
+ // If a mid-dialog request is timed out, or the call/transaction
+ // does not exist anymore at the server, then terminate the
+ // dialog.
+ if (r->code == R_408_REQUEST_TIMEOUT ||
+ r->code == R_481_TRANSACTION_NOT_EXIST)
+ {
+ send_bye();
+ }
+}
+
+void t_dialog::state_w4re_invite_resp(t_response *r, t_tuid tuid, t_tid tid) {
+ if (r->hdr_cseq.method != INVITE) return;
+
+ switch (r->get_class()) {
+ case R_1XX:
+ if (r->code == R_100_TRYING) break;
+
+ // RFC 3262 4
+ // Discard retransmissiona and out-of-sequence reliable
+ // provisional responses.
+ if (must_discard_100rel(r)) return;
+
+ if (state == DS_W4RE_INVITE_RESP2) {
+ // RFC 3262
+ // Discard retransmissions and out-of-order
+ // reliable provisional responses.
+ if (must_discard_100rel(r)) return;
+ }
+
+ // RFC 3262
+ // Send PRACK if required
+ send_prack_if_required(r);
+
+ // fall thru
+ case R_2XX:
+ // Process SDP answer if answer is present and no
+ // answer has been received yet.
+ if (!session_re_invite->recvd_answer && r->body) {
+ int warn_code;
+ string warn_text;
+
+ if (r->body->get_type() != BODY_SDP) {
+ // Only SDP bodies are supported
+ ui->cb_unsupported_content_type(
+ line->get_line_number(), r);
+ request_cancelled = true;
+ } else if (session_re_invite->
+ process_sdp_answer((t_sdp *)r->body,
+ warn_code, warn_text))
+ {
+ session_re_invite->recvd_answer = true;
+ } else {
+ // SDP answer is not supported. Cancel
+ // the INVITE.
+ request_cancelled = true;
+ ui->cb_sdp_answer_not_supported(
+ line->get_line_number(), warn_text);
+ break;
+ }
+ }
+
+ // This implementation always sends an offer in
+ // INVITE. So an answer must be in a 2XX response
+ // as PRACK is not supported.
+ if (r->get_class() == R_2XX && !r->body) {
+ request_cancelled = true;
+ ui->cb_sdp_answer_missing(line->get_line_number());
+ break;
+ }
+
+ // Refresh target URI and display name
+ if (r->get_class() == R_2XX &&
+ r->hdr_contact.is_populated() &&
+ r->hdr_contact.contact_list.size() > 0)
+ {
+ remote_target_uri = r->
+ hdr_contact.contact_list.front().uri;
+ remote_target_display = r->
+ hdr_contact.contact_list.front().display;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ switch (r->get_class()) {
+ case R_1XX:
+ // Provisional response received.
+ state = DS_W4RE_INVITE_RESP2;
+
+ // Start re-INVITE guard timer (no RFC requirement)
+ line->start_timer(LTMR_RE_INVITE_GUARD, get_object_id());
+
+ // User indicated that the request should be cancelled.
+ // Now that the first provional response has been received,
+ // a CANCEL can be sent.
+ if (request_cancelled) {
+ send_cancel(true);
+ }
+
+ break;
+ case R_2XX:
+ // Success received.
+ line->stop_timer(LTMR_RE_INVITE_GUARD, get_object_id());
+
+ ack_2xx_invite(r);
+ ui->cb_reinvite_success(line->get_line_number(), r);
+ state = DS_CONFIRMED;
+
+ if (request_cancelled) {
+ // User indicated that the request should be cancelled,
+ // but no response was received yet. A final response
+ // has been received. Instead of CANCEL a BYE will be
+ // sent now.
+ send_bye();
+ } else if (end_after_2xx_invite) {
+ // Or user cancelled the request already, but the 2XX
+ // glared with CANCEL.
+ log_file->write_report("CANCEL / 2XX INVITE glare.",
+ "t_dialog::state_w4invite_resp");
+ send_bye();
+ } else {
+ // Make the re-INIVTE session info the current info
+ activate_new_session();
+ }
+
+ break;
+ case R_3XX:
+ case R_4XX:
+ case R_5XX:
+ case R_6XX:
+ default:
+ // Final response (failure) received.
+ // Treat unknown response classes as failure.
+ line->stop_timer(LTMR_RE_INVITE_GUARD, get_object_id());
+ ui->cb_reinvite_failed(line->get_line_number(), r);
+ remove_client_request(&req_out_invite);
+
+ // RFC 3261 14.1
+ // delete re-INVITE session info. Old session info
+ // stays as re-INVITE failed.
+ MEMMAN_DELETE(session_re_invite);
+ delete session_re_invite;
+ session_re_invite = NULL;
+
+ state = DS_CONFIRMED;
+
+ switch(reinvite_purpose) {
+ case REINVITE_HOLD:
+ // A call hold may not fail for the user as
+ // this cause problems with soundcard access and
+ // showing line status in the GUI. Even though re-INVITE
+ // failed, the RTP still stopped. So simply indicated
+ // that the hold failed, such that a subsequent retrieve
+ // can simply restart the RTP.
+ hold_failed = true;
+ break;
+ case REINVITE_RETRIEVE:
+ line->failed_retrieve();
+ if (r->code != R_491_REQUEST_PENDING) {
+ ui->cb_retrieve_failed(line->get_line_number(), r);
+ }
+ break;
+ default:
+ assert(false);
+ }
+
+ // RFC 3261 14.1
+ // Start wait timer before retrying a re-INVITE after a
+ // glare.
+ if (r->code == R_491_REQUEST_PENDING) {
+ line->start_timer(LTMR_GLARE_RETRY, get_object_id());
+ }
+
+ // RFC 3261 14.1
+ if (r->code == R_408_REQUEST_TIMEOUT ||
+ r->code == R_481_TRANSACTION_NOT_EXIST)
+ {
+ send_bye();
+ }
+ break;
+ }
+}
+
+void t_dialog::state_w4re_invite_resp(t_line_timer timer) {
+ switch(timer) {
+ case LTMR_RE_INVITE_GUARD:
+ // Abort the INVITE as the user cannot terminate
+ // it in a normal way.
+ if (req_out_invite) {
+ t_tid _tid = req_out_invite->get_tid();
+ if (_tid > 0) {
+ evq_trans_mgr->push_abort_trans(_tid);
+ }
+ } else {
+ // Consider this as if a 408 Timeout response has
+ // been received. Terminate the dialog.
+ send_bye();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void t_dialog::activate_new_session(void) {
+ if (session->equal_audio(*session_re_invite)) {
+ log_file->write_report("SDP in re-INVITE is a noop.",
+ "t_dialog::activate_new_session");
+
+ MEMMAN_DELETE(session_re_invite);
+ delete session_re_invite;
+ session_re_invite = NULL;
+ return;
+ }
+
+ log_file->write_report("Renew session as specified by SDP in re-INVITE.",
+ "t_dialog::activate_new_session");
+
+ // Stop current session
+ MEMMAN_DELETE(session);
+ delete session;
+
+ // Create new session
+ session = session_re_invite;
+ session_re_invite = NULL;
+ session->start_rtp();
+}
+
+void t_dialog::process_1xx_2xx_invite_resp(t_response *r) {
+ t_user *user_config = phone_user->get_user_profile();
+
+ // Process SDP answer if answer is present and no
+ // answer has been received yet.
+ if (r->body) {
+ int warn_code;
+ string warn_text;
+
+ if (r->body->get_type() != BODY_SDP) {
+ // Only SDP bodies are supported
+ ui->cb_unsupported_content_type(line->get_line_number(), r);
+ request_cancelled = true;
+ } else if (!session->recvd_answer ||
+ (user_config->get_allow_sdp_change() &&
+ ((t_sdp *)r->body)->origin.session_version !=
+ session->dst_sdp_version))
+ {
+ // Only process SDP if no SDP was received yet (RFC 3261
+ // 13.3.1. Or process SDP if overridden by the
+ // allow_sdp_change setting in the user profile.
+ // A changed SDP must have a new version number (RFC 3264)
+ if (session->process_sdp_answer((t_sdp *)r->body,
+ warn_code, warn_text))
+ {
+ // If this is a changed SDP, then stop the
+ // current RTP stream based on the previous SDP.
+ if (session->recvd_answer) session->stop_rtp();
+
+ session->recvd_answer = true;
+
+ // The following code part handles the ugly interaction
+ // between forking and early media (Vonage uses this).
+ // In case of forking 1xx responses with SDP may com
+ // from different destinations. Only the first 1xx will
+ // create a media stream. Media streams on other legs cannot
+ // be created as that would give sound conflicts.
+ // When a 2xx response with SDP is received, an early media
+ // stream on another leg must be killed.
+ // Due to forking multiple 2xx repsonses from different
+ // destinations may be received. Only the first 2xx response
+ // will create a media session. The other dialogs receiving
+ // a 2xx will be released immediately anyway (see line.cpp).
+ bool start_media = true;
+ t_dialog *d = line->get_dialog_with_active_session();
+ if (d != NULL) {
+ if (r->get_class() == R_2XX &&
+ d->get_state() != DS_CONFIRMED)
+ {
+ log_file->write_header(
+ "t_dialog::process_1xx_2xx_invite_resp");
+ log_file->write_raw(
+ "Kill early media on another dialog, id=");
+ log_file->write_raw(d->get_object_id());
+ log_file->write_endl();
+ log_file->write_footer();
+
+ d->kill_rtp();
+ } else {
+ log_file->write_header(
+ "t_dialog::process_1xx_2xx_invite_resp");
+ log_file->write_raw(
+ "Cannot start media as another dialog (id=");
+ log_file->write_raw(d->get_object_id());
+ log_file->write_raw(") already has media.\n");
+ log_file->write_footer();
+
+ start_media = false;
+ }
+ }
+
+ if (start_media) {
+ if (r->is_provisional()) {
+ log_file->write_report("Starting early media.",
+ "t_dialog::process_1xx_2xx_invite_resp");
+ }
+
+ // Stop locally played tones to free the soundcard
+ // for the voice stream
+ ui->cb_stop_call_notification(line->get_line_number());
+
+ session->start_rtp();
+ }
+ } else {
+ // SDP answer is not supported. Cancel
+ // the INVITE.
+ request_cancelled = true;
+ ui->cb_sdp_answer_not_supported(
+ line->get_line_number(), warn_text);
+ }
+ }
+ } else if (r->code == R_180_RINGING &&
+ !ringing_received && !session->recvd_answer)
+ {
+ // There is no SDP and far-end indicated that it is ringing
+ // so generate ring back tone locally.
+ ui->cb_play_ringback(user_config);
+ ringing_received = true;
+ }
+
+ // This implementation always sends an offer in
+ // INVITE. So an answer must be in a 2XX response if
+ // no answer has been received in a provisional response.
+ if (!session->recvd_answer && r->get_class() == R_2XX && !r->body) {
+ request_cancelled = true;
+ ui->cb_sdp_answer_missing(line->get_line_number());
+ }
+
+ // RFC 3261 13.3.1.4
+ // A 2XX response to an INVITE should contain a Supported header
+ // listing all supported extensions.
+ // Set extensions supported by remote party
+ if (r->get_class() == R_2XX && r->hdr_supported.is_populated()) {
+ remote_extensions.insert(r->hdr_supported.features.begin(),
+ r->hdr_supported.features.end());
+ }
+}
+
+void t_dialog::ack_2xx_invite(t_response *r) {
+ t_ip_port ip_port;
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (ack) {
+ // delete previous cached ACK
+ MEMMAN_DELETE(ack);
+ delete ack;
+ }
+ ack = create_request(ACK);
+ ack->get_destination(ip_port, *user_config);
+
+ // If for some strange reason the destination could
+ // not be computed then wait for a retransmission of
+ // 2XX.
+ if (ip_port.ipaddr != 0 && ip_port.port != 0) {
+ evq_sender->push_network(ack, ip_port);
+ } else {
+ log_file->write_header("t_dialog::ack_2xx_invite", LOG_SIP, LOG_CRITICAL);
+ log_file->write_raw("Cannot determine destination IP address for ACK.\n\n");
+ log_file->write_raw(ack->encode());
+ log_file->write_footer();
+ }
+
+ remove_client_request(&req_out_invite);
+}
+
+void t_dialog::send_prack_if_required(t_response *r) {
+ t_user *user_config = phone_user->get_user_profile();
+
+ // RFC 3262
+ // Send PRACK if needed
+ if (r->get_class() == R_1XX && r->code != R_100_TRYING) {
+ // RFC 3262 4
+ // Send PRACK if the 1xx response is sent reliable and 100rel
+ // is enabled.
+ if (r->hdr_to.tag.size() > 0 &&
+ r->hdr_require.contains(EXT_100REL) &&
+ r->hdr_rseq.is_populated() &&
+ remote_target_uri.is_valid() &&
+ user_config->get_ext_100rel() != EXT_DISABLED)
+ {
+ t_request *prack = create_request(PRACK);
+ prack->hdr_rack.set_method(r->hdr_cseq.method);
+ prack->hdr_rack.set_cseq_nr(r->hdr_cseq.seqnr);
+ prack->hdr_rack.set_resp_nr(r->hdr_rseq.resp_nr);
+
+ // Delete previous PRACK request if it is still pending
+ if (req_prack) {
+ log_file->write_report("Previous PRACK still pending.",
+ "t_dialog::send_prack_if_needed");
+ remove_client_request(&req_prack);
+ }
+
+ req_prack = new t_client_request(user_config, prack, 0);
+ MEMMAN_NEW(req_prack);
+ line->send_request(prack, req_prack->get_tuid());
+ MEMMAN_DELETE(prack);
+ delete prack;
+ }
+ }
+}
+
+bool t_dialog::must_discard_100rel(t_response *r) {
+ t_user *user_config = phone_user->get_user_profile();
+
+ // RFC 3262 4
+ // Discard retransmissiona and out-of-sequence reliable
+ // provisional responses.
+ if (r->code > R_100_TRYING && r->hdr_to.tag.size() > 0 &&
+ r->hdr_require.contains(EXT_100REL) &&
+ r->hdr_rseq.is_populated() &&
+ user_config->get_ext_100rel() != EXT_DISABLED)
+ {
+ if (remote_resp_nr == 0) {
+ // This is the first response with a repsonse nr.
+ // Initialize the remote response nr
+ remote_resp_nr = r->hdr_rseq.resp_nr;
+ return false;
+ }
+
+ if (r->hdr_rseq.resp_nr <= remote_resp_nr) {
+ // This is a retransmission.
+ // PRACK has already been sent. The transaction
+ // layer takes care of retransmitting PRACK
+ // if PRACK got lost.
+ log_file->write_report("Discard 1xx retransmission.",
+ "t_dialog::must_discard_100rel");
+ return true;
+ }
+
+ if (r->hdr_rseq.resp_nr != remote_resp_nr + 1) {
+ // A provisional response has been lost.
+ // Discard this response and wait for a retransmission
+ // of the lost response.
+ log_file->write_report("Discard out-of-order 1xx",
+ "t_dialog::must_discard_100rel");
+ return true;
+ }
+ }
+
+ remote_resp_nr = r->hdr_rseq.resp_nr;
+ return false;
+}
+
+bool t_dialog::respond_prack(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+
+ // RFC 3262 3
+ if (resp_1xx_invite &&
+ r->hdr_rack.method == resp_1xx_invite->hdr_cseq.method &&
+ r->hdr_rack.cseq_nr == resp_1xx_invite->hdr_cseq.seqnr &&
+ r->hdr_rack.resp_nr == resp_1xx_invite->hdr_rseq.resp_nr)
+ {
+ // The provisional response has been delivered now.
+ line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id());
+ line->stop_timer(LTMR_100REL_GUARD, get_object_id());
+ MEMMAN_DELETE(resp_1xx_invite);
+ delete resp_1xx_invite;
+ resp_1xx_invite = NULL;
+
+ // Send 200 on the PRACK request
+ resp = r->create_response(R_200_OK);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return true;
+ } else {
+ // PRACK does not match pending 1xx response
+ // Send a 481 on the PRACK request
+ resp = r->create_response(R_481_TRANSACTION_NOT_EXIST);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return false;
+ }
+}
+
+void t_dialog::send_request(t_request *r, t_tuid tuid) {
+ line->send_request(r, tuid);
+}
+
+////////////
+// Public
+////////////
+
+t_dialog::t_dialog(t_line *_line) :
+ t_abstract_dialog(_line->get_phone_user())
+{
+ line = _line;
+
+ req_out = NULL;
+ req_out_invite = NULL;
+ req_in_invite = NULL;
+ req_cancel = NULL;
+ req_prack = NULL;
+ req_refer = NULL;
+ req_info = NULL;
+ req_stun = NULL;
+
+ request_cancelled = false;
+ end_after_ack = false;
+ end_after_2xx_invite = false;
+ answer_after_prack = false;
+ ringing_received = false;
+
+ resp_invite = NULL;
+ resp_1xx_invite = NULL;
+ ack = NULL;
+
+ state = DS_NULL;
+
+ // Timers
+ dur_ack_timeout = 0;
+ id_ack_timeout = 0;
+ id_ack_guard = 0;
+ id_re_invite_guard = 0;
+ id_glare_retry = 0;
+ id_cancel_guard = 0;
+
+ // RFC 3262
+ // Timers
+ dur_100rel_timeout = 0;
+ id_100rel_timeout = 0;
+ id_100rel_guard = 0;
+
+ t_user *user_config = phone_user->get_user_profile();
+
+ // Create session
+ session = new t_session(this, USER_HOST(user_config, AUTO_IP4_ADDRESS), line->get_rtp_port());
+ MEMMAN_NEW(session);
+ session_re_invite = NULL;
+
+ // Subscription
+ sub_refer = NULL;
+ is_referred_call = false;
+ refer_state = REFST_NULL;
+ refer_accepted = false;
+ refer_succeeded = false;
+ out_refer_req_failed = false;
+}
+
+t_dialog::~t_dialog() {
+ if (req_out) remove_client_request(&req_out);
+ if (req_out_invite) remove_client_request(&req_out_invite);
+ if (req_in_invite) remove_client_request(&req_in_invite);
+ if (req_cancel) remove_client_request(&req_cancel);
+ if (req_prack) remove_client_request(&req_prack);
+ if (req_refer) remove_client_request(&req_refer);
+ if (req_info) remove_client_request(&req_info);
+ if (req_stun) remove_client_request(&req_stun);
+ if (resp_invite) { MEMMAN_DELETE(resp_invite); delete resp_invite; }
+ if (resp_1xx_invite) {
+ MEMMAN_DELETE(resp_1xx_invite);
+ delete resp_1xx_invite;
+ }
+ if (ack) { MEMMAN_DELETE(ack); delete ack; }
+ if (session) { MEMMAN_DELETE(session); delete session; }
+ if (session_re_invite) {
+ MEMMAN_DELETE(session_re_invite);
+ delete session_re_invite;
+ }
+ if (sub_refer) { MEMMAN_DELETE(sub_refer); delete sub_refer; }
+
+ for (list<t_client_request *>::iterator i = inc_req_queue.begin();
+ i != inc_req_queue.end(); i++)
+ {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+}
+
+// Copy will only be used on the open dialog.
+t_dialog *t_dialog::copy(void) {
+ t_dialog *d = new t_dialog(*this);
+ MEMMAN_NEW(d);
+
+ d->generate_new_id();
+
+ // Increment reference count on client request
+ if (req_out) d->req_out->inc_ref_count();
+ if (req_out_invite) d->req_out_invite->inc_ref_count();
+ if (req_in_invite) d->req_in_invite->inc_ref_count();
+ if (req_prack) d->req_prack->inc_ref_count();
+ if (req_refer) d->req_refer->inc_ref_count();
+ if (req_stun) d->req_stun->inc_ref_count();
+
+ // The open dialog will handle the CANCEL, so delete it
+ // from the copy.
+ if (req_cancel) d->req_cancel = NULL;
+
+ if (resp_invite) d->resp_invite = (t_response *)resp_invite->copy();
+ if (resp_1xx_invite) d->resp_1xx_invite = (t_response *)resp_1xx_invite->copy();
+ if (ack) d->ack = (t_request *)ack->copy();
+ dur_ack_timeout = 0;
+ id_ack_timeout = 0;
+ id_ack_guard = 0;
+ dur_100rel_timeout = 0;
+ id_100rel_timeout = 0;
+ id_100rel_guard = 0;
+
+ if (session) {
+ d->session = new t_session(*session);
+ MEMMAN_NEW(d->session);
+ d->session->set_owner(d);
+
+ // If an audio session was already created for early media
+ // then the audio session will be moved to the copy of the
+ // dialog. Only 1 dialog can have an audio session.
+ // See process_1xx_2xx_invite_resp for more information on
+ // early media problems.
+ // Clear a possible audio session in the open dialog.
+ t_audio_session *as = session->get_audio_session();
+ if (as) {
+ as->set_session(d->session);
+ session->set_audio_session(NULL);
+ log_file->write_report(
+ "An audio session was created on an open dialog.",
+ "t_dialog::copy",
+ LOG_NORMAL, LOG_DEBUG);
+ }
+ }
+
+ log_file->write_header("t_dialog::copy", LOG_NORMAL, LOG_DEBUG);
+ log_file->write_raw("Created dialog through copy, id=");
+ log_file->write_raw(d->get_object_id());
+ log_file->write_endl();
+ log_file->write_footer();
+
+ return d;
+}
+
+void t_dialog::send_invite(const t_url &to_uri, const string &to_display,
+ const string &subject, const t_hdr_referred_by &hdr_referred_by,
+ const t_hdr_replaces &hdr_replaces,
+ const t_hdr_require &hdr_require,
+ const t_hdr_request_disposition &hdr_request_disposition,
+ bool anonymous)
+{
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (state != DS_NULL) {
+ throw X_DIALOG_ALREADY_ESTABLISHED;
+ }
+
+ // If STUN is enabled, then first send a STUN binding request to
+ // discover the IP adderss and port for media.
+ if (phone->use_stun(user_config)) {
+ if (!stun_bind_media()) {
+ ui->cb_stun_failed_call_ended(line->get_line_number());
+ state = DS_TERMINATED;
+ return;
+ }
+ }
+
+ t_request invite(INVITE);
+
+ // RFC 3261 12.2.1.1
+ // Request URI and Route header
+ invite.set_route(to_uri, phone_user->get_service_route());
+
+ // Set Call-ID header
+ call_id = NEW_CALL_ID(user_config);
+ invite.hdr_call_id.set_call_id(call_id);
+ call_id_owner = true;
+
+ // Set To header
+ invite.hdr_to.set_uri(to_uri);
+ invite.hdr_to.set_display(to_display);
+
+ // Set From header
+ local_tag = NEW_TAG;
+ local_uri.set_url(line->create_user_uri());
+ local_display = user_config->get_display(anonymous);
+ invite.hdr_from.set_uri(local_uri);
+ invite.hdr_from.set_display(local_display);
+ invite.hdr_from.set_tag(local_tag);
+
+ // Privacy header
+ if (line->get_hide_user()) {
+ invite.hdr_privacy.add_privacy(PRIVACY_ID);
+ }
+
+ // Set P-Preferred-Identity header
+ if (anonymous && user_config->get_send_p_preferred_id()) {
+ t_identity identity;
+ identity.set_uri(user_config->create_user_uri(false));
+ identity.set_display(user_config->get_display(false));
+ invite.hdr_p_preferred_identity.add_identity(identity);
+ }
+
+ // Set CSeq header
+ local_seqnr = rand() % 1000 + 1;
+ invite.hdr_cseq.set_method(INVITE);
+ invite.hdr_cseq.set_seqnr(local_seqnr);
+
+ // Set Max-Forwards header
+ invite.hdr_max_forwards.set_max_forwards(MAX_FORWARDS);
+
+ // User-Agent
+ SET_HDR_USER_AGENT(invite.hdr_user_agent);
+
+ // RFC 3261 13.2.1
+ // Allow and Supported headers
+ SET_HDR_ALLOW(invite.hdr_allow, user_config);
+ SET_HDR_SUPPORTED(invite.hdr_supported, user_config);
+
+ // Extensions specific for INVITE
+ if (user_config->get_ext_100rel() != EXT_DISABLED) {
+ invite.hdr_supported.add_feature(EXT_100REL);
+ }
+
+ // Require header
+ switch (user_config->get_ext_100rel()) {
+ case EXT_PREFERRED:
+ case EXT_REQUIRED:
+ invite.hdr_require.add_feature(EXT_100REL);
+ break;
+ default:
+ break;
+ }
+
+ // Subject header
+ if (subject != "") {
+ invite.hdr_subject.set_subject(subject);
+ }
+
+ // Organization
+ if (!anonymous) {
+ SET_HDR_ORGANIZATION(invite.hdr_organization, user_config);
+ }
+
+ // RFC 3892 Referred-By header if a call is initated because
+ // of an incoming REFER.
+ invite.hdr_referred_by = hdr_referred_by;
+
+ // RFC 3891 Replaces header
+ invite.hdr_replaces = hdr_replaces;
+
+ // Add required extension passed by the upper layer
+ if (hdr_require.is_populated()) {
+ invite.hdr_require.add_features(hdr_require.features);
+ }
+
+ // RFC 3841 Request-Disposition header
+ invite.hdr_request_disposition = hdr_request_disposition;
+
+ // Calculate destinations
+ // See create_request() for more comments
+ invite.calc_destinations(*user_config);
+
+ // The Contatc, Via header and SDP can only be created after the destinations
+ // are calculated, because the destination deterimines which
+ // local IP address should be used.
+
+ // Create SDP offer
+ session->create_sdp_offer(&invite, SDP_O_USER);
+
+ // Set Via header
+ unsigned long local_ip = invite.get_local_ip();
+ t_via via(USER_HOST(user_config, h_ip2str(local_ip)), PUBLIC_SIP_PORT(user_config));
+ invite.hdr_via.add_via(via);
+
+ // Set Contact header
+ t_contact_param contact;
+ contact.uri.set_url(line->create_user_contact(h_ip2str(local_ip)));
+ invite.hdr_contact.add_contact(contact);
+
+ // Send INVITE
+ req_out_invite = new t_client_request(user_config, &invite, 0);
+ MEMMAN_NEW(req_out_invite);
+
+ // Trigger call script
+ t_call_script script(user_config, t_call_script::TRIGGER_OUT_CALL,
+ line->get_line_number() + 1);
+ script.exec_notify(&invite);
+
+ line->send_request(&invite, req_out_invite->get_tuid());
+ line->call_hist_record.start_call(&invite, t_call_record::DIR_OUT,
+ user_config->get_profile_name());
+
+ state = DS_W4INVITE_RESP;
+}
+
+bool t_dialog::resend_invite_auth(t_response *resp) {
+ t_user *user_config = phone_user->get_user_profile();
+ if (!req_out_invite) return false;
+
+ assert(state == DS_W4INVITE_RESP || state == DS_W4INVITE_RESP2);
+
+ t_request *req = req_out_invite->get_request();
+
+ // Add authorization header, increment CSeq and create new branch id
+ if (get_phone()->authorize(user_config, req, resp)) {
+ resend_request(req_out_invite);
+
+ // Reset state in case a 100 Trying was received
+ state = DS_W4INVITE_RESP;
+
+ return true;
+ }
+
+ return false;
+}
+
+bool t_dialog::resend_invite_unsupported(t_response *resp) {
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (!req_out_invite) return false;
+ if (resp->code != R_420_BAD_EXTENSION) return false;
+ if (!resp->hdr_unsupported.is_populated()) return false;
+ if (resp->hdr_unsupported.features.empty()) return false;
+
+ t_request *req = req_out_invite->get_request();
+
+ // If no extensions were required then return.
+ if (!req->hdr_require.is_populated()) return false;
+ if (req->hdr_require.features.empty()) return false;
+
+ bool removed_ext = false;
+
+ for (list<string>::iterator i = resp->hdr_unsupported.features.begin();
+ i != resp->hdr_unsupported.features.end(); i++)
+ {
+ if (req->hdr_require.contains(*i)) {
+ if (*i == EXT_100REL) {
+ if (user_config->get_ext_100rel() == EXT_PREFERRED) {
+ req->hdr_require.del_feature(*i);
+ } else {
+ // The 100rel is required.
+ return false;
+ }
+ } else {
+ // There is no specific requirement for
+ // this extension so do not remove it.
+ return false;
+ }
+
+ removed_ext = true;
+ }
+ }
+
+ // Return if none of the unsupported extensions was required.
+ if (!removed_ext) return false;
+
+ if (req->hdr_require.features.empty()) {
+ // There are no required features anymore
+ req->hdr_require.unpopulate();
+ }
+
+ resend_request(req_out_invite);
+
+ // Reset state in case a 100 Trying was received
+ state = DS_W4INVITE_RESP;
+
+ return true;
+}
+
+bool t_dialog::redirect_invite(t_response *resp) {
+ t_contact_param contact;
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (!req_out_invite) return false;
+
+ // If the response is a 3XX response then add redirection contacts
+ if (resp->get_class() == R_3XX && resp->hdr_contact.is_populated()) {
+ req_out_invite->redirector.add_contacts(
+ resp->hdr_contact.contact_list);
+ }
+
+ // Get next destination
+ if (!req_out_invite->redirector.get_next_contact(contact)) {
+ // There is no next destination
+ return false;
+ }
+
+ assert(state == DS_W4INVITE_RESP || state == DS_W4INVITE_RESP2);
+
+ t_request *req = req_out_invite->get_request();
+
+ // Ask user for permission to redirect if indicated by user config
+ if (user_config->get_ask_user_to_redirect()) {
+ if(!ui->cb_ask_user_to_redirect_invite(user_config,
+ contact.uri, contact.display))
+ {
+ // User did not permit to redirect
+ return false;
+ }
+ }
+
+ // Change the request URI to the new URI.
+ // As the URI changes the destination set must be recalculated
+ req->uri = contact.uri;
+ req->calc_destinations(*user_config);
+
+ ui->cb_redirecting_request(user_config, line->get_line_number(), contact);
+ resend_request(req_out_invite);
+
+ // Reset state in case a 100 Trying was received
+ state = DS_W4INVITE_RESP;
+
+ return true;
+}
+
+bool t_dialog::failover_invite(void) {
+ if (!req_out_invite) return false;
+
+ log_file->write_report("Failover to next destination.",
+ "t_dialog::failover_invite");
+
+ t_request *req = req_out_invite->get_request();
+
+ // Get next destination
+ if (!req->next_destination()) {
+ log_file->write_report("No next destination for failover.",
+ "t_dialog::failover_invite");
+ return false;
+ }
+
+ assert(state == DS_W4INVITE_RESP || state == DS_W4INVITE_RESP2);
+ resend_request(req_out_invite);
+
+ // Reset state in case a 100 Trying was received
+ state = DS_W4INVITE_RESP;
+
+ return true;
+}
+
+void t_dialog::send_bye(void) {
+ t_user *user_config = phone_user->get_user_profile();
+
+ switch (state) {
+ case DS_W4INVITE_RESP2:
+ case DS_EARLY:
+ case DS_CONFIRMED:
+ break;
+ case DS_W4RE_INVITE_RESP:
+ case DS_W4RE_INVITE_RESP2:
+ // send BYE after completion of re-INVITE
+ request_cancelled = true;
+ return;
+ case DS_W4ACK:
+ case DS_W4ACK_RE_INVITE:
+ // send BYE after completion of re-INVITE
+ request_cancelled = true;
+ return;
+ case DS_TERMINATED:
+ // Dialog has already been terminated. Do not send BYE.
+ return;
+ default:
+ log_file->write_header("t_dialog::failover_invite",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("Cannot send BYE on dialog in state ");
+ log_file->write_raw(state);
+ log_file->write_endl();
+ log_file->write_footer();
+ return;
+ }
+
+ // If a previous request is still pending then remove it.
+ if (req_out) { MEMMAN_DELETE(req_out); delete (req_out); }
+
+ t_request *bye = create_request(BYE);
+ req_out = new t_client_request(user_config, bye, 0);
+ MEMMAN_NEW(req_out);
+
+ // Trigger call script
+ t_call_script script(user_config, t_call_script::TRIGGER_LOCAL_RELEASE,
+ line->get_line_number() + 1);
+ script.exec_notify(bye);
+
+ line->send_request(bye, req_out->get_tuid());
+ line->call_hist_record.end_call(false);
+ MEMMAN_DELETE(bye);
+ delete bye;
+
+ state = DS_W4BYE_RESP;
+ ui->cb_call_ended(line->get_line_number());
+}
+
+void t_dialog::send_options(void) {
+ t_user *user_config = phone_user->get_user_profile();
+
+ // Request can only be sent in a confirmed dialog.
+ if (state != DS_CONFIRMED) return;
+
+ // If a previous request is still pending then remove it.
+ if (req_out) { MEMMAN_DELETE(req_out); delete (req_out); }
+
+ t_request *r = create_request(OPTIONS);
+
+ // Accept
+ r->hdr_accept.add_media(t_media("application","sdp"));
+ req_out = new t_client_request(user_config, r, 0);
+ MEMMAN_NEW(req_out);
+ line->send_request(r, req_out->get_tuid());
+ MEMMAN_DELETE(r);
+ delete r;
+}
+
+void t_dialog::send_cancel(bool early_dialog_exists) {
+ t_request *cancel;
+ t_user *user_config = phone_user->get_user_profile();
+
+ switch (state) {
+ case DS_W4INVITE_RESP:
+ case DS_W4RE_INVITE_RESP:
+ if (!early_dialog_exists) {
+ // wait for first response then send CANCEL or BYE
+ request_cancelled = true;
+ break;
+ }
+ // Fall through
+ case DS_W4INVITE_RESP2:
+ case DS_W4RE_INVITE_RESP2:
+ case DS_EARLY:
+ if (req_cancel) {
+ // CANCEL has been sent already
+ break;
+ }
+
+ cancel = create_request(CANCEL);
+ req_cancel = new t_client_request(user_config, cancel, 0);
+ MEMMAN_NEW(req_cancel);
+ line->send_request(cancel, req_cancel->get_tuid());
+ MEMMAN_DELETE(cancel);
+ delete cancel;
+
+ // Make sure dialog is terminated if CANCEL glares with
+ // 2XX on INVITE.
+ set_end_after_2xx_invite(true);
+ break;
+ default:
+ break;
+ }
+
+ ui->cb_call_ended(line->get_line_number());
+}
+
+void t_dialog::set_end_after_2xx_invite(bool on) {
+ end_after_2xx_invite = on;
+}
+
+void t_dialog::send_re_invite(void) {
+ assert(session_re_invite);
+ t_user *user_config = phone_user->get_user_profile();
+
+ // Request can only be sent in a confirmed dialog.
+ if (state != DS_CONFIRMED) return;
+
+ // Do nothing if a re-INVITE is already in progress
+ if (req_out_invite) return;
+
+ t_request *r = create_request(INVITE);
+
+ // Set Contact header
+ // INVITE must contain a contact header
+ t_contact_param contact;
+ contact.uri.set_url(line->create_user_contact(h_ip2str(r->get_local_ip())));
+ r->hdr_contact.add_contact(contact);
+
+ // RFC 3261 13.2.1
+ // Allow and Supported headers
+ SET_HDR_ALLOW(r->hdr_allow, user_config);
+ SET_HDR_SUPPORTED(r->hdr_supported, user_config);
+
+ // Extensions specific for INVITE
+ if (user_config->get_ext_100rel() != EXT_DISABLED) {
+ // If some weird far end implementation wants to send
+ // a reliable provisional then support it.
+ // As a provisional response not needed for a re-INVITE,
+ // do not require the 100rel.
+ r->hdr_supported.add_feature(EXT_100REL);
+ }
+
+ // Create SDP offer
+ session_re_invite->create_sdp_offer(r, SDP_O_USER);
+
+ // Send INVITE
+ req_out_invite = new t_client_request(user_config, r, 0);
+ MEMMAN_NEW(req_out_invite);
+ line->send_request(r, req_out_invite->get_tuid());
+ MEMMAN_DELETE(r);
+ delete r;
+
+ state = DS_W4RE_INVITE_RESP;
+}
+
+bool t_dialog::resend_request_auth(t_response *resp) {
+ t_client_request **current_cr;
+
+ switch (resp->hdr_cseq.method) {
+ case INVITE:
+ // re-INVITE
+ if (!req_out_invite) return false;
+ assert(state == DS_W4RE_INVITE_RESP ||
+ state == DS_W4RE_INVITE_RESP2);
+ current_cr = &req_out_invite;
+ break;
+ case PRACK:
+ if (!req_prack) return false;
+ current_cr = &req_prack;
+ break;
+ case REFER:
+ if (!req_refer) return false;
+ current_cr = &req_refer;
+ break;
+ case INFO:
+ if (!req_info) return false;
+ current_cr = &req_info;
+ break;
+ case SUBSCRIBE:
+ case NOTIFY:
+ if (!sub_refer) return false;
+ if (!sub_refer->req_out) return false;
+ current_cr = &(sub_refer->req_out);
+ break;
+ default:
+ // other requests
+ if (!req_out) return false;
+ current_cr = &req_out;
+ }
+
+ if (t_abstract_dialog::resend_request_auth(*current_cr, resp)) {
+ if (resp->hdr_cseq.method == INVITE) {
+ // Reset state in case a 100 Trying was received
+ state = DS_W4RE_INVITE_RESP;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool t_dialog::redirect_request(t_response *resp) {
+ t_client_request **current_cr;
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (resp->hdr_cseq.method == INVITE) {
+ // re-INVITE
+ if (!req_out_invite) return false;
+ assert(state == DS_W4RE_INVITE_RESP ||
+ state == DS_W4RE_INVITE_RESP2);
+ current_cr = &req_out_invite;
+ } else {
+ // non-INVITE
+ if (!req_out) return false;
+ current_cr = &req_out;
+ }
+
+ t_contact_param contact;
+ if (!t_abstract_dialog::redirect_request(*current_cr, resp, contact)) return false;
+
+ // Re-INVITE
+ if (resp->hdr_cseq.method == INVITE) {
+ // Reset state in case a 100 Trying was received
+ state = DS_W4RE_INVITE_RESP;
+ }
+
+ ui->cb_redirecting_request(user_config, line->get_line_number(), contact);
+ return true;
+}
+
+bool t_dialog::failover_request(t_response *resp) {
+ t_client_request **current_cr;
+
+ if (resp->hdr_cseq.method == INVITE) {
+ // re-INVITE
+ if (!req_out_invite) return false;
+ assert(state == DS_W4RE_INVITE_RESP ||
+ state == DS_W4RE_INVITE_RESP2);
+ current_cr = &req_out_invite;
+ } else {
+ // non-INVITE
+ if (!req_out) return false;
+ current_cr = &req_out;
+ }
+
+ if (!t_abstract_dialog::failover_request(*current_cr)) return false;
+
+ // Re-INVITE
+ if (resp->hdr_cseq.method == INVITE) {
+ // Reset state in case a 100 Trying was received
+ state = DS_W4RE_INVITE_RESP;
+ }
+
+ return true;
+}
+
+void t_dialog::hold(bool rtponly) {
+ assert(!session_re_invite);
+
+ // Stop glare retry timer
+ if (id_glare_retry) {
+ line->stop_timer(LTMR_GLARE_RETRY, get_object_id());
+ }
+
+ reinvite_purpose = REINVITE_HOLD;
+
+ if (rtponly) {
+ session->stop_rtp();
+ session->hold();
+
+ // Stopping the RTP only is like a full call hold where
+ // the re-INVITE failed. By setting the hold_failed flag,
+ // a subsequent retrieve will only start RTP.
+ hold_failed = true;
+ return;
+ }
+
+ hold_failed = false;
+ session_re_invite = session->create_call_hold();
+ send_re_invite();
+
+ // Stop the audio streams now. If we do not stop the stream now
+ // the stream will be stopped when a 200 OK is received on the
+ // re-INVITE. However, when the line is put on-hold because
+ // the user switches to another line that already has a held call
+ // a race condition might occur:
+ //
+ // 1. A re-INVITE on this line is sent to put it on-hold
+ // 2. A re-INVITE on the other line is sent to retrieve the call
+ // 3. If the 200 OK on the second re-INVITE comes in before the
+ // the 200 OK on the first re-INVITE, then the audio streams
+ // for the second line will be started already while the first
+ // line still has the audio device open. On some systems this
+ // causes a dead lock as the audio device may only be opened
+ // once.
+ //
+ // Also if the re-INVITE to put the line on-hold fails, the
+ // audio might not be stopped at all. It must be stopped however
+ // as the user has switched to the other line. So stopping the
+ // audio now will make sure the audio device is idle when the
+ // second call is retrieved.
+ session->stop_rtp();
+
+ // Prevent RTP stream from getting started even if the signaling
+ // for hold fails. After all the user has put the phone locally
+ // on-hold, so RTP should never be started.
+ session->hold();
+}
+
+void t_dialog::retrieve(void) {
+ assert(!session_re_invite);
+ t_user *user_config = phone_user->get_user_profile();
+
+ // Stop glare retry timer
+ if (id_glare_retry) {
+ line->stop_timer(LTMR_GLARE_RETRY, get_object_id());
+ }
+
+ // Allow RTP stream to be started again.
+ session->unhold();
+
+ // If the previous call-hold failed, then only RTP needs to
+ // be restarted. The session description did never change
+ // because of the failure.
+ if (hold_failed) {
+ session->start_rtp();
+ return;
+ }
+
+ // If STUN is enabled, then first send a STUN binding request to
+ // discover the IP adderss and port for media.
+ if (phone->use_stun(user_config)) {
+ if (!stun_bind_media()) {
+ // No re-INVITE can be sent. Simply return.
+ // User will decide if the call should be
+ // torn down.
+ return;
+ }
+ }
+
+ reinvite_purpose = REINVITE_RETRIEVE;
+ session_re_invite = session->create_call_retrieve();
+ send_re_invite();
+}
+
+void t_dialog::kill_rtp(void){
+ session->kill_rtp();
+ if (session_re_invite) session_re_invite->kill_rtp();
+}
+
+void t_dialog::send_refer(const t_url &uri, const string &display) {
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (state != DS_CONFIRMED) return;
+
+ if (refer_state != REFST_NULL) return;
+
+ // If a previous refer is still in progress, then do nothing
+ if (req_refer) {
+ log_file->write_report("A REFER request is already in progress.",
+ "t_dialog::send_refer");
+ return;
+ }
+
+ // If a refer subscription already exists, then do nothing
+ if (sub_refer) {
+ log_file->write_report("Refer subscription exists already.",
+ "t_dialog::send_refer");
+ return;
+ }
+
+ t_request *refer = create_request(REFER);
+
+ // Refer-To header
+ refer->hdr_refer_to.set_uri(uri);
+ refer->hdr_refer_to.set_display(display);
+
+ // Referred-By header
+ refer->hdr_referred_by.set_uri(line->create_user_uri());
+ refer->hdr_referred_by.set_display(user_config->get_display(line->get_hide_user()));
+
+ req_refer = new t_client_request(user_config, refer, 0);
+ MEMMAN_NEW(req_refer);
+ line->send_request(refer, req_refer->get_tuid());
+ MEMMAN_DELETE(refer);
+ delete refer;
+
+ refer_succeeded = false;
+ out_refer_req_failed = false;
+ refer_state = REFST_W4RESP;
+}
+
+void t_dialog::send_dtmf(char digit, bool inband, bool info) {
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (info) {
+ if (req_info) {
+ // An INFO request is still in progress, put the
+ // DTMF digit in the queue
+ dtmf_queue.push(digit);
+ } else {
+ t_request *info_request = create_request(INFO);
+
+ // Content-Type header
+ info_request->hdr_content_type.set_media(t_media("application", "dtmf-relay"));
+
+ // application/dtmf-relay body
+ info_request->body = new t_sip_body_dtmf_relay(digit,
+ user_config->get_dtmf_duration());
+ MEMMAN_NEW(info_request->body);
+
+ req_info = new t_client_request(user_config, info_request, 0);
+ MEMMAN_NEW(req_info);
+ line->send_request(info_request, req_info->get_tuid());
+ MEMMAN_DELETE(info_request);
+ delete info_request;
+
+ ui->cb_send_dtmf(line->get_line_number(), char2dtmf_ev(digit));
+ }
+ } else {
+ if (session) session->send_dtmf(digit, inband);
+ }
+}
+
+bool t_dialog::stun_bind_media(void) {
+ t_user *user_config = phone_user->get_user_profile();
+
+ try {
+ unsigned long mapped_ip;
+ unsigned short mapped_port;
+ int stun_err_code;
+ string stun_err_reason;
+ bool ret = get_stun_binding(user_config, line->get_rtp_port(),
+ mapped_ip, mapped_port,
+ stun_err_code, stun_err_reason);
+
+ if (!ret) {
+ // STUN request failed
+ ui->cb_stun_failed(user_config, stun_err_code, stun_err_reason);
+
+ log_file->write_header("t_dialog::stun_bind_media",
+ LOG_NORMAL, LOG_CRITICAL);
+ log_file->write_raw("STUN bind request for media failed.\n");
+ log_file->write_raw(stun_err_code);
+ log_file->write_raw(" ");
+ log_file->write_raw(stun_err_reason);
+ log_file->write_endl();
+ log_file->write_footer();
+ return false;
+ }
+
+ // STUN binding request succeeded.
+ session->receive_host = h_ip2str(mapped_ip);
+ session->receive_port = mapped_port;
+ } catch (int err) {
+ // STUN request failed
+ ui->cb_stun_failed(user_config);
+
+ log_file->write_header("t_dialog::stun_bind_media",
+ LOG_NORMAL, LOG_CRITICAL);
+ log_file->write_raw("STUN bind request for media failed.\n");
+ log_file->write_raw(get_error_str(err));
+ log_file->write_endl();
+ log_file->write_footer();
+ return false;
+ }
+
+ return true;
+}
+
+void t_dialog::recvd_response(t_response *r, t_tuid tuid, t_tid tid) {
+ t_user *user_config = phone_user->get_user_profile();
+ t_abstract_dialog::recvd_response(r, tuid, tid);
+
+ if (r->hdr_cseq.method == INVITE &&
+ tuid == 0 && tid == 0 && !req_out_invite)
+ {
+ t_ip_port ip_port;
+
+ // Only a retransmission of a 2XX INVITE is allowed.
+ if (r->get_class() != R_2XX) return;
+ if (!ack) return;
+ if (r->hdr_cseq.seqnr != ack->hdr_cseq.seqnr)
+ {
+ // The 2XX response does not match the ACK
+ return;
+ }
+
+ ack->get_destination(ip_port, *user_config);
+ if (ip_port.ipaddr != 0 && ip_port.port != 0) {
+ evq_sender->push_network(ack, ip_port);
+ }
+
+ return;
+ }
+
+ if (r->hdr_cseq.method == CANCEL) {
+ if (!req_cancel) return;
+ if (r->is_final()) {
+ remove_client_request(&req_cancel);
+ if (r->is_success()) {
+ line->start_timer(LTMR_CANCEL_GUARD, get_object_id());
+ } else {
+ // CANCEL request failed.
+ ui->cb_cancel_failed(line->get_line_number(), r);
+
+ // Abort the INVITE as the user cannot terminate
+ // it in a normal way.
+ if (req_out_invite) {
+ t_tid _tid = req_out_invite->get_tid();
+ if (_tid > 0) {
+ evq_trans_mgr->push_abort_trans(_tid);
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ // No processing done for PRACK responses.
+ if (r->hdr_cseq.method == PRACK) {
+ if (!req_prack) return;
+ t_request *prack = req_prack->get_request();
+
+ if (r->hdr_cseq.seqnr != prack->hdr_cseq.seqnr) {
+ // The response does not match the latest sent PRACK.
+ // It might match a previous sent PRACK. However, when
+ // a previous PRACK fails, then the latest PRACK will also
+ // fail, so the failure will be handled in the end without
+ // the overhead to keep a list of all pending PRACKs which
+ // should be a rare case.
+ return;
+ }
+
+ if (r->is_final()) {
+ // PRACK is finished, so remove request
+ remove_client_request(&req_prack);
+
+ // Tear down the call if PRACK failed and call is
+ // not yet established.
+ if (!r->is_success() && state == DS_EARLY) {
+ log_file->write_header("t_dialog::recvd_response",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("PRACK failed: ");
+ log_file->write_raw(r->code);
+ log_file->write_raw(" ");
+ log_file->write_raw(r->reason);
+ log_file->write_endl();
+ log_file->write_raw("Call will be cancelled.\n");
+ log_file->write_footer();
+
+ ui->cb_prack_failed(line->get_line_number(), r);
+ send_cancel(true);
+
+ // Ignore the failure in other states.
+ // The call has been setup, so all seems fine.
+ }
+ }
+
+ return;
+ }
+
+ // Determine if this is an INVITE or non-INVITE response
+ t_client_request *req;
+ bool send_to_sub_refer = false;
+
+ switch(r->hdr_cseq.method) {
+ case INVITE:
+ req = req_out_invite;
+ break;
+ case SUBSCRIBE:
+ case NOTIFY:
+ if (!sub_refer) return;
+ req = sub_refer->req_out;
+ send_to_sub_refer = true;
+ break;
+ case REFER:
+ req = req_refer;
+ break;
+ case INFO:
+ req = req_info;
+ break;
+ default:
+ req = req_out;
+ }
+
+ // Discard response if no request is pending
+ if (!req) {
+ return;
+ }
+
+ // Check cseq
+ if (r->hdr_cseq.method != req->get_request()->method) {
+ return;
+ }
+ if (r->hdr_cseq.seqnr != req->get_request()->hdr_cseq.seqnr) return;
+
+ // Set the transaction identifier. This identifier is needed if the
+ // transaction must be aborted at a later time.
+ req->set_tid(tid);
+
+ if (send_to_sub_refer) {
+ sub_refer->recv_response(r, tuid, tid);
+ if (sub_refer->get_state() == SS_TERMINATED) {
+ MEMMAN_DELETE(sub_refer);
+ delete sub_refer;
+ sub_refer = NULL;
+ if (state == DS_CONFIRMED_SUB) {
+ state = DS_TERMINATED;
+ }
+ }
+ return;
+ }
+
+ switch (state) {
+ case DS_W4INVITE_RESP:
+ case DS_W4INVITE_RESP2:
+ state_w4invite_resp(r, tuid, tid);
+ break;
+ case DS_EARLY:
+ state_early(r, tuid, tid);
+ break;
+ case DS_W4BYE_RESP:
+ state_w4bye_resp(r, tuid, tid);
+ break;
+ case DS_CONFIRMED:
+ state_confirmed_resp(r, tuid, tid);
+ break;
+ case DS_W4RE_INVITE_RESP:
+ case DS_W4RE_INVITE_RESP2:
+ state_w4re_invite_resp(r, tuid, tid);
+ break;
+ default:
+ // No response expected in other states. Discard.
+ break;
+ }
+}
+
+void t_dialog::recvd_request(t_request *r, t_tuid tuid, t_tid tid) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+
+ // CANCEL will be handled by recvd_cancel()
+
+ t_abstract_dialog::recvd_request(r, tuid, tid);
+
+ switch (r->method) {
+ case ACK:
+ // When ACK is received then the current incoming request
+ // must be INVITE.
+ if (!req_in_invite) return;
+ if (req_in_invite->get_request()->hdr_cseq.seqnr !=
+ r->hdr_cseq.seqnr)
+ {
+ log_file->write_header("t_dialog::recvd_request",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("ACK does not match a pending INVITE.\n");
+ log_file->write_raw("Discard ACK.\n");
+ log_file->write_footer();
+ return;
+ }
+ break;
+ case INVITE:
+ if (remote_seqnr_set && r->hdr_cseq.seqnr <= remote_seqnr) {
+ // Request received out of sequence. Discard.
+ log_file->write_header("t_dialog::recvd_request",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("INVITE is received out of order.\n");
+ log_file->write_raw("Remote seqnr = ");
+ log_file->write_raw(remote_seqnr);
+ log_file->write_endl();
+ log_file->write_raw("Received seqnr = ");
+ log_file->write_raw(r->hdr_cseq.seqnr);
+ log_file->write_endl();
+ log_file->write_raw("Discard INVITE.\n");
+ log_file->write_footer();
+ return;
+ }
+
+ remote_seqnr = r->hdr_cseq.seqnr;
+ remote_seqnr_set = true;
+
+ if (req_in_invite) {
+ // RFC 3261 14.2
+ // Another INVITE is received while the previous
+ // one is not finished.
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR,
+ "Previous INVITE still in progress");
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ return;
+ } else if (req_out_invite) {
+ // RFC 3261 14.2
+ // re-INVITE glare
+ resp = r->create_response(R_491_REQUEST_PENDING);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ return;
+ } else {
+ req_in_invite = new t_client_request(user_config, r, tid);
+ MEMMAN_NEW(req_in_invite);
+ }
+ break;
+ case REFER:
+ // Reset refer_accepted indication.
+ refer_accepted = false;
+ // fall thru
+ default:
+ // Check cseq
+ // RFC 3261 12.2.2
+ if (remote_seqnr_set && r->hdr_cseq.seqnr <= remote_seqnr) {
+ // Request received out of order.
+ log_file->write_header("t_dialog::recvd_request",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("CSeq seqnr is out of sequence.\n");
+ log_file->write_raw("Reveived seqnr: ");
+ log_file->write_raw(r->hdr_cseq.seqnr);
+ log_file->write_endl();
+ log_file->write_raw("Remote seqnr: ");
+ log_file->write_raw(remote_seqnr);
+ log_file->write_endl();
+ log_file->write_footer();
+
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR,
+ "Request received out of order");
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return;
+ }
+
+ remote_seqnr = r->hdr_cseq.seqnr;
+ remote_seqnr_set = true;
+ }
+
+ t_dialog_state old_state = state;
+
+ switch (state) {
+ case DS_NULL:
+ state_null(r, tuid, tid);
+ break;
+ case DS_W4ACK:
+ state_w4ack(r, tuid, tid);
+ break;
+ case DS_W4ACK_RE_INVITE:
+ state_w4ack_re_invite(r, tuid, tid);
+ break;
+ case DS_W4ANSWER:
+ state_w4answer(r, tuid, tid);
+ break;
+ case DS_W4RE_INVITE_RESP:
+ case DS_W4RE_INVITE_RESP2:
+ state_w4re_invite_resp(r, tuid, tid);
+ break;
+ case DS_W4BYE_RESP:
+ state_w4bye_resp(r, tuid, tid);
+ break;
+ case DS_CONFIRMED:
+ state_confirmed(r, tuid, tid);
+ break;
+ case DS_CONFIRMED_SUB:
+ state_confirmed_sub(r, tuid, tid);
+ break;
+ default:
+ // No request expected in other states. Discard.
+ resp = r->create_response(R_500_INTERNAL_SERVER_ERROR);
+ line->send_response(resp, tuid, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+ break;
+ }
+
+ // If the state has changed, then waiting requests needs to be
+ // processed.
+ if (state != old_state && !inc_req_queue.empty()) {
+ t_client_request *queued_cr = inc_req_queue.front();
+ inc_req_queue.pop_front();
+
+ log_file->write_header("t_dialog::recvd_request",
+ LOG_NORMAL, LOG_INFO);
+ log_file->write_raw("Process queued ");
+ log_file->write_raw(method2str(r->method, r->unknown_method));
+ log_file->write_endl();
+ log_file->write_footer();
+
+ recvd_request(queued_cr->get_request(), 0, queued_cr->get_tid());
+ MEMMAN_DELETE(queued_cr);
+ delete queued_cr;
+ }
+}
+
+// RFC 3261 9.2
+void t_dialog::recvd_cancel(t_request *r, t_tid cancel_tid,
+ t_tid target_tid)
+{
+ t_response *resp;
+
+ assert(r->method == CANCEL);
+
+ // Send 200 as response to CANCEL
+ resp = r->create_response(R_200_OK);
+ // RFC 3261 9.2
+ // The To-tag in the response to the CANCEL should be the same
+ // as the To-tag in the original request.
+ resp->hdr_to.set_tag(local_tag);
+ line->send_response(resp, 0, cancel_tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ switch (state) {
+ case DS_W4ANSWER:
+ state_w4answer(r, 0, cancel_tid);
+ break;
+ default:
+ // Ignore CANCEL in other states.
+ break;
+ }
+}
+
+void t_dialog::recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid) {
+ // Not used anymore.
+ // STUN requests are performed in a synchronous way.
+}
+
+// RFC 3261 13.3.1.4
+void t_dialog::answer(void) {
+ t_user *user_config = phone_user->get_user_profile();
+ if (!req_in_invite) return;
+
+ t_request *invite_req = req_in_invite->get_request();
+
+ // RFC 3262 3
+ // Delay the final response if we are still waiting for a PRACK
+ // on a 1xx response containing SDP
+ if (resp_1xx_invite && resp_1xx_invite->body) {
+ answer_after_prack = true;
+ return;
+ }
+
+ if (state != DS_W4ANSWER) {
+ throw X_WRONG_STATE;
+ }
+
+ resp_invite = invite_req->create_response(R_200_OK);
+ resp_invite->hdr_to.set_tag(local_tag);
+
+ // Set Organization header
+ SET_HDR_ORGANIZATION(resp_invite->hdr_organization, user_config);
+
+ // RFC 3261 12.1.1
+ // Copy the Record-Route header from request to response
+ if (invite_req->hdr_record_route.is_populated()) {
+ resp_invite->hdr_record_route = invite_req->hdr_record_route;
+ }
+
+ // Set Contact header
+ t_contact_param contact;
+ contact.uri.set_url(line->create_user_contact(h_ip2str(resp_invite->get_local_ip())));
+ resp_invite->hdr_contact.add_contact(contact);
+
+ // Set Allow and Supported headers
+ SET_HDR_ALLOW(resp_invite->hdr_allow, user_config);
+ SET_HDR_SUPPORTED(resp_invite->hdr_supported, user_config);
+
+ // RFC 3261 13.3.1.4
+ // Create SDP offer if no offer was received in INVITE and no offer
+ // was sent in a reliable 1xx response (RFC 3262 5)
+ // Otherwise if no offer was sent in a reliable 1xx, create an SDP answer.
+ if (!session->sent_offer) {
+ if (!session->recvd_offer && !session->sent_offer) {
+ session->create_sdp_offer(resp_invite, SDP_O_USER);
+ } else {
+ session->create_sdp_answer(resp_invite, SDP_O_USER);
+ session->start_rtp();
+ }
+ }
+
+ // Trigger call script
+ t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL_ANSWERED,
+ line->get_line_number() + 1);
+ script.exec_notify(resp_invite);
+
+ line->call_hist_record.answer_call(resp_invite);
+ line->send_response(resp_invite, req_in_invite->get_tuid(),
+ req_in_invite->get_tid());
+ line->start_timer(LTMR_ACK_GUARD, get_object_id());
+ line->start_timer(LTMR_ACK_TIMEOUT, get_object_id());
+
+ // Stop 100rel timers if they are running.
+ line->stop_timer(LTMR_100REL_GUARD, get_object_id());
+ line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id());
+
+ state = DS_W4ACK;
+}
+
+void t_dialog::reject(int code, string reason) {
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (state != DS_W4ANSWER) {
+ throw X_WRONG_STATE;
+ }
+
+ assert(req_in_invite);
+ assert(code >= 400);
+
+ resp = req_in_invite->get_request()->create_response(code, reason);
+ resp->hdr_to.set_tag(local_tag);
+
+ // Trigger call script
+ t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL_FAILED,
+ line->get_line_number() + 1);
+ script.exec_notify(resp);
+
+ line->send_response(resp, req_in_invite->get_tuid(),
+ req_in_invite->get_tid());
+ line->call_hist_record.fail_call(resp);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ // Stop 100rel timers if they are running.
+ line->stop_timer(LTMR_100REL_GUARD, get_object_id());
+ line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id());
+
+ state = DS_TERMINATED;
+}
+
+void t_dialog::redirect(const list<t_display_url> &destinations, int code, string reason)
+{
+ t_response *resp;
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (state != DS_W4ANSWER) {
+ throw X_WRONG_STATE;
+ }
+
+ assert(req_in_invite);
+ assert(code >= 300 && code <= 399);
+
+ resp = req_in_invite->get_request()->create_response(code, reason);
+ resp->hdr_to.set_tag(local_tag);
+
+ t_contact_param *contact;
+ float q = 0.9;
+ for (list<t_display_url>::const_iterator i = destinations.begin();
+ i != destinations.end(); i++)
+ {
+ contact = new t_contact_param();
+ MEMMAN_NEW(contact);
+ contact->display = i->display;
+ contact->uri = i->url;
+ contact->set_qvalue(q);
+ resp->hdr_contact.add_contact(*contact);
+ MEMMAN_DELETE(contact);
+ delete contact;
+ q = q - 0.1;
+ if (q < 0.1) q = 0.1;
+ }
+
+ // Trigger call script
+ t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL_FAILED,
+ line->get_line_number() + 1);
+ script.exec_notify(resp);
+
+ line->send_response(resp, req_in_invite->get_tuid(),
+ req_in_invite->get_tid());
+ line->call_hist_record.fail_call(resp);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ // Stop 100rel timers if they are running.
+ line->stop_timer(LTMR_100REL_GUARD, get_object_id());
+ line->stop_timer(LTMR_100REL_TIMEOUT, get_object_id());
+
+ state = DS_TERMINATED;
+}
+
+bool t_dialog::match_response(t_response *r, t_tuid tuid) {
+ if (tuid != 0) {
+ if (req_out && req_out->get_tuid() == tuid) return true;
+ if (req_out_invite && req_out_invite->get_tuid() == tuid) {
+ return true;
+ }
+ if (req_cancel && req_cancel->get_tuid() == tuid) {
+ return true;
+ }
+ return false;
+ }
+
+ // The implementation sends CANCEL on the open dialog.
+ // The tags of a CANCEL response will be identical to the tags of
+ // the INVITE, so it matches all pending dialogs as well.
+ // So a CANCEL should only match if the dialog has a CANCEL request
+ // pending.
+ if (r->hdr_cseq.method == CANCEL && !req_cancel) return false;
+
+ return t_abstract_dialog::match_response(r, tuid);
+}
+
+bool t_dialog::match_response(StunMessage *r, t_tuid tuid) {
+ if (tuid == 0) return false;
+ if (!req_stun) return false;
+
+ return (req_stun->get_tuid() == tuid);
+}
+
+bool t_dialog::match_cancel(t_request *r, t_tid target_tid) {
+ return (req_in_invite && req_in_invite->get_tid() == target_tid);
+}
+
+bool t_dialog::is_invite_retrans(t_request *r) {
+ assert(r->method == INVITE);
+
+ // An INVITE can only be a retransmission if an incoming INVITE is
+ // still in progress.
+ if (!req_in_invite) return false;
+ t_request *request = req_in_invite->get_request();
+
+ // RFC 3261 17.2.3
+ t_via &orig_top_via = request->hdr_via.via_list.front();
+ t_via &recv_top_via = r->hdr_via.via_list.front();
+
+ if (recv_top_via.rfc3261_compliant()) {
+ if (orig_top_via.branch != recv_top_via.branch) return false;
+ if (orig_top_via.host != recv_top_via.host) return false;
+ if (orig_top_via.port != recv_top_via.port) return false;
+ return (request->hdr_cseq.method == r->hdr_cseq.method);
+ }
+
+ // Matching rules for backward compatibiliy with RFC 2543
+ // TODO: verify rules for matching via headers
+ return (request->uri.sip_match(r->uri) &&
+ request->hdr_to.tag == r->hdr_to.tag &&
+ request->hdr_from.tag == r->hdr_from.tag &&
+ request->hdr_call_id.call_id == r->hdr_call_id.call_id &&
+ request->hdr_cseq.seqnr == r->hdr_cseq.seqnr &&
+ orig_top_via.host == recv_top_via.host &&
+ orig_top_via.port == recv_top_via.port);
+}
+
+void t_dialog::process_invite_retrans(void) {
+ t_ip_port ip_port;
+
+ // Retransmit 2xx response.
+ // Send the response directly to the sender thread
+ // as the INVITE transaction completed already.
+ // (see RFC 3261 17.2.1)
+ if (!resp_invite) return; // there is no response to send
+ resp_invite->get_destination(ip_port);
+ if (ip_port.ipaddr == 0) {
+ // This should not happen. The response has been
+ // sent before so it should be possible to sent
+ // it again. Ignore the timeout. When the ACK
+ // guard timer expires, the dialog will be
+ // cleaned up.
+ return;
+ }
+ evq_sender->push_network(resp_invite, ip_port);
+}
+
+t_dialog_state t_dialog::get_state(void) const {
+ return state;
+}
+
+void t_dialog::timeout(t_line_timer timer) {
+ switch(state) {
+ case DS_W4INVITE_RESP:
+ case DS_W4INVITE_RESP2:
+ state_w4invite_resp(timer);
+ break;
+ case DS_EARLY:
+ state_early(timer);
+ break;
+ case DS_W4ACK:
+ state_w4ack(timer);
+ break;
+ case DS_W4ACK_RE_INVITE:
+ state_w4ack_re_invite(timer);
+ break;
+ case DS_W4RE_INVITE_RESP2:
+ state_w4re_invite_resp(timer);
+ break;
+ case DS_W4ANSWER:
+ state_w4answer(timer);
+ break;
+ case DS_CONFIRMED:
+ state_confirmed(timer);
+ break;
+ default:
+ // Timeout not expected in other states. Ignore.
+ break;
+ }
+}
+
+void t_dialog::timeout_sub(t_subscribe_timer timer, const string &event_type,
+ const string &event_id)
+{
+ if (sub_refer &&
+ sub_refer->get_event_type() == event_type &&
+ sub_refer->get_event_id() == event_id)
+ {
+ sub_refer->timeout(timer);
+ } else {
+ // Timeout does not match with the current subscription.
+ // Ignore.
+ return;
+ }
+
+ if (sub_refer->get_state() == SS_TERMINATED && state == DS_CONFIRMED_SUB) {
+ MEMMAN_DELETE(sub_refer);
+ delete sub_refer;
+ sub_refer = NULL;
+ state = DS_TERMINATED;
+ }
+}
+
+t_phone *t_dialog::get_phone(void) const {
+ return line->get_phone();
+}
+
+t_line *t_dialog::get_line(void) const {
+ return line;
+}
+
+t_session *t_dialog::get_session(void) const {
+ return session;
+}
+
+t_audio_session *t_dialog::get_audio_session(void) const {
+ if (!session) return NULL;
+
+ return session->get_audio_session();
+}
+
+bool t_dialog::has_active_session(void) const {
+ if (session) return session->is_rtp_active();
+
+ return false;
+}
+
+// RFC 3515
+// Send a NOTIFY with reference progress to the referror
+void t_dialog::notify_refer_progress(t_response *r) {
+ if (!sub_refer) return;
+
+ if (r->is_final()) {
+ sub_refer->send_notify(r, SUBSTATE_TERMINATED, EV_REASON_NORESOURCE);
+ } else {
+ sub_refer->send_notify(r, SUBSTATE_ACTIVE);
+ }
+}
+
+bool t_dialog::will_release(void) const {
+ return state == DS_W4BYE_RESP || request_cancelled ||
+ end_after_2xx_invite || end_after_ack;
+}