--- /dev/null
+/* 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
+ *
+ * Flow API fow Lua.
+ *
+ * local flow = require("suricata.flow")
+ */
+
+#include "suricata-common.h"
+
+#include "util-lua-flowlib.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 f (flow) pointer */
+extern const char lua_ext_key_f[];
+static const char suricata_flow[] = "suricata:flow";
+
+struct LuaFlow {
+ Flow *f;
+};
+
+static int LuaFlowGC(lua_State *luastate)
+{
+ SCLogDebug("gc:start");
+ struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+ SCLogDebug("flow %p", s->f);
+ s->f = NULL;
+ SCLogDebug("gc:done");
+ return 0;
+}
+
+/** \internal
+ * \brief fill lua stack with flow id
+ * \param luastate the lua state
+ * \retval cnt number of data items placed on the stack
+ *
+ * Places: flow id (number)
+ */
+static int LuaFlowId(lua_State *luastate)
+{
+ struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+ if (s == NULL || s->f == NULL) {
+ LUA_ERROR("failed to get flow");
+ }
+
+ Flow *f = s->f;
+
+ int64_t id = FlowGetId(f);
+ lua_pushinteger(luastate, id);
+ return 1;
+}
+
+/** \internal
+ * \brief fill lua stack with AppLayerProto
+ * \param luastate the lua state
+ * \retval cnt number of data items placed on the stack
+ *
+ * Places: alproto as string (string), alproto_ts as string (string),
+ * alproto_tc as string (string), alproto_orig as string (string),
+ * alproto_expect as string (string)
+ */
+static int LuaFlowAppLayerProto(lua_State *luastate)
+{
+ struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+ if (s == NULL || s->f == NULL) {
+ LUA_ERROR("failed to get flow");
+ }
+
+ Flow *f = s->f;
+ lua_pushstring(luastate, AppProtoToString(f->alproto));
+ lua_pushstring(luastate, AppProtoToString(f->alproto_ts));
+ lua_pushstring(luastate, AppProtoToString(f->alproto_tc));
+ lua_pushstring(luastate, AppProtoToString(f->alproto_orig));
+ lua_pushstring(luastate, AppProtoToString(f->alproto_expect));
+ return 5;
+}
+
+/** \internal
+ * \brief fill lua stack with flow has alerts
+ * \param luastate the lua state
+ * \retval cnt number of data items placed on the stack
+ *
+ * Places: alerts (bool)
+ */
+static int LuaFlowHasAlerts(lua_State *luastate)
+{
+ struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+ if (s == NULL || s->f == NULL) {
+ LUA_ERROR("failed to get flow");
+ }
+
+ Flow *f = s->f;
+ lua_pushboolean(luastate, FlowHasAlerts(f));
+ return 1;
+}
+
+/** \internal
+ * \brief fill lua stack with flow stats
+ * \param luastate the lua state
+ * \retval cnt number of data items placed on the stack
+ *
+ * Places: ts pkts (number), ts bytes (number), tc pkts (number), tc bytes (number)
+ */
+static int LuaFlowStats(lua_State *luastate)
+{
+ struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+ if (s == NULL || s->f == NULL) {
+ LUA_ERROR("failed to get flow");
+ }
+
+ Flow *f = s->f;
+ lua_pushinteger(luastate, f->todstpktcnt);
+ lua_pushinteger(luastate, f->todstbytecnt);
+ lua_pushinteger(luastate, f->tosrcpktcnt);
+ lua_pushinteger(luastate, f->tosrcbytecnt);
+ return 4;
+}
+
+/** \internal
+ * \brief fill lua stack with flow timestamps
+ * \param luastate the lua state
+ * \retval cnt number of data items placed on the stack
+ *
+ * Places: seconds (number), seconds (number), microseconds (number),
+ * microseconds (number)
+ */
+static int LuaFlowTimestamps(lua_State *luastate)
+{
+ struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+ if (s == NULL || s->f == NULL) {
+ LUA_ERROR("failed to get flow");
+ }
+
+ Flow *f = s->f;
+ lua_pushnumber(luastate, (double)SCTIME_SECS(f->startts));
+ lua_pushnumber(luastate, (double)SCTIME_SECS(f->lastts));
+ lua_pushnumber(luastate, (double)SCTIME_USECS(f->startts));
+ lua_pushnumber(luastate, (double)SCTIME_USECS(f->lastts));
+ return 4;
+}
+
+static int LuaFlowTimestringIso8601(lua_State *luastate)
+{
+ struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+ if (s == NULL || s->f == NULL) {
+ LUA_ERROR("failed to get flow");
+ }
+
+ Flow *f = s->f;
+ char timebuf[64];
+ CreateIsoTimeString(f->startts, timebuf, sizeof(timebuf));
+ lua_pushstring(luastate, timebuf);
+ return 1;
+}
+
+/** \internal
+ * \brief legacy format as used by fast.log, http.log, etc.
+ */
+static int LuaFlowTimestringLegacy(lua_State *luastate)
+{
+ struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+ if (s == NULL || s->f == NULL) {
+ LUA_ERROR("failed to get flow");
+ }
+
+ Flow *f = s->f;
+ char timebuf[64];
+ CreateTimeString(f->startts, timebuf, sizeof(timebuf));
+ lua_pushstring(luastate, timebuf);
+ return 1;
+}
+
+/** \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 LuaFlowTuple(lua_State *luastate)
+{
+ struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+ if (s == NULL || s->f == NULL) {
+ LUA_ERROR("failed to get flow");
+ }
+ Flow *f = s->f;
+ int ipver = 0;
+ if (FLOW_IS_IPV4(f)) {
+ ipver = 4;
+ } else if (FLOW_IS_IPV6(f)) {
+ ipver = 6;
+ }
+ lua_pushinteger(luastate, ipver);
+ if (ipver == 0)
+ return 1;
+
+ char srcip[46] = "", dstip[46] = "";
+ if (FLOW_IS_IPV4(f)) {
+ PrintInet(AF_INET, (const void *)&(f->src.addr_data32[0]), srcip, sizeof(srcip));
+ PrintInet(AF_INET, (const void *)&(f->dst.addr_data32[0]), dstip, sizeof(dstip));
+ } else if (FLOW_IS_IPV6(f)) {
+ PrintInet(AF_INET6, (const void *)&(f->src.address), srcip, sizeof(srcip));
+ PrintInet(AF_INET6, (const void *)&(f->dst.address), dstip, sizeof(dstip));
+ }
+
+ lua_pushstring(luastate, srcip);
+ lua_pushstring(luastate, dstip);
+
+ /* proto and ports (or type/ code) */
+ lua_pushinteger(luastate, f->proto);
+ if (f->proto == IPPROTO_TCP || f->proto == IPPROTO_UDP) {
+ lua_pushinteger(luastate, f->sp);
+ lua_pushinteger(luastate, f->dp);
+ } else if (f->proto == IPPROTO_ICMP || f->proto == IPPROTO_ICMPV6) {
+ lua_pushinteger(luastate, f->icmp_s.type);
+ lua_pushinteger(luastate, f->icmp_s.code);
+ } else {
+ lua_pushinteger(luastate, 0);
+ lua_pushinteger(luastate, 0);
+ }
+ return 6;
+}
+
+static int LuaFlowGet(lua_State *luastate)
+{
+ Flow *f = LuaStateGetFlow(luastate);
+ if (f == NULL) {
+ LUA_ERROR("failed to get flow");
+ }
+
+ struct LuaFlow *s = (struct LuaFlow *)lua_newuserdata(luastate, sizeof(*s));
+ if (s == NULL) {
+ LUA_ERROR("failed to allocate userdata");
+ }
+ s->f = f;
+ luaL_getmetatable(luastate, suricata_flow);
+ lua_setmetatable(luastate, -2);
+ return 1;
+}
+
+static const luaL_Reg flowlib[] = {
+ // clang-format off
+ { "get", LuaFlowGet },
+ { NULL, NULL }
+ // clang-format on
+};
+
+static const luaL_Reg flowlib_meta[] = {
+ // clang-format off
+ { "id", LuaFlowId },
+ { "app_layer_proto", LuaFlowAppLayerProto },
+ { "has_alerts", LuaFlowHasAlerts },
+ { "stats", LuaFlowStats },
+ { "timestamps", LuaFlowTimestamps },
+ { "timestring_iso8601", LuaFlowTimestringIso8601 },
+ { "timestring_legacy", LuaFlowTimestringLegacy },
+ { "tuple", LuaFlowTuple },
+ { "__gc", LuaFlowGC },
+ { NULL, NULL }
+ // clang-format on
+};
+
+int LuaLoadFlowLib(lua_State *luastate)
+{
+ luaL_newmetatable(luastate, suricata_flow);
+ lua_pushvalue(luastate, -1);
+ lua_setfield(luastate, -2, "__index");
+ luaL_setfuncs(luastate, flowlib_meta, 0);
+
+ luaL_newlib(luastate, flowlib);
+ return 1;
+}