Barracuda Application Server C/C++ Reference
Native APIs, integration guides, and platform interfaces
Advanced Lua Bindings

Our online Lua Binding Generator is a good starting point for building your own Lua bindings. These bindings enable LSP pages and Lua scripts to call your own C functions.

‍A convenient way to test the examples below is to use the Mako Server. Mako can include Lua bindings either statically at compile time or dynamically at runtime. For dynamic loading, build the binding as a loadable Lua module, such as a DLL/shared library, and load it at runtime. The online Lua binding tutorial provides an introductory Lua binding tutorial and a walkthrough of loading modules dynamically, while the SOL2 C++ tutorial shows how to statically include Lua bindings at compile time. Note, for monolithic RTOS builds, Lua bindings must be included at compile time.

This page explains how to write manual Lua bindings, which are preferred for complex interactions such as working with objects on the C side. The Lua book provides a good introduction to designing Lua bindings.

This page explains:

  • How to design a Lua interface to a C object, such as a handle in C code or a C++ class.
  • How to release the global mutex for time-consuming code, enabling concurrent requests.
  • How to call Lua code asynchronously from C code.

Object-Oriented Access and Releasing the Global Mutex

This tutorial shows how to write a Lua interface to a C object. You can use the same method to export a C++ class to Lua. The tutorial also shows how to release the global mutex for C functions that may take some time to complete, enabling the web server to accept other HTTP requests while the C function is running.

The tutorial requires that you have an understanding of Lua user data and Lua metatables as described in the Lua book. An older version of the book is available online where you can read the two sections Object-Oriented Access and metatables.

The Lua interface to the C object will be as follows:

Dynamically create a Lua object that represents the C object.

local l2c = Lua2C.open()

The C object is created by calling the open function in the global Lua2C table. The Lua2C table and the open function are both part of this exercise. In other words, we will extend the Lua engine with this functionality.

The dynamically created object will have two callable functions.

l2c:fast()
l2c:slow()

The "fast" and "slow" methods each increment a counter when called and return the number of times that method has been called. The "fast" function returns immediately, while the "slow" function takes 5 seconds to execute. The "slow" function therefore releases the global mutex while processing, enabling other threads in the server to execute.

Lua can return multiple values so both the "fast" and "slow" functions return two Lua integers: fast() returns fastCount, slowCount, and slow() returns slowCount, fastCount. Both methods require the userdata returned by Lua2C.open() as self; a different object raises a Lua argument error. open() takes no arguments and returns one userdata. These teaching methods have no operational error result. For application bindings, raise an error for incorrect API use and return nil, error for recoverable operational failures.

Creating the Lua2C table and binding the 'open' function

We must start by creating a C function that registers our Lua2C table.

int luaopen_Lua2C(lua_State* L)
{
luaL_requiref(L, "Lua2C", installLua2C, TRUE);
return 1;
}

The luaopen_Lua2C function is the only global function in the C file, and it is the function you must call from your startup code – i.e. from the code creating the main Lua state. The name of the function can be any name, but we have opted to use a naming convention that is compatible with the Lua function require. Our C file can be compiled as a standalone module on operating systems that support DLLs/shared libraries, and loaded on demand by calling require("Lua2C").

Notice how we set the last argument in function luaL_requiref to TRUE. This construction registers the Lua2C table as a global variable and makes it possible to reference this table directly in the global namespace, without having to call require("Lua2C") from the Lua code.

In a typical embedded system, function luaopen_Lua2C will be called just after you create the main Lua state, and the C file with the Lua2C bindings will be compiled and linked into the system. In other words, it will not be loaded dynamically as a dynamic library.

/* blp is initialized and the server mutex is held. */
L = balua_create(&blp);
if(L)
lua_pop(L, luaopen_Lua2C(L)); /* Balance installation results. */
/* Handle NULL according to the host application's startup policy. */
#define balua_create(p)
Create a BAS Lua VM.
Definition: balua.h:108

Function luaopen_Lua2C returns a value designed for dynamic loading via require. This return value is not needed when the code is linked directly with the server. After creating the main Lua state "L", call luaopen_Lua2C and pop the values pushed by luaopen_Lua2C off the stack. This keeps the Lua stack balanced.

In function luaopen_Lua2C, luaL_requiref takes function installLua2C as an argument. The installLua2C function installs the "open" function in a new Lua table and returns this table. Lua will then register this table as "Lua2C".

static int installLua2C(lua_State *L)
{
static const luaL_Reg mdfTable[] = {
{"open", Lua2C_open},
{NULL, NULL}
};
luaL_newlib(L, mdfTable);
return 1;
} /* End */

Lua code can now call Lua2C.open() and a call to this function from Lua ends up in the Lua binding Lua2C_open.

