Description: Michael Larabel's 6-line VoIP client implementation of Twinkle (normally supports two). Development sponsored by Global Fone.
Last Change: Thu 12/13/07 20:15
/*
Copyright (C) 2005-2007 Michel de Boer <michel@twinklephone.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <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_udp;
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, unsigned long ipaddr,
unsigned short port, unsigned short _tuid) :
t_transaction(r, _tuid)
{
dst_ipaddr = ipaddr;
dst_port = port;
// Send request
evq_sender_udp->push_network(r, ipaddr, 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_ipaddr == icmp.ipaddr && dst_port == icmp.port);
}
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) {
timer_D = transaction_mgr->start_timer(DURATION_D, 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, unsigned long ipaddr,
unsigned short port, unsigned short _tuid) :
t_trans_client(r, ipaddr, port, _tuid)
{
assert(r->method == INVITE);
ack = NULL;
duration_A = DURATION_A;
state = TS_CALLING;
start_timer_A();
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());
unsigned long ipaddr;
unsigned short 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(ipaddr, port);
ack->set_destination(ipaddr, port);
// Send ACK
evq_sender_udp->push_network(ack, dst_ipaddr,
dst_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_udp->push_network(ack, dst_ipaddr, dst_port);
break;
default:
break;
}
}
void t_tc_invite::process_icmp(const t_icmp_msg &icmp) {
t_response *r;
switch(state) {
case TS_CALLING:
stop_timer_A();
stop_timer_B();
// An ICMP error 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);
log_file->write_header("t_tc_invite::process_icmp",
LOG_NORMAL, LOG_INFO);
log_file->write_raw("ICMP error received.\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 ICMP error 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_udp->push_network(request, dst_ipaddr,
dst_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) {
timer_K = transaction_mgr->start_timer(DURATION_K, 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, unsigned long ipaddr,
unsigned short port, unsigned short _tuid) :
t_trans_client(r, ipaddr, port, _tuid)
{
assert(r->method != INVITE);
state = TS_TRYING;
duration_E = DURATION_E;
start_timer_E();
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) {
t_response *r;
switch(state) {
case TS_TRYING:
stop_timer_E();
stop_timer_F();
// An ICMP error 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);
log_file->write_header("t_tc_non_invite::process_icmp",
LOG_NORMAL, LOG_INFO);
log_file->write_raw("ICMP error received.\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 ICMP error 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_udp->push_network(request, dst_ipaddr,
dst_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) {
unsigned long ipaddr;
unsigned short port;
if (r->code == R_100_TRYING && resp_100_trying_sent) {
// Send 100 Trying only once
return;
}
t_transaction::process_provisional(r);
r->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(r, ipaddr, port);
if (r->code == R_100_TRYING) {
resp_100_trying_sent = true;
}
}
}
void t_trans_server::process_final(t_response *r) {
unsigned long ipaddr;
unsigned short port;
t_transaction::process_final(r);
r->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(r, ipaddr, 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) {
timer_I = transaction_mgr->start_timer(DURATION_I, 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 {
start_timer_G();
start_timer_H();
state = TS_COMPLETED;
}
break;
default:
// No final responses are expected anymore. Discard.
break;
}
}
void t_ts_invite::process_retransmission(void) {
unsigned long ipaddr;
unsigned short 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->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(r, ipaddr, port);
}
}
break;
case TS_COMPLETED:
// Retransmit the final response
t_trans_server::process_retransmission();
final->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(final, ipaddr, port);
}
break;
default:
// Retransmissions should not happen in other states.
// Discard.
break;
}
}
void t_ts_invite::timeout(t_sip_timer t) {
unsigned long ipaddr;
unsigned short 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->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(final,
ipaddr, 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) {
timer_J = transaction_mgr->start_timer(DURATION_J, 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) {
unsigned long ipaddr;
unsigned short port;
t_response *r;
switch (state) {
case TS_PROCEEDING:
// Retransmit the latest provisional response
t_trans_server::process_retransmission();
r = provisional.back();
r->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(r, ipaddr, port);
}
break;
case TS_COMPLETED:
// Retransmit the final response
t_trans_server::process_retransmission();
final->hdr_via.get_response_dst(ipaddr, port);
if (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_udp->push_network(final, ipaddr, 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;
}
}