osmocom-bb/src/host/layer23/src/mobile/script_lua.c

695 lines
16 KiB
C

/* (C) 2017-2018 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* 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.
*
*/
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <osmocom/bb/common/osmocom_data.h>
#include <osmocom/bb/mobile/app_mobile.h>
#include <osmocom/bb/common/logging.h>
#include <osmocom/bb/mobile/primitives.h>
#include <osmocom/core/select.h>
#include <osmocom/vty/misc.h>
#include <sys/types.h>
#include <sys/socket.h>
struct timer_userdata {
int cb_ref;
};
struct fd_userdata {
struct lua_State *state;
struct osmo_fd fd;
int cb_ref;
};
static char lua_prim_key[] = "osmocom.org-mobile-prim";
static struct mobile_prim_intf *get_primitive(lua_State *L)
{
struct mobile_prim_intf *intf;
lua_pushlightuserdata(L, lua_prim_key);
lua_gettable(L, LUA_REGISTRYINDEX);
intf = (void *) lua_topointer(L, -1);
lua_pop(L, 1);
return intf;
}
static int lua_osmo_do_log(lua_State *L, int loglevel)
{
int argc = lua_gettop(L);
lua_Debug ar = { 0, };
int i;
lua_getstack(L, 1, &ar);
lua_getinfo(L, "nSl", &ar);
LOGPSRC(DLUA, loglevel, ar.source, ar.currentline, "%s", "");
for (i = 1; i <= argc; ++i) {
if (!lua_isstring(L, i))
continue;
LOGPSRCC(DLUA, loglevel, ar.source, ar.currentline, 1,
"%s%s", i > 1 ? "\t" : "", lua_tostring(L, i));
}
LOGPC(DLUA, loglevel, "\n");
return 0;
}
static int lua_osmo_print(lua_State *L)
{
return lua_osmo_do_log(L, LOGL_NOTICE);
}
static int lua_osmo_debug(lua_State *L)
{
return lua_osmo_do_log(L, LOGL_DEBUG);
}
static int lua_osmo_error(lua_State *L)
{
return lua_osmo_do_log(L, LOGL_ERROR);
}
static int lua_osmo_fatal(lua_State *L)
{
return lua_osmo_do_log(L, LOGL_FATAL);
}
static const struct luaL_Reg global_runtime[] = {
{ "print", lua_osmo_print },
{ "log_notice", lua_osmo_print },
{ "log_debug", lua_osmo_debug },
{ "log_error", lua_osmo_error },
{ "log_fatal", lua_osmo_fatal },
{ NULL, NULL },
};
/* Push table and function. Stack+=2 */
static bool load_cb(struct osmocom_ms *ms, const char *cb_name)
{
struct lua_State *L = ms->lua_state;
if (ms->lua_cb_ref == LUA_REFNIL)
return false;
lua_rawgeti(L, LUA_REGISTRYINDEX, ms->lua_cb_ref);
lua_pushstring(L, cb_name);
lua_gettable(L, -2);
if (lua_isnil(L, -1)) {
LOGP(DLUA, LOGL_DEBUG, "No handler for %s\n", cb_name);
lua_pop(L, 2);
return false;
}
return true;
}
/* Call callback. Stack-=func + args. func/args popped by lua_pcall */
static void call_cb(lua_State *L, int args)
{
int err = lua_pcall(L, args, 0, 0);
if (err) {
LOGP(DLUA, LOGL_ERROR, "lua error: %s\n", lua_tostring(L, -1));
lua_pop(L, 2);
}
}
static void handle_timeout(struct mobile_prim_intf *intf, struct mobile_timer_param *param)
{
struct timer_userdata *timer = (void *)(intptr_t) param->timer_id;
lua_State *L = intf->ms->lua_state;
int err;
lua_rawgeti(L, LUA_REGISTRYINDEX, timer->cb_ref);
luaL_unref(L, LUA_REGISTRYINDEX, timer->cb_ref);
timer->cb_ref = LUA_NOREF;
err = lua_pcall(L, 0, 0, 0);
if (err) {
LOGP(DLUA, LOGL_ERROR, "lua error: %s\n", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
static void handle_started(struct mobile_prim_intf *intf, struct mobile_started_param *param)
{
struct lua_State *L = intf->ms->lua_state;
if (!load_cb(intf->ms, "Started"))
return;
lua_pushinteger(L, param->started);
call_cb(L, 1);
lua_pop(L, 1);
}
static void handle_shutdown(struct mobile_prim_intf *intf, struct mobile_shutdown_param *param)
{
struct lua_State *L = intf->ms->lua_state;
if (!load_cb(intf->ms, "Shutdown"))
return;
lua_pushinteger(L, param->old_state);
lua_pushinteger(L, param->new_state);
call_cb(L, 2);
lua_pop(L, 1);
}
static void handle_sms(struct mobile_prim_intf *intf, struct mobile_sms_param *param)
{
struct lua_State *L = intf->ms->lua_state;
if (!load_cb(intf->ms, "Sms"))
return;
lua_createtable(L, 0, 11);
lua_pushinteger(L, param->sms.validity_minutes);
lua_setfield(L, -2, "validity_minutes");
lua_pushinteger(L, param->sms.reply_path_req);
lua_setfield(L, -2, "reply_path_req");
lua_pushinteger(L, param->sms.status_rep_req);
lua_setfield(L, -2, "status_rep_req");
lua_pushinteger(L, param->sms.ud_hdr_ind);
lua_setfield(L, -2, "ud_hdr_ind");
lua_pushinteger(L, param->sms.protocol_id);
lua_setfield(L, -2, "protocol_id");
lua_pushinteger(L, param->sms.data_coding_scheme);
lua_setfield(L, -2, "data_coding_scheme");
lua_pushinteger(L, param->sms.msg_ref);
lua_setfield(L, -2, "msg_ref");
lua_pushstring(L, param->sms.address);
lua_setfield(L, -2, "address");
lua_pushinteger(L, param->sms.time);
lua_setfield(L, -2, "time");
lua_pushlstring(L, (char *) param->sms.user_data, param->sms.user_data_len);
lua_setfield(L, -2, "user_data");
lua_pushstring(L, param->sms.text);
lua_setfield(L, -2, "text");
lua_pushinteger(L, param->cause_valid);
lua_pushinteger(L, param->cause);
call_cb(L, 3);
lua_pop(L, 1);
}
static void handle_mm(struct mobile_prim_intf *intf, struct mobile_mm_param *param)
{
lua_State *L = intf->ms->lua_state;
if (!load_cb(intf->ms, "Mm"))
return;
lua_pushinteger(L, param->state);
lua_pushinteger(L, param->substate);
lua_pushinteger(L, param->prev_substate);
call_cb(L, 3);
lua_pop(L, 1);
}
static int lua_osmo_timeout(lua_State *L)
{
struct mobile_prim *prim;
struct timer_userdata *timer;
if(lua_gettop(L) != 2) {
lua_pushliteral(L, "Need two arguments");
lua_error(L);
return 0;
}
luaL_argcheck(L, lua_isnumber(L, -2), 1, "Timeout needs to be a number");
luaL_argcheck(L, lua_isfunction(L, -1), 2, "Callback needs to be a function");
/*
* Create a handle to prevent the function to be GCed while we run the
* timer. Add a metatable to the object so itself will be GCed properly.
*/
timer = lua_newuserdata(L, sizeof(*timer));
luaL_getmetatable(L, "Timer");
lua_setmetatable(L, -2);
lua_pushvalue(L, -2);
timer->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
/* Take the address of the user data... */
prim = mobile_prim_alloc(PRIM_MOB_TIMER, PRIM_OP_REQUEST);
prim->u.timer.timer_id = (intptr_t) timer;
prim->u.timer.seconds = lua_tonumber(L, -3);
mobile_prim_intf_req(get_primitive(L), prim);
return 1;
}
static int lua_timer_cancel(lua_State *L)
{
struct mobile_prim *prim;
struct timer_userdata *timer;
luaL_argcheck(L, lua_isuserdata(L, -1), 1, "No userdata");
timer = lua_touserdata(L, -1);
if (timer->cb_ref != LUA_NOREF) {
luaL_unref(L, LUA_REGISTRYINDEX, timer->cb_ref);
timer->cb_ref = LUA_NOREF;
}
prim = mobile_prim_alloc(PRIM_MOB_TIMER_CANCEL, PRIM_OP_REQUEST);
prim->u.timer.timer_id = (intptr_t) timer;
mobile_prim_intf_req(get_primitive(L), prim);
return 0;
}
static const struct luaL_Reg timer_funcs[] = {
{ "cancel", lua_timer_cancel },
{ "__gc", lua_timer_cancel },
{ NULL, NULL },
};
static int lua_osmo_ms(lua_State *L)
{
lua_pushlightuserdata(L, get_primitive(L)->ms);
luaL_getmetatable(L, "MS");
lua_setmetatable(L, -2);
return 1;
}
static int lua_ms_imei(lua_State *L)
{
struct osmocom_ms *ms = get_primitive(L)->ms;
luaL_argcheck(L, lua_isuserdata(L, -1), 1, "No userdata");
lua_pushstring(L, ms->settings.imei);
return 1;
}
static int lua_ms_imsi(lua_State *L)
{
struct osmocom_ms *ms = get_primitive(L)->ms;
luaL_argcheck(L, lua_isuserdata(L, -1), 1, "No userdata");
lua_pushstring(L, ms->subscr.imsi);
return 1;
}
static int lua_ms_shutdown_state(lua_State *L)
{
struct osmocom_ms *ms = get_primitive(L)->ms;
lua_pushinteger(L, ms->shutdown);
return 1;
}
static int lua_ms_started(lua_State *L)
{
struct osmocom_ms *ms = get_primitive(L)->ms;
lua_pushinteger(L, ms->started);
return 1;
}
static int lua_ms_register(lua_State *L)
{
struct osmocom_ms *ms = get_primitive(L)->ms;
/* callbacks must be a table */
luaL_checktype(L, 2, LUA_TTABLE);
if (ms->lua_cb_ref != LUA_REFNIL)
luaL_unref(L, LUA_REGISTRYINDEX, ms->lua_cb_ref);
ms->lua_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
return 0;
}
static int lua_ms_no_shutdown(lua_State *L)
{
struct osmocom_ms *ms = get_primitive(L)->ms;
char *name;
int res;
res = mobile_start(ms, &name);
lua_pushinteger(L, res);
return 1;
}
static int lua_ms_shutdown(lua_State *L)
{
struct osmocom_ms *ms = get_primitive(L)->ms;
int argc = lua_gettop(L);
int force = 0;
int res;
if (argc >= 1) {
luaL_argcheck(L, lua_isboolean(L, -1), 1, "Force");
force = lua_toboolean(L, -1);
}
res = mobile_stop(ms, force);
lua_pushinteger(L, res);
return 1;
}
static int lua_ms_sms_send_simple(lua_State *L)
{
const char *sms_sca, *number, *text;
struct mobile_prim *prim;
struct gsm_sms *sms;
int msg_ref;
luaL_argcheck(L, lua_isnumber(L, -1), 4, "msg_ref needs to be a number");
luaL_argcheck(L, lua_isstring(L, -2), 3, "text must be a string");
luaL_argcheck(L, lua_isstring(L, -3), 2, "number must be a string");
luaL_argcheck(L, lua_isstring(L, -4), 1, "sms_sca must be a string");
msg_ref = (int) lua_tonumber(L, -1);
text = lua_tostring(L, -2);
number = lua_tostring(L, -3);
sms_sca = lua_tostring(L, -4);
prim = mobile_prim_alloc(PRIM_MOB_SMS, PRIM_OP_REQUEST);
sms = sms_from_text(number, 0, text);
if (!sms) {
lua_pushinteger(L, -ENOMEM);
msgb_free(prim->hdr.msg);
return 1;
}
prim->u.sms.sms = *sms;
prim->u.sms.sms.msg_ref = msg_ref;
osmo_strlcpy(prim->u.sms.sca, sms_sca, sizeof(prim->u.sms.sca));
mobile_prim_intf_req(get_primitive(L), prim);
lua_pushinteger(L, 0);
return 1;
}
static int lua_ms_name(lua_State *L)
{
lua_pushstring(L, get_primitive(L)->ms->name);
return 1;
}
static int lua_reselect_network(lua_State *L)
{
struct mobile_prim *prim;
prim = mobile_prim_alloc(PRIM_MOB_NETWORK_RESELECT, PRIM_OP_REQUEST);
mobile_prim_intf_req(get_primitive(L), prim);
return 1;
}
/* Expect a fd on the stack and enable SO_PASSCRED */
static int lua_unix_passcred(lua_State *L)
{
int one = 1;
int fd, rc;
luaL_argcheck(L, lua_isnumber(L, -1), 1, "needs to be a filedescriptor");
fd = (int) lua_tonumber(L, -1);
rc = setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
if (rc != 0)
LOGP(DLUA, LOGL_ERROR, "Failed to set SO_PASSCRED: %s\n",
strerror(errno));
lua_pushinteger(L, rc);
return 1;
}
static void lua_fd_do_unregister(lua_State *L, struct fd_userdata *fdu) {
/* Unregister the fd and forget about the callback */
osmo_fd_unregister(&fdu->fd);
if (fdu->cb_ref != LUA_NOREF) {
luaL_unref(L, LUA_REGISTRYINDEX, fdu->cb_ref);
fdu->cb_ref = LUA_NOREF;
}
}
static int lua_fd_cb(struct osmo_fd *fd, unsigned int what) {
struct fd_userdata *fdu;
lua_State *L;
int cb_ref;
int err;
if (!fd->data) {
LOGP(DLUA, LOGL_ERROR,
"fd callback for fd(%d) but no lua callback\n", fd->fd);
return 0;
}
fdu = fd->data;
L = fdu->state;
cb_ref = fdu->cb_ref;
lua_rawgeti(L, LUA_REGISTRYINDEX, cb_ref);
err = lua_pcall(L, 0, 1, 0);
if (err) {
LOGP(DLUA, LOGL_ERROR, "lua error: %s\n", lua_tostring(L, -1));
lua_pop(L, 1);
}
/* Check if we should continue */
luaL_argcheck(L, lua_isnumber(L, -1), 1, "needs to be a number");
if (lua_tonumber(L, -1) == 1)
return 0;
lua_fd_do_unregister(L, fdu);
return 0;
}
/* Register the fd */
static int lua_register_fd(lua_State *L)
{
struct fd_userdata *fdu;
int fd;
/* fd, cb */
luaL_argcheck(L, lua_isnumber(L, -2), 1, "needs to be a filedescriptor");
luaL_argcheck(L, lua_isfunction(L, -1), 2, "Callback needs to be a function");
/* Create a table so a user can unregister (and unregister on GC) */
fdu = lua_newuserdata(L, sizeof(*fdu));
fdu->state = L;
fdu->fd.fd = -1;
luaL_getmetatable(L, "Fd");
lua_setmetatable(L, -2);
/* Set the filedescriptor */
fd = (int) lua_tonumber(L, -3);
osmo_fd_setup(&fdu->fd, fd, OSMO_FD_READ, lua_fd_cb, fdu, 0);
/* Assuming that an error here will lead to a GC */
if (osmo_fd_register(&fdu->fd) != 0) {
fdu->cb_ref = LUA_NOREF;
lua_pushliteral(L, "Can't register the fd");
lua_error(L);
return 0;
}
/* Take the callback and keep a reference to it */
lua_pushvalue(L, -2);
fdu->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
return 1;
}
static int lua_fd_unregister(lua_State *L) {
struct fd_userdata *fdu;
luaL_argcheck(L, lua_isuserdata(L, -1), 1, "No userdata");
fdu = lua_touserdata(L, -1);
lua_fd_do_unregister(L, fdu);
return 0;
}
static const struct luaL_Reg fd_funcs[] = {
{ "unregister", lua_fd_unregister },
{ "__gc", lua_fd_unregister },
{ NULL, NULL },
};
static const struct luaL_Reg ms_funcs[] = {
{ "imsi", lua_ms_imsi },
{ "imei", lua_ms_imei },
{ "shutdown_state", lua_ms_shutdown_state },
{ "started", lua_ms_started },
{ "register", lua_ms_register },
{ "start", lua_ms_no_shutdown },
{ "stop", lua_ms_shutdown },
{ "sms_send_simple", lua_ms_sms_send_simple },
{ "number", lua_ms_name },
{ "reselect_network", lua_reselect_network },
{ NULL, NULL },
};
static const struct luaL_Reg osmo_funcs[] = {
{ "timeout", lua_osmo_timeout },
{ "unix_passcred", lua_unix_passcred },
{ "register_fd", lua_register_fd },
{ "ms", lua_osmo_ms },
{ NULL, NULL },
};
static void lua_prim_ind(struct mobile_prim_intf *intf, struct mobile_prim *prim)
{
switch (OSMO_PRIM_HDR(&prim->hdr)) {
case OSMO_PRIM(PRIM_MOB_TIMER, PRIM_OP_INDICATION):
handle_timeout(intf, (struct mobile_timer_param *) &prim->u.timer);
break;
case OSMO_PRIM(PRIM_MOB_STARTED, PRIM_OP_INDICATION):
handle_started(intf, (struct mobile_started_param *) &prim->u.started);
break;
case OSMO_PRIM(PRIM_MOB_SHUTDOWN, PRIM_OP_INDICATION):
handle_shutdown(intf, (struct mobile_shutdown_param *) &prim->u.shutdown);
break;
case OSMO_PRIM(PRIM_MOB_SMS, PRIM_OP_INDICATION):
handle_sms(intf, (struct mobile_sms_param *) &prim->u.sms);
break;
case OSMO_PRIM(PRIM_MOB_MM, PRIM_OP_INDICATION):
handle_mm(intf, (struct mobile_mm_param *) &prim->u.mm);
break;
default:
LOGP(DLUA, LOGL_ERROR, "Unknown primitive: %d\n", OSMO_PRIM_HDR(&prim->hdr));
};
}
/*
* Add functions to the global lua scope. Technically these are
* included in the _G table. The following lua code can be used
* to inspect it.
*
* > for n in pairs(_G) do print(n) end
*/
static void add_globals(lua_State *L)
{
lua_getglobal(L, "_G");
luaL_setfuncs(L, global_runtime, 0);
lua_pop(L, 1);
}
static void create_meta_table(lua_State *L, const char *name, const luaL_Reg *regs)
{
luaL_newmetatable(L, name);
lua_pushliteral(L, "__index");
lua_pushvalue(L, -2);
lua_rawset(L, -3);
luaL_setfuncs(L, regs, 0);
lua_pop(L, 1);
}
static void add_runtime(lua_State *L, struct mobile_prim_intf *intf)
{
add_globals(L);
/* Add a osmo module/table. Seems an oldschool module? */
lua_newtable(L);
luaL_setfuncs(L, osmo_funcs, 0);
lua_setglobal(L, "osmo");
/* Create metatables so we can GC objects... */
create_meta_table(L, "Timer", timer_funcs);
create_meta_table(L, "MS", ms_funcs);
create_meta_table(L, "Fd", fd_funcs);
/* Remember the primitive pointer... store it in the registry */
lua_pushlightuserdata(L, lua_prim_key);
lua_pushlightuserdata(L, intf);
lua_settable(L, LUA_REGISTRYINDEX);
}
static void *talloc_lua_alloc(void *ctx, void *ptr, size_t osize, size_t nsize)
{
if (nsize == 0) {
talloc_free(ptr);
return NULL;
}
return talloc_realloc_size(ctx, ptr, nsize);
}
int script_lua_close(struct osmocom_ms *ms)
{
struct mobile_prim_intf *intf;
if (!ms->lua_state)
return 0;
intf = get_primitive(ms->lua_state);
lua_close(ms->lua_state);
mobile_prim_intf_free(intf);
ms->lua_state = NULL;
return 0;
}
int script_lua_load(struct vty *vty, struct osmocom_ms *ms, const char *filename)
{
struct mobile_prim_intf *intf;
int err;
script_lua_close(ms);
ms->lua_state = lua_newstate(talloc_lua_alloc, ms);
if (!ms->lua_state)
return -1;
ms->lua_cb_ref = LUA_REFNIL;
luaL_openlibs(ms->lua_state);
intf = mobile_prim_intf_alloc(ms);
intf->indication = lua_prim_ind;
add_runtime(ms->lua_state, intf);
err = luaL_loadfilex(ms->lua_state, filename, NULL);
if (err) {
vty_out(vty, "%% LUA load error: %s%s",
lua_tostring(ms->lua_state, -1), VTY_NEWLINE);
lua_pop(ms->lua_state, 1);
return -2;
}
err = lua_pcall(ms->lua_state, 0, 0, 0);
if (err) {
vty_out(vty, "%% LUA execute error: %s%s",
lua_tostring(ms->lua_state, -1), VTY_NEWLINE);
lua_pop(ms->lua_state, 1);
return -3;
}
return 0;
}