static int Lua2C_open(lua_State *L)
{
Lua2C* l2c = (Lua2C*)lua_newuserdata(L, sizeof(Lua2C));
l2c->slowCnt = 0;
l2c->fastCnt = 0;
/* Get metatable for Lua2C */
if(luaL_newmetatable(L, LUA2C))
{ /* Failed getting meta i.e. first time this func is called. */
static const luaL_Reg Lua2CLib[] = {
{"fast", Lua2C_fast},
{"slow", Lua2C_slow},
{"__gc", Lua2C_gc},
{NULL, NULL}
};
/* Create metatable */
lua_pushvalue(L, -1); /* Push table (t) created by luaL_newmetatable */
lua_setfield(L, -2, "__index"); /* t.__index == t */
/* Set t.fast=Lua2C_fast, t.slow=Lua2C_slow, and t.__gc=Lua2C_gc */
luaL_setfuncs(L, Lua2CLib,0);
}
lua_setmetatable(L, -2); /* Set meta for Lua2C userdata */
return 1; /* Lua2C userdata */
} /* End */

The key to understanding this code is to read the Lua documentation for luaL_newmetatable and review how metatable.__index = metatable works. This function creates a new table the first time it is called and retrieves the table on subsequent calls. The table is set as a metatable for the Lua userdata, i.e. for the C object Lua2C. The metatable binds the three Lua functions, "slow", "fast", and "__gc" to the dynamically created Lua2C object, making it possible for Lua code to use the userdata and call functions "fast" and "slow". The "__gc" function is run by Lua when the garbage collector reclaims the memory for the userdata object. The "__gc" function is the destructor.

Lua code can now call the "fast" and "slow" functions. The following will print out "1 0" and "2 0".

local l2c = Lua2C.open()
print(l2c.fast(l2c))
print(l2c:fast()) -- Syntactic sugar for 'l2c.fast(l2c)'

This Lua example triggers the C function Lua2C_fast twice.

static int Lua2C_fast(lua_State *L)
{
Lua2C* l2c = (Lua2C*)luaL_checkudata(L,1,LUA2C);
l2c->fastCnt++;
lua_pushinteger(L, l2c->fastCnt);
lua_pushinteger(L, l2c->slowCnt);
return 2;
}

Function Lua2C_fast verifies that the first stack argument is a Lua2C object, typecasts the Lua userdata object to a Lua2C object, increments the counter, and pushes the two return values onto the Lua stack.

Releasing the Global Mutex

Function Lua2C_slow is similar to function Lua2C_fast. It performs the same checking, increments the counter, and pushes the two values onto the stack. The "slow" function also sleeps for 5 seconds, simulating a lengthy C call.

static int Lua2C_slow(lua_State *L)
{
Lua2C* l2c = (Lua2C*)luaL_checkudata(L,1,LUA2C);
Thread_sleep(5000); /* 5 secs */
l2c->slowCnt++;
lua_pushinteger(L, l2c->slowCnt);
lua_pushinteger(L, l2c->fastCnt);
return 2;
}
#define balua_releasemutex(m)
Release mutex m if it is not NULL.
Definition: balua.h:128
#define balua_getmutex(L)
Get the SoDisp mutex associated with the Lua VM.
Definition: balua.h:122
#define balua_setmutex(m)
Acquire mutex m, waiting if necessary.
Definition: balua.h:132
A mutual exclusion class.
Definition: ThreadLib.h:196

This code fetches the server's main mutex and releases the mutex just before sleeping for 5 seconds. The mutex is reclaimed immediately after the sleep completes. You must make sure the mutex is locked when working with the Lua state L. The mutex is always locked when the Lua binding starts, and it must be locked again when the binding returns.

When an LSP page calls the "slow" function, it will block for 5 seconds. This enables other threads in the server's pool to execute. An introduction to the thread mechanism in the server can be found in the introduction under section Thread Mapping and Coroutines. Many of the Barracuda Application Server's Lua bindings release the mutex when calling code that can block. Examples of such functions are the functions in the SQL library and functions in the response object such as response:write.

Each native thread in the server's thread pool is mapped to one Lua coroutine stack, enabling the server to handle many concurrent Lua stacks at the same time. The next section explains how to map a coroutine stack onto a native thread when calling Lua from C code.

Download Example Code

Download the source code for this example.

Automatic Mutex Wrapper Generation

Creating mutex-release code for many lengthy function calls is tedious. You may also have existing Lua bindings, or Lua bindings from the Internet, that you want to adapt quickly. Real Time Logic provides two scripts that automate this process.

mutex.lua: Mutex wrapper script generator

This script creates C code that wraps around the original functions. Each wrapper function releases the mutex, calls the original function, and locks the mutex when the original function returns.

Let's say you have the following function and you want to wrap this function with another function that releases the mutex while it runs.

int myfunction(int arg1, const char* arg2);

Create a file such as functions.txt and put all the function declarations you want to wrap into this file. In our case, we put the function declaration above into this file. The next step is to create the wrapper code. Run the mutex script as follows:

lua mutex.lua functions.txt my-generated-wrapper my-header.h

The Lua script mutex.lua parses the function declarations in functions.txt and produces my-generated-wrapper.h and my-generated-wrapper.c. The last argument passed into mutex.lua (my-header.h) is a header file that provides the original function declarations. This file is included in the output (my-generated-wrapper.h).

my-generated-wrapper.h:

#include "my-header.h"
int myfunction_L(lua_State* L, int arg1, const char* arg2);
#define myfunction(arg1, arg2) myfunction_L(L, arg1, arg2)

