/*
 *  wslua_dumper.c
 *
 * Wireshark's interface to the Lua Programming Language
 *
 * (c) 2006, Luis E. Garcia Ontanon <luis@ontanon.org>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <wiretap/wtap_opttypes.h>
#include <epan/wmem_scopes.h>

/* WSLUA_MODULE Dumper Saving Capture Files

   The classes/functions defined in this module are for using a `Dumper` object to
   make Wireshark save a capture file to disk. `Dumper` represents Wireshark's built-in
   file format writers (see the `wtap_name_to_file_type_subtype` function).

   (The `wtap_filetypes` table is deprecated, and should
   only be used in code that must run on Wireshark 3.4.3 and earlier 3.4
   releases or in Wireshark 3.2.11 and earlier 3.2.x releases.)

   To have a Lua script create its own file format writer, see the chapter titled
   "Custom file format reading/writing".
*/

#include "wslua.h"
#include <math.h>

WSLUA_CLASS_DEFINE(PseudoHeader,NOP);
/*
 A pseudoheader to be used to save captured frames.
 */

enum lua_pseudoheader_type {
    PHDR_NONE,
    PHDR_ETH,
    PHDR_X25,
    PHDR_ISDN,
    PHDR_ATM,
    PHDR_ASCEND,
    PHDR_P2P,
    PHDR_WIFI,
    PHDR_COSINE,
    PHDR_IRDA,
    PHDR_NETTL,
    PHDR_MTP2,
    PHDR_K12
};

struct lua_pseudo_header {
    enum lua_pseudoheader_type type;
    union wtap_pseudo_header* wph;
};

WSLUA_CONSTRUCTOR PseudoHeader_none(lua_State* L) {
    /*
     Creates a "no" pseudoheader.

    */
    PseudoHeader ph = (PseudoHeader)g_malloc(sizeof(struct lua_pseudo_header));
    ph->type = PHDR_NONE;
    ph->wph = NULL;

    pushPseudoHeader(L,ph);

    WSLUA_RETURN(1);
    /* A null pseudoheader */
}

WSLUA_CONSTRUCTOR PseudoHeader_eth(lua_State* L) {
    /*
     Creates an ethernet pseudoheader.
     */

#define WSLUA_OPTARG_PseudoHeader_eth_FCSLEN 1 /* The fcs length */

    PseudoHeader ph = (PseudoHeader)g_malloc(sizeof(struct lua_pseudo_header));
    ph->type = PHDR_ETH;
    ph->wph = (union wtap_pseudo_header *)g_malloc(sizeof(union wtap_pseudo_header));
    ph->wph->eth.fcs_len = (int)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_eth_FCSLEN,-1);

    pushPseudoHeader(L,ph);

    WSLUA_RETURN(1); /* The ethernet pseudoheader */
}

WSLUA_CONSTRUCTOR PseudoHeader_atm(lua_State* L) {
    /*
     Creates an ATM pseudoheader.
     */
#define WSLUA_OPTARG_PseudoHeader_atm_AAL 1 /* AAL number */
#define WSLUA_OPTARG_PseudoHeader_atm_VPI 2 /* VPI */
#define WSLUA_OPTARG_PseudoHeader_atm_VCI 3 /* VCI */
#define WSLUA_OPTARG_PseudoHeader_atm_CHANNEL 4 /* Channel */
#define WSLUA_OPTARG_PseudoHeader_atm_CELLS 5 /* Number of cells in the PDU */
#define WSLUA_OPTARG_PseudoHeader_atm_AAL5U2U 6 /* AAL5 User to User indicator */
#define WSLUA_OPTARG_PseudoHeader_atm_AAL5LEN 7 /* AAL5 Len */

    PseudoHeader ph = (PseudoHeader)g_malloc(sizeof(struct lua_pseudo_header));
    ph->type = PHDR_ATM;
    ph->wph = (union wtap_pseudo_header *)g_malloc(sizeof(union wtap_pseudo_header));
    ph->wph->atm.aal = (uint8_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_atm_AAL,5);
    ph->wph->atm.vpi = (uint16_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_atm_VPI,1);
    ph->wph->atm.vci = (uint16_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_atm_VCI,1);
    ph->wph->atm.channel = (uint16_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_atm_CHANNEL,0);
    ph->wph->atm.cells = (uint16_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_atm_CELLS,1);
    ph->wph->atm.aal5t_u2u = (uint16_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_atm_AAL5U2U,1);
    ph->wph->atm.aal5t_len = (uint16_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_atm_AAL5LEN,0);

    pushPseudoHeader(L,ph);
    WSLUA_RETURN(1);
    /* The ATM pseudoheader */
}

