diff options
Diffstat (limited to 'src/phone.cpp')
-rw-r--r-- | src/phone.cpp | 3562 |
1 files changed, 3562 insertions, 0 deletions
diff --git a/src/phone.cpp b/src/phone.cpp new file mode 100644 index 0000000..96882c6 --- /dev/null +++ b/src/phone.cpp @@ -0,0 +1,3562 @@ +/* + 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 <sys/types.h> +#include <sys/wait.h> +#include "call_history.h" +#include "call_script.h" +#include "exceptions.h" +#include "phone.h" +#include "line.h" +#include "log.h" +#include "sdp/sdp.h" +#include "translator.h" +#include "util.h" +#include "user.h" +#include "userintf.h" +#include "audits/memman.h" +#include "parser/parse_ctrl.h" +#include "sockets/socket.h" +#include "stun/stun_transaction.h" + +extern t_phone *phone; +extern t_event_queue *evq_timekeeper; +extern string user_host; + +// t_transfer_data + +t_transfer_data::t_transfer_data(t_request *r, unsigned short _lineno, bool _hide_user, + t_phone_user *pu) : + refer_request(dynamic_cast<t_request *>(r->copy())), + lineno(_lineno), + hide_user(_hide_user), + phone_user(pu) +{} + +t_transfer_data::~t_transfer_data() { + MEMMAN_DELETE(refer_request); + delete refer_request; +} + +t_request *t_transfer_data::get_refer_request(void) const { + return refer_request; +} + +bool t_transfer_data::get_hide_user(void) const { + return hide_user; +} + +unsigned short t_transfer_data::get_lineno(void) const { + return lineno; +} + +t_phone_user *t_transfer_data::get_phone_user(void) const { + return phone_user; +} + + +// t_phone + +/////////// +// Private +/////////// + +void t_phone::move_line_to_background(unsigned short lineno) { + // The line will be released in the background. It should + // immediately release its RTP ports as these maybe needed + // for new calls. + lines.at(lineno)->kill_rtp(); + + cleanup_3way_state(lineno); + + // Move the line to the back of the vector. + lines.push_back(lines.at(lineno)); + lines.back()->line_number = lines.size() - 1; + + // Create a new line for making calls. + lines.at(lineno) = new t_line(this, lineno); + MEMMAN_NEW(lines.at(lineno)); + + // The new line must have the same RTP port as the + // releasing line, otherwise it may conflict with + // the other lines. Due to call transfers, the port + // number may be unrelated to the line position. + lines.at(lineno)->rtp_port = lines.back()->get_rtp_port(); + + log_file->write_header("t_phone::move_line_to_background", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Moved line "); + log_file->write_raw(lineno + 1); + log_file->write_raw(" to background position "); + log_file->write_raw(lines.back()->get_line_number() + 1); + log_file->write_endl(); + log_file->write_footer(); + + // Notify the user interface of the line state change + ui->cb_line_state_changed(); +} + +void t_phone::cleanup_dead_lines(void) { + // Only remove idle lines at the end of the dead pool to avoid + // moving lines in the vector. + while (lines.size() > NUM_CALL_LINES && lines.back()->get_state() == LS_IDLE) + { + + log_file->write_header("t_phone::cleanup_dead_lines", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Removed dead line "); + log_file->write_raw(lines.back()->get_line_number() + 1); + log_file->write_endl(); + log_file->write_footer(); + + MEMMAN_DELETE(lines.back()); + delete lines.back(); + lines.pop_back(); + } +} + +void t_phone::move_releasing_lines_to_background(void) { + // NOTE: the line on the REFERRER position is not moved to the + // background as a subscription may still be active. + for (int i = 0; i < NUM_USER_LINES; i++) { + if (lines.at(i)->get_substate() == LSSUB_RELEASING && + !lines.at(i)->get_keep_seized()) + { + move_line_to_background(i); + } + } +} + +void t_phone::cleanup_3way_state(unsigned short lineno) { + assert(lineno < lines.size()); + + lock(); + + // Clean up 3-way data if the line was involved in a 3-way + if (is_3way) + { + bool line_in_3way = false; + t_audio_session *as_peer; + t_line *line_peer; + + if (lineno == line1_3way->get_line_number()) { + line_in_3way = true; + line_peer = line2_3way; + } else if (lineno == line2_3way->get_line_number()) { + line_in_3way = true; + line_peer = line1_3way; + } + + if (line_in_3way) { + // Stop the 3-way mixing on the peer line + as_peer = line_peer->get_audio_session(); + if (as_peer) as_peer->stop_3way(); + + // Make the peer line the active line + set_active_line(line_peer->get_line_number()); + + // If the 3-way was with mixed codec sample rates, then + // the remaining audio session might have a mismatch + // between the sound card sample rate and the codec + // sample rate. In that case clear the sample rate by + // toggling the audio session off and on. + if (!as_peer->matching_sample_rates()) { + log_file->write_report( + "Hold/retrieve call to align codec and sound card.", + "t_phone::line_cleared", LOG_NORMAL, LOG_DEBUG); + line_peer->hold(true); + line_peer->retrieve(); + } + + is_3way = false; + line1_3way = NULL; + line2_3way = NULL; + ui->cb_line_state_changed(); + } + } + + unlock(); +} + +void t_phone::cleanup_3way(void) { + if (!is_3way) return; + + if (line1_3way->get_substate() == LSSUB_IDLE) { + cleanup_3way_state(line1_3way->get_line_number()); + } else if (line2_3way->get_substate() == LSSUB_IDLE) { + cleanup_3way_state(line2_3way->get_line_number()); + } +} + +void t_phone::invite(t_phone_user *pu, const t_url &to_uri, const string &to_display, + const string &subject, bool no_fork, bool anonymous) +{ + // Ignore if active line is not idle + if (lines[active_line]->get_state() != LS_IDLE) { + return; + } + + lines[active_line]->invite(pu, to_uri, to_display, subject, no_fork, anonymous); +} + +void t_phone::answer(void) { + // Ignore if active line is idle + if (lines[active_line]->get_state() == LS_IDLE) return; + + lines[active_line]->answer(); +} + +void t_phone::reject(void) { + // Ignore if active line is idle + if (lines[active_line]->get_state() == LS_IDLE) return; + + lines[active_line]->reject(); +} + +void t_phone::reject(unsigned short line) { + if (line > NUM_USER_LINES) return; + if (lines[line]->get_state() == LS_IDLE) return; + + lines[line]->reject(); +} + +void t_phone::redirect(const list<t_display_url> &destinations, int code, string reason) +{ + // Ignore if active line is idle + if (lines[active_line]->get_state() == LS_IDLE) return; + + lines[active_line]->redirect(destinations, code, reason); +} + +void t_phone::end_call(void) { + // If 3-way is active then end call on both lines + if (is_3way && ( + active_line == line1_3way->get_line_number() || + active_line == line2_3way->get_line_number())) + { + if (sys_config->get_hangup_both_3way()) { + line1_3way->end_call(); + line2_3way->end_call(); + + // NOTE: moving a line to the dying pool causes the + // 3way line pointers to be cleared. + unsigned short lineno1 = line1_3way->get_line_number(); + unsigned short lineno2 = line2_3way->get_line_number(); + move_line_to_background(lineno1); + move_line_to_background(lineno2); + } else { + // Hangup the active line, and make the next + // line active. + int l = active_line; + activate_line((l+1) % NUM_USER_LINES); + lines.at(l)->end_call(); + move_line_to_background(l); + } + + return; + } + + // Ignore if active line is idle + if (lines.at(active_line)->get_state() == LS_IDLE) return; + + lines.at(active_line)->end_call(); + move_line_to_background(active_line); +} + +void t_phone::registration(t_phone_user *pu, t_register_type register_type, + unsigned long expires) +{ + pu->registration(register_type, false, expires); +} + +void t_phone::options(t_phone_user *pu, const t_url &to_uri, const string &to_display) { + pu->options(to_uri, to_display); +} + +void t_phone::options(void) { + lines[active_line]->options(); +} + +bool t_phone::hold(bool rtponly) { + // A line in a 3-way call cannot be held + if (is_3way && ( + active_line == line1_3way->get_line_number() || + active_line == line2_3way->get_line_number())) + { + return false; + } + + return lines[active_line]->hold(rtponly); +} + +void t_phone::retrieve(void) { + lines[active_line]->retrieve(); +} + +void t_phone::refer(const t_url &uri, const string &display) { + lines[active_line]->refer(uri, display); +} + +void t_phone::refer(unsigned short lineno_from, unsigned short lineno_to) +{ + // The nicest transfer is an attended transfer. An attended transfer + // is only possible of the transfer target supports the 'replaces' + // extension (RFC 3891). + // If 'replaces' is not supported, then a transfer with consultation + // is done. First hang up the consultation call, then transfer the + // line. + // HACK: if the call is in progress, then assume that Replaces is + // supported. We don't know if it is as the call is not established + // yet. An in-progress call can only be replaced if the user + // deliberately allowed this (allow_transfer_consultation_inprog). + if (lines.at(lineno_to)->remote_extension_supported(EXT_REPLACES)) { + log_file->write_report("Remote end supports 'replaces'.\n"\ + "Attended transfer.", + "t_phone::refer"); + refer_attended(lineno_from, lineno_to); + } else if (get_line_substate(lineno_to) == LSSUB_OUTGOING_PROGRESS) { + log_file->write_report("Call transfer while consultation in progress.\n"\ + "Attended transfer.", + "t_phone::refer"); + refer_attended(lineno_from, lineno_to); + } else { + log_file->write_report("Remote end does not support 'replaces'.\n"\ + "Transfer with consultation.", + "t_phone::refer"); + refer_consultation(lineno_from, lineno_to); + } +} + +// Attended call transfer +// See draft-ietf-sipping-cc-transfer-07 7.3 +void t_phone::refer_attended(unsigned short lineno_from, unsigned short lineno_to) +{ + t_line *line = lines.at(lineno_to); + + switch (line->get_substate()) + { + case LSSUB_ESTABLISHED: + // Transfer allowed + break; + case LSSUB_OUTGOING_PROGRESS: + { + unsigned short dummy; + t_user *user_config = get_line_user(lineno_to); + if (!user_config->get_allow_transfer_consultation_inprog() || + !is_line_transfer_consult(lineno_to, dummy)) + { + // Transfer not allowed + return; + } + } + + // Transfer allowed + break; + default: + // Transfer not allowed + return; + }; + + t_user *user_config = get_line_user(lineno_from); + + // draft-ietf-sipping-cc-transfer-07 section 7.3 + // The call must be referred to the contact URI of the far-end. + // As the contact URI may not be globally routable, the AoR + // may be used alternatively. + t_url uri; + string display; + if (user_config->get_attended_refer_to_aor()) + { + if (line->get_substate() == LSSUB_OUTGOING_PROGRESS) { + uri = line->get_remote_uri_pending(); + display = line->get_remote_target_display_pending(); + } else { + uri = line->get_remote_uri(); + display = line->get_remote_target_display(); + } + } else { + if (line->get_substate() == LSSUB_OUTGOING_PROGRESS) { + uri = line->get_remote_target_uri_pending(); + display = line->get_remote_target_display_pending(); + } else { + uri = line->get_remote_target_uri(); + display = line->get_remote_target_display(); + } + } + + // Create Replaces header for replacing the call on lineno_to + t_hdr_replaces hdr_replaces; + + if (line->get_substate() == LSSUB_OUTGOING_PROGRESS) { + hdr_replaces.set_call_id(line->get_call_id_pending()); + hdr_replaces.set_from_tag(line->get_local_tag_pending()); + hdr_replaces.set_to_tag(line->get_remote_tag_pending()); + } else { + hdr_replaces.set_call_id(line->get_call_id()); + hdr_replaces.set_from_tag(line->get_local_tag()); + hdr_replaces.set_to_tag(line->get_remote_tag()); + } + + uri.add_header(hdr_replaces); + + // draft-ietf-sipping-cc-transfer-07 section 7.3 + // If the call is referred to the AoR, then add a Require header + // that requires the 'Replaces' extension, to make the correct phone + // ring in case of forking. + if (user_config->get_attended_refer_to_aor()) { + t_hdr_require hdr_require; + hdr_require.add_feature(EXT_REPLACES); + uri.add_header(hdr_require); + } + + // Transfer call + lines.at(lineno_from)->refer(uri, display); +} + +// Call transfer with consultation +// See draft-ietf-sipping-cc-transfer-07 7 +void t_phone::refer_consultation(unsigned short lineno_from, unsigned short lineno_to) +{ + t_line *line = lines.at(lineno_to); + + if (line->get_substate() != LSSUB_ESTABLISHED) { + return; + } + + // Refer call to the URI of the far-end + t_url uri = line->get_remote_uri(); + string display = line->get_remote_display(); + + // End consultation call + line->end_call(); + move_line_to_background(lineno_to); + + // Transfer call + lines.at(lineno_from)->refer(uri, display); +} + +void t_phone::setup_consultation_call(const t_url &uri, const string &display) { + unsigned short consult_line; + if (!get_idle_line(consult_line)) { + log_file->write_report("Cannot get idle line for consultation call.", + "t_phone::setup_consultation_call"); + return; + } + + unsigned short xfer_line = active_line; + t_user *user_config = get_line_user(xfer_line); + + t_phone_user *pu = find_phone_user(user_config->get_profile_name()); + if (!pu) { + log_file->write_header("t_phone::setup_consultation_call", + LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user_config->get_profile_name()); + log_file->write_footer(); + } + + activate_line(consult_line); + + string subject = TRANSLATE("Call transfer - %1"); + subject = replace_first(subject, "%1", + ui->format_sip_address(user_config, + get_remote_display(xfer_line), + get_remote_uri(xfer_line))); + + bool no_fork = false; + if (user_config->get_allow_transfer_consultation_inprog()) { + // If the configuration allows a call to be transferred + // while the consultation call is still in progress, then + // we send a no-fork request disposition in the INVTE + // to setup the consultation call. This way we know that + // the call has not been forked and it should be possible + // to replace the early dialog. + // + // The scenario is: + // A calls B + // B sets up a consultation call to C (no-fork) + // B sends a REFER with replaces to A + // A sends an INVITE with replaces to C. + // + // NOTE: this is a non-standard implementation. RFC 3891 + // does not allow to replace an early dialog not + // setup by the UA. In this case the REFER from A to C + // intends to replace the dialog from B to C, but C + // did not setup the B-C dialog itself. + no_fork = true; + } + + invite(pu, uri, display, subject, no_fork, false); + + lines.at(consult_line)->set_is_transfer_consult(true, xfer_line); + lines.at(xfer_line)->set_to_be_transferred(true, consult_line); + + ui->cb_consultation_call_setup(user_config, consult_line); +} + +void t_phone::activate_line(unsigned short l) { + unsigned short a = get_active_line(); + if (a == l) return; + + // Just switch the active line if there is a conference. + if (is_3way) { + set_active_line(l); + ui->cb_line_state_changed(); + return; + } + + + // Put the current active line on hold if it has a call. + // Only established calls can be put on-hold. Transient calls + // should be torn down or just kept in the same transient state + // when switching to the other line. + if (get_line(a)->get_state() == LS_BUSY && !hold()) { + // The line is busy but could not be put on-hold. Determine + // what to do based on the line sub state. + switch(get_line(a)->get_substate()) { + case LSSUB_OUTGOING_PROGRESS: + // User has outgoing call in progress on the active + // line, but decided to switch line, so tear down + // the call. + end_call(); + ui->cb_stop_call_notification(a); + break; + case LSSUB_INCOMING_PROGRESS: + // The incoming call on the current active will stay, + // just stop the ring tone. + ui->cb_stop_call_notification(a); + break; + case LSSUB_ANSWERING: + // Answering is in progress, so call cannot be put + // on-hold. Tear down the call. + end_call(); + break; + case LSSUB_RELEASING: + // The releasing call on the current line will get + // released. No need to take any action here. + break; + default: + // This should not happen. + log_file->write_report("ERROR: Call cannot be put on hold.", + "t_phone::activate_line"); + } + } + + set_active_line(l); + + // Retrieve the call on the new active line unless that line + // is transferring a call and the user profile indicates that + // the referrer holds the call during call transfer. + t_user *user_config = lines[l]->get_user(); + if (get_line_refer_state(l) == REFST_NULL || + (user_config && !user_config->get_referrer_hold())) + { + retrieve(); + } + + // Play ring tone, if the new active line has an incoming call + // in progress. + if (get_line(l)->get_substate() == LSSUB_INCOMING_PROGRESS) { + ui->cb_play_ringtone(l); + } + + ui->cb_line_state_changed(); +} + +void t_phone::send_dtmf(char digit, bool inband, bool info) { + lines[active_line]->send_dtmf(digit, inband, info); +} + +void t_phone::start_timer(t_phone_timer timer, t_phone_user *pu) { + t_tmr_phone *t; + t_user *user_config = pu->get_user_profile(); + + switch(timer) { + case PTMR_NAT_KEEPALIVE: + t = new t_tmr_phone(user_config->get_timer_nat_keepalive() * 1000, timer, this); + MEMMAN_NEW(t); + pu->id_nat_keepalive = t->get_object_id(); + break; + case PTMR_TCP_PING: + t = new t_tmr_phone(user_config->get_timer_tcp_ping() * 1000, timer, this); + MEMMAN_NEW(t); + pu->id_tcp_ping = t->get_object_id(); + break; + default: + assert(false); + } + + evq_timekeeper->push_start_timer(t); + MEMMAN_DELETE(t); + delete t; +} + +void t_phone::stop_timer(t_phone_timer timer, t_phone_user *pu) { + unsigned short *id; + + switch(timer) { + case PTMR_REGISTRATION: + id = &pu->id_registration; + break; + case PTMR_NAT_KEEPALIVE: + id = &pu->id_nat_keepalive; + break; + case PTMR_TCP_PING: + id = &pu->id_tcp_ping; + break; + default: + assert(false); + } + + if (*id != 0) evq_timekeeper->push_stop_timer(*id); + *id = 0; +} + +void t_phone::start_set_timer(t_phone_timer timer, long time, t_phone_user *pu) { + t_tmr_phone *t; + + + switch(timer) { + case PTMR_REGISTRATION: + long new_time; + + // Re-register before registration expires + if (pu->get_last_reg_failed() || time <= RE_REGISTER_DELTA * 1000) { + new_time = time; + } else { + new_time = time - (RE_REGISTER_DELTA * 1000); + } + t = new t_tmr_phone(new_time, timer, this); + MEMMAN_NEW(t); + pu->id_registration = t->get_object_id(); + break; + default: + assert(false); + } + + evq_timekeeper->push_start_timer(t); + MEMMAN_DELETE(t); + delete t; +} + +void t_phone::handle_response_out_of_dialog(t_response *r, t_tuid tuid, t_tid tid) { + t_phone_user *pu = match_phone_user(r, tuid); + if (!pu) { + log_file->write_report("Response does not match any pending request.", + "t_phone::handle_response_out_of_dialog"); + return; + } + + log_file->write_header("t_phone::handle_response_out_of_dialog", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Out of dialog matches phone user: "); + log_file->write_raw(pu->get_user_profile()->get_profile_name()); + log_file->write_endl(); + log_file->write_footer(); + + pu->handle_response_out_of_dialog(r, tuid, tid); +} + +void t_phone::handle_response_out_of_dialog(StunMessage *r, t_tuid tuid) { + t_phone_user *pu = match_phone_user(r, tuid); + if (!pu) { + log_file->write_report("STUN response does not match any pending request.", + "t_phone::handle_response_out_of_dialog"); + return; + } + + pu->handle_response_out_of_dialog(r, tuid); +} + +t_phone_user *t_phone::find_phone_user(const string &profile_name) const { + for (list<t_phone_user *>::const_iterator i = phone_users.begin(); + i != phone_users.end(); ++i) + { + if (!(*i)->is_active()) continue; + + t_user *user_config = (*i)->get_user_profile(); + if (user_config->get_profile_name() == profile_name) { + return *i; + } + } + + return NULL; +} + +t_phone_user *t_phone::find_phone_user(const t_url &user_uri) const { + for (list<t_phone_user *>::const_iterator i = phone_users.begin(); + i != phone_users.end(); ++i) + { + if (!(*i)->is_active()) continue; + + t_user *user_config = (*i)->get_user_profile(); + if (t_url(user_config->create_user_uri(false)) == user_uri) { + return *i; + } + } + + return NULL; +} + +t_phone_user *t_phone::match_phone_user(t_response *r, t_tuid tuid, bool active_only) { + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); ++i) + { + if (active_only && !(*i)->is_active()) continue; + if ((*i)->match(r, tuid)) return *i; + } + + return NULL; +} + +t_phone_user *t_phone::match_phone_user(t_request *r, bool active_only) { + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); ++i) + { + if (active_only && !(*i)->is_active()) continue; + if ((*i)->match(r)) return *i; + } + + return NULL; +} + +t_phone_user *t_phone::match_phone_user(StunMessage *r, t_tuid tuid, bool active_only) { + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); ++i) + { + if (active_only && !(*i)->is_active()) continue; + if ((*i)->match(r, tuid)) return *i; + } + + return NULL; +} + +int t_phone::hunt_line(void) { + // Send incoming call to active line if it is idle. + if (lines.at(active_line)->get_substate() == LSSUB_IDLE) { + return active_line; + } + + if (sys_config->get_call_waiting() || all_lines_idle()) { + // Send the INVITE to the first idle unseized line + for (unsigned short i = 0; i < NUM_USER_LINES; i++) { + if (lines[i]->get_substate() == LSSUB_IDLE) { + return i; + } + } + } + + return -1; +} + +////////////// +// Protected +////////////// + +void t_phone::recvd_provisional(t_response *r, t_tuid tuid, t_tid tid) { + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r, tuid)) { + lines[i]->recvd_provisional(r, tuid, tid); + 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_phone::recvd_success(t_response *r, t_tuid tuid, t_tid tid) { + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r, tuid)) { + lines[i]->recvd_success(r, tuid, tid); + return; + } + } + + // out-of-dialog responses + handle_response_out_of_dialog(r, tuid, tid); +} + +void t_phone::recvd_redirect(t_response *r, t_tuid tuid, t_tid tid) { + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r, tuid)) { + lines[i]->recvd_redirect(r, tuid, tid); + return; + } + } + + // out-of-dialog responses + handle_response_out_of_dialog(r, tuid, tid); +} + +void t_phone::recvd_client_error(t_response *r, t_tuid tuid, t_tid tid) { + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r, tuid)) { + lines[i]->recvd_client_error(r, tuid, tid); + return; + } + } + + // out-of-dialog responses + handle_response_out_of_dialog(r, tuid, tid); +} + +void t_phone::recvd_server_error(t_response *r, t_tuid tuid, t_tid tid) { + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r, tuid)) { + lines[i]->recvd_server_error(r, tuid, tid); + return; + } + } + + // out-of-dialog responses + handle_response_out_of_dialog(r, tuid, tid); +} + +void t_phone::recvd_global_error(t_response *r, t_tuid tuid, t_tid tid) { + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r, tuid)) { + lines[i]->recvd_global_error(r, tuid, tid); + return; + } + } + + // out-of-dialog responses + handle_response_out_of_dialog(r, tuid, tid); +} + +void t_phone::post_process_response(t_response *r, t_tuid tuid, t_tid tid) { + cleanup_dead_lines(); + move_releasing_lines_to_background(); + cleanup_3way(); +} + +void t_phone::recvd_invite(t_request *r, t_tid tid) { + // Check if this INVITE is a retransmission. + // Once the TU sent a 2XX repsonse on an INVITE it has to deal + // with retransmissions. + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->is_invite_retrans(r)) { + lines[i]->process_invite_retrans(); + return; + } + } + + // RFC 3261 12.2.2 + // An INVITE with a To-header without a tag is an initial + // INVITE + if (r->hdr_to.tag == "") { + recvd_initial_invite(r, tid); + } else { + recvd_re_invite(r, tid); + } +} + +void t_phone::recvd_initial_invite(t_request *r, t_tid tid) { + t_response *resp; + list <string> unsupported; + t_call_record call_record; + + // Find out for which user this INVITE is. + t_phone_user *pu = match_phone_user(r, true); + if (!pu) { + resp = r->create_response(R_404_NOT_FOUND); + send_response(resp, 0, tid); + + // Do not create a call history record as this is a misrouted + // call. + + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // Reject call if phone is not active + if (!is_active) { + resp = r->create_response(R_480_TEMP_NOT_AVAILABLE); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + t_user *user_config = pu->get_user_profile(); + + // Check if the far end requires any unsupported extensions + if (!user_config->check_required_ext(r, unsupported)) + { + // Not all required extensions are supported + resp = r->create_response(R_420_BAD_EXTENSION); + resp->hdr_unsupported.set_features(unsupported); + send_response(resp, 0, tid); + + // Do not create a call history record here. The far-end + // should retry the call without the extension, so this + // is not a missed call from the user point of view. + + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // RFC 3891 3 + // If a replaces header is present, check if it matches a dialog + int replace_line = -1; + if (r->hdr_replaces.is_populated() && user_config->get_ext_replaces()) { + bool early_matched = false; + bool no_fork_req_disposition = + r->hdr_request_disposition.is_populated() && + r->hdr_request_disposition.fork_directive == t_hdr_request_disposition::NO_FORK; + + for (size_t i = 0; i < lines.size(); i++) { + if (lines.at(i)->match_replaces(r->hdr_replaces.call_id, + r->hdr_replaces.to_tag, + r->hdr_replaces.from_tag, + no_fork_req_disposition, + early_matched)) + { + replace_line = i; + break; + } + } + + if (replace_line >= NUM_CALL_LINES) { + // Replaces header matches a releasing line. + resp = r->create_response(R_603_DECLINE); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } else if (replace_line >= 0) { + if (replace_line == active_line) { + if (r->hdr_replaces.early_only && !early_matched) { + resp = r->create_response(R_486_BUSY_HERE); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // The existing call will be torn down only after + // it has been checked that this incoming INVITE + // is not rejected by the user, e.g. DND. + } else { + // Implementation decision: + // Don't allow a held call to be replaced. + resp = r->create_response(R_486_BUSY_HERE); + send_response(resp, 0, tid); + + // Create a call history record + call_record.start_call(r, t_call_record::DIR_IN, + user_config->get_profile_name()); + call_record.fail_call(resp); + call_history->add_call_record(call_record); + + MEMMAN_DELETE(resp); + delete resp; + return; + } + } else { + // Replaces does not match any line. + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + } + + // Hunt for an idle line to handle the call. + int hunted_line = -1; + if (replace_line >= 0) { + hunted_line = replace_line; + } else { + hunted_line = hunt_line(); + } + + t_display_url display_url; + list<t_display_url> cf_dest; // call forwarding destinations + + // Call user defineable incoming call script to determine how + // to handle this call + t_script_result script_result; + + if (!user_config->get_script_incoming_call().empty()) { + // Send 100 Trying as the script might take a while + resp = r->create_response(R_100_TRYING); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + + t_call_script script(user_config, t_call_script::TRIGGER_IN_CALL, hunted_line + 1); + script.exec_action(script_result, r); + + if (!script_result.display_msgs.empty()) { + string text(join_strings(script_result.display_msgs, "\n")); + ui->cb_display_msg(text, MSG_NO_PRIO); + } + + // Override display name with caller name returned by script + if (!script_result.caller_name.empty()) { + r->hdr_from.display_override = script_result.caller_name; + log_file->write_header("t_phone::recvd_invite", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Override display name with caller name:\n"); + log_file->write_raw(script_result.caller_name); + log_file->write_endl(); + log_file->write_footer(); + } + } + + t_call_script script_in_call_failed(user_config, t_call_script::TRIGGER_IN_CALL_FAILED, 0); + + // Lookup address in address book. + if (script_result.caller_name.empty() && + sys_config->get_ab_lookup_name() && + (sys_config->get_ab_override_display() || r->hdr_from.display.empty())) + { + // Send 100 Trying as name lookup might take a while + resp = r->create_response(R_100_TRYING); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + + string name = ui->get_name_from_abook(user_config, r->hdr_from.uri); + if (!name.empty()) { + r->hdr_from.display_override = name; + log_file->write_header("t_phone::recvd_invite", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw( + "Override display name with address book name:\n"); + log_file->write_raw(name); + log_file->write_endl(); + log_file->write_footer(); + } + } + + // Perform the action in the script_result. + // NOTE: the default action is "continue" + switch (script_result.action) { + case t_script_result::ACTION_CONTINUE: + // Continue with call + break; + case t_script_result::ACTION_AUTOANSWER: + log_file->write_report("Incoming call script action: autoanswer", + "t_phone::recvd_invite"); + break; + case t_script_result::ACTION_REJECT: + log_file->write_report("Incoming call script action: reject", + "t_phone::recvd_invite"); + resp = r->create_response(R_603_DECLINE, script_result.reason); + send_response(resp, 0, tid); + + // Create a call history record + call_record.start_call(r, t_call_record::DIR_IN, + user_config->get_profile_name()); + call_record.fail_call(resp); + call_history->add_call_record(call_record); + + // Trigger call script + script_in_call_failed.exec_notify(resp); + + MEMMAN_DELETE(resp); + delete resp; + return; + break; + case t_script_result::ACTION_DND: + log_file->write_report("Incoming call script action: dnd", + "t_phone::recvd_invite"); + resp = r->create_response(R_480_TEMP_NOT_AVAILABLE, + script_result.reason); + send_response(resp, 0, tid); + + // Create a call history record + call_record.start_call(r, t_call_record::DIR_IN, + user_config->get_profile_name()); + call_record.fail_call(resp); + call_history->add_call_record(call_record); + + // Trigger call script + script_in_call_failed.exec_notify(resp); + + MEMMAN_DELETE(resp); + delete resp; + return; + break; + case t_script_result::ACTION_REDIRECT: + log_file->write_report("Incoming call script action: redirect", + "t_phone::recvd_invite"); + ui->expand_destination(user_config, + script_result.contact, display_url); + if (display_url.is_valid()) { + cf_dest.clear(); + cf_dest.push_back(display_url); + resp = r->create_response(R_302_MOVED_TEMPORARILY); + resp->hdr_contact.set_contacts(cf_dest); + } else { + log_file->write_report("Invalid redirect contact", + "t_phone::recvd_invite", + LOG_NORMAL, LOG_WARNING); + resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); + } + send_response(resp, 0, tid); + + // Create a call history record + call_record.start_call(r, t_call_record::DIR_IN, + user_config->get_profile_name()); + call_record.fail_call(resp); + call_history->add_call_record(call_record); + + // Trigger call script + script_in_call_failed.exec_notify(resp); + + MEMMAN_DELETE(resp); + delete resp; + return; + break; + default: + log_file->write_report("Error in incoming call script", + "t_phone::recvd_invite", LOG_NORMAL, LOG_WARNING); + resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); + send_response(resp, 0, tid); + + // Create a call history record + call_record.start_call(r, t_call_record::DIR_IN, + user_config->get_profile_name()); + call_record.fail_call(resp); + call_history->add_call_record(call_record); + + // Trigger call script + script_in_call_failed.exec_notify(resp); + + MEMMAN_DELETE(resp); + delete resp; + return; + break; + } + + // Call forwarding always + // NOTE: if a call script returned the autoanswer action, then + // call forwarding should be bypassed + if (pu->service->get_cf_active(CF_ALWAYS, cf_dest) && + script_result.action == t_script_result::ACTION_CONTINUE) + { + log_file->write_report("Call redirection unconditional", + "t_phone::recvd_invite"); + resp = r->create_response(R_302_MOVED_TEMPORARILY); + resp->hdr_contact.set_contacts(cf_dest); + send_response(resp, 0, tid); + + // Create a call history record + call_record.start_call(r, t_call_record::DIR_IN, + user_config->get_profile_name()); + call_record.fail_call(resp); + call_history->add_call_record(call_record); + + // Trigger call script + script_in_call_failed.exec_notify(resp); + + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // Do not disturb + // RFC 3261 21.4.18 + // NOTE: if a call script returned the autoanswer action, then + // do not disturb should be bypassed + if (pu->service->is_dnd_active() && + script_result.action == t_script_result::ACTION_CONTINUE) + { + log_file->write_report("Do not disturb", + "t_phone::recvd_invite"); + resp = r->create_response(R_480_TEMP_NOT_AVAILABLE); + send_response(resp, 0, tid); + + // Create a call history record + call_record.start_call(r, t_call_record::DIR_IN, + user_config->get_profile_name()); + call_record.fail_call(resp); + call_history->add_call_record(call_record); + + // Trigger call script + script_in_call_failed.exec_notify(resp); + + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // RFC 3891 + bool auto_answer_replace_call = false; + if (replace_line >= 0) { + // This call replaces an existing call. Tear down this existing + // call. This will clear the active line. + log_file->write_report("End call due to Replaces header.", + "t_phone::recvd_initial_invite"); + + if (lines.at(replace_line)->get_substate() == LSSUB_INCOMING_PROGRESS) { + ui->cb_stop_call_notification(replace_line); + lines.at(replace_line)->reject(); + } else { + lines.at(replace_line)->end_call(); + auto_answer_replace_call = true; + } + + move_line_to_background(replace_line); + } + + // Auto answer + if (hunted_line == active_line) { + // Auto-answer is only applicable to the active line. + + if (replace_line >= 0 && auto_answer_replace_call) { + // RFC 3891 + // This call replaces an existing established call, answer immediate. + lines.at(active_line)->set_auto_answer(true); + } else if (pu->service->is_auto_answer_active() || + script_result.action == t_script_result::ACTION_AUTOANSWER) + { + // Auto answer + log_file->write_report("Auto answer", + "t_phone::recvd_invite"); + lines.at(active_line)->set_auto_answer(true); + } + } + + // Send INVITE to hunted line + if (hunted_line >= 0) { + lines.at(hunted_line)->recvd_invite(pu, r, tid, + script_result.ringtone); + return; + } + + // The phone is busy + // Call forwarding busy + if (pu->service->get_cf_active(CF_BUSY, cf_dest)) { + log_file->write_report("Call redirection busy", + "t_phone::recvd_invite"); + resp = r->create_response(R_302_MOVED_TEMPORARILY); + resp->hdr_contact.set_contacts(cf_dest); + send_response(resp, 0, tid); + + // Create a call history record + call_record.start_call(r, t_call_record::DIR_IN, + user_config->get_profile_name()); + call_record.fail_call(resp); + call_history->add_call_record(call_record); + + // Trigger call script + script_in_call_failed.exec_notify(resp); + + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // Send busy response + resp = r->create_response(R_486_BUSY_HERE); + send_response(resp, 0, tid); + + // Create a call history record + call_record.start_call(r, t_call_record::DIR_IN, + user_config->get_profile_name()); + call_record.fail_call(resp); + call_history->add_call_record(call_record); + + // Trigger call script + script_in_call_failed.exec_notify(resp); + + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_re_invite(t_request *r, t_tid tid) { + t_response *resp; + list <string> unsupported; + + // RFC 3261 12.2.2 + // A To-header with a tag is a mid-dialog request. + // Find a line that matches the request + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r)) { + t_phone_user *pu = lines[i]->get_phone_user(); + assert(pu); + t_user *user_config = pu->get_user_profile(); + + // Check if the far end requires any unsupported extensions + if (!user_config->check_required_ext(r, unsupported)) + { + // Not all required extensions are supported + resp = r->create_response(R_420_BAD_EXTENSION); + resp->hdr_unsupported.set_features(unsupported); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + lines[i]->recvd_invite(pu, r, tid, ""); + return; + } + } + + // No dialog matches with the request. + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_ack(t_request *r, t_tid tid) { + t_response *resp; + + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r)) { + lines[i]->recvd_ack(r, tid); + return; + } + } + + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_cancel(t_request *r, t_tid cancel_tid, + t_tid target_tid) +{ + t_response *resp; + + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match_cancel(r, target_tid)) { + lines[i]->recvd_cancel(r, cancel_tid, target_tid); + return; + } + } + + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, cancel_tid); + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_bye(t_request *r, t_tid tid) { + t_response *resp; + list <string> unsupported; + + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r)) { + t_user *user_config = lines[i]->get_user(); + assert(user_config); + + if (!user_config->check_required_ext(r, unsupported)) + { + // Not all required extensions are supported + resp = r->create_response(R_420_BAD_EXTENSION); + resp->hdr_unsupported.set_features(unsupported); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + lines[i]->recvd_bye(r, tid); + return; + } + } + + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_options(t_request *r, t_tid tid) { + t_response *resp; + if (r->hdr_to.tag =="") { + // Out-of-dialog OPTIONS + t_phone_user *pu = find_phone_user_out_dialog_request(r, tid); + if (pu) { + resp = pu->create_options_response(r); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + } + } else { + // In-dialog OPTIONS + t_line *l = find_line_in_dialog_request(r, tid); + if (l) { + l->recvd_options(r, tid); + } + } +} + +t_phone_user *t_phone::find_phone_user_out_dialog_request(t_request *r, t_tid tid) { + t_response *resp; + list <string> unsupported; + + // Find out for which user this request is. + t_phone_user *pu = match_phone_user(r, true); + if (!pu) { + resp = r->create_response(R_404_NOT_FOUND); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return NULL; + } + + // Check if the far end requires any unsupported extensions + if (!pu->get_user_profile()->check_required_ext(r, unsupported)) + { + // Not all required extensions are supported + resp = r->create_response(R_420_BAD_EXTENSION); + resp->hdr_unsupported.set_features(unsupported); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return NULL; + } + + return pu; +} + +t_line *t_phone::find_line_in_dialog_request(t_request *r, t_tid tid) { + t_response *resp; + list <string> unsupported; + + // RFC 3261 12.2.2 + // A To-header with a tag is a mid-dialog request. + // No dialog matches with the request. + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r)) { + t_user *user_config = lines[i]->get_user(); + assert(user_config); + + // Check if the far end requires any unsupported extensions + if (!user_config->check_required_ext(r, unsupported)) + { + // Not all required extensions are supported + resp = r->create_response(R_420_BAD_EXTENSION); + resp->hdr_unsupported.set_features(unsupported); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return NULL; + } + + return lines[i]; + } + } + + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return NULL; +} + +void t_phone::recvd_register(t_request *r, t_tid tid) { + // The softphone is not a registrar. + t_response *resp = r->create_response(R_403_FORBIDDEN); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + + // TEST ONLY: code for testing a 423 Interval Too Brief + /* + if (r->hdr_contact.contact_list.front().get_expires() < 30) { + t_response *resp = r->create_response( + R_423_INTERVAL_TOO_BRIEF); + resp->hdr_min_expires.set_time(30); + send_response(resp, 0, tid); + delete resp; + return; + } + + // Code for testing a 200 OK response (register) + t_response *resp = r->create_response(R_200_OK); + resp->hdr_contact.set_contacts(r->hdr_contact.contact_list); + resp->hdr_contact.contact_list.front().set_expires(30); + resp->hdr_date.set_now(); + send_response(resp, 0, tid); + delete resp; + + // Code for testing 200 OK response (de-register) + t_response *resp = r->create_response(R_200_OK); + send_response(resp, 0, tid); + delete resp; + + // Code for testing 200 OK response (query) + t_response *resp = r->create_response(R_200_OK); + t_contact_param contact; + contact.uri.set_url("sip:aap@xs4all.nl"); + resp->hdr_contact.add_contact(contact); + contact.uri.set_url("sip:noot@xs4all.nl"); + resp->hdr_contact.add_contact(contact); + send_response(resp, 0, tid); + delete resp; + + // Code for testing a 401 response (register) + if (r->hdr_authorization.is_populated() && + r->hdr_authorization.credentials_list.front().digest_response. + nonce == "0123456789abcdef") + { + t_response *resp = r->create_response(R_200_OK); + resp->hdr_contact.set_contacts(r->hdr_contact.contact_list); + resp->hdr_contact.contact_list.front().set_expires(30); + resp->hdr_date.set_now(); + send_response(resp, 0, tid); + delete resp; + } else { + t_response *resp = r->create_response(R_401_UNAUTHORIZED); + t_challenge c; + c.auth_scheme = AUTH_DIGEST; + c.digest_challenge.realm = "mtel.nl"; + if (r->hdr_authorization.is_populated()) { + c.digest_challenge.nonce = "0123456789abcdef"; + c.digest_challenge.stale = true; + } else { + c.digest_challenge.nonce = "aaaaaa0123456789"; + } + 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); + } + */ +} + +void t_phone::recvd_prack(t_request *r, t_tid tid) { + t_response *resp; + + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r)) { + lines[i]->recvd_prack(r, tid); + return; + } + } + + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_subscribe(t_request *r, t_tid tid) { + t_response *resp; + + if (r->hdr_event.event_type != SIP_EVENT_REFER) { + // Non-supported event type + resp = r->create_response(R_489_BAD_EVENT); + resp->hdr_allow_events.add_event_type(SIP_EVENT_REFER); + send_response(resp, 0 ,tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r)) { + lines[i]->recvd_subscribe(r, tid); + return; + } + } + + if (r->hdr_to.tag == "") { + // A REFER outside a dialog is not allowed by Twinkle + if (r->hdr_event.event_type == SIP_EVENT_REFER) { + // RFC 3515 2.4.4 + resp = r->create_response(R_403_FORBIDDEN); + send_response(resp, 0 ,tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + } + + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_notify(t_request *r, t_tid tid) { + t_response *resp; + t_phone_user *pu; + + // Check support for the notified event + if (!SIP_EVENT_SUPPORTED(r->hdr_event.event_type)) { + // Non-supported event type + resp = r->create_response(R_489_BAD_EVENT); + ADD_SUPPORTED_SIP_EVENTS(resp->hdr_allow_events); + send_response(resp, 0 ,tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // MWI or presence notification + if (r->hdr_event.event_type == SIP_EVENT_MSG_SUMMARY || + r->hdr_event.event_type == SIP_EVENT_PRESENCE) + { + pu = match_phone_user(r, true); + if (pu) { + pu->recvd_notify(r, tid); + } else { + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0 ,tid); + MEMMAN_DELETE(resp); + delete resp; + } + + return; + } + + // REFER notification + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r)) { + lines[i]->recvd_notify(r, tid); + if (lines[i]->get_refer_state() == REFST_NULL) { + // Refer subscription has finished. + log_file->write_report("Refer subscription terminated.", + "t_phone::recvd_notify"); + + if (lines[i]->get_substate() == LSSUB_RELEASING || + lines[i]->get_substate() == LSSUB_IDLE) + { + // The line is (being) cleared already. So this + // NOTIFY signals the end of the refer subscription + // attached to this line. + cleanup_dead_lines(); + return; + } else if (lines[i]->is_refer_succeeded()) { + log_file->write_report( + "Refer succeeded. End call with referee,", + "t_phone::recvd_notify"); + lines[i]->end_call(); + } else { + log_file->write_report("Refer failed.", + "t_phone::recvd_notify"); + + t_user *user_config = lines[i]->get_user(); + assert(user_config); + if (user_config->get_referrer_hold() && + lines[i]->get_is_on_hold()) + { + // Retrieve the call if the line is active. + if (i == active_line) { + log_file->write_report( + "Retrieve call with referee.", + "t_phone::recvd_notify"); + lines[i]->retrieve(); + } + } + } + } + return; + } + } + + if (r->hdr_to.tag == "") { + // NOTIFY outside a dialog is not allowed. + resp = r->create_response(R_403_FORBIDDEN); + send_response(resp, 0 ,tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_refer(t_request *r, t_tid tid) { + t_response *resp; + + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r)) { + t_phone_user *pu = lines[i]->get_phone_user(); + assert(pu); + t_user *user_config = pu->get_user_profile(); + + list <string> unsupported; + if (!user_config->check_required_ext(r, unsupported)) + { + // Not all required extensions are supported + resp = r->create_response(R_420_BAD_EXTENSION); + resp->hdr_unsupported.set_features(unsupported); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // Reject if a 3-way call is established. + if (is_3way) { + log_file->write_report("3-way call active. Reject REFER.", + "t_phone::recvd_refer"); + resp = r->create_response(R_603_DECLINE); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // Reject if the line is on-hold. + if (is_3way || lines[i]->get_is_on_hold()) { + log_file->write_report("Line is on-hold. Reject REFER.", + "t_phone::recvd_refer"); + resp = r->create_response(R_603_DECLINE); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + // Check if a refer is already in progress + if (i == LINENO_REFERRER || + lines[LINENO_REFERRER]->get_state() != LS_IDLE || + incoming_refer_data != NULL) + { + log_file->write_report( + "A REFER is still in progress. Reject REFER.", + "t_phone::recvd_refer"); + resp = r->create_response(R_603_DECLINE); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + if (!lines[i]->recvd_refer(r, tid)) { + // Refer has been rejected. + log_file->write_report("Incoming REFER rejected.", + "t_phone::recvd_refer"); + return; + } + + // Make sure the line stays seized if the far-end ends the + // call, so a line will be available if the user gives permission + // for the call transfer. + lines[i]->set_keep_seized(true); + incoming_refer_data = new t_transfer_data(r, i, + lines[i]->get_hide_user(), pu); + MEMMAN_NEW(incoming_refer_data); + return; + } + } + + if (r->hdr_to.tag == "") { + // Twinkle does not allow a REFER outside a dialog. + resp = r->create_response(R_403_FORBIDDEN); + send_response(resp, 0 ,tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_refer_permission(bool permission) { + if (!incoming_refer_data) { + // This should not happen + log_file->write_report("Incoming REFER data is gone.", + "t_phone::recvd_refer_permission", + LOG_NORMAL, LOG_WARNING); + return; + } + + unsigned short i = incoming_refer_data->get_lineno(); + t_request *r = incoming_refer_data->get_refer_request(); + bool hide_user = incoming_refer_data->get_hide_user(); + t_phone_user *pu = incoming_refer_data->get_phone_user(); + t_user *user_config = pu->get_user_profile(); + + lines[i]->recvd_refer_permission(permission, r); + + if (!permission) { + log_file->write_report("Incoming REFER rejected.", + "t_phone::recvd_refer_permission"); + lines[i]->set_keep_seized(false); + move_releasing_lines_to_background(); + + MEMMAN_DELETE(incoming_refer_data); + delete incoming_refer_data; + incoming_refer_data = NULL; + return; + } else { + log_file->write_report("Incoming REFER allowed.", + "t_phone::recvd_refer_permission"); + } + + if (lines[i]->get_substate() == LSSUB_ESTABLISHED) { + // Put line on-hold and place it in the referrer line + log_file->write_report( + "Hold call before calling the refer-target.", + "t_phone::recvd_refer_permission"); + + if (user_config->get_referee_hold()) { + lines[i]->hold(); + } else { + // The user profile indicates that the line should + // not be put on-hold, i.e. do not send re-INVITE. + // So only stop RTP. + lines[i]->hold(true); + } + } + + // Move the original line to the REFERRER line (the line may be idle + // already). + t_line *l = lines[i]; + lines[i] = lines[LINENO_REFERRER]; + lines[i]->line_number = i; + lines[LINENO_REFERRER] = l; + lines[LINENO_REFERRER]->line_number = LINENO_REFERRER; + lines[LINENO_REFERRER]->set_keep_seized(false); + + ui->cb_line_state_changed(); + + // Setup call to the Refer-To destination + log_file->write_report("Call refer-target.", + "t_phone::recvd_refer_permission"); + + t_hdr_replaces hdr_replaces; + t_hdr_require hdr_require; + + // Analyze headers in Refer-To URI. + // For an attended call transfer the Refer-To URI + // will contain a Replaces header and possibly a Require + // header. Other headers are ignored for now. + // See draft-ietf-sipping-cc-transfer-07 7.3 + if (!r->hdr_refer_to.uri.get_headers().empty()) { + try { + list<string> parse_errors; + t_sip_message *m = t_parser::parse_headers( + r->hdr_refer_to.uri.get_headers(), + parse_errors); + hdr_replaces = m->hdr_replaces; + hdr_require = m->hdr_require; + MEMMAN_DELETE(m); + delete m; + } catch (int) { + log_file->write_header("t_phone::recvd_refer_permission", + LOG_NORMAL, LOG_WARNING); + log_file->write_raw("Cannot parse headers in Refer-To URI\n"); + log_file->write_raw(r->hdr_refer_to.uri.encode()); + log_file->write_endl(); + log_file->write_footer(); + } + } + + ui->cb_call_referred(user_config, i, r); + + lines[i]->invite(pu, + r->hdr_refer_to.uri.copy_without_headers(), + r->hdr_refer_to.display, "", r->hdr_referred_by, + hdr_replaces, hdr_require, t_hdr_request_disposition(), + hide_user); + lines[i]->open_dialog->is_referred_call = true; + + MEMMAN_DELETE(incoming_refer_data); + delete incoming_refer_data; + incoming_refer_data = NULL; + + return; +} + +void t_phone::recvd_info(t_request *r, t_tid tid) { + t_response *resp; + list <string> unsupported; + + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r)) { + t_user *user_config = lines[i]->get_user(); + assert(user_config); + + if (!user_config->check_required_ext(r, unsupported)) + { + // Not all required extensions are supported + resp = r->create_response(R_420_BAD_EXTENSION); + resp->hdr_unsupported.set_features(unsupported); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return; + } + + lines[i]->recvd_info(r, tid); + return; + } + } + + resp = r->create_response(R_481_TRANSACTION_NOT_EXIST); + send_response(resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; +} + +void t_phone::recvd_message(t_request *r, t_tid tid) { + if (r->hdr_to.tag =="") { + // Out-of-dialog MESSAGE + t_phone_user *pu = find_phone_user_out_dialog_request(r, tid); + if (pu) { + pu->recvd_message(r, tid); + } + } else { + // In-dialog MESSAGE + t_line *l = find_line_in_dialog_request(r, tid); + if (l) { + l->recvd_message(r, tid); + } + } +} + +void t_phone::post_process_request(t_request *r, t_tid cancel_tid, t_tid target_tid) { + cleanup_dead_lines(); + move_releasing_lines_to_background(); + cleanup_3way(); +} + + +void t_phone::failure(t_failure failure, t_tid tid) { + // TODO +} + +void t_phone::recvd_stun_resp(StunMessage *r, t_tuid tuid, t_tid tid) { + for (unsigned short i = 0; i < lines.size(); i++) { + if (lines[i]->match(r, tuid)) { + lines[i]->recvd_stun_resp(r, tuid, tid); + return; + } + } + + // out-of-dialog STUN responses + handle_response_out_of_dialog(r, tuid); +} + +void t_phone::handle_event_timeout(t_event_timeout *e) { + t_timer *t = e->get_timer(); + t_tmr_phone *tmr_phone; + t_tmr_line *tmr_line; + t_tmr_subscribe *tmr_subscribe; + t_tmr_publish *tmr_publish; + t_object_id line_id; + + lock(); + + switch (t->get_type()) { + case TMR_PHONE: + tmr_phone = dynamic_cast<t_tmr_phone *>(t); + timeout(tmr_phone->get_phone_timer(), tmr_phone->get_object_id()); + break; + case TMR_LINE: + tmr_line = dynamic_cast<t_tmr_line *>(t); + line_timeout(tmr_line->get_line_id(), tmr_line->get_line_timer(), + tmr_line->get_dialog_id()); + break; + case TMR_SUBSCRIBE: + tmr_subscribe = dynamic_cast<t_tmr_subscribe *>(t); + line_id = tmr_subscribe->get_line_id(); + if (line_id == 0) { + subscription_timeout(tmr_subscribe->get_subscribe_timer(), + tmr_subscribe->get_object_id()); + } else { + line_timeout_sub(line_id, tmr_subscribe->get_subscribe_timer(), + tmr_subscribe->get_dialog_id(), + tmr_subscribe->get_sub_event_type(), + tmr_subscribe->get_sub_event_id()); + } + break; + case TMR_PUBLISH: + tmr_publish = dynamic_cast<t_tmr_publish *>(t); + publication_timeout(tmr_publish->get_publish_timer(), + tmr_publish->get_object_id()); + break; + default: + assert(false); + break; + } + + unlock(); +} + +void t_phone::line_timeout(t_object_id id, t_line_timer timer, t_object_id did) { + // If there is no line with id anymore, then the timer expires + // silently. + t_line *line = get_line_by_id(id); + if (line) { + line->timeout(timer, did); + } +} + +void t_phone::line_timeout_sub(t_object_id id, t_subscribe_timer timer, t_object_id did, + const string &event_type, const string &event_id) +{ + // If there is no line with id anymore, then the timer expires + // silently. + t_line *line = get_line_by_id(id); + if (line) { + line->timeout_sub(timer, did, event_type, event_id); + } +} + +void t_phone::subscription_timeout(t_subscribe_timer timer, t_object_id id_timer) +{ + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); i++) + { + if ((*i)->match_subscribe_timer(timer, id_timer)) { + (*i)->timeout_sub(timer, id_timer); + } + } +} + +void t_phone::publication_timeout(t_publish_timer timer, t_object_id id_timer) { + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); i++) + { + if ((*i)->match_publish_timer(timer, id_timer)) { + (*i)->timeout_publish(timer, id_timer); + } + } +} + +void t_phone::timeout(t_phone_timer timer, unsigned short id_timer) { + lock(); + + switch (timer) { + case PTMR_REGISTRATION: + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); ++i) + { + if ((*i)->id_registration == id_timer) { + (*i)->timeout(timer); + } + } + break; + case PTMR_NAT_KEEPALIVE: + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); ++i) + { + if ((*i)->id_nat_keepalive == id_timer) { + (*i)->timeout(timer); + } + } + break; + case PTMR_TCP_PING: + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); ++i) + { + if ((*i)->id_tcp_ping == id_timer) { + (*i)->timeout(timer); + } + } + break; + default: + assert(false); + } + + unlock(); +} + +void t_phone::handle_broken_connection(t_event_broken_connection *e) { + // Find the phone user that was associated with the connection. + // This phone user has to handle the event. + t_phone_user *pu = find_phone_user(e->get_user_uri()); + if (pu) { + pu->handle_broken_connection(); + } else { + log_file->write_header("t_phone::handle_broken_connection", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("Cannot find active phone user "); + log_file->write_raw(e->get_user_uri().encode()); + log_file->write_endl(); + log_file->write_footer(); + } +} + + +/////////// +// Public +/////////// + +t_phone::t_phone() : t_transaction_layer(), lines(NUM_CALL_LINES) { + is_active = true; + active_line = 0; + + // Create phone lines + for (unsigned short i = 0; i < NUM_CALL_LINES; i++) { + lines[i] = new t_line(this, i); + MEMMAN_NEW(lines[i]); + } + + // Initialize 3-way conference data + is_3way = false; + line1_3way = NULL; + line2_3way = NULL; + + incoming_refer_data = NULL; + + struct timeval t; + gettimeofday(&t, NULL); + startup_time = t.tv_sec; + + // NOTE: The RTP ports for the lines are initialized after the + // system settings have been read. +} + +t_phone::~t_phone() { + // Delete phone lines + log_file->write_header("t_phone::~t_phone"); + log_file->write_raw("Number of lines to cleanup: "); + log_file->write_raw(lines.size()); + log_file->write_endl(); + log_file->write_footer(); + + if (incoming_refer_data) { + MEMMAN_DELETE(incoming_refer_data); + delete incoming_refer_data; + } + + for (unsigned short i = 0; i < lines.size(); i++) { + MEMMAN_DELETE(lines[i]); + delete lines[i]; + } + + // Delete all phone users + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); i++) + { + MEMMAN_DELETE(*i); + delete *i; + } +} + +void t_phone::pub_invite(t_user *user, + const t_url &to_uri, const string &to_display, + const string &subject, bool anonymous) +{ + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + invite(pu, to_uri, to_display, subject, false, anonymous); + } else { + log_file->write_header("t_phone::pub_invite", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_footer(); + } + + unlock(); +} + +void t_phone::pub_answer(void) { + lock(); + answer(); + unlock(); +} + +void t_phone::pub_reject(void) { + lock(); + reject(); + unlock(); +} + +void t_phone::pub_reject(unsigned short line) { + lock(); + reject(line); + unlock(); +} + +void t_phone::pub_redirect(const list<t_display_url> &destinations, int code, string reason) +{ + lock(); + redirect(destinations, code, reason); + unlock(); +} + +void t_phone::pub_end_call(void) { + lock(); + end_call(); + unlock(); +} + +void t_phone::pub_registration(t_user *user, + t_register_type register_type, + unsigned long expires) +{ + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + registration(pu, register_type, expires); + } else { + log_file->write_header("t_phone::pub_registration", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_footer(); + } + + unlock(); +} + +void t_phone::pub_options(t_user *user, + const t_url &to_uri, const string &to_display) +{ + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + options(pu, to_uri, to_display); + } else { + log_file->write_header("t_phone::pub_options", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_footer(); + } + + unlock(); +} + +void t_phone::pub_options(void) { + lock(); + options(); + unlock(); +} + +bool t_phone::pub_hold(void) { + lock(); + bool retval = hold(); + unlock(); + return retval; +} + +void t_phone::pub_retrieve(void) { + lock(); + retrieve(); + unlock(); +} + +void t_phone::pub_refer(const t_url &uri, const string &display) { + lock(); + refer(uri, display); + unlock(); +} + +void t_phone::pub_setup_consultation_call(const t_url &uri, const string &display) { + lock(); + setup_consultation_call(uri, display); + unlock(); +} + +void t_phone::pub_refer(unsigned short lineno_from, unsigned short lineno_to) { + lock(); + refer(lineno_from, lineno_to); + unlock(); +} + +void t_phone::mute(bool enable) { + lock(); + + // In a 3-way call, both lines must be muted + if (is_3way && ( + active_line == line1_3way->get_line_number() || + active_line == line2_3way->get_line_number())) + { + line1_3way->mute(enable); + line2_3way->mute(enable); + } + else + { + lines[active_line]->mute(enable); + } + + unlock(); +} + +void t_phone::pub_activate_line(unsigned short l) { + lock(); + activate_line(l); + unlock(); +} + +void t_phone::pub_send_dtmf(char digit, bool inband, bool info) { + lock(); + send_dtmf(digit, inband, info); + unlock(); +} + +bool t_phone::pub_seize(void) { + bool retval; + + lock(); + retval = lines[active_line]->seize(); + unlock(); + + return retval; +} + +bool t_phone::pub_seize(unsigned short line) { + assert(line < NUM_USER_LINES); + bool retval; + + lock(); + retval = lines[line]->seize(); + unlock(); + + return retval; +} + +void t_phone::pub_unseize(void) { + lock(); + lines[active_line]->unseize(); + unlock(); +} + +void t_phone::pub_unseize(unsigned short line) { + assert(line < NUM_USER_LINES); + + lock(); + lines[line]->unseize(); + unlock(); +} + +void t_phone::pub_confirm_zrtp_sas(unsigned short line) { + assert(line < NUM_USER_LINES); + lock(); + lines[line]->confirm_zrtp_sas(); + unlock(); +} + +void t_phone::pub_confirm_zrtp_sas(void) { + lock(); + lines[active_line]->confirm_zrtp_sas(); + unlock(); +} + +void t_phone::pub_reset_zrtp_sas_confirmation(unsigned short line) { + assert(line < NUM_USER_LINES); + lock(); + lines[line]->reset_zrtp_sas_confirmation(); + unlock(); +} + +void t_phone::pub_reset_zrtp_sas_confirmation(void) { + lock(); + lines[active_line]->reset_zrtp_sas_confirmation(); + unlock(); +} + +void t_phone::pub_enable_zrtp(void) { + lock(); + lines[active_line]->enable_zrtp(); + unlock(); +} + +void t_phone::pub_zrtp_request_go_clear(void) { + lock(); + lines[active_line]->zrtp_request_go_clear(); + unlock(); +} + +void t_phone::pub_zrtp_go_clear_ok(unsigned short line) { + assert(line < NUM_USER_LINES); + lock(); + lines[line]->zrtp_go_clear_ok(); + unlock(); +} + +void t_phone::pub_subscribe_mwi(t_user *user) { + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + pu->subscribe_mwi(); + } else { + log_file->write_header("t_phone::pub_subscribe_mwi", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_footer(); + } + + unlock(); +} + +void t_phone::pub_unsubscribe_mwi(t_user *user) { + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + pu->unsubscribe_mwi(); + } else { + log_file->write_header("t_phone::pub_unsubscribe_mwi", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_footer(); + } + + unlock(); +} + +void t_phone::pub_subscribe_presence(t_user *user) { + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + pu->subscribe_presence(); + } else { + log_file->write_header("t_phone::pub_subscribe_presence", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_footer(); + } + + unlock(); +} + +void t_phone::pub_unsubscribe_presence(t_user *user) { + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + pu->unsubscribe_presence(); + } else { + log_file->write_header("t_phone::pub_unsubscribe_presence", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_footer(); + } + + unlock(); +} + +void t_phone::pub_publish_presence(t_user *user, t_presence_state::t_basic_state basic_state) { + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + pu->publish_presence(basic_state); + } else { + log_file->write_header("t_phone::pub_publish_presence", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_footer(); + } + + unlock(); +} + +void t_phone::pub_unpublish_presence(t_user *user) { + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + pu->unpublish_presence(); + } else { + log_file->write_header("t_phone::pub_publish_presence", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_footer(); + } + + unlock(); +} + +bool t_phone::pub_send_message(t_user *user, const t_url &to_uri, const string &to_display, + const t_msg &msg) +{ + bool retval = true; + + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + retval = pu->send_message(to_uri, to_display, msg); + } else { + log_file->write_header("t_phone::pub_send_message", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_endl(); + log_file->write_footer(); + + retval = false; + } + + unlock(); + + return retval; +} + +bool t_phone::pub_send_im_iscomposing(t_user *user, const t_url &to_uri, const string &to_display, + const string &state, time_t refresh) +{ + bool retval = true; + + lock(); + + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + retval = pu->send_im_iscomposing(to_uri, to_display, state, refresh); + } else { + log_file->write_header("t_phone::pub_send_im_iscomposing", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("User profile not active: "); + log_file->write_raw(user->get_profile_name()); + log_file->write_endl(); + log_file->write_footer(); + + retval = false; + } + + unlock(); + + return retval; +} + +t_phone_state t_phone::get_state(void) const { + lock(); + for (unsigned short i = 0; i < NUM_USER_LINES; i++) { + if (lines[i]->get_state() == LS_IDLE) { + unlock(); + return PS_IDLE; + } + } + + // All lines are busy, so the phone is busy. + unlock(); + return PS_BUSY; +} + +bool t_phone::all_lines_idle(void) const { + lock(); + for (unsigned short i = 0; i < NUM_USER_LINES; i++) { + if (lines[i]->get_substate() != LSSUB_IDLE) { + unlock(); + return false; + } + } + + // All lines are idle + unlock(); + return true; +} + +bool t_phone::get_idle_line(unsigned short &lineno) const { + lock(); + + bool found_idle_line = false; + for (unsigned short i = 0; i < NUM_USER_LINES; i++) { + if (lines[i]->get_substate() == LSSUB_IDLE) { + lineno = i; + found_idle_line = true; + break; + } + } + + unlock(); + return found_idle_line; +} + +void t_phone::set_active_line(unsigned short l) { + lock(); + assert (l < NUM_USER_LINES); + active_line = l; + unlock(); +} + +unsigned short t_phone::get_active_line(void) const { + return active_line; +} + +t_line *t_phone::get_line_by_id(t_object_id id) const { + for (size_t i = 0; i < lines.size(); i++) { + if (lines[i]->get_object_id() == id) { + return lines[i]; + } + } + + return NULL; +} + +t_line *t_phone::get_line(unsigned short lineno) const { + assert(lineno < lines.size()); + return lines[lineno]; +} + +bool t_phone::authorize(t_user *user, t_request *r, t_response *resp) +{ + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->authorize(r, resp); + unlock(); + + return result; +} + +void t_phone::remove_cached_credentials(t_user *user, const string &realm) { + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) pu->remove_cached_credentials(realm); + unlock(); +} + +bool t_phone::get_is_registered(t_user *user) { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->get_is_registered(); + unlock(); + + return result; +} + +bool t_phone::get_last_reg_failed(t_user *user) { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->get_last_reg_failed(); + unlock(); + + return result; +} + +t_line_state t_phone::get_line_state(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_line_state s = get_line(lineno)->get_state(); + unlock(); + return s; +} + +t_line_substate t_phone::get_line_substate(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_line_substate s = get_line(lineno)->get_substate(); + unlock(); + return s; +} + +bool t_phone::is_line_on_hold(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_is_on_hold(); + unlock(); + return b; +} + +bool t_phone::is_line_muted(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_is_muted(); + unlock(); + return b; +} + +bool t_phone::is_line_transfer_consult(unsigned short lineno, + unsigned short &transfer_from_line) const +{ + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_is_transfer_consult(transfer_from_line); + unlock(); + return b; +} + +bool t_phone::line_to_be_transferred(unsigned short lineno, + unsigned short &transfer_to_line) const +{ + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_to_be_transferred(transfer_to_line); + unlock(); + return b; +} + +bool t_phone::is_line_encrypted(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_is_encrypted(); + unlock(); + return b; +} + +bool t_phone::is_line_auto_answered(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->get_auto_answer(); + unlock(); + return b; +} + +t_refer_state t_phone::get_line_refer_state(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_refer_state s = get_line(lineno)->get_refer_state(); + unlock(); + return s; +} + +t_user *t_phone::get_line_user(unsigned short lineno) { + assert(lineno < lines.size()); + lock(); + t_user *user = get_line(lineno)->get_user(); + unlock(); + return user; +} + +bool t_phone::has_line_media(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + bool b = get_line(lineno)->has_media(); + unlock(); + return b; +} + +bool t_phone::is_mwi_subscribed(t_user *user) const { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->is_mwi_subscribed(); + unlock(); + + return result; +} + +bool t_phone::is_mwi_terminated(t_user *user) const { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->is_mwi_terminated(); + unlock(); + + return result; +} + +t_mwi t_phone::get_mwi(t_user *user) const { + t_mwi result; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->mwi; + unlock(); + + return result; +} + +bool t_phone::is_presence_terminated(t_user *user) const { + bool result = false; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) result = pu->is_presence_terminated(); + unlock(); + + return result; +} + +t_url t_phone::get_remote_uri(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_url uri = get_line(lineno)->get_remote_uri(); + unlock(); + return uri; +} + +string t_phone::get_remote_display(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + string display = get_line(lineno)->get_remote_display(); + unlock(); + return display; +} + +bool t_phone::part_of_3way(unsigned short lineno) { + lock(); + + if (!is_3way) { + unlock(); + return false; + } + + if (line1_3way->get_line_number() == lineno) { + unlock(); + return true; + } + + if (line2_3way->get_line_number() == lineno) { + unlock(); + return true; + } + + unlock(); + return false; +} + +t_line *t_phone::get_3way_peer_line(unsigned short lineno) { + lock(); + + if (!is_3way) { + unlock(); + return NULL; + } + + if (line1_3way->get_line_number() == lineno) { + unlock(); + return line2_3way; + } + + unlock(); + return line1_3way; +} + +bool t_phone::join_3way(unsigned short lineno1, unsigned short lineno2) { + assert(lineno1 < NUM_USER_LINES); + assert(lineno2 < NUM_USER_LINES); + + lock(); + + // Check if there isn't a 3-way already + if (is_3way) { + unlock(); + return false; + } + + // Both lines must have a call. + if (lines[lineno1]->get_substate() != LSSUB_ESTABLISHED || + lines[lineno2]->get_substate() != LSSUB_ESTABLISHED) + { + unlock(); + return false; + } + + // One of the lines must be on-hold + t_line *held_line, *talking_line; + if (lines[lineno1]->get_is_on_hold()) { + held_line = lines[lineno1]; + talking_line = lines[lineno2]; + } else if (lines[lineno2]->get_is_on_hold()) { + held_line = lines[lineno2]; + talking_line = lines[lineno1]; + } else { + unlock(); + return false; + } + + // Set 3-way data + is_3way = true; + line1_3way = talking_line; + line2_3way = held_line; + + // The user may have put both lines on-hold. In this case the + // talking line is on-hold too! + if (talking_line->get_is_on_hold()) { + // Retrieve the held call + // As the 3-way indication (is_3way) is set, the audio sessions + // will automatically connect to each other. + talking_line->retrieve(); + } else { + // Start the 3-way on the talking line + t_audio_session *as_talking = talking_line->get_audio_session(); + if (as_talking) as_talking->start_3way(); + } + + // Retrieve the held call + held_line->retrieve(); + + unlock(); + return true; +} + +void t_phone::notify_refer_progress(t_response *r, unsigned short referee_lineno) { + if (lines[LINENO_REFERRER]->get_state() != LS_IDLE) { + lines[LINENO_REFERRER]->notify_refer_progress(r); + + if (!lines[LINENO_REFERRER]->active_dialog || + lines[LINENO_REFERRER]->active_dialog->get_state() != DS_CONFIRMED) + { + // The call to the referrer has already been + // terminated. + return; + } + + if (r->is_final()) { + if (r->is_success()) { + // Reference was successful, end the call with + // with the referrer. + log_file->write_header( + "t_phone::notify_refer_progress"); + log_file->write_raw( + "Call to refer-target succeeded.\n"); + log_file->write_raw( + "End call with referrer.\n"); + log_file->write_footer(); + + lines[LINENO_REFERRER]->end_call(); + } else { + // Reference failed, retrieve the call with the + // referrer. + log_file->write_header( + "t_phone::notify_refer_progress"); + log_file->write_raw( + "Call to refer-target failed.\n"); + log_file->write_raw( + "Restore call with referrer.\n"); + log_file->write_footer(); + + // Retrieve the parked line + t_line *l = lines[referee_lineno]; + lines[referee_lineno] = lines[LINENO_REFERRER]; + lines[referee_lineno]->line_number = referee_lineno; + lines[LINENO_REFERRER] = l; + lines[LINENO_REFERRER]->line_number = LINENO_REFERRER; + + // Retrieve the call if the line is active + if (referee_lineno == active_line) { + log_file->write_report( + "Retrieve call with referrer.", + "t_phone::notify_refer_progress"); + lines[referee_lineno]->retrieve(); + } + + t_user *user_config = lines[referee_lineno]->get_user(); + assert(user_config); + + ui->cb_retrieve_referrer(user_config, referee_lineno); + } + } + } +} + +t_call_info t_phone::get_call_info(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_call_info call_info = get_line(lineno)->get_call_info(); + unlock(); + return call_info; +} + +t_call_record t_phone::get_call_hist(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + t_call_record call_hist = get_line(lineno)->call_hist_record; + unlock(); + return call_hist; +} + +string t_phone::get_ringtone(unsigned short lineno) const { + assert(lineno < lines.size()); + + lock(); + string ringtone = get_line(lineno)->get_ringtone(); + unlock(); + return ringtone; +} + +time_t t_phone::get_startup_time(void) const { + return startup_time; +} + +void t_phone::init_rtp_ports(void) { + for (size_t i = 0; i < lines.size(); i++) { + lines[i]->init_rtp_port(); + } +} + +bool t_phone::add_phone_user(const t_user &user_config, t_user **dup_user) { + lock(); + + t_phone_user *existing_phone_user = NULL; + + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); i++) + { + t_user *user = (*i)->get_user_profile(); + + // If the profile is already added, then just activate it. + if (user->get_profile_name() == user_config.get_profile_name()) + { + existing_phone_user = (*i); + // Continue checking to see if activating this user + // does not conflict with another already active user. + continue; + } + + // Check if there is already another profile for the same + // user. + if (user->get_name() == user_config.get_name() && + user->get_domain() == user_config.get_domain() && + (*i)->is_active()) + { + *dup_user = user; + unlock(); + return false; + } + + // Check if there is already another profile having + // the same contact name. + if (user->get_contact_name() == user_config.get_contact_name() && + USER_HOST(user, AUTO_IP4_ADDRESS) == USER_HOST(&user_config, AUTO_IP4_ADDRESS) && + (*i)->is_active()) + { + *dup_user = user; + unlock(); + return false; + } + } + + // Activate existing profile + if (existing_phone_user) { + if (!existing_phone_user->is_active()) { + existing_phone_user->activate(user_config); + } + unlock(); + return true; + } + + // Add the user + t_phone_user *pu = new t_phone_user(user_config); + MEMMAN_NEW(pu); + phone_users.push_back(pu); + unlock(); + + return true; +} + +void t_phone::remove_phone_user(const t_user &user_config) { + lock(); + t_phone_user *pu = find_phone_user(user_config.get_profile_name()); + if (pu) pu->deactivate(); + unlock(); +} + +list<t_user *> t_phone::ref_users(void) { + list<t_user *> l; + + lock(); + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); i++) + { + if (!(*i)->is_active()) continue; + l.push_back((*i)->get_user_profile()); + } + unlock(); + + return l; +} + +t_user *t_phone::ref_user_display_uri(const string &display_uri) { + t_user *u = NULL; + + lock(); + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); i++) + { + if (!(*i)->is_active()) continue; + if ((*i)->get_user_profile()->get_display_uri() == display_uri) { + u = (*i)->get_user_profile(); + break; + } + } + unlock(); + + return u; +} + +t_user *t_phone::ref_user_profile(const string &profile_name) { + t_user *u = NULL; + + lock(); + t_phone_user *pu = find_phone_user(profile_name); + if (pu) u = pu->get_user_profile(); + unlock(); + + return u; +} + +t_service *t_phone::ref_service(t_user *user) { + assert(user); + t_service *srv = NULL; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) srv = pu->service; + unlock(); + + return srv; +} + +t_buddy_list *t_phone::ref_buddy_list(t_user *user) { + assert(user); + t_buddy_list *l = NULL; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) l = pu->get_buddy_list(); + unlock(); + + return l; +} + +t_presence_epa *t_phone::ref_presence_epa(t_user *user) { + assert(user); + t_presence_epa *epa = NULL; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) epa = pu->get_presence_epa(); + unlock(); + + return epa; +} + +string t_phone::get_ip_sip(const t_user *user, const string &auto_ip) const { + string result; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + result = pu->get_ip_sip(auto_ip); + } else { + result = LOCAL_IP; + } + unlock(); + + if (result == AUTO_IP4_ADDRESS) result = auto_ip; + + return result; +} + +unsigned short t_phone::get_public_port_sip(const t_user *user) const { + unsigned short result; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + result = pu->get_public_port_sip(); + } else { + result = sys_config->get_sip_port(); + } + unlock(); + + return result; +} + +bool t_phone::use_stun(t_user *user) { + bool result; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + result = pu->use_stun; + } else { + result = false; + } + unlock(); + + return result; +} + +bool t_phone::use_nat_keepalive(t_user *user) { + bool result; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + result = pu->use_nat_keepalive; + } else { + result = false; + } + unlock(); + + return result; +} + +void t_phone::disable_stun(t_user *user) { + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) pu->use_stun = false; + unlock(); +} + +void t_phone::sync_nat_keepalive(t_user *user) { + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) pu->sync_nat_keepalive(); + unlock(); +} + +bool t_phone::stun_discover_nat(list<string> &msg_list) { + bool retval = true; + + lock(); + for (list<t_phone_user *>::iterator i = phone_users.begin(); + i != phone_users.end(); ++i) + { + if (!(*i)->is_active()) continue; + t_user *user_config = (*i)->get_user_profile(); + + if (user_config->get_sip_transport() == SIP_TRANS_UDP || + user_config->get_sip_transport() == SIP_TRANS_AUTO) + { + if (user_config->get_use_stun()) + { + string msg; + if (!::stun_discover_nat(*i, msg)) { + string s("User profile: "); + s + user_config->get_profile_name(); + s += "\n\n"; + s += msg; + msg_list.push_back(s); + retval = false; + } + } + else + { + (*i)->use_nat_keepalive = user_config->get_enable_nat_keepalive(); + } + } + } + unlock(); + + return retval; +} + +bool t_phone::stun_discover_nat(t_user *user, string &msg) { + bool retval = true; + + lock(); + if (user->get_sip_transport() == SIP_TRANS_UDP || + user->get_sip_transport() == SIP_TRANS_AUTO) + { + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (user->get_use_stun()) { + if (pu) retval = ::stun_discover_nat(pu, msg); + } + else + { + if (pu) pu->use_nat_keepalive = user->get_enable_nat_keepalive(); + } + } + unlock(); + + return retval; +} + +t_response *t_phone::create_options_response(t_user *user, t_request *r, + bool in_dialog) +{ + t_response *resp; + + lock(); + t_phone_user *pu = find_phone_user(user->get_profile_name()); + if (pu) { + resp = pu->create_options_response(r, in_dialog); + } else { + resp = r->create_response(R_500_INTERNAL_SERVER_ERROR); + } + unlock(); + + return resp; +} + +void t_phone::init(void) { + lock(); + + list<t_user *> user_list = ref_users(); + + for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) + { + // Automatic registration at startup if requested + if ((*i)->get_register_at_startup()) { + pub_registration(*i, REG_REGISTER, DUR_REGISTRATION(*i)); + } else { + // No registration will be done, so initialize extensions now. + init_extensions(*i); + } + + // NOTE: Extension initialization is done after registration. + // This way STUN will have set the correct + // IP adres (STUN is done as part of registration.) + } + + unlock(); +} + +void t_phone::init_extensions(t_user *user_config) { + // Subscribe to MWI + if (user_config->get_mwi_sollicited()) { + pub_subscribe_mwi(user_config); + } + + // Publish presence + if (user_config->get_pres_publish_startup()) { + pub_publish_presence(user_config, t_presence_state::ST_BASIC_OPEN); + } + + // Subscribe to presence + pub_subscribe_presence(user_config); +} + +bool t_phone::set_sighandler(void) const { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + + sa.sa_handler = phone_sighandler; + sa.sa_flags = SA_RESTART; + if (sigaction (SIGCHLD, &sa, NULL) < 0) return false; + if (sigaction (SIGTERM, &sa, NULL) < 0) return false; + if (sigaction (SIGINT, &sa, NULL) < 0) return false; + + return true; +} + + +void t_phone::terminate(void) { + string msg; + lock(); + + // Clear all lines + log_file->write_report("Clear all lines.", + "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); + for (size_t i = 0; i < NUM_CALL_LINES; i++) { + switch (lines[i]->get_substate()) { + case LSSUB_IDLE: + case LSSUB_RELEASING: + break; + case LSSUB_SEIZED: + lines[i]->unseize(); + break; + case LSSUB_INCOMING_PROGRESS: + ui->cb_stop_call_notification(i); + lines[i]->reject(); + break; + case LSSUB_OUTGOING_PROGRESS: + ui->cb_stop_call_notification(i); + // Fall thru + case LSSUB_ANSWERING: + case LSSUB_ESTABLISHED: + lines[i]->end_call(); + break; + } + } + + // Deactivate phone + is_active = false; + + // De-register all registered users. + list<t_user *> user_list = ref_users(); + ui->cb_display_msg("Deregistering phone..."); + for (list<t_user *>::iterator i = user_list.begin(); + i != user_list.end(); i++) + { + // Unsubscribe MWI + if (is_mwi_subscribed(*i)) { + msg = (*i)->get_profile_name(); + msg += ": Unsubscribe MWI."; + log_file->write_report(msg, + "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); + pub_unsubscribe_mwi(*i); + } + + // Unpublish presence + pub_unpublish_presence(*i); + + // Unsubscribe presence + pub_unsubscribe_presence(*i); + + // De-register + if (get_is_registered(*i)) { + msg = (*i)->get_profile_name(); + msg += ": Deregister."; + log_file->write_report(msg, + "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); + pub_registration(*i, REG_DEREGISTER); + } + } + + unlock(); + + // Wait till phone is deregistered. + for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) + { + while (get_is_registered(*i)) { + sleep(1); + } + msg = (*i)->get_profile_name(); + msg += ": Registration terminated."; + log_file->write_report(msg, "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); + } + + // Wait for MWI unsubscription + int mwi_wait = 0; + for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) + { + while (!is_mwi_terminated(*i) && mwi_wait <= DUR_UNSUBSCRIBE_GUARD/1000) { + sleep(1); + mwi_wait++; + } + msg = (*i)->get_profile_name(); + msg += ": MWI subscription terminated."; + log_file->write_report(msg, "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); + } + + // Wait for presence unsubscription + int presence_wait = 0; + for (list<t_user *>::iterator i = user_list.begin(); i != user_list.end(); i++) + { + while (!is_presence_terminated(*i) && presence_wait <= DUR_UNSUBSCRIBE_GUARD/1000) { + sleep(1); + presence_wait++; + } + msg = (*i)->get_profile_name(); + msg += ": presence subscriptions terminated."; + log_file->write_report(msg, "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); + } + + // Wait till all lines are idle + log_file->write_report("Waiting for all lines to become idle.", + "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); + int dur = 0; + while (dur < QUIT_IDLE_WAIT) { + if (all_lines_idle()) break; + sleep(1); + dur++; + } + + // Force lines to idle state if they could not be cleared + // gracefully + lock(); + for (size_t i = 0; i < lines.size(); i++) { + if (lines[i]->get_substate() != LSSUB_IDLE) { + msg = "Force line %1 to idle state."; + msg = replace_first(msg, "%1", int2str(i)); + log_file->write_report(msg, "t_phone::terminate", + LOG_NORMAL, LOG_DEBUG); + lines[i]->force_idle(); + } + } + + log_file->write_report("Finished phone termination.", + "t_phone::terminate", LOG_NORMAL, LOG_DEBUG); + unlock(); +} + +void *phone_uas_main(void *arg) { + phone->run(); + return NULL; +} + +void *phone_sigwait(void *arg) { + sigset_t sigset; + int sig; + int child_status; + pid_t pid; + + sigemptyset(&sigset); + sigaddset(&sigset, SIGINT); + sigaddset(&sigset, SIGTERM); + sigaddset(&sigset, SIGCHLD); + + while (true) { + // When SIGCONT is received after SIGSTOP, sigwait returns + // with EINTR ?? + if (sigwait(&sigset, &sig) == EINTR) continue; + + switch (sig) { + case SIGINT: + log_file->write_report("SIGINT received.", "::phone_sigwait"); + ui->cmd_quit(); + return NULL; + case SIGTERM: + log_file->write_report("SIGTERM received.", "::phone_sigwait"); + ui->cmd_quit(); + return NULL; + case SIGCHLD: + // Cleanup terminated child process + pid = wait(&child_status); + log_file->write_header("::phone_sigwait"); + log_file->write_raw("SIGCHLD received.\n"); + log_file->write_raw("Pid "); + log_file->write_raw((int)pid); + log_file->write_raw(" terminated.\n"); + log_file->write_footer(); + break; + default: + log_file->write_header("::phone_sigwait", LOG_NORMAL, LOG_WARNING); + log_file->write_raw("Unexpected signal ("); + log_file->write_raw(sig); + log_file->write_raw(") received.\n"); + log_file->write_footer(); + } + } +} + +void phone_sighandler(int sig) { + int child_status; + pid_t pid; + + // Minimal processing should be done in a signal handler. + // No I/O should be performed. + switch (sig) { + case SIGINT: + // Post a quit command instead of executing it. As executing + // involves a lock that may lead to a deadlock. + ui->cmd_quit_async(); + break; + case SIGTERM: + ui->cmd_quit_async(); + break; + case SIGCHLD: + // Cleanup terminated child process + pid = wait(&child_status); + break; + } +} |