Select Git revision
automalua.c
automalua.c 41.21 KiB
/* Automa-Lua - xAAL Automata with Lua
* (c) 2019 Christophe Lohr <christophe.lohr@imt-atlantique.fr>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <termios.h>
#include <sys/timerfd.h>
#include <sys/queue.h>
#include <json-c/json.h>
#include <cbor.h>
#include <uuid/uuid.h>
#include <lua5.2/lua.h>
#include <lua5.2/lualib.h>
#include <lua5.2/lauxlib.h>
#include <xaal.h>
#define ALIVE_PERIOD 120
/****************************/
/* Chapter: data structures */
/****************************/
/**
* Section: Lua context
*/
/* List of lua states with script name */
typedef TAILQ_HEAD(luactxshead, luactxentry) luactxs_t;
typedef struct luactxentry {
lua_State* L;
char *script;
uuid_t baseaddr;
char *parameter;
unsigned dev_inc;
TAILQ_ENTRY(luactxentry) entries;
} luactx_t;
/* Retrieves a lua context by lua_State */
luactx_t *retrieve_luactx(luactxs_t *luactxs, lua_State *L) {
luactx_t *luactx;
TAILQ_FOREACH(luactx, luactxs, entries)
if (luactx->L == L)
return luactx;
return NULL;
}
/**
* Section: embedded devices
*/
/* List of embedded devices */
typedef LIST_HEAD(deviceshead, deviceentry) devices_t;
typedef struct deviceentry {
xAAL_devinfo_t devinfo;
luactx_t *luactx;
uuid_t *wanted_sources;
size_t wanted_sources_nb;
char **wanted_devTypes;
LIST_ENTRY(deviceentry) entries;
} device_t;
/* Retrieves a device by addr in the list */
device_t *retrieve_device(devices_t *devices, const uuid_t *addr) {
device_t *device;
LIST_FOREACH(device, devices, entries)
if (uuid_compare(device->devinfo.addr, *addr) == 0)
return device;
return NULL;
}
/*******************/
/* Chapter: Alarms */
/*******************/
/* List of alarms */
typedef CIRCLEQ_HEAD(alarmshead, alarmentry) alarms_t;
typedef struct alarmentry {
int delay;
uuid_t addr;
char *parameter;
lua_State* L;
CIRCLEQ_ENTRY(alarmentry) entries;
} alarm_t;
/* Synchronize timer on the next alarm to trigger */
void adjust_timer(alarms_t *alarms, int timerfd) {
struct itimerspec timerspec;
int delay;
if (alarms->cqh_first != (void *)alarms) /* Pickup first alarm's delay*/
delay = alarms->cqh_first->delay;
else /* Empty queue: unarms the timer. */
delay = 0;
timerspec.it_interval.tv_sec = 0;
timerspec.it_interval.tv_nsec = 0;
timerspec.it_value.tv_sec = delay;
timerspec.it_value.tv_nsec = 0;
if ( timerfd_settime(timerfd, 0, &timerspec, NULL) == -1 )
fprintf(xAAL_error_log, "Could not adjust timer for alarms: %s\n", strerror(errno));
}
/* Retrun how many seconds remain on this timer */
int remaining_timer(int timerfd) {
struct itimerspec timerspec;
if (timerfd_gettime(timerfd, &timerspec) == -1) {
fprintf(xAAL_error_log, "timerfd_gettime: %s\n", strerror(errno));
return 0;
} else
return timerspec.it_value.tv_sec;
}
/* Add an entry to the list of alarms */
/* The list is sorted by 'delay'; try to maintain this */
void alarms_queue_add(int delay, const uuid_t *addr, const char *parameter,
lua_State* L, alarms_t *alarms, int timerfd) {
alarm_t *al, *np;
int prev_delay = -1;
/* Build alarm data */
al = (alarm_t *)malloc(sizeof(alarm_t));
al->delay = delay;
uuid_copy(al->addr, *addr);
al->parameter = strdup(parameter);
al->L = L;
if (alarms->cqh_first == (void *)alarms) {
/* The list of alarms is empty */
CIRCLEQ_INSERT_HEAD(alarms, al, entries);
} else {
/* Synchronize the delay of the new alarm */
al->delay -= remaining_timer(timerfd);
if (al->delay < 0)
al->delay = 0;
/* Insert the new alarm at its place */
CIRCLEQ_FOREACH(np, alarms, entries) {
if ( (al->delay >= prev_delay) && (al->delay < np->delay) ) {
CIRCLEQ_INSERT_BEFORE(alarms, np, al, entries);
break;
}
prev_delay = np->delay;
}
}
/* Ajust timer if the alarm was inserted at the head */
adjust_timer(alarms, timerfd);
}
/* It's time to trigger an alarm */
void alarms_queue_run(alarms_t *alarms, int timerfd, luactxs_t *luactxs) {
alarm_t *np;
int delay;
if (alarms->cqh_first == (void *)alarms)
return; /* Empty queue, this should not happen */
delay = alarms->cqh_first->delay;
CIRCLEQ_FOREACH(np, alarms, entries) {
np->delay -= delay;
if (np->delay <= 0) {
CIRCLEQ_REMOVE(alarms, np, entries);
lua_getglobal(np->L, "xAAL_Lua_alarm_callback");
if ( !lua_isfunction(np->L, -1) ) {
luactx_t *luactx = retrieve_luactx(luactxs, np->L);
fprintf(xAAL_error_log,"Error: no xAAL_Lua_alarm_callback() in %s\n",
luactx ? luactx->script : "unknown Lua script");
exit(EXIT_FAILURE);
} else {
lua_pushlstring(np->L, (char*)np->addr, 16);
lua_pushstring(np->L, np->parameter);
lua_call(np->L, 2, 0);
}
free(np->parameter);
free(np);
}
}
/* In case one removed the first probe, adjust timer */
adjust_timer(alarms, timerfd);
}
/****************/
/* Chapter: Lua */
/****************/
/* Make it global to be used by C functions called by lua */
/* Ugly! How to avoid this? */
devices_t *p_embedded;
luactxs_t *p_luactxs;
uuid_t groupId;
xAAL_businfo_t *p_bus;
alarms_t *p_alarms;
int timerfd;
/* Converts a cbor item into a Lua data and push it on the stack.
* The (hidden) maxdepth parameter may prevents infinit recursion on
* self-referencing data structure... Awful but secure.
*/
#define MAXDEPTH 30
void lua_push_cbor_ex(lua_State *L, cbor_item_t *item, unsigned maxdepth) {
if (!maxdepth--) {
lua_pushnil(L);
return;
}
switch ( cbor_typeof(item) ) {
case CBOR_TYPE_UINT:
lua_pushinteger(L, cbor_get_int(item));
break;
case CBOR_TYPE_NEGINT:
lua_pushinteger(L, -cbor_get_int(item)-1);
break;
case CBOR_TYPE_BYTESTRING: {
size_t len;
unsigned char *bstr = xAAL_cbor_bytestring_dup(item, &len);
lua_pushlstring(L, (char *)bstr, len);
free(bstr);
break; }
case CBOR_TYPE_STRING: {
size_t len;
const char *str = xAAL_cbor_string_dup(item, &len);
lua_pushstring(L, str);
break; }
case CBOR_TYPE_ARRAY: {
size_t i, len = cbor_array_size(item);
lua_newtable(L);
for (i=0; i < len; i++) {
lua_pushnumber(L, i+1);
lua_push_cbor_ex(L, cbor_move(cbor_array_get(item, i)), maxdepth);
lua_settable(L, -3);
}
break; }
case CBOR_TYPE_MAP: {
size_t i, len = cbor_map_size(item);
struct cbor_pair *map = cbor_map_handle(item);
lua_newtable(L);
for (i=0; i < len; i++) {
lua_push_cbor_ex(L, map[i].key, maxdepth);
lua_push_cbor_ex(L, map[i].value, maxdepth);
lua_settable(L, -3);
}
break; }
case CBOR_TYPE_TAG:
lua_push_cbor_ex(L, cbor_move(cbor_tag_item(item)), maxdepth);
break;
case CBOR_TYPE_FLOAT_CTRL: {
if ( cbor_is_float(item) )
lua_pushnumber(L, cbor_float_get_float(item));
else {
switch ( cbor_ctrl_value(item) ) {
case CBOR_CTRL_NONE:
lua_pushnil(L); //?
break;
case CBOR_CTRL_FALSE:
lua_pushboolean(L, false);
break;
case CBOR_CTRL_TRUE:
lua_pushboolean(L, true);
break;
case CBOR_CTRL_NULL:
lua_pushnil(L);
break;
case CBOR_CTRL_UNDEF:
lua_pushnil(L); //?
break;
default:
lua_pushnil(L); //?
}
}
break; }
default:
lua_pushnil(L); //?
}
}
void lua_push_cbor(lua_State *L, cbor_item_t *obj) {
lua_push_cbor_ex(L, obj, MAXDEPTH);
}
/* Converts a lua data to a cbor data
* Tables are converted as an array if all key are numbers,
* otherwise they are converted as cbor maps
* Note: array intexes are shifted (lua starts at 1, cbor at 0) */
cbor_item_t *lua_to_cbor_ex(lua_State *L, int idx, unsigned maxdepth) {
if (!maxdepth--)
return cbor_new_undef();
switch (lua_type(L, idx)) {
case LUA_TBOOLEAN:
return cbor_build_bool(lua_toboolean(L, idx));
break;
case LUA_TNUMBER: {
double d = lua_tonumber(L, idx);
if (d==(long)d)
return xAAL_cbor_build_int(lua_tointeger(L, idx));
else
return xAAL_cbor_build_float(d);
break; }
case LUA_TSTRING:
return cbor_build_string(lua_tostring(L, idx));
break;
case LUA_TTABLE: {
cbor_item_t *cmap, *carray, *cval;
double d;
int isnum;
cmap = cbor_new_indefinite_map();
carray = cbor_new_indefinite_array();
lua_pushvalue(L, idx); // Copy reference of the table on top of the stack
lua_pushnil(L);
while (lua_next(L, -2)) {
cval = lua_to_cbor_ex(L, -1, maxdepth);
lua_pushvalue(L, -2); // copy 'key' to safely work on it
d = lua_tonumberx(L, -1, &isnum);
if ( isnum && ( d==(long)d ) )
//cbor_array_set(carray, lua_tonumber(L, -1) - 1, cval);
cbor_array_push(carray, cval);
else
cbor_map_add(cmap, (struct cbor_pair){ cbor_move(lua_to_cbor_ex(L, -1, maxdepth)), cbor_move(cval) });
lua_pop(L, 2); // pop value + copy of key, leaving original key
}
lua_pop(L, 1); // Remove copy of the table
// If cmap is empty, the the table was a pure array, return it
if (cbor_map_size(cmap) == 0) {
cbor_decref(&cmap);
return carray;
} else { // Else, insert carray content into cmap and return it
size_t i, len = cbor_array_size(carray);
for (i=0; i < len; i++)
cbor_map_add(cmap, (struct cbor_pair){ cbor_move(xAAL_cbor_build_int(i+1)), cbor_move(cbor_array_get(carray, i)) });
cbor_decref(&carray);
return cmap;
}
break;}
default:
return cbor_new_null();
}
}
cbor_item_t *lua_to_cbor(lua_State *L, int idx) {
return lua_to_cbor_ex(L, idx, MAXDEPTH);
}
/* Converts a lua table of string (just values)
* to a C array of string terminated by NULL */
char **lua_tostringarray(lua_State *L, int idx) {
char **ret;
unsigned i = 0;
if (!lua_istable(L, idx))
return NULL;
ret = (char **)malloc( (lua_rawlen(L,idx)+1) * sizeof(char*));
lua_pushvalue(L, idx);
lua_pushnil(L);
while (lua_next(L, -2)) {
ret[i++] = strdup(lua_tostring(L, -1));
lua_pop(L, 1);
}
lua_pop(L, 1);
ret[i] = NULL;
return ret;
}
void print_uuid(const char *prompt, uuid_t *uuid) {
if (uuid) {
char str[37];
uuid_unparse(*uuid, str);
printf("%s: %s\n", prompt, str);
} else
printf("%s: none\n", prompt);
}
/* Converts a lua table of bytestring[16] to a C array of uuid */
uuid_t *lua_to_wanted_sources(lua_State *L, int idx, size_t *nb) {
uuid_t *wanted_sources;
size_t len;
const char *str;
size_t table_len;
*nb = 0;
if (!lua_istable(L, idx))
return NULL;
table_len = lua_rawlen(L,idx);
if (table_len == 0)
return NULL;
wanted_sources = (uuid_t*)malloc(table_len*sizeof(uuid_t));
lua_pushvalue(L, idx);
lua_pushnil(L);
while (lua_next(L, -2)) {
str = lua_tolstring(L, -1, &len);
if (len == 16)
uuid_copy(wanted_sources[(*nb)++], (unsigned char*)str);
lua_pop(L, 1);
}
lua_pop(L, 1);
return wanted_sources;
}
/* Converts a lua table of uuid to cbor */
cbor_item_t *lua_to_cbor_targets(lua_State *L, int idx) {
cbor_item_t *ctargets;
const char *str;
size_t len;
if (!lua_istable(L, idx))
return NULL;
ctargets = cbor_new_definite_array(lua_rawlen(L,idx));
lua_pushvalue(L, idx);
lua_pushnil(L);
while (lua_next(L, -2)) {
str = lua_tolstring(L, -1, &len);
if (len == 16)
cbor_array_push(ctargets, cbor_move(cbor_build_bytestring((cbor_data)str, 16)));
lua_pop(L, 1);
}
lua_pop(L, 1);
return ctargets;
}
/* Gets an uuid, increments it by n (positive)
* and produces the resulting uuid */
void uuid_inc(uuid_t uu_dst, const uuid_t uu_src, unsigned n) {
int i;
for (i=15; i >= 0; i--) {
n += uu_src[i];
uu_dst[i] = n & 0xFF;
n >>= 8;
}
}
/* Helper for Lua script: xAAL_Lua_declare_device() */
/* Expected parameter is a table with: addr(bytestring[16]) devType(string)
* vendorId(string) productId(string) version(string) url(string)
* info(string) unsupportedAttributes(table of strings)
* unsupportedMethods(idem) unsupportedNotifications (idem)
* If success, it returns the address of the registred device (bytestring[16]).
*/
int xAAL_Lua_declare_device(lua_State* L) {
device_t *newdev;
uuid_t addr;
luactx_t *luactx;
if (lua_gettop(L) != 1) {
fprintf(xAAL_error_log, "Error xAAL_Lua_declare_device(): expected one argument\n");
return 0;
}
if (!lua_istable(L,1)) {
fprintf(xAAL_error_log,"Error xAAL_Lua_declare_device(): expected a table as argument\n");
return 0;
}
luactx = retrieve_luactx(p_luactxs, L);
if (luactx == NULL) {
fprintf(xAAL_error_log,"Error xAAL_Lua_declare_device(): invalid Lua context\n");
return 0;
}
uuid_clear(addr);
lua_getfield(L, 1, "addr");
if (!lua_isnil(L, -1) && lua_isstring(L, -1)) {
size_t len;
const char *str = lua_tolstring(L, -1, &len);
if (len == 16)
uuid_copy(addr, (unsigned char*)str);
}
lua_pop(L, 1);
if (uuid_is_null(addr)) {
if ( luactx->dev_inc == 0 )
uuid_copy(addr, luactx->baseaddr);
else
uuid_inc(addr, luactx->baseaddr, luactx->dev_inc);
newdev = NULL;
} else {
newdev = retrieve_device(p_embedded, &addr);
}
if (uuid_is_null(groupId))
uuid_generate(groupId);
if (newdev == NULL) {
newdev = (device_t *)malloc(sizeof(device_t));
newdev->luactx = luactx;
uuid_copy(newdev->devinfo.addr, addr);
xAAL_add_wanted_target(&addr, p_bus);
newdev->devinfo.groupId = &groupId;
newdev->devinfo.hwId = NULL;
newdev->luactx->dev_inc++;
newdev->wanted_sources = NULL;
newdev->wanted_sources_nb = 0;
newdev->wanted_devTypes = NULL;
LIST_INSERT_HEAD(p_embedded, newdev, entries);
}
lua_getfield(L, 1, "devType");
if (!lua_isnil(L, -1) && lua_isstring(L, -1))
newdev->devinfo.devType = strdup(lua_tostring(L, -1));
else
newdev->devinfo.devType = "basic.basic";
lua_pop(L, 1);
newdev->devinfo.alivemax = ALIVE_PERIOD * 2;
lua_getfield(L, 1, "vendorId");
if (!lua_isnil(L, -1) && lua_isstring(L, -1))
newdev->devinfo.vendorId = strdup(lua_tostring(L, -1));
else
newdev->devinfo.vendorId = NULL;
lua_pop(L, 1);
lua_getfield(L, 1, "productId");
if (!lua_isnil(L, -1) && lua_isstring(L, -1))
newdev->devinfo.productId = strdup(lua_tostring(L, -1));
else
newdev->devinfo.productId = NULL;
lua_pop(L, 1);
lua_getfield(L, 1, "version");
if (!lua_isnil(L, -1) && lua_isstring(L, -1))
newdev->devinfo.version = strdup(lua_tostring(L, -1));
else
newdev->devinfo.version = NULL;
lua_pop(L, 1);
lua_getfield(L, 1, "url");
if (!lua_isnil(L, -1) && lua_isstring(L, -1))
newdev->devinfo.url = strdup(lua_tostring(L, -1));
else
newdev->devinfo.url = NULL;
lua_pop(L, 1);
lua_getfield(L, 1, "info");
if (!lua_isnil(L, -1) && lua_isstring(L, -1))
newdev->devinfo.info = strdup(lua_tostring(L, -1));
else
newdev->devinfo.info = NULL;
lua_pop(L, 1);
lua_getfield(L, 1, "unsupportedAttributes");
if (!lua_isnil(L, -1) && lua_istable(L, -1))
newdev->devinfo.unsupportedAttributes = lua_tostringarray(L, -1);
else
newdev->devinfo.unsupportedAttributes = NULL;
lua_pop(L, 1);
lua_getfield(L, 1, "unsupportedMethods");
if (!lua_isnil(L, -1) && lua_istable(L, -1))
newdev->devinfo.unsupportedMethods = lua_tostringarray(L, -1);
else
newdev->devinfo.unsupportedMethods = NULL;
lua_pop(L, 1);
lua_getfield(L, 1, "unsupportedNotifications");
if (!lua_isnil(L, -1) && lua_istable(L, -1))
newdev->devinfo.unsupportedNotifications = lua_tostringarray(L, -1);
else
newdev->devinfo.unsupportedNotifications = NULL;
lua_pop(L, 1);
lua_pushlstring(L, (char*)newdev->devinfo.addr, 16);
return 1;
}
/* Helper for Lua script: xAAL_Lua_write_bus() */
/* Expected parameters are:
* device_addr(bytestring[16]),
* msgType(unsigned),
* action(string)
* body(table or bytestring (i.e. already cbor serialized content) ),
* targets(table of bytestring[16])
* Returns true if it success to send the message
*/
int xAAL_Lua_write_bus(lua_State* L) {
device_t *device;
const char *action;
xAAL_msgType_t msgType;
cbor_item_t *cbody, *ctargets;
bool r;
if (lua_gettop(L) != 5) {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): expected 5 arguments\n");
return 0;
}
if (!lua_isstring(L,1)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): expected a bytestring[16] as argument 1 (device addr uuid)\n");
return 0;
} else {
size_t len;
const char *str = lua_tolstring(L, 1, &len);
if (len == 16) {
device = retrieve_device(p_embedded, (uuid_t*)str);
if (device == NULL) {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): unknown device\n");
return 0;
}
if (device->luactx->L != L) {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): device out of scope of the Lua script\n");
return 0;
}
} else {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): expected a bytestring[16] as argument 1 (device addr uuid)\n");
return 0;
}
}
if (!lua_isnumber(L,2)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): expected a number as argument 2 (msgType[0:notify|1:request|2:reply])\n");
return 0;
} else {
msgType = lua_tointeger(L, 2);
if (msgType<0 || msgType>xAAL_MSGTYPE_LAST) {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): invalid msgType:%d\n", msgType);
return 0;
}
}
if (!lua_isstring(L,3)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): expected a string as argument 3 (action)\n");
return 0;
} else {
action = lua_tostring(L, 3);
}
if (lua_istable(L,4)) {
if ( lua_rawlen(L, 4) == 0 )
cbody = NULL;
else
cbody = lua_to_cbor(L, 4);
} else if (lua_isstring(L,4)) {
size_t len;
const char *str = lua_tolstring(L, 4, &len);
if (len) {
struct cbor_load_result cresult;
cbody = cbor_load((cbor_data)str, len, &cresult);
if ( (cbody == NULL) && xAAL_error_log)
fprintf(xAAL_error_log, "CBOR error; code %d, position %lu\n", cresult.error.code, cresult.error.position);
} else {
cbody = NULL;
}
} else {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): expected a table or a string as argument 2 (body of the message)\n");
return 0;
}
if (!lua_istable(L,5)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_write_bus(): expected a table as argument 3 (targets)\n");
return 0;
} else {
if ( lua_rawlen(L, 5) == 0 )
ctargets = NULL;
else
ctargets = lua_to_cbor_targets(L, 5);
}
r = xAAL_write_bus(p_bus, &(device->devinfo), msgType, action, cbody, ctargets);
lua_pushboolean(L, r);
return 1;
}
/* Helper for Lua script: xAAL_Lua_filter_broadcast_by_source() */
/* Expected parameters are:
* address(string) of the device concerned by the filter
* a table of source addresses (string)
* Returns nothing
*/
int xAAL_Lua_filter_broadcast_by_source(lua_State* L) {
device_t *device;
if (lua_gettop(L) != 2) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_source(): expected 2 arguments\n");
return 0;
}
if (!lua_isstring(L,1)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_source(): expected a bytestring[16] as argument 1 (device addr uuid)\n");
return 0;
} else {
size_t len;
const char *str = lua_tolstring(L, 1, &len);
device = (len == 16)? retrieve_device(p_embedded, (uuid_t*)str) : NULL;
if (device == NULL) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_source(): unknown device\n");
return 0;
}
if (device->luactx->L != L) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_source(): device out of scope of the Lua script\n");
return 0;
}
}
if (!lua_istable(L,2)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_source(): expected a table as argument 2 (sources)\n");
return 0;
} else {
free(device->wanted_sources);
if ( lua_rawlen(L, 2) == 0 )
device->wanted_sources = NULL;
else
device->wanted_sources = lua_to_wanted_sources(L, 2, &device->wanted_sources_nb);
}
return 0;
}
/* Helper for Lua script: xAAL_Lua_filter_broadcast_by_devType() */
/* Expected parameters are:
* address(string) of the device concerned by the filter
* a table of devTypes (string)
* Returns nothing
*/
int xAAL_Lua_filter_broadcast_by_devType(lua_State* L) {
device_t *device;
if (lua_gettop(L) != 2) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_devType(): expected 2 arguments\n");
return 0;
}
if (!lua_isstring(L,1)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_devType(): expected a bytestring[16] as argument 1 (device addr uuid)\n");
return 0;
} else {
size_t len;
const char *str = lua_tolstring(L, 1, &len);
device = (len == 16)? retrieve_device(p_embedded, (uuid_t*)str) : NULL;
if (device == NULL) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_devType(): unknown device\n");
return 0;
}
if (device->luactx->L != L) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_devType(): device out of scope of the Lua script\n");
return 0;
}
}
if (!lua_istable(L,2)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_filter_broadcast_by_devType(): expected a table as argument 2 (devTypes)\n");
return 0;
} else {
free(device->wanted_devTypes);
if ( lua_rawlen(L, 2) == 0 )
device->wanted_devTypes = NULL;
else
device->wanted_devTypes = lua_tostringarray(L, 2);
}
return 0;
}
/* Helper for Lua script: xAAL_Lua_set_alarm() */
/* Expected parameters are:
* number of seconds to trigger the alarm
* the address(string) of the device concerned by the alarm
* a parameter(string) to be passed back while triggering the alarm
* Returns nothing
*/
int xAAL_Lua_set_alarm(lua_State* L) {
int delay;
const char *parameter, *str;
uuid_t addr;
if (lua_gettop(L) != 3) {
fprintf(xAAL_error_log, "Error xAAL_Lua_set_alarm(): expected 3 arguments\n");
return 0;
}
if (!lua_isnumber(L, 1)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_set_alarm(): expected a number as argument 1 (seconds)\n");
return 0;
} else
delay = lua_tointeger(L, 1);
if (!lua_isstring(L, 2)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_set_alarm(): expected a bytestring[16] as argument 2 (device address uuid)\n");
return 0;
} else {
size_t len;
str = lua_tolstring(L, 2, &len);
if (len == 16)
uuid_copy(addr, (const unsigned char*)str);
else {
fprintf(xAAL_error_log, "Error xAAL_Lua_set_alarm(): expected a bytestring[16] as argument 2 (device address uuid)\n");
return 0;
}
}
if (!lua_isstring(L, 3)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_set_alarm(): expected a string as argument 2 (parameter)\n");
return 0;
} else
parameter = lua_tostring(L, 3);
alarms_queue_add(delay, &addr, parameter, L, p_alarms, timerfd);
return 0;
}
/*** My own minimal libuuid1 lua binding ***/
/* Push a new uuid on the stack */
int xAAL_Lua_uuid_generate(lua_State* L) {
uuid_t out;
uuid_generate(out);
lua_pushlstring(L, (const char*)out, 16);
return 1;
}
/* Expect a string of 36 chars, push an uuid */
int xAAL_Lua_uuid_parse(lua_State* L) {
const char *str;
size_t len;
uuid_t uuid;
if (lua_gettop(L) != 1) {
fprintf(xAAL_error_log, "Error xAAL_Lua_uuid_parse(): expected 1 argument\n");
return 0;
}
if (!lua_isstring(L, 1)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_uuid_parse(): expected a string[36] as argument 1\n");
return 0;
}
str = lua_tolstring(L, 1, &len);
if (len != 36) {
fprintf(xAAL_error_log, "Error xAAL_Lua_uuid_parse(): expected a string[36] as argument 1\n");
return 0;
}
if (uuid_parse(str, uuid) == -1) {
fprintf(xAAL_error_log, "Error xAAL_Lua_uuid_parse(): parsing error\n");
return 0;
}
lua_pop(L, 1);
lua_pushlstring(L, (const char*)uuid, 16);
return 1;
}
/* Expect a bytestring[16], push an string */
int xAAL_Lua_uuid_unparse(lua_State* L) {
const char *str;
size_t len;
char uuid[37];
if (lua_gettop(L) != 1) {
fprintf(xAAL_error_log, "Error xAAL_Lua_uuid_unparse(): expected 1 argument\n");
return 0;
}
if (!lua_isstring(L, 1)) {
fprintf(xAAL_error_log, "Error xAAL_Lua_uuid_unparse(): expected a bytestring[16] as argument 1\n");
return 0;
}
str = lua_tolstring(L, 1, &len);
if (len != 16) {
fprintf(xAAL_error_log, "Error xAAL_Lua_uuid_unparse(): expected a bytestring[16] as argument 1\n");
return 0;
}
uuid_unparse((const unsigned char*)str, uuid);
lua_pop(L, 1);
lua_pushstring(L, uuid);
return 1;
}
/* Add a Lua context in the list */
void add_lua_context(luactxs_t *luactxs, const char *script,
const char *baseaddr, const char *parameter) {
luactx_t *luactx;
luactx = (luactx_t*)malloc(sizeof(luactx_t));
luactx->script = strdup(script);
if ( !baseaddr || (uuid_parse(baseaddr, luactx->baseaddr) == -1) )
uuid_generate(luactx->baseaddr);
luactx->parameter = (parameter)? strdup(parameter) : NULL;
luactx->dev_inc = 0;
TAILQ_INSERT_TAIL(luactxs, luactx, entries);
}
/* Activates Lua contexts */
void activate_lua_contexts(luactxs_t *luactxs, devices_t *embedded) {
luactx_t *luactx;
TAILQ_FOREACH(luactx, luactxs, entries) {
luactx->L = luaL_newstate();
luaL_openlibs(luactx->L); // TODO: replace it by a secure sandbox...
// Provides environment info to the Lua script
lua_pushlstring(luactx->L, (char*)luactx->baseaddr, 16);
lua_setglobal(luactx->L, "xAAL_Lua_baseaddr");
lua_pushlstring(luactx->L, (char*)groupId, 16);
lua_setglobal(luactx->L, "xAAL_Lua_groupId");
lua_pushstring(luactx->L, luactx->parameter);
lua_setglobal(luactx->L, "xAAL_Lua_parameter");
// Registers functions to be used by the Lua script
lua_register(luactx->L, "xAAL_Lua_declare_device", xAAL_Lua_declare_device);
lua_register(luactx->L, "xAAL_Lua_write_bus", xAAL_Lua_write_bus);
lua_register(luactx->L, "xAAL_Lua_filter_broadcast_by_source", xAAL_Lua_filter_broadcast_by_source);
lua_register(luactx->L, "xAAL_Lua_filter_broadcast_by_devType", xAAL_Lua_filter_broadcast_by_devType);
lua_register(luactx->L, "xAAL_Lua_set_alarm", xAAL_Lua_set_alarm);
lua_register(luactx->L, "xAAL_Lua_uuid_generate", xAAL_Lua_uuid_generate);
lua_register(luactx->L, "xAAL_Lua_uuid_parse", xAAL_Lua_uuid_parse);
lua_register(luactx->L, "xAAL_Lua_uuid_unparse", xAAL_Lua_uuid_unparse);
// Starts the Lua script
if ( luaL_dofile(luactx->L, luactx->script ) != 0 ) {
device_t *device;
fprintf(stderr, "Error: %s\n", lua_tostring(luactx->L,-1));
LIST_FOREACH(device, embedded, entries)
if (device->luactx == luactx) {
LIST_REMOVE(device, entries);
free(device->wanted_sources);
free(device->wanted_devTypes);
free(device);
}
TAILQ_REMOVE(luactxs, luactx, entries);
lua_close(luactx->L);
free(luactx->script);
free(luactx->parameter);
free(luactx);
}
}
}
/*****************/
/* Chapter: xAAL */
/*****************/
void alive_sender(int sig) {
device_t *device;
LIST_FOREACH(device, p_embedded, entries)
if ( !xAAL_notify_alive(p_bus, &(device->devinfo)) )
fprintf(xAAL_error_log, "Could not send spontaneous alive notification.\n");
alarm(ALIVE_PERIOD);
}
/* Manage broadcast filters */
bool pass_filters(device_t *device, cbor_item_t *ctargets,
const uuid_t *source, const char *devType) {
if (cbor_array_size(ctargets) != 0)
return true;
if (device->wanted_sources) {
if ( xAAL_uuids_get_item(device->wanted_sources, device->wanted_sources_nb, source) == -1 )
return false;
}
if (device->wanted_devTypes) {
char **item;
for (item=device->wanted_devTypes; *item != NULL; item++)
if (strcmp(*item, devType) == 0)
break;
if (*item == NULL)
return false;
}
return true;
}
/* Manage received message */
void manage_msg(const xAAL_businfo_t *bus, devices_t *embedded) {
cbor_item_t *cbody, *ctargets;
char *devType, *action;
xAAL_msgType_t msgType;
uuid_t *source;
device_t *device;
bool call_lua;
if (!xAAL_read_bus(bus, &ctargets, &source, &devType, &msgType, &action, &cbody))
return;
/* Check if it is for any embedded */
LIST_FOREACH(device, embedded, entries) {
if (xAAL_targets_match(ctargets, &device->devinfo.addr)) {
call_lua = false;
/* Stadards requests. Manage directly. */
if (msgType == xAAL_REQUEST) {
if ( (strcmp(action, "isAlive") == 0)
&& xAAL_isAliveDevType_match(cbody, device->devinfo.devType) ) {
if ( !xAAL_notify_alive(bus, &(device->devinfo)) )
fprintf(xAAL_error_log, "Could not reply to isAlive\n");
} else if ( strcmp(action, "getDescription") == 0 ) {
if ( !xAAL_reply_getDescription(bus, &(device->devinfo), source) )
fprintf(xAAL_error_log, "Could not reply to getDescription\n");
} else
call_lua = true;
} else
call_lua = true;
//
call_lua &= pass_filters(device, ctargets, source, devType);
//
/* Pass message to Lua and call it */
if (call_lua) {
lua_getglobal(device->luactx->L, "xAAL_Lua_receive_callback");
if ( !lua_isfunction(device->luactx->L, -1) ) {
fprintf(xAAL_error_log,"Error: no xAAL_Lua_receive_callback() in %s\n", device->luactx->script);
exit(EXIT_FAILURE);
} else {
lua_pushlstring(device->luactx->L, (char*)device->devinfo.addr, 16);
lua_pushlstring(device->luactx->L, (char*)source, 16);
lua_pushstring(device->luactx->L, devType);
lua_pushinteger(device->luactx->L, msgType);
lua_pushstring(device->luactx->L, action);
lua_push_cbor(device->luactx->L, cbody);
lua_call(device->luactx->L, 6, 0);
}
}
}
}
xAAL_free_msg(ctargets, source, devType, action, cbody);
}
/*************************/
/* Chapter: User Options */
/*************************/
/* Options from cmdline or conffile */
typedef struct {
char *addr;
char *port;
int hops;
char *passphrase;
char *conffile;
bool immutable;
bool daemon;
char *logfile;
char *pidfile;
luactxs_t *luactxs;
} options_t;
/* Parse cmdline */
void parse_cmdline(int argc, char **argv, options_t *opts) {
int opt;
bool arg_error = false;
while ((opt = getopt(argc, argv, "a:p:h:s:g:c:idl:P:D:U:")) != -1) {
switch (opt) {
case 'a':
opts->addr = optarg;
break;
case 'p':
opts->port = optarg;
break;
case 'h':
opts->hops = atoi(optarg);
break;
case 's':
opts->passphrase = optarg;
break;
case 'g':
if ( uuid_parse(optarg, groupId) == -1 ) {
fprintf(stderr, "Warning: invalid uuid '%s'\n", optarg);
uuid_clear(groupId);
}
break;
case 'c':
opts->conffile = optarg;
break;
case 'i':
opts->immutable = true;
break;
case 'd':
opts->daemon = true;
break;
case 'l':
opts->logfile = optarg;
break;
case 'P':
opts->pidfile = optarg;
break;
default: /* '?' */
arg_error = true;
}
}
while (optind < argc)
add_lua_context(opts->luactxs, argv[optind++], NULL, NULL);
if (arg_error) {
fprintf(stderr, "Usage: %s [-a <addr>] [-p <port>] [-h <hops>] [-s <secret>] [-g <groupId>]\n"
" [-c <conffile>] [-d] [-l <logfile>] [-P <pidfile>]\n"
" [-D <dir>] [-U <baseurl>] [<scripts>...]\n"
"-a <addr> multicast IPv4 or IPv6 address of the xAAL bus\n"
"-p <port> UDP port of the xAAL bus\n"
"-h <hops> Hops limit for multicast packets\n"
"-s <secret> Secret passphrase\n"
"-g <groupId> UUID of the group of automatons; random by default\n"
"-c <conffile> Filename of the configuration file (cson format)\n"
" Use 'schemory.conf' by default\n"
"-i Immutable config file (do not re-write it)\n"
"-d Start as a daemon\n"
"-l <logfile> Filename to write errors; stderr by default\n"
"-P <pidfile> Filename to write pid; none by default\n"
"<scripts> Lua scripts to load\n", argv[0]);
exit(EXIT_FAILURE);
}
}
/* Read a config file (json format) */
void read_config(options_t *opts) {
struct json_object *jconf, *jaddr, *jport, *jhops, *jpassphrase, *jgroupid,
*jconffile, *jimmutable, *jdaemon, *jlogfile, *jpidfile,
*jautomata, *jautomaton, *jscript, *jbaseaddr, *jparameter;
int auto_len, i;
const char *script, *baseaddr, *parameter;
/* read file */
jconf = json_object_from_file(opts->conffile);
if (json_object_is_type(jconf, json_type_null)) {
fprintf(stderr, "Could not parse config file %s\n", opts->conffile);
return;
}
/* parse bus addr */
if (json_object_object_get_ex(jconf, "addr", &jaddr)
&& json_object_is_type(jaddr, json_type_string))
opts->addr = strdup(json_object_get_string(jaddr));
/* parse bus port */
if (json_object_object_get_ex(jconf, "port", &jport)
&& json_object_is_type(jport, json_type_string))
opts->port = strdup(json_object_get_string(jport));
/* parse bus hops */
if (json_object_object_get_ex(jconf, "hops", &jhops)
&& json_object_is_type(jhops, json_type_int))
opts->hops = json_object_get_int(jhops);
/* parse passphrase */
if (json_object_object_get_ex(jconf, "passphrase", &jpassphrase)
&& json_object_is_type(jpassphrase, json_type_string))
opts->passphrase = strdup(json_object_get_string(jpassphrase));
/* parse schemory xAAL address (uuid) */
if (json_object_object_get_ex(jconf, "groupId", &jgroupid)
&& json_object_is_type(jgroupid, json_type_string))
if ( uuid_parse(json_object_get_string(jgroupid), groupId) == -1 )
uuid_clear(groupId);
/* parse config file name */
if (json_object_object_get_ex(jconf, "conffile", &jconffile)
&& json_object_is_type(jconffile, json_type_string))
opts->conffile = strdup(json_object_get_string(jconffile));
/* parse immutable flag */
if (json_object_object_get_ex(jconf, "immutable", &jimmutable)
&& json_object_is_type(jimmutable, json_type_boolean))
opts->immutable = json_object_get_boolean(jimmutable);
/* parse daemon flag */
if (json_object_object_get_ex(jconf, "daemon", &jdaemon)
&& json_object_is_type(jdaemon, json_type_boolean))
opts->daemon = json_object_get_boolean(jdaemon);
/* parse pid file name */
if (json_object_object_get_ex(jconf, "pidfile", &jpidfile)
&& json_object_is_type(jpidfile, json_type_string))
opts->pidfile = strdup(json_object_get_string(jpidfile));
/* parse log file name */
if (json_object_object_get_ex(jconf, "logfile", &jlogfile)
&& json_object_is_type(jlogfile, json_type_string))
opts->logfile = strdup(json_object_get_string(jlogfile));
/* parse automata list */
if (json_object_object_get_ex(jconf, "automata", &jautomata)
&& json_object_is_type(jautomata, json_type_array)) {
auto_len = json_object_array_length(jautomata);
for (i=0; i<auto_len; i++) {
jautomaton = json_object_array_get_idx(jautomata, i);
if (json_object_is_type(jautomaton, json_type_object)
&& json_object_object_get_ex(jautomaton, "script", &jscript)
&& json_object_is_type(jscript, json_type_string)) {
script = json_object_get_string(jscript);
if (json_object_object_get_ex(jautomaton, "baseaddr", &jbaseaddr)
&& json_object_is_type(jbaseaddr, json_type_string))
baseaddr = json_object_get_string(jbaseaddr);
if (json_object_object_get_ex(jautomaton, "parameter", &jparameter)
&& json_object_is_type(jparameter, json_type_string))
parameter = json_object_get_string(jparameter);
else
parameter = NULL;
add_lua_context(opts->luactxs, script, baseaddr, parameter);
}
}
}
json_object_put(jconf);
}
/* Re-write config file (json format) */
void write_config(options_t *opts) {
struct json_object *jconf, *jautomata, *jautomaton;
luactx_t *luactx;
char uuid[37];
jconf = json_object_new_object();
jconf = json_object_new_object();
json_object_object_add(jconf, "addr", json_object_new_string(opts->addr));
json_object_object_add(jconf, "port", json_object_new_string(opts->port));
json_object_object_add(jconf, "hops", json_object_new_int(opts->hops));
json_object_object_add(jconf, "passphrase",json_object_new_string(opts->passphrase));
uuid_unparse(groupId, uuid);
json_object_object_add(jconf, "groupId", json_object_new_string(uuid));
json_object_object_add(jconf, "conffile", json_object_new_string(opts->conffile));
json_object_object_add(jconf, "immutable", json_object_new_boolean(opts->immutable));
json_object_object_add(jconf, "daemon", json_object_new_boolean(opts->daemon));
if (opts->logfile)
json_object_object_add(jconf, "logfile", json_object_new_string(opts->logfile));
if (opts->pidfile)
json_object_object_add(jconf, "pidfile", json_object_new_string(opts->pidfile));
jautomata = json_object_new_array();
TAILQ_FOREACH(luactx, opts->luactxs, entries) {
jautomaton = json_object_new_object();
json_object_object_add(jautomaton, "script", json_object_new_string(luactx->script));
uuid_unparse(luactx->baseaddr, uuid);
json_object_object_add(jautomaton, "baseaddr", json_object_new_string(uuid));
if (luactx->parameter)
json_object_object_add(jautomaton, "parameter", json_object_new_string(luactx->parameter));
json_object_array_add(jautomata, jautomaton);
}
json_object_object_add(jconf, "automata", jautomata);
if (json_object_to_file_ext(opts->conffile, jconf, JSON_C_TO_STRING_PRETTY
| JSON_C_TO_STRING_SPACED) == -1)
fprintf(xAAL_error_log, "Writing config file: %s\n", strerror(errno));
json_object_put(jconf);
}
/*****************/
/* Chapter: main */
/*****************/
/* Global variable for the handler */
options_t *opts;
/* Called at exit */
void terminate() {
if (!opts->immutable)
write_config(opts);
if (opts->pidfile)
unlink(opts->pidfile);
}
/* Handler for Ctrl-C &co. */
void cancel(int s) {
terminate();
exit(EXIT_SUCCESS);
}
/* Main */
int main(int argc, char* argv[]) {
devices_t embedded;
xAAL_businfo_t bus;
luactxs_t luactxs;
alarms_t alarms;
fd_set rfds, rfds_;
int fd_max;
uint64_t exp;
options_t options = { .addr=NULL, .port=NULL, .hops=-1, .passphrase=NULL,
.conffile="automalua.conf",
.immutable=false, .daemon=false, .logfile=NULL,
.pidfile=NULL, .luactxs=&luactxs };
uuid_clear(groupId);
LIST_INIT(&embedded);
TAILQ_INIT(&luactxs);
CIRCLEQ_INIT(&alarms);
p_embedded = &embedded;
p_bus = &bus;
p_luactxs = &luactxs;
p_alarms = &alarms;
timerfd = timerfd_create(CLOCK_REALTIME, 0);
if (timerfd == -1)
perror("Could not create timer for alarms");
/* Parse cmdline arguments */
parse_cmdline(argc, argv, &options);
/* Load config */
read_config(&options);
/* Manage logfile */
if (options.logfile) {
xAAL_error_log = fopen(options.logfile, "a");
if (xAAL_error_log == NULL) {
perror("Opening logfile");
xAAL_error_log = stderr;
}
} else
xAAL_error_log = stderr;
/* Join the xAAL bus */
if ( !options.addr || !options.port || !options.passphrase) {
fprintf(xAAL_error_log, "Please provide the address, the port and the passphrase of the xAAL bus.\n");
exit(EXIT_FAILURE);
} else if ( !xAAL_join_bus(options.addr, options.port, options.hops, 1, &bus) )
exit(EXIT_FAILURE);
/* Setup security of the bus */
bus.maxAge = 2*60; /*seconds*/;
bus.key = xAAL_pass2key(options.passphrase);
if (bus.key == NULL) {
fprintf(stderr, "Could not compute key from passphrase\n");
exit(EXIT_FAILURE);
}
/* Start as a daemon */
if (options.daemon && (daemon(1,1) == -1) )
fprintf(xAAL_error_log, "daemon: %s\n", strerror(errno));
/* Write pidfile */
{
FILE *pfile;
if (options.pidfile) {
pfile = fopen(options.pidfile, "w");
if (pfile == NULL)
fprintf(xAAL_error_log, "Opening pidfile: %s\n", strerror(errno));
else {
fprintf(pfile, "%d\n", getpid());
fclose(pfile);
}
}
}
/* Init Lua scripts */
activate_lua_contexts(&luactxs, &embedded);
/* Manage alive notifications */
{
struct sigaction act_alarm;
act_alarm.sa_handler = alive_sender;
act_alarm.sa_flags = ~SA_RESETHAND;
sigemptyset(&act_alarm.sa_mask);
sigaction(SIGALRM, &act_alarm, NULL);
alive_sender(0);
}
/* Manage Ctrl-C &co. */
opts = &options;
signal(SIGHUP, cancel);
signal(SIGINT, cancel);
signal(SIGQUIT, cancel);
signal(SIGTERM, cancel);
atexit(terminate);
/* prepare the fd set for the following select */
FD_ZERO(&rfds);
FD_SET(timerfd, &rfds);
fd_max = timerfd;
FD_SET(bus.sfd, &rfds);
fd_max = (fd_max > bus.sfd)? fd_max : bus.sfd;
/* main loop */
for (;;) {
rfds_ = rfds;
if ( (select(fd_max+1, &rfds_, NULL, NULL, NULL) == -1) && (errno != EINTR) )
fprintf(xAAL_error_log, "select: %s\n", strerror(errno));
/* An xAAL message from the bus */
if (FD_ISSET(bus.sfd, &rfds_))
manage_msg(&bus, &embedded);
/* It's time to trigger an alarm */
if (FD_ISSET(timerfd, &rfds_)) {
if ( read(timerfd, &exp, sizeof(uint64_t)) == -1 )
fprintf(xAAL_error_log, "Alarm timer: %s\n", strerror(errno));
alarms_queue_run(&alarms, timerfd, &luactxs);
}
}
}