WSLUA_CONSTRUCTOR PseudoHeader_mtp2(lua_State* L) {
    /* Creates an MTP2 PseudoHeader. */
#define WSLUA_OPTARG_PseudoHeader_mtp2_SENT 1 /* True if the packet is sent, False if received. */
#define WSLUA_OPTARG_PseudoHeader_mtp2_ANNEXA 2 /* True if annex A is used.  */
#define WSLUA_OPTARG_PseudoHeader_mtp2_LINKNUM 3 /* Link Number. */
    PseudoHeader ph = (PseudoHeader)g_malloc(sizeof(struct lua_pseudo_header));
    ph->type = PHDR_MTP2;
    ph->wph = (union wtap_pseudo_header *)g_malloc(sizeof(union wtap_pseudo_header));
    ph->wph->mtp2.sent = (uint8_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_mtp2_SENT,0);
    ph->wph->mtp2.annex_a_used = (uint8_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_mtp2_ANNEXA,0);
    ph->wph->mtp2.link_number = (uint16_t)luaL_optinteger(L,WSLUA_OPTARG_PseudoHeader_mtp2_LINKNUM,0);

    pushPseudoHeader(L,ph);
    WSLUA_RETURN(1); /* The MTP2 pseudoheader */
}

#if 0
static int PseudoHeader_x25(lua_State* L) { luaL_error(L,"not implemented"); return 0; }
static int PseudoHeader_isdn(lua_State* L) { luaL_error(L,"not implemented"); return 0; }
static int PseudoHeader_ascend(lua_State* L) { luaL_error(L,"not implemented"); return 0; }
static int PseudoHeader_wifi(lua_State* L) { luaL_error(L,"not implemented"); return 0; }
static int PseudoHeader_cosine(lua_State* L) { luaL_error(L,"not implemented"); return 0; }
static int PseudoHeader_irda(lua_State* L) { luaL_error(L,"not implemented"); return 0; }
static int PseudoHeader_nettl(lua_State* L) { luaL_error(L,"not implemented"); return 0; }
static int PseudoHeader_k12(lua_State* L) { luaL_error(L,"not implemented"); return 0; }
#endif

/* Gets registered as metamethod automatically by WSLUA_REGISTER_CLASS/META */
static int PseudoHeader__gc(lua_State* L _U_) {
    /* do NOT free PseudoHeader */
    return 0;
}

WSLUA_METHODS PseudoHeader_methods[] = {
    WSLUA_CLASS_FNREG(PseudoHeader,mtp2),
    WSLUA_CLASS_FNREG(PseudoHeader,atm),
    WSLUA_CLASS_FNREG(PseudoHeader,eth),
    WSLUA_CLASS_FNREG(PseudoHeader,none),
    {0,0}
};

WSLUA_META PseudoHeader_meta[] = {
    {0,0}
};

int PseudoHeader_register(lua_State* L) {
    WSLUA_REGISTER_CLASS(PseudoHeader)
    return 0;
}


WSLUA_CLASS_DEFINE(Dumper,FAIL_ON_NULL("Dumper already closed"));

static GHashTable* dumper_encaps;
#define DUMPER_ENCAP(d) GPOINTER_TO_INT(g_hash_table_lookup(dumper_encaps,d))

static const char* cross_plat_fname(const char* fname) {
    static char fname_clean[256];
    char* f;

    (void) g_strlcpy(fname_clean,fname,255);
    fname_clean[255] = '\0';

    for(f = fname_clean; *f; f++) {
        switch(*f) {
            case '/': case '\\':
                *f = *(G_DIR_SEPARATOR_S);
                break;
            default:
                break;
        }
    }

    return fname_clean;
}

