/*
 * $Id: trans_layer.cpp 1713 2010-03-30 14:11:14Z rco $
 *
 * Copyright (C) 2007 Raphael Coeffic
 *
 * This file is part of SEMS, a free SIP media server.
 *
 * SEMS 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 released under
 * the GPL with the additional exemption that compiling, linking,
 * and/or using OpenSSL is allowed.
 *
 * For a license to use the SEMS software under conditions
 * other than those described here, or to purchase support for this
 * software, please contact iptel.org by e-mail at the following addresses:
 *    info@iptel.org
 *
 * SEMS 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 "trans_layer.h"
#include "sip_parser.h"
#include "trans_table.h"
#include "parse_cseq.h"
#include "parse_from_to.h"
#include "parse_route.h"
#include "parse_100rel.h"
#include "parse_extensions.h"
#include "parse_next_hop.h"
#include "sip_trans.h"
#include "msg_fline.h"
#include "msg_hdrs.h"
#include "udp_trsp.h"
#include "ip_util.h"
#include "resolver.h"
#include "sip_ua.h"
#include "msg_logger.h"

#include "wheeltimer.h"
#include "sip_timers.h"
#include "tr_blacklist.h"

#define DEFAULT_BL_TTL 60000 /* 60s */

#include "log.h"

#include "AmUtils.h"
#include "AmConfig.h"
#include "AmSipEvent.h"

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <assert.h>

#include <algorithm>

using std::make_shared;
using std::string_view;

bool trans_layer::accept_fr_without_totag = false;
unsigned int trans_layer::default_bl_ttl = DEFAULT_BL_TTL;

bool trans_layer::less_case_i::operator () (const string& lhs, const string& rhs) const
{
    return lower_cmp_n(lhs.c_str(),lhs.length(),
		       rhs.c_str(),rhs.length()) < 0;
}

trans_layer::trans_layer()
    : ua(NULL),
      transports()
{
}

trans_layer::~trans_layer()
{}


void trans_layer::register_ua(sip_ua* ua)
{
    this->ua = ua;
}

int trans_layer::register_transport(const shared_ptr<trsp_socket>& trsp)
{
    int if_num = trsp->get_if();
    if(transports.size() <= (size_t)if_num)
	transports.resize(if_num+1);

    if(transports[if_num].find(trsp->get_transport())
       != transports[if_num].end()) {
	WARN("transport already registered for this interface");
	return -1;
    }

    transports[if_num][trsp->get_transport()] = trsp;
    return 0;
}

void trans_layer::clear_transports()
{
    transports.clear();
}

int trans_layer::set_trsp_socket(sip_msg& msg, const string& next_trsp,
				  int out_interface)
{
    if((out_interface < 0)
       || ((unsigned int)out_interface >= transports.size())) {

	out_interface = find_outbound_if(&msg.remote_ip);
	if(out_interface < 0) {
	    DBG("could not find any suitable outbound interface");
	    return -1;
	}
    }

    if(transports[out_interface].empty()) {
	ERROR("no transport for this interface");
	return -1;
    }

    prot_collection::iterator prot_sock_it =
	transports[out_interface].find(next_trsp);

    if(prot_sock_it == transports[out_interface].end()) {

	DBG("could not find transport '%s' in outbound interface %i",
	    next_trsp.c_str(), out_interface);

	prot_sock_it = transports[out_interface].find("udp");

	// if we couldn't find anything, take whatever is there...
	if(prot_sock_it == transports[out_interface].end()) {
	    DBG("could not find transport 'udp' in outbound interface %i",
		out_interface);
	    prot_sock_it = transports[out_interface].begin();
	}
    }

    msg.local_socket = prot_sock_it->second;

    return 0;
}

static int patch_contact_transport(sip_header* contact, const cstring& trsp,
				   string& n_contact)
{
    DBG("contact: <%.*s>", contact->value.len, contact->value.s);

    list<cstring> contact_list;
    if(parse_nameaddr_list(contact_list, contact->value.s,
			   contact->value.len) < 0) {
	DBG("Could not parse contact list\n");
	return -1;
    }

    const char* marker = contact->value.s;
    const char* hf_end = marker + contact->value.len;

    for(list<cstring>::iterator ct_it = contact_list.begin();
	ct_it != contact_list.end(); ct_it++) {

	sip_nameaddr na;
	const char* c = ct_it->s;
	if(parse_nameaddr_uri(&na,&c,ct_it->len) < 0) {
	    DBG("Could not parse nameaddr & URI (%.*s)\n",ct_it->len,ct_it->s);
	    return -1;
	}

	bool found_trsp = false;
	for(list<sip_avp*>::iterator p_it = na.uri.params.begin();
	    p_it != na.uri.params.end(); p_it++) {

	    if(!lower_cmp_n((*p_it)->name.s,(*p_it)->name.len,"transport",9)) {
		found_trsp = true;
		if(lower_cmp_n((*p_it)->value.s,(*p_it)->value.len,
			     trsp.s,trsp.len)) {
		    // copy everything from last marker until param value
		    n_contact.append(marker, (*p_it)->value.s - marker);
		    // copy param value
		    n_contact.append(trsp.s, trsp.len);
		    // set marker after transport value
		    marker = (*p_it)->value.s + (*p_it)->value.len;
		}
	    }
	}

	if(!found_trsp){
	    // copy everything from last marker until end of addr
	    n_contact.append(marker, na.addr.s + na.addr.len - marker);
	    // copy new param + value
	    n_contact.append(";transport=");
	    n_contact.append(trsp.s, trsp.len);
	    // set marker after transport value
	    marker = na.addr.s + na.addr.len;
	}
    }

    if(!n_contact.empty()) {
	// finish copy
	n_contact.append(marker, hf_end - marker);
	contact->value = stl2cstr(n_contact);
    }

    return 0;
}

