Modified and improved regexroute.

Added extra support methods in engine, fixed minor annoyances.


git-svn-id: http://yate.null.ro/svn/yate/trunk@118 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2004-12-01 00:54:04 +00:00
parent 536180ee0f
commit fd651a3b2b
6 changed files with 176 additions and 96 deletions

View File

@ -34,9 +34,12 @@
; Expressions are scanned from top to bottom; the first match returns the value
; Each line must be of the form:
; regexp=context_name
; To match a message parameter you can use the format:
; ${paramname}regexp=context_name
; Strings captured with the regular expression construct \(...\) can be
; inserted in the context name using \1, \2, \3, ... while \0 holds the entire
; matched regexp even if no capture was used
; Message parameters can be inserted in the context name using ${paramname}
;
; Example:
;^$=empty
@ -54,18 +57,24 @@
; Expressions are scanned from top to bottom; the first match returns the value
; Each line must be of the form:
; regexp=target
; To match a message parameter you can use the format:
; ${paramname}regexp=target
; Strings captured with the regular expression construct \(...\) can be
; inserted in the target using \1, \2, \3, ...
; Message parameters can be inserted in the target using ${paramname}
;
; First character of a matched target can have a special meaning
; - returns immediately from the context without routing
; < calls another context, returns at the next entry if the other context
did not return successfully
; > jumps another context, does not return to this context
; ! modify the called string instead of specifying a target
; First word of a matched target can have a special meaning
; return - returns immediately from the context without routing
; include - calls another context, returns at the next entry if the other
; context did not return successfully
; jump - jumps another context, does not return to this context
; match - modify the matched string instead of specifying a target
;
; It is possible to set message parameters by appending them as name=value
; while separating them with semicolons (;)
; Please note that the match string is not changed together with the message
; parameter from which it was copied; for example in routing stage using
; "match 123" and ";called=123" have different effects
;
; Example:
; route the emergency 112 and 911 numbers to POTS, any channel on an E1,

View File

@ -120,6 +120,17 @@ void Configuration::clearKey(const String &sect, const String &key)
l->clearParam(key);
}
void Configuration::addValue(const String &sect, const char *key, const char *value)
{
DDebug(DebugInfo,"Configuration::addValue(\"%s\",\"%s\",\"%s\")",sect.c_str(),key,value);
ObjList *l = makeSectHolder(sect);
if (!l)
return;
NamedList *n = static_cast<NamedList *>(l->get());
if (n)
n->addParam(key,value);
}
void Configuration::setValue(const String &sect, const char *key, const char *value)
{
DDebug(DebugInfo,"Configuration::setValue(\"%s\",\"%s\",\"%s\")",sect.c_str(),key,value);
@ -181,7 +192,7 @@ bool Configuration::load()
}
int q = s.find('=');
if (q > 0)
setValue(sect,s.substr(0,q).trimBlanks(),s.substr(q+1).trimBlanks());
addValue(sect,s.substr(0,q).trimBlanks(),s.substr(q+1).trimBlanks());
}
::fclose(f);
return true;

View File