WSLUA_CONSTRUCTOR Dumper_new(lua_State* L) {
    /*
     Creates a file to write packets.
     `Dumper:new_for_current()` will probably be a better choice, especially for file types other than pcapng.
    */
#define WSLUA_ARG_Dumper_new_FILENAME 1 /* The name of the capture file to be created. */
#define WSLUA_OPTARG_Dumper_new_FILETYPE 2 /* The type of the file to be created - a number returned by `wtap_name_to_file_type_subtype()`. Defaults to pcapng.
                                              (The `wtap_filetypes` table
                                              is deprecated, and should only be used
                                              in code that must run on Wireshark 3.4.3 and earlier 3.4.x releases
                                              or in Wireshark 3.2.11 and earlier
                                              3.2.x releases.) */
#define WSLUA_OPTARG_Dumper_new_ENCAP 3 /* The encapsulation to be used in the file to be created - a number entry from the `wtap_encaps` table.
                                              Defaults to per-packet encapsulation for pcapng
                                              (which doesn't have file-level encapsulation;
                                              this will create IDBs on demand as necessary)
                                              and Ethernet encapsulation for other file types. */
    Dumper d;
    const char* fname = luaL_checkstring(L,WSLUA_ARG_Dumper_new_FILENAME);
    int filetype = (int)luaL_optinteger(L,WSLUA_OPTARG_Dumper_new_FILETYPE,wtap_pcapng_file_type_subtype());
    /* If we're writing pcapng, then WTAP_ENCAP_NONE and WTAP_ENCAP_PER_PACKET
     * generate a fake IDB on demand when the first packet for an encapsulation
     * type is written. Specifying any other encapsulation will generate a fake
     * IDB for that encapsulation upon opening even if there are no packets of
     * that type.
     * XXX - Default to PER_PACKET for any file type that supports it? */
    int encap  = (int)luaL_optinteger(L,WSLUA_OPTARG_Dumper_new_ENCAP, filetype == wtap_pcapng_file_type_subtype() ? WTAP_ENCAP_PER_PACKET : WTAP_ENCAP_ETHERNET);
    int err = 0;
    char *err_info = NULL;
    const char* filename = cross_plat_fname(fname);
    wtap_dump_params params = WTAP_DUMP_PARAMS_INIT;

    params.encap = encap;
    /* XXX - Check for an existing file, or the same file name as the current
     * capture file?
     */
    d = wtap_dump_open(filename, filetype, WTAP_UNCOMPRESSED, &params, &err,
                       &err_info);

    if (! d ) {
        /* WSLUA_ERROR("Error while opening file for writing"); */
        switch (err) {
        case WTAP_ERR_NOT_REGULAR_FILE:
            luaL_error(L,"The file \"%s\" is a \"special file\" or socket or other non-regular file",
                       filename);
            break;

        case WTAP_ERR_CANT_WRITE_TO_PIPE:
            luaL_error(L,"The file \"%s\" is a pipe, and %s capture files can't be written to a pipe",
                       filename, wtap_file_type_subtype_description(filetype));
            break;

        case WTAP_ERR_UNWRITABLE_FILE_TYPE:
            luaL_error(L,"Files of file type %s cannot be written",
                       wtap_file_type_subtype_description(filetype));
            break;

        case WTAP_ERR_UNWRITABLE_ENCAP:
            luaL_error(L,"Files of file type %s don't support encapsulation %s",
                       wtap_file_type_subtype_description(filetype),
                       wtap_encap_name(encap));
            break;

        case WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED:
            luaL_error(L,"Files of file type %s don't support per-packet encapsulation",
                       wtap_file_type_subtype_description(filetype));
            break;

        case WTAP_ERR_CANT_OPEN:
            luaL_error(L,"The file \"%s\" could not be created for some unknown reason",
                       filename);
            break;

        case WTAP_ERR_SHORT_WRITE:
            luaL_error(L,"A full header couldn't be written to the file \"%s\".",
                       filename);
            break;

        case WTAP_ERR_COMPRESSION_NOT_SUPPORTED:
            luaL_error(L,"Files of file type %s cannot be written as a compressed file",
                       wtap_file_type_subtype_description(filetype));
            break;

        case WTAP_ERR_INTERNAL:
            luaL_error(L,"An internal error occurred creating the file \"%s\" (%s)",
                       filename,
                       err_info != NULL ? err_info : "no information supplied");
            g_free(err_info);
            break;

        default:
            luaL_error(L,"error while opening \"%s\": %s",
                       filename,
                       wtap_strerror(err));
            break;
        }
        return 0;
    }

    g_hash_table_insert(dumper_encaps,d,GINT_TO_POINTER(encap));

    pushDumper(L,d);
    WSLUA_RETURN(1);
    /* The newly created Dumper object */
}

