Added support for multiple sniffer rules containing filter/age/params.

This commit is contained in:
marian 2023-05-18 15:25:14 +03:00
parent 4c2de15148
commit e3f3ba3884
2 changed files with 539 additions and 272 deletions

View File

@ -110,16 +110,18 @@
; filtersniff: regexp: Default filter to apply to message sniffer at initialization
; If empty it will match all messages except engine.timer which is never displayed
; Example for a filter matching all chan.Anything messages and engine.halt:
; filtersniff=^\(chan\.\|engine\.halt$\)
; filtersniff=^\(chan\.\|engine\.halt\)$
;filtersniff=
; agesniff: float: Display only messages whose age or delay is higher than this value
; This is a floating point number in seconds (1.5 means 1500msec)
;agesniff=0
; filtersniffparams: string: Default parameter(s) filter to apply to message sniffer at initialization
; If empty it will not attempt to match message parameters
; Format: [any] param1=value1 [param2=value2 ...]
; 'any' indicates that message matches if at least one configured parameter matches
; Format: [any] [negated] param1=value1 [param2=value2 ...]
; any: message matches if at least one configured parameter matches
; negated: message matches if list does not match
; Value to match is handled as regexp. It may end with '^' to revert match (i.e. matches if regexp don't match)
; Value may be empty. In this case the parameter matches if missing in message or present with empty value
; Example for a filter matching messages with empty route_type or route_type=call
@ -128,6 +130,16 @@
; filtersniffparams=any caller=^123$ called=^123$
;filtersniffparams=
; msgsniff:<NAME>: string: Add a message sniffer rule
; <NAME> is optional. If missing the rule is handled as the one set using filtersniff/agesniff/filtersniffparams
; This parameter may be repeated with different <NAME> value to add multiple rules
; A rule will replace a previously defined rule with the same name
; Format: [filter=[value]] [age=[value]] [params [any negated] [name=value]]
; Examples:
; msgsniff:=filter=^\(chan\.\) age=0.5 params id=^sip/
; msgsniff:extra=filter=^\(call\.cdr\)$ params any caller=123 called=^2
;msgsniff:<NAME>=
; trace_msg_time: boolean: Instruct message dispatcher to set message event(s) time (enqueue / dispatch)
;trace_msg_time=no

View File

@ -21,152 +21,176 @@
#include <yatengine.h>
#include <stdio.h>
#include <math.h>
#ifdef XDEBUG
#define SNIFF_DEBUG_CHANGE_LIST 9
#define SNIFF_DEBUG_BUILD 10
#else
//#define SNIFF_DEBUG_CHANGE_LIST 9
//#define SNIFF_DEBUG_BUILD 1
#endif
using namespace TelEngine;
namespace { // anonymous
static const char* s_debugs[] =
{
"on",
"off",
"enable",
"disable",
"true",
"false",
"yes",
"no",
"filter",
"timer",
"age",
"params",
0
};
class SniffParamMatch : public String
class MatchingItemMessage : public MatchingItemCustom
{
YCLASS(MatchingItemMessage,MatchingItemCustom)
public:
inline SniffParamMatch(const char* name, const String& rex)
: String(name), m_match(rex.length() < 2 || '^' != rex[rex.length() - 1])
{
m_filter = m_match ? rex : rex.substr(0,rex.length() - 1);
m_filter.compile();
inline MatchingItemMessage(const char* name, MatchingItemBase* msg, MatchingItemBase* params = 0,
uint64_t age = 0)
: MatchingItemCustom(name,"message"),
m_matchName(msg), m_matchParams(params), m_minAge(age)
{}
~MatchingItemMessage() {
TelEngine::destruct(m_matchName);
TelEngine::destruct(m_matchParams);
}
inline bool empty() const
{ return !(m_matchName || m_matchParams || m_minAge); }
inline const MatchingItemBase* matchName() const
{ return m_matchName; }
inline const MatchingItemBase* matchParams() const
{ return m_matchParams; }
inline uint64_t minAge() const
{ return m_minAge; }
virtual bool runMatchListParam(const NamedList& list, MatchingParams* params = 0) const {
if (m_minAge) {
const Message* msg = YOBJECT(Message,&list);
if (msg) {
if (!params) {
if (m_minAge < Time::now() - msg->msgTime().usec())
return false;
}
else {
if (!params->m_now)
params->m_now = Time::now();
if (m_minAge < params->m_now - msg->msgTime().usec())
return false;
}
}
}
if (m_matchName && !m_matchName->runMatchString(list,params))
return false;
return !m_matchParams || m_matchParams->runMatchListParam(list,params);
}
virtual MatchingItemBase* copy() const {
return new MatchingItemMessage(name(),m_matchName ? m_matchName->copy() : 0,
m_matchParams ? static_cast<MatchingItemList*>(m_matchParams->copy()) : 0,
m_minAge);
}
virtual String& dump(String& buf, const MatchingItemDump* dump = 0,
const String& indent = String::empty(), const String& origIndent = String::empty(),
unsigned int depth = 0) const {
AutoGenObject tmpDump;
if (!dump) {
tmpDump = new MatchingItemDump;
dump = static_cast<const MatchingItemDump*>(tmpDump.data());
}
String msg, params;
dump->dumpValue(m_matchName,msg);
dump->dump(m_matchParams,params,indent,origIndent);
if (m_minAge)
msg.printfAppend("%sage %u.%06u",msg ? " " : "",
(unsigned int)(m_minAge / 1000000),(unsigned int)(m_minAge % 1000000));
if (!(msg || params))
return buf;
if (name())
buf << indent << "Name: " << name();
if (msg)
buf << indent << "Message: " << msg;
buf << params;
return buf;
}
inline SniffParamMatch(const SniffParamMatch& other)
: String(other), m_filter(other.m_filter), m_match(other.m_match)
{
m_filter.compile();
}
Regexp m_filter; // Regexp to match parameter name
bool m_match; // Match value, false to revert match (matches if value don't matches)
private:
MatchingItemBase* m_matchName; // Message name match
MatchingItemBase* m_matchParams; // Message parameters match
uint64_t m_minAge; // Minimum age. No match is message age is less than this value
};
class SniffMatch : public RefObject
{
public:
inline SniffMatch()
: m_allParams(true)
inline SniffMatch(MatchingItemList* lst)
: m_match(lst ? lst : new MatchingItemList(""))
{}
inline SniffMatch(const SniffMatch& other)
: m_params(other.m_params.length()), m_allParams(other.m_allParams)
{
setFilter(other.m_filter);
for (unsigned int i = 0; i < other.m_params.length(); i++) {
const SniffParamMatch* m = static_cast<const SniffParamMatch*>(other.m_params[i]);
if (m)
m_params.set(new SniffParamMatch(*m),i);
~SniffMatch()
{ TelEngine::destruct(m_match); }
inline const MatchingItemList* matching() const
{ return m_match; }
inline bool matches(const Message& msg, MatchingParams& params) const
{ return m_match->matchListParam(msg,&params); }
inline void dump(String& buf) {
unsigned int n = m_match->count();
if (!n)
return;
String s;
MatchingItemDump d;
// We are always building regexp, no enclose needed
d.m_rexEnclose = 0;
for (unsigned int i = 0; i < n; i++) {
String tmp;
s.append(d.dump(m_match->at(i),tmp,"\r\n"," "),"\r\n-----");
}
const char* sep = (s && n > 1) ? "\r\n-----" : "";
buf << sep << s << sep;
}
inline bool valid() const
{ return m_filter || m_params.length(); };
inline bool matches(const Message& msg) const {
if (m_filter && !m_filter.matches(msg))
return false;
if (m_params.length()) {
bool matched = false;
for (unsigned int i = 0; i < m_params.length(); i++) {
SniffParamMatch* m = static_cast<SniffParamMatch*>(m_params[i]);
if (!m)
continue;
const NamedString* ns = msg.getParam(*m);
bool ok = false;
// Match if:
// - filter set: regexp matches
// - filter not set: match if parameter is missing or empty
if (m->m_filter)
ok = (m->m_match == m->m_filter.matches(TelEngine::c_safe(ns)));
else
ok = TelEngine::null(ns);
if (ok) {
matched = true;
if (m_allParams)
continue;
break;
}
if (m_allParams)
return false;
}
if (!matched)
return false;
}
return true;
}
inline void setFilter(const String& value) {
m_filter = value;
m_filter.compile();
}
inline void setParams(const String& line) {
String s = line;
m_allParams = !s.startSkip("any");
ObjList list;
static const Regexp s_matchParam("^\\(.* \\)\\?\\([^= ]\\+\\)=\\([^=]*\\)$");
while (s.matches(s_matchParam)) {
list.insert(new SniffParamMatch(s.matchString(2),s.matchString(3).trimSpaces()));
s = s.matchString(1);
}
m_params.assign(list);
}
Regexp m_filter; // Filter for message name
ObjVector m_params; // Filter(s) for message parameters
bool m_allParams; // Match all parameters or at least one
private:
MatchingItemList* m_match;
};
class MsgSniff : public Plugin
{
public:
enum FilterParams {
FilterName = 0,
FilterFilter,
FilterParams,
FilterAge,
FilterParamsCount
};
MsgSniff();
virtual void initialize();
inline void setFilter(SniffMatch* flt = 0) {
Lock lck(m_mutex);
if (flt == m_filter)
return;
m_filter = flt && flt->valid() ? flt : 0;
TelEngine::destruct(flt);
inline bool itemComplete(String& itemList, const String& item, const String& partWord) {
if (!partWord || item.startsWith(partWord)) {
itemList.append(item,"\t");
return true;
}
return false;
}
inline bool listComplete(String& itemList, const String* items, const String& partWord) {
bool ok = false;
while (!TelEngine::null(items))
ok = itemComplete(itemList,*items++,partWord);
return ok;
}
inline void setFilter(MatchingItemList* lst = 0) {
SniffMatch* sm = lst ? new SniffMatch(lst) : 0;
WLock lck(m_lock);
m_filter = (sm && sm->matching()->count()) ? sm : 0;
TelEngine::destruct(sm);
}
inline bool getFilter(RefPointer<SniffMatch>& flt) {
Lock lck(m_mutex);
RLock lck(m_lock);
flt = m_filter;
return (0 != flt);
}
inline bool filterMatches(const Message& msg) {
RefPointer<SniffMatch> flt;
return !getFilter(flt) || flt->matches(msg);
}
void handleMsg(const Message& msg, bool* handled = 0);
void handleCommand(String& line);
void commandComplete(Message& msg);
String& dumpSnifferState(String& buf);
MatchingItemMessage* splitFilter(const String& line, String* params = 0);
MatchingItemMessage* buildFilter(String* filterParams, String* filter = 0,
String* params = 0, const String* age = 0, const MatchingItemMessage* old = 0);
MatchingItemList* changeListItem(MatchingItemList* lst, MatchingItemMessage* item,
const MatchingItemMessage* old = 0);
private:
bool m_first;
RWLock m_lock;
RefPointer<SniffMatch> m_filter;
Mutex m_mutex;
};
class SniffHandler : public MessageHandler
@ -184,176 +208,83 @@ public:
static bool s_active = true;
static bool s_timer = false;
static u_int64_t s_minAge = 0;
static const String s_command = "sniffer";
static const String s_onOff[] = {"on", "off", ""};
static const String s_commands[] = {"set", "reset", "filter", "params", "age", ""};
static const Regexp s_completeOnOff("^sniffer( ((on|off) )?timer)?$",true);
static const Regexp s_completeAllCmds("^sniffer( (on|off))?$",true);
static const Regexp s_completeCmds("^sniffer( (on|off))?( timer( (on|off))?)?$",true);
static const char* s_help =
" sniffer [on|off] [timer [on|off]] [{set|reset|filter|params|age} ...]\r\n"
"Change sniffer rules, enable or disable sniffer and/or timer, display status\r\n"
"Multiple rules with filter/params/age may be configured. A rule may be named\r\n"
"Rule parameters:\r\n"
"filter: message name filter (regexp)\r\n"
"params: message parameters filter. Format: [any] [negated] name=regexp ...\r\n"
"age: message minimum age filter (seconds, 1.5=1500ms)\r\n"
"regexp: may end with ^ for negated match\r\n"
" sniffer [on|off] set [name=[value]] [filter=[value]] [age=[value]] [params [any negated] [name=value]]\r\n"
"Add, replace or remove a sniffer rule\r\n"
" sniffer [on|off] reset [name=[value]] [filter=[value]] [age=[value]] [params [any negated] [name=value]]\r\n"
"Reset all sniffer rules (except timer). Optional set a new rule\r\n"
" sniffer [on|off] [{filter|params|age} ...]\r\n"
"Partial (re)set of unnamed rule data\r\n"
" sniffer [on|off] timer [on|off]\r\n"
"Enable or disable engine.timer message handling\r\n"
;
INIT_PLUGIN(MsgSniff);
static void dumpParams(const Message &msg, String& par)
static inline String& extractUntilSpace(String& dest, String& line)
{
unsigned n = msg.length();
for (unsigned i = 0; i < n; i++) {
const NamedString *s = msg.getParam(i);
if (s) {
par << "\r\n param['" << s->name() << "'] = ";
if (s->name() == YSTRING("password"))
par << "(hidden)";
else
par << "'" << *s << "'";
if (const NamedPointer* p = YOBJECT(NamedPointer,s)) {
char buf[64];
GenObject* obj = p->userData();
::sprintf(buf," [%p]",obj);
par << buf;
if (obj)
par << " '" << obj->toString() << "'";
}
}
int pos = line.find(' ');
if (pos >= 0) {
dest = line.substr(0,pos);
line = line.substr(pos + 1);
}
else {
dest = line;
line = "";
}
return dest;
}
bool SniffHandler::received(Message &msg)
{
if (!s_timer && (msg == YSTRING("engine.timer")))
return false;
if (msg == YSTRING("engine.command")) {
static const String name("sniffer");
String line(msg.getValue(YSTRING("line")));
if (line.startSkip(name)) {
line >> s_active;
line.trimSpaces();
if (line.startSkip("timer"))
(line >> s_timer).trimSpaces();
RefPointer<SniffMatch> crtFlt;
__plugin.getFilter(crtFlt);
SniffMatch* newFlt = 0;
if (line.startSkip("filter")) {
if (crtFlt)
newFlt = new SniffMatch(*crtFlt);
else
newFlt = new SniffMatch;
newFlt->setFilter(line);
line = "";
const NamedString* line = msg.getParam(YSTRING("line"));
if (!line)
__plugin.commandComplete(msg);
else if (line->startsWith(s_command)) {
String l = *line;
if (l.startSkip(s_command)) {
__plugin.handleCommand(l);
__plugin.dumpSnifferState(msg.retValue());
return true;
}
if (line.startSkip("params")) {
if (!newFlt) {
if (crtFlt)
newFlt = new SniffMatch(*crtFlt);
else
newFlt = new SniffMatch;
}
newFlt->setParams(line);
line = "";
}
if (newFlt) {
__plugin.setFilter(newFlt);
__plugin.getFilter(crtFlt);
}
if (line.startSkip("age")) {
s_minAge = (u_int64_t)(1000000.0 * fabs(line.toDouble()));
line = "";
}
msg.retValue() << "Message sniffer: " << (s_active ? "on" : "off");
if (s_active)
msg.retValue() << ", timer: " << (s_timer ? "on" : "off");
if (s_active && crtFlt) {
if (crtFlt->m_filter)
msg.retValue() << ", filter: " << crtFlt->m_filter;
if (crtFlt->m_params.length()) {
msg.retValue() << ", params:";
if (!crtFlt->m_allParams)
msg.retValue() << " any";
for (unsigned int i = 0; i < crtFlt->m_params.length(); i++) {
SniffParamMatch* m = static_cast<SniffParamMatch*>(crtFlt->m_params[i]);
msg.retValue() << " " << *m << "=" << m->m_filter << (m->m_match ? "" : "^");
}
}
}
if (s_active && s_minAge)
msg.retValue() << ", age: " << String().printf("%u.%06u",
(unsigned int)(s_minAge / 1000000),(unsigned int)(s_minAge % 1000000));
msg.retValue() << "\r\n";
}
}
else if (msg == YSTRING("engine.help")) {
const String& line = msg[YSTRING("line")];
if (line == s_command) {
msg.retValue() = s_help;
return true;
}
line = msg.getParam(YSTRING("partline"));
if (line.null()) {
if (name.startsWith(msg.getValue(YSTRING("partword"))))
msg.retValue().append(name,"\t");
}
else if (name == line) {
line = msg.getValue(YSTRING("partword"));
for (const char** b = s_debugs; *b; b++)
if (line.null() || String(*b).startsWith(line))
msg.retValue().append(*b,"\t");
}
}
if (!s_active)
return false;
if (!__plugin.filterMatches(msg))
return false;
u_int64_t mt = msg.msgTime().usec();
u_int64_t dt = Time::now() - mt;
if (s_minAge && (dt < s_minAge))
return false;
String extra;
if (msg.msgTimeEnqueue() && msg.msgTimeDispatch() > msg.msgTimeEnqueue()) {
uint64_t dur = msg.msgTimeDispatch() - msg.msgTimeEnqueue();
extra.printf(" queued=%u.%06u",(unsigned int)(dur / 1000000),
(unsigned int)(dur % 1000000));
}
String par;
dumpParams(msg,par);
Output("Sniffed '%s' time=%u.%06u age=%u.%06u%s%s\r\n thread=%p '%s'\r\n data=%p\r\n retval='%s'%s",
msg.c_str(),
(unsigned int)(mt / 1000000),
(unsigned int)(mt % 1000000),
(unsigned int)(dt / 1000000),
(unsigned int)(dt % 1000000),
extra.safe(),
(msg.broadcast() ? " (broadcast)" : ""),
Thread::current(),
Thread::currentName(),
msg.userData(),
msg.retValue().c_str(),
par.safe());
__plugin.handleMsg(msg);
return false;
};
void HookHandler::dispatched(const Message& msg, bool handled)
{
if (!s_active || (!s_timer && (msg == YSTRING("engine.timer"))))
return;
if (!__plugin.filterMatches(msg))
return;
u_int64_t dt = Time::now() - msg.msgTime().usec();
if (s_minAge && (dt < s_minAge))
return;
String par;
dumpParams(msg,par);
const char* rval = msg.retValue().c_str();
const char* rsep = "'";
if (handled && rval && (rval[0] != '-' || rval[1]) && (msg == YSTRING("user.auth"))) {
rval = "(hidden)";
rsep = "";
}
Output("Returned %s '%s' delay=%u.%06u%s\r\n thread=%p '%s'\r\n data=%p\r\n retval=%s%s%s%s",
String::boolText(handled),
msg.c_str(),
(unsigned int)(dt / 1000000),
(unsigned int)(dt % 1000000),
(msg.broadcast() ? " (broadcast)" : ""),
Thread::current(),
Thread::currentName(),
msg.userData(),
rsep,rval,rsep,
par.safe());
__plugin.handleMsg(msg,&handled);
}
MsgSniff::MsgSniff()
: Plugin("msgsniff"),
m_first(true), m_mutex(false,"FilterSniff")
m_first(true), m_lock("FilterSniff")
{
Output("Loaded module MsgSniffer");
}
@ -363,15 +294,34 @@ void MsgSniff::initialize()
Output("Initializing module MsgSniffer");
if (m_first) {
m_first = false;
s_active = Engine::config().getBoolValue("general","msgsniff",false);
SniffMatch* m = new SniffMatch;
m->setFilter(Engine::config().getValue("general","filtersniff"));
m->setParams(Engine::config().getValue("general","filtersniffparams"));
setFilter(m);
s_minAge = (u_int64_t)(1000000.0 * fabs(Engine::config().getDoubleValue("general","agesniff")));
String trackName = Engine::config().getValue("general","msgsniff_trackname");
if (trackName) {
if (trackName.isBoolean()) {
String trackName;
NamedList* genSect = Engine::config().getSection(YSTRING("general"));
if (genSect) {
NamedList& gen = *genSect;
s_active = gen.getBoolValue(YSTRING("msgsniff"));
MatchingItemList* lst = 0;
String filter = gen[YSTRING("filtersniff")];
String params = gen[YSTRING("filtersniffparams")];
String age = gen[YSTRING("agesniff")];
if (filter || params || age)
lst = changeListItem(lst,buildFilter(0,&filter,&params,&age));
for (ObjList* o = gen.paramList()->skipNull(); o; o = o->skipNext()) {
NamedString* ns = static_cast<NamedString*>(o->get());
if (ns->name() == YSTRING("msgsniff") || ns->name().startsWith("msgsniff:")) {
String fp[FilterParamsCount];
fp[FilterName] = ns->name().substr(9);
lst = changeListItem(lst,splitFilter(*ns,fp));
}
}
setFilter(lst);
#ifdef SNIFF_DEBUG_CHANGE_LIST
if (m_filter) {
String tmp;
Debug(this,SNIFF_DEBUG_CHANGE_LIST,"Loaded\r\n%s",dumpSnifferState(tmp).c_str());
}
#endif
trackName = gen[YSTRING("msgsniff_trackname")];
if (trackName && trackName.isBoolean()) {
if (trackName.toBoolean())
trackName = "msgsniff";
else
@ -383,6 +333,311 @@ void MsgSniff::initialize()
}
}
}; // anonymous namespace
void MsgSniff::handleMsg(const Message& msg, bool* handled)
{
if (!s_active || (!s_timer && msg == YSTRING("engine.timer")))
return;
RefPointer<SniffMatch> filter;
uint64_t age = 0;
if (getFilter(filter)) {
MatchingParams params;
if (!filter->matches(msg,params))
return;
age = params.m_now ? params.m_now : Time::now();
}
else
age = Time::now();
/* vi: set ts=8 sw=4 sts=4 noet: */
String par;
for (ObjList* o = msg.paramList()->skipNull(); o; o = o->skipNext()) {
const NamedString* s = static_cast<NamedString*>(o->get());
String tmp;
tmp << "\r\n param['" << s->name() << "'] = ";
if (s->name() == YSTRING("password"))
tmp << "(hidden)";
else
tmp << "'" << *s << "'";
if (const NamedPointer* p = YOBJECT(NamedPointer,s)) {
GenObject* obj = p->userData();
if (obj)
tmp.printfAppend(" [%p] '%s'",obj,obj->toString().safe());
else
tmp.printfAppend(" [%p]",obj);
}
par << tmp;
}
uint64_t mt = msg.msgTime().usec();
age -= mt;
if (!handled) {
String extra;
if (msg.msgTimeEnqueue() && msg.msgTimeDispatch() > msg.msgTimeEnqueue()) {
uint64_t dur = msg.msgTimeDispatch() - msg.msgTimeEnqueue();
extra.printf(" queued=%u.%06u",(unsigned int)(dur / 1000000),
(unsigned int)(dur % 1000000));
}
Output("Sniffed '%s' time=%u.%06u age=%u.%06u%s%s\r\n thread=%p '%s'\r\n data=%p\r\n retval='%s'%s",
msg.c_str(),
(unsigned int)(mt / 1000000),
(unsigned int)(mt % 1000000),
(unsigned int)(age / 1000000),
(unsigned int)(age % 1000000),
extra.safe(),
(msg.broadcast() ? " (broadcast)" : ""),
Thread::current(),
Thread::currentName(),
msg.userData(),
msg.retValue().c_str(),
par.safe());
}
else {
const char* rval = msg.retValue().c_str();
const char* rsep = "'";
if (*handled && rval && (rval[0] != '-' || rval[1]) && (msg == YSTRING("user.auth"))) {
rval = "(hidden)";
rsep = "";
}
Output("Returned %s '%s' delay=%u.%06u%s\r\n thread=%p '%s'\r\n data=%p\r\n retval=%s%s%s%s",
String::boolText(*handled),
msg.c_str(),
(unsigned int)(age / 1000000),
(unsigned int)(age % 1000000),
(msg.broadcast() ? " (broadcast)" : ""),
Thread::current(),
Thread::currentName(),
msg.userData(),
rsep,rval,rsep,
par.safe());
}
}
void MsgSniff::handleCommand(String& line)
{
line >> s_active;
line.trimSpaces();
if (line.startSkip("timer"))
(line >> s_timer).trimSpaces();
if (!line)
return;
const MatchingItemList* crtList = 0;
const MatchingItemMessage* old = 0;
RefPointer<SniffMatch> crtFlt;
if (getFilter(crtFlt)) {
crtList = crtFlt->matching();
old = YOBJECT(MatchingItemMessage,crtList ? crtList->find(String::empty()) : 0);
}
String cmd;
extractUntilSpace(cmd,line);
MatchingItemMessage* newItem = 0;
if (cmd == YSTRING("filter"))
newItem = buildFilter(0,&line,0,0,old);
else if (cmd == YSTRING("params"))
newItem = buildFilter(0,0,&line,0,old);
else if (cmd == YSTRING("age"))
newItem = buildFilter(0,0,0,&line,old);
else if (cmd == YSTRING("set")) {
if (line) {
String fp[FilterParamsCount];
newItem = splitFilter(line,fp);
const String& n = crtList ? (const String&)fp[FilterName] : String::empty();
old = n ? YOBJECT(MatchingItemMessage,crtList->find(n)) : 0;
}
}
else if (cmd == YSTRING("reset")) {
crtList = 0;
setFilter(0);
if (line)
newItem = splitFilter(line);
}
else
return;
if (newItem || old) {
MatchingItemBase* mib = crtList ? crtList->copy() : 0;
MatchingItemList* lst = YOBJECT(MatchingItemList,mib);
if (!lst)
TelEngine::destruct(mib);
setFilter(changeListItem(lst,newItem,old));
}
}
void MsgSniff::commandComplete(Message& msg)
{
const String& partLine = msg[YSTRING("partline")];
const String& partWord = msg[YSTRING("partword")];
if (!partLine || partLine == YSTRING("help"))
itemComplete(msg.retValue(),s_command,partWord);
else if (s_command == partLine) {
listComplete(msg.retValue(),s_onOff,partWord);
listComplete(msg.retValue(),s_commands,partWord);
itemComplete(msg.retValue(),YSTRING("timer"),partWord);
}
else if (partLine.startsWith(s_command,true)) {
String line = partLine;
if (line.matches(s_completeAllCmds)) {
listComplete(msg.retValue(),s_commands,partWord);
itemComplete(msg.retValue(),YSTRING("timer"),partWord);
}
else if (line.matches(s_completeOnOff))
listComplete(msg.retValue(),s_onOff,partWord);
else if (line.matches(s_completeCmds))
listComplete(msg.retValue(),s_commands,partWord);
}
}
String& MsgSniff::dumpSnifferState(String& buf)
{
buf << "Message sniffer: " << (s_active ? "on" : "off");
if (s_active || s_timer)
buf << ", timer: " << (s_timer ? "on" : "off");
RefPointer<SniffMatch> flt;
if (getFilter(flt))
flt->dump(buf);
buf << "\r\n";
return buf;
}
MatchingItemMessage* MsgSniff::splitFilter(const String& line, String* fp)
{
if (!line)
return 0;
String fpTmp[FilterParamsCount];
if (!fp)
fp = fpTmp;
const char* s = line;
unsigned int len = line.length();
unsigned int skipped = 0;
while (*s) {
len -= String::c_skip_chars(s," ");
if (!*s)
break;
int set = -1;
if (0 != (skipped = String::c_skip(s,"name=",len,5)))
set = FilterName;
else if (0 != (skipped = String::c_skip(s,"filter=",len,7)))
set = FilterFilter;
else if (0 != (skipped = String::c_skip(s,"age=",len,4)))
set = FilterAge;
else if (String::c_skip(s,"params ",len,7)) {
fp[FilterParams] = s;
break;
}
const char* orig = s;
unsigned int n = String::c_skip_chars(s," ",-1,false);
len -= skipped + n;
if (set >= 0)
fp[set].assign(orig,n);
}
#ifdef SNIFF_DEBUG_BUILD
Debug(this,DebugAll,"splitFilter '%s' -> name='%s' filter='%s' age='%s' params='%s'",line.safe(),
fp[FilterName].safe(),fp[FilterFilter].safe(),fp[FilterAge].safe(),fp[FilterParams].safe());
#endif
return buildFilter(fp);
}
MatchingItemMessage* MsgSniff::buildFilter(String* filterParams,
String* filter, String* params, const String* age, const MatchingItemMessage* old)
{
const String* name = &String::empty();
if (filterParams) {
name = filterParams + FilterName;
filter = filterParams + FilterFilter;
params = filterParams + FilterParams;
age = filterParams + FilterAge;
}
else if (!(filter || params || age))
return 0;
#ifdef SNIFF_DEBUG_BUILD
Debug(this,DebugAll,"buildFilter '%s' '%s' '%s' '%s' (%p)",TelEngine::c_safe(name),
TelEngine::c_safe(filter),TelEngine::c_safe(params),TelEngine::c_safe(age),old);
#endif
MatchingItemBase* matchName = 0;
if (!TelEngine::null(filter))
matchName = MatchingItemRegexp::build("",*filter,-1,false,false,0);
MatchingItemBase* matchParams = 0;
if (!TelEngine::null(params)) {
static const Regexp s_matchParamListParam("^( *)?(any|negated)( .*)?$",true);
static const Regexp s_matchParam("^(.* )?([^= ]+)=([^=]*)$",true);
bool matchAll = true;
bool negated = false;
while (params->matches(s_matchParamListParam)) {
String tmp = params->matchString(2);
*params = params->matchString(3);
if (tmp == YSTRING("any"))
matchAll = false;
else if (tmp == YSTRING("negated"))
negated = true;
}
MatchingItemList* lst = new MatchingItemList("Params",matchAll,negated);
while (params->matches(s_matchParam)) {
MatchingItemBase* p = MatchingItemRegexp::build(params->matchString(2),
params->matchString(3).trimSpaces(),-1,false,false,0);
lst->insert(p);
*params = params->matchString(1);
}
if (lst->count())
matchParams = lst;
else
TelEngine::destruct(lst);
}
uint64_t minAge = 0;
if (age) {
double d = age->toDouble();
minAge = (uint64_t)(1000000.0 * (d >= 0 ? d : -d));
}
if (old) {
if (!filter && old->matchName())
matchName = old->matchName()->copy();
if (!params && old->matchParams())
matchParams = old->matchParams()->copy();
if (!age)
minAge = old->minAge();
}
if (!(matchName || matchParams || minAge))
return 0;
MatchingItemMessage* m = new MatchingItemMessage(*name,matchName,matchParams,minAge);
#ifdef SNIFF_DEBUG_BUILD
String tmp;
Debug(this,SNIFF_DEBUG_BUILD,"Built item\r\n-----%s\r\n-----",
MatchingItemDump::dumpItem(m,tmp,"\r\n"," ").safe());
#endif
return m;
}
MatchingItemList* MsgSniff::changeListItem(MatchingItemList* lst, MatchingItemMessage* item,
const MatchingItemMessage* old)
{
if (item == old)
return lst;
String oldName;
if (!item || item->empty()) {
if (!lst)
return 0;
if (!old)
old = item;
#ifdef SNIFF_DEBUG_CHANGE_LIST
Debug(this,SNIFF_DEBUG_CHANGE_LIST,"Removing item '%s' index=%d",
old->name().safe(),lst->indexOf(old->name()));
#endif
lst->set(0,lst->indexOf(old->name()));
TelEngine::destruct(item);
return lst;
}
#ifdef SNIFF_DEBUG_CHANGE_LIST
String tmp;
MatchingItemDump::dumpItem(item,tmp,"\r\n"," ");
if (tmp)
tmp = "\r\n-----" + tmp + "\r\n-----";
int idx = lst ? lst->indexOf(item->name()) : -1;
Debug(this,SNIFF_DEBUG_CHANGE_LIST,"%s item list (%p) index=%d%s",
idx >= 0 ? "Replacing" : "Adding",lst,idx,tmp.safe());
#endif
if (!lst) {
lst = new MatchingItemList("",false);
lst->append(item);
}
else
lst->set(item,lst->indexOf(item->name()));
return lst;
}
}; // anonymous namespace