int trans_layer::send_reply(sip_msg& msg, const trans_ticket& tt,
			     const string& dialog_id, const string& to_tag,
			     const shared_ptr<msg_logger>& logger)
{
    // Ref.: RFC 3261 8.2.6, 12.1.1
    //
    // Fields to copy (from RFC 3261):
    //  - From
    //  - Call-ID
    //  - CSeq
    //  - Vias (same order)
    //  - To (+ tag if not yet present in request)
    //  - (if a dialog is created) Record-Route
    //
    // Fields to generate (if INVITE transaction):
    //    - Contact
    //    - Route: copied from
    //
    // SHOULD be contained:
    //  - Allow, Supported
    //
    // MAY be contained:
    //  - Accept

    if (!tt._bucket || !tt._t) {
	ERROR("Invalid transaction ticket\n");
	return -1;
    }

    trans_bucket* bucket = tt._bucket;
    sip_trans*    t = tt._t;

    bucket->lock();
    if(!bucket->exist(t)){
	bucket->unlock();
	ERROR("Invalid transaction key: transaction does not exist (%p;%p)\n",bucket,t);
	return -1;
    }

    if(t->reply_status >= 200){
	bucket->unlock();
	ERROR("Transaction has already been closed with a final reply\n");
	return -1;
    }

    sip_msg& req = *t->msg;

    auto local_socket = req.local_socket;

    // patch Contact-HF
    vector<string> contact_buf;
    if(!local_socket->is_opt_set(trsp_socket::no_transport_in_contact)) {
	    cstring trsp(local_socket->get_transport());

	    contact_buf.resize(msg.contacts.size());
	    vector<string>::iterator contact_buf_it = contact_buf.begin();

	    for(list<sip_header*>::iterator contact_it = msg.contacts.begin();
		contact_it != msg.contacts.end(); contact_it++, contact_buf_it++) {

	        patch_contact_transport(*contact_it, trsp, *contact_buf_it);
	    }
    }

    bool have_to_tag = false;

    // add 'received' should be added
    // check if first Via has rport parameter

    assert(req.via1);
    assert(req.via_p1);

    string remote_ip_str = get_addr_str(&req.remote_ip);

    bool append_received = !(req.via_p1->host == remote_ip_str.c_str());

    // needed if rport parameter was present but empty
    string remote_port_str;
    if(req.via_p1->has_rport) {
	if(!req.via_p1->rport.len){
	    remote_port_str = int2str(ntohs(((sockaddr_in*)&req.remote_ip)->sin_port));
	}
    }

    unsigned int rel100_ext = 0;
    unsigned int rseq = 0;
    int reply_code = msg.u.reply->code;

    // copy necessary headers
    for(list<sip_header*>::iterator it = req.hdrs.begin();
	it != req.hdrs.end(); ++it) {

	assert((*it));
	switch((*it)->type){

	case sip_header::H_TO:
	    if (!(*it)->p) break; // ignore if not parsed
	    if (!to_tag.empty()) {
		if (((sip_from_to*)(*it)->p)->tag.len) {
		    // To-tag present in request...
		    have_to_tag = true;
		    // ... save it:
		    t->to_tag = c2stlstr(((sip_from_to*)(*it)->p)->tag);
		}
	    }
	    else if(reply_code >= 300) {
		// Let final error replies clear
		// the to-tag if not present:
		// (necessary to match pre-RFC3261 non-200 ACKs)
		t->to_tag.clear();
	    }
	    break;

	case sip_header::H_REQUIRE:
	    if (rel100_ext)
		// there was already a(nother?) Require HF
		continue;
	    if(!parse_extensions(&rel100_ext, (*it)->value.s, (*it)->value.len)) {
		ERROR("failed to parse(own?) 'Require' hdr.\n");
		continue;
	    }

	    rel100_ext = rel100_ext & SIP_EXTENSION_100REL;

	    if (rel100_ext && rseq) { // our RSeq's are never 0
		t->last_rseq = rseq;
		continue; // the end.
	    }
	    break;

	case sip_header::H_RSEQ:
	    if (rseq) {
		ERROR("multiple 'RSeq' headers in reply.\n");
		continue;
	    }
	    if (!parse_rseq(&rseq, (*it)->value.s, (*it)->value.len)) {
		ERROR("failed to parse (own?) 'RSeq' hdr.\n");
		continue;
	    }
	    if (rel100_ext) {
		t->last_rseq = rseq;
		continue; // the end.
	    }
	    break;
	}
    }

    string c_len = int2str((unsigned int) msg.body.length());

    auto reply_buf = make_shared<string>();
    string& reply_str = *reply_buf; // for convenience

    status_line_wr(reply_str, reply_code, msg.u.reply->reason);

    for(list<sip_header*>::iterator it = req.hdrs.begin();
	it != req.hdrs.end(); ++it) {

	switch((*it)->type){

	case sip_header::H_VIA:

	    if((*it) == req.via1) {// 1st Via

		// move this code to something like:
		// write_reply_via(old_via1,old_via_p1,remote_ip_str,remote_port_str)

		unsigned int len;

                reply_str += c2stlstr((*it)->name);

		reply_str += ':';
		reply_str += SP;

		if(req.via_p1->has_rport && !req.via_p1->rport.len){

		    // copy everything from the beginning up to the "rport" param:
		    len = (req.via_p1->rport.s + req.via_p1->rport.len) - req.via1->value.s;
                    reply_str += string_view(req.via1->value.s, len);

		    // add '='
		    reply_str += '=';

		    // add the remote port
                    reply_str += remote_port_str;

		    //copy up to the end of the first Via parm
		    len = req.via_p1->eop - (req.via_p1->rport.s + req.via_p1->rport.len);
                    reply_str += string_view(req.via_p1->rport.s + req.via_p1->rport.len, len);
		}
		else {
		    //copy up to the end of the first Via parm
		    len = req.via_p1->eop - req.via1->value.s;
                    reply_str += string_view(req.via1->value.s, len);
		}

		if(append_received) {
		    reply_str += ";received=";
                    reply_str += remote_ip_str;
		}


		//copy the rest of the first Via header
		len = req.via1->value.s + req.via1->value.len - req.via_p1->eop;
                reply_str += string_view(req.via_p1->eop, len);

		reply_str += CR;
		reply_str += LF;
	    }
	    else {
		copy_hdr_wr(reply_str, *it);
	    }
	    break;

	case sip_header::H_TO:
	    if (!(*it)->p) break; // ignore if not parsed
	    if (to_tag.empty() || have_to_tag) {
		copy_hdr_wr(reply_str, *it);
	    }
	    else {
		reply_str += c2stlstr((*it)->name);

		reply_str += ':';
		reply_str += SP;

		reply_str += c2stlstr((*it)->value);

		reply_str += ";tag=";

		t->to_tag = to_tag;

                reply_str += to_tag;

		reply_str += CR;
		reply_str += LF;
	    }
	    break;

	case sip_header::H_FROM:
	case sip_header::H_CALL_ID:
	case sip_header::H_CSEQ:
	case sip_header::H_RECORD_ROUTE:
	    copy_hdr_wr(reply_str, *it);
	    break;
	}
    }

    copy_hdrs_wr(reply_str, msg.hdrs);
    content_length_wr(reply_str, (char*)c_len.c_str());

    reply_str += CR;
    reply_str += LF;

    reply_str += msg.body;

    DBG("reply_len = %zu\n", reply_str.length());

    int err = -1;

    // Inspect topmost 'Via' and select proper addr (TODO: resolve DNS names)
    // refs: RFC3261 18.2.2; RFC3581

    sockaddr_storage remote_ip;
    if(!local_socket) {

	ERROR("request to be replied has no transport socket set\n");
	goto end;
    }

    remote_ip = req.remote_ip;

    // force_via_address option? send to 1st via
    if(local_socket->is_opt_set(trsp_socket::force_via_address)) {
	string via_host = c2stlstr(req.via_p1->host);
	DBG("force_via_address: setting remote IP to via '%s'\n", via_host.c_str());
	if (resolver::instance()->str2ip(via_host.c_str(), &remote_ip,
					 (address_type)(IPv4 | IPv6)) != 1) {
	    ERROR("Invalid via_host '%s'\n", via_host.c_str());
	    goto end;
	}
    }

    if(local_socket->is_opt_set(trsp_socket::force_via_address)) {

	if(req.via_p1->has_rport){

	    if(req.via_p1->rport_i){
		// use 'rport'
		((sockaddr_in*)&remote_ip)->sin_port = htons(req.via_p1->rport_i);
	    }
	    // else: use the source port from the replied request (from IP hdr)
	    //       (already set).
	}
	else {

	    if(req.via_p1->port_i){
		// use port from 'sent-by' via address
		((sockaddr_in*)&remote_ip)->sin_port = htons(req.via_p1->port_i);
	    }
	    else {
		// use 5060
		((sockaddr_in*)&remote_ip)->sin_port = htons(5060);
	    }
	}
    }

    DBG("Sending to %s:%i <%.*s...>\n",
	get_addr_str(&remote_ip).c_str(),
	ntohs(((sockaddr_in*)&remote_ip)->sin_port),
	(int) std::min<size_t>(50, reply_str.length()) /* preview - instead of p_msg->len */, reply_str.c_str());

    //TODO: pass send-flags down to here
    err = local_socket->send(&remote_ip, reply_str.c_str(), reply_str.length(), 0);
    if(err < 0){
	ERROR("could not send to %s:%i <%.*s...>\n",
	      get_addr_str(&remote_ip).c_str(),
	      ntohs(((sockaddr_in*)&remote_ip)->sin_port),
              (int) std::min<size_t>(50, reply_str.length()) /* preview - instead of p_msg->len */, reply_str.c_str());

	if(!local_socket->is_reliable()) {
	    // set timer to capture retransmissions
	    // and delete transaction afterwards
	    t->reset_timer(STIMER_J,J_TIMER,bucket->get_id());
	}
	else {
	    // reliable transport
	    bucket->remove(t);
	}
	goto end;
    }

    stats.inc_sent_replies();

    t->retr_buf = reply_buf;
    memcpy(&t->retr_addr,&remote_ip,sizeof(sockaddr_storage));
    t->retr_socket = local_socket;

    if(logger) {
	sockaddr_storage src_ip;
	local_socket->copy_addr_to(&src_ip);
	logger->log(reply_str.c_str(), reply_str.length(), &src_ip, &remote_ip,
		    req.u.request->method_str,reply_code);

	if(!t->logger){
	    t->logger = logger;
	}
    }

    if(update_uas_reply(bucket,t,reply_code) == TS_REMOVED)
	goto end;

    if (!dialog_id.empty() && t->dialog_id.empty()) {
        t->dialog_id = dialog_id;
    }

 end:
    bucket->unlock();
    return err;
}

int trans_layer::send_sf_error_reply(const trans_ticket* tt, const sip_msg* req,
				      int reply_code, const cstring& reason,
				      const cstring& hdrs, const cstring& body)
{
    char to_tag_buf[SL_TOTAG_LEN];
    compute_sl_to_tag(to_tag_buf,req);
    string to_tag(to_tag_buf, SL_TOTAG_LEN);

    sip_msg reply;
    reply.u.reply = new sip_reply(reply_code,reason);

    const char* c = hdrs.s;
    int err = parse_headers(reply, &c, c+hdrs.len);
    if(err){
	ERROR("Malformed additional header\n");
	return -1;
    }
    reply.body = c2stlstr(body);

    return send_reply(reply, *tt, "", to_tag);
}

int trans_layer::send_sl_reply(sip_msg* req, int reply_code,
			       const cstring& reason, const cstring& hdrs,
			       const cstring& body)
{
    // Ref.: RFC 3261 8.2.6, 12.1.1
    //
    // Fields to copy (from RFC 3261):
    //  - From
    //  - Call-ID
    //  - CSeq
    //  - Vias (same order)
    //  - To (+ tag if not yet present in request)
    //  - (if a dialog is created) Record-Route
    //
    // Fields to generate (if INVITE transaction):
    //    - Contact
    //    - Route: copied from
    //
    // SHOULD be contained:
    //  - Allow, Supported
    //
    // MAY be contained:
    //  - Accept

    assert(req);

    bool have_to_tag = false;

    for(list<sip_header*>::iterator it = req->hdrs.begin();
	it != req->hdrs.end(); ++it) {

	assert(*it);
	switch((*it)->type){

	case sip_header::H_TO:

	    if ((*it)->p && ((sip_from_to*)(*it)->p)->tag.len) {
		// To-tag present in request
		have_to_tag = true;
	    }
	    break;
	}
    }

    string c_len = int2str(body.len);

    string reply_str;

    status_line_wr(reply_str, reply_code,reason);

    for(list<sip_header*>::iterator it = req->hdrs.begin();
	it != req->hdrs.end(); ++it) {

	switch((*it)->type){

	case sip_header::H_TO:

	    if(have_to_tag){
		copy_hdr_wr(reply_str, *it);
	    }
	    else {
		reply_str += c2stlstr((*it)->name);

		reply_str += ':';
		reply_str += SP;

		reply_str += c2stlstr((*it)->value);

		reply_str += ";tag=";

		char to_tag[SL_TOTAG_LEN];
		compute_sl_to_tag(to_tag,req);
		reply_str += string_view(to_tag, SL_TOTAG_LEN);

		reply_str += CR;
		reply_str += LF;
	    }
	    break;

	case sip_header::H_FROM:
	case sip_header::H_CALL_ID:
	case sip_header::H_CSEQ:
	case sip_header::H_VIA:
	case sip_header::H_RECORD_ROUTE:
	    copy_hdr_wr(reply_str, *it);
	    break;
	}
    }

    if (hdrs.len) {
	reply_str += c2stlstr(hdrs);
    }

    content_length_wr(reply_str, (char*)c_len.c_str());

    reply_str += CR;
    reply_str += LF;

    if(body.len){

	reply_str += c2stlstr(body);
    }

    assert(req->local_socket);

    int err = req->local_socket->send(&req->remote_ip, reply_str.c_str(), reply_str.length(), 0);

    stats.inc_sent_replies();

    return err;
}


static void prepare_strict_routing(sip_msg* msg, string& ext_uri_buffer)
{
    if(msg->route.empty())
	return;

    sip_header* fr = msg->route.front();
    sip_uri* route_uri = get_first_route_uri(fr);

    // Loose routing is used,
    // no need for further processing
    if(!route_uri || is_loose_route(route_uri))
	return;

    sip_route* route = (sip_route*)fr->p;
    sip_nameaddr* fr_na = route->elmts.front()->addr;
    cstring fr_na_addr = fr_na->addr;

    if(route->elmts.size() == 1){
	// remove current route header from message
	msg->route.pop_front();

	list<sip_header*>::iterator h_it =
	    std::find(msg->hdrs.begin(),msg->hdrs.end(),fr);

	if(h_it != msg->hdrs.end())
	    msg->hdrs.erase(h_it);

	delete fr;
    }
    else if(route->elmts.size() > 1) {
	// remove first element from the list
	delete route->elmts.front();
	route->elmts.pop_front();

	// fetch the next route element
	route_elmt* nxt_re = route->elmts.front();

	// adjust route header
	fr->value.len = (fr->value.s + fr->value.len) - nxt_re->route.s;
	fr->value.s   = nxt_re->route.s;
    }

    // copy r_uri at the end of the route set.
    // ext_uri_buffer must have the same scope as 'msg'
    ext_uri_buffer = "<" + c2stlstr(msg->u.request->ruri_str) + ">";
    msg->hdrs.push_back(new sip_header(0,"Route",stl2cstr(ext_uri_buffer)));

    // and replace the R-URI with the first route URI
    msg->u.request->ruri_str = fr_na_addr;
}


//
// Ref. RFC 3261 "12.2.1.1 Generating the Request"
//
int trans_layer::set_next_hop(sip_msg& msg,
			       string& next_hop,
			       unsigned short& next_port,
			       string& next_trsp)
{
    static const cstring default_trsp("udp");

    list<sip_header*>& route_hdrs = msg.route;
    int err=0;

    if(!route_hdrs.empty()){

	sip_header* fr = route_hdrs.front();
	sip_uri* route_uri = get_first_route_uri(fr);
	if(route_uri == NULL) {

	    DBG("Parsing 1st route uri failed\n");
	    return -1;
	}

	if (next_hop.empty()) {
	    next_hop  = c2stlstr(route_uri->host);
	    if(route_uri->port_str.len)
		next_port = route_uri->port;
	    if(route_uri->trsp && route_uri->trsp->value.len)
		next_trsp = c2stlstr(route_uri->trsp->value);
	}
    }
    else {

	sip_uri parsed_r_uri;
	cstring& r_uri = msg.u.request->ruri_str;

	err = parse_uri(&parsed_r_uri,r_uri.s,r_uri.len);
	if(err < 0){
	    ERROR("Invalid Request URI\n");
	    return -1;
	}
	DBG("setting next-hop based on request-URI\n");
	next_hop  = c2stlstr(parsed_r_uri.host);
	if(parsed_r_uri.port_str.len)
	    next_port = parsed_r_uri.port;
	if(parsed_r_uri.trsp)
	    next_trsp = c2stlstr(parsed_r_uri.trsp->value);
    }

    if(next_trsp.empty()) {
	DBG("no transport specified, setting default one (%.*s)",
	    default_trsp.len,default_trsp.s);
	next_trsp = c2stlstr(default_trsp);
    }

    DBG("next_hop:next_port is <%s:%u/%s>\n",
	next_hop.c_str(), next_port,
	next_trsp.c_str());

    return 0;
}

static void set_err_reply_from_req(sip_msg* err, sip_msg* req,
				   int code, const char* reason)
{
    err->type = SIP_REPLY;
    err->u.reply = new sip_reply();
    err->u.reply->code = code;
    err->u.reply->reason = cstring(reason);

    // pre-parse message
    if(!req->from->p) {
	DBG("parsing From-HF...");
	req->from->p = new sip_from_to();
	parse_from_to((sip_from_to*)req->from->p,
		      req->from->value.s,req->from->value.len);
    }
    err->from = req->from;

    if(!req->to->p) {
	DBG("parsing To-HF...");
	req->to->p = new sip_from_to();
	parse_from_to((sip_from_to*)req->to->p,
		      req->to->value.s,req->to->value.len);
    }
    err->to = req->to;

    if(!req->cseq->p) {
	DBG("parsing CSeq-HF...");
	req->cseq->p = new sip_cseq();
	parse_cseq((sip_cseq*)req->cseq->p,
		   req->cseq->value.s,req->cseq->value.len);
    }
    err->cseq = req->cseq;
    err->callid = req->callid;
    err->via_p1 = req->via_p1;
}

void trans_layer::transport_error(sip_msg* msg)
{
    const char* err_msg=0;
    int ret = parse_sip_msg(*msg, err_msg);
    if(ret){
	DBG("parse_sip_msg returned %i\n",ret);

	if(!err_msg){
	    err_msg = (char*)"unknown parsing error";
	}
	DBG("parsing error: %s\n",err_msg);

	DBG("Message was: \"%s\"\n", msg->buf.c_str());
	return;
    }

    // transport errors for replies and ACK requests
    // should be silently dropped
    if((msg->type != SIP_REQUEST) ||
       (msg->u.request->method == sip_request::ACK))
	return;

    // generate error reply
    sip_msg* err = new sip_msg();
    set_err_reply_from_req(err,msg,503,"Transport Error");

    // err will be deleted there, as any received message
    process_rcvd_msg(err);
}

static void translate_string(sip_msg* dst_msg, cstring& dst,
			     const sip_msg* src_msg, const cstring& src)
{
    // TODO: use string views
    dst.s = dst_msg->buf.c_str() + (src.s - src_msg->buf.c_str());
    dst.len = src.len;
}

static void translate_hdr(sip_msg* dst_msg, sip_header*& dst,
			  const sip_msg* src_msg, const sip_header* src)
{
    dst = new sip_header();
    dst_msg->hdrs.push_back(dst);
    dst->type = src->type;
    translate_string(dst_msg,dst->name,src_msg,src->name);
    translate_string(dst_msg,dst->value,src_msg,src->value);
    dst->p = NULL;
}

static void gen_error_reply_from_req(sip_msg& reply, const sip_msg* req,
				     int code, const char* reason)
{
    reply.copy_msg_buf(req->buf);

    reply.type = SIP_REPLY;
    reply.u.reply = new sip_reply();

    reply.u.reply->code = code;
    reply.u.reply->reason = cstring(reason);

    translate_hdr(&reply,reply.from, req,req->from);
    reply.from->p = new sip_from_to();
    parse_from_to((sip_from_to*)reply.from->p,
		  reply.from->value.s,reply.from->value.len);

    translate_hdr(&reply,reply.to, req,req->to);
    reply.to->p = new sip_from_to();
    parse_from_to((sip_from_to*)reply.to->p,
		  reply.to->value.s,reply.to->value.len);

    translate_hdr(&reply,reply.cseq, req,req->cseq);
    reply.cseq->p = new sip_cseq();
    parse_cseq((sip_cseq*)reply.cseq->p,
	       reply.cseq->value.s,reply.cseq->value.len);

    translate_hdr(&reply,reply.callid, req,req->callid);
}

void trans_layer::timeout(trans_bucket* bucket, sip_trans* t)
{
    t->reset_all_timers();
    t->state = TS_TERMINATED;

    // send 408 to 'ua'
    sip_msg reply;
    gen_error_reply_from_req(reply,t->msg,408,"Timeout");

    string dialog_id = t->dialog_id;

    if(t->flags & TR_FLAG_DISABLE_BL) {
	bucket->remove(t);
    }
    else {
	// set blacklist timer
	t->reset_timer(STIMER_BL,BL_TIMER,bucket->get_id());
    }
    bucket->unlock();

    ua->handle_sip_reply(dialog_id, &reply);
}

static int patch_ruri_with_remote_ip(string& n_uri, sip_msg* msg)
{
    // - parse R-URI
    // - replace host/port
    // - generate new R-URI
    cstring old_ruri = msg->u.request->ruri_str;
    struct sip_uri parsed_uri;
    if(parse_uri(&parsed_uri, old_ruri.s, old_ruri.len) < 0) {
	ERROR("could not parse local R-URI ('%.*s')",old_ruri.len,old_ruri.s);
	return -1;
    }

    // copy from the beginning until URI-host
    n_uri = string(old_ruri.s, parsed_uri.host.s - old_ruri.s);

    // append new host and port
    n_uri += get_addr_str(&msg->remote_ip);
    unsigned short new_port = am_get_port(&msg->remote_ip);
    if(new_port != 5060) {
 	n_uri += ":" + int2str(new_port);
    }

    if(parsed_uri.port_str.len) {
	// copy from end of port-string until the end of old R-URI
	n_uri += string(parsed_uri.port_str.s + parsed_uri.port_str.len,
			old_ruri.s + old_ruri.len
			- (parsed_uri.port_str.s
			   + parsed_uri.port_str.len));
    }
    else {
	// copy from end of host-string until the end of old R-URI
	n_uri += string(parsed_uri.host.s + parsed_uri.host.len,
			old_ruri.s + old_ruri.len
			- (parsed_uri.host.s
			   + parsed_uri.host.len));
    }

    msg->u.request->ruri_str = stl2cstr(n_uri);

    return 0;
}

