/* Copyright (C) 2025 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * 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
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * \file
 *
 * Packet API for Lua.
 *
 * local packet = require("suricata.packet")
 */

#include "suricata-common.h"

#include "util-lua-packetlib.h"

#include "app-layer-protos.h" /* Required by util-lua-common. */
#include "util-lua-common.h"
#include "util-lua.h"
#include "util-debug.h"
#include "util-print.h"

/* key for p (packet) pointer */
extern const char lua_ext_key_p[];
static const char suricata_packet[] = "suricata:packet";

struct LuaPacket {
    Packet *p;
};

static int LuaPacketGC(lua_State *luastate)
{
    SCLogDebug("gc:start");
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s != NULL) {
        SCLogDebug("packet %p", s->p);
        s->p = NULL;
    }
    SCLogDebug("gc:done");
    return 0;
}

static int LuaPacketPayload(lua_State *luastate)
{
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s == NULL || s->p == NULL) {
        LUA_ERROR("failed to get packet");
    }

    LuaPushStringBuffer(luastate, (const uint8_t *)s->p->payload, (size_t)s->p->payload_len);
    return 1;
}

static int LuaPacketPacket(lua_State *luastate)
{
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s == NULL || s->p == NULL) {
        LUA_ERROR("failed to get packet");
    }

    LuaPushStringBuffer(luastate, (const uint8_t *)GET_PKT_DATA(s->p), (size_t)GET_PKT_LEN(s->p));
    return 1;
}

static int LuaPacketPcapCnt(lua_State *luastate)
{
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s == NULL || s->p == NULL) {
        LUA_ERROR("failed to get packet");
    }

    lua_pushinteger(luastate, s->p->pcap_cnt);
    return 1;
}

/** \internal
 *  \brief legacy format as used by fast.log, http.log, etc.
 */
static int LuaPacketTimestringLegacy(lua_State *luastate)
{
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s == NULL || s->p == NULL) {
        LUA_ERROR("failed to get packet");
    }

    char timebuf[64];
    CreateTimeString(s->p->ts, timebuf, sizeof(timebuf));
    lua_pushstring(luastate, timebuf);
    return 1;
}

static int LuaPacketTimestringIso8601(lua_State *luastate)
{
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s == NULL || s->p == NULL) {
        LUA_ERROR("failed to get packet");
    }

    char timebuf[64];
    CreateIsoTimeString(s->p->ts, timebuf, sizeof(timebuf));
    lua_pushstring(luastate, timebuf);
    return 1;
}

static int LuaPacketTimestamp(lua_State *luastate)
{
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s == NULL || s->p == NULL) {
        LUA_ERROR("failed to get packet");
    }

    lua_pushnumber(luastate, (double)SCTIME_SECS(s->p->ts));
    lua_pushnumber(luastate, (double)SCTIME_USECS(s->p->ts));
    return 2;
}

/** \internal
 *  \brief fill lua stack with header info
 *  \param luastate the lua state
 *  \retval cnt number of data items placed on the stack
 *
 *  Places: ipver (number), src ip (string), dst ip (string), protocol (number),
 *          sp or icmp type (number), dp or icmp code (number).
 */
static int LuaPacketTuple(lua_State *luastate)
{
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s == NULL || s->p == NULL) {
        LUA_ERROR("failed to get packet");
    }
    Packet *p = s->p;

    int ipver = 0;
    if (PacketIsIPv4(p)) {
        ipver = 4;
    } else if (PacketIsIPv6(p)) {
        ipver = 6;
    }
    lua_pushinteger(luastate, ipver);
    if (ipver == 0)
        return 1;

    char srcip[46] = "", dstip[46] = "";
    if (PacketIsIPv4(p)) {
        PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), srcip, sizeof(srcip));
        PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), dstip, sizeof(dstip));
    } else if (PacketIsIPv6(p)) {
        PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), srcip, sizeof(srcip));
        PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), dstip, sizeof(dstip));
    }

    lua_pushstring(luastate, srcip);
    lua_pushstring(luastate, dstip);

    /* proto and ports (or type/code) */
    lua_pushinteger(luastate, p->proto);
    if (p->proto == IPPROTO_TCP || p->proto == IPPROTO_UDP) {
        lua_pushinteger(luastate, p->sp);
        lua_pushinteger(luastate, p->dp);

    } else if (p->proto == IPPROTO_ICMP || p->proto == IPPROTO_ICMPV6) {
        lua_pushinteger(luastate, p->icmp_s.type);
        lua_pushinteger(luastate, p->icmp_s.code);
    } else {
        lua_pushinteger(luastate, 0);
        lua_pushinteger(luastate, 0);
    }

    return 6;
}

/** \internal
 *  \brief get tcp/udp/sctp source port
 *  \param luastate the lua state
 */
static int LuaPacketSport(lua_State *luastate)
{
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s == NULL || s->p == NULL) {
        LUA_ERROR("failed to get packet");
    }
    Packet *p = s->p;

    switch (p->proto) {
        case IPPROTO_TCP:
        case IPPROTO_UDP:
        case IPPROTO_SCTP:
            lua_pushinteger(luastate, p->sp);
            break;
        default:
            LUA_ERROR("sp only available for tcp, udp and sctp");
    }

    return 1;
}

/** \internal
 *  \brief get tcp/udp/sctp dest port
 *  \param luastate the lua state
 */
static int LuaPacketDport(lua_State *luastate)
{
    struct LuaPacket *s = (struct LuaPacket *)lua_touserdata(luastate, 1);
    if (s == NULL || s->p == NULL) {
        LUA_ERROR("failed to get packet");
    }
    Packet *p = s->p;

    switch (p->proto) {
        case IPPROTO_TCP:
        case IPPROTO_UDP:
        case IPPROTO_SCTP:
            lua_pushinteger(luastate, p->dp);
            break;
        default:
            LUA_ERROR("dp only available for tcp, udp and sctp");
    }

    return 1;
}

static int LuaPacketGet(lua_State *luastate)
{
    Packet *p = LuaStateGetPacket(luastate);
    if (p == NULL) {
        LUA_ERROR("failed to get packet");
    }

    struct LuaPacket *s = (struct LuaPacket *)lua_newuserdata(luastate, sizeof(*s));
    if (s == NULL) {
        LUA_ERROR("failed to get userdata");
    }
    s->p = p;
    luaL_getmetatable(luastate, suricata_packet);
    lua_setmetatable(luastate, -2);
    return 1;
}

static const luaL_Reg packetlib[] = {
    // clang-format off
    { "get", LuaPacketGet },
    { NULL, NULL }
    // clang-format on
};

static const luaL_Reg packetlib_meta[] = {
    // clang-format off
    { "packet", LuaPacketPacket },
    { "payload", LuaPacketPayload },
    { "pcap_cnt", LuaPacketPcapCnt },
    { "timestring_legacy", LuaPacketTimestringLegacy },
    { "timestring_iso8601", LuaPacketTimestringIso8601 },
    { "timestamp", LuaPacketTimestamp },
    { "tuple", LuaPacketTuple },
    { "sp", LuaPacketSport },
    { "dp", LuaPacketDport },
    { "__gc", LuaPacketGC },
    { NULL, NULL }
    // clang-format on
};

int LuaLoadPacketLib(lua_State *luastate)
{
    luaL_newmetatable(luastate, suricata_packet);
    lua_pushvalue(luastate, -1);
    lua_setfield(luastate, -2, "__index");
    luaL_setfuncs(luastate, packetlib_meta, 0);

    luaL_newlib(luastate, packetlib);
    return 1;
}
