summaryrefslogtreecommitdiffstats
path: root/src/line.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/line.cpp')
-rw-r--r--src/line.cpp2279
1 files changed, 2279 insertions, 0 deletions
diff --git a/src/line.cpp b/src/line.cpp
new file mode 100644
index 0000000..d494262
--- /dev/null
+++ b/src/line.cpp
@@ -0,0 +1,2279 @@
+/*
+ Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <assert.h>
+#include <iostream>
+#include <signal.h>
+#include "exceptions.h"
+#include "line.h"
+#include "log.h"
+#include "sdp/sdp.h"
+#include "util.h"
+#include "user.h"
+#include "userintf.h"
+#include "audits/memman.h"
+
+extern t_event_queue *evq_timekeeper;
+
+///////////////
+// t_call_info
+///////////////
+
+t_call_info::t_call_info() {
+ clear();
+}
+
+void t_call_info::clear(void) {
+ from_uri.set_url("");
+ from_display.clear();
+ from_display_override.clear();
+ from_organization.clear();
+ to_uri.set_url("");
+ to_display.clear();
+ to_organization.clear();
+ subject.clear();
+ dtmf_supported = false;
+ hdr_referred_by = t_hdr_referred_by();
+ last_provisional_reason.clear();
+ send_codec = CODEC_NULL;
+ recv_codec = CODEC_NULL;
+ refer_supported = false;
+}
+
+string t_call_info::get_from_display_presentation(void) const {
+ if (from_display_override.empty()) {
+ return from_display;
+ } else {
+ return from_display_override;
+ }
+}
+
+
+///////////
+// t_line
+///////////
+
+///////////
+// Private
+///////////
+
+t_dialog *t_line::match_response(t_response *r,
+ const list<t_dialog *> &l) const
+{
+ list<t_dialog *>::const_iterator i;
+ for (i = l.begin(); i != l.end(); i++) {
+ if ((*i)->match_response(r, 0)) return *i;
+ }
+
+ return NULL;
+}
+
+t_dialog *t_line::match_response(StunMessage *r, t_tuid tuid,
+ const list<t_dialog *> &l) const
+{
+ list<t_dialog *>::const_iterator i;
+ for (i = l.begin(); i != l.end(); i++) {
+ if ((*i)->match_response(r, tuid)) return *i;
+ }
+
+ return NULL;
+}
+
+t_dialog *t_line::match_call_id_tags(const string &call_id,
+ const string &to_tag, const string &from_tag,
+ const list<t_dialog *> &l) const
+{
+ list<t_dialog *>::const_iterator i;
+ for (i = l.begin(); i != l.end(); i++) {
+ if ((*i)->match(call_id, to_tag, from_tag)) return *i;
+ }
+
+ return NULL;
+}
+
+t_dialog *t_line::get_dialog(t_object_id did) const {
+ list<t_dialog *>::const_iterator i;
+
+ if (did == 0) return NULL;
+
+ if (open_dialog && open_dialog->get_object_id() == did) {
+ return open_dialog;
+ }
+
+ if (active_dialog && active_dialog->get_object_id() == did) {
+ return active_dialog;
+ }
+
+ for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) {
+ if ((*i)->get_object_id() == did) return *i;
+ }
+
+ for (i = dying_dialogs.begin(); i != dying_dialogs.end(); i++) {
+ if ((*i)->get_object_id() == did) return *i;
+ }
+
+ return NULL;
+}
+
+void t_line::cleanup(void) {
+ list<t_dialog *>::iterator i;
+
+ if (open_dialog && open_dialog->get_state() == DS_TERMINATED) {
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ open_dialog = NULL;
+ }
+
+ if (active_dialog && active_dialog->get_state() == DS_TERMINATED) {
+ MEMMAN_DELETE(active_dialog);
+ delete active_dialog;
+ active_dialog = NULL;
+
+ stop_timer(LTMR_INVITE_COMP);
+ stop_timer(LTMR_NO_ANSWER);
+
+ // If the call has been ended within 64*T1 seconds
+ // after the reception of the first 2XX response, there
+ // might still be open and pending dialogs. To be nice these
+ // dialogs should be kept till the 64*T1 timer expires.
+ // This complicates the setup of new call however. For
+ // now the dialogs will be killed. If a slow UAS
+ // still responds, it has bad luck and will time out.
+ //
+ // TODO:
+ // A nice solution would be to move the pending and open
+ // dialog to the dying dialog and start a new time 64*T1
+ // timer to keep the dying dialogs alive. A sequence of
+ // a few short calls would add to the dying dialogs and
+ // keep some dialogs alive longer than necessary. This
+ // only has an impact on resources, not on signalling.
+ // Note that the open dialog must be appended after the
+ // pending dialogs, otherwise all received responses for
+ // a pending dialog will match the open dialog if that
+ // match is tried first by match_response()
+ for (i = pending_dialogs.begin(); i != pending_dialogs.end();
+ i++)
+ {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+ pending_dialogs.clear();
+
+ if (open_dialog) {
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ }
+ open_dialog = NULL;
+ }
+
+ if (active_dialog) {
+ if (active_dialog->get_state() == DS_CONFIRMED_SUB) {
+ // The calls have been released but a subscription is
+ // still active.
+ substate = LSSUB_RELEASING;
+ } else if (active_dialog->will_release()) {
+ substate = LSSUB_RELEASING;
+ }
+ }
+
+ for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) {
+ if ((*i)->get_state() == DS_TERMINATED) {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ *i = NULL;
+ }
+ }
+ pending_dialogs.remove(NULL);
+
+ for (i = dying_dialogs.begin(); i != dying_dialogs.end(); i++) {
+ if ((*i)->get_state() == DS_TERMINATED) {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ *i = NULL;
+ }
+ }
+ dying_dialogs.remove(NULL);
+
+ if (!open_dialog && !active_dialog && pending_dialogs.size() == 0) {
+ state = LS_IDLE;
+
+ if (keep_seized) {
+ substate = LSSUB_SEIZED;
+ } else {
+ substate = LSSUB_IDLE;
+ }
+
+ is_on_hold = false;
+ is_muted = false;
+ hide_user = false;
+ cleanup_transfer_consult_state();
+ try_to_encrypt = false;
+ auto_answer = false;
+ call_info.clear();
+ call_history->add_call_record(call_hist_record);
+ call_hist_record.renew();
+ phone_user = NULL;
+ user_defined_ringtone.clear();
+ ui->cb_line_state_changed();
+ }
+}
+
+void t_line::cleanup_open_pending(void) {
+ if (open_dialog) {
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ open_dialog = NULL;
+ }
+
+ list<t_dialog *>::iterator i;
+ for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+ pending_dialogs.clear();
+
+ if (!active_dialog) {
+ is_on_hold = false;
+ is_muted = false;
+ hide_user = false;
+ cleanup_transfer_consult_state();
+ try_to_encrypt = false;
+ auto_answer = false;
+ state = LS_IDLE;
+
+ if (keep_seized) {
+ substate = LSSUB_SEIZED;
+ } else {
+ substate = LSSUB_IDLE;
+ }
+
+ call_info.clear();
+ call_history->add_call_record(call_hist_record);
+ call_hist_record.renew();
+ phone_user = NULL;
+ user_defined_ringtone.clear();
+ ui->cb_line_state_changed();
+ }
+}
+
+void t_line::cleanup_forced(void) {
+ list<t_dialog *>::iterator i;
+
+ if (open_dialog) {
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ open_dialog = NULL;
+ }
+
+ if (active_dialog) {
+ MEMMAN_DELETE(active_dialog);
+ delete active_dialog;
+ active_dialog = NULL;
+ }
+
+ for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ *i = NULL;
+ }
+ pending_dialogs.remove(NULL);
+
+ for (i = dying_dialogs.begin(); i != dying_dialogs.end(); i++) {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ *i = NULL;
+ }
+ dying_dialogs.remove(NULL);
+
+ // TODO: stop running timers?
+
+ state = LS_IDLE;
+ substate = LSSUB_IDLE;
+ keep_seized = false;
+ is_on_hold = false;
+ is_muted = false;
+ hide_user = false;
+ cleanup_transfer_consult_state();
+ auto_answer = false;
+ call_info.clear();
+ call_history->add_call_record(call_hist_record);
+ call_hist_record.renew();
+ phone_user = NULL;
+ user_defined_ringtone.clear();
+ ui->cb_line_state_changed();
+}
+
+void t_line::cleanup_transfer_consult_state(void) {
+ if (is_transfer_consult) {
+ t_line *from_line = phone->get_line(consult_transfer_from_line);
+ from_line->set_to_be_transferred(false, 0);
+ is_transfer_consult = false;
+ }
+
+ if (to_be_transferred) {
+ t_line *to_line = phone->get_line(consult_transfer_to_line);
+ to_line->set_is_transfer_consult(false, 0);
+ to_be_transferred = false;
+ }
+}
+
+
+///////////
+// Public
+///////////
+
+t_line::t_line(t_phone *_phone, unsigned short _line_number) :
+ t_id_object()
+{
+ // NOTE: The rtp_port attribute can only be initialized when
+ // a user profile has been selected.
+
+ phone = _phone;
+ state = LS_IDLE;
+ substate = LSSUB_IDLE;
+ open_dialog = NULL;
+ active_dialog = NULL;
+ is_on_hold = false;
+ is_muted = false;
+ hide_user = false;
+ is_transfer_consult = false;
+ to_be_transferred = false;
+ try_to_encrypt = false;
+ auto_answer = false;
+ line_number = _line_number;
+ id_invite_comp = 0;
+ id_no_answer = 0;
+ phone_user = NULL;
+ user_defined_ringtone.clear();
+ keep_seized = false;
+}
+
+t_line::~t_line() {
+ list<t_dialog *>::iterator i;
+
+ // Stop timers
+ if (id_invite_comp) stop_timer(LTMR_INVITE_COMP);
+ if (id_no_answer) stop_timer(LTMR_NO_ANSWER);
+
+ // Delete pointers
+ if (open_dialog) {
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ }
+ if (active_dialog) {
+ MEMMAN_DELETE(active_dialog);
+ delete active_dialog;
+ }
+
+ // Delete dialogs
+ for (i = pending_dialogs.begin(); i != pending_dialogs.end(); i++) {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+
+ for (i = dying_dialogs.begin(); i != dying_dialogs.end(); i++) {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+}
+
+t_line_state t_line::get_state(void) const {
+ return state;
+}
+
+t_line_substate t_line::get_substate(void) const {
+ return substate;
+}
+
+t_refer_state t_line::get_refer_state(void) const {
+ if (active_dialog) return active_dialog->refer_state;
+ return REFST_NULL;
+}
+
+void t_line::start_timer(t_line_timer timer, t_object_id did) {
+ t_tmr_line *t;
+ t_dialog *dialog = get_dialog(did);
+ unsigned long dur;
+
+ assert(phone_user);
+
+ switch(timer) {
+ case LTMR_ACK_TIMEOUT:
+ assert(dialog);
+ // RFC 3261 13.3.1.4
+ if (dialog->dur_ack_timeout == 0) {
+ dialog->dur_ack_timeout = DURATION_T1;
+ } else {
+ dialog->dur_ack_timeout *= 2;
+ if (dialog->dur_ack_timeout > DURATION_T2 ) {
+ dialog->dur_ack_timeout = DURATION_T2;
+ }
+ }
+ t = new t_tmr_line(dialog->dur_ack_timeout , timer, get_object_id(),
+ did);
+ MEMMAN_NEW(t);
+ dialog->id_ack_timeout = t->get_object_id();
+ break;
+ case LTMR_ACK_GUARD:
+ assert(dialog);
+ // RFC 3261 13.3.1.4
+ t = new t_tmr_line(64 * DURATION_T1, timer, get_object_id(), did);
+ MEMMAN_NEW(t);
+ dialog->id_ack_guard = t->get_object_id();
+ break;
+ case LTMR_INVITE_COMP:
+ // RFC 3261 13.2.2.4
+ t = new t_tmr_line(64 * DURATION_T1, timer, get_object_id(), did);
+ MEMMAN_NEW(t);
+ id_invite_comp = t->get_object_id();
+ break;
+ case LTMR_NO_ANSWER:
+ t = new t_tmr_line(DUR_NO_ANSWER(phone_user->get_user_profile()),
+ timer, get_object_id(), did);
+ MEMMAN_NEW(t);
+ id_no_answer = t->get_object_id();
+ break;
+ case LTMR_RE_INVITE_GUARD:
+ assert(dialog);
+ t = new t_tmr_line(DUR_RE_INVITE_GUARD, timer, get_object_id(), did);
+ MEMMAN_NEW(t);
+ dialog->id_re_invite_guard = t->get_object_id();
+ break;
+ case LTMR_GLARE_RETRY:
+ assert(dialog);
+ if (dialog->is_call_id_owner()) {
+ dur = DUR_GLARE_RETRY_OWN;
+ } else {
+ dur = DUR_GLARE_RETRY_NOT_OWN;
+ }
+ t = new t_tmr_line(dur, timer, get_object_id(), did);
+ MEMMAN_NEW(t);
+ dialog->id_glare_retry = t->get_object_id();
+ break;
+ case LTMR_100REL_TIMEOUT:
+ assert(dialog);
+ // RFC 3262 3
+ if (dialog->dur_100rel_timeout == 0) {
+ dialog->dur_100rel_timeout = DUR_100REL_TIMEOUT;
+ } else {
+ dialog->dur_100rel_timeout *= 2;
+ }
+ t = new t_tmr_line(dialog->dur_100rel_timeout , timer, get_object_id(),
+ did);
+ MEMMAN_NEW(t);
+ dialog->id_100rel_timeout = t->get_object_id();
+ break;
+ case LTMR_100REL_GUARD:
+ assert(dialog);
+ // RFC 3262 3
+ t = new t_tmr_line(DUR_100REL_GUARD, timer, get_object_id(), did);
+ MEMMAN_NEW(t);
+ dialog->id_100rel_guard = t->get_object_id();
+ break;
+ case LTMR_CANCEL_GUARD:
+ assert(dialog);
+ t = new t_tmr_line(DUR_CANCEL_GUARD, timer, get_object_id(), did);
+ MEMMAN_NEW(t);
+ dialog->id_cancel_guard = t->get_object_id();
+ break;
+ default:
+ assert(false);
+ }
+
+ evq_timekeeper->push_start_timer(t);
+ MEMMAN_DELETE(t);
+ delete t;
+}
+
+void t_line::stop_timer(t_line_timer timer, t_object_id did) {
+ t_object_id *id;
+ t_dialog *dialog = get_dialog(did);
+
+ switch(timer) {
+ case LTMR_ACK_TIMEOUT:
+ assert(dialog);
+ dialog->dur_ack_timeout = 0;
+ id = &dialog->id_ack_timeout;
+ break;
+ case LTMR_ACK_GUARD:
+ assert(dialog);
+ id = &dialog->id_ack_guard;
+ break;
+ case LTMR_INVITE_COMP:
+ id = &id_invite_comp;
+ break;
+ case LTMR_NO_ANSWER:
+ id = &id_no_answer;
+ break;
+ case LTMR_RE_INVITE_GUARD:
+ assert(dialog);
+ id = &dialog->id_re_invite_guard;
+ break;
+ case LTMR_GLARE_RETRY:
+ assert(dialog);
+ id = &dialog->id_glare_retry;
+ break;
+ case LTMR_100REL_TIMEOUT:
+ assert(dialog);
+ dialog->dur_100rel_timeout = 0;
+ id = &dialog->id_100rel_timeout;
+ break;
+ case LTMR_100REL_GUARD:
+ assert(dialog);
+ id = &dialog->id_100rel_guard;
+ break;
+ case LTMR_CANCEL_GUARD:
+ assert(dialog);
+ id = &dialog->id_cancel_guard;
+
+ // KLUDGE
+ if (*id == 0) {
+ // Cancel is always sent on the open dialog.
+ // The timer is probably stopped from a pending dialog,
+ // therefore the timer is stopped on the wrong dialog.
+ // Check if the open dialog has a CANCEL guard timer.
+ if (open_dialog) id = &open_dialog->id_cancel_guard;
+ }
+ break;
+ default:
+ assert(false);
+ }
+
+ if (*id != 0) evq_timekeeper->push_stop_timer(*id);
+ *id = 0;
+}
+
+void t_line::invite(t_phone_user *pu, const t_url &to_uri, const string &to_display,
+ const string &subject, bool no_fork, bool anonymous)
+{
+ t_hdr_request_disposition hdr_request_disposition;
+
+ if (no_fork) {
+ hdr_request_disposition.set_fork_directive(
+ t_hdr_request_disposition::NO_FORK);
+ }
+
+ invite(pu, to_uri, to_display, subject, t_hdr_referred_by(),
+ t_hdr_replaces(), t_hdr_require(), hdr_request_disposition,
+ anonymous);
+}
+
+void t_line::invite(t_phone_user *pu, const t_url &to_uri, const string &to_display,
+ const string &subject, const t_hdr_referred_by &hdr_referred_by,
+ const t_hdr_replaces &hdr_replaces,
+ const t_hdr_require &hdr_require,
+ const t_hdr_request_disposition &hdr_request_disposition,
+ bool anonymous)
+{
+ assert(pu);
+
+ // Ignore if line is not idle
+ if (state != LS_IDLE) {
+ return;
+ }
+
+ assert(!open_dialog);
+
+ // Validate speaker and mic
+ string error_msg;
+ if (!sys_config->exec_audio_validation(false, true, true, error_msg)) {
+ ui->cb_show_msg(error_msg, MSG_CRITICAL);
+ return;
+ }
+
+ phone_user = pu;
+ t_user *user_config = pu->get_user_profile();
+
+ call_info.from_uri = create_user_uri(); // NOTE: hide_user is not set yet
+ call_info.from_display = user_config->get_display(false);
+ call_info.from_organization = user_config->get_organization();
+ call_info.to_uri = to_uri;
+ call_info.to_display = to_display;
+ call_info.to_organization.clear();
+ call_info.subject = subject;
+ call_info.hdr_referred_by = hdr_referred_by;
+
+ try_to_encrypt = user_config->get_zrtp_enabled();
+
+ state = LS_BUSY;
+ substate = LSSUB_OUTGOING_PROGRESS;
+ hide_user = anonymous;
+ ui->cb_line_state_changed();
+
+ open_dialog = new t_dialog(this);
+ MEMMAN_NEW(open_dialog);
+ open_dialog->send_invite(to_uri, to_display, subject, hdr_referred_by,
+ hdr_replaces, hdr_require, hdr_request_disposition,
+ anonymous);
+
+ cleanup();
+}
+
+void t_line::answer(void) {
+ // Ignore if line is idle
+ if (state == LS_IDLE) return;
+ assert(active_dialog);
+
+ // Validate speaker and mic
+ string error_msg;
+ if (!sys_config->exec_audio_validation(false, true, true, error_msg)) {
+ ui->cb_show_msg(error_msg, MSG_CRITICAL);
+ return;
+ }
+
+ stop_timer(LTMR_NO_ANSWER);
+
+ try {
+ substate = LSSUB_ANSWERING;
+ ui->cb_line_state_changed();
+ active_dialog->answer();
+ }
+ catch (t_exception x) {
+ // TODO: there is no call to answer
+ }
+
+ cleanup();
+}
+
+void t_line::reject(void) {
+ // Ignore if line is idle
+ if (state == LS_IDLE) return;
+ assert(active_dialog);
+
+ stop_timer(LTMR_NO_ANSWER);
+
+ try {
+ active_dialog->reject(R_603_DECLINE);
+ }
+ catch (t_exception x) {
+ // TODO: there is no call to reject
+ }
+
+ cleanup();
+}
+
+void t_line::redirect(const list<t_display_url> &destinations, int code, string reason)
+{
+ // Ignore if line is idle
+ if (state == LS_IDLE) return;
+ assert(active_dialog);
+
+ stop_timer(LTMR_NO_ANSWER);
+
+ try {
+ active_dialog->redirect(destinations, code, reason);
+ }
+ catch (t_exception x) {
+ // TODO: there is no call to redirect
+ }
+
+ cleanup();
+}
+
+void t_line::end_call(void) {
+ // Ignore if phone is idle
+ if (state == LS_IDLE) return;
+
+ if (active_dialog) {
+ substate = LSSUB_RELEASING;
+ ui->cb_line_state_changed();
+ ui->cb_stop_call_notification(line_number);
+ active_dialog->send_bye();
+
+ // If the line was part of a transfer with consultation,
+ // then clean the consultation state as the transfer cannot
+ // proceed anymore.
+ cleanup_transfer_consult_state();
+
+ cleanup();
+ return;
+ }
+
+ // Always send the CANCEL on the open dialog.
+ // The pending dialogs will be cleared when the INVITE gets
+ // terminated.
+ // CANCEL is send on the open dialog as the CANCEL must have
+ // the same tags as the INVITE.
+ if (open_dialog) {
+ substate = LSSUB_RELEASING;
+ ui->cb_line_state_changed();
+ ui->cb_stop_call_notification(line_number);
+ open_dialog->send_cancel(!pending_dialogs.empty());
+
+ // Make sure dialog is terminated if CANCEL glares with
+ // 2XX on INVITE.
+ for (list<t_dialog *>::iterator i = pending_dialogs.begin();
+ i != pending_dialogs.end(); i++)
+ {
+ (*i)->set_end_after_2xx_invite(true);
+ }
+
+ cleanup();
+ return;
+ }
+
+ // NOTE:
+ // The call is only ended for real when the dialog reaches
+ // the DS_TERMINATED state, i.e. a 200 OK on BYE is received
+ // or a 487 TERMINATED on INVITE is received.
+}
+
+void t_line::send_dtmf(char digit, bool inband, bool info) {
+ // DTMF may be sent on an early media session, so find
+ // a dialog that has an RTP session. There can be at most 1.
+ t_dialog *d = get_dialog_with_active_session();
+
+ if (d) {
+ d->send_dtmf(digit, inband, info);
+ cleanup();
+ return;
+ }
+}
+
+void t_line::options(void) {
+ if (active_dialog && active_dialog->get_state() == DS_CONFIRMED) {
+ active_dialog->send_options();
+ cleanup();
+ return;
+ }
+}
+
+bool t_line::hold(bool rtponly) {
+ if (is_on_hold) return true;
+
+ if (active_dialog && active_dialog->get_state() == DS_CONFIRMED) {
+ active_dialog->hold(rtponly);
+ is_on_hold = true;
+ ui->cb_line_state_changed();
+ cleanup();
+ return true;
+ }
+
+ return false;
+}
+
+void t_line::retrieve(void) {
+ if (!is_on_hold) return;
+
+ if (active_dialog && active_dialog->get_state() == DS_CONFIRMED) {
+ active_dialog->retrieve();
+ is_on_hold = false;
+ ui->cb_line_state_changed();
+ cleanup();
+ return;
+ }
+}
+
+void t_line::kill_rtp(void) {
+ if (active_dialog) active_dialog->kill_rtp();
+
+ for (list<t_dialog *>::iterator i = pending_dialogs.begin();
+ i != pending_dialogs.end(); i++)
+ {
+ (*i)->kill_rtp();
+ }
+
+ for (list<t_dialog *>::iterator i = dying_dialogs.begin();
+ i != dying_dialogs.end(); i++)
+ {
+ (*i)->kill_rtp();
+ }
+}
+
+void t_line::refer(const t_url &uri, const string &display) {
+ if (active_dialog && active_dialog->get_state() == DS_CONFIRMED) {
+ active_dialog->send_refer(uri, display);
+ ui->cb_line_state_changed();
+ cleanup();
+ return;
+ }
+}
+
+void t_line::mute(bool enable) {
+ is_muted = enable;
+}
+
+void t_line::recvd_provisional(t_response *r, t_tuid tuid, t_tid tid) {
+ t_dialog *d;
+
+ if (active_dialog && active_dialog->match_response(r, 0)) {
+ active_dialog->recvd_response(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, pending_dialogs);
+ if (d) {
+ d->recvd_response(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, dying_dialogs);
+ if (d) {
+ d->recvd_response(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ if (open_dialog && open_dialog->match_response(r, tuid)) {
+ if (r->hdr_cseq.method == INVITE) {
+ if (r->hdr_to.tag.size() > 0) {
+ // Create a new pending dialog
+ d = open_dialog->copy();
+ pending_dialogs.push_back(d);
+ d->recvd_response(r, tuid, tid);
+ } else {
+ open_dialog->recvd_response(r, tuid, tid);
+ }
+ } else {
+ open_dialog->recvd_response(r, tuid, tid);
+ }
+
+ cleanup();
+ return;
+ }
+
+ // out-of-dialog response
+ // Provisional responses should only be given for INVITE.
+ // A response for an INVITE is always in a dialog.
+ // Ignore provisional responses for other requests.
+}
+
+void t_line::recvd_success(t_response *r, t_tuid tuid, t_tid tid) {
+ t_dialog *d;
+
+ if (active_dialog && active_dialog->match_response(r, 0)) {
+ active_dialog->recvd_response(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, pending_dialogs);
+ if (d) {
+ d->recvd_response(r, tuid, tid);
+ if (r->hdr_cseq.method == INVITE) {
+ if (!active_dialog) {
+ // Make the dialog the active dialog
+ active_dialog = d;
+ pending_dialogs.remove(d);
+ start_timer(LTMR_INVITE_COMP);
+ substate = LSSUB_ESTABLISHED;
+ ui->cb_line_state_changed();
+ } else {
+ // An active dialog already exists.
+ // Terminate this dialog by sending BYE
+ d->send_bye();
+ }
+ }
+
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, dying_dialogs);
+ if (d) {
+ d->recvd_response(r, tuid, tid);
+ if (r->hdr_cseq.method == INVITE) {
+ d->send_bye();
+ }
+ cleanup();
+ return;
+ }
+
+ if (open_dialog && open_dialog->match_response(r, tuid)) {
+ if (r->hdr_cseq.method == INVITE) {
+ // Create a new dialog
+ d = open_dialog->copy();
+
+ if (!active_dialog) {
+ active_dialog = d;
+ active_dialog->recvd_response(r, tuid, tid);
+ start_timer(LTMR_INVITE_COMP);
+ substate = LSSUB_ESTABLISHED;
+ ui->cb_line_state_changed();
+ } else {
+ pending_dialogs.push_back(d);
+ d->recvd_response(r, tuid, tid);
+
+ // An active dialog already exists.
+ // Terminate this dialog by sending BYE
+ d->send_bye();
+ }
+ } else {
+ open_dialog->recvd_response(r, tuid, tid);
+ }
+
+ cleanup();
+ return;
+ }
+
+ // Response does not match with any pending request. Discard.
+}
+
+void t_line::recvd_redirect(t_response *r, t_tuid tuid, t_tid tid) {
+ t_dialog *d;
+
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (active_dialog) {
+ // If an active dialog exists then non-2XX should
+ // only be for this dialog.
+ if (active_dialog->match_response(r, 0)) {
+ // Redirection of mid-dialog request
+ if (!user_config->get_allow_redirection() ||
+ !active_dialog->redirect_request(r))
+ {
+ // Redirection not allowed/failed
+ active_dialog->recvd_response(r, tuid, tid);
+ }
+
+ // Retrieve a held line after a REFER failure
+ if (r->hdr_cseq.method == REFER &&
+ active_dialog->out_refer_req_failed)
+ {
+ active_dialog->out_refer_req_failed = false;
+ if (phone->get_active_line() == line_number &&
+ user_config->get_referrer_hold())
+ {
+ retrieve();
+ }
+ }
+ }
+
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, pending_dialogs);
+ if (d) {
+ d->recvd_response(r, tuid, tid);
+ if (r->hdr_cseq.method == INVITE) {
+ pending_dialogs.remove(d);
+ MEMMAN_DELETE(d);
+ delete d;
+
+ // RFC 3261 13.2.2.3
+ // All early dialogs are considered terminated
+ // upon reception of the non-2xx final response.
+ list<t_dialog *>::iterator i;
+ for (i = pending_dialogs.begin();
+ i != pending_dialogs.end(); i++)
+ {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+ pending_dialogs.clear();
+
+ if (open_dialog) {
+ if (!user_config->get_allow_redirection() ||
+ !open_dialog->redirect_invite(r))
+ {
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ open_dialog = NULL;
+ }
+ }
+ }
+
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, dying_dialogs);
+ if (d) {
+ d->recvd_response(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ if (open_dialog && open_dialog->match_response(r, tuid)) {
+ if (r->hdr_cseq.method != INVITE) {
+ // TODO: can there be a non-INVITE response for an
+ // open dialog??
+ open_dialog->recvd_response(r, tuid, tid);
+ }
+
+ if (r->hdr_cseq.method == INVITE) {
+ if (!user_config->get_allow_redirection() ||
+ !open_dialog->redirect_invite(r))
+ {
+ // Redirection failed/not allowed
+ open_dialog->recvd_response(r, tuid, tid);
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ open_dialog = NULL;
+ }
+
+ // RFC 3261 13.2.2.3
+ // All early dialogs are considered terminated
+ // upon reception of the non-2xx final response.
+ list<t_dialog *>::iterator i;
+ for (i = pending_dialogs.begin();
+ i != pending_dialogs.end(); i++)
+ {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+ pending_dialogs.clear();
+ }
+
+ cleanup();
+ return;
+ }
+
+ // out-of-dialog responses should be handled by the phone
+}
+
+void t_line::recvd_client_error(t_response *r, t_tuid tuid, t_tid tid) {
+ t_dialog *d;
+
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (active_dialog) {
+ // If an active dialog exists then non-2XX should
+ // only be for this dialog.
+ if (active_dialog->match_response(r, 0)) {
+ bool response_processed = false;
+
+ if (r->must_authenticate()) {
+ // Authentication for mid-dialog request
+ if (active_dialog->resend_request_auth(r))
+ {
+ // Authorization successul.
+ // The response does not need to be
+ // processed any further
+ response_processed = true;
+ }
+ }
+
+ if (!response_processed) {
+ // The request failed, redirect it if there
+ // are other destinations available.
+ if (!user_config->get_allow_redirection() ||
+ !active_dialog->redirect_request(r))
+ {
+ // Request failed
+ active_dialog->
+ recvd_response(r, tuid, tid);
+ }
+ }
+
+ // Retrieve a held line after a REFER failure
+ if (r->hdr_cseq.method == REFER &&
+ active_dialog->out_refer_req_failed)
+ {
+ active_dialog->out_refer_req_failed = false;
+ if (phone->get_active_line() == line_number &&
+ user_config->get_referrer_hold())
+ {
+ retrieve();
+ }
+ }
+ }
+
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, pending_dialogs);
+ if (d) {
+ if (r->hdr_cseq.method != INVITE) {
+ if (r->must_authenticate()) {
+ // Authentication for non-INVITE request in pending dialog
+ if (!d->resend_request_auth(r)) {
+ // Could not authorize, send response to dialog
+ // where it will be handle as a client failure.
+ d->recvd_response(r, tuid, tid);
+ }
+ } else {
+ d->recvd_response(r, tuid, tid);
+ }
+ } else {
+ d->recvd_response(r, tuid, tid);
+ pending_dialogs.remove(d);
+ MEMMAN_DELETE(d);
+ delete d;
+
+ // RFC 3261 13.2.2.3
+ // All early dialogs are considered terminated
+ // upon reception of the non-2xx final response.
+ list<t_dialog *>::iterator i;
+ for (i = pending_dialogs.begin();
+ i != pending_dialogs.end(); i++)
+ {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+ pending_dialogs.clear();
+
+ if (open_dialog) {
+ bool response_processed = false;
+
+ if (r->must_authenticate()) {
+ // INVITE authentication
+ if (open_dialog->resend_invite_auth(r))
+ {
+ // Authorization successul.
+ // The response does not need to
+ // be processed any further
+ response_processed = true;
+ }
+ }
+
+ // Resend INVITE if the response indicated that
+ // required extensions are not supported.
+ if (!response_processed &&
+ open_dialog->resend_invite_unsupported(r))
+ {
+ response_processed = true;
+ }
+
+ if (!response_processed) {
+ // The request failed, redirect it if there
+ // are other destinations available.
+ if (!user_config->get_allow_redirection() ||
+ !open_dialog->redirect_invite(r))
+ {
+ // Request failed
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ open_dialog = NULL;
+ }
+ }
+ }
+ }
+
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, dying_dialogs);
+ if (d) {
+ d->recvd_response(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ if (open_dialog && open_dialog->match_response(r, tuid)) {
+ // If the response is a 401/407 then do not send the
+ // response to the dialog as the request must be resent.
+ // For an INVITE request, the transaction layer has already
+ // sent ACK for a failure response.
+ if (r->hdr_cseq.method != INVITE) {
+ if (r->must_authenticate()) {
+ // Authenticate non-INVITE request
+ if (!open_dialog->resend_request_auth(r)) {
+ // Could not authorize, handle as other client
+ // errors.
+ open_dialog->recvd_response(r, tuid, tid);
+ }
+ } else {
+ open_dialog->recvd_response(r, tuid, tid);
+ }
+ }
+
+ if (r->hdr_cseq.method == INVITE) {
+ bool response_processed = false;
+
+ if (r->must_authenticate()) {
+ // INVITE authentication
+ if (open_dialog->resend_invite_auth(r))
+ {
+ // Authorization successul.
+ // The response does not need to
+ // be processed any further
+ response_processed = true;
+ }
+ }
+
+ // Resend INVITE if the response indicated that
+ // required extensions are not supported.
+ if (!response_processed &&
+ open_dialog->resend_invite_unsupported(r))
+ {
+ response_processed = true;
+ }
+
+ if (!response_processed) {
+ // The request failed, redirect it if there
+ // are other destinations available.
+ if (!user_config->get_allow_redirection() ||
+ !open_dialog->redirect_invite(r))
+ {
+ // Request failed
+ open_dialog->recvd_response(r, tuid, tid);
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ open_dialog = NULL;
+ }
+ }
+
+ // RFC 3261 13.2.2.3
+ // All early dialogs are considered terminated
+ // upon reception of the non-2xx final response.
+ list<t_dialog *>::iterator i;
+ for (i = pending_dialogs.begin();
+ i != pending_dialogs.end(); i++)
+ {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+ pending_dialogs.clear();
+ }
+
+ cleanup();
+ return;
+ }
+
+ // out-of-dialog responses should be handled by the phone
+}
+
+void t_line::recvd_server_error(t_response *r, t_tuid tuid, t_tid tid) {
+ t_dialog *d;
+
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (active_dialog) {
+ // If an active dialog exists then non-2XX should
+ // only be for this dialog.
+ if (active_dialog->match_response(r, 0)) {
+ bool response_processed = false;
+
+ if (r->code == R_503_SERVICE_UNAVAILABLE) {
+ // RFC 3263 4.3
+ // Failover to next destination
+ if (active_dialog->failover_request(r))
+ {
+ // Failover successul.
+ // The response does not need to be
+ // processed any further
+ response_processed = true;
+ }
+ }
+
+ if (!response_processed) {
+ // The request failed, redirect it if there
+ // are other destinations available.
+ if (!user_config->get_allow_redirection() ||
+ !active_dialog->redirect_request(r))
+ {
+ // Request failed
+ active_dialog->
+ recvd_response(r, tuid, tid);
+ }
+ }
+
+ // Retrieve a held line after a REFER failure
+ if (r->hdr_cseq.method == REFER &&
+ active_dialog->out_refer_req_failed)
+ {
+ active_dialog->out_refer_req_failed = false;
+ if (phone->get_active_line() == line_number &&
+ user_config->get_referrer_hold())
+ {
+ retrieve();
+ }
+ }
+ }
+
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, pending_dialogs);
+ if (d) {
+ d->recvd_response(r, tuid, tid);
+ if (r->hdr_cseq.method == INVITE) {
+ pending_dialogs.remove(d);
+ MEMMAN_DELETE(d);
+ delete d;
+
+ // RFC 3261 13.2.2.3
+ // All early dialogs are considered terminated
+ // upon reception of the non-2xx final response.
+ list<t_dialog *>::iterator i;
+ for (i = pending_dialogs.begin();
+ i != pending_dialogs.end(); i++)
+ {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+ pending_dialogs.clear();
+
+ if (open_dialog) {
+ bool response_processed = false;
+
+ if (r->code == R_503_SERVICE_UNAVAILABLE) {
+ // INVITE failover
+ if (open_dialog->failover_invite())
+ {
+ // Failover successul.
+ // The response does not need to
+ // be processed any further
+ response_processed = true;
+ }
+ }
+
+ if (!response_processed) {
+ // The request failed, redirect it if there
+ // are other destinations available.
+ if (!user_config->get_allow_redirection() ||
+ !open_dialog->redirect_invite(r))
+ {
+ // Request failed
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ open_dialog = NULL;
+ }
+ }
+ }
+ }
+
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, dying_dialogs);
+ if (d) {
+ d->recvd_response(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ if (open_dialog && open_dialog->match_response(r, tuid)) {
+ // If the response is a 503 then do not send the
+ // response to the dialog as the request must be resent.
+ // For an INVITE request, the transaction layer has already
+ // sent ACK for a failure response.
+ if (r->code != R_503_SERVICE_UNAVAILABLE && r->hdr_cseq.method != INVITE) {
+ open_dialog->recvd_response(r, tuid, tid);
+ }
+
+ if (r->hdr_cseq.method == INVITE) {
+ bool response_processed = false;
+
+ if (r->code == R_503_SERVICE_UNAVAILABLE) {
+ // INVITE failover
+ if (open_dialog->failover_invite())
+ {
+ // Failover successul.
+ // The response does not need to
+ // be processed any further
+ response_processed = true;
+ }
+ }
+
+ if (!response_processed) {
+ // The request failed, redirect it if there
+ // are other destinations available.
+ if (!user_config->get_allow_redirection() ||
+ !open_dialog->redirect_invite(r))
+ {
+ // Request failed
+ open_dialog->recvd_response(r, tuid, tid);
+ MEMMAN_DELETE(open_dialog);
+ delete open_dialog;
+ open_dialog = NULL;
+ }
+ }
+
+ // RFC 3261 13.2.2.3
+ // All early dialogs are considered terminated
+ // upon reception of the non-2xx final response.
+ list<t_dialog *>::iterator i;
+ for (i = pending_dialogs.begin();
+ i != pending_dialogs.end(); i++)
+ {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+ pending_dialogs.clear();
+ }
+
+ cleanup();
+ return;
+ }
+
+ // out-of-dialog responses should be handled by the phone
+}
+
+void t_line::recvd_global_error(t_response *r, t_tuid tuid, t_tid tid) {
+ recvd_redirect(r, tuid, tid);
+}
+
+void t_line::recvd_invite(t_phone_user *pu, t_request *r, t_tid tid, const string &ringtone) {
+ t_user *user_config = NULL;
+
+ switch (state) {
+ case LS_IDLE:
+ assert(!active_dialog);
+ assert(r->hdr_to.tag == "");
+
+ /*
+ // TEST ONLY
+ // Test code to test INVITE authentication
+ if (!r->hdr_authorization.is_populated()) {
+ resp = r->create_response(R_401_UNAUTHORIZED);
+ t_challenge c;
+ c.auth_scheme = AUTH_DIGEST;
+ c.digest_challenge.realm = "mtel.nl";
+ c.digest_challenge.nonce = "0123456789abcdef";
+ c.digest_challenge.opaque = "secret";
+ c.digest_challenge.algorithm = ALG_MD5;
+ c.digest_challenge.qop_options.push_back(QOP_AUTH);
+ c.digest_challenge.qop_options.push_back(QOP_AUTH_INT);
+ resp->hdr_www_authenticate.set_challenge(c);
+ send_response(resp, 0, tid);
+ return;
+ }
+ */
+
+ assert(pu);
+ phone_user = pu;
+ user_config = phone_user->get_user_profile();
+ user_defined_ringtone = ringtone;
+
+ call_info.from_uri = r->hdr_from.uri;
+ call_info.from_display = r->hdr_from.display;
+ call_info.from_display_override = r->hdr_from.display_override;
+ if (r->hdr_organization.is_populated()) {
+ call_info.from_organization = r->hdr_organization.name;
+ } else {
+ call_info.from_organization.clear();
+ }
+ call_info.to_uri = r->hdr_to.uri;
+ call_info.to_display = r->hdr_to.display;
+ call_info.to_organization.clear();
+ call_info.subject = r->hdr_subject.subject;
+
+ try_to_encrypt = user_config->get_zrtp_enabled();
+
+ // 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))
+ {
+ call_info.refer_supported = true;
+ }
+
+ active_dialog = new t_dialog(this);
+ MEMMAN_NEW(active_dialog);
+ active_dialog->recvd_request(r, 0, tid);
+ state = LS_BUSY;
+ substate = LSSUB_INCOMING_PROGRESS;
+ ui->cb_line_state_changed();
+ start_timer(LTMR_NO_ANSWER);
+ cleanup();
+
+ // Answer if auto answer mode is activated
+ if (auto_answer) {
+ // Validate speaker and mic
+ string error_msg;
+ if (!sys_config->exec_audio_validation(false, true, true, error_msg)) {
+ ui->cb_display_msg(error_msg, MSG_CRITICAL);
+ } else {
+ answer();
+ }
+ }
+ break;
+ case LS_BUSY:
+ // Only re-INVITEs can be sent to a busy line
+ assert(r->hdr_to.tag != "");
+
+ /*
+ // TEST ONLY
+ // Test code to test re-INVITE authentication
+ if (!r->hdr_authorization.is_populated()) {
+ resp = r->create_response(R_401_UNAUTHORIZED);
+ t_challenge c;
+ c.auth_scheme = AUTH_DIGEST;
+ c.digest_challenge.realm = "mtel.nl";
+ c.digest_challenge.nonce = "0123456789abcdef";
+ c.digest_challenge.opaque = "secret";
+ c.digest_challenge.algorithm = ALG_MD5;
+ c.digest_challenge.qop_options.push_back(QOP_AUTH);
+ c.digest_challenge.qop_options.push_back(QOP_AUTH_INT);
+ resp->hdr_www_authenticate.set_challenge(c);
+ send_response(resp, 0, tid);
+ return;
+ }
+ */
+
+ if (active_dialog && active_dialog->match_request(r)) {
+ // re-INVITE
+ active_dialog->recvd_request(r, 0, tid);
+ cleanup();
+ return;
+ }
+
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+void t_line::recvd_ack(t_request *r, t_tid tid) {
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_request(r, 0, tid);
+ substate = LSSUB_ESTABLISHED;
+ ui->cb_line_state_changed();
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+}
+
+void t_line::recvd_cancel(t_request *r, t_tid cancel_tid,
+ t_tid target_tid)
+{
+ // A CANCEL matches a dialog if the target tid equals the tid
+ // of the INVITE request. This will be checked by
+ // dialog::recvd_cancel() itself.
+ if (active_dialog) {
+ active_dialog->recvd_cancel(r, cancel_tid, target_tid);
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+}
+
+void t_line::recvd_bye(t_request *r, t_tid tid) {
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_request(r, 0, tid);
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+}
+
+void t_line::recvd_options(t_request *r, t_tid tid) {
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_request(r, 0, tid);
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+}
+
+void t_line::recvd_prack(t_request *r, t_tid tid) {
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_request(r, 0, tid);
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+}
+
+void t_line::recvd_subscribe(t_request *r, t_tid tid) {
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_request(r, 0, tid);
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+}
+
+void t_line::recvd_notify(t_request *r, t_tid tid) {
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_request(r, 0, tid);
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+}
+
+void t_line::recvd_info(t_request *r, t_tid tid) {
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_request(r, 0, tid);
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+}
+
+void t_line::recvd_message(t_request *r, t_tid tid) {
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_request(r, 0, tid);
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+}
+
+bool t_line::recvd_refer(t_request *r, t_tid tid) {
+ bool retval = false;
+
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_request(r, 0, tid);
+ retval = active_dialog->refer_accepted;
+ } else {
+ // Should not get here as phone already checked that
+ // the request matched with this line
+ assert(false);
+ }
+ cleanup();
+ return retval;
+}
+
+void t_line::recvd_refer_permission(bool permission, t_request *r) {
+ if (active_dialog && active_dialog->match_request(r)) {
+ active_dialog->recvd_refer_permission(permission, r);
+ }
+ cleanup();
+}
+
+void t_line::recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid) {
+ t_dialog *d;
+
+ if (active_dialog && active_dialog->match_response(r, tuid)) {
+ active_dialog->recvd_stun_resp(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ if (open_dialog && open_dialog->match_response(r, tuid)) {
+ open_dialog->recvd_stun_resp(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, tuid, pending_dialogs);
+ if (d) {
+ d->recvd_stun_resp(r, tuid, tid);
+ cleanup();
+ return;
+ }
+
+ d = match_response(r, tuid, dying_dialogs);
+ if (d) {
+ d->recvd_stun_resp(r, tuid, tid);
+ cleanup();
+ return;
+ }
+}
+
+void t_line::failure(t_failure failure, t_tid tid) {
+ // TODO
+}
+
+void t_line::timeout(t_line_timer timer, t_object_id did) {
+ t_dialog *dialog = get_dialog(did);
+ list<t_display_url> cf_dest; // call forwarding destinations
+
+ switch (timer) {
+ case LTMR_ACK_TIMEOUT:
+ // If there is no dialog then ignore the timeout
+ if (dialog) {
+ dialog->id_ack_timeout = 0;
+ dialog->timeout(timer);
+ }
+ break;
+ case LTMR_ACK_GUARD:
+ // If there is no dialog then ignore the timeout
+ if (dialog) {
+ dialog->id_ack_guard = 0;
+ dialog->dur_ack_timeout = 0;
+ dialog->timeout(timer);
+ }
+ break;
+ case LTMR_INVITE_COMP:
+ id_invite_comp = 0;
+ // RFC 3261 13.2.2.4
+ // The UAC core considers the INVITE transaction completed
+ // 64*T1 seconds after the reception of the first 2XX
+ // response.
+ // Cleanup all open and pending dialogs
+ cleanup_open_pending();
+ break;
+ case LTMR_NO_ANSWER:
+ // User did not answer the call.
+ // Reject call or redirect it if CF_NOANSWER is active.
+ // If there is no active dialog then ignore the timeout.
+ // The timer should have been stopped already.
+ log_file->write_report("No answer timeout",
+ "t_line::timeout");
+
+ if (active_dialog) {
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+ t_service *srv = phone->ref_service(user_config);
+ if (srv->get_cf_active(CF_NOANSWER, cf_dest)) {
+ log_file->write_report("Call redirection no answer",
+ "t_line::timeout");
+ active_dialog->redirect(cf_dest,
+ R_302_MOVED_TEMPORARILY);
+ } else {
+ active_dialog->reject(R_480_TEMP_NOT_AVAILABLE,
+ REASON_480_NO_ANSWER);
+ }
+
+ ui->cb_answer_timeout(get_line_number());
+ }
+ break;
+ case LTMR_RE_INVITE_GUARD:
+ // If there is no dialog then ignore the timeout
+ if (dialog) {
+ dialog->id_re_invite_guard = 0;
+ dialog->timeout(timer);
+ }
+ break;
+ case LTMR_GLARE_RETRY:
+ // If there is no dialog then ignore the timeout
+ if (dialog) {
+ dialog->id_glare_retry = 0;
+ dialog->timeout(timer);
+ }
+ break;
+ case LTMR_100REL_TIMEOUT:
+ // If there is no dialog then ignore the timeout
+ if (dialog) {
+ dialog->id_100rel_timeout = 0;
+ dialog->timeout(timer);
+ }
+ break;
+ case LTMR_100REL_GUARD:
+ // If there is no dialog then ignore the timeout
+ if (dialog) {
+ dialog->id_100rel_guard = 0;
+ dialog->dur_100rel_timeout = 0;
+ dialog->timeout(timer);
+ }
+ break;
+ case LTMR_CANCEL_GUARD:
+ // If there is no dialog then ignore the timeout
+ if (dialog) {
+ dialog->id_cancel_guard = 0;
+ dialog->timeout(timer);
+ }
+ break;
+ default:
+ assert(false);
+ }
+
+ cleanup();
+}
+
+void t_line::timeout_sub(t_subscribe_timer timer, t_object_id did,
+ const string &event_type, const string &event_id)
+{
+ t_dialog *dialog = get_dialog(did);
+ if (dialog) dialog->timeout_sub(timer, event_type, event_id);
+ cleanup();
+}
+
+bool t_line::match(t_response *r, t_tuid tuid) const {
+ if (open_dialog && open_dialog->match_response(r, tuid)) {
+ return true;
+ }
+
+ if (active_dialog && active_dialog->match_response(r, 0)) {
+ return true;
+ }
+
+ if (match_response(r, pending_dialogs)) {
+ return true;
+ }
+
+ if (match_response(r, dying_dialogs)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool t_line::match(t_request *r) const {
+ assert(r->method != CANCEL);
+ return (active_dialog && active_dialog->match_request(r));
+}
+
+bool t_line::match_cancel(t_request *r, t_tid target_tid) const {
+ assert(r->method == CANCEL);
+
+ // A CANCEL matches a dialog if the target tid equals the tid
+ // of the INVITE request.
+ return (active_dialog && active_dialog->match_cancel(r, target_tid));
+}
+
+bool t_line::match(StunMessage *r, t_tuid tuid) const {
+ if (open_dialog && open_dialog->match_response(r, tuid)) {
+ return true;
+ }
+
+ if (active_dialog && active_dialog->match_response(r, tuid)) {
+ return true;
+ }
+
+ if (match_response(r, tuid, pending_dialogs)) {
+ return true;
+ }
+
+ if (match_response(r, tuid, dying_dialogs)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool t_line::match_replaces(const string &call_id, const string &to_tag,
+ const string &from_tag, bool no_fork_req_disposition,
+ bool &early_matched) const
+{
+ if (active_dialog && active_dialog->match(call_id, to_tag, from_tag)) {
+ early_matched = false;
+ return true;
+ }
+
+ // RFC 3891 3
+ // An early dialog only matches when it was created by the UA
+ // As an exception to this rule we accept a match when the incoming
+ // request contained a no-fork request disposition. This disposition
+ // indicated that the request did not fork. The reason why RFC 3891 3
+ // does not allow a match is to avoid problems with forked requests.
+ // With this exception, call transfer scenario's during ringing can
+ // be implemented.
+ t_dialog *d;
+ if ((d = match_call_id_tags(call_id, to_tag, from_tag, pending_dialogs)) != NULL &&
+ (d->is_call_id_owner() || no_fork_req_disposition))
+ {
+ early_matched = true;
+ return true;
+ }
+
+ return false;
+}
+
+bool t_line::is_invite_retrans(t_request *r) {
+ assert(r->method == INVITE);
+ return (active_dialog && active_dialog->is_invite_retrans(r));
+}
+
+void t_line::process_invite_retrans(void) {
+ if (active_dialog) active_dialog->process_invite_retrans();
+}
+
+string t_line::create_user_contact(const string &auto_ip) const {
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+ return user_config->create_user_contact(hide_user, auto_ip);
+}
+
+string t_line::create_user_uri(void) const {
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+ return user_config->create_user_uri(hide_user);
+}
+
+t_response *t_line::create_options_response(t_request *r, bool in_dialog) const
+{
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+ return phone->create_options_response(user_config, r, in_dialog);
+}
+
+void t_line::send_response(t_response *r, t_tuid tuid, t_tid tid) {
+ if (hide_user) {
+ r->hdr_privacy.add_privacy(PRIVACY_ID);
+ }
+ phone->send_response(r, tuid, tid);
+}
+
+void t_line::send_request(t_request *r, t_tuid tuid) {
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+ phone->send_request(user_config, r, tuid);
+}
+
+t_phone *t_line::get_phone(void) const {
+ return phone;
+}
+
+unsigned short t_line::get_line_number(void) const {
+ return line_number;
+}
+
+bool t_line::get_is_on_hold(void) const {
+ return is_on_hold;
+}
+
+bool t_line::get_is_muted(void) const {
+ return is_muted;
+}
+
+bool t_line::get_hide_user(void) const {
+ return hide_user;
+}
+
+bool t_line::get_is_transfer_consult(unsigned short &lineno) const {
+ lineno = consult_transfer_from_line;
+ return is_transfer_consult;
+}
+
+void t_line::set_is_transfer_consult(bool enable, unsigned short lineno) {
+ is_transfer_consult = enable;
+ consult_transfer_from_line = lineno;
+}
+
+bool t_line::get_to_be_transferred(unsigned short &lineno) const {
+ lineno = consult_transfer_to_line;
+ return to_be_transferred;
+}
+
+void t_line::set_to_be_transferred(bool enable, unsigned short lineno) {
+ to_be_transferred = enable;
+ consult_transfer_to_line = lineno;
+}
+
+bool t_line::get_is_encrypted(void) const {
+ t_audio_session *as = get_audio_session();
+ if (as) return as->get_is_encrypted();
+ return false;
+}
+
+bool t_line::get_try_to_encrypt(void) const {
+ return try_to_encrypt;
+}
+
+bool t_line::get_auto_answer(void) const {
+ return auto_answer;
+}
+
+void t_line::set_auto_answer(bool enable) {
+ auto_answer = enable;
+}
+
+bool t_line::is_refer_succeeded(void) const {
+ if (active_dialog) return active_dialog->refer_succeeded;
+ return false;
+}
+
+bool t_line::has_media(void) const {
+ t_session *session = get_session();
+ return (session && !session->receive_host.empty() && !session->dst_rtp_host.empty());
+}
+
+t_url t_line::get_remote_target_uri(void) const {
+ if (!active_dialog) return t_url();
+ return active_dialog->get_remote_target_uri();
+}
+
+t_url t_line::get_remote_target_uri_pending(void) const {
+ if (pending_dialogs.empty()) return t_url();
+ return pending_dialogs.front()->get_remote_target_uri();
+}
+
+string t_line::get_remote_target_display(void) const {
+ if (!active_dialog) return "";
+ return active_dialog->get_remote_target_display();
+}
+
+string t_line::get_remote_target_display_pending(void) const {
+ if (pending_dialogs.empty()) return "";
+ return pending_dialogs.front()->get_remote_target_display();
+}
+
+t_url t_line::get_remote_uri(void) const {
+ if (!active_dialog) return t_url();
+ return active_dialog->get_remote_uri();
+}
+
+t_url t_line::get_remote_uri_pending(void) const {
+ if (pending_dialogs.empty()) return t_url();
+ return pending_dialogs.front()->get_remote_uri();
+}
+
+string t_line::get_remote_display(void) const {
+ if (!active_dialog) return "";
+ return active_dialog->get_remote_display();
+}
+
+string t_line::get_remote_display_pending(void) const {
+ if (pending_dialogs.empty()) return "";
+ return pending_dialogs.front()->get_remote_display();
+}
+
+string t_line::get_call_id(void) const {
+ if (!active_dialog) return "";
+ return active_dialog->get_call_id();
+}
+
+string t_line::get_call_id_pending(void) const {
+ if (pending_dialogs.empty()) return "";
+ return pending_dialogs.front()->get_call_id();
+}
+
+string t_line::get_local_tag(void) const {
+ if (!active_dialog) return "";
+ return active_dialog->get_local_tag();
+}
+
+string t_line::get_local_tag_pending(void) const {
+ if (pending_dialogs.empty()) return "";
+ return pending_dialogs.front()->get_local_tag();
+}
+
+string t_line::get_remote_tag(void) const {
+ if (!active_dialog) return "";
+ return active_dialog->get_remote_tag();
+}
+
+string t_line::get_remote_tag_pending(void) const {
+ if (pending_dialogs.empty()) return "";
+ return pending_dialogs.front()->get_remote_tag();
+}
+
+bool t_line::remote_extension_supported(const string &extension) const {
+ if (!active_dialog) return false;
+ return active_dialog->remote_extension_supported(extension);
+}
+
+bool t_line::seize(void) {
+ // Only an idle line can be seized.
+ if (substate != LSSUB_IDLE) return false;
+
+ substate = LSSUB_SEIZED;
+ ui->cb_line_state_changed();
+
+ return true;
+}
+
+void t_line::unseize(void) {
+ // Only a seized line can be unseized.
+ if (substate != LSSUB_SEIZED) return;
+
+ substate = LSSUB_IDLE;
+ ui->cb_line_state_changed();
+}
+
+t_session *t_line::get_session(void) const {
+ if (!active_dialog) return NULL;
+
+ return active_dialog->get_session();
+}
+
+t_audio_session *t_line::get_audio_session(void) const {
+ if (!active_dialog) return NULL;
+
+ return active_dialog->get_audio_session();
+}
+
+void t_line::notify_refer_progress(t_response *r) {
+ if (active_dialog) active_dialog->notify_refer_progress(r);
+}
+
+void t_line::failed_retrieve(void) {
+ // Call retrieve failed, so line is still on-hold
+ is_on_hold = true;
+ ui->cb_line_state_changed();
+}
+
+void t_line::failed_hold(void) {
+ // Call hold failed, so line is not on-hold
+ is_on_hold = false;
+ ui->cb_line_state_changed();
+}
+
+void t_line::retry_retrieve_succeeded(void) {
+ // Retry of retrieve succeeded, so line is not on-hold anymore
+ is_on_hold = false;
+ ui->cb_line_state_changed();
+}
+
+t_call_info t_line::get_call_info(void) const {
+ return call_info;
+}
+
+void t_line::ci_set_dtmf_supported(bool supported, bool inband, bool info) {
+ call_info.dtmf_supported = supported;
+ call_info.dtmf_inband = inband;
+ call_info.dtmf_info = info;
+}
+
+void t_line::ci_set_last_provisional_reason(const string &reason) {
+ call_info.last_provisional_reason = reason;
+}
+
+void t_line::ci_set_send_codec(t_audio_codec codec) {
+ call_info.send_codec = codec;
+}
+
+void t_line::ci_set_recv_codec(t_audio_codec codec) {
+ call_info.recv_codec = codec;
+}
+
+void t_line::ci_set_refer_supported(bool supported) {
+ call_info.refer_supported = supported;
+}
+
+void t_line::init_rtp_port(void) {
+ rtp_port = sys_config->get_rtp_port() + line_number * 2;
+}
+
+unsigned short t_line::get_rtp_port(void) const {
+ return rtp_port;
+}
+
+t_user *t_line::get_user(void) const {
+ t_user *user_config = NULL;
+
+ if (phone_user) {
+ user_config = phone_user->get_user_profile();
+ }
+
+ return user_config;
+}
+
+t_phone_user *t_line::get_phone_user(void) const {
+ return phone_user;
+}
+
+string t_line::get_ringtone(void) const {
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (!user_defined_ringtone.empty()) {
+ // Ring tone returned by incoming call script
+ return user_defined_ringtone;
+ } else if (!user_config->get_ringtone_file().empty()) {
+ // Ring tone from user profile
+ return user_config->get_ringtone_file();
+ } else if (!sys_config->get_ringtone_file().empty()) {
+ // Ring tone from system settings
+ return sys_config->get_ringtone_file();
+ } else {
+ // Twinkle default
+ return FILE_RINGTONE;
+ }
+}
+
+void t_line::confirm_zrtp_sas(void) {
+ t_audio_session *as = get_audio_session();
+
+ if (as && !as->get_zrtp_sas_confirmed()) {
+ as->confirm_zrtp_sas();
+ ui->cb_zrtp_sas_confirmed(line_number);
+ ui->cb_line_state_changed();
+ log_file->write_header("t_line::confirm_zrtp_sas");
+ log_file->write_raw("Line ");
+ log_file->write_raw(line_number + 1);
+ log_file->write_raw(": User confirmed ZRTP SAS\n");
+ log_file->write_footer();
+ }
+}
+
+void t_line::reset_zrtp_sas_confirmation(void) {
+ t_audio_session *as = get_audio_session();
+
+ if (as && as->get_zrtp_sas_confirmed()) {
+ as->reset_zrtp_sas_confirmation();
+ ui->cb_zrtp_sas_confirmation_reset(line_number);
+ ui->cb_line_state_changed();
+ log_file->write_header("t_line::reset_zrtp_sas_confirmation");
+ log_file->write_raw("Line ");
+ log_file->write_raw(line_number + 1);
+ log_file->write_raw(": User reset ZRTP SAS confirmation\n");
+ log_file->write_footer();
+ }
+}
+
+void t_line::enable_zrtp(void) {
+ t_audio_session *as = get_audio_session();
+ if (as) {
+ as->enable_zrtp();
+ }
+}
+
+void t_line::zrtp_request_go_clear(void) {
+ t_audio_session *as = get_audio_session();
+ if (as) {
+ as->zrtp_request_go_clear();
+ }
+}
+
+void t_line::zrtp_go_clear_ok(void) {
+ t_audio_session *as = get_audio_session();
+ if (as) {
+ as->zrtp_go_clear_ok();
+ }
+}
+
+void t_line::force_idle(void) {
+ cleanup_forced();
+}
+
+void t_line::set_keep_seized(bool seize) {
+ keep_seized = seize;
+ cleanup();
+}
+
+bool t_line::get_keep_seized(void) const {
+ return keep_seized;
+}
+
+t_dialog *t_line::get_dialog_with_active_session(void) const {
+ if (open_dialog && open_dialog->has_active_session()) {
+ return open_dialog;
+ }
+
+ if (active_dialog && active_dialog->has_active_session()) {
+ return active_dialog;
+ }
+
+ for (list<t_dialog *>::const_iterator it = pending_dialogs.begin();
+ it != pending_dialogs.end(); ++it)
+ {
+ if ((*it)->has_active_session()) {
+ return *it;
+ }
+ }
+
+ for (list<t_dialog *>::const_iterator it = dying_dialogs.begin();
+ it != dying_dialogs.end(); ++it)
+ {
+ if ((*it)->has_active_session()) {
+ return *it;
+ }
+ }
+
+ return NULL;
+}