WSLUA_METHOD Dumper_close(lua_State* L) {
    /* Closes a dumper. */
    Dumper* dp = (Dumper*)luaL_checkudata(L, 1, "Dumper");
    int err;
    char *err_info;

    if (! *dp) {
        WSLUA_ERROR(Dumper_close,"Cannot operate on a closed dumper");
        return 0;
    }

    g_hash_table_remove(dumper_encaps,*dp);

    if (!wtap_dump_close(*dp, NULL, &err, &err_info)) {
        if (err_info != NULL) {
            luaL_error(L,"error closing: %s (%s)",
                       wtap_strerror(err), err_info);
            g_free(err_info);
        } else {
            luaL_error(L,"error closing: %s",
                       wtap_strerror(err));
        }
    }

    /* this way if we close a dumper any attempt to use it (for everything but GC) will yield an error */
    *dp = NULL;

    return 0;
}

WSLUA_METHOD Dumper_flush(lua_State* L) {
    /*
     Writes all unsaved data of a dumper to the disk.
     */
    Dumper d = checkDumper(L,1);
    int err;

    if (!d) return 0;

    if (!wtap_dump_flush(d, &err)) {
        luaL_error(L,"error while dumping: %s",
                   wtap_strerror(err));
    }

    return 0;
}

WSLUA_METHOD Dumper_dump(lua_State* L) {
    /*
     Dumps an arbitrary packet.
     Note: Dumper:dump_current() will fit best in most cases.
     */
#define WSLUA_ARG_Dumper_dump_TIMESTAMP 2 /* The absolute timestamp the packet will have. */
#define WSLUA_ARG_Dumper_dump_PSEUDOHEADER 3 /* The `PseudoHeader` to use. */
#define WSLUA_ARG_Dumper_dump_BYTEARRAY 4 /* The data to be saved */

    Dumper d = checkDumper(L,1);
    PseudoHeader ph;
    ByteArray ba;
    wtap_rec rec;
    double ts;
    int err;
    char *err_info;

    if (!d) return 0;

    ts = luaL_checknumber(L,WSLUA_ARG_Dumper_dump_TIMESTAMP);
    ph = checkPseudoHeader(L,WSLUA_ARG_Dumper_dump_PSEUDOHEADER);

    if (!ph) {
        WSLUA_ARG_ERROR(Dumper_dump,PSEUDOHEADER,"need a PseudoHeader");
        return 0;
    }

    ba = checkByteArray(L,WSLUA_ARG_Dumper_dump_BYTEARRAY);

    if (! ba) {
        WSLUA_ARG_ERROR(Dumper_dump,BYTEARRAY,"must be a ByteArray");
        return 0;
    }

    memset(&rec, 0, sizeof rec);

    rec.rec_type = REC_TYPE_PACKET;

    rec.presence_flags = WTAP_HAS_TS;
    rec.ts.secs  = (unsigned int)(floor(ts));
    rec.ts.nsecs = (unsigned int)(floor((ts - (double)rec.ts.secs) * 1000000000));

    rec.rec_header.packet_header.len       = ba->len;
    rec.rec_header.packet_header.caplen    = ba->len;
    rec.rec_header.packet_header.pkt_encap = DUMPER_ENCAP(d);
    if (ph->wph) {
        rec.rec_header.packet_header.pseudo_header = *ph->wph;
    }

    /* TODO: Can we get access to pinfo->rec->block here somehow? We
     * should be copying it to pkthdr.pkt_block if we can. */

    if (! wtap_dump(d, &rec, ba->data, &err, &err_info)) {
        switch (err) {

        case WTAP_ERR_UNWRITABLE_REC_DATA:
            luaL_error(L,"error while dumping: %s (%s)",
                       wtap_strerror(err), err_info);
            g_free(err_info);
            break;

        default:
            luaL_error(L,"error while dumping: %s",
                       wtap_strerror(err));
            break;
        }
    }

    return 0;

}