my-generated-wrapper.c:

#undef myfunction
int myfunction_L(lua_State* L, int arg1, const char* arg2)
{
int ret;
ThreadMutex* __tm = balua_getmutex(L);
balua_releasemutex(__tm);
ret = myfunction(arg1, arg2);
balua_setmutex(__tm);
return ret;
}

fext.lua: Function declaration extraction script

The file that lists all functions (functions.txt) can be created manually, or you can use fext.lua to parse your header files and automatically produce functions.txt.

$lua fext.lua
Usage: lua fext.lua [-p pattern] [-c cdata-blob-file] [-e exclude-file] input-files....

The optional pattern (-p) means that only files matching this pattern should be included. See Lua patterns for details.

You do not want to create wrappers for functions not being used by your Lua bindings. The optional -c option is for loading a "blob file" that is a concatenation of all C files that are part of your Lua binding library. When a function is found in the header file(s), the "blob file" is searched. The function is only included in functions.txt if it is found in the "blob file". You can, for example, create this file as follows: cat *.c > c-data-blob.txt

You may also want to limit the set of functions included. The -e option is for a file that lists all functions that should be excluded.

Download mutex.lua and fext.lua

For additional examples on how to use these two Lua scripts, see the build scripts (build.sh) for the MongoDB and PostgreSQL Modules.

Calling Lua Code Asynchronously from C Code

The Barracuda App Server enables multiple threads to call and execute Lua code. In the previous section, we explained how threads in the thread pool can simultaneously execute in the Lua VM instance by using coroutine thread stacks. Only one thread can execute at any time in the VM, but Lua scripts can call Lua bindings (C functions), which can then release the global mutex protecting the Lua VM. This construction enables multiple threads to cooperatively run the Lua VM instance.

Easy Solution

Calling Lua asynchronously from C code is more complicated than calling C code from Lua. The recommended design is explained below, but you may first want to consider an easier alternative. The Barracuda App Server provides an API for creating native threads in Lua. Lua code running in the native thread can repeatedly call a Lua-to-C function that blocks until data is available. The C side of the binding releases the global mutex as explained in the mutex section, uses an OS primitive to wait for data, reclaims the mutex, and returns the data to Lua. This pattern simulates an asynchronous call while keeping the design simpler.

local function myThreadFunc()
while true do -- endless loop
local data1,data2,data3 = myBlockingCFunction()
-- Send data as an event to the system by calling some function
end
end
local thread=ba.thread.create()
thread:run(myThreadFunc)

Recommended Solution

Let's revisit the diagram from the Thread Mapping introduction.

Thread Mapping

Suppose you have an asynchronous source that generates two integers, and you want to call a Lua function and pass those integers as arguments when the event occurs. If your C startup code created an LThreadMgr instance, you can use the Lua Thread Library to achieve this.

The following must be in your startup code. See Xedge's C code for an example.

LThreadMgr ltMgr;
LThreadMgr_constructor (....)

The following C example passes two integers to a global Lua function onNativeEvent(value1, value2). Define that function before submitting events. The event source must be a normal native thread, not an interrupt handler. Stop the event source before destroying ltMgr or the Lua VM.

#include "lxrc.h"
typedef struct
{
ThreadJob super;
int myvar1;
int myvar2;
} MyJob;
extern LThreadMgr ltMgr; /* Initialized by the host during startup. */
static void myCallback(ThreadJob* tj, int msgh, LThreadMgr* mgr)
{
MyJob* job = (MyJob*)tj;
lua_State* L = tj->Lt;
(void)mgr;
/* The worker holds the mutex. Push the function before its arguments. */
lua_getglobal(L, "onNativeEvent");
if(lua_isfunction(L, -1))
{
lua_pushinteger(L, job->myvar1);
lua_pushinteger(L, job->myvar2);
/* msgh reports callback errors; the worker clears the stack afterward. */
(void)lua_pcall(L, 2, 0, msgh);
}
/* The manager frees job after return. */
}
int myEvent(int var1, int var2)
{
ThreadMutex* m = HttpServer_getMutex(ltMgr.server);
MyJob* job = (MyJob*)ThreadJob_lcreate(sizeof(MyJob), myCallback);
if(!job)
return -1; /* Allocation failed; the event was not queued. */
job->myvar1 = var1;
job->myvar2 = var2;
/* This entry point is for a native thread that does not already own m. */
ThreadMutex_set(m);
(void)LThreadMgr_run(&ltMgr, (ThreadJob*)job);
ThreadMutex_release(m);
return 0; /* Queued, not a report of Lua callback completion. */
}
BA_API ThreadJob * ThreadJob_lcreate(size_t size, ThreadJob_LRun lrun)
Create a thread job designed to execute Lua code.
BA_API int LThreadMgr_run(LThreadMgr *o, ThreadJob *tj)
This function sends a thread job to an available idle thread, or queues the job if no threads are cur...
The global instance created by C code or a dynamic instance created by ba.thread.create
Definition: lxrc.h:138
HttpServer * server
The server object.
Definition: lxrc.h:143
A thread job created by ThreadJob_create or ThreadJob_lcreate.
Definition: lxrc.h:157
lua_State * Lt
Borrowed Lua thread, valid only during the job callback.
Definition: lxrc.h:161

