summaryrefslogtreecommitdiffstats
path: root/src/transaction.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/transaction.cpp')
-rw-r--r--src/transaction.cpp1307
1 files changed, 1307 insertions, 0 deletions
diff --git a/src/transaction.cpp b/src/transaction.cpp
new file mode 100644
index 0000000..30ea6dd
--- /dev/null
+++ b/src/transaction.cpp
@@ -0,0 +1,1307 @@
+/*
+ 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 "log.h"
+#include "events.h"
+#include "timekeeper.h"
+#include "transaction.h"
+#include "transaction_mgr.h"
+#include "user.h"
+#include "util.h"
+#include "audits/memman.h"
+
+extern t_event_queue *evq_sender;
+extern t_event_queue *evq_trans_layer;
+extern t_transaction_mgr *transaction_mgr;
+
+string trans_state2str(t_trans_state s) {
+ switch(s) {
+ case TS_NULL: return "TS_NULL";
+ case TS_CALLING: return "TS_CALLING";
+ case TS_TRYING: return "TS_TRYING";
+ case TS_PROCEEDING: return "TS_PROCEEDING";
+ case TS_COMPLETED: return "TS_COMPLETED";
+ case TS_CONFIRMED: return "TS_CONFIRMED";
+ case TS_TERMINATED: return "TS_TERMINATED";
+ }
+
+ return "UNKNOWN";
+}
+
+///////////////////////////////////////////////////////////
+// RFC 3261 17
+// General transaction
+///////////////////////////////////////////////////////////
+
+t_mutex t_transaction::mtx_class;
+t_tid t_transaction::next_id = 1;
+
+t_transaction::t_transaction(t_request *r, unsigned short _tuid) {
+ mtx_class.lock();
+ id = next_id++;
+ if (next_id == 65535) next_id = 1;
+ mtx_class.unlock();
+
+ state = TS_NULL;
+ request = (t_request *)r->copy();
+ final = NULL;
+ tuid = _tuid;
+}
+
+t_transaction::~t_transaction() {
+ MEMMAN_DELETE(request);
+ delete request;
+ if (final != NULL) {
+ MEMMAN_DELETE(final);
+ delete final;
+ }
+
+ for (list<t_response *>::iterator i = provisional.begin();
+ i != provisional.end(); i++)
+ {
+ MEMMAN_DELETE(*i);
+ delete *i;
+ }
+}
+
+t_tid t_transaction::get_id(void) const {
+ return id;
+}
+
+void t_transaction::process_provisional(t_response *r) {
+ provisional.push_back((t_response *)r->copy());
+}
+
+void t_transaction::process_final(t_response *r) {
+ final = (t_response *)r->copy();
+}
+
+void t_transaction::process_response(t_response *r) {
+ if (r->is_provisional()) {
+ process_provisional(r);
+ } else {
+ process_final(r);
+ }
+}
+
+t_trans_state t_transaction::get_state(void) const {
+ return state;
+}
+
+void t_transaction::set_tuid(unsigned short _tuid) {
+ tuid = _tuid;
+}
+
+t_method t_transaction::get_method(void) const {
+ return request->method;
+}
+
+string t_transaction::get_to_tag(void) {
+ string tag;
+
+ tag = request->hdr_to.tag;
+ if (tag.size() > 0) return tag;
+ if (to_tag.size() > 0) return to_tag;
+ to_tag = random_token(TAG_LEN);
+ return to_tag;
+}
+
+// RCF 3261 section 8.2.6.2
+t_response *t_transaction::create_response(int code, string reason) {
+ t_response *r;
+
+ r = request->create_response(code, reason);
+
+ // NOTE: 100 Trying does not establish a dialog
+ if (code != R_100_TRYING) {
+ r->hdr_to.set_tag(get_to_tag());
+ }
+
+ return r;
+}
+
+///////////////////////////////////////////////////////////
+// RFC 3261 17.1
+// Client transaction
+///////////////////////////////////////////////////////////
+
+t_trans_client::t_trans_client(t_request *r, const t_ip_port &ip_port,
+ unsigned short _tuid) :
+ t_transaction(r, _tuid),
+ dst_ip_port(ip_port)
+{
+ // Send request
+ evq_sender->push_network(r, dst_ip_port);
+}
+
+// RFC 3261 17.1.3, 8.2.6.2
+// Section 17.1.3 states that only the branch and CSeq method should match.
+// This can lead to the following problem however:
+//
+// 1) A response matches a BYE request, but has a wrong call id.
+// 2) As the response matches the request, the transaction finishes.
+// 3) Then the response is delivered to the TU which tries to match the
+// response to a dialog.
+// 4) As the call id is wrong, no match is found an the response is discarded.
+// 5) Now the TU keeps waiting forever for a response on the BYE
+//
+// By taking the call id into account here, this scenario is prevented.
+// When a call id is wrong, the BYE request will be retransmitted due to
+// timeouts until the transaction times out completely and a 408 is sent
+// to the TU.
+//
+// Same problem can occur when tags do not match, so tag is take into account
+// as well. So tags are take into account as well.
+bool t_trans_client::match(t_response *r) const {
+ t_via &req_top_via = request->hdr_via.via_list.front();
+ t_via &resp_top_via = r->hdr_via.via_list.front();
+
+ return (req_top_via.branch == resp_top_via.branch &&
+ request->hdr_cseq.method == r->hdr_cseq.method &&
+ request->hdr_call_id.call_id == r->hdr_call_id.call_id &&
+ request->hdr_from.tag == r->hdr_from.tag &&
+ (request->hdr_to.tag.empty() || request->hdr_to.tag == r->hdr_to.tag));
+}
+
+// An ICMP error matches a transaction when the destination IP address/port
+// of the packet that caused the ICMP error equals the destination
+// IP address/port of the transaction. Other information of the packet causing
+// the ICMP error is not available.
+// In theory when multiple transactions are open for the same destination, the
+// wrong transaction may process the ICMP error. In practice this should rarely
+// happen as the destination will be unreachable for all those transactions.
+// If it happens a transaction gets aborted.
+bool t_trans_client::match(const t_icmp_msg &icmp) const {
+ return (dst_ip_port.ipaddr == icmp.ipaddr && dst_ip_port.port == icmp.port);
+}
+
+bool t_trans_client::match(const string &branch, const t_method &cseq_method) const {
+ t_via &req_top_via = request->hdr_via.via_list.front();
+
+ return (req_top_via.branch == branch &&
+ request->hdr_cseq.method == cseq_method);
+}
+
+void t_trans_client::process_provisional(t_response *r) {
+ // Set the to_tag, such that an internally genrated answer (when needed)
+ // will have the correct tag.
+ // An INVITE transaction may receive provisional responses with
+ // different to-tags. Only the first to-tag will be kept and an
+ // internally generated response will match this tag.
+ if (!r->hdr_to.tag.empty() && to_tag.empty()) {
+ to_tag = r->hdr_to.tag;
+ }
+
+ t_transaction::process_provisional(r);
+}
+
+///////////////////////////////////////////////////////////
+// RFC 3261 17.1.1
+// Client INVITE transaction
+///////////////////////////////////////////////////////////
+
+void t_tc_invite::start_timer_A(void) {
+ timer_A = transaction_mgr->start_timer(duration_A, TIMER_A, id);
+
+ // Double duration for a next start
+ duration_A = 2 * duration_A;
+}
+
+void t_tc_invite::start_timer_B(void) {
+ timer_B = transaction_mgr->start_timer(DURATION_B, TIMER_B, id);
+}
+
+void t_tc_invite::start_timer_D(void) {
+ // RFC 3261 17.1.1.2
+ // For reliable transport timer D must be set to zero seconds.
+ if (dst_ip_port.transport == "udp") {
+ timer_D = transaction_mgr->start_timer(DURATION_D, TIMER_D, id);
+ } else {
+ timer_D = transaction_mgr->start_timer(0, TIMER_D, id);
+ }
+}
+
+void t_tc_invite::stop_timer_A(void) {
+ if (timer_A) {
+ transaction_mgr->stop_timer(timer_A);
+ timer_A = 0;
+ }
+}
+
+void t_tc_invite::stop_timer_B(void) {
+ if (timer_B) {
+ transaction_mgr->stop_timer(timer_B);
+ timer_B = 0;
+ }
+}
+
+void t_tc_invite::stop_timer_D(void) {
+ if (timer_D) {
+ transaction_mgr->stop_timer(timer_D);
+ timer_D = 0;
+ }
+}
+
+t_tc_invite::t_tc_invite(t_request *r, const t_ip_port &ip_port,
+ unsigned short _tuid) :
+ t_trans_client(r, ip_port, _tuid)
+{
+ assert(r->method == INVITE);
+
+ ack = NULL;
+ duration_A = DURATION_A;
+ state = TS_CALLING;
+
+ // RFC 3261 17.1.1.2
+ // Start timer A for unreliable transports.
+ if (ip_port.transport == "udp") start_timer_A();
+
+ // RFC 3261 17.1.1.2
+ // Start timer B for all transports
+ start_timer_B();
+
+ timer_D = 0;
+}
+
+t_tc_invite::~t_tc_invite() {
+ if (ack != NULL) {
+ MEMMAN_DELETE(ack);
+ delete ack;
+ }
+ stop_timer_A();
+ stop_timer_B();
+ stop_timer_D();
+}
+
+void t_tc_invite::process_provisional(t_response *r) {
+ assert(r->is_provisional());
+
+ switch (state) {
+ case TS_CALLING:
+ stop_timer_A();
+ stop_timer_B();
+ // fall through
+ case TS_PROCEEDING:
+ t_trans_client::process_provisional(r);
+ state = TS_PROCEEDING;
+
+ // Report to TU
+ evq_trans_layer->push_user(r, tuid, id);
+ break;
+ default:
+ // Discard provisional response in other states
+ break;
+ }
+}
+
+void t_tc_invite::process_final(t_response *r) {
+ assert(r->is_final());
+
+ t_ip_port ip_port;
+
+ switch (state) {
+ case TS_CALLING:
+ stop_timer_A();
+ stop_timer_B();
+ // fall through
+ case TS_PROCEEDING:
+ t_trans_client::process_final(r);
+
+ if (r->is_success()) {
+ state = TS_TERMINATED;
+ } else {
+ // RFC 3261 17.1.1.3
+ // construct ACK
+ ack = new t_request(ACK);
+ MEMMAN_NEW(ack);
+ ack->uri = request->uri;
+ ack->hdr_call_id = request->hdr_call_id;
+ ack->hdr_from = request->hdr_from;
+ ack->hdr_to = r->hdr_to;
+ ack->hdr_via.add_via(
+ request->hdr_via.via_list.front());
+ ack->hdr_cseq.set_seqnr(request->hdr_cseq.seqnr);
+ ack->hdr_cseq.set_method(ACK);
+ ack->hdr_route = request->hdr_route;
+ ack->hdr_max_forwards.set_max_forwards(MAX_FORWARDS);
+ SET_HDR_USER_AGENT(ack->hdr_user_agent)
+
+ // RFC 3261 22.1
+ // Duplicate Authorization and Proxy-Authorization
+ // headers from INVITE if the credentials in the
+ // INVITE are accepted.
+ if (r->code != R_401_UNAUTHORIZED &&
+ r->code != R_407_PROXY_AUTH_REQUIRED)
+ {
+ ack->hdr_authorization =
+ request->hdr_authorization;
+ ack->hdr_proxy_authorization =
+ request->hdr_proxy_authorization;
+ }
+
+ // RFC 3263 4
+ // ACK for non-2xx SIP responses to INVITE MUST be sent t
+ // to the same host.
+ request->get_current_destination(ip_port);
+ ack->set_destination(ip_port);
+
+ // Send ACK
+ evq_sender->push_network(ack, dst_ip_port);
+
+ start_timer_D();
+ state = TS_COMPLETED;
+ }
+
+ // Report to TU
+ evq_trans_layer->push_user(r, tuid, id);
+ break;
+ case TS_COMPLETED:
+ // A failure has been received. So 2XX is not
+ // expected anymore. Discard 2XX.
+ if (r->is_success()) {
+ break;
+ }
+
+ // Retransmit ACK
+ evq_sender->push_network(ack, dst_ip_port);
+ break;
+ default:
+ break;
+ }
+}
+
+void t_tc_invite::process_icmp(const t_icmp_msg &icmp) {
+ log_file->write_report("ICMP error received.", "t_tc_invite::process_icmp");
+ process_failure(FAIL_TRANSPORT);
+}
+
+void t_tc_invite::process_failure(t_failure failure) {
+ t_response *r;
+
+ switch(state) {
+ case TS_CALLING:
+ stop_timer_A();
+ stop_timer_B();
+
+ switch (failure) {
+ case FAIL_TRANSPORT:
+ // A transport failure indicates a kind of network problem.
+ // So the server is not available. Generate an internal
+ // 503 Service Unavailable repsponse to notify the TU.
+ r = create_response(R_503_SERVICE_UNAVAILABLE);
+ break;
+ case FAIL_TIMEOUT:
+ r = create_response(R_408_REQUEST_TIMEOUT);
+ break;
+ default:
+ log_file->write_header("t_tc_invite::process_failure",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("Unknown type of failure: ");
+ log_file->write_raw((int)failure);
+ log_file->write_endl();
+ log_file->write_footer();
+
+ r = create_response(R_400_BAD_REQUEST);
+ break;
+ }
+
+
+ log_file->write_header("t_tc_invite::process_failure",
+ LOG_NORMAL, LOG_INFO);
+ log_file->write_raw("Transaction failed.\n\n");
+ log_file->write_raw("Send internal:\n");
+ log_file->write_raw(r->encode());
+ log_file->write_footer();
+
+ evq_trans_layer->push_user(r, tuid, id);
+ MEMMAN_DELETE(r);
+ delete r;
+ state = TS_TERMINATED;
+ break;
+ default:
+ // In other states a response has been received already,
+ // so this failure seems to be a mismatch. Discard.
+ break;
+ }
+}
+
+void t_tc_invite::timeout(t_sip_timer t) {
+ t_response *r;
+
+ assert (t == TIMER_A || t == TIMER_B || t == TIMER_D);
+
+ switch (state) {
+ case TS_CALLING:
+ switch (t) {
+ case TIMER_A:
+ // Resend request
+ evq_sender->push_network(request, dst_ip_port);
+ start_timer_A();
+ break;
+ case TIMER_B:
+ stop_timer_A();
+ timer_B = 0;
+ // Report timer expiry to TU
+ r = create_response(R_408_REQUEST_TIMEOUT);
+
+ log_file->write_header("t_tc_invite::timeout",
+ LOG_NORMAL, LOG_INFO);
+ log_file->write_raw("Timer B expired.\n\n");
+ log_file->write_raw("Send internal:\n");
+ log_file->write_raw(r->encode());
+ log_file->write_footer();
+
+ evq_trans_layer->push_user(r, tuid, id);
+ MEMMAN_DELETE(r);
+ delete r;
+ state = TS_TERMINATED;
+ break;
+ default:
+ // Ignore expiry of other timers.
+ // Other timers should have been stopped.
+ break;
+ }
+ break;
+ case TS_COMPLETED:
+ switch (t) {
+ case TIMER_D:
+ timer_D = 0;
+ state = TS_TERMINATED;
+ break;
+ default:
+ // Ignore expiry of other timers.
+ // Other timers should have been stopped.
+ break;
+ }
+ break;
+ default:
+ // Ignore timer expiries in other states
+ // Other timers should have been stopped.
+ break;
+ }
+}
+
+void t_tc_invite::abort(void) {
+ t_response *r;
+
+ switch (state) {
+ case TS_PROCEEDING:
+ r = create_response(R_408_REQUEST_TIMEOUT, "Request Aborted");
+
+ log_file->write_header("t_tc_invite::abort",
+ LOG_NORMAL, LOG_INFO);
+ log_file->write_raw("Invite transaction aborted.\n\n");
+ log_file->write_raw("Send internal:\n");
+ log_file->write_raw(r->encode());
+ log_file->write_footer();
+
+ evq_trans_layer->push_user(r, tuid, id);
+ MEMMAN_DELETE(r);
+ delete r;
+ state = TS_TERMINATED;
+ break;
+ default:
+ // Ignore abortion in other states.
+ // In other states the request can be terminated in
+ // a normal way.
+ break;
+ }
+}
+
+///////////////////////////////////////////////////////////
+// RFC 3261 17.1.2
+// Client non-INVITE transaction
+///////////////////////////////////////////////////////////
+
+void t_tc_non_invite::start_timer_E(void) {
+ if (state == TS_PROCEEDING) duration_E = DURATION_T2;
+ timer_E = transaction_mgr->start_timer(duration_E, TIMER_E, id);
+ duration_E = 2 * duration_E;
+ if (duration_E > DURATION_T2) duration_E = DURATION_T2;
+}
+
+void t_tc_non_invite::start_timer_F(void) {
+ timer_F = transaction_mgr->start_timer(DURATION_F, TIMER_F, id);
+}
+
+void t_tc_non_invite::start_timer_K(void) {
+ // RFC 3261 17.1.2.2
+ // For reliable transports set timer K to zero seconds.
+ if (dst_ip_port.transport == "udp") {
+ timer_K = transaction_mgr->start_timer(DURATION_K, TIMER_K, id);
+ } else {
+ timer_K = transaction_mgr->start_timer(0, TIMER_K, id);
+ }
+}
+
+void t_tc_non_invite::stop_timer_E(void) {
+ if (timer_E) {
+ transaction_mgr->stop_timer(timer_E);
+ timer_E = 0;
+ }
+}
+
+void t_tc_non_invite::stop_timer_F(void) {
+ if (timer_F) {
+ transaction_mgr->stop_timer(timer_F);
+ timer_F = 0;
+ }
+}
+
+void t_tc_non_invite::stop_timer_K(void) {
+ if (timer_K) {
+ transaction_mgr->stop_timer(timer_K);
+ timer_K = 0;
+ }
+}
+
+t_tc_non_invite::t_tc_non_invite(t_request *r, const t_ip_port &ip_port,
+ unsigned short _tuid) :
+ t_trans_client(r, ip_port, _tuid)
+{
+ assert(r->method != INVITE);
+
+ state = TS_TRYING;
+ duration_E = DURATION_E;
+
+ // RFC 3261 17.1.2.2
+ // Start timer E for unreliable transports.
+ if (ip_port.transport == "udp") start_timer_E();
+
+ // RFC 3261 17.1.2.2
+ // Start timer F for all transports.
+ start_timer_F();
+
+ timer_K = 0;
+}
+
+t_tc_non_invite::~t_tc_non_invite() {
+ stop_timer_E();
+ stop_timer_F();
+ stop_timer_K();
+}
+
+void t_tc_non_invite::process_provisional(t_response *r) {
+ assert(r->is_provisional());
+
+ switch (state) {
+ case TS_TRYING:
+ case TS_PROCEEDING:
+ t_trans_client::process_provisional(r);
+ state = TS_PROCEEDING;
+ // Report to TU
+ evq_trans_layer->push_user(r, tuid, id);
+ break;
+ default:
+ // Discard provisional response in other states
+ break;
+ }
+}
+
+void t_tc_non_invite::process_final(t_response *r) {
+ assert(r->is_final());
+
+ switch (state) {
+ case TS_TRYING:
+ case TS_PROCEEDING:
+ t_trans_client::process_final(r);
+ stop_timer_E();
+ stop_timer_F();
+ // Report to TU
+ evq_trans_layer->push_user(r, tuid, id);
+ start_timer_K();
+ state = TS_COMPLETED;
+ break;
+ case TS_COMPLETED:
+ // The received response is a retransmission.
+ // AS the final response is already received this
+ // retransmission can be discarded.
+ // fall through
+ default:
+ break;
+ }
+}
+
+void t_tc_non_invite::process_icmp(const t_icmp_msg &icmp) {
+ log_file->write_report("ICMP error received.", "t_tc_non_invite::process_icmp");
+ process_failure(FAIL_TRANSPORT);
+}
+
+void t_tc_non_invite::process_failure(t_failure failure) {
+ t_response *r;
+
+ switch(state) {
+ case TS_TRYING:
+ stop_timer_E();
+ stop_timer_F();
+
+ switch (failure) {
+ case FAIL_TRANSPORT:
+ // A transport failure indicates a kind of network problem.
+ // So the server is not available. Generate an internal
+ // 503 Service Unavailable repsponse to notify the TU.
+ r = create_response(R_503_SERVICE_UNAVAILABLE);
+ break;
+ case FAIL_TIMEOUT:
+ r = create_response(R_408_REQUEST_TIMEOUT);
+ break;
+ default:
+ log_file->write_header("t_tc_non_invite::process_failure",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("Unknown type of failure: ");
+ log_file->write_raw((int)failure);
+ log_file->write_endl();
+ log_file->write_footer();
+
+ r = create_response(R_400_BAD_REQUEST);
+ break;
+ }
+
+
+ log_file->write_header("t_tc_non_invite::process_failure",
+ LOG_NORMAL, LOG_INFO);
+ log_file->write_raw("Transaction failed.\n\n");
+ log_file->write_raw("Send internal:\n");
+ log_file->write_raw(r->encode());
+ log_file->write_footer();
+
+ evq_trans_layer->push_user(r, tuid, id);
+ MEMMAN_DELETE(r);
+ delete r;
+ state = TS_TERMINATED;
+ break;
+ default:
+ // In other states a response has been received already,
+ // so this failure seems to be a mismatch. Discard.
+ break;
+ }
+}
+
+void t_tc_non_invite::timeout(t_sip_timer t) {
+ t_response *r;
+
+ assert (t == TIMER_E || t == TIMER_F || t == TIMER_K);
+
+ switch (state) {
+ case TS_TRYING:
+ case TS_PROCEEDING:
+ switch (t) {
+ case TIMER_E:
+ // Resend request
+ evq_sender->push_network(request, dst_ip_port);
+ start_timer_E();
+ break;
+ case TIMER_F:
+ timer_F = 0;
+ stop_timer_E();
+ // Report timer expiry to TU
+ r = create_response(R_408_REQUEST_TIMEOUT);
+
+ log_file->write_header("t_tc_non_invite::timeout",
+ LOG_NORMAL, LOG_INFO);
+ log_file->write_raw("Timer F expired.\n\n");
+ log_file->write_raw("Send internal:\n");
+ log_file->write_raw(r->encode());
+ log_file->write_footer();
+
+ evq_trans_layer->push_user(r, tuid, id);
+ MEMMAN_DELETE(r);
+ delete r;
+ state = TS_TERMINATED;
+ break;
+ default:
+ // Ignore expiry of other timers.
+ // Other timers should have been stopped.
+ break;
+ }
+ break;
+ case TS_COMPLETED:
+ switch (t) {
+ case TIMER_K:
+ timer_K = 0;
+ state = TS_TERMINATED;
+ break;
+ default:
+ // Ignore expiry of other timers.
+ // Other timers should have been stopped.
+ break;
+
+ }
+ default:
+ // Ignore timer expiries in other states
+ // Other timers should have been stopped.
+ break;
+ }
+}
+
+void t_tc_non_invite::abort(void) {
+ t_response *r;
+
+ switch (state) {
+ case TS_TRYING:
+ case TS_PROCEEDING:
+ stop_timer_E();
+ stop_timer_F();
+ r = create_response(R_408_REQUEST_TIMEOUT, "Request Aborted");
+
+ log_file->write_header("t_tc_non_invite::abort",
+ LOG_NORMAL, LOG_INFO);
+ log_file->write_raw("Non-invite transaction aborted.\n\n");
+ log_file->write_raw("Send internal:\n");
+ log_file->write_raw(r->encode());
+ log_file->write_footer();
+
+ evq_trans_layer->push_user(r, tuid, id);
+ MEMMAN_DELETE(r);
+ delete r;
+ state = TS_TERMINATED;
+ break;
+ default:
+ // Ignore abortion in other states.
+ // In other states the request can be terminated in
+ // a normal way.
+ break;
+ }
+}
+
+///////////////////////////////////////////////////////////
+// RFC 3261 17.2
+// Server transaction
+///////////////////////////////////////////////////////////
+
+t_trans_server::t_trans_server(t_request *r, unsigned short _tuid) :
+ t_transaction(r, _tuid), resp_100_trying_sent(false)
+{
+ t_trans_server *t;
+ t_tid tid_cancel = 0;
+
+ // Report to TU
+ if (request->method == CANCEL) {
+ t = transaction_mgr->find_cancel_target(r);
+ if (t) tid_cancel = t->get_id();
+ evq_trans_layer->push_user_cancel(r, tuid, id, tid_cancel);
+ } else {
+ evq_trans_layer->push_user(r, tuid, id);
+ }
+}
+
+void t_trans_server::process_provisional(t_response *r) {
+ t_ip_port ip_port;
+
+ if (r->code == R_100_TRYING && resp_100_trying_sent) {
+ // Send 100 Trying only once
+ return;
+ }
+
+ t_transaction::process_provisional(r);
+ r->get_destination(ip_port);
+ if (ip_port.ipaddr == 0) {
+ // The response cannot be sent.
+ state = TS_TERMINATED;
+ // Report failure to TU
+ evq_trans_layer->push_failure(FAIL_TRANSPORT, id);
+ } else {
+ // Send response
+ evq_sender->push_network(r, ip_port);
+
+ if (r->code == R_100_TRYING) {
+ resp_100_trying_sent = true;
+ }
+ }
+}
+
+void t_trans_server::process_final(t_response *r) {
+ t_ip_port ip_port;
+
+ t_transaction::process_final(r);
+ r->get_destination(ip_port);
+
+ if (ip_port.ipaddr == 0) {
+ // The response cannot be sent.
+ state = TS_TERMINATED;
+ // Report failure to TU
+ evq_trans_layer->push_failure(FAIL_TRANSPORT, id);
+ } else {
+ // Send response
+ evq_sender->push_network(r, ip_port);
+ }
+}
+
+void t_trans_server::process_retransmission(void) {
+ // nothing to do
+}
+
+// RFC 3261 17.2.3
+// NOTE: retransmission of an incoming INVITE for which a 2XX response
+// has been sent already is checked by the TU.
+// see dialog::is_invite_retrans
+bool t_trans_server::match(t_request *r, bool cancel) const {
+ t_via &orig_top_via = request->hdr_via.via_list.front();
+ t_via &recv_top_via = r->hdr_via.via_list.front();
+
+ if (recv_top_via.rfc3261_compliant()) {
+ if (orig_top_via.branch != recv_top_via.branch) return false;
+ if (orig_top_via.host != recv_top_via.host) return false;
+ if (orig_top_via.port != recv_top_via.port) return false;
+
+ switch(r->method) {
+ case ACK:
+ // return (request->hdr_cseq.method == INVITE);
+ return (request->method == INVITE);
+ break;
+ case CANCEL:
+ if (!cancel) {
+ // return (request->hdr_cseq.method ==
+ // r->hdr_cseq.method);
+ return (request->method == r->method);
+ }
+
+ // The target of CANCEL cannot be a CANCEL request
+ // return (request->hdr_cseq.method != CANCEL);
+ return (request->method != CANCEL);
+ break;
+ default:
+ // return (request->hdr_cseq.method ==
+ // r->hdr_cseq.method);
+ return (request->method == r->method);
+ break;
+ }
+ }
+
+ // Matching rules for backward compatibiliy with RFC 2543
+ // TODO: verify rules for matching via headers
+ switch (r->method) {
+ case INVITE:
+ return (request->method == INVITE &&
+ request->uri.sip_match(r->uri) &&
+ request->hdr_to.tag == r->hdr_to.tag &&
+ request->hdr_from.tag == r->hdr_from.tag &&
+ request->hdr_call_id.call_id ==
+ r->hdr_call_id.call_id &&
+ request->hdr_cseq.seqnr == r->hdr_cseq.seqnr &&
+ orig_top_via.host == recv_top_via.host &&
+ orig_top_via.port == recv_top_via.port);
+ break;
+ case ACK:
+ return (request->method == INVITE &&
+ request->uri.sip_match(r->uri) &&
+ request->hdr_from.tag == r->hdr_from.tag &&
+ request->hdr_call_id.call_id ==
+ r->hdr_call_id.call_id &&
+ request->hdr_cseq.seqnr == r->hdr_cseq.seqnr &&
+ orig_top_via.host == recv_top_via.host &&
+ orig_top_via.port == recv_top_via.port &&
+ final != NULL &&
+ final->hdr_to.tag == r->hdr_to.tag);
+ break;
+ case CANCEL:
+ if (cancel) {
+ return (request->uri.sip_match(r->uri) &&
+ request->hdr_from.tag == r->hdr_from.tag &&
+ request->hdr_call_id.call_id ==
+ r->hdr_call_id.call_id &&
+ request->hdr_cseq.seqnr ==
+ r->hdr_cseq.seqnr &&
+ request->hdr_cseq.method != CANCEL &&
+ orig_top_via.host == recv_top_via.host &&
+ orig_top_via.port == recv_top_via.port);
+ }
+ // fall through
+ default:
+ return (request->uri.sip_match(r->uri) &&
+ request->hdr_from.tag == r->hdr_from.tag &&
+ request->hdr_call_id.call_id ==
+ r->hdr_call_id.call_id &&
+ request->hdr_cseq == r->hdr_cseq &&
+ orig_top_via.host == recv_top_via.host &&
+ orig_top_via.port == recv_top_via.port);
+ break;
+ }
+
+ // Should not get here
+ return false;
+}
+
+bool t_trans_server::match(t_request *r) const {
+ return match(r, false);
+}
+
+bool t_trans_server::match_cancel(t_request *r) const {
+ assert(r->method == CANCEL);
+ return match(r, true);
+}
+
+///////////////////////////////////////////////////////////
+// RFC 3261 17.2.1
+// Server INVITE transaction
+///////////////////////////////////////////////////////////
+
+void t_ts_invite::start_timer_G(void) {
+ timer_G = transaction_mgr->start_timer(duration_G, TIMER_G, id);
+ duration_G = 2 * duration_G;
+ if (duration_G > DURATION_T2) duration_G = DURATION_T2;
+}
+
+void t_ts_invite::start_timer_H(void) {
+ timer_H = transaction_mgr->start_timer(DURATION_H, TIMER_H, id);
+}
+
+void t_ts_invite::start_timer_I(void) {
+ // RFC 17.2.1
+ // Set timer I to T4 seconds for unreliable transports and to 0 for
+ // reliable transports.
+ if (request->src_ip_port.transport == "udp") {
+ timer_I = transaction_mgr->start_timer(DURATION_I, TIMER_I, id);
+ } else {
+ timer_I = transaction_mgr->start_timer(0, TIMER_I, id);
+ }
+}
+
+void t_ts_invite::stop_timer_G(void) {
+ if (timer_G) {
+ transaction_mgr->stop_timer(timer_G);
+ timer_G = 0;
+ }
+}
+
+void t_ts_invite::stop_timer_H(void) {
+ if (timer_H) {
+ transaction_mgr->stop_timer(timer_H);
+ timer_H = 0;
+ }
+}
+
+void t_ts_invite::stop_timer_I(void) {
+ if (timer_I) {
+ transaction_mgr->stop_timer(timer_I);
+ timer_I = 0;
+ }
+}
+
+t_ts_invite::t_ts_invite(t_request *r, unsigned short _tuid) :
+ t_trans_server(r, _tuid)
+{
+ assert(r->method == INVITE);
+
+ state = TS_PROCEEDING;
+ ack = NULL;
+ timer_G = 0;
+ timer_H = 0;
+ timer_I = 0;
+ duration_G = DURATION_G;
+}
+
+t_ts_invite::~t_ts_invite() {
+ if (ack != NULL) {
+ MEMMAN_DELETE(ack);
+ delete ack;
+ }
+ stop_timer_G();
+ stop_timer_H();
+ stop_timer_I();
+}
+
+void t_ts_invite::process_provisional(t_response *r) {
+ assert(r->is_provisional());
+
+ switch (state) {
+ case TS_PROCEEDING:
+ t_trans_server::process_provisional(r);
+ break;
+ default:
+ // TU should not send a provisional response
+ // in other states.
+ assert(false);
+ break;
+ }
+}
+
+void t_ts_invite::process_final(t_response *r) {
+ assert(r->is_final());
+
+ switch (state) {
+ case TS_PROCEEDING:
+ t_trans_server::process_final(r);
+ if (r->is_success()) {
+ state = TS_TERMINATED;
+ } else {
+ // RFC 3261 17.2.1
+ // Start timer G for unreliable transports.
+ if (request->src_ip_port.transport == "udp") {
+ start_timer_G();
+ }
+
+ // RFC 3261 17.2.1
+ // Start timer H for all transports
+ start_timer_H();
+
+ state = TS_COMPLETED;
+ }
+ break;
+ default:
+ // No final responses are expected anymore. Discard.
+ break;
+ }
+}
+
+void t_ts_invite::process_retransmission(void) {
+ t_ip_port ip_port;
+
+ switch (state) {
+ case TS_PROCEEDING:
+ // Retransmit the latest provisional response (if present)
+ t_trans_server::process_retransmission();
+ if (provisional.size() > 0) {
+ t_response *r = provisional.back();
+ r->get_destination(ip_port);
+ if (ip_port.ipaddr == 0) {
+ // The response cannot be sent.
+ state = TS_TERMINATED;
+ // Report failure to TU
+ evq_trans_layer->push_failure(
+ FAIL_TRANSPORT, id);
+ } else {
+ // Send response
+ evq_sender->push_network(r, ip_port);
+ }
+ }
+ break;
+ case TS_COMPLETED:
+ // Retransmit the final response
+ t_trans_server::process_retransmission();
+ final->get_destination(ip_port);
+ if (ip_port.ipaddr == 0) {
+ // The response cannot be sent.
+ state = TS_TERMINATED;
+ // Report failure to TU
+ evq_trans_layer->push_failure(FAIL_TRANSPORT, id);
+ } else {
+ // Send response
+ evq_sender->push_network(final, ip_port);
+ }
+ break;
+ default:
+ // Retransmissions should not happen in other states.
+ // Discard.
+ break;
+ }
+}
+
+void t_ts_invite::timeout(t_sip_timer t) {
+ t_ip_port ip_port;
+
+ assert(t == TIMER_G || t == TIMER_I || t == TIMER_H);
+
+ switch (state) {
+ case TS_COMPLETED:
+ switch (t) {
+ case TIMER_G:
+ timer_G = 0;
+
+ // Retransmit the final response
+ final->get_destination(ip_port);
+ if (ip_port.ipaddr == 0) {
+ // The response cannot be sent.
+ stop_timer_H();
+ state = TS_TERMINATED;
+ // Report failure to TU
+ evq_trans_layer->push_failure(
+ FAIL_TRANSPORT, id);
+ } else {
+ // Send response
+ evq_sender->push_network(final, ip_port);
+ start_timer_G();
+ }
+ break;
+ case TIMER_H:
+ timer_H = 0;
+ stop_timer_G();
+ state = TS_TERMINATED;
+ // Report timer expiry to TU
+ evq_trans_layer->push_failure(FAIL_TIMEOUT, id);
+ break;
+ default:
+ // No other timers should be running. Discard.
+ break;
+ }
+ break;
+ case TS_CONFIRMED:
+ switch (t) {
+ case TIMER_I:
+ timer_I = 0;
+ state = TS_TERMINATED;
+ break;
+ default:
+ // No other timers should be running. Discard.
+ break;
+ }
+ default:
+ // In other states no timers should be running.
+ break;
+ }
+}
+
+void t_ts_invite::acknowledge(t_request *ack_request) {
+ assert(ack_request->method == ACK);
+
+ switch (state) {
+ case TS_COMPLETED:
+ ack = (t_request *)ack_request->copy();
+ stop_timer_G();
+ stop_timer_H();
+ start_timer_I();
+ state = TS_CONFIRMED;
+ // Report TU
+ // ACK should not be reported to TU for non-2xx
+ // evq_trans_layer->push_user(ack_request, tuid, id);
+ break;
+ default:
+ // ACK is not expected in other states. Discard;
+ break;
+ }
+}
+
+///////////////////////////////////////////////////////////
+// RFC 3261 17.2.2
+// Server non-INVITE transaction
+///////////////////////////////////////////////////////////
+
+void t_ts_non_invite::start_timer_J(void) {
+ // RFC 3261 17.2.2
+ // For unreliable transports set timer J to 64*T1, for reliable
+ // transports set it to 0.
+ if (request->src_ip_port.transport == "udp") {
+ timer_J = transaction_mgr->start_timer(DURATION_J, TIMER_J, id);
+ } else {
+ timer_J = transaction_mgr->start_timer(0, TIMER_J, id);
+ }
+}
+
+void t_ts_non_invite::stop_timer_J(void) {
+ if (timer_J) {
+ transaction_mgr->stop_timer(timer_J);
+ timer_J = 0;
+ }
+}
+
+t_ts_non_invite::t_ts_non_invite(t_request *r, unsigned short _tuid) :
+ t_trans_server(r, _tuid)
+{
+ assert(r->method != INVITE);
+ timer_J = 0;
+ state = TS_TRYING;
+}
+
+t_ts_non_invite::~t_ts_non_invite() {
+ stop_timer_J();
+}
+
+void t_ts_non_invite::process_provisional(t_response *r) {
+ assert(r->is_provisional());
+
+ switch (state) {
+ case TS_TRYING:
+ case TS_PROCEEDING:
+ t_trans_server::process_provisional(r);
+ state = TS_PROCEEDING;
+ break;
+ default:
+ // TU should not send a provisional response
+ // in other states.
+ assert(false);
+ break;
+ }
+}
+
+void t_ts_non_invite::process_final(t_response *r) {
+ assert(r->is_final());
+
+ switch (state) {
+ case TS_TRYING:
+ case TS_PROCEEDING:
+ t_trans_server::process_final(r);
+ start_timer_J();
+ state = TS_COMPLETED;
+ break;
+ default:
+ // No final responses are expected anymore. Discard.
+ break;
+ }
+}
+
+void t_ts_non_invite::process_retransmission(void) {
+ t_ip_port ip_port;
+ t_response *r;
+
+ switch (state) {
+ case TS_PROCEEDING:
+ // Retransmit the latest provisional response
+ t_trans_server::process_retransmission();
+ r = provisional.back();
+ r->get_destination(ip_port);
+ if (ip_port.ipaddr == 0) {
+ // The response cannot be sent.
+ state = TS_TERMINATED;
+ // Report failure to TU
+ evq_trans_layer->push_failure(FAIL_TRANSPORT, id);
+ } else {
+ // Send response
+ evq_sender->push_network(r, ip_port);
+ }
+ break;
+ case TS_COMPLETED:
+ // Retransmit the final response
+ t_trans_server::process_retransmission();
+ final->get_destination(ip_port);
+ if (ip_port.ipaddr == 0) {
+ // The response cannot be sent.
+ stop_timer_J();
+ state = TS_TERMINATED;
+ // Report failure to TU
+ evq_trans_layer->push_failure(FAIL_TRANSPORT, id);
+ } else {
+ // Send response
+ evq_sender->push_network(final, ip_port);
+ }
+ break;
+ default:
+ // Retransmissions should not happen in other states.
+ // Discard.
+ break;
+ }
+}
+
+void t_ts_non_invite::timeout(t_sip_timer t) {
+ assert (t == TIMER_J);
+
+ switch (state) {
+ case TS_COMPLETED:
+ switch (t) {
+ case TIMER_J:
+ timer_J = 0;
+ state = TS_TERMINATED;
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+}