/*
 * msgsniff.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * A sample message sniffer that inserts a wildcard message handler
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2004-2014 Null Team
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <yatengine.h>

#include <stdio.h>
#include <math.h>

using namespace TelEngine;
namespace { // anonymous

static const char* s_debugs[] =
{
    "on",
    "off",
    "enable",
    "disable",
    "true",
    "false",
    "yes",
    "no",
    "filter",
    "timer",
    "age",
    0
};


class MsgSniff : public Plugin
{
public:
    MsgSniff();
    virtual void initialize();
private:
    bool m_first;
};

class SniffHandler : public MessageHandler
{
public:
    SniffHandler() : MessageHandler(0,0) { }
    virtual bool received(Message &msg);
};

class HookHandler : public MessagePostHook
{
public:
    virtual void dispatched(const Message& msg, bool handled);
};

static bool s_active = true;
static bool s_timer = false;
static u_int64_t s_minAge = 0;
static Regexp s_filter;
static Mutex s_mutex(false,"FilterSniff");

static void dumpParams(const Message &msg, String& par)
{
    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() << "'";
	    }
	}
    }
}

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();
	    if (line.startSkip("filter")) {
		s_mutex.lock();
		s_filter = line;
		s_mutex.unlock();
	    }
	    if (line.startSkip("age"))
		s_minAge = (u_int64_t)(1000000.0 * fabs(line.toDouble()));
	    msg.retValue() << "Message sniffer: " << (s_active ? "on" : "off");
	    if (s_active)
		msg.retValue() << ", timer: " << (s_timer ? "on" : "off");
	    if (s_active && s_filter)
		msg.retValue() << ", filter: " << s_filter;
	    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";
	    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;
    Lock lock(s_mutex);
    if (s_filter && !s_filter.matches(msg))
	return false;
    lock.drop();
    u_int64_t mt = msg.msgTime().usec();
    u_int64_t dt = Time::now() - mt;
    if (s_minAge && (dt < s_minAge))
	return false;
    String par;
    dumpParams(msg,par);
    Output("Sniffed '%s' time=%u.%06u age=%u.%06u%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),
	(msg.broadcast() ? " (broadcast)" : ""),
	Thread::current(),
	Thread::currentName(),
	msg.userData(),
	msg.retValue().c_str(),
	par.safe());
    return false;
};


void HookHandler::dispatched(const Message& msg, bool handled)
{
    if (!s_active || (!s_timer && (msg == YSTRING("engine.timer"))))
	return;
    Lock lock(s_mutex);
    if (s_filter && !s_filter.matches(msg))
	return;
    lock.drop();
    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());
}


MsgSniff::MsgSniff()
    : Plugin("msgsniff"),
      m_first(true)
{
    Output("Loaded module MsgSniffer");
}

void MsgSniff::initialize()
{
    Output("Initializing module MsgSniffer");
    if (m_first) {
	m_first = false;
	s_active = Engine::config().getBoolValue("general","msgsniff",false);
	s_mutex.lock();
	s_filter = Engine::config().getValue("general","filtersniff");
	s_mutex.unlock();
	s_minAge = (u_int64_t)(1000000.0 * fabs(Engine::config().getDoubleValue("general","agesniff")));
	Engine::install(new SniffHandler);
	Engine::self()->setHook(new HookHandler);
    }
}

INIT_PLUGIN(MsgSniff);

}; // anonymous namespace

/* vi: set ts=8 sw=4 sts=4 noet: */