ThreadJob_lcreate(size, callback) returns a job pointer or NULL on allocation failure. It allocates at least sizeof(ThreadJob) bytes; extra payload storage is uninitialized. After LThreadMgr_run, the manager owns the job. Its return is TRUE when an idle worker was signalled and FALSE when the job was queued for a busy pool. Both mean acceptance. Pending jobs may be discarded during shutdown, so keep their payload cleanup requirements simple.

References: LThreadMgr, ThreadJob, ThreadMutex, and SoDispMutex. See the Lua documentation for lua_pushinteger and lua_pcall.

The C file AsynchLua.c in the Xedge's source code directory shows how to implement a complete example. Here is a copy of the AsynchLua.c file:

/*
* This software may only be used in accordance with the terms and
* conditions stipulated in the corresponding license agreement under
* which it has been supplied, or, at your option, under the terms of
* the MIT License.
*
* The MIT License text follows below.
*
* No attribution or contribution is required when copying, modifying,
* or redistributing this file.
*
* MIT License:
* Copyright (c) 2025 Real Time Logic
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*
The following example is part of the Advanced Lua Bindings tutorial
and demonstrates how to call Lua code asynchronously from C
code. This example receives UDP messages on ports 8080 and 8090,
forwarding these events to Lua if the relevant Lua functions have
been created (example 1) and installed (example 2).
Introduction:
https://realtimelogic.com/ba/doc/en/C/reference/html/md_en_C_md_LuaBindings.html#AsynchC2Lua
NOTE:
A more rudimentary example than the two following examples can be
found in the source code led.c (Example 5).
Example 1:
The example listening on port 8080, referred to as 'ex1' below,
expects a global Lua function to be defined. If the function is
found, the example will call this function. To create this function,
add the following Lua code to the .preload script of a Lua
application using Xedge:
function _G.udpmsg(msg)
trace("Global func received:", msg)
end
You can also insert this code into an LSP page to experiment with
the incoming data. Repeatedly refreshing the LSP page will replace
the previous function with a new function.
Example 2:
The example listening on port 8090, referred to as 'ex2' below, expects
a Lua callback function to be installed. To install the callback
function, use the following code:
UDPTST.install(function(msg)
trace("CB received:", msg)
end)
You can insert this code into an LSP page as well. Calling the
UDPTST.install() function repeatedly will replace the previous
callback with the new callback.
The following Python script can be used for sending UDP broadcast
messages on port 8080 and generate UDP events for example 1. To test
example 2, change the port number in the Python script to 8090.
Here is the Python script:
#!/usr/bin/env python3
import socket
import time
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
client.settimeout(0.2)
cnt=1
while True:
message = "UDP Message %d" % cnt
cnt=cnt+1
client.sendto(message.encode('utf-8'), ("255.255.255.255", 8080))
print(message, flush=True)
time.sleep(1)
*/
#include "xedge.h"
#include <stdlib.h>
#define PORT_EX1 8080 /* UDP listen port number for Example 1 */
#define PORT_EX2 8090
#define MAXLINE 1024
/* The LThreadMgr configured in xedge.c */
extern LThreadMgr ltMgr;
/* The Socket Dispatcher (SoDisp) mutex protecting everything. */
ThreadMutex* soDispMutex;
/* Lua REGISTRY index reference to callback installed when calling
* UDPTST.install(). See function installUdpCallbackEx2() below.
*/
static int luaFuncRefEx2;
/* This callback is called by one of the threads managed by LThreadMgr
* when a job is taken off the queue and executed. The callback
* attempts to find the global Lua function 'udpmsg', and if the
* function is found, it will be executed.
*/
static void runLuaEx1(ThreadJob* job, int msgh, LThreadMgr* mgr)
{
lua_State* L = job->Lt;
lua_pushglobaltable(L);
lua_getfield(L, -1, "udpmsg");
if (lua_isfunction(L, -1))
{
lua_pushstring(L, (char*)(job + 1));
lua_pcall(L, 1, 0, msgh);
}
else
{
HttpTrace_printf(0, "Err: global lua function 'udpmsg' missing\n");
}
}
/* This callback is similar to runLuaEx1() above, but instead of
* looking up a global Lua function, it uses the Lua function
* referenced by the luaFuncRefEx2 variable. This variable is set when
* Lua code calls UDPTST.install().
*/
static void runLuaEx2(ThreadJob* job, int msgh, LThreadMgr* mgr)
{
if(luaFuncRefEx2)
{
lua_State* L = job->Lt;
lua_rawgeti(L, LUA_REGISTRYINDEX, luaFuncRefEx2);
baAssert(lua_isfunction(L, -1));
lua_pushstring(L, (char*)(job + 1));
lua_pcall(L, 1, 0, msgh);
}
else
{
HttpTrace_printf(0,"Err: Lua callback function 'udpmsg' not installed\n");
}
}
/* This is a socket dispatcher (SoDisp) callback function. The
* function is called by the SoDisp instance when we receive an UDP
* message. The function is responsible for reading socket data,
* creating a job by calling ThreadJob_lcreate, and dispatching the
* job by calling LThreadMgr_run. This function is used by both
* example 1 and example 2 code.
*/
static void asyncDispRecEv(SoDispCon* con, int port, ThreadJob_LRun callback)
{
char buffer[MAXLINE];
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = INADDR_ANY;
int n = recvfrom(con->httpSocket.hndl, (char *)buffer, MAXLINE-1,
0, ( struct sockaddr *) &sin,
&len);
if(n > 0)
{
ThreadJob* job;
buffer[n] = 0;
job=ThreadJob_lcreate(sizeof(ThreadJob)+n+1, callback);
if(job)
{
strcpy((char*)(job + 1), buffer); /* copy received message to job */
/* We do not need to lock the mutex since it was already
* locked by SoDisp. This code validates that the SoDisp
* thread is the owner.
*/
baAssert(ThreadMutex_isOwner(soDispMutex));
LThreadMgr_run(&ltMgr, job);
}
}
}
/* SoDisp callback for example 1
*/
static void asyncDispRecEvEx1(SoDispCon* con)
{
asyncDispRecEv(con, PORT_EX1, runLuaEx1);
}
/* SoDisp callback for example 2
*/
static void asyncDispRecEvEx2(SoDispCon* con)
{
asyncDispRecEv(con, PORT_EX2, runLuaEx2);
}
/* This function opens a UDP socket on a specified port number and
* performs the necessary steps for installing the socket in the
* socket dispatcher SoDisp. Although this function is required for
* this example to function properly, you do not need to comprehend
* the details of its implementation, since the main focus of this
* example is to demonstrate how to use the LThreadMgr.
*/
static void openServerSock(int port,SoDispCon_DispRecEv callback)
{
SoDispCon* con;
struct sockaddr_in servaddr;
int sockfd;
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
perror("socket creation failed");
exit(1);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(port);
if(bind(sockfd,(const struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)
{
perror("bind failed");
exit(2);
}
/* Quick and dirty; memory never released. */
con = (SoDispCon*)baMalloc(sizeof(SoDispCon));
SoDispCon_constructor(con, ltMgr.server->dispatcher, callback);
con->httpSocket.hndl = sockfd;
SoDisp_addConnection(ltMgr.server->dispatcher, con);
SoDisp_activateRec(ltMgr.server->dispatcher, con);
}
/* Open and install the two UDP socket objects used in this example.
*/
static void initSocketServer()
{
openServerSock(PORT_EX1, asyncDispRecEvEx1);
openServerSock(PORT_EX2, asyncDispRecEvEx2);
}
/* This Lua binding is registered by the xedgeOpenAUX() function below
* and is invoked when Lua code calls UDPTST.install(). The binding
* saves a reference to the specified function (argument 1) in the
* luaFuncRefEx2 variable, which is used by the runLuaEx2() function
* to push the function onto the Lua stack.
*/
static int installUdpCallbackEx2(lua_State* L)
{
if(lua_isfunction(L, 1))
{
if(luaFuncRefEx2)
{ /* Release old callback */
luaL_unref(L, LUA_REGISTRYINDEX, luaFuncRefEx2);
}
lua_settop(L, 1); /* Make sure we only have one arg. */
/* Save reference to function in registry */
luaFuncRefEx2=luaL_ref(L, LUA_REGISTRYINDEX);
}
else
luaL_typeerror(L, 1, "function");
return 0;
}
/* This function installs the Lua binding, enabling Lua code to call
* UDPTST.install(). The code employs standard Lua syntax, which is
* included in any literature that explains Lua bindings. The function
* is called by the Xedge startup code.
*
* NOTE: See also led.c: xedgeOpenAUX(), as this code enables additional
* Xedge features you may consider using.
*/
int xedgeOpenAUX(XedgeOpenAUX* aux)
{
soDispMutex = HttpServer_getMutex(ltMgr.server);
initSocketServer();
static const luaL_Reg reg[] = {
{"install", installUdpCallbackEx2},
{NULL, NULL}
};
luaL_newlib(aux->L, reg);
lua_setglobal(aux->L, "UDPTST");
return 0;
}
BA_API void HttpTrace_printf(int prio, const char *fmt,...)
Write data to the trace buffer.
void(* SoDispCon_DispRecEv)(struct SoDispCon *con)
Dispatcher receive notification, invoked with its mutex held.
Definition: SoDispCon.h:108
BA_API void SoDispCon_constructor(SoDispCon *o, struct SoDisp *dispatcher, SoDispCon_DispRecEv e)
Initialize an empty connection without opening a socket.
BA_API void SoDisp_activateRec(SoDisp *o, struct SoDispCon *con)
Enable receive events.
BA_API void SoDisp_addConnection(SoDisp *o, struct SoDispCon *con)
Register a connection without enabling events.
void * baMalloc(size_t size)
Allocate uninitialized storage using the target's configured allocator.
void(* ThreadJob_LRun)(struct ThreadJob *tj, int msgh, struct LThreadMgr *mgr)
ThreadJob callback designed for calling Lua code using lua_pcall.
Definition: lxrc.h:130
Contains information about the physical socket connection.
Definition: SoDispCon.h:120

Initializing the TPM

The example code led.c includes the xedgeOpenAUX() function, which shows how to add additional secrets to the TPM and enable a device-unique key.

/*
* This software may only be used in accordance with the terms and
* conditions stipulated in the corresponding license agreement under
* which it has been supplied, or, at your option, under the terms of
* the MIT License.
*
* The MIT License text follows below.
*
* No attribution or contribution is required when copying, modifying,
* or redistributing this file.
*
* MIT License:
* Copyright (c) 2025 - 2026 Real Time Logic
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*
Example Overview:
This code demonstrates how to:
1. Create Lua bindings to interface with C code
2. Utilize the custom Xedge configuration file API for
reading/writing the Xedge configuration file
3. Add additional secrets for TPM key generation
4. Integrate an embedded, read-only ZIP file system
5. Send the time event 'sntp' to the Xedge Lua code
*/
#include "xedge.h"
#include <sys/stat.h>
/* The LThreadMgr configured in xedge.c */
extern LThreadMgr ltMgr;
/*
Ex 1. Create Lua bindings to interface with C code.
The LED_ functions show how to create a very simple Lua binding for
controlling one LED.
Code below copied from:
https://tutorial.realtimelogic.com/Lua-Bindings.lsp
Lua binding tutorials:
https://tutorial.realtimelogic.com/Lua-Bindings.lsp
https://realtimelogic.com/ba/doc/en/introduction.html#UsingLSP
https://realtimelogic.com/ba/doc/en/C/reference/html/md_en_C_md_LuaBindings.html
Auto generation:
https://realtimelogic.com/swig/
*/
/********* Simulated HW **********/
static int ledState=0;
static void setLed(int val)
{
ledState=val;
printf("Set simulated LED %s\n", ledState ? "on" : "off");
}
static int getLed(void)
{
printf("Get simulated LED state (%s)\n", ledState ? "on" : "off");
return ledState;
}
/********* End simulated HW **********/
static int LED_setLed(lua_State* L)
{
int on = lua_toboolean(L, 1); /* val at stack position 1 */
setLed(on); /* Call the LED function that is interfacing the HW */
return 0; /* No Lua return value */
}
static int LED_getLed(lua_State* L)
{
/* Call the LED function that get's the LED state from the HW */
int on = getLed();
/* Push the on/off state onto the Lua stack */
lua_pushboolean(L, on);
return 1; /* Inform Lua that we have one return value */
}
/************** END LED API CODE (Ex 1) **************************/
/*
Ex 2. Utilize the custom Xedge configuration file API for
reading/writing the Xedge configuration file.
This optional feature can be utilized when the code is compiled
with NO_BAIO_DISK (no file system API) or to enhance the security
of the Xedge configuration file. When a file system is included
with Xedge, the configuration file is stored as a backup on the
file system. Enabling this security enhancement is recommended,
particularly when an Xedge-based firmware release includes the
Xedge IDE. If both the Xedge config file API and the file system
are enabled, the Xedge Lua code ensures that the configuration file
is replicated and automatically restores either file if it becomes
corrupt or missing. In this example, we store the configuration
file on the file system; however, for a firmware release build, we
recommend using a separate flash partition, ideally within the
microcontroller's flash memory, to maximize security.
Note: this file can be read only.
*/
static int xedgeCfgFile(lua_State* L)
{
FILE* fp;
static const char* fn = "Xedge-configuration-file";
if(lua_isstring(L, 1)) /* Write (optional feature) */
{
size_t size;
const char* data=lua_tolstring(L, 1, &size);
fp = fopen(fn, "wb");
if(fp)
{
fwrite(data, size, 1, fp);
fclose(fp);
lua_pushboolean(L, TRUE);
return 1;
}
}
else /* Read */
{
struct stat st;
if( ! stat(fn, &st) )
{
fp = fopen(fn, "rb");
if(fp)
{
luaL_Buffer b;
char* data = luaL_buffinitsize(L,&b,st.st_size);
fread(data, st.st_size, 1, fp);
fclose(fp);
luaL_addsize(&b, st.st_size);
luaL_pushresult(&b);
return 1;
}
}
}
/* Failed: return nil,error message */
lua_pushnil(L);
lua_pushliteral(L,"OOPS");
return 2;
}
/* Ex 4: Add an embedded ZIP file (read only file system)
The C code below was generated as follows:
echo hello > hello.txt
zip hello.zip hello.txt
bin2c -z hello hello.zip hello.c
Ref bin2c: https://realtimelogic.com/downloads/bin2c/
The content from hello.c can be found below:
*/
/* The C array below and the two following functions were copied from
* the generated file hello.zip.
*/
static const U8 cspPages[] = {
(U8)0x50,(U8)0x4B,(U8)0x03,(U8)0x04,(U8)0x14,(U8)0x00,(U8)0x00,(U8)0x00
,(U8)0x00,(U8)0x00,(U8)0x5C,(U8)0x75,(U8)0x50,(U8)0x59,(U8)0x86,(U8)0xA6
,(U8)0x10,(U8)0x36,(U8)0x05,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x05,(U8)0x00
,(U8)0x00,(U8)0x00,(U8)0x09,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x68,(U8)0x65
,(U8)0x6C,(U8)0x6C,(U8)0x6F,(U8)0x2E,(U8)0x74,(U8)0x78,(U8)0x74,(U8)0x68
,(U8)0x65,(U8)0x6C,(U8)0x6C,(U8)0x6F,(U8)0x50,(U8)0x4B,(U8)0x01,(U8)0x02
,(U8)0x14,(U8)0x00,(U8)0x14,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x00
,(U8)0x5C,(U8)0x75,(U8)0x50,(U8)0x59,(U8)0x86,(U8)0xA6,(U8)0x10,(U8)0x36
,(U8)0x05,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x05,(U8)0x00,(U8)0x00,(U8)0x00
,(U8)0x09,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x00
,(U8)0x00,(U8)0x00,(U8)0x20,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x00
,(U8)0x00,(U8)0x00,(U8)0x68,(U8)0x65,(U8)0x6C,(U8)0x6C,(U8)0x6F,(U8)0x2E
,(U8)0x74,(U8)0x78,(U8)0x74,(U8)0x50,(U8)0x4B,(U8)0x05,(U8)0x06,(U8)0x00
,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x01,(U8)0x00,(U8)0x01,(U8)0x00,(U8)0x37
,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x2C,(U8)0x00,(U8)0x00,(U8)0x00,(U8)0x00
,(U8)0x00
};
static int
DataZipReader_diskRead(
CspReader* o,void* data,U32 offset,U32 size,int blockStart)
{
(void)o;
(void)blockStart;
memcpy(data, cspPages+offset, size);
return 0;
}
ZipReader* hello(void)
{
static ZipReader zipReader;
ZipReader_constructor(&zipReader,DataZipReader_diskRead,sizeof(cspPages));
CspReader_setIsValid(&zipReader);
return &zipReader;
}
/*************************** END Ex 4 **************************/
/*
Ex 5: This example shows how to send the SNTP event to Xedge. A
device without a clock must use an SNTP client to update the system
time. See the following for an introduction:
https://realtimelogic.com/ba/examples/xedge/readme.html#time
The SNTP client C code may include an event mechanism for the C
code to use; however, in the following example, we wait for the time
to be set in a thread. When the time is set, the code calls the Lua
'_XedgeEvent' function with the argument sntp to signal that we have
the correct time.
The code below uses the LThreadMgr object:
https://realtimelogic.com/ba/doc/en/C/reference/html/md_en_C_md_LuaBindings.html#fullsolution
*/
/* This callback is called by one of the threads managed by LThreadMgr
* when a job is taken off the queue and executed. The callback
* attempts to find the global Lua function '_XedgeEvent', and if the
* function is found, it will be executed as follows: _XedgeEvent("sntp")
*/
static void executeXedgeEvent(ThreadJob* job, int msgh, LThreadMgr* mgr)
{
lua_State* L = job->Lt;
lua_pushglobaltable(L); /* _G */
lua_getfield(L, -1, "_XedgeEvent");
if(lua_isfunction(L, -1)) /* Do we have _G._XedgeEvent */
{
/* Call _XedgeEvent("sntp") */
lua_pushstring(L,"sntp"); /* Arg */
lua_pcall(L, 1, 0, msgh); /* one arg, no return value */
}
}
/* Thread started by xedgeOpenAUX() */
static void checkTimeThread(Thread* th)
{
ThreadMutex* soDispMutex = HttpServer_getMutex(ltMgr.server);
(void)th; /* not used */
/* Use the compile time macros for date and time and convert the
* date/time to a value that can be used by function baParseDate
*/
const char* d = __DATE__;
char buf[50];
if (!(basnprintf(buf, sizeof(buf), "Mon, %c%c %c%c%c %s %s GMT",
d[4] == ' ' ? '0' : d[4],d[5], d[0],d[1],d[2], d + 7, __TIME__) < 0))
{
BaTime compileT = baParseDate(buf);
if(compileT) /* If OK: Seconds since 1970 */
{
compileT -= 24*60*60; /* Give it one day for time zone adj. */
/* Wait for time to be updated by NTP */
while(baGetUnixTime() < compileT)
Thread_sleep(500);
/* Initiate executing the Lua func _XedgeEvent("sntp") */
ThreadJob* job=ThreadJob_lcreate(sizeof(ThreadJob), executeXedgeEvent);
if(!job)
ThreadMutex_set(soDispMutex);
LThreadMgr_run(&ltMgr, job);
ThreadMutex_release(soDispMutex);
}
}
/* Exit thread */
}
/*************************** END Ex 5 **************************/
/*
The function below is called by the Xedge startup code.
*/
int xedgeOpenAUX(XedgeOpenAUX* aux)
{
static const luaL_Reg ledReg[] = {
{"setLed", LED_setLed},
{"getLed", LED_getLed},
{NULL, NULL}
};
/* Ex1: Install the LED API as a global variable */
luaL_newlib(aux->L, ledReg);
lua_setglobal(aux->L, "LED");
/* Ex2: Install the optional config file handler. See function and comments above */
aux->xedgeCfgFile = xedgeCfgFile;
/* Ex 3. Add additional secrets for TPM key generation
Config TPM: https://realtimelogic.com/ba/examples/xedge/readme.html#security
TPM API: https://realtimelogic.com/ba/doc/en/lua/auxlua.html#TPM
When using the softTPM, in addition to the main secret you must
set in EncryptionKey.h, additional secrets can be added to the
logic in .config that calculates the pre-master key. At least
one key should be a unique ID specific to the device. It could be
the Ethernet MAC address or any other unique ID. In the ESP32
reference port, we use "eFUSE Registers". Note that you cannot
use random generated data, as secret(s) must be persistent.
*/
#ifndef NO_ENCRYPTIONKEY
const U8 secret[] = {'Q','W','E','R','T','Y'}; /* NO TRAILING ZERO EXAMPLE */
/* Send secret to Lua code */
aux->addSecret(aux, FALSE, secret, sizeof(secret));
lua_pushliteral(aux->L,"You can add any number of secrets");
aux->addSecret(aux, FALSE, 0, 0);
/* Set unique (TRUE) when adding a device unique key such as the MAC address
* aux->addSecret(aux, TRUE, deviceMacAddr, 6);
*/
#endif
/* Ex 4: Add an embedded ZIP file (read only file system)
The content of hello.txt (see embedded zip file below) can be
printed using Lua as follows:
print(ba.mkio"hello-handle":open"hello.txt":read"a")
*/
balua_installZIO(aux->L, "hello-handle", hello());
/* Ex 5 */
static Thread checkTime;
Thread_constructor(&checkTime, checkTimeThread, ThreadPrioNormal, 2000);
Thread_start(&checkTime);
return 0; /* OK */
}
@ FE_MALLOC
error code 2 = size needed
Definition: BaErrorCodes.h:50
#define baFatalE(ecode1, ecode2)
Report a fatal condition with the current source file and line.
Definition: BaErrorCodes.h:148
BA_API int basnprintf(char *buf, int len, const char *fmt,...)
Format into a bounded caller-owned buffer.
#define CspReader_setIsValid(o)
Mark the implementation initialized after opening/checking its data.
Definition: CspRunTm.h:175
BA_API void ZipReader_constructor(ZipReader *o, CspReader_Read r, U32 zipFileSize)
Initialize a reader interface; no ZIP data is read yet.
BA_API void balua_installZIO(lua_State *L, const char *name, struct ZipReader *reader)
Install a Zip I/O interface into the Lua environment.
S64 BaTime
An arithmetic type representing calendar time with epoch of 1970-01-01 00:00:00 UTC,...
Definition: GenPrimT.h:103
BA_API BaTime baParseDate(const char *str)
Parse an HTTP date string as UTC.
uint32_t U32
Unsigned 32-bit integer.
Definition: GenPrimT.h:93
uint8_t U8
Unsigned 8-bit integer.
Definition: GenPrimT.h:89
Abstract interface class for reading the "dat" file generated by HttpLink.
Definition: CspRunTm.h:126
A simple thread class.
Definition: ThreadLib.h:261
Abstract interface class for reading a ZipFile.
Definition: ZipFileIterator.h:74

Using request/response in Lua Bindings

You may be porting an existing web application to the Barracuda App Server, or you may simply need the server's request or response object in your Lua binding. The following example shows how to write a Lua binding that can use the request and response objects.

static int LuaCmdEnv2C_printParams(lua_State *L)
{
HttpCommand* cmd=baluaENV_checkcmd(L, 1);
HttpRequest* r = &cmd->request;
HttpParameterIterator iter;
HttpParameterIterator_constructor(&iter, r);
for( ; HttpParameterIterator_hasMoreElements(&iter);
HttpParameterIterator_nextElement(&iter) )
{
printf("Key/Val: %s = %s\n",
HttpParameterIterator_getName(&iter),
HttpParameterIterator_getValue(&iter));
}
return 0; /* No return value */
}

This Lua binding uses function baluaENV_checkcmd for extracting the HttpCommand argument. In Lua the request/response objects are the same and represent the HttpCommand object.

Lua code can now call the function as follows:

LuaCmdEnv2C.printParams(request)
LuaCmdEnv2C.printParams(response) -- request = response

Notice the baluaENV prefix in function baluaENV_checkcmd. The ENV prefix means that the function requires a special environment defined for the Barracuda App Server. The environment is provided as a C Closure. We must set this environment when we register the Lua binding.

In the first example above, we used the macro luaL_newlib to register the Lua bindings in function installLua2C. This macro is defined as:

#define luaL_newlib(L,l) (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))

Notice that the macro passes in the value zero as the number of up-values to function luaL_setfuncs.

The macro luaL_newlib cannot be used for this example since we need to push the Barracuda App Server environment onto the stack and let function luaL_setfuncs set this environment for the Lua bindings. Instead of calling macro luaL_newlib, we do the following:

luaL_newlibtable(L,funcs); /* The table LuaCmdEnv2C */
balua_pushbatab(L); /* Push the BA ENV required by func baluaENV_checkcmd */
luaL_setfuncs(L,funcs,1); /* nup=1 (arg 3): the BA ENV */

Download Example Code

Download the source code for this example.