WSLUA_METHOD Dumper_new_for_current(lua_State* L) {
    /*
     Creates a capture file using the same encapsulation as the one of the current packet.
     */
#define WSLUA_OPTARG_Dumper_new_for_current_FILETYPE 2 /* The file type. Defaults to pcapng. */
    Dumper d;
    const char* fname = luaL_checkstring(L,1);
    int filetype = (int)luaL_optinteger(L,WSLUA_OPTARG_Dumper_new_for_current_FILETYPE,wtap_pcapng_file_type_subtype());
    int encap;
    int err = 0;
    char *err_info = NULL;
    const char* filename = cross_plat_fname(fname);
    wtap_dump_params params = WTAP_DUMP_PARAMS_INIT;

    if (! lua_pinfo ) {
        WSLUA_ERROR(Dumper_new_for_current,"Cannot be used outside a tap or a dissector");
        return 0;
    }

    if (lua_pinfo->rec->rec_type != REC_TYPE_PACKET) {
        return 0;
    }

    encap = lua_pinfo->rec->rec_header.packet_header.pkt_encap;
    params.encap = encap;
    d = wtap_dump_open(filename, filetype, WTAP_UNCOMPRESSED, &params, &err,
                       &err_info);

    if (! d ) {
        switch (err) {
        case WTAP_ERR_NOT_REGULAR_FILE:
            luaL_error(L,"The file \"%s\" is a \"special file\" or socket or other non-regular file",
                       filename);
            break;

        case WTAP_ERR_CANT_WRITE_TO_PIPE:
            luaL_error(L,"The file \"%s\" is a pipe, and %s capture files can't be written to a pipe",
                       filename, wtap_file_type_subtype_description(filetype));
            break;

        case WTAP_ERR_UNWRITABLE_FILE_TYPE:
            luaL_error(L,"Files of file type %s cannot be written",
                       wtap_file_type_subtype_description(filetype));
            break;

        case WTAP_ERR_UNWRITABLE_ENCAP:
            luaL_error(L,"Files of file type %s don't support encapsulation %s",
                       wtap_file_type_subtype_description(filetype),
                       wtap_encap_name(encap));
            break;

        case WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED:
            luaL_error(L,"Files of file type %s don't support per-packet encapsulation",
                       wtap_file_type_subtype_description(filetype));
            break;

        case WTAP_ERR_CANT_OPEN:
            luaL_error(L,"The file \"%s\" could not be created for some unknown reason",
                       filename);
            break;

        case WTAP_ERR_SHORT_WRITE:
            luaL_error(L,"A full header couldn't be written to the file \"%s\".",
                       filename);
            break;

        case WTAP_ERR_COMPRESSION_NOT_SUPPORTED:
            luaL_error(L,"Files of file type %s cannot be written as a compressed file",
                       wtap_file_type_subtype_description(filetype));
            break;

        case WTAP_ERR_INTERNAL:
             luaL_error(L,"An internal error occurred creating the file \"%s\" (%s)",
                        filename,
                        err_info != NULL ? err_info : "no information supplied");
             g_free(err_info);
             break;

        default:
            luaL_error(L,"error while opening \"%s\": %s",
                       filename,
                       wtap_strerror(err));
            break;
        }
        return 0;
    }

    pushDumper(L,d);
    WSLUA_RETURN(1); /* The newly created Dumper Object */

}

