/*
Copyright (C) 2005-2007 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 "sockets/socket.h"
#include "stun/stun_transaction.h"
extern t_event_queue *evq_sender_udp;
extern t_event_queue *evq_trans_mgr;
extern string user_host;
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);
// 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.
unsigned long ipaddr;
unsigned short port;
orig_req->get_destination(ipaddr, port, *user_config);
r->set_destination(ipaddr, 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());
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;
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(USER_HOST(user_config),
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 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);
// According to RFC 3262 4, a reliable provisional response
// must establish a dialog. RFC 3261 12.1.1 tells that
// a response that establishes a dialog must contain
// a contact header and copy record-route headers from
// the request.
// 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());
resp->hdr_contact.add_contact(contact);
// 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, local_uri.get_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;
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) {
unsigned long ipaddr;
unsigned short port;
t_response *resp;
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 UDP 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->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(resp_1xx_invite, ipaddr, 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;
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;
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) {
unsigned long ipaddr;
unsigned short 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 UDP 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->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(resp_invite, ipaddr, 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_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_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;
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(USER_HOST(user_config),
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());
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,
local_uri.get_user());
} else {
session_re_invite->create_sdp_answer(resp_invite,
local_uri.get_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_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());
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;
if (!r->body || 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;
log_file->write_report("Received in-dialog MESSAGE.",
"t_dialog::process_message", LOG_NORMAL, LOG_DEBUG);
if (!r->body ||
r->body->get_type() != BODY_PLAIN_TEXT)
{
resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE);
// RFC 3261 21.4.13
SET_MESSAGE_HDR_ACCEPT(resp->hdr_accept);
phone->send_response(resp, 0, tid);
MEMMAN_DELETE(resp);
delete resp;
return;
}
bool accepted = ui->cb_message_request(line->get_user(), r);
if (accepted) {
resp = r->create_response(R_200_OK);
} else {
resp = r->create_response(R_486_BUSY_HERE);
}
resp = r->create_response(R_200_OK);
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;
// 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;
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) {
// 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) {
unsigned long ipaddr;
unsigned short port;
if (ack) {
// delete previous cached ACK
MEMMAN_DELETE(ack);
delete ack;
}
ack = create_request(ACK);
ack->get_destination(ipaddr, port, *user_config);
// If for some strange reason the destination could
// not be computed then wait for a retransmission of
// 2XX.
if (ipaddr != 0 && port != 0) {
evq_sender_udp->push_network(ack, ipaddr, 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) {
// 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) {
// 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_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;
// Create session
session = new t_session(this, USER_HOST(user_config), 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, bool anonymous)
{
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);
invite.uri = to_uri;
// 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 Contact header
t_contact_param contact;
contact.uri.set_url(line->create_user_contact());
invite.hdr_contact.add_contact(contact);
// Set Via header
t_via via(USER_HOST(user_config), PUBLIC_SIP_UDP_PORT(user_config));
invite.hdr_via.add_via(via);
// 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);
}
// Create SDP offer
session->create_sdp_offer(&invite, local_uri.get_user());
// Calculate destinations
// See create_request() for more comments
invite.calc_destinations(*user_config);
// 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) {
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) {
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;
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) {
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) {
// 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;
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);
// 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());
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, local_uri.get_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;
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);
// 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) {
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) {
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) {
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_abstract_dialog::recvd_response(r, tuid, tid);
if (r->hdr_cseq.method == INVITE &&
tuid == 0 && tid == 0 && !req_out_invite)
{
unsigned long ipaddr;
unsigned short 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(ipaddr, port, *user_config);
if (ipaddr != 0 && port != 0) {
evq_sender_udp->push_network(ack, ipaddr, 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;
// 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) {
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());
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, local_uri.get_user());
} else {
session->create_sdp_answer(resp_invite, local_uri.get_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;
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;
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->q = 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) {
unsigned long ipaddr;
unsigned short port;
// Retransmit 2xx response.
// Send the response directly to the UDP 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->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(resp_invite, ipaddr, 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;
}
syntax highlighted by Code2HTML, v. 0.9.1