@ -533,6 +533,19 @@ bool String::startsWith(const char *what, bool wordBreak) const
return (::strncmp(m_string,what,l) == 0);
}
bool String::startSkip(const char *what, bool wordBreak)
{
if (startsWith(what,wordBreak)) {
const char *p = m_string + ::strlen(what);
if (wordBreak)
while (isWordBreak(*p))
p++;
assign(p);
return true;
}
return false;
}
bool String::endsWith(const char *what, bool wordBreak) const
{
if (!(m_string && what && *what))
@ -586,19 +599,23 @@ String String::replaceMatches(const String &templ) const
for (;;) {
pos = templ.find('\\',ofs);
if (pos < 0) {
s += templ.substr(ofs);
s << templ.substr(ofs);
break;
}
s += templ.substr(ofs,pos-ofs);
s << templ.substr(ofs,pos-ofs);
pos++;
char c = templ[pos];
if (c == '\\') {
pos++;
s += "\\";
s << "\\";
}
else if ('0' <= c && c <= '9') {
pos++;
s += matchString(c - '0');
s << matchString(c - '0');
}
else {
pos++;
s << "\\" << c;
}
ofs = pos;
}

View File

@ -45,6 +45,20 @@ static void setMessage(Message &msg, String &line)
bool first = true;
for (ObjList *p = strs; p; p=p->next()) {
String *s = static_cast<String*>(p->get());
if (s) {
int p1;
while ((p1 = s->find("${")) >= 0) {
// handle ${paramname} replacements
int p2 = s->find('}',p1+2);
if (p2 > 0) {
String v = s->substr(p1+2,p2-p1-2);
v.trimBlanks();
DDebug("RegexRoute",DebugAll,"Replacing parameter '%s'",
v.c_str());
*s = s->substr(0,p1) + msg.getValue(v) + s->substr(p2+1);
}
}
}
if (first) {
first = false;
line = s ? *s : "";
@ -67,12 +81,12 @@ static void setMessage(Message &msg, String &line)
strs->destruct();
}
static bool oneContext(Message &msg, String &called, const String &context, int depth = 0)
static bool oneContext(Message &msg, String &str, const String &context, String &ret, int depth = 0)
{
if (!(context && *context))
return false;
if (depth > 5) {
Debug("RegexRoute",DebugWarn,"Loop detected, current context '%s'",context.c_str());
Debug("RegexRoute",DebugWarn,"Possible loop detected, current context '%s'",context.c_str());
return false;
}
NamedList *l = s_cfg.getSection(context);
@ -82,51 +96,75 @@ static bool oneContext(Message &msg, String &called, const String &context, int
NamedString *n = l->getParam(i);
if (n) {
Regexp r(n->name());
if (called.matches(r)) {
String val = called.replaceMatches(*n);
String val;
if (r.startsWith("${")) {
// handle special case ${paramname}regexp=value
int p = r.find('}');
if (p < 3) {
Debug("RegexRoute",DebugWarn,"Invalid parameter match '%s' in rule #%u in context '%s'",
r.c_str(),i+1,context.c_str());
continue;
}
val = r.substr(2,p-2);
r = r.substr(p+1);
val.trimBlanks();
r.trimBlanks();
if (val.null() || r.null()) {
Debug("RegexRoute",DebugWarn,"Missing parameter or rule in rule #%u in context '%s'",
i+1,context.c_str());
continue;
}
NDebug("RegexRoute",DebugAll,"Using message parameter '%s'",
val.c_str());
val = msg.getValue(val);
}
else
val = str;
val.trimBlanks();
if (val.matches(r)) {
val = val.replaceMatches(*n);
setMessage(msg,val);
val.trimBlanks();
switch (val[0]) {
case 0:
break;
case '-':
return false;
case '>':
val >> ">";
val.trimBlanks();
NDebug("RegexRoute",DebugAll,"Jumping to context '%s' by rule #%u '%s'",
val.c_str(),i+1,r.c_str());
return oneContext(msg,called,val,depth+1);
case '<':
val >> "<";
val.trimBlanks();
NDebug("RegexRoute",DebugAll,"Calling context '%s' by rule #%u '%s'",
val.c_str(),i+1,r.c_str());
if (oneContext(msg,called,val,depth+1)) {
DDebug("RegexRoute",DebugAll,"Returning true from context '%s'", context.c_str());
return true;
}
break;
case '!':
val >> "!";
val.trimBlanks();
if (!val.null()) {
NDebug("RegexRoute",DebugAll,"Setting called '%s' by rule #%u '%s'",
val.c_str(),i+1,r.c_str());
called = val;
}
break;
default:
DDebug("RegexRoute",DebugAll,"Routing call to '%s' in context '%s' via `%s' by rule #%u '%s'",
called.c_str(),context.c_str(),val.c_str(),i+1,r.c_str());
msg.retValue() = val;
if (val.null()) {
// special case: do nothing on empty target
continue;
}
else if (val == "return") {
NDebug("RegexRoute",DebugAll,"Returning false from context '%s'", context.c_str());
return false;
}
else if (val.startSkip("jump")) {
NDebug("RegexRoute",DebugAll,"Jumping to context '%s' by rule #%u '%s'",
val.c_str(),i+1,n->name().c_str());
return oneContext(msg,str,val,ret,depth+1);
}
else if (val.startSkip("include")) {
NDebug("RegexRoute",DebugAll,"Including context '%s' by rule #%u '%s'",
val.c_str(),i+1,n->name().c_str());
if (oneContext(msg,str,val,ret,depth+1)) {
DDebug("RegexRoute",DebugAll,"Returning true from context '%s'", context.c_str());
return true;
}
}
else if (val.startSkip("match")) {
if (!val.null()) {
NDebug("RegexRoute",DebugAll,"Setting match string '%s' by rule #%u '%s' in context '%s'",
val.c_str(),i+1,n->name().c_str(),context.c_str());
str = val;
}
}
else {
DDebug("RegexRoute",DebugAll,"Returning '%s' for '%s' in context '%s' by rule #%u '%s'",
val.c_str(),str.c_str(),context.c_str(),i+1,n->name().c_str());
ret = val;
return true;
}
}
}
}
}
DDebug("RegexRoute",DebugAll,"Returning false from context '%s'", context.c_str());
DDebug("RegexRoute",DebugAll,"Returning false at end of context '%s'", context.c_str());
return false;
}
@ -137,9 +175,11 @@ bool RouteHandler::received(Message &msg)
if (called.null())
return false;
const char *context = msg.getValue("context","default");
if (oneContext(msg,called,context)) {
Debug(DebugInfo,"Routing call to '%s' in context '%s' via `%s' in %llu usec",
called.c_str(),context,msg.retValue().c_str(),Time::now()-tmr);
String ret;
if (oneContext(msg,called,context,ret)) {
Debug(DebugInfo,"Routing call to '%s' in context '%s' via '%s' in %llu usec",
called.c_str(),context,ret.c_str(),Time::now()-tmr);
msg.retValue() = ret;
return true;
}
Debug(DebugInfo,"Could not route call to '%s' in context '%s', wasted %llu usec",
@ -161,32 +201,25 @@ bool PrerouteHandler::received(Message &msg)
// return immediately if there is already a context
if (msg.getValue("context"))
return false;
// String s(msg.getValue("caller"));
String s(msg.getValue("driver")); s+="/";
s+=msg.getValue("span"); s+="/";
s+=msg.getValue("channel"); s+="/";
s+=msg.getValue("caller");
if (s.null())
String caller(msg.getValue("caller"));
if (caller.null()) {
caller << msg.getValue("driver") << "/";
caller << msg.getValue("span") << "/";
caller << msg.getValue("channel");
}
if (caller == "//")
return false;
NamedList *l = s_cfg.getSection("contexts");
if (l) {
unsigned int len = l->length();
for (unsigned int i=0; i<len; i++) {
NamedString *n = l->getParam(i);
if (n) {
Regexp r(n->name());
if (s.matches(r)) {
msg.addParam("context",s.replaceMatches(*n));
Debug(DebugInfo,"Classifying caller '%s' in context '%s' by rule #%u '%s' in %llu usec",
s.c_str(),msg.getValue("context"),i+1,r.c_str(),Time::now()-tmr);
return true;
}
}
}
String ret;
if (oneContext(msg,caller,"contexts",ret)) {
Debug(DebugInfo,"Classifying caller '%s' in context '%s' in %llu usec",
caller.c_str(),ret.c_str(),Time::now()-tmr);
msg.addParam("context",ret);
return true;
}
Debug(DebugInfo,"Could not classify call from '%s', wasted %llu usec",
s.c_str(),Time::now()-tmr);
caller.c_str(),Time::now()-tmr);
return false;
};

View File

@ -224,16 +224,6 @@ void Connection::run()
}
}
static bool startSkip(String &s, const char *keyword)
{
if (s.startsWith(keyword,true)) {
s >> keyword;
s.trimBlanks();
return true;
}
return false;
}
void Connection::processLine(const char *line)
{
DDebug("RManager",DebugInfo,"processLine = %s",line);
@ -242,7 +232,7 @@ void Connection::processLine(const char *line)
if (str.null())
return;
if (startSkip(str,"status"))
if (str.startSkip("status"))
{
Message m("status");
if (!str.null()) {
@ -254,7 +244,7 @@ void Connection::processLine(const char *line)
str << m.retValue() << "%%-status\n";
write(str);
}
else if (startSkip(str,"drop"))
else if (str.startSkip("drop"))
{
if (str.null()) {
write(m_machine ? "%%=drop:fail=noarg\n" : "You must specify what connection to drop!\n");
@ -276,7 +266,7 @@ void Connection::processLine(const char *line)
str = (m_machine ? "%%=drop:fail:" : "Could not drop ") + str + "\n";
write(str);
}
else if (startSkip(str,"call"))
else if (str.startSkip("call"))
{
int pos = str.find(' ');
if (pos <= 0) {
@ -293,9 +283,9 @@ void Connection::processLine(const char *line)
str = (m_machine ? "%%=call:fail:" : "Could not call ") + str + "\n";
write(str);
}
else if (startSkip(str,"debug"))
else if (str.startSkip("debug"))
{
if (startSkip(str,"level")) {
if (str.startSkip("level")) {
int dbg = debugLevel();
str >> dbg;
dbg = debugLevel(dbg);
@ -312,31 +302,31 @@ void Connection::processLine(const char *line)
}
write(str);
}
else if (startSkip(str,"machine"))
else if (str.startSkip("machine"))
{
str >> m_machine;
str = "Machine mode: ";
str += (m_machine ? "on\n" : "off\n");
write(str);
}
else if (startSkip(str,"reload"))
else if (str.startSkip("reload"))
{
write(m_machine ? "%%=reload\n" : "Reinitializing...\n");
Engine::init();
}
else if (startSkip(str,"quit"))
else if (str.startSkip("quit"))
{
write(m_machine ? "%%=quit\n" : "Goodbye!\n");
cancel();
}
else if (startSkip(str,"stop"))
else if (str.startSkip("stop"))
{
unsigned code = 0;
str >> code;
write(m_machine ? "%%=shutdown\n" : "Engine shutting down - bye!\n");
Engine::halt(code);
}
else if (startSkip(str,"help") || startSkip(str,"?"))
else if (str.startSkip("help") || str.startSkip("?"))
{
Message m("help");
if (!str.null())

View File

@ -750,6 +750,18 @@ public:
*/
bool endsWith(const char *what, bool wordBreak = false) const;
/**
* Checks if the string starts with a substring and removes it
* @param what Substring to search for
* @param wordBreak Check if a word boundary follows the substring;
* this parameter defaults to True because the intended use of this
* method is to separate commands from their parameters
* @return True if the substring occurs at the beginning of the string
* and also removes the substring; if wordBreak is True any word
* breaking characters are also removed
*/
bool startSkip(const char *what, bool wordBreak = true);
/**
* Checks if matches another string
* @param value String to check for match
@ -1301,6 +1313,14 @@ public:
*/
void clearKey(const String &sect, const String &key);
/**
* Add the value of a key in a section.
* @param sect Name of the section, will be created if missing
* @param key Name of the key to add in the section
* @param value Value to set in the key
*/
void addValue(const String &sect, const char *key, const char *value = 0);
/**
* Set the value of a key in a section.
* @param sect Name of the section, will be created if missing