static int generate_and_parse_new_msg(sip_msg* msg, sip_msg*& p_msg)
{
    char branch_buf[BRANCH_BUF_LEN];
    compute_branch(branch_buf,msg->callid->value,msg->cseq->value);
    cstring branch(branch_buf,BRANCH_BUF_LEN);

    string via(msg->local_socket->get_advertised_ip());
    if(msg->local_socket->get_port() != 5060)
	via += ":" + int2str(msg->local_socket->get_port());

    cstring trsp(msg->local_socket->get_transport());

    // patch Contact-HF transport parameter
    vector<string> contact_buffers(msg->contacts.size());
    vector<string>::iterator contact_buf_it = contact_buffers.begin();
    list<sip_header*> n_contacts;

    //TODO: patch copies of the Contact-HF instead of the original HFs
    for(list<sip_header*>::iterator contact_it = msg->contacts.begin();
	contact_it != msg->contacts.end(); contact_it++, contact_buf_it++) {

	n_contacts.push_back(new sip_header(**contact_it));
	patch_contact_transport(n_contacts.back(),trsp,*contact_buf_it);
    }

    string content_len = int2str(msg->body.length());

    // Allocate new message
    p_msg = new sip_msg();
    string& buf = p_msg->buf; // for convenience

    // generate it
    request_line_wr(buf, msg->u.request->method_str,
 		    msg->u.request->ruri_str);

    via_wr(buf, trsp, stl2cstr(via), branch, true);
    copy_hdrs_wr(buf, msg->vias);
    copy_hdrs_wr_no_via_contact(buf, msg->hdrs);

    copy_hdrs_wr(buf, n_contacts);
    free_headers(n_contacts);

    content_length_wr(buf, content_len.c_str());

    buf += CR;
    buf += LF;

    buf += msg->body;

    // and parse it
    const char* err_msg=0;
    if(parse_sip_msg(*p_msg, err_msg)){
 	ERROR("Parser failed on generated request\n");
        ERROR("Message was: <%s>\n", buf.c_str());
 	delete p_msg;
 	p_msg = NULL;
 	return MALFORMED_SIP_MSG;
    }

    // copy msg->remote_ip
    memcpy(&p_msg->remote_ip,&msg->remote_ip,sizeof(sockaddr_storage));
    p_msg->local_socket = msg->local_socket;
 
    return 0;
}

int trans_layer::send_request(sip_msg& msg, trans_ticket& tt,
			       const string& dialog_id,
			       const cstring& _next_hop, 
			       int out_interface, unsigned int flags,
			       const shared_ptr<msg_logger>& logger)
{
    // Request-URI
    // To
    // From
    // Call-ID
    // CSeq
    // Max-Forwards
    // Via
    // Contact
    // Supported / Require
    // Content-Length / Content-Type

    int res=0;
    list<sip_destination> dest_list;
    if (_next_hop.len) {

	res = parse_next_hop(c2stlstr(_next_hop), dest_list);
	if(res || dest_list.empty()) {
	    DBG("parse_next_hop %.*s failed (%i)\n",
		_next_hop.len, _next_hop.s, res);
	    return res;
	}
    }
    else {
	sip_destination dest;
	if(set_next_hop(msg, dest.host, dest.port, dest.trsp) < 0){
	    DBG("set_next_hop failed\n");
	    return -1;
	}
	dest_list.push_back(dest);
    }

    unique_ptr<sip_target_set> targets(new sip_target_set());
    res = resolver::instance()->resolve_targets(dest_list,targets.get());
    if(res < 0){
	DBG("resolve_targets failed\n");
	return res;
    }

    targets->debug();
    targets->reset_iterator();

    string uri_buffer; // must have the same scope as 'msg'
    prepare_strict_routing(&msg, uri_buffer);

    if(!msg.u.request->ruri_str.len ||
       !msg.u.request->method_str.len) {

	ERROR("empty method name or R-URI");
	return -1;
    }
    else {
	DBG("send_request to R-URI <%.*s>",
	    msg.u.request->ruri_str.len,
	    msg.u.request->ruri_str.s);
    }

    int err = 0;
    string ruri; // buffer needs to be @ function scope
    string next_trsp;
    sip_msg* p_msg=NULL;

    tt._bucket = 0;
    tt._t = 0;

 try_next_dest:
    if(targets->get_next(msg.remote_ip, next_trsp, flags) < 0) {
	DBG("next_ip(): no more destinations! reply 500");
	sip_msg err;
	set_err_reply_from_req(&err, &msg, 500,
			       "No destination available");
	ua->handle_sip_reply(dialog_id, &err);
	return 0;
    }

    if(set_trsp_socket(msg, next_trsp, out_interface) < 0)
	return -1;

    if((flags & TR_FLAG_NEXT_HOP_RURI) &&
       (patch_ruri_with_remote_ip(ruri, &msg) < 0)) {
 	return -1;
    }

    // generate new msg and parse it
    err = generate_and_parse_new_msg(&msg, p_msg);
    if(err != 0) { return err; }

    DBG("Sending to %s:%i <%s...>\n",
	get_addr_str(&p_msg->remote_ip).c_str(),
	ntohs(((sockaddr_in*)&p_msg->remote_ip)->sin_port),
	p_msg->buf.c_str());

    tt._bucket = get_trans_bucket(p_msg->callid->value,
				   get_cseq(p_msg)->num_str);
    tt._bucket->lock();

    err = p_msg->send(flags);
    if(err < 0){
	ERROR("Error from transport layer\n");

	if(default_bl_ttl) {
	    tr_blacklist::instance()->insert(p_msg->remote_ip,
					     default_bl_ttl,"503");
	}

	delete p_msg;
	p_msg = NULL;

	tt._bucket->unlock();
	goto try_next_dest;
    }
    else {
        stats.inc_sent_requests();

	// save parsed method, as update_uac_request
	// might delete p_msg, and msg.u.request->method is not set
	int method = p_msg->u.request->method;

	DBG("update_uac_request tt->_t =%p\n", tt._t);
	err = update_uac_request(tt._bucket, tt._t,p_msg);
	if(err < 0){
	    DBG("Could not update UAC state for request\n");
	    delete p_msg;
	    tt._bucket->unlock();
	    return err;
	}

	if(tt._t && (method != sip_request::ACK)) {
	    // save flags & target set in transaction
	    tt._t->flags = flags;

	    if(tt._t->targets)	delete tt._t->targets;
	    tt._t->targets = targets.release();

	    if(tt._t->targets->has_next()){
		tt._t->reset_timer(STIMER_M, M_TIMER,
				    tt._bucket->get_id());
	    }
	}

	DBG("logger = %p\n",logger.get());

	if(logger) {
	    sockaddr_storage src_ip;
	    msg.local_socket->copy_addr_to(&src_ip);

	    cstring method_str = msg.u.request->method_str;
	    const char* msg_buffer=NULL;
	    unsigned int msg_len=0;

	    if(tt._t && (method == sip_request::ACK)) {
		// in case of ACK, p_msg gets deleted in update_uac_request
		msg_buffer = tt._t->retr_buf->c_str();
		msg_len = tt._t->retr_buf->length();
	    }
		/* p_msg could have been freed already by update_uac_request() */
	    else if (p_msg) {
		msg_buffer = p_msg->buf.c_str();
		msg_len = p_msg->buf.length();
	    }

	    logger->log(msg_buffer, msg_len,
			&src_ip, &msg.remote_ip,
			method_str);

	    if(tt._t && !tt._t->logger) {
		tt._t->logger = logger;
	    }
	}

	if (!dialog_id.empty() && tt._t && tt._t->dialog_id.empty()) {
            tt._t->dialog_id = dialog_id;
	}
    }

    tt._bucket->unlock();

    return err;
}