WSLUA_METHOD Dumper_dump_current(lua_State* L) {
    /*
     Dumps the current packet as it is.
     */
    Dumper d = checkDumper(L,1);
    wtap_rec rec;
    const unsigned char* data;
    tvbuff_t* tvb;
    struct data_source *data_src;
    int err = 0;
    char *err_info;

    if (!d) return 0;

    if (! lua_pinfo ) {
        WSLUA_ERROR(Dumper_new_for_current,"Cannot be used outside a tap or a dissector");
        return 0;
    }

    if (lua_pinfo->rec->rec_type != REC_TYPE_PACKET) {
        return 0;
    }

    data_src = (struct data_source*) (lua_pinfo->data_src->data);
    if (!data_src)
        return 0;

    tvb = get_data_source_tvb(data_src);

    memset(&rec, 0, sizeof rec);

    rec.rec_type                           = REC_TYPE_PACKET;
    rec.presence_flags                     = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;
    rec.ts                                 = lua_pinfo->abs_ts;
    rec.rec_header.packet_header.len       = tvb_reported_length(tvb);
    rec.rec_header.packet_header.caplen    = tvb_captured_length(tvb);
    rec.rec_header.packet_header.pkt_encap = lua_pinfo->rec->rec_header.packet_header.pkt_encap;
    rec.rec_header.packet_header.pseudo_header = *lua_pinfo->pseudo_header;

    /*
     * wtap_dump does not modify rec.block, so it should be possible to
     * pass epan_get_modified_block() or lua_pinfo->rec->block directly.
     * Temporarily duplicating the memory should not hurt though.
     */
    if (lua_pinfo->fd->has_modified_block) {
        rec.block = epan_get_modified_block(lua_pinfo->epan, lua_pinfo->fd);
        rec.block_was_modified = true;
    } else {
        rec.block = lua_pinfo->rec->block;
    }

    data = (const unsigned char *)tvb_memdup(lua_pinfo->pool,tvb,0,rec.rec_header.packet_header.caplen);

    if (! wtap_dump(d, &rec, data, &err, &err_info)) {
        switch (err) {

        case WTAP_ERR_UNWRITABLE_REC_DATA:
            luaL_error(L,"error while dumping: %s (%s)",
                       wtap_strerror(err), err_info);
            g_free(err_info);
            break;

        default:
            luaL_error(L,"error while dumping: %s",
                       wtap_strerror(err));
            break;
        }
    }

    return 0;
}

/* Gets registered as metamethod automatically by WSLUA_REGISTER_CLASS/META */
static int Dumper__gc(lua_State* L) {
    Dumper* dp = (Dumper*)luaL_checkudata(L, 1, "Dumper");
    int err;
    char *err_info;

    /* If we are Garbage Collected it means the Dumper is no longer usable. Close it */

    if (! *dp)
        return 0; /* already closed, nothing to do! */

    g_hash_table_remove(dumper_encaps,*dp);

    if (!wtap_dump_close(*dp, NULL, &err, &err_info)) {
        if (err_info != NULL) {
            luaL_error(L,"error closing: %s (%s)",
                       wtap_strerror(err), err_info);
            g_free(err_info);
        } else {
            luaL_error(L,"error closing: %s",
                       wtap_strerror(err));
        }
    }

    return 0;
}


WSLUA_METHODS Dumper_methods[] = {
    WSLUA_CLASS_FNREG(Dumper,new),
    WSLUA_CLASS_FNREG(Dumper,new_for_current),
    WSLUA_CLASS_FNREG(Dumper,close),
    WSLUA_CLASS_FNREG(Dumper,flush),
    WSLUA_CLASS_FNREG(Dumper,dump),
    WSLUA_CLASS_FNREG(Dumper,dump_current),
    { NULL, NULL }
};

WSLUA_META Dumper_meta[] = {
    { NULL, NULL }
};

int Dumper_register(lua_State* L) {
    if (dumper_encaps != NULL) {
        g_hash_table_unref(dumper_encaps);
    }
    dumper_encaps = g_hash_table_new(g_direct_hash,g_direct_equal);
    WSLUA_REGISTER_CLASS(Dumper);
    return 0;
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=4 tabstop=8 expandtab:
 * :indentSize=4:tabSize=8:noTabs=true:
 */
