/*
 * wslua_listener.c
 *
 * Wireshark's interface to the Lua Programming Language
 *
 *  Implementation of tap Listeners
 *
 * (c) 2006, Luis E. Garcia Ontanon <luis@ontanon.org>
 *
 * $Id$
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * This program 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

#include <epan/emem.h>

/* WSLUA_MODULE Listener Post-dissection packet analysis */

#include "wslua.h"

WSLUA_CLASS_DEFINE(Listener,NOP,NOP);
/*
    A Listener, is called once for every packet that matches a certain filter or has a certain tap.
    It can read the tree, the packet's Tvb eventually the tapped data but it cannot
    add elements to the tree.
 */

static int tap_packet_cb_error_handler(lua_State* L) {
    const gchar* error =  lua_tostring(L,1);
    static gchar* last_error = NULL;
    static int repeated = 0;
    static int next = 2;
    const gchar* where =  (lua_pinfo) ?

    ep_strdup_printf("Lua: on packet %i Error During execution of Listener Packet Callback",lua_pinfo->fd->num) :
    ep_strdup_printf("Lua: Error During execution of Listener Packet Callback") ;

    /* show the error the 1st, 3rd, 5th, 9th, 17th, 33th... time it appears to avoid window flooding */
    /* XXX the last series of identical errors won't be shown (the user however gets at least one message) */

    if (! last_error) {
        report_failure("%s:\n%s",where,error);
        last_error = g_strdup(error);
        repeated = 0;
        next = 2;
        return 0;
    }

    if (g_str_equal(last_error,error) ) {
        repeated++;
        if ( repeated == next ) {
            report_failure("%s happened %i times:\n %s",where,repeated,error);
            next *= 2;
        }
    } else {
        report_failure("%s happened %i times:\n %s",where,repeated,last_error);
        g_free(last_error);
        last_error = g_strdup(error);
        repeated = 0;
        next = 2;
        report_failure("%s:\n %s",where,error);
    }

    return 0;
}


static int lua_tap_packet(void *tapdata, packet_info *pinfo, epan_dissect_t *edt, const void *data) {
    Listener tap = (Listener)tapdata;
    int retval = 0;

    if (tap->packet_ref == LUA_NOREF) return 0;

    lua_settop(tap->L,0);

    lua_pushcfunction(tap->L,tap_packet_cb_error_handler);
    lua_rawgeti(tap->L, LUA_REGISTRYINDEX, tap->packet_ref);

    push_Pinfo(tap->L, pinfo);
    push_Tvb(tap->L, edt->tvb);

    if (tap->extractor) {
        tap->extractor(tap->L,data);
    } else {
        lua_pushnil(tap->L);
    }

    lua_pinfo = pinfo;
    lua_tvb = edt->tvb;
    lua_tree = (struct _wslua_treeitem *)g_malloc(sizeof(struct _wslua_treeitem));
    lua_tree->tree = edt->tree;
    lua_tree->item = NULL;
    lua_tree->expired = FALSE;

    switch ( lua_pcall(tap->L,3,1,1) ) {
        case 0:
            retval = luaL_optint(tap->L,-1,1);
            break;
        case LUA_ERRRUN:
            break;
        case LUA_ERRMEM:
            g_warning("Memory alloc error while calling listener tap callback packet");
            break;
        default:
            g_assert_not_reached();
            break;
    }

    clear_outstanding_Pinfo();
    clear_outstanding_Tvb();

    lua_pinfo = NULL;
    lua_tvb = NULL;
    lua_tree = NULL;

    return retval;
}

static int tap_reset_cb_error_handler(lua_State* L) {
    const gchar* error =  lua_tostring(L,1);
    report_failure("Lua: Error During execution of Listener init Callback:\n %s",error);
    return 1;
}

static void lua_tap_reset(void *tapdata) {
    Listener tap = (Listener)tapdata;

    if (tap->init_ref == LUA_NOREF) return;

    lua_pushcfunction(tap->L,tap_reset_cb_error_handler);
    lua_rawgeti(tap->L, LUA_REGISTRYINDEX, tap->init_ref);

    switch ( lua_pcall(tap->L,0,0,1) ) {
        case 0:
            break;
        case LUA_ERRRUN:
            g_warning("Runtime error while calling a listener's init()");
            break;
        case LUA_ERRMEM:
            g_warning("Memory alloc error while calling a listener's init()");
            break;
        default:
            g_assert_not_reached();
            break;
    }
}

static void lua_tap_draw(void *tapdata) {
    Listener tap = (Listener)tapdata;
    const gchar* error;
    if (tap->draw_ref == LUA_NOREF) return;

    lua_pushcfunction(tap->L,tap_reset_cb_error_handler);
    lua_rawgeti(tap->L, LUA_REGISTRYINDEX, tap->draw_ref);

    switch ( lua_pcall(tap->L,0,0,1) ) {
        case 0:
            /* OK */
            break;
        case LUA_ERRRUN:
            error = lua_tostring(tap->L,-1);
            g_warning("Runtime error while calling a listener's draw(): %s",error);
            break;
        case LUA_ERRMEM:
            g_warning("Memory alloc error while calling a listener's draw()");
            break;
        default:
            g_assert_not_reached();
            break;
    }
}