int trans_layer::cancel(trans_ticket* tt, const string& dialog_id,
			 unsigned int inv_cseq, const cstring& hdrs)
{
    assert(tt);
    assert(tt->_bucket && tt->_t);

    trans_bucket* bucket = tt->_bucket;
    sip_trans*    t = tt->_t;

    bucket->lock();
    if(!bucket->exist(t) || (t->state == TS_ABANDONED)){
	if (!dialog_id.empty())
	    t = bucket->find_uac_trans(dialog_id, inv_cseq);
	else
	    t = NULL;
    }

    if(!t){
	bucket->unlock();
	DBG("No transaction to cancel: wrong key or finally replied\n");
	return 0;
    }

    if(t->canceled) {
	DBG("Transaction has already been canceled\n");
	bucket->unlock();
	return 0;
    }

    sip_msg* req = t->msg;

    // RFC 3261 says: SHOULD NOT be sent for other request
    // than INVITE.
    if(req->u.request->method != sip_request::INVITE){
	t->dump();
	bucket->unlock();
	ERROR("Trying to cancel a non-INVITE request (we SHOULD NOT do that); inv_cseq: %u, i:%s\n",
	      inv_cseq, dialog_id.c_str());
	return -1;
    }

    switch(t->state){
    case TS_CALLING: {
	// Abandon canceled transaction
	t->clear_timer(STIMER_A);
	t->clear_timer(STIMER_M);
	t->flags |= TR_FLAG_DISABLE_BL;
	t->state = TS_ABANDONED;

	// Answer request internally to terminate the dialog...
	sip_msg reply;
	gen_error_reply_from_req(reply, t->msg, 487, "Request Terminated");
	string dlg_id = t->dialog_id;
	bucket->unlock();
	ua->handle_sip_reply(dlg_id, &reply);
	return 0;
    }

    case TS_COMPLETED:
	ERROR("Trying to cancel a request while in TS_COMPLETED state; inv_cseq: %u, i:%s\n",
	      inv_cseq, dialog_id.c_str());
	t->dump();
	bucket->unlock();
	return -1;

    case TS_PROCEEDING:
    case TS_ABANDONED:
	// continue with CANCEL request
	break;

    default:
	ERROR("Trying to cancel a request while in %s state; inv_cseq: %u, i:%s\n",
	      t->state_str(), inv_cseq, dialog_id.c_str());
	t->dump();
	bucket->unlock();
	return -1;
    }

    cstring cancel_str("CANCEL");
    cstring zero("0");

    // Allocate new message
    sip_msg* p_msg = new sip_msg();
    string& buf = p_msg->buf; // for convenience

    // generate it
    request_line_wr(buf, cancel_str,
		    req->u.request->ruri_str);

    copy_hdr_wr(buf, req->via1);
    copy_hdr_wr(buf, req->to);
    copy_hdr_wr(buf, req->from);
    copy_hdr_wr(buf, req->callid);
    cseq_wr(buf, get_cseq(req)->num_str,cancel_str);
    copy_hdrs_wr(buf, req->route);
    copy_hdrs_wr(buf, req->contacts);

    buf += c2stlstr(hdrs);

    content_length_wr(buf, zero);

    buf += CR;
    buf += LF;

    // and parse it
    const char* err_msg=0;
    if(parse_sip_msg(*p_msg, err_msg)){
	ERROR("Parser failed on generated request\n");
	ERROR("Message was: <%s>\n", buf.c_str());
	delete p_msg;
	bucket->unlock();
	return MALFORMED_SIP_MSG;
    }

    memcpy(&p_msg->remote_ip,&req->remote_ip,sizeof(sockaddr_storage));
    p_msg->local_socket = req->local_socket;

    DBG("Sending to %s:%i:\n<%s>\n",
	get_addr_str(&p_msg->remote_ip).c_str(),
	ntohs(((sockaddr_in*)&p_msg->remote_ip)->sin_port),
	p_msg->buf.c_str());

    int send_err = p_msg->send(t->flags);
    if(send_err < 0){
	ERROR("Error from transport layer\n");
	delete p_msg;
    }
    else {
        stats.inc_sent_requests(); // ?

	sip_trans* cancel_t=NULL;
	send_err = update_uac_request(bucket,cancel_t,p_msg);
	if(send_err<0){
	    DBG("Could not update state for UAC transaction\n");
	    delete p_msg;
	}
	else {
            t->canceled = true;

            if(t->logger) {
                sockaddr_storage src_ip;
                p_msg->local_socket->copy_addr_to(&src_ip);
                t->logger->log(p_msg->buf.c_str(), p_msg->buf.length(), &src_ip,
                               &p_msg->remote_ip,cancel_str);

                if(!cancel_t->logger) {
                    cancel_t->logger = t->logger;
                }
            }
	}
    }

    bucket->unlock();
    return send_err;
}


#define DROP_MSG \
          delete msg;\
          return

void trans_layer::received_msg(sip_msg* msg)
{
    const char* err_msg=0;
    int err = parse_sip_msg(*msg, err_msg);

    if(err){
	DBG("parse_sip_msg returned %i\n",err);

	if(!err_msg){
	    err_msg = (char*)"unknown parsing error";
	}

	DBG("parsing error: %s\n",err_msg);

	DBG("Message was: \"%s\"\n", msg->buf.c_str());

	if((err != MALFORMED_FLINE)
	   && (msg->type == SIP_REQUEST)
	   && (msg->u.request->method != sip_request::ACK)){

	    send_sl_reply(msg,400,cstring(err_msg),
			  cstring(),cstring());
	}

	DROP_MSG;
    }

    process_rcvd_msg(msg);
}

void trans_layer::process_rcvd_msg(sip_msg* msg)
{
    assert(msg->callid && get_cseq(msg));
    unsigned int h = hash(msg->callid->value, get_cseq(msg)->num_str);

    trans_bucket* bucket = get_trans_bucket(h);
    sip_trans* t = NULL;

    bucket->lock();

    int err=0;
    switch(msg->type){
    case SIP_REQUEST:
        stats.inc_received_requests();

	if((t = bucket->match_request(msg,TT_UAS)) != NULL){

	    if(t->logger) {
		t->logger->log(msg->buf.c_str(), msg->buf.length(), &msg->remote_ip,
			       &msg->local_ip,msg->u.request->method_str);
	    }

	    if(msg->u.request->method != t->msg->u.request->method){

		// ACK matched INVITE transaction
		DBG("ACK matched INVITE transaction %p\n", t);
		int t_state = t->state;
		err = update_uas_request(bucket,t,msg);
		DBG("update_uas_request(bucket,t=%p,msg) = %i\n",t, err);
		if(err<0){
		    DBG("trans_layer::update_uas_trans() failed!\n");
		    // Anyway, there is nothing we can do...
		}
		else {

		    // do not touch the transaction anymore:
		    // it could have been deleted !!!

		    // should we forward the ACK to SEMS-App upstream? Yes
		    bucket->unlock();

		    if(t_state == TS_TERMINATED_200) {
			//  let's pass the request to
			//  the UA, iff it was a 200-ACK
			assert(ua);
			DBG("Passing ACK to the UA.\n");
			ua->handle_sip_request(trans_ticket(), // dummy
					       msg);
		    }
		    else {
			DBG("Absorbing non-200-ACK\n");
		    }

		    DROP_MSG;
		}
	    }
	    else {
		DBG("Found retransmission\n");
		t->retransmit(); // retransmit reply
	    }
	}
	else {
             unsigned inv_h;
             trans_bucket* inv_bucket;
             sip_trans* inv_t;

             switch (msg->u.request->method) {
                 case sip_request::ACK:
                     // non-2xx ACK??? drop!
                     break;

                 case sip_request::PRACK:
                     bucket->unlock();
                     if (! msg->rack) {
                       send_sl_reply(msg, 400,
                           cstring("Missing valid RSeq header"),
                           cstring(),cstring());
                       DROP_MSG;
                     }
                     /* match INVITE transaction, cool off the 1xx timers */
                     inv_h = hash(msg->callid->value, get_rack(msg)->cseq_str);
                     inv_bucket = get_trans_bucket(inv_h);
                     inv_bucket->lock();
                     if((inv_t = inv_bucket->match_1xx_prack(msg)) != NULL) {
                         assert(msg->u.request->method !=
                             inv_t->msg->u.request->method);
                         err = update_uas_request(inv_bucket,inv_t,msg);
                         DBG("update_uas_request(bucket,t,msg) = %i\n",err);
                     }
                     inv_bucket->unlock();
                     bucket->lock();
                     // no break

                 default:
                     // New transaction
                     t = bucket->add_trans(msg, TT_UAS);

                     bucket->unlock();

                     //  let's pass the request to
                     //  the UA.
                     assert(ua);
                     ua->handle_sip_request(trans_ticket(t,bucket),msg);

                     // forget the msg: it will be
                     // owned by the new transaction
                     return;
             }
	}
	break;

    case SIP_REPLY:
        stats.inc_received_replies();

	if((t = bucket->match_reply(msg)) != NULL){

	    // Reply matched UAC transaction

	    DBG("Reply matched an existing transaction\n");

	    if(t->logger && msg->local_socket && !msg->buf.empty()) {
		t->logger->log(msg->buf.c_str(), msg->buf.length(), &msg->remote_ip,
			       &msg->local_ip,get_cseq(msg)->method_str,
			       msg->u.reply->code);
	    }

	    string dialog_id = t->dialog_id;
	    int res = update_uac_reply(bucket, t, msg);
	    if(res < 0){
		ERROR("update_uac_trans() failed, so what happens now???\n");
		break;
	    }
	    // do not touch the transaction anymore:
	    // it could have been deleted !!!
	    if (res) {
		bucket->unlock();
		ua->handle_sip_reply(dialog_id, msg);
		DROP_MSG;
		//return; - part of DROP_MSG
	    }
	}
	else {
	    DBG("Reply did NOT match any existing transaction...\n");
	    DBG("reply code = %i\n",msg->u.reply->code);
	    if( (msg->u.reply->code >= 200) &&
	        (msg->u.reply->code <  300) ) {

		bucket->unlock();

		// pass to UA
		//assert(ua);
		//ua->handle_sip_reply(msg);

		DROP_MSG;
	    }
	}
	break;

    default:
	ERROR("Got unknown message type: Bug?\n");
	break;
    }

    // unlock_drop:
    bucket->unlock();
    DROP_MSG;
}


