You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sems/core/plug-in/sipctrl/hash_table.cpp

495 lines
11 KiB

/*
* $Id$
*
* 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
*
* For a license to use the ser 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 "hash_table.h"
#include "hash.h"
#include "sip_parser.h"
#include "parse_header.h"
#include "parse_cseq.h"
#include "parse_via.h"
#include "parse_from_to.h"
#include "sip_trans.h"
#include "log.h"
#include <sys/time.h>
#include <time.h>
#include <assert.h>
//
// Global transaction table
//
trans_bucket _trans_table[H_TABLE_ENTRIES];
trans_bucket::trans_bucket()
{
id = (unsigned long)(trans_bucket*)(this - _trans_table);
pthread_mutex_init(&m,NULL);
}
trans_bucket::~trans_bucket()
{
pthread_mutex_destroy(&m);
}
void trans_bucket::lock()
{
pthread_mutex_lock(&m);
}
void trans_bucket::unlock()
{
pthread_mutex_unlock(&m);
}
sip_trans* trans_bucket::match_request(sip_msg* msg)
{
// assert(msg && msg->cseq && msg->callid);
// sip_cseq* cseq = dynamic_cast<sip_cseq*>(msg->cseq->p);
// assert(cseq);
DBG("Matching %.*s request\n",
msg->u.request->method_str.len,
msg->u.request->method_str.s);
//this should have been checked before
assert(msg->via_p1);
if(elmts.empty())
return NULL;
bool do_3261_match = false;
sip_trans* t = NULL;
// Try first RFC 3261 matching
if(msg->via_p1->branch.len > MAGIC_BRANCH_LEN){
do_3261_match = !memcmp(msg->via_p1->branch.s,
MAGIC_BRANCH_COOKIE,
MAGIC_BRANCH_LEN);
}
DBG("do_3261_match = %i\n",do_3261_match);
if(do_3261_match){
const char* branch = msg->via_p1->branch.s + MAGIC_BRANCH_LEN;
int len = msg->via_p1->branch.len - MAGIC_BRANCH_LEN;
trans_list::iterator it = elmts.begin();
for(;it!=elmts.end();++it) {
if( ((*it)->type != TT_UAS) ||
((*it)->msg->type != SIP_REQUEST)){
continue;
}
if(msg->u.request->method != (*it)->msg->u.request->method) {
if( (msg->u.request->method == sip_request::ACK) &&
((*it)->msg->u.request->method == sip_request::INVITE) ) {
t = match_200_ack(*it,msg);
if(t){
break;
}
}
continue;
}
if((*it)->msg->via_p1->branch.len != len + MAGIC_BRANCH_LEN)
continue;
if((*it)->msg->via_p1->host.len !=
msg->via_p1->host.len)
continue;
if((*it)->msg->via_p1->port.len !=
msg->via_p1->port.len)
continue;
if(memcmp(branch,
(*it)->msg->via_p1->branch.s+MAGIC_BRANCH_LEN,len))
continue;
if(memcmp((*it)->msg->via_p1->host.s,
msg->via_p1->host.s,msg->via_p1->host.len))
continue;
if(memcmp((*it)->msg->via_p1->port.s,
msg->via_p1->port.s,msg->via_p1->port.len))
continue;
// found matching transaction
t = *it;
break;
}
}
else {
// Pre-3261 matching
sip_from_to* from = dynamic_cast<sip_from_to*>(msg->from->p);
sip_from_to* to = dynamic_cast<sip_from_to*>(msg->to->p);
sip_cseq* cseq = dynamic_cast<sip_cseq*>(msg->cseq->p);
assert(from && to && cseq);
trans_list::iterator it = elmts.begin();
for(;it!=elmts.end();++it) {
//Request matching:
// Request-URI
// From-tag
// Call-ID
// Cseq
// top Via
// To-tag
//ACK matching:
// Request-URI
// From-tag
// Call-ID
// Cseq (number only)
// top Via
// + To-tag of reply
if( ((*it)->type != TT_UAS) ||
((*it)->msg->type != SIP_REQUEST))
continue;
if( (msg->u.request->method != (*it)->msg->u.request->method) &&
( (msg->u.request->method != sip_request::ACK) ||
((*it)->msg->u.request->method != sip_request::INVITE) ) )
continue;
sip_from_to* it_from = dynamic_cast<sip_from_to*>((*it)->msg->from->p);
if(from->tag.len != it_from->tag.len)
continue;
sip_cseq* it_cseq = dynamic_cast<sip_cseq*>((*it)->msg->cseq->p);
if(cseq->num_str.len != it_cseq->num_str.len)
continue;
if(memcmp(from->tag.s,it_from->tag.s,from->tag.len))
continue;
if(memcmp(cseq->num_str.s,it_cseq->num_str.s,cseq->num_str.len))
continue;
if(msg->u.request->method == sip_request::ACK){
// ACKs must include To-tag from previous reply
if(to->tag.len != (*it)->to_tag.len)
continue;
if(memcmp(to->tag.s,(*it)->to_tag.s,to->tag.len))
continue;
if((*it)->reply_status < 300){
// 2xx ACK matching
// TODO: additional work for dialog matching???
// R-URI should match reply Contact ...
// Anyway, we don't keep the contact from reply.
t = *it;
break;
}
}
else {
// non-ACK
sip_from_to* it_to = dynamic_cast<sip_from_to*>((*it)->msg->to->p);
if(to->tag.len != it_to->tag.len)
continue;
if(memcmp(to->tag.s,it_to->tag.s,to->tag.len))
continue;
}
// non-ACK and non-2xx ACK matching
if((*it)->msg->u.request->ruri_str.len !=
msg->u.request->ruri_str.len )
continue;
if(memcmp(msg->u.request->ruri_str.s,
(*it)->msg->u.request->ruri_str.s,
msg->u.request->ruri_str.len))
continue;
//TODO: missing top-Via matching
// found matching transaction
t = *it;
break;
}
}
return t;
}
sip_trans* trans_bucket::match_reply(sip_msg* msg)
{
if(elmts.empty())
return NULL;
assert(msg->via_p1);
if(msg->via_p1->branch.len <= MAGIC_BRANCH_LEN){
// this cannot match...
return NULL;
}
sip_trans* t = NULL;
const char* branch = msg->via_p1->branch.s + MAGIC_BRANCH_LEN;
int len = msg->via_p1->branch.len - MAGIC_BRANCH_LEN;
assert(get_cseq(msg));
trans_list::iterator it = elmts.begin();
for(;it!=elmts.end();++it) {
if((*it)->type != TT_UAC){
continue;
}
if((*it)->msg->via_p1->branch.len != msg->via_p1->branch.len)
continue;
if(get_cseq((*it)->msg)->num_str.len != get_cseq(msg)->num_str.len)
continue;
if(get_cseq((*it)->msg)->method_str.len != get_cseq(msg)->method_str.len)
continue;
if(memcmp(msg->via_p1->branch.s+MAGIC_BRANCH_LEN,
branch,len))
continue;
if(memcmp(get_cseq((*it)->msg)->num_str.s,get_cseq(msg)->num_str.s,
get_cseq(msg)->num_str.len))
continue;
if(memcmp(get_cseq((*it)->msg)->method_str.s,get_cseq(msg)->method_str.s,
get_cseq(msg)->method_str.len))
continue;
// found matching transaction
t = *it;
break;
}
return t;
}
sip_trans* trans_bucket::match_200_ack(sip_trans* t, sip_msg* msg)
{
sip_from_to* from = dynamic_cast<sip_from_to*>(msg->from->p);
sip_from_to* to = dynamic_cast<sip_from_to*>(msg->to->p);
sip_cseq* cseq = dynamic_cast<sip_cseq*>(msg->cseq->p);
assert(from && to && cseq);
sip_from_to* t_from = dynamic_cast<sip_from_to*>(t->msg->from->p);
if(from->tag.len != t_from->tag.len)
return NULL;
sip_cseq* t_cseq = dynamic_cast<sip_cseq*>(t->msg->cseq->p);
if(cseq->num != t_cseq->num)
return NULL;
if(msg->callid->value.len != t->msg->callid->value.len)
return NULL;
if(to->tag.len != t->to_tag.len)
return NULL;
if(memcmp(from->tag.s,t_from->tag.s,from->tag.len))
return NULL;
if(memcmp(msg->callid->value.s,t->msg->callid->value.s,
msg->callid->value.len))
return NULL;
if(memcmp(to->tag.s,t->to_tag.s,to->tag.len))
return NULL;
return t;
}
sip_trans* trans_bucket::add_trans(sip_msg* msg, int ttype)
{
sip_trans* t = new sip_trans();
t->msg = msg;
t->type = ttype;
t->reply_status = 0;
assert(msg->type == SIP_REQUEST);
if(msg->u.request->method == sip_request::INVITE){
if(t->type == TT_UAS)
t->state = TS_PROCEEDING;
else
t->state = TS_CALLING;
}
else {
t->state = TS_TRYING;
}
elmts.push_back(t);
return t;
}
trans_bucket::trans_list::iterator trans_bucket::find_trans(sip_trans* t)
{
trans_list::iterator it = elmts.begin();
for(;it!=elmts.end();++it)
if(*it == t)
break;
return it;
}
bool trans_bucket::exist(sip_trans* t)
{
return find_trans(t) != elmts.end();
}
void trans_bucket::remove_trans(sip_trans* t)
{
trans_list::iterator it = find_trans(t);
if(it != elmts.end()){
elmts.erase(it);
delete t;
DBG("~sip_trans()\n");
}
}
unsigned int hash(const cstring& ci, const cstring& cs)
{
unsigned int h=0;
h = hashlittle(ci.s,ci.len,h);
h = hashlittle(cs.s,cs.len,h);
return h & (H_TABLE_ENTRIES-1);
}
char _branch_lookup[] = {
'a','b','c','d','e','f','g','h',
'i','j','k','l','m','n','o','p',
'q','r','s','t','u','v','w','x',
'y','z','A','B','C','D','E','F',
'G','H','I','J','K','L','M','N',
'O','P','Q','R','S','T','U','V',
'W','X','Y','Z','0','1','2','3',
'4','5','6','7','8','9','.','~'
};
void compute_branch(char* branch, const cstring& callid, const cstring& cseq)
{
unsigned int h=0;
unsigned int h_tv=0;
timeval tv;
gettimeofday(&tv,NULL);
h = hashlittle(callid.s,callid.len,h);
h = hashlittle(cseq.s,cseq.len,h);
h_tv = tv.tv_sec + tv.tv_usec;
h += h_tv >> 16;
h_tv &= 0xFFFF;
branch[0] = _branch_lookup[h&0x3F];
branch[1] = _branch_lookup[(h >> 6)&0x3F];
branch[2] = _branch_lookup[(h >> 12)&0x3F];
branch[3] = _branch_lookup[(h >> 18)&0x3F];
branch[4] = _branch_lookup[(h >> 24)&0x3F];
branch[5] = _branch_lookup[(h >> 30)&((h_tv << 2) & 0x3F)];
branch[6] = _branch_lookup[(h_tv >> 4)&0x3F];
branch[7] = _branch_lookup[(h_tv >> 10)&0x3F];
}
trans_bucket* get_trans_bucket(const cstring& callid, const cstring& cseq_num)
{
return &_trans_table[hash(callid,cseq_num)];
}
trans_bucket* get_trans_bucket(unsigned int h)
{
assert(h < H_TABLE_ENTRIES);
return &_trans_table[h];
}
void dumps_transactions()
{
for(int i=0; i<H_TABLE_ENTRIES; i++){
trans_bucket* bucket = get_trans_bucket(i);
bucket->lock();
bucket->dump();
bucket->unlock();
}
}
void trans_bucket::dump()
{
if(elmts.empty())
return;
DBG("*** Bucket ID: %i ***\n",(int)get_id());
for(trans_list::iterator it = elmts.begin(); it != elmts.end(); ++it) {
DBG("type=0x%x; msg=%p; to_tag=%.*s; reply_status=%i; state=%i; retr_buf=%p\n",
(*it)->type,(*it)->msg,(*it)->to_tag.len,(*it)->to_tag.s,(*it)->reply_status,(*it)->state,(*it)->retr_buf);
}
}
/** EMACS **
* Local variables:
* mode: c++
* c-basic-offset: 4
* End:
*/