WSLUA_CONSTRUCTOR Listener_new(lua_State* L) {
    /* Creates a new Listener listener */
#define WSLUA_OPTARG_Listener_new_TAP 1 /* The name of this tap */
#define WSLUA_OPTARG_Listener_new_FILTER 2 /* A filter that when matches the tap.packet function gets called (use nil to be called for every packet) */

    const gchar* tap_type = luaL_optstring(L,WSLUA_OPTARG_Listener_new_TAP,"frame");
    const gchar* filter = luaL_optstring(L,WSLUA_OPTARG_Listener_new_FILTER,NULL);
    Listener tap;
    GString* error;

    tap = (Listener)g_malloc(sizeof(struct _wslua_tap));

    tap->name = g_strdup(tap_type);
    tap->filter = filter ? g_strdup(filter) : NULL;
    tap->extractor = wslua_get_tap_extractor(tap_type);
    tap->L = L;
    tap->packet_ref = LUA_NOREF;
    tap->draw_ref = LUA_NOREF;
    tap->init_ref = LUA_NOREF;

    /*
     * XXX - do all Lua taps require the protocol tree?  If not, it might
     * be useful to have a way to indicate whether any do.
     *
     * XXX - do any Lua taps require the columns?  If so, we either need
     * to request them for this tap, or do so if any Lua taps require them.
     */
    error = register_tap_listener(tap_type, tap, tap->filter, TL_REQUIRES_PROTO_TREE, lua_tap_reset, lua_tap_packet, lua_tap_draw);

    if (error) {
        g_free(tap->filter);
        g_free(tap->name);
        g_free(tap);
        /* WSLUA_ERROR(new_tap,"tap registration error"); */
        luaL_error(L,"Error while registering tap:\n%s",error->str);
        g_string_free(error,TRUE); /* XXX LEAK? */
    }

    pushListener(L,tap);
    WSLUA_RETURN(1); /* The newly created Listener listener object */
}

WSLUA_METHOD Listener_remove(lua_State* L) {
    /* Removes a tap listener */
    Listener tap = checkListener(L,1);

    if (!tap) return 0;

    remove_tap_listener(tap);

    return 0;
}

WSLUA_METAMETHOD Listener__tostring(lua_State* L) {
    /* Generates a string of debug info for the tap listener */
    Listener tap = checkListener(L,1);
    gchar* str;

    if (!tap) return 0;

    str = ep_strdup_printf("Listener(%s) filter: %s",tap->name, tap->filter ? tap->filter : "NONE");
    lua_pushstring(L,str);

    return 1;
}


static int Listener__newindex(lua_State* L) {
    /* WSLUA_ATTRIBUTE Listener_packet WO A function that will be called once every packet matches the Listener listener filter.

        function tap.packet(pinfo,tvb,tapinfo) ... end
        Note: tapinfo is a table of info based on the Listener's type, or nil.
    */
    /* WSLUA_ATTRIBUTE Listener_draw WO A function that will be called once every few seconds to redraw the gui objects;
                in tshark this funtion is called only at the very end of the capture file.

        function tap.draw() ... end
    */
    /* WSLUA_ATTRIBUTE Listener_reset WO A function that will be called at the end of the capture run.

        function tap.reset() ... end
    */
    Listener tap = shiftListener(L,1);
    const gchar* idx = lua_shiftstring(L,1);
    int* refp = NULL;

    if (!idx) return 0;

    if (g_str_equal(idx,"packet")) {
        refp = &(tap->packet_ref);
    } else if (g_str_equal(idx,"draw")) {
        refp = &(tap->draw_ref);
    } else if (g_str_equal(idx,"reset")) {
        refp = &(tap->init_ref);
    } else {
        luaL_error(L,"No such attribute `%s' for a tap",idx);
        return 0;
    }

    if (! lua_isfunction(L,1)) {
        luaL_error(L,"Listener's attribute `%s' must be a function");
        return 0;
    }

    lua_pushvalue(L, 1);
    *refp = luaL_ref(L, LUA_REGISTRYINDEX);

    return 0;
}

static int Listener__gc(lua_State* L _U_) {
    /* do NOT free Listener, it's never free'd */
    return 0;
}

static const luaL_Reg Listener_methods[] = {
    {"new", Listener_new},
    {"remove", Listener_remove},
    { NULL, NULL }
};

static const luaL_Reg Listener_meta[] = {
    {"__tostring", Listener__tostring},
    {"__newindex", Listener__newindex},
    { NULL, NULL }
};

int Listener_register(lua_State* L) {
    wslua_set_tap_enums(L);
    WSLUA_REGISTER_CLASS(Listener);
    return 1;
}