int trans_layer::update_uac_reply(trans_bucket* bucket, sip_trans* t, sip_msg* msg)
{
    assert(msg->type == SIP_REPLY);

    string to_tag;
    int     reply_code = msg->u.reply->code;

    DBG("update_uac_reply(reply code = %i, trans=%p)\n",reply_code, t);

    if(reply_code < 200){

	// Provisional reply
	switch(t->state){

	case TS_CALLING:
	    t->reset_all_timers();
	    // fall through trap

	case TS_TRYING:
	    t->state = TS_PROCEEDING;
	    // fall through trap

	case TS_PROCEEDING:
	    if(t->msg->u.request->method != sip_request::CANCEL) {
		if(t->msg->u.request->method == sip_request::INVITE) {
		    t->reset_timer(STIMER_C, C_TIMER, bucket->get_id());
		}
		goto pass_reply;
	    }
	    else
		goto end;

	case TS_ABANDONED:
	    // disable blacklisting: remote UA did reply
	    DBG("disable blacklisting: remote UA (%s/%i) did reply",
		am_inet_ntop(&msg->remote_ip).c_str(),
		am_get_port(&msg->remote_ip));
	    t->flags |= TR_FLAG_DISABLE_BL;
	    bucket->unlock();
	    {
		// send CANCEL
		trans_ticket tt(t,bucket);
		cancel(&tt, "", 0, cstring());

		// Now remove the transaction
		bucket->lock();
		//bucket->remove(t);
	    }
	    goto end;

	case TS_TERMINATED:
	    // disable blacklisting: remote UA did reply
	    DBG("disable blacklisting: remote UA (%s/%i) did reply",
		am_inet_ntop(&msg->remote_ip).c_str(),
		am_get_port(&msg->remote_ip));
	    t->flags |= TR_FLAG_DISABLE_BL;
	    goto end;

	case TS_COMPLETED:
	default:
	    goto end;
	}
    }

    to_tag = c2stlstr(((sip_from_to*)msg->to->p)->tag);
    // if((t->msg->u.request->method == sip_request::INVITE) &&
    //    (reply_code < 300) &&
    //    !to_tag.len){
    // 	//if (!trans_layer::accept_fr_without_totag) {
    // 	ERROR("To-tag missing in final reply to INVITE"
    // 	      //" (see sems.conf: accept_fr_without_totag?)"
    // 	      );
    // 	return -1;
    // 	//}
    // }

    if(t->msg->u.request->method == sip_request::INVITE){

	if(reply_code >= 300){

	    bool forget_reply = false;
	    if(reply_code == 503 &&
	       (t->state == TS_CALLING ||
		t->state == TS_PROCEEDING)) {

		if(!(t->flags & TR_FLAG_DISABLE_BL)) {
		    tr_blacklist::instance()->insert(t->msg->remote_ip,
						     default_bl_ttl,"503");
		}

		if(msg->local_socket) { // remote reply
		    if(!try_next_ip(bucket,t,true))
			forget_reply = true;
		}
		else { // local reply
		    if(!try_next_ip(bucket,t,false))
			goto end;
		}
	    }

	    // Final error reply
	    switch(t->state){

	    case TS_CALLING:

		t->reset_all_timers();

	    case TS_PROCEEDING:

		t->clear_timer(STIMER_C);

		t->state = TS_COMPLETED;
		send_non_200_ack(msg,t);
		t->reset_timer(STIMER_D, D_TIMER, bucket->get_id());

		if(forget_reply)
		    goto end;

		goto pass_reply;

	    case TS_ABANDONED:
	    case TS_TERMINATED:
		// local reply: do not send an ACK in this case
		if(!msg->local_socket) {
		    t->reset_all_timers();
		    bucket->remove(t);
		    goto end;
		}

		// disable blacklisting: remote UA did reply
		DBG("disable blacklisting: remote UA (%s/%i) did reply",
		    am_inet_ntop(&msg->remote_ip).c_str(),
		    am_get_port(&msg->remote_ip));

		t->flags |= TR_FLAG_DISABLE_BL;
		// fall through trap

	    case TS_COMPLETED:
		// generate a new non-200 ACK
		send_non_200_ack(msg,t);
	    default:
		goto end;
	    }
	}
	else {

	    DBG("Positive final reply to INVITE transaction (state=%i)\n",t->state);

	    // Positive final reply to INVITE transaction
	    switch(t->state){

	    case TS_CALLING:
	    case TS_PROCEEDING: // first 2xx reply

		// TODO:
		//  we should take care of 200 ACK re-transmissions
		//    - on first reply:
		//      - save to-tag.
		//    - compare to-tag on subsequent replies.
		//      - (if different):
		//        - (generate new 200 ACK based on reply).
		//        - (send BYE (check for existing UAC trans)).
		//      - else:
		//        - re-transmit ACK.

		t->state  = TS_TERMINATED_200;
		t->reset_all_timers();

		// Timer B & C share the same slot,
		// so it would pretty useless to clear
		// the same timer slot another time ;-)
		//t->clear_timer(STIMER_C);

		t->reset_timer(STIMER_L, L_TIMER, bucket->get_id());

		if (t->to_tag.empty() && !to_tag.empty()) {
                        t->to_tag = to_tag;
		}

		goto pass_reply;

	    case TS_TERMINATED_200: // subsequent 2xx reply (no ACK sent)

		if (to_tag != t->to_tag) {

		    // TODO:
		    //   (this should be implemented in the UA)
		    //   we should also send a 200 ACK here,
		    //   but this would mean that we should
		    //   also be sending a BYE to quit
		    //   this dialog...
		    //
		    DBG("Received 200 reply with different To-tag as the previous one.\n");
		    goto end;
		}

		DBG("Received 200 reply retransmission\n");
		t->retransmit();
		goto end;

	    case TS_ABANDONED:
	    case TS_TERMINATED:
		//TODO: send ACK+BYE
		DBG("disable blacklisting: remote UA (%s/%i) did reply",
		    am_inet_ntop(&msg->remote_ip).c_str(),
		    am_get_port(&msg->remote_ip));
		t->flags |= TR_FLAG_DISABLE_BL;
		goto end;

	    default:
		goto end;
	    }
	}
    }
    else { // non-INVITE transaction

	if(reply_code == 503) {
	    if(default_bl_ttl) {
		tr_blacklist::instance()->insert(t->msg->remote_ip,
						 default_bl_ttl,"503");
	    }
	    if(!try_next_ip(bucket,t,false))
		goto end;
	}

	// Final reply
	switch(t->state){

	case TS_TRYING:
	case TS_CALLING:
	case TS_PROCEEDING:

	    t->state = TS_COMPLETED;

	    t->clear_timer(STIMER_E);
	    t->clear_timer(STIMER_M);
	    t->clear_timer(STIMER_F);

	    {
		int t_method = t->msg->u.request->method;
		if(msg->local_socket &&
		   !msg->local_socket->is_reliable())
		    t->reset_timer(STIMER_K, K_TIMER, bucket->get_id());
		else {
		    bucket->remove(t);
		}

		// ??? we don't pass CANCEL replies to UA layer ???
		if(t_method != sip_request::CANCEL)
		    goto pass_reply;
		else
		    goto end;
	    }

	case TS_COMPLETED:
	    // Absorb reply retransmission (only if UDP)
	    goto end;

	case TS_ABANDONED:
	case TS_TERMINATED:
	    //local reply
	    if(!msg->local_socket) {
		if(reply_code == 500 || reply_code == 503) {
		    // no more replies will come...
		    bucket->remove(t);
		}
		goto end;
	    }

	    INFO("disable blacklisting: remote UA (%s/%i) did reply",
		 am_inet_ntop(&msg->remote_ip).c_str(),
		 am_get_port(&msg->remote_ip));
	    t->flags |= TR_FLAG_DISABLE_BL;
	    goto end;

	default:
	    goto end;
	}
    }

 pass_reply:
    return 1;
 end:
    return 0;
}

int trans_layer::update_uac_request(trans_bucket* bucket, sip_trans*& t,
				     sip_msg*& msg)
{
    if(msg->u.request->method != sip_request::ACK){
	t = bucket->add_trans(msg,TT_UAC);

	DBG("update_uac_request(t=%p)\n", t);
    }
    else {
	// 200 ACK
	if(!msg->local_socket->is_reliable()) {
	    t = bucket->match_request(msg,TT_UAC);
	    if(t == NULL){
		WARN("While sending 200 ACK: no matching transaction\n");
		return -1;
	    }
	    DBG("update_uac_request(200 ACK, t=%p)\n", t);
	    // clear old retransmission buffer
	    // and
	    // transfer the message buffer 
	    // to the transaction (incl. ownership)
	    t->retr_buf.reset(new string(std::move(msg->buf)));
	    msg->buf.clear();

	    // copy destination address
	    memcpy(&t->retr_addr,&msg->remote_ip,sizeof(sockaddr_storage));
	    t->retr_socket = msg->local_socket;

	    // remove the message;

	    delete msg;
	    msg = NULL;
	}

	return 0;
    }

    DBG("transport = '%s'; is_reliable = %i",
	msg->local_socket->get_transport(),
	msg->local_socket->is_reliable());

    switch(msg->u.request->method){

    case sip_request::INVITE:
	if(!msg->local_socket->is_reliable()) {
	    // if transport == UDP
	    t->reset_timer(STIMER_A,A_TIMER,bucket->get_id());
	}

	// for any transport type
	t->reset_timer(STIMER_B,B_TIMER,bucket->get_id());
	break;

    default:
	if(!msg->local_socket->is_reliable()) {
	    // if transport == UDP
	    t->reset_timer(STIMER_E,E_TIMER,bucket->get_id());
	}

	// for any transport type
	t->reset_timer(STIMER_F,F_TIMER,bucket->get_id());
	break;
    }

    return 0;
}

int trans_layer::update_uas_reply(trans_bucket* bucket, sip_trans* t, int reply_code)
{
    DBG("update_uas_reply(t=%p)\n", t);

    t->reply_status = reply_code;
    bool reliable_trsp = t->retr_socket->is_reliable();

    if(t->reply_status >= 300){

	// error reply
	t->state = TS_COMPLETED;

	if(t->msg->u.request->method == sip_request::INVITE){

	    if(!reliable_trsp)
		t->reset_timer(STIMER_G,G_TIMER,bucket->get_id());

	    t->reset_timer(STIMER_H,H_TIMER,bucket->get_id());
	}
	else if(!reliable_trsp) {
	    // 64*T1_TIMER if UDP / 0 if !UDP
	    t->reset_timer(STIMER_J,J_TIMER,bucket->get_id());
	}
	else {
	    bucket->remove(t);
	    return TS_REMOVED;
	}
    }
    else if(t->reply_status >= 200) {

	if(t->msg->u.request->method == sip_request::INVITE){

	    // final reply
	    //
	    // In this stack, the transaction layer
	    // takes care of re-transmiting the 200 reply
	    // in a UAS INVITE transaction. The code above
	    // is commented out and shows the behavior as
	    // required by the RFC.
	    //
	    t->state = TS_TERMINATED_200;

	    if(!reliable_trsp)
		t->reset_timer(STIMER_G,G_TIMER,bucket->get_id());

	    t->reset_timer(STIMER_H,H_TIMER,bucket->get_id());
	}
	else if(!reliable_trsp) {
	    // Only for unreliable transports.
	    t->state = TS_COMPLETED;
	    t->reset_timer(STIMER_J,J_TIMER,bucket->get_id());
	}
	else {
	    // reliable transports
	    bucket->remove(t);
	    return TS_REMOVED;
	}
    }
    else {
	// provisional reply
        if (t->last_rseq) {
            t->state = TS_PROCEEDING_REL;
            // see above notes, for 2xx replies; the same applies for
            // 1xx/PRACK

	    if(!reliable_trsp)
		t->reset_timer(STIMER_G,G_TIMER,bucket->get_id());

	    t->reset_timer(STIMER_H,H_TIMER,bucket->get_id());
        } else {
	    t->state = TS_PROCEEDING;
        }
    }

    return t->state;
}

int trans_layer::update_uas_request(trans_bucket* bucket, sip_trans* t, sip_msg* msg)
{
    DBG("update_uas_request(t=%p)\n", t);
    int method = msg->u.request->method;

    if(method != sip_request::ACK &&
       method != sip_request::PRACK) {

	ERROR("Bug? Recvd non PR-/ACK request for existing UAS transact.!?\n");
	return -1;
    }

    switch(t->state){

    case TS_PROCEEDING:
	// ACK or PRACK after non-reliable 1xx???
	return -1;

    case TS_PROCEEDING_REL:
	if(method == sip_request::PRACK) {
	    // stop retransmissions
	    t->clear_timer(STIMER_G);
	    t->clear_timer(STIMER_H);
	}
        return t->state;

    case TS_COMPLETED: // non-2xx-ACK
	if(method != sip_request::ACK) return -1;
	t->state = TS_CONFIRMED;

	t->clear_timer(STIMER_G);
	t->clear_timer(STIMER_H);

	if(msg->local_socket->is_reliable()){
	    bucket->remove(t);
	    return TS_REMOVED;
	}

	t->reset_timer(STIMER_I,I_TIMER,bucket->get_id());

	// drop through
    case TS_CONFIRMED: // non-2xx-ACK re-transmission
	return t->state;

    case TS_TERMINATED_200: // 2xx-ACK
	if(method != sip_request::ACK) return -1;
	// remove transaction
	bucket->remove(t);
	return TS_REMOVED;

    default:
	DBG("Bug? Unknown state at this point: %i\n",t->state);
	break;
    }

    return -1;
}

void trans_layer::send_non_200_ack(sip_msg* reply, sip_trans* t)
{
    sip_msg* inv = t->msg;

    static constexpr string method("ACK");

    cstring content_len("0");

    string ack_buf;

    request_line_wr(ack_buf, stl2cstr(method), inv->u.request->ruri_str);

    copy_hdr_wr(ack_buf, inv->via1);

    copy_hdr_wr(ack_buf, inv->from);
    copy_hdr_wr(ack_buf, reply->to);
    copy_hdr_wr(ack_buf, inv->callid);

    cseq_wr(ack_buf, get_cseq(inv)->num_str, stl2cstr(method));

    if(!inv->route.empty())
	 copy_hdrs_wr(ack_buf, inv->route);

    content_length_wr(ack_buf, "0");

    ack_buf += CR;
    ack_buf += LF;

    DBG("About to send ACK\n");

    assert(inv->local_socket);
    int send_err = inv->local_socket->send(&inv->remote_ip, ack_buf.c_str(),
					   ack_buf.length(), t->flags);
    if(send_err < 0){
	ERROR("Error from transport layer\n");
    }
    else stats.inc_sent_requests();

    if(t->logger) {
	sockaddr_storage src_ip;
	inv->local_socket->copy_addr_to(&src_ip);
	t->logger->log(ack_buf.c_str(), ack_buf.length(), &src_ip, &inv->remote_ip, stl2cstr(method));
    }
}

void trans_layer::timer_expired(trans_timer* t, trans_bucket* bucket,
				 sip_trans* tr)
{
    int n = t->type >> 16;
    int type = t->type & 0xFFFF;

    switch(type){

    case STIMER_A:  // Calling: (re-)send INV

	n++;
	tr->msg->send(tr->flags);
        stats.inc_sent_request_retrans();

	if(tr->logger) {
	    sockaddr_storage src_ip;
	    tr->msg->local_socket->copy_addr_to(&src_ip);
	    tr->logger->log(tr->msg->buf.c_str(), tr->msg->buf.length(), &src_ip,&tr->msg->remote_ip,
			    tr->msg->u.request->method_str);
	}

	tr->reset_timer((n<<16) | type, A_TIMER<<n, bucket->get_id());
	break;

    case STIMER_B:  // Calling: -> Terminated

	tr->clear_timer(STIMER_B);
	if(tr->state == TS_CALLING) {
	    DBG("Transaction timeout!\n");
	    // unlocks the bucket
	    timeout(bucket,tr);
	    return;
	}
	else if(tr->state == TS_ABANDONED) {
	    if(tr->flags & TR_FLAG_DISABLE_BL) {
		bucket->remove(tr);
	    }
	    else {
		// set blacklist timer
		tr->reset_timer(STIMER_BL,BL_TIMER,bucket->get_id());
	    }
	}
	else {
	    DBG("Transaction timeout timer hit while state=%s (0x%x)",
		tr->state_str(), tr->state);
	    bucket->remove(tr);
	}
	break;

    case STIMER_C: // Proceeding -> Terminated

	// Note: remember well, we first set timer C
	//       after the first provisional reply.
	tr->clear_timer(STIMER_C);
	//if(tr->state != TS_PROCEEDING)
	//  break; // shouldn't happen
	bucket->unlock();

	{
	    // send CANCEL
	    trans_ticket tt(tr,bucket);
	    cancel(&tt, "", 0, cstring());

	    // Now remove the transaction
	    bucket->lock();
	    if(bucket->exist(tr)) {
		// unlocks the bucket
		timeout(bucket,tr);
		return;
	    }
	}
	break;

    case STIMER_F:  // Trying/Proceeding: terminate transaction

	tr->clear_timer(STIMER_F);

	switch(tr->state) {

	case TS_TRYING:
	case TS_PROCEEDING:
	    DBG("Transaction timeout!\n");
	    // unlocks the bucket
	    timeout(bucket,tr);
	    return;
	case TS_ABANDONED:
	    if(tr->flags & TR_FLAG_DISABLE_BL) {
		bucket->remove(tr);
	    }
	    else {
		// set blacklist timer
		tr->reset_timer(STIMER_BL,BL_TIMER,bucket->get_id());
	    }
	    break;
	default:
	    DBG("Transaction timeout timer hit while state=%s (0x%x)",
		tr->state_str(), tr->state);
	    bucket->remove(tr);
	}
	break;

    case STIMER_H:  // PENDING_REL, Completed: -> Terminated
        if(tr->type == TT_UAS) { // TODO: can timer _H fire for TT_UAC??
          bool handled;

          switch(tr->state) {
          case TS_PROCEEDING_REL: // missing PRACK for rel-1xx
            assert(tr->retr_buf);
            tr->clear_timer(type); // stop retransmissions
            //signal timeout to UA
            ua->handle_reply_timeout(AmSipTimeoutEvent::noPRACK, tr, bucket);
                //tr->msg, tr->retr_buf, tr->retr_len); //signal timeout to UA
            handled = true; // let the UA [3..6]xx, if it wishes so
            break;

          case TS_TERMINATED_200: // missing ACK for (locally replied) 200
          case TS_COMPLETED: // missing ACK for (locally replied) [3..6]xx
            tr->clear_timer(type);
            ////ua->timer_expired(tr,STIMER_H);
            ua->handle_reply_timeout(AmSipTimeoutEvent::noACK, tr);
            bucket->remove(tr);
            handled = true;
            break;

          default:
            handled = false;
          }

          if (handled)
            break;
        }

    case STIMER_D:  // Completed: -> Terminated
    case STIMER_K:  // Completed: terminate transaction
    case STIMER_J:  // Completed: -> Terminated
    case STIMER_I:  // Confirmed: -> Terminated
    case STIMER_L:  // Terminated_200 -> Terminated

	// TODO:
	//  - check if the UA has sent the ACK.
	//    else, send ACK & BYE.

	tr->clear_timer(type);
	bucket->remove(tr);
	break;

    case STIMER_E:  // Trying/Proceeding: (re-)send request
    case STIMER_G:  { // Completed: (re-)send response

	n++; // re-transmission counter

	//
	// in this stack, the transaction layer
	// takes care of re-transmiting the 200 reply
	// in a UAS INVITE transaction.
	//
	if(tr->type == TT_UAS){

	    // re-transmit reply to INV
	    tr->retransmit();
            stats.inc_sent_reply_retrans();
	}
	else {

	    // re-transmit request
	    tr->msg->send(tr->flags);
            stats.inc_sent_request_retrans();

	    if(tr->logger) {
		sockaddr_storage src_ip;
		tr->msg->local_socket->copy_addr_to(&src_ip);
		tr->logger->log(tr->msg->buf.c_str(), tr->msg->buf.length(),
				&src_ip,&tr->msg->remote_ip,
				tr->msg->u.request->method_str);
	    }
	}

	unsigned int retr_timer = (type == STIMER_E) ?
	    E_TIMER << n : G_TIMER << n;

	if(retr_timer<<n > T2_TIMER) {
	    tr->reset_timer((n<<16) | type, T2_TIMER, bucket->get_id());
	}
	else {
	    tr->reset_timer((n<<16) | type, retr_timer, bucket->get_id());
	}
    } break;

    case STIMER_M: {
	if(!try_next_ip(bucket,tr,true)) {
	    // Abandon old transaction
	    tr->clear_timer(STIMER_A);
	    tr->state = TS_ABANDONED;
	}
    } break;

    case STIMER_BL:
	tr->clear_timer(STIMER_BL);
	if(!(tr->flags & TR_FLAG_DISABLE_BL)) {
	    // insert destination to blacklist
	    if(default_bl_ttl) {
		tr_blacklist::instance()->insert(tr->msg->remote_ip,
						 default_bl_ttl,
						 "timeout");
	    }
	}
	bucket->remove(tr);
	break;

    default:
	ERROR("Invalid timer type %i\n",type);
	break;
    }

    bucket->unlock();
}

/**
 * Tries to find an interface suitable for
 * sending to the destination supplied.
 */
int trans_layer::find_outbound_if(sockaddr_storage* remote_ip)
{
    if(transports.size() == 0)
	return 0;

    if(transports.size() == 1)
	return 0;

    int temp_sock = socket(remote_ip->ss_family, SOCK_DGRAM, 0 );
    if (temp_sock == -1) {
	ERROR( "ERROR: socket() failed: %s\n",
	       strerror(errno));
	return 0;
    }

    sockaddr_storage from;
    socklen_t    len=sizeof(from);
    trsp_socket* tsock=NULL;

    if (connect(temp_sock, (sockaddr*)remote_ip,
		remote_ip->ss_family == AF_INET ?
		sizeof(sockaddr_in) : sizeof(sockaddr_in6))==-1) {

	ERROR("connect failed: %s\n",
	      strerror(errno));
	goto error;
    }

    if (getsockname(temp_sock, (sockaddr*)&from, &len)==-1) {
	ERROR("getsockname failed: %s\n",
	      strerror(errno));
	goto error;
    }
    close(temp_sock);

    // try with alternative address
    char local_ip[NI_MAXHOST];
    if(am_inet_ntop(&from,local_ip,NI_MAXHOST) != NULL) {
	map<string,unsigned short>::iterator if_it =
	    AmConfig::LocalSIPIP2If.find(local_ip);
	if(if_it == AmConfig::LocalSIPIP2If.end()){
	    ERROR("Could not find a local interface for "
		  "resolved local IP (local_ip='%s')",
		  local_ip);
	}
	else {
	    return if_it->second;
	}
    }

    // no matching interface
    return -1;

 error:
    close(temp_sock);
    return 0;
}

sip_trans* trans_layer::copy_uac_trans(sip_trans* tr)
{
    assert(tr && (tr->type == TT_UAC));
    sip_trans* n_tr = new sip_trans();

    n_tr->type  = tr->type;
    n_tr->flags = tr->flags;

    n_tr->dialog_id = tr->dialog_id;

    if(tr->logger) {
	n_tr->logger = tr->logger;
    }

    return n_tr;
}

int trans_layer::try_next_ip(trans_bucket* bucket, sip_trans* tr,
			      bool use_new_trans)
{
    tr->clear_timer(STIMER_M);

    string next_trsp;
    sockaddr_storage sa;

 try_next_dest:
    // get the next ip
    if(!tr->targets ||
       tr->targets->get_next(sa, next_trsp, tr->flags) < 0){
	DBG("no more destinations!");
	return -1;
    }

    if(use_new_trans) {
	string   n_uri;
	cstring  old_uri;
	unique_ptr<sip_trans> n_tr(copy_uac_trans(tr));

	// Warning: no deep copy!!!
	//  -> do not forget to release() before it's too late!
	sip_msg tmp_msg(*tr->msg);

	// remove last Via-HF
	tmp_msg.vias.pop_front();

	// copy the new address back
	memcpy(&tmp_msg.remote_ip,&sa,sizeof(sockaddr_storage));

	// backup R-URI before possible update
	old_uri = tr->msg->u.request->ruri_str;

	int out_interface = tmp_msg.local_socket->get_if();
	tmp_msg.local_socket = NULL;
	if(set_trsp_socket(tmp_msg, next_trsp, out_interface) < 0)
	    return -1;

	if(n_tr->flags & TR_FLAG_NEXT_HOP_RURI) {
	    // patch R-URI, generate& parse new message
	    if(patch_ruri_with_remote_ip(n_uri, &tmp_msg)) {
		ERROR("could not patch R-URI with new destination");
		tmp_msg.release();
		return -1;
	    }
	}

	sip_msg* p_msg=NULL;
	if(generate_and_parse_new_msg(&tmp_msg,p_msg)) {
	    ERROR("could not generate&parse new message");
	    tmp_msg.release();
	    return -1;
	}

	tmp_msg.release();
	n_tr->msg = p_msg;

	// take over target set
	n_tr->targets = tr->targets;
	tr->targets = NULL;

	// restore old R-URI
	tr->msg->u.request->ruri_str = old_uri;

	trans_timer* t_bf = tr->get_timer(STIMER_B);
	tr = n_tr.release();

	// keep old timer B/F
	if(t_bf) {
	    t_bf = new trans_timer(*t_bf,bucket->get_id(),tr);
	    tr->reset_timer(t_bf,t_bf->type);
	}

	bucket->append(tr);
    }
    else {
	// copy the new address back
	memcpy(&tr->msg->remote_ip,&sa,sizeof(sockaddr_storage));

	auto old_sock = tr->msg->local_socket;
	int out_interface = old_sock->get_if();
	if(set_trsp_socket(*tr->msg, next_trsp, out_interface) < 0)
	    return -1;

	if(tr->flags & TR_FLAG_NEXT_HOP_RURI) {
	    string   n_uri;
	    sip_msg* p_msg=NULL;

	    // patch R-URI, generate& parse new message
	    if(patch_ruri_with_remote_ip(n_uri, tr->msg) ||
	       generate_and_parse_new_msg(tr->msg,p_msg)) {
		ERROR("could not patch R-URI with new destination");
		return -1;
	    }

	    delete tr->msg;
	    tr->msg = p_msg;
	}
	else if(old_sock != tr->msg->local_socket) {
	    string   n_uri;
	    sip_msg* p_msg=NULL;

	    // patch R-URI, generate & parse new message
	    if(generate_and_parse_new_msg(tr->msg,p_msg)) {
		return -1;
	    }

	    delete tr->msg;
	    tr->msg = p_msg;
	}
	else {
	    // only create new branch tag
	    // -> patched directly in the msg's buffer
	    compute_branch((char*)(tr->msg->via_p1->branch.s+MAGIC_BRANCH_LEN),
			   tr->msg->callid->value,tr->msg->cseq->value);
	}
    }


    // and re-send
    if(tr->msg->send(tr->flags) < 0) {
	ERROR("Error from transport layer\n");

	if(default_bl_ttl) {
	    tr_blacklist::instance()->insert(tr->msg->remote_ip,
					     default_bl_ttl,"503");
	}

	use_new_trans = false;
	goto try_next_dest;
    }

    stats.inc_sent_requests();

    if(tr->logger) {
	sockaddr_storage src_ip;
	tr->msg->local_socket->copy_addr_to(&src_ip);
	tr->logger->log(tr->msg->buf.c_str(), tr->msg->buf.length(),
			&src_ip,&tr->msg->remote_ip,
			tr->msg->u.request->method_str);
    }

    if(tr->msg->u.request->method == sip_request::INVITE) {
	tr->state = TS_CALLING;
	if(!tr->msg->local_socket->is_reliable()) {
	    tr->reset_timer(STIMER_A,A_TIMER,bucket->get_id());
	}
	if(!tr->get_timer(STIMER_B)) {
	    tr->reset_timer(STIMER_B,B_TIMER,bucket->get_id());
	}
    }
    else {
	tr->state = TS_TRYING;
	if(!tr->msg->local_socket->is_reliable()) {
	    tr->reset_timer(STIMER_E,E_TIMER,bucket->get_id());
	}
	if(!tr->get_timer(STIMER_F)) {
	    tr->reset_timer(STIMER_F,F_TIMER,bucket->get_id());
	}
    }

    if(tr->targets->has_next())
	tr->reset_timer(STIMER_M,M_TIMER,bucket->get_id());

    return 0;
}

void trans_ticket::lock_bucket() const
{
    _bucket->lock();
}

void trans_ticket::unlock_bucket() const
{
    _bucket->unlock();
}

const sip_trans* trans_ticket::get_trans() const
{
    if(_bucket->exist(_t))
	return _t;
    else
	return NULL;
}

void trans_ticket::remove_trans()
{
    _bucket->remove(_t);
}

/** EMACS **
 * Local variables:
 * mode: c++
 * c-basic-offset: 4
